gsd-pi 2.65.0 → 2.66.0-dev.1b4e601
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/mcp-server.js +6 -2
- package/dist/resources/extensions/browser-tools/capture.js +20 -1
- package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +93 -0
- package/dist/resources/extensions/gsd/auto/finalize-timeout.js +2 -0
- package/dist/resources/extensions/gsd/auto/loop.js +2 -2
- package/dist/resources/extensions/gsd/auto/phases.js +48 -5
- package/dist/resources/extensions/gsd/auto/run-unit.js +13 -2
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto/types.js +2 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +2 -1
- package/dist/resources/extensions/gsd/auto-dispatch.js +99 -9
- package/dist/resources/extensions/gsd/auto-model-selection.js +7 -5
- package/dist/resources/extensions/gsd/auto-post-unit.js +17 -6
- package/dist/resources/extensions/gsd/auto-prompts.js +24 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +40 -22
- package/dist/resources/extensions/gsd/auto-start.js +175 -12
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +10 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +29 -7
- package/dist/resources/extensions/gsd/auto.js +21 -15
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +17 -4
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +10 -0
- package/dist/resources/extensions/gsd/bootstrap/query-tools.js +6 -4
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +5 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -3
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +3 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +31 -1
- package/dist/resources/extensions/gsd/commands/context.js +8 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +23 -2
- package/dist/resources/extensions/gsd/commands-extensions.js +1 -1
- package/dist/resources/extensions/gsd/config-overlay.js +312 -0
- package/dist/resources/extensions/gsd/db-writer.js +13 -3
- package/dist/resources/extensions/gsd/detection.js +1 -1
- package/dist/resources/extensions/gsd/dispatch-guard.js +2 -1
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -0
- package/dist/resources/extensions/gsd/doctor.js +2 -1
- package/dist/resources/extensions/gsd/files.js +17 -0
- package/dist/resources/extensions/gsd/gitignore.js +1 -0
- package/dist/resources/extensions/gsd/gsd-db.js +47 -4
- package/dist/resources/extensions/gsd/guided-flow.js +220 -29
- package/dist/resources/extensions/gsd/index.js +1 -1
- package/dist/resources/extensions/gsd/json-persistence.js +5 -2
- package/dist/resources/extensions/gsd/md-importer.js +14 -7
- package/dist/resources/extensions/gsd/notification-overlay.js +1 -1
- package/dist/resources/extensions/gsd/notification-widget.js +2 -1
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +1 -1
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +17 -11
- package/dist/resources/extensions/gsd/pre-execution-checks.js +26 -5
- package/dist/resources/extensions/gsd/preferences-types.js +3 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +45 -1
- package/dist/resources/extensions/gsd/preferences.js +9 -2
- package/dist/resources/extensions/gsd/preparation.js +1092 -0
- package/dist/resources/extensions/gsd/prompt-validation.js +67 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +3 -3
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss-prepared.md +424 -0
- package/dist/resources/extensions/gsd/prompts/discuss.md +2 -0
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +6 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -4
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +23 -0
- package/dist/resources/extensions/gsd/prompts/queue.md +2 -0
- package/dist/resources/extensions/gsd/prompts/rethink.md +2 -1
- package/dist/resources/extensions/gsd/prompts/system.md +2 -2
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +56 -23
- package/dist/resources/extensions/gsd/quick.js +19 -15
- package/dist/resources/extensions/gsd/reactive-graph.js +12 -0
- package/dist/resources/extensions/gsd/roadmap-slices.js +24 -5
- package/dist/resources/extensions/gsd/safety/content-validator.js +3 -3
- package/dist/resources/extensions/gsd/session-lock.js +23 -1
- package/dist/resources/extensions/gsd/state.js +115 -28
- package/dist/resources/extensions/gsd/templates/context-enhanced.md +138 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +15 -3
- package/dist/resources/extensions/gsd/tools/complete-slice.js +27 -6
- package/dist/resources/extensions/gsd/tools/complete-task.js +31 -7
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +7 -5
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +5 -2
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +119 -0
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +30 -0
- package/dist/resources/extensions/gsd/tools/reopen-task.js +18 -0
- package/dist/resources/extensions/gsd/triage-resolution.js +33 -16
- package/dist/resources/extensions/gsd/undo.js +3 -2
- package/dist/resources/extensions/gsd/workflow-events.js +1 -0
- package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
- package/dist/resources/extensions/gsd/workflow-projections.js +7 -9
- package/dist/resources/extensions/gsd/workflow-reconcile.js +100 -9
- package/dist/resources/extensions/gsd/workflow-templates.js +11 -2
- package/dist/resources/extensions/gsd/worktree-manager.js +5 -2
- package/dist/resources/extensions/gsd/worktree.js +9 -0
- package/dist/resources/extensions/shared/interview-ui.js +1 -1
- package/dist/resources/extensions/subagent/agents.js +19 -5
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
- package/dist/web/standalone/.next/build-manifest.json +4 -4
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +19 -19
- package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
- package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/6502.8874bcae249c02e1.js +9 -0
- package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-a1c1e452c6b32d04.js → webpack-9fed74684e1c5bb1.js} +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js +30 -19
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js +51 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +9 -9
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +10 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +20 -5
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +15 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +18 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.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 +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/retry-handler.test.ts +80 -0
- package/packages/pi-coding-agent/src/core/retry-handler.ts +37 -25
- package/packages/pi-coding-agent/src/core/sdk.ts +9 -9
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +10 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +20 -4
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +27 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +16 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +5 -0
- package/packages/pi-tui/dist/components/image.d.ts +2 -0
- package/packages/pi-tui/dist/components/image.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/image.js +4 -0
- package/packages/pi-tui/dist/components/image.js.map +1 -1
- package/packages/pi-tui/dist/components/image.test.d.ts +6 -0
- package/packages/pi-tui/dist/components/image.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/components/image.test.js +32 -0
- package/packages/pi-tui/dist/components/image.test.js.map +1 -0
- package/packages/pi-tui/dist/tui.d.ts +1 -0
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +8 -2
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/components/image.test.ts +36 -0
- package/packages/pi-tui/src/components/image.ts +5 -0
- package/packages/pi-tui/src/tui.ts +8 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/browser-tools/capture.ts +19 -1
- package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +93 -0
- package/src/resources/extensions/gsd/auto/finalize-timeout.ts +3 -0
- package/src/resources/extensions/gsd/auto/loop.ts +2 -2
- package/src/resources/extensions/gsd/auto/phases.ts +68 -3
- package/src/resources/extensions/gsd/auto/run-unit.ts +12 -2
- package/src/resources/extensions/gsd/auto/session.ts +4 -0
- package/src/resources/extensions/gsd/auto/types.ts +5 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +2 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +110 -9
- package/src/resources/extensions/gsd/auto-model-selection.ts +7 -5
- package/src/resources/extensions/gsd/auto-post-unit.ts +16 -6
- package/src/resources/extensions/gsd/auto-prompts.ts +31 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +29 -23
- package/src/resources/extensions/gsd/auto-start.ts +188 -10
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +10 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +28 -7
- package/src/resources/extensions/gsd/auto.ts +19 -8
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +16 -4
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +10 -0
- package/src/resources/extensions/gsd/bootstrap/query-tools.ts +5 -4
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +11 -3
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +3 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +36 -1
- package/src/resources/extensions/gsd/commands/context.ts +7 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +26 -2
- package/src/resources/extensions/gsd/commands-extensions.ts +1 -1
- package/src/resources/extensions/gsd/config-overlay.ts +331 -0
- package/src/resources/extensions/gsd/db-writer.ts +11 -3
- package/src/resources/extensions/gsd/detection.ts +1 -1
- package/src/resources/extensions/gsd/dispatch-guard.ts +2 -1
- package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -0
- package/src/resources/extensions/gsd/doctor.ts +2 -1
- package/src/resources/extensions/gsd/files.ts +19 -0
- package/src/resources/extensions/gsd/gitignore.ts +1 -0
- package/src/resources/extensions/gsd/gsd-db.ts +46 -4
- package/src/resources/extensions/gsd/guided-flow.ts +254 -30
- package/src/resources/extensions/gsd/index.ts +1 -0
- package/src/resources/extensions/gsd/json-persistence.ts +6 -3
- package/src/resources/extensions/gsd/md-importer.ts +13 -6
- package/src/resources/extensions/gsd/notification-overlay.ts +1 -1
- package/src/resources/extensions/gsd/notification-widget.ts +2 -1
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +1 -1
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +19 -11
- package/src/resources/extensions/gsd/pre-execution-checks.ts +32 -7
- package/src/resources/extensions/gsd/preferences-types.ts +25 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +45 -1
- package/src/resources/extensions/gsd/preferences.ts +9 -2
- package/src/resources/extensions/gsd/preparation.ts +1419 -0
- package/src/resources/extensions/gsd/prompt-validation.ts +88 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +3 -3
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss-prepared.md +424 -0
- package/src/resources/extensions/gsd/prompts/discuss.md +2 -0
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +6 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -4
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +23 -0
- package/src/resources/extensions/gsd/prompts/queue.md +2 -0
- package/src/resources/extensions/gsd/prompts/rethink.md +2 -1
- package/src/resources/extensions/gsd/prompts/system.md +2 -2
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +56 -23
- package/src/resources/extensions/gsd/quick.ts +20 -15
- package/src/resources/extensions/gsd/reactive-graph.ts +18 -0
- package/src/resources/extensions/gsd/roadmap-slices.ts +21 -5
- package/src/resources/extensions/gsd/safety/content-validator.ts +3 -3
- package/src/resources/extensions/gsd/session-lock.ts +17 -1
- package/src/resources/extensions/gsd/state.ts +115 -26
- package/src/resources/extensions/gsd/templates/context-enhanced.md +138 -0
- package/src/resources/extensions/gsd/tests/adversarial-review-fixes.test.ts +223 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +33 -2
- package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +41 -0
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +72 -0
- package/src/resources/extensions/gsd/tests/complete-task-normalize-lists.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/defer-milestone-stamp.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/discuss-incremental-persistence.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard-closed-status.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/error-success-mask.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/frontmatter-parse-noise.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/guided-flow-state-rebuild.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +11 -9
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +28 -30
- package/src/resources/extensions/gsd/tests/integration/test-isolation.ts +53 -0
- package/src/resources/extensions/gsd/tests/integration-prepared-discussion.test.ts +525 -0
- package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +11 -10
- package/src/resources/extensions/gsd/tests/needs-remediation-revalidation.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/note-captures-executed.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +189 -0
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +77 -0
- package/src/resources/extensions/gsd/tests/phantom-ghost-detection.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/phantom-milestone-default-queued.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +284 -20
- package/src/resources/extensions/gsd/tests/pre-execution-fail-closed.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/preparation.test.ts +1211 -0
- package/src/resources/extensions/gsd/tests/project-root-cwd-crash.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/projection-no-plan-overwrite.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/prompt-builder.test.ts +669 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +7 -4
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts +4 -5
- package/src/resources/extensions/gsd/tests/run-uat-replay-cap.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/show-config-command.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/skip-slice-state-rebuild.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/slice-sequence-insert.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/stale-lockfile-recovery.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +147 -0
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +13 -0
- package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +6 -7
- package/src/resources/extensions/gsd/tests/status-db-open.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/subagent-agent-discovery.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/symlink-extension-discovery.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +29 -1
- package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +3 -4
- package/src/resources/extensions/gsd/tests/verification-operational-gate.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +89 -0
- package/src/resources/extensions/gsd/tests/wave1-critical-regressions.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/wave2-events-regressions.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/wave3-session-regressions.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/wave4-write-safety-regressions.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/wave5-consistency-regressions.test.ts +165 -0
- package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/workflow-logger-audit.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +16 -17
- package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +13 -9
- package/src/resources/extensions/gsd/tests/worktree.test.ts +26 -9
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +127 -2
- package/src/resources/extensions/gsd/tests/zero-slice-roadmap-guided.test.ts +19 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +13 -3
- package/src/resources/extensions/gsd/tools/complete-slice.ts +26 -6
- package/src/resources/extensions/gsd/tools/complete-task.ts +29 -7
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +11 -9
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +5 -2
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +152 -0
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +27 -0
- package/src/resources/extensions/gsd/tools/reopen-task.ts +17 -0
- package/src/resources/extensions/gsd/triage-resolution.ts +37 -17
- package/src/resources/extensions/gsd/types.ts +4 -0
- package/src/resources/extensions/gsd/undo.ts +3 -2
- package/src/resources/extensions/gsd/workflow-events.ts +5 -3
- package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
- package/src/resources/extensions/gsd/workflow-projections.ts +7 -8
- package/src/resources/extensions/gsd/workflow-reconcile.ts +109 -8
- package/src/resources/extensions/gsd/workflow-templates.ts +11 -2
- package/src/resources/extensions/gsd/worktree-manager.ts +4 -2
- package/src/resources/extensions/gsd/worktree.ts +10 -0
- package/src/resources/extensions/shared/interview-ui.ts +1 -1
- package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +8 -10
- package/src/resources/extensions/subagent/agents.ts +30 -6
- package/dist/web/standalone/.next/static/chunks/6502.7593d7797a4b3999.js +0 -9
- package/dist/web/standalone/.next/static/chunks/app/page-62be3b5fa91e4c8f.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
- /package/dist/web/standalone/.next/static/{MRM3OSYIAa4HMDqVGQ9nt → fcV2z87tmOazTEreFWNdG}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{MRM3OSYIAa4HMDqVGQ9nt → fcV2z87tmOazTEreFWNdG}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,1211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for GSD Preparation — codebase analysis and brief generation.
|
|
3
|
+
*
|
|
4
|
+
* Exercises the pure preparation functions:
|
|
5
|
+
* - analyzeCodebase() with various project layouts
|
|
6
|
+
* - formatCodebaseBrief() output format and truncation
|
|
7
|
+
* - Pattern extraction from sampled files
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import test from "node:test";
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { mkdirSync, writeFileSync, rmSync, existsSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import {
|
|
16
|
+
analyzeCodebase,
|
|
17
|
+
formatCodebaseBrief,
|
|
18
|
+
aggregatePriorContext,
|
|
19
|
+
formatPriorContextBrief,
|
|
20
|
+
researchEcosystem,
|
|
21
|
+
formatEcosystemBrief,
|
|
22
|
+
runPreparation,
|
|
23
|
+
type CodebaseBrief,
|
|
24
|
+
type PriorContextBrief,
|
|
25
|
+
type EcosystemBrief,
|
|
26
|
+
type EcosystemFinding,
|
|
27
|
+
type PreparationUIContext,
|
|
28
|
+
type PreparationPreferences,
|
|
29
|
+
type PreparationResult,
|
|
30
|
+
} from "../preparation.ts";
|
|
31
|
+
import { PROJECT_FILES } from "../detection.ts";
|
|
32
|
+
|
|
33
|
+
// ─── Test Helpers ───────────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
function makeTempDir(prefix: string): string {
|
|
36
|
+
const dir = join(
|
|
37
|
+
tmpdir(),
|
|
38
|
+
`gsd-preparation-test-${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
39
|
+
);
|
|
40
|
+
mkdirSync(dir, { recursive: true });
|
|
41
|
+
return dir;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function cleanup(dir: string): void {
|
|
45
|
+
try {
|
|
46
|
+
rmSync(dir, { recursive: true, force: true });
|
|
47
|
+
} catch {
|
|
48
|
+
// best-effort
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ─── analyzeCodebase ────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
test("analyzeCodebase: empty directory returns valid brief structure", async (t) => {
|
|
55
|
+
const dir = makeTempDir("empty");
|
|
56
|
+
t.after(() => cleanup(dir));
|
|
57
|
+
|
|
58
|
+
const brief = await analyzeCodebase(dir);
|
|
59
|
+
|
|
60
|
+
assert.ok(brief, "should return a brief");
|
|
61
|
+
assert.ok(brief.techStack, "should have techStack");
|
|
62
|
+
assert.ok(brief.moduleStructure, "should have moduleStructure");
|
|
63
|
+
assert.ok(brief.patterns, "should have patterns");
|
|
64
|
+
assert.ok(Array.isArray(brief.sampledFiles), "should have sampledFiles array");
|
|
65
|
+
assert.equal(brief.sampledFiles.length, 0, "empty dir should have no sampled files");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("analyzeCodebase: detects package.json in PROJECT_FILES", async (t) => {
|
|
69
|
+
const dir = makeTempDir("pkg-json");
|
|
70
|
+
t.after(() => cleanup(dir));
|
|
71
|
+
|
|
72
|
+
writeFileSync(
|
|
73
|
+
join(dir, "package.json"),
|
|
74
|
+
JSON.stringify({ name: "test-project", scripts: { test: "jest" } }),
|
|
75
|
+
"utf-8",
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const brief = await analyzeCodebase(dir);
|
|
79
|
+
|
|
80
|
+
assert.ok(
|
|
81
|
+
brief.techStack.detectedFiles.includes("package.json"),
|
|
82
|
+
"should detect package.json",
|
|
83
|
+
);
|
|
84
|
+
assert.equal(brief.techStack.primaryLanguage, "javascript/typescript");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("analyzeCodebase: detects module structure from src/ directory", async (t) => {
|
|
88
|
+
const dir = makeTempDir("module-struct");
|
|
89
|
+
t.after(() => cleanup(dir));
|
|
90
|
+
|
|
91
|
+
// Create src directory with subdirs
|
|
92
|
+
mkdirSync(join(dir, "src", "components"), { recursive: true });
|
|
93
|
+
mkdirSync(join(dir, "src", "utils"), { recursive: true });
|
|
94
|
+
mkdirSync(join(dir, "src", "hooks"), { recursive: true });
|
|
95
|
+
mkdirSync(join(dir, "test"), { recursive: true });
|
|
96
|
+
|
|
97
|
+
const brief = await analyzeCodebase(dir);
|
|
98
|
+
|
|
99
|
+
assert.ok(
|
|
100
|
+
brief.moduleStructure.topLevelDirs.includes("src"),
|
|
101
|
+
"should detect src as top-level dir",
|
|
102
|
+
);
|
|
103
|
+
assert.ok(
|
|
104
|
+
brief.moduleStructure.topLevelDirs.includes("test"),
|
|
105
|
+
"should detect test as top-level dir",
|
|
106
|
+
);
|
|
107
|
+
assert.ok(
|
|
108
|
+
brief.moduleStructure.srcSubdirs.includes("components"),
|
|
109
|
+
"should detect components subdir",
|
|
110
|
+
);
|
|
111
|
+
assert.ok(
|
|
112
|
+
brief.moduleStructure.srcSubdirs.includes("utils"),
|
|
113
|
+
"should detect utils subdir",
|
|
114
|
+
);
|
|
115
|
+
assert.ok(
|
|
116
|
+
brief.moduleStructure.srcSubdirs.includes("hooks"),
|
|
117
|
+
"should detect hooks subdir",
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("analyzeCodebase: samples TypeScript files from src/", async (t) => {
|
|
122
|
+
const dir = makeTempDir("sample-ts");
|
|
123
|
+
t.after(() => cleanup(dir));
|
|
124
|
+
|
|
125
|
+
// Create src directory with TypeScript files
|
|
126
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
127
|
+
writeFileSync(
|
|
128
|
+
join(dir, "src", "index.ts"),
|
|
129
|
+
`export async function main() { await fetch('/api'); }`,
|
|
130
|
+
"utf-8",
|
|
131
|
+
);
|
|
132
|
+
writeFileSync(
|
|
133
|
+
join(dir, "src", "utils.ts"),
|
|
134
|
+
`export function helper() { try { return 1; } catch (e) { throw e; } }`,
|
|
135
|
+
"utf-8",
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const brief = await analyzeCodebase(dir);
|
|
139
|
+
|
|
140
|
+
assert.ok(brief.sampledFiles.length > 0, "should sample at least one file");
|
|
141
|
+
assert.ok(
|
|
142
|
+
brief.sampledFiles.some((f) => f.startsWith("src/")),
|
|
143
|
+
"should prefer src/ files",
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("analyzeCodebase: excludes test files from sampling", async (t) => {
|
|
148
|
+
const dir = makeTempDir("exclude-tests");
|
|
149
|
+
t.after(() => cleanup(dir));
|
|
150
|
+
|
|
151
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
152
|
+
writeFileSync(join(dir, "src", "index.ts"), `export const x = 1;`, "utf-8");
|
|
153
|
+
writeFileSync(
|
|
154
|
+
join(dir, "src", "index.test.ts"),
|
|
155
|
+
`import test from 'node:test'; test('x', () => {});`,
|
|
156
|
+
"utf-8",
|
|
157
|
+
);
|
|
158
|
+
writeFileSync(
|
|
159
|
+
join(dir, "src", "utils.spec.ts"),
|
|
160
|
+
`describe('utils', () => { it('works', () => {}); });`,
|
|
161
|
+
"utf-8",
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const brief = await analyzeCodebase(dir);
|
|
165
|
+
|
|
166
|
+
// Should only have index.ts, not test/spec files
|
|
167
|
+
for (const file of brief.sampledFiles) {
|
|
168
|
+
assert.ok(!file.endsWith(".test.ts"), `should not sample ${file}`);
|
|
169
|
+
assert.ok(!file.endsWith(".spec.ts"), `should not sample ${file}`);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("analyzeCodebase: excludes node_modules from sampling", async (t) => {
|
|
174
|
+
const dir = makeTempDir("exclude-nm");
|
|
175
|
+
t.after(() => cleanup(dir));
|
|
176
|
+
|
|
177
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
178
|
+
mkdirSync(join(dir, "node_modules", "some-pkg"), { recursive: true });
|
|
179
|
+
writeFileSync(join(dir, "src", "index.ts"), `export const x = 1;`, "utf-8");
|
|
180
|
+
writeFileSync(
|
|
181
|
+
join(dir, "node_modules", "some-pkg", "index.js"),
|
|
182
|
+
`module.exports = {};`,
|
|
183
|
+
"utf-8",
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const brief = await analyzeCodebase(dir);
|
|
187
|
+
|
|
188
|
+
for (const file of brief.sampledFiles) {
|
|
189
|
+
assert.ok(!file.includes("node_modules"), `should not sample ${file}`);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("analyzeCodebase: extracts async/await pattern", async (t) => {
|
|
194
|
+
const dir = makeTempDir("async-await");
|
|
195
|
+
t.after(() => cleanup(dir));
|
|
196
|
+
|
|
197
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
198
|
+
writeFileSync(
|
|
199
|
+
join(dir, "src", "api.ts"),
|
|
200
|
+
`
|
|
201
|
+
export async function fetchData() {
|
|
202
|
+
const res = await fetch('/api');
|
|
203
|
+
const data = await res.json();
|
|
204
|
+
return data;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export async function saveData(data: any) {
|
|
208
|
+
await fetch('/api', { method: 'POST', body: JSON.stringify(data) });
|
|
209
|
+
}
|
|
210
|
+
`,
|
|
211
|
+
"utf-8",
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
const brief = await analyzeCodebase(dir);
|
|
215
|
+
|
|
216
|
+
assert.equal(
|
|
217
|
+
brief.patterns.asyncStyle,
|
|
218
|
+
"async/await",
|
|
219
|
+
"should detect async/await as primary style",
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("analyzeCodebase: extracts try/catch error handling", async (t) => {
|
|
224
|
+
const dir = makeTempDir("try-catch");
|
|
225
|
+
t.after(() => cleanup(dir));
|
|
226
|
+
|
|
227
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
228
|
+
writeFileSync(
|
|
229
|
+
join(dir, "src", "handler.ts"),
|
|
230
|
+
`
|
|
231
|
+
export function handleError() {
|
|
232
|
+
try {
|
|
233
|
+
doSomething();
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.error(error);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function anotherHandler() {
|
|
240
|
+
try {
|
|
241
|
+
doOther();
|
|
242
|
+
} catch (e) {
|
|
243
|
+
throw new Error('wrapped');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
`,
|
|
247
|
+
"utf-8",
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const brief = await analyzeCodebase(dir);
|
|
251
|
+
|
|
252
|
+
assert.equal(
|
|
253
|
+
brief.patterns.errorHandling,
|
|
254
|
+
"try/catch",
|
|
255
|
+
"should detect try/catch as primary error handling",
|
|
256
|
+
);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("analyzeCodebase: extracts camelCase naming convention", async (t) => {
|
|
260
|
+
const dir = makeTempDir("camel-case");
|
|
261
|
+
t.after(() => cleanup(dir));
|
|
262
|
+
|
|
263
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
264
|
+
writeFileSync(
|
|
265
|
+
join(dir, "src", "utils.ts"),
|
|
266
|
+
`
|
|
267
|
+
export function getUserById(userId: string) {
|
|
268
|
+
return fetchUser(userId);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function calculateTotalPrice(itemPrices: number[]) {
|
|
272
|
+
return itemPrices.reduce((a, b) => a + b, 0);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function formatDisplayName(firstName: string, lastName: string) {
|
|
276
|
+
return \`\${firstName} \${lastName}\`;
|
|
277
|
+
}
|
|
278
|
+
`,
|
|
279
|
+
"utf-8",
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
const brief = await analyzeCodebase(dir);
|
|
283
|
+
|
|
284
|
+
// camelCase should be detected (getUserById, userId, fetchUser, etc.)
|
|
285
|
+
assert.ok(
|
|
286
|
+
brief.patterns.namingConvention === "camelCase" || brief.patterns.namingConvention === "mixed",
|
|
287
|
+
`should detect camelCase or mixed, got ${brief.patterns.namingConvention}`,
|
|
288
|
+
);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("analyzeCodebase: gracefully handles empty directories", async (t) => {
|
|
292
|
+
const dir = makeTempDir("empty-src");
|
|
293
|
+
t.after(() => cleanup(dir));
|
|
294
|
+
|
|
295
|
+
// Create empty src directory
|
|
296
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
297
|
+
|
|
298
|
+
const brief = await analyzeCodebase(dir);
|
|
299
|
+
|
|
300
|
+
// Should not throw, should return valid structure
|
|
301
|
+
assert.ok(brief.patterns, "should have patterns");
|
|
302
|
+
assert.equal(brief.patterns.asyncStyle, "unknown", "should return unknown for empty");
|
|
303
|
+
assert.equal(brief.patterns.errorHandling, "unknown", "should return unknown for empty");
|
|
304
|
+
assert.equal(brief.patterns.namingConvention, "unknown", "should return unknown for empty");
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("analyzeCodebase: returns unknown for unrecognized language patterns (Ruby)", async (t) => {
|
|
308
|
+
// Ruby is detected by LANGUAGE_MAP but not in LANGUAGE_PATTERNS registry
|
|
309
|
+
// This tests the graceful fallback behavior: naming convention still works,
|
|
310
|
+
// but language-specific patterns (async/error) should return "unknown"
|
|
311
|
+
const dir = makeTempDir("ruby-project");
|
|
312
|
+
t.after(() => cleanup(dir));
|
|
313
|
+
|
|
314
|
+
// Create a Ruby project with Gemfile (detected as "ruby" in LANGUAGE_MAP)
|
|
315
|
+
writeFileSync(join(dir, "Gemfile"), `source "https://rubygems.org"\ngem "rails"`, "utf-8");
|
|
316
|
+
|
|
317
|
+
// Add a Ruby file with patterns that would match JS/TS regexes incorrectly
|
|
318
|
+
mkdirSync(join(dir, "lib"), { recursive: true });
|
|
319
|
+
writeFileSync(
|
|
320
|
+
join(dir, "lib", "service.rb"),
|
|
321
|
+
`
|
|
322
|
+
class UserService
|
|
323
|
+
def fetch_user(user_id)
|
|
324
|
+
user = User.find(user_id)
|
|
325
|
+
user
|
|
326
|
+
rescue ActiveRecord::RecordNotFound => e
|
|
327
|
+
Rails.logger.error("User not found: #{e.message}")
|
|
328
|
+
nil
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def async_task(&block)
|
|
332
|
+
# Ruby doesn't have async/await but has yield and blocks
|
|
333
|
+
Thread.new { yield }
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
`,
|
|
337
|
+
"utf-8",
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
const brief = await analyzeCodebase(dir);
|
|
341
|
+
|
|
342
|
+
// Language should be detected as Ruby
|
|
343
|
+
assert.equal(brief.techStack.primaryLanguage, "ruby", "should detect ruby from Gemfile");
|
|
344
|
+
|
|
345
|
+
// Language-specific patterns should return "unknown" (not JS/TS patterns)
|
|
346
|
+
assert.equal(
|
|
347
|
+
brief.patterns.asyncStyle,
|
|
348
|
+
"unknown",
|
|
349
|
+
"should return unknown for async style in unrecognized language",
|
|
350
|
+
);
|
|
351
|
+
assert.equal(
|
|
352
|
+
brief.patterns.errorHandling,
|
|
353
|
+
"unknown",
|
|
354
|
+
"should return unknown for error handling in unrecognized language",
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
// But naming convention detection should still work (it's universal)
|
|
358
|
+
// The Ruby code uses snake_case (fetch_user, user_id) and camelCase (UserService)
|
|
359
|
+
assert.ok(
|
|
360
|
+
brief.patterns.namingConvention !== "unknown",
|
|
361
|
+
"naming convention should still be detected for unrecognized languages",
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
// Evidence should explain why patterns aren't available
|
|
365
|
+
assert.ok(
|
|
366
|
+
brief.patterns.evidence.asyncStyle.some((e) => e.includes("not in pattern registry")),
|
|
367
|
+
"evidence should explain async style is not available",
|
|
368
|
+
);
|
|
369
|
+
assert.ok(
|
|
370
|
+
brief.patterns.evidence.errorHandling.some((e) => e.includes("not in pattern registry")),
|
|
371
|
+
"evidence should explain error handling is not available",
|
|
372
|
+
);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// ─── formatCodebaseBrief ────────────────────────────────────────────────────────
|
|
376
|
+
|
|
377
|
+
test("formatCodebaseBrief: produces markdown output", async (t) => {
|
|
378
|
+
const brief: CodebaseBrief = {
|
|
379
|
+
techStack: {
|
|
380
|
+
primaryLanguage: "javascript/typescript",
|
|
381
|
+
detectedFiles: ["package.json", "tsconfig.json"],
|
|
382
|
+
packageManager: "npm",
|
|
383
|
+
isMonorepo: false,
|
|
384
|
+
hasTests: true,
|
|
385
|
+
hasCI: true,
|
|
386
|
+
},
|
|
387
|
+
moduleStructure: {
|
|
388
|
+
topLevelDirs: ["src", "test"],
|
|
389
|
+
srcSubdirs: ["components", "utils"],
|
|
390
|
+
totalFilesSampled: 5,
|
|
391
|
+
},
|
|
392
|
+
patterns: {
|
|
393
|
+
asyncStyle: "async/await",
|
|
394
|
+
errorHandling: "try/catch",
|
|
395
|
+
namingConvention: "camelCase",
|
|
396
|
+
evidence: {
|
|
397
|
+
asyncStyle: ["src/api.ts: async/await (5 occurrences)"],
|
|
398
|
+
errorHandling: ["src/handler.ts: try/catch (3 occurrences)"],
|
|
399
|
+
namingConvention: ["camelCase: 50 occurrences"],
|
|
400
|
+
},
|
|
401
|
+
fileCounts: {
|
|
402
|
+
asyncAwait: 3,
|
|
403
|
+
promises: 0,
|
|
404
|
+
callbacks: 0,
|
|
405
|
+
tryCatch: 2,
|
|
406
|
+
errorCallbacks: 0,
|
|
407
|
+
resultTypes: 0,
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
sampledFiles: ["src/index.ts", "src/utils.ts"],
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
const formatted = formatCodebaseBrief(brief);
|
|
414
|
+
|
|
415
|
+
assert.ok(formatted.includes("## Tech Stack"), "should have Tech Stack section");
|
|
416
|
+
assert.ok(formatted.includes("## Module Structure"), "should have Module Structure section");
|
|
417
|
+
assert.ok(formatted.includes("## Code Patterns"), "should have Code Patterns section");
|
|
418
|
+
assert.ok(formatted.includes("javascript/typescript"), "should include language");
|
|
419
|
+
assert.ok(formatted.includes("npm"), "should include package manager");
|
|
420
|
+
assert.ok(formatted.includes("async/await"), "should include async style");
|
|
421
|
+
assert.ok(formatted.includes("try/catch"), "should include error handling");
|
|
422
|
+
assert.ok(formatted.includes("camelCase"), "should include naming convention");
|
|
423
|
+
assert.ok(formatted.includes("3 async/await files"), "should include file counts for async style");
|
|
424
|
+
assert.ok(formatted.includes("2 try/catch files"), "should include file counts for error handling");
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
test("formatCodebaseBrief: caps output at 3000 chars", async (t) => {
|
|
428
|
+
// Create a brief with many files to exceed the limit
|
|
429
|
+
const manyFiles = Array.from({ length: 100 }, (_, i) => `file-${i}.ts`);
|
|
430
|
+
|
|
431
|
+
const brief: CodebaseBrief = {
|
|
432
|
+
techStack: {
|
|
433
|
+
primaryLanguage: "javascript/typescript",
|
|
434
|
+
detectedFiles: manyFiles,
|
|
435
|
+
packageManager: "npm",
|
|
436
|
+
isMonorepo: false,
|
|
437
|
+
hasTests: true,
|
|
438
|
+
hasCI: true,
|
|
439
|
+
},
|
|
440
|
+
moduleStructure: {
|
|
441
|
+
topLevelDirs: Array.from({ length: 50 }, (_, i) => `dir-${i}`),
|
|
442
|
+
srcSubdirs: Array.from({ length: 50 }, (_, i) => `subdir-${i}`),
|
|
443
|
+
totalFilesSampled: 100,
|
|
444
|
+
},
|
|
445
|
+
patterns: {
|
|
446
|
+
asyncStyle: "async/await",
|
|
447
|
+
errorHandling: "try/catch",
|
|
448
|
+
namingConvention: "camelCase",
|
|
449
|
+
evidence: {
|
|
450
|
+
asyncStyle: manyFiles.map((f) => `${f}: async/await (10 occurrences)`),
|
|
451
|
+
errorHandling: manyFiles.map((f) => `${f}: try/catch (5 occurrences)`),
|
|
452
|
+
namingConvention: ["camelCase: 500 occurrences"],
|
|
453
|
+
},
|
|
454
|
+
fileCounts: {
|
|
455
|
+
asyncAwait: 50,
|
|
456
|
+
promises: 10,
|
|
457
|
+
callbacks: 5,
|
|
458
|
+
tryCatch: 30,
|
|
459
|
+
errorCallbacks: 5,
|
|
460
|
+
resultTypes: 0,
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
sampledFiles: manyFiles,
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
const formatted = formatCodebaseBrief(brief);
|
|
467
|
+
|
|
468
|
+
assert.ok(
|
|
469
|
+
formatted.length <= 3000,
|
|
470
|
+
`should cap at 3000 chars, got ${formatted.length}`,
|
|
471
|
+
);
|
|
472
|
+
if (formatted.length === 3000) {
|
|
473
|
+
assert.ok(formatted.endsWith("..."), "should end with ellipsis when truncated");
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
test("formatCodebaseBrief: handles minimal brief", async (t) => {
|
|
478
|
+
const brief: CodebaseBrief = {
|
|
479
|
+
techStack: {
|
|
480
|
+
primaryLanguage: undefined,
|
|
481
|
+
detectedFiles: [],
|
|
482
|
+
packageManager: undefined,
|
|
483
|
+
isMonorepo: false,
|
|
484
|
+
hasTests: false,
|
|
485
|
+
hasCI: false,
|
|
486
|
+
},
|
|
487
|
+
moduleStructure: {
|
|
488
|
+
topLevelDirs: [],
|
|
489
|
+
srcSubdirs: [],
|
|
490
|
+
totalFilesSampled: 0,
|
|
491
|
+
},
|
|
492
|
+
patterns: {
|
|
493
|
+
asyncStyle: "unknown",
|
|
494
|
+
errorHandling: "unknown",
|
|
495
|
+
namingConvention: "unknown",
|
|
496
|
+
evidence: {
|
|
497
|
+
asyncStyle: [],
|
|
498
|
+
errorHandling: [],
|
|
499
|
+
namingConvention: [],
|
|
500
|
+
},
|
|
501
|
+
fileCounts: {
|
|
502
|
+
asyncAwait: 0,
|
|
503
|
+
promises: 0,
|
|
504
|
+
callbacks: 0,
|
|
505
|
+
tryCatch: 0,
|
|
506
|
+
errorCallbacks: 0,
|
|
507
|
+
resultTypes: 0,
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
sampledFiles: [],
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
const formatted = formatCodebaseBrief(brief);
|
|
514
|
+
|
|
515
|
+
assert.ok(formatted.includes("## Tech Stack"), "should still have sections");
|
|
516
|
+
assert.ok(formatted.includes("**Monorepo:** No"), "should show monorepo status");
|
|
517
|
+
assert.ok(formatted.includes("unknown"), "should show unknown patterns");
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// ─── Integration: Brief includes PROJECT_FILES markers ──────────────────────────
|
|
521
|
+
|
|
522
|
+
test("analyzeCodebase: brief includes detected files from PROJECT_FILES", async (t) => {
|
|
523
|
+
const dir = makeTempDir("project-files");
|
|
524
|
+
t.after(() => cleanup(dir));
|
|
525
|
+
|
|
526
|
+
// Create several PROJECT_FILES markers
|
|
527
|
+
writeFileSync(join(dir, "package.json"), '{"name": "test"}', "utf-8");
|
|
528
|
+
writeFileSync(join(dir, "tsconfig.json"), '{}', "utf-8");
|
|
529
|
+
mkdirSync(join(dir, ".github", "workflows"), { recursive: true });
|
|
530
|
+
writeFileSync(
|
|
531
|
+
join(dir, ".github", "workflows", "ci.yml"),
|
|
532
|
+
"name: CI",
|
|
533
|
+
"utf-8",
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
const brief = await analyzeCodebase(dir);
|
|
537
|
+
|
|
538
|
+
assert.ok(
|
|
539
|
+
brief.techStack.detectedFiles.includes("package.json"),
|
|
540
|
+
"should detect package.json",
|
|
541
|
+
);
|
|
542
|
+
assert.ok(
|
|
543
|
+
brief.techStack.hasCI,
|
|
544
|
+
"should detect CI from .github/workflows",
|
|
545
|
+
);
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
test("analyzeCodebase: brief includes sampled file patterns", async (t) => {
|
|
549
|
+
const dir = makeTempDir("sampled-patterns");
|
|
550
|
+
t.after(() => cleanup(dir));
|
|
551
|
+
|
|
552
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
553
|
+
|
|
554
|
+
// Write files with distinct patterns
|
|
555
|
+
writeFileSync(
|
|
556
|
+
join(dir, "src", "async-heavy.ts"),
|
|
557
|
+
`
|
|
558
|
+
async function one() { await fetch('/a'); }
|
|
559
|
+
async function two() { await fetch('/b'); }
|
|
560
|
+
async function three() { await fetch('/c'); }
|
|
561
|
+
`,
|
|
562
|
+
"utf-8",
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
const brief = await analyzeCodebase(dir);
|
|
566
|
+
|
|
567
|
+
assert.ok(brief.sampledFiles.length > 0, "should have sampled files");
|
|
568
|
+
assert.ok(
|
|
569
|
+
brief.patterns.evidence.asyncStyle.length > 0,
|
|
570
|
+
"should have async style evidence",
|
|
571
|
+
);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
// ─── aggregatePriorContext ──────────────────────────────────────────────────────
|
|
575
|
+
|
|
576
|
+
test("aggregatePriorContext: handles missing files gracefully", async (t) => {
|
|
577
|
+
const dir = makeTempDir("no-gsd");
|
|
578
|
+
t.after(() => cleanup(dir));
|
|
579
|
+
|
|
580
|
+
// Create .gsd directory but no files
|
|
581
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
582
|
+
|
|
583
|
+
const brief = await aggregatePriorContext(dir);
|
|
584
|
+
|
|
585
|
+
assert.equal(brief.decisions.totalCount, 0, "should have no decisions");
|
|
586
|
+
assert.equal(brief.requirements.totalCount, 0, "should have no requirements");
|
|
587
|
+
assert.equal(brief.knowledge, "No prior knowledge recorded.", "should indicate no knowledge");
|
|
588
|
+
assert.equal(brief.summaries, "No prior milestone summaries.", "should indicate no summaries");
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
test("aggregatePriorContext: handles completely empty directory", async (t) => {
|
|
592
|
+
const dir = makeTempDir("empty-project");
|
|
593
|
+
t.after(() => cleanup(dir));
|
|
594
|
+
|
|
595
|
+
const brief = await aggregatePriorContext(dir);
|
|
596
|
+
|
|
597
|
+
assert.equal(brief.decisions.totalCount, 0);
|
|
598
|
+
assert.equal(brief.requirements.totalCount, 0);
|
|
599
|
+
assert.equal(brief.knowledge, "No prior knowledge recorded.");
|
|
600
|
+
assert.equal(brief.summaries, "No prior milestone summaries.");
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
test("aggregatePriorContext: parses DECISIONS.md and groups by scope", async (t) => {
|
|
604
|
+
const dir = makeTempDir("decisions");
|
|
605
|
+
t.after(() => cleanup(dir));
|
|
606
|
+
|
|
607
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
608
|
+
writeFileSync(
|
|
609
|
+
join(dir, ".gsd", "DECISIONS.md"),
|
|
610
|
+
`# Decisions Register
|
|
611
|
+
|
|
612
|
+
| # | When | Scope | Decision | Choice | Rationale | Revisable? | Made By |
|
|
613
|
+
|---|------|-------|----------|--------|-----------|------------|---------|
|
|
614
|
+
| D001 | M001/S01 | pattern | Async style | async/await | Modern standard | Yes | agent |
|
|
615
|
+
| D002 | M001/S02 | architecture | Data layer | SQLite | Simple, embedded | No | human |
|
|
616
|
+
| D003 | M001/S03 | pattern | Error handling | try/catch | Consistency | Yes | agent |
|
|
617
|
+
`,
|
|
618
|
+
"utf-8",
|
|
619
|
+
);
|
|
620
|
+
|
|
621
|
+
const brief = await aggregatePriorContext(dir);
|
|
622
|
+
|
|
623
|
+
assert.equal(brief.decisions.totalCount, 3, "should parse all decisions");
|
|
624
|
+
assert.equal(brief.decisions.byScope.get("pattern")?.length, 2, "should group pattern scope");
|
|
625
|
+
assert.equal(brief.decisions.byScope.get("architecture")?.length, 1, "should group architecture scope");
|
|
626
|
+
|
|
627
|
+
const patternDecisions = brief.decisions.byScope.get("pattern")!;
|
|
628
|
+
assert.equal(patternDecisions[0].id, "D001");
|
|
629
|
+
assert.equal(patternDecisions[0].decision, "Async style");
|
|
630
|
+
assert.equal(patternDecisions[0].choice, "async/await");
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
test("aggregatePriorContext: parses REQUIREMENTS.md and groups by status", async (t) => {
|
|
634
|
+
const dir = makeTempDir("requirements");
|
|
635
|
+
t.after(() => cleanup(dir));
|
|
636
|
+
|
|
637
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
638
|
+
writeFileSync(
|
|
639
|
+
join(dir, ".gsd", "REQUIREMENTS.md"),
|
|
640
|
+
`# Requirements
|
|
641
|
+
|
|
642
|
+
## Active
|
|
643
|
+
|
|
644
|
+
### R001 — First requirement
|
|
645
|
+
- Status: active
|
|
646
|
+
- Description: Something active
|
|
647
|
+
|
|
648
|
+
### R002 — Second requirement
|
|
649
|
+
- Status: active
|
|
650
|
+
- Description: Also active
|
|
651
|
+
|
|
652
|
+
## Validated
|
|
653
|
+
|
|
654
|
+
### R003 — Validated requirement
|
|
655
|
+
- Status: validated
|
|
656
|
+
- Description: This was validated
|
|
657
|
+
|
|
658
|
+
## Deferred
|
|
659
|
+
|
|
660
|
+
### R004 — Deferred requirement
|
|
661
|
+
- Status: deferred
|
|
662
|
+
- Description: Postponed for later
|
|
663
|
+
`,
|
|
664
|
+
"utf-8",
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
const brief = await aggregatePriorContext(dir);
|
|
668
|
+
|
|
669
|
+
assert.equal(brief.requirements.totalCount, 4, "should parse all requirements");
|
|
670
|
+
assert.equal(brief.requirements.active.length, 2, "should have 2 active");
|
|
671
|
+
assert.equal(brief.requirements.validated.length, 1, "should have 1 validated");
|
|
672
|
+
assert.equal(brief.requirements.deferred.length, 1, "should have 1 deferred");
|
|
673
|
+
|
|
674
|
+
assert.equal(brief.requirements.active[0].id, "R001");
|
|
675
|
+
assert.equal(brief.requirements.active[0].description, "First requirement");
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
test("aggregatePriorContext: loads KNOWLEDGE.md content", async (t) => {
|
|
679
|
+
const dir = makeTempDir("knowledge");
|
|
680
|
+
t.after(() => cleanup(dir));
|
|
681
|
+
|
|
682
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
683
|
+
writeFileSync(
|
|
684
|
+
join(dir, ".gsd", "KNOWLEDGE.md"),
|
|
685
|
+
`# Knowledge Base
|
|
686
|
+
|
|
687
|
+
## Rules
|
|
688
|
+
|
|
689
|
+
| # | Scope | Rule | Why | Added |
|
|
690
|
+
|---|-------|------|-----|-------|
|
|
691
|
+
| K001 | global | Always use TypeScript | Type safety | manual |
|
|
692
|
+
|
|
693
|
+
## Patterns
|
|
694
|
+
|
|
695
|
+
**Pattern X:** Do this for better Y.
|
|
696
|
+
`,
|
|
697
|
+
"utf-8",
|
|
698
|
+
);
|
|
699
|
+
|
|
700
|
+
const brief = await aggregatePriorContext(dir);
|
|
701
|
+
|
|
702
|
+
assert.ok(brief.knowledge.includes("Rules"), "should include knowledge content");
|
|
703
|
+
assert.ok(brief.knowledge.includes("TypeScript"), "should include rule text");
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
test("aggregatePriorContext: truncates oversized content without cutting mid-section", async (t) => {
|
|
707
|
+
const dir = makeTempDir("large-knowledge");
|
|
708
|
+
t.after(() => cleanup(dir));
|
|
709
|
+
|
|
710
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
711
|
+
|
|
712
|
+
// Create large knowledge file
|
|
713
|
+
const largeContent = `# Knowledge Base
|
|
714
|
+
|
|
715
|
+
## Section One
|
|
716
|
+
|
|
717
|
+
${"Lorem ipsum dolor sit amet. ".repeat(100)}
|
|
718
|
+
|
|
719
|
+
## Section Two
|
|
720
|
+
|
|
721
|
+
${"More content here. ".repeat(100)}
|
|
722
|
+
|
|
723
|
+
## Section Three
|
|
724
|
+
|
|
725
|
+
${"Even more content. ".repeat(100)}
|
|
726
|
+
`;
|
|
727
|
+
|
|
728
|
+
writeFileSync(join(dir, ".gsd", "KNOWLEDGE.md"), largeContent, "utf-8");
|
|
729
|
+
|
|
730
|
+
const brief = await aggregatePriorContext(dir);
|
|
731
|
+
|
|
732
|
+
assert.ok(brief.knowledge.length <= 2000, "should truncate to 2K chars");
|
|
733
|
+
assert.ok(brief.knowledge.includes("[truncated]"), "should indicate truncation");
|
|
734
|
+
// Should try to preserve section boundaries
|
|
735
|
+
assert.ok(
|
|
736
|
+
brief.knowledge.includes("## Section"),
|
|
737
|
+
"should keep section headings intact",
|
|
738
|
+
);
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
test("aggregatePriorContext: loads milestone summaries", async (t) => {
|
|
742
|
+
const dir = makeTempDir("milestones");
|
|
743
|
+
t.after(() => cleanup(dir));
|
|
744
|
+
|
|
745
|
+
mkdirSync(join(dir, ".gsd", "milestones", "M001"), { recursive: true });
|
|
746
|
+
mkdirSync(join(dir, ".gsd", "milestones", "M002"), { recursive: true });
|
|
747
|
+
|
|
748
|
+
writeFileSync(
|
|
749
|
+
join(dir, ".gsd", "milestones", "M001", "MILESTONE-SUMMARY.md"),
|
|
750
|
+
`# M001 — First Milestone
|
|
751
|
+
|
|
752
|
+
**Implemented core functionality and established patterns.**
|
|
753
|
+
|
|
754
|
+
## What Happened
|
|
755
|
+
Did stuff.
|
|
756
|
+
`,
|
|
757
|
+
"utf-8",
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
writeFileSync(
|
|
761
|
+
join(dir, ".gsd", "milestones", "M002", "MILESTONE-SUMMARY.md"),
|
|
762
|
+
`# M002 — Second Milestone
|
|
763
|
+
|
|
764
|
+
**Extended the system with new features.**
|
|
765
|
+
|
|
766
|
+
## What Happened
|
|
767
|
+
Did more stuff.
|
|
768
|
+
`,
|
|
769
|
+
"utf-8",
|
|
770
|
+
);
|
|
771
|
+
|
|
772
|
+
const brief = await aggregatePriorContext(dir);
|
|
773
|
+
|
|
774
|
+
assert.ok(brief.summaries.includes("M001"), "should include M001 summary");
|
|
775
|
+
assert.ok(brief.summaries.includes("M002"), "should include M002 summary");
|
|
776
|
+
assert.ok(
|
|
777
|
+
brief.summaries.includes("core functionality"),
|
|
778
|
+
"should extract one-liner from M001",
|
|
779
|
+
);
|
|
780
|
+
assert.ok(
|
|
781
|
+
brief.summaries.includes("new features"),
|
|
782
|
+
"should extract one-liner from M002",
|
|
783
|
+
);
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
// ─── formatPriorContextBrief ────────────────────────────────────────────────────
|
|
787
|
+
|
|
788
|
+
test("formatPriorContextBrief: produces markdown with all sections", async (t) => {
|
|
789
|
+
const brief: PriorContextBrief = {
|
|
790
|
+
decisions: {
|
|
791
|
+
byScope: new Map([
|
|
792
|
+
[
|
|
793
|
+
"pattern",
|
|
794
|
+
[
|
|
795
|
+
{ id: "D001", scope: "pattern", decision: "Async", choice: "await", rationale: "Modern" },
|
|
796
|
+
],
|
|
797
|
+
],
|
|
798
|
+
[
|
|
799
|
+
"architecture",
|
|
800
|
+
[
|
|
801
|
+
{ id: "D002", scope: "architecture", decision: "DB", choice: "SQLite", rationale: "Simple" },
|
|
802
|
+
],
|
|
803
|
+
],
|
|
804
|
+
]),
|
|
805
|
+
totalCount: 2,
|
|
806
|
+
},
|
|
807
|
+
requirements: {
|
|
808
|
+
active: [{ id: "R001", description: "Core feature", status: "active" }],
|
|
809
|
+
validated: [],
|
|
810
|
+
deferred: [],
|
|
811
|
+
totalCount: 1,
|
|
812
|
+
},
|
|
813
|
+
knowledge: "Some knowledge here.",
|
|
814
|
+
summaries: "### M001\nDid things.",
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
const formatted = formatPriorContextBrief(brief);
|
|
818
|
+
|
|
819
|
+
assert.ok(formatted.includes("## Prior Decisions"), "should have decisions section");
|
|
820
|
+
assert.ok(formatted.includes("## Prior Requirements"), "should have requirements section");
|
|
821
|
+
assert.ok(formatted.includes("## Prior Knowledge"), "should have knowledge section");
|
|
822
|
+
assert.ok(formatted.includes("## Prior Milestone Summaries"), "should have summaries section");
|
|
823
|
+
assert.ok(formatted.includes("D001"), "should include decision ID");
|
|
824
|
+
assert.ok(formatted.includes("R001"), "should include requirement ID");
|
|
825
|
+
assert.ok(formatted.includes("pattern"), "should include scope heading");
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
test("formatPriorContextBrief: handles empty brief", async (t) => {
|
|
829
|
+
const brief: PriorContextBrief = {
|
|
830
|
+
decisions: {
|
|
831
|
+
byScope: new Map(),
|
|
832
|
+
totalCount: 0,
|
|
833
|
+
},
|
|
834
|
+
requirements: {
|
|
835
|
+
active: [],
|
|
836
|
+
validated: [],
|
|
837
|
+
deferred: [],
|
|
838
|
+
totalCount: 0,
|
|
839
|
+
},
|
|
840
|
+
knowledge: "No prior knowledge recorded.",
|
|
841
|
+
summaries: "No prior milestone summaries.",
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
const formatted = formatPriorContextBrief(brief);
|
|
845
|
+
|
|
846
|
+
assert.ok(formatted.includes("No prior decisions recorded"), "should indicate no decisions");
|
|
847
|
+
assert.ok(formatted.includes("No prior requirements recorded"), "should indicate no requirements");
|
|
848
|
+
assert.ok(formatted.includes("No prior knowledge recorded"), "should indicate no knowledge");
|
|
849
|
+
assert.ok(formatted.includes("No prior milestone summaries"), "should indicate no summaries");
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
test("formatPriorContextBrief: caps total output at 6K chars", async (t) => {
|
|
853
|
+
// Create a brief with lots of content
|
|
854
|
+
const manyDecisions: Array<{
|
|
855
|
+
id: string;
|
|
856
|
+
scope: string;
|
|
857
|
+
decision: string;
|
|
858
|
+
choice: string;
|
|
859
|
+
rationale: string;
|
|
860
|
+
}> = [];
|
|
861
|
+
for (let i = 0; i < 100; i++) {
|
|
862
|
+
manyDecisions.push({
|
|
863
|
+
id: `D${String(i).padStart(3, "0")}`,
|
|
864
|
+
scope: "pattern",
|
|
865
|
+
decision: `Decision number ${i} with some extra text for length`,
|
|
866
|
+
choice: `Choice ${i} with more text to make it longer`,
|
|
867
|
+
rationale: `Rationale ${i}`,
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
const manyRequirements: Array<{
|
|
872
|
+
id: string;
|
|
873
|
+
description: string;
|
|
874
|
+
status: "active";
|
|
875
|
+
}> = [];
|
|
876
|
+
for (let i = 0; i < 100; i++) {
|
|
877
|
+
manyRequirements.push({
|
|
878
|
+
id: `R${String(i).padStart(3, "0")}`,
|
|
879
|
+
description: `Requirement ${i} with a long description that takes up space`,
|
|
880
|
+
status: "active",
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
const brief: PriorContextBrief = {
|
|
885
|
+
decisions: {
|
|
886
|
+
byScope: new Map([["pattern", manyDecisions]]),
|
|
887
|
+
totalCount: 100,
|
|
888
|
+
},
|
|
889
|
+
requirements: {
|
|
890
|
+
active: manyRequirements,
|
|
891
|
+
validated: [],
|
|
892
|
+
deferred: [],
|
|
893
|
+
totalCount: 100,
|
|
894
|
+
},
|
|
895
|
+
knowledge: "A ".repeat(1000),
|
|
896
|
+
summaries: "B ".repeat(1000),
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
const formatted = formatPriorContextBrief(brief);
|
|
900
|
+
|
|
901
|
+
assert.ok(formatted.length <= 6000, `should cap at 6000 chars, got ${formatted.length}`);
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
// ─── researchEcosystem ──────────────────────────────────────────────────────────
|
|
905
|
+
// Note: Ecosystem research now always returns available: false from the preparation
|
|
906
|
+
// phase. Research happens during the discussion using web search tools.
|
|
907
|
+
|
|
908
|
+
test("researchEcosystem: always returns available: false (research happens during discussion)", async (t) => {
|
|
909
|
+
const dir = makeTempDir("ecosystem-disabled");
|
|
910
|
+
t.after(() => cleanup(dir));
|
|
911
|
+
|
|
912
|
+
const brief = await researchEcosystem(["Next.js", "TypeScript"], dir);
|
|
913
|
+
|
|
914
|
+
assert.equal(brief.available, false, "should indicate research not available from preparation");
|
|
915
|
+
assert.ok(brief.skippedReason, "should have skipped reason");
|
|
916
|
+
assert.ok(
|
|
917
|
+
brief.skippedReason!.includes("during the discussion"),
|
|
918
|
+
"should explain research happens during discussion",
|
|
919
|
+
);
|
|
920
|
+
assert.deepEqual(brief.queries, [], "should have empty queries");
|
|
921
|
+
assert.deepEqual(brief.findings, [], "should have empty findings");
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
test("researchEcosystem: returns consistent result regardless of tech stack", async (t) => {
|
|
925
|
+
const dir = makeTempDir("ecosystem-consistent");
|
|
926
|
+
t.after(() => cleanup(dir));
|
|
927
|
+
|
|
928
|
+
// With tech stack
|
|
929
|
+
const briefWithTech = await researchEcosystem(["React", "Next.js"], dir);
|
|
930
|
+
// Without tech stack
|
|
931
|
+
const briefEmpty = await researchEcosystem([], dir);
|
|
932
|
+
|
|
933
|
+
// Both should return the same unavailable result
|
|
934
|
+
assert.equal(briefWithTech.available, false);
|
|
935
|
+
assert.equal(briefEmpty.available, false);
|
|
936
|
+
assert.deepEqual(briefWithTech.queries, []);
|
|
937
|
+
assert.deepEqual(briefEmpty.queries, []);
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
// ─── formatEcosystemBrief ─��─────────────────────────────────────────────────────
|
|
941
|
+
// Note: formatEcosystemBrief now returns a simple fixed message since ecosystem
|
|
942
|
+
// research always returns unavailable from the preparation phase.
|
|
943
|
+
|
|
944
|
+
test("formatEcosystemBrief: returns simplified message for discussion-phase research", async (t) => {
|
|
945
|
+
const brief: EcosystemBrief = {
|
|
946
|
+
available: false,
|
|
947
|
+
queries: [],
|
|
948
|
+
findings: [],
|
|
949
|
+
skippedReason: "Ecosystem research is performed during the discussion using web search tools, not during preparation.",
|
|
950
|
+
};
|
|
951
|
+
|
|
952
|
+
const formatted = formatEcosystemBrief(brief);
|
|
953
|
+
|
|
954
|
+
assert.ok(formatted.includes("## Ecosystem Research"), "should have section header");
|
|
955
|
+
assert.ok(formatted.includes("during the discussion"), "should mention discussion phase");
|
|
956
|
+
assert.ok(formatted.includes("web search tools"), "should mention web search tools");
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
test("formatEcosystemBrief: returns consistent output regardless of brief content", async (t) => {
|
|
960
|
+
// Even if a brief has findings (which shouldn't happen from preparation),
|
|
961
|
+
// the function returns the simplified message
|
|
962
|
+
const briefWithFindings: EcosystemBrief = {
|
|
963
|
+
available: true,
|
|
964
|
+
queries: ["test query"],
|
|
965
|
+
findings: [{ query: "test", title: "Test", snippet: "test", url: "https://example.com" }],
|
|
966
|
+
provider: "tavily",
|
|
967
|
+
};
|
|
968
|
+
|
|
969
|
+
const briefEmpty: EcosystemBrief = {
|
|
970
|
+
available: false,
|
|
971
|
+
queries: [],
|
|
972
|
+
findings: [],
|
|
973
|
+
skippedReason: "Test reason",
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
const formatted1 = formatEcosystemBrief(briefWithFindings);
|
|
977
|
+
const formatted2 = formatEcosystemBrief(briefEmpty);
|
|
978
|
+
|
|
979
|
+
// Both should return the same simplified message
|
|
980
|
+
assert.equal(formatted1, formatted2, "should return consistent output");
|
|
981
|
+
assert.ok(formatted1.includes("## Ecosystem Research"), "should have section header");
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
|
|
985
|
+
// ─── runPreparation (Orchestrator) ──────────────────────────────────────────────
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* Mock UI context that captures notifications for testing.
|
|
989
|
+
*/
|
|
990
|
+
function createMockUI(): PreparationUIContext & { notifications: Array<{ message: string; type?: string }> } {
|
|
991
|
+
const notifications: Array<{ message: string; type?: string }> = [];
|
|
992
|
+
return {
|
|
993
|
+
notifications,
|
|
994
|
+
notify(message: string, type?: "info" | "warning" | "error" | "success") {
|
|
995
|
+
notifications.push({ message, type });
|
|
996
|
+
},
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
test("runPreparation: returns complete result with all briefs populated", async (t) => {
|
|
1001
|
+
const dir = makeTempDir("runprep-full");
|
|
1002
|
+
t.after(() => cleanup(dir));
|
|
1003
|
+
|
|
1004
|
+
// Set up a minimal project
|
|
1005
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
1006
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
1007
|
+
writeFileSync(join(dir, "package.json"), '{"name": "test-project"}', "utf-8");
|
|
1008
|
+
writeFileSync(join(dir, "src", "index.ts"), 'export const x = 1;', "utf-8");
|
|
1009
|
+
|
|
1010
|
+
const ui = createMockUI();
|
|
1011
|
+
const prefs: PreparationPreferences = {
|
|
1012
|
+
discuss_preparation: true,
|
|
1013
|
+
discuss_web_research: false, // Skip web research to avoid API key requirement
|
|
1014
|
+
discuss_depth: "standard",
|
|
1015
|
+
};
|
|
1016
|
+
|
|
1017
|
+
const result = await runPreparation(dir, ui, prefs);
|
|
1018
|
+
|
|
1019
|
+
// Check result structure
|
|
1020
|
+
assert.equal(result.enabled, true, "should be enabled");
|
|
1021
|
+
assert.ok(result.codebase, "should have codebase");
|
|
1022
|
+
assert.ok(result.priorContext, "should have priorContext");
|
|
1023
|
+
assert.ok(result.ecosystem, "should have ecosystem");
|
|
1024
|
+
assert.ok(typeof result.codebaseBrief === "string", "should have codebaseBrief");
|
|
1025
|
+
assert.ok(typeof result.priorContextBrief === "string", "should have priorContextBrief");
|
|
1026
|
+
assert.ok(typeof result.ecosystemBrief === "string", "should have ecosystemBrief");
|
|
1027
|
+
assert.ok(result.durationMs > 0, "should have positive duration");
|
|
1028
|
+
assert.equal(result.ecosystemResearchPerformed, false, "should not have performed ecosystem research");
|
|
1029
|
+
|
|
1030
|
+
// Check TUI progress notifications
|
|
1031
|
+
assert.ok(ui.notifications.length > 0, "should have notifications");
|
|
1032
|
+
assert.ok(
|
|
1033
|
+
ui.notifications.some((n) => n.message.includes("Analyzing codebase")),
|
|
1034
|
+
"should show codebase analysis start",
|
|
1035
|
+
);
|
|
1036
|
+
assert.ok(
|
|
1037
|
+
ui.notifications.some((n) => n.message.includes("✓ Analyzed codebase")),
|
|
1038
|
+
"should show codebase analysis complete",
|
|
1039
|
+
);
|
|
1040
|
+
assert.ok(
|
|
1041
|
+
ui.notifications.some((n) => n.message.includes("Reviewing prior context")),
|
|
1042
|
+
"should show prior context start",
|
|
1043
|
+
);
|
|
1044
|
+
assert.ok(
|
|
1045
|
+
ui.notifications.some((n) => n.message.includes("✓ Reviewed prior context")),
|
|
1046
|
+
"should show prior context complete",
|
|
1047
|
+
);
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
test("runPreparation: returns early when discuss_preparation is false", async (t) => {
|
|
1051
|
+
const dir = makeTempDir("runprep-disabled");
|
|
1052
|
+
t.after(() => cleanup(dir));
|
|
1053
|
+
|
|
1054
|
+
const ui = createMockUI();
|
|
1055
|
+
const prefs: PreparationPreferences = {
|
|
1056
|
+
discuss_preparation: false,
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
const result = await runPreparation(dir, ui, prefs);
|
|
1060
|
+
|
|
1061
|
+
assert.equal(result.enabled, false, "should indicate preparation disabled");
|
|
1062
|
+
assert.equal(result.codebaseBrief, "", "should have empty codebase brief");
|
|
1063
|
+
assert.equal(result.priorContextBrief, "", "should have empty prior context brief");
|
|
1064
|
+
assert.equal(result.ecosystemBrief, "", "should have empty ecosystem brief");
|
|
1065
|
+
assert.equal(ui.notifications.length, 0, "should not show any notifications");
|
|
1066
|
+
assert.ok(result.durationMs >= 0, "should have non-negative duration");
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
test("runPreparation: ecosystem research always returns unavailable (happens during discussion)", async (t) => {
|
|
1070
|
+
const dir = makeTempDir("runprep-no-ecosystem");
|
|
1071
|
+
t.after(() => cleanup(dir));
|
|
1072
|
+
|
|
1073
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
1074
|
+
writeFileSync(join(dir, "package.json"), '{"name": "test"}', "utf-8");
|
|
1075
|
+
|
|
1076
|
+
const ui = createMockUI();
|
|
1077
|
+
const prefs: PreparationPreferences = {
|
|
1078
|
+
discuss_preparation: true,
|
|
1079
|
+
discuss_web_research: true, // Even with this enabled, ecosystem research returns unavailable
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
const result = await runPreparation(dir, ui, prefs);
|
|
1083
|
+
|
|
1084
|
+
assert.equal(result.enabled, true);
|
|
1085
|
+
assert.equal(result.ecosystemResearchPerformed, false, "should not perform ecosystem research from preparation");
|
|
1086
|
+
assert.equal(result.ecosystem.available, false);
|
|
1087
|
+
assert.ok(
|
|
1088
|
+
result.ecosystem.skippedReason?.includes("during the discussion"),
|
|
1089
|
+
"should indicate research happens during discussion",
|
|
1090
|
+
);
|
|
1091
|
+
|
|
1092
|
+
// Should NOT have ecosystem research notifications (no longer part of preparation)
|
|
1093
|
+
assert.ok(
|
|
1094
|
+
!ui.notifications.some((n) => n.message.includes("Researching ecosystem")),
|
|
1095
|
+
"should not show ecosystem research notification",
|
|
1096
|
+
);
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
test("runPreparation: works without UI context (silent mode)", async (t) => {
|
|
1100
|
+
const dir = makeTempDir("runprep-silent");
|
|
1101
|
+
t.after(() => cleanup(dir));
|
|
1102
|
+
|
|
1103
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
1104
|
+
writeFileSync(join(dir, "package.json"), '{"name": "test"}', "utf-8");
|
|
1105
|
+
|
|
1106
|
+
const prefs: PreparationPreferences = {
|
|
1107
|
+
discuss_preparation: true,
|
|
1108
|
+
discuss_web_research: false,
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
// Pass null for UI to test silent mode
|
|
1112
|
+
const result = await runPreparation(dir, null, prefs);
|
|
1113
|
+
|
|
1114
|
+
assert.equal(result.enabled, true, "should work without UI");
|
|
1115
|
+
assert.ok(result.codebase, "should have codebase");
|
|
1116
|
+
assert.ok(result.priorContext, "should have priorContext");
|
|
1117
|
+
assert.ok(result.durationMs > 0, "should have duration");
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
test("runPreparation: completes within 60s requirement (R112)", async (t) => {
|
|
1121
|
+
const dir = makeTempDir("runprep-timing");
|
|
1122
|
+
t.after(() => cleanup(dir));
|
|
1123
|
+
|
|
1124
|
+
// Create a project with some content to analyze
|
|
1125
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
1126
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
1127
|
+
writeFileSync(join(dir, "package.json"), '{"name": "test"}', "utf-8");
|
|
1128
|
+
writeFileSync(join(dir, "tsconfig.json"), '{}', "utf-8");
|
|
1129
|
+
|
|
1130
|
+
for (let i = 0; i < 10; i++) {
|
|
1131
|
+
writeFileSync(
|
|
1132
|
+
join(dir, "src", `file${i}.ts`),
|
|
1133
|
+
`export async function fn${i}() { await Promise.resolve(); }\n`.repeat(50),
|
|
1134
|
+
"utf-8",
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
const prefs: PreparationPreferences = {
|
|
1139
|
+
discuss_preparation: true,
|
|
1140
|
+
discuss_web_research: false,
|
|
1141
|
+
discuss_depth: "standard",
|
|
1142
|
+
};
|
|
1143
|
+
|
|
1144
|
+
const startTime = performance.now();
|
|
1145
|
+
const result = await runPreparation(dir, null, prefs);
|
|
1146
|
+
const elapsed = performance.now() - startTime;
|
|
1147
|
+
|
|
1148
|
+
assert.ok(result.durationMs < 60000, `should complete within 60s, took ${result.durationMs}ms`);
|
|
1149
|
+
assert.ok(elapsed < 60000, `elapsed time should be under 60s, was ${elapsed}ms`);
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
test("runPreparation: does not throw on any input", async (t) => {
|
|
1153
|
+
const dir = makeTempDir("runprep-robust");
|
|
1154
|
+
t.after(() => cleanup(dir));
|
|
1155
|
+
|
|
1156
|
+
// Test with completely empty directory
|
|
1157
|
+
const prefs: PreparationPreferences = {};
|
|
1158
|
+
|
|
1159
|
+
let result: PreparationResult | undefined;
|
|
1160
|
+
let error: unknown;
|
|
1161
|
+
|
|
1162
|
+
try {
|
|
1163
|
+
result = await runPreparation(dir, null, prefs);
|
|
1164
|
+
} catch (e) {
|
|
1165
|
+
error = e;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
assert.equal(error, undefined, "should not throw");
|
|
1169
|
+
assert.ok(result, "should return result");
|
|
1170
|
+
assert.equal(result!.enabled, true, "should be enabled by default");
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
test("runPreparation: detects framework from config files in codebase brief", async (t) => {
|
|
1174
|
+
const dir = makeTempDir("runprep-framework");
|
|
1175
|
+
t.after(() => cleanup(dir));
|
|
1176
|
+
|
|
1177
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
1178
|
+
writeFileSync(join(dir, "package.json"), '{"name": "test"}', "utf-8");
|
|
1179
|
+
writeFileSync(join(dir, "next.config.mjs"), 'export default {};', "utf-8");
|
|
1180
|
+
|
|
1181
|
+
const prefs: PreparationPreferences = {
|
|
1182
|
+
discuss_preparation: true,
|
|
1183
|
+
};
|
|
1184
|
+
|
|
1185
|
+
const result = await runPreparation(dir, null, prefs);
|
|
1186
|
+
|
|
1187
|
+
// Should detect Next.js config file in codebase analysis
|
|
1188
|
+
assert.ok(
|
|
1189
|
+
result.codebase.techStack.detectedFiles.includes("next.config.mjs"),
|
|
1190
|
+
"should detect next.config.mjs in codebase brief",
|
|
1191
|
+
);
|
|
1192
|
+
// Ecosystem queries are always empty from preparation (research happens during discussion)
|
|
1193
|
+
assert.deepEqual(result.ecosystem.queries, [], "ecosystem queries should be empty from preparation");
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
test("runPreparation: default preferences enable preparation and web research", async (t) => {
|
|
1197
|
+
const dir = makeTempDir("runprep-defaults");
|
|
1198
|
+
t.after(() => cleanup(dir));
|
|
1199
|
+
|
|
1200
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
1201
|
+
|
|
1202
|
+
const ui = createMockUI();
|
|
1203
|
+
const prefs: PreparationPreferences = {}; // All defaults
|
|
1204
|
+
|
|
1205
|
+
const result = await runPreparation(dir, ui, prefs);
|
|
1206
|
+
|
|
1207
|
+
// With defaults, preparation should be enabled
|
|
1208
|
+
assert.equal(result.enabled, true, "should be enabled by default");
|
|
1209
|
+
// Notifications should be shown
|
|
1210
|
+
assert.ok(ui.notifications.length > 0, "should show notifications");
|
|
1211
|
+
});
|