gsd-pi 2.65.0 → 2.66.0-dev.e159299
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 +17 -17
- 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 +17 -17
- 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.map +1 -1
- package/packages/pi-tui/dist/tui.js +3 -1
- 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 +3 -1
- 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 → h8aBiLMFjb__ogynY08cm}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{MRM3OSYIAa4HMDqVGQ9nt → h8aBiLMFjb__ogynY08cm}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,1092 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Preparation — Structured brief generation for discussion LLM sessions.
|
|
3
|
+
*
|
|
4
|
+
* Produces structured briefs (codebase, prior context, ecosystem) before
|
|
5
|
+
* the discussion LLM session starts.
|
|
6
|
+
*
|
|
7
|
+
* Pure functions, zero UI dependencies (except for runPreparation orchestrator).
|
|
8
|
+
*/
|
|
9
|
+
import { readdirSync, openSync, readSync, closeSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { readdirSync as readdirSyncNode } from "node:fs";
|
|
12
|
+
import { detectProjectSignals, scanProjectFiles, } from "./detection.js";
|
|
13
|
+
import { loadFile } from "./files.js";
|
|
14
|
+
// ─── Constants ──────────────────────────────────────────────────────────────────
|
|
15
|
+
/** Maximum characters for the codebase section. */
|
|
16
|
+
const MAX_CODEBASE_BRIEF_CHARS = 3000;
|
|
17
|
+
/** Number of files to sample for pattern extraction. */
|
|
18
|
+
const SAMPLE_FILE_COUNT = 5;
|
|
19
|
+
/** Maximum bytes to read from each sampled file. */
|
|
20
|
+
const MAX_FILE_SAMPLE_BYTES = 8192;
|
|
21
|
+
/** Directories to skip when sampling. */
|
|
22
|
+
const SKIP_DIRS = new Set([
|
|
23
|
+
"node_modules",
|
|
24
|
+
"dist",
|
|
25
|
+
"build",
|
|
26
|
+
".git",
|
|
27
|
+
"coverage",
|
|
28
|
+
".next",
|
|
29
|
+
".nuxt",
|
|
30
|
+
"target",
|
|
31
|
+
".turbo",
|
|
32
|
+
"vendor",
|
|
33
|
+
"__pycache__",
|
|
34
|
+
".venv",
|
|
35
|
+
"venv",
|
|
36
|
+
]);
|
|
37
|
+
/** File patterns to exclude when sampling. */
|
|
38
|
+
const EXCLUDE_PATTERNS = [
|
|
39
|
+
/\.test\.(ts|tsx|js|jsx|mjs|cjs)$/,
|
|
40
|
+
/\.spec\.(ts|tsx|js|jsx|mjs|cjs)$/,
|
|
41
|
+
/\.d\.ts$/,
|
|
42
|
+
/test-.*\.(ts|tsx|js|jsx)$/,
|
|
43
|
+
/.*\.min\.(js|css)$/,
|
|
44
|
+
];
|
|
45
|
+
/** File extensions to sample for pattern extraction (JS/TS default). */
|
|
46
|
+
const SAMPLE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
47
|
+
/** Common source file extensions for universal pattern detection (naming convention).
|
|
48
|
+
* Used when the language is not in LANGUAGE_PATTERNS but we still want to detect camelCase/snake_case. */
|
|
49
|
+
const UNIVERSAL_SOURCE_EXTENSIONS = [
|
|
50
|
+
// JavaScript/TypeScript
|
|
51
|
+
".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs",
|
|
52
|
+
// Python
|
|
53
|
+
".py", ".pyw", ".pyi",
|
|
54
|
+
// Ruby
|
|
55
|
+
".rb", ".rake", ".gemspec",
|
|
56
|
+
// Go
|
|
57
|
+
".go",
|
|
58
|
+
// Rust
|
|
59
|
+
".rs",
|
|
60
|
+
// Java/Kotlin
|
|
61
|
+
".java", ".kt", ".kts",
|
|
62
|
+
// C/C++
|
|
63
|
+
".c", ".cpp", ".cc", ".cxx", ".h", ".hpp",
|
|
64
|
+
// C#
|
|
65
|
+
".cs",
|
|
66
|
+
// Swift
|
|
67
|
+
".swift",
|
|
68
|
+
// PHP
|
|
69
|
+
".php",
|
|
70
|
+
// Scala
|
|
71
|
+
".scala",
|
|
72
|
+
// Elixir/Erlang
|
|
73
|
+
".ex", ".exs", ".erl",
|
|
74
|
+
// Haskell
|
|
75
|
+
".hs", ".lhs",
|
|
76
|
+
// Shell
|
|
77
|
+
".sh", ".bash", ".zsh",
|
|
78
|
+
// Lua
|
|
79
|
+
".lua",
|
|
80
|
+
// Dart
|
|
81
|
+
".dart",
|
|
82
|
+
];
|
|
83
|
+
// ─── Pattern Detection Regexes ──────────────────────────────────────────────────
|
|
84
|
+
/** Async/await usage patterns. */
|
|
85
|
+
const ASYNC_AWAIT_RE = /\basync\s+function\b|\basync\s*\(|\bawait\s+/g;
|
|
86
|
+
/** Callback-style patterns (common patterns like done, callback, cb). */
|
|
87
|
+
const CALLBACK_RE = /\b(callback|cb|done)\s*\(|\bfunction\s*\([^)]*\bfunction\b/g;
|
|
88
|
+
/** Promise patterns (.then, .catch, new Promise). */
|
|
89
|
+
const PROMISE_RE = /\.then\s*\(|\.catch\s*\(|\bnew\s+Promise\s*\(/g;
|
|
90
|
+
/** Try/catch patterns. */
|
|
91
|
+
const TRY_CATCH_RE = /\btry\s*\{[\s\S]*?\bcatch\s*\(/g;
|
|
92
|
+
/** Error-first callback patterns. */
|
|
93
|
+
const ERROR_CALLBACK_RE = /\bif\s*\(\s*(err|error)\s*\)|\(err(or)?\s*,/g;
|
|
94
|
+
/** Result type patterns (Rust-style, fp-ts, etc.). */
|
|
95
|
+
const RESULT_TYPE_RE = /\bResult<|\bEither<|\bisOk\(|\bisErr\(|\b(Ok|Err)\(/g;
|
|
96
|
+
/** camelCase identifier patterns. */
|
|
97
|
+
const CAMEL_CASE_RE = /\b[a-z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]*\b/g;
|
|
98
|
+
/** snake_case identifier patterns. */
|
|
99
|
+
const SNAKE_CASE_RE = /\b[a-z][a-z0-9]*_[a-z0-9_]+\b/g;
|
|
100
|
+
/** PascalCase identifier patterns (for types/classes). */
|
|
101
|
+
const PASCAL_CASE_RE = /\bclass\s+[A-Z][a-zA-Z0-9]*|\binterface\s+[A-Z][a-zA-Z0-9]*|\btype\s+[A-Z][a-zA-Z0-9]*/g;
|
|
102
|
+
// ─── Language Pattern Registry ──────────────────────────────────────────────────
|
|
103
|
+
/**
|
|
104
|
+
* Registry of language-specific patterns for code analysis.
|
|
105
|
+
* Keys MUST match detection.ts LANGUAGE_MAP values exactly.
|
|
106
|
+
*/
|
|
107
|
+
export const LANGUAGE_PATTERNS = {
|
|
108
|
+
"javascript/typescript": {
|
|
109
|
+
displayName: "JavaScript/TypeScript",
|
|
110
|
+
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
|
|
111
|
+
asyncStyle: {
|
|
112
|
+
modern: /\basync\s+function\b|\basync\s*\(|\bawait\s+/g,
|
|
113
|
+
modernLabel: "async/await",
|
|
114
|
+
legacy: /\.then\s*\(|\.catch\s*\(|\bnew\s+Promise\s*\(/g,
|
|
115
|
+
legacyLabel: "promises",
|
|
116
|
+
},
|
|
117
|
+
errorHandling: {
|
|
118
|
+
structured: /\btry\s*\{[\s\S]*?\bcatch\s*\(/g,
|
|
119
|
+
structuredLabel: "try/catch",
|
|
120
|
+
inline: /\bif\s*\(\s*(err|error)\s*\)|\(err(or)?\s*,/g,
|
|
121
|
+
inlineLabel: "error-callbacks",
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
python: {
|
|
125
|
+
displayName: "Python",
|
|
126
|
+
extensions: [".py", ".pyw", ".pyi"],
|
|
127
|
+
asyncStyle: {
|
|
128
|
+
modern: /\basync\s+def\b|\bawait\s+/g,
|
|
129
|
+
modernLabel: "async/await",
|
|
130
|
+
legacy: /\.add_done_callback\(|ThreadPoolExecutor|ProcessPoolExecutor/g,
|
|
131
|
+
legacyLabel: "futures/executors",
|
|
132
|
+
},
|
|
133
|
+
errorHandling: {
|
|
134
|
+
structured: /\btry\s*:[\s\S]*?\bexcept\b/g,
|
|
135
|
+
structuredLabel: "try/except",
|
|
136
|
+
inline: /\braise\s+\w+Error|\bassert\s+/g,
|
|
137
|
+
inlineLabel: "raise/assert",
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
rust: {
|
|
141
|
+
displayName: "Rust",
|
|
142
|
+
extensions: [".rs"],
|
|
143
|
+
asyncStyle: {
|
|
144
|
+
modern: /\basync\s+fn\b|\.await\b/g,
|
|
145
|
+
modernLabel: "async/await",
|
|
146
|
+
legacy: /\bthread::spawn\(|\bmpsc::/g,
|
|
147
|
+
legacyLabel: "threads/channels",
|
|
148
|
+
},
|
|
149
|
+
errorHandling: {
|
|
150
|
+
structured: /\bResult<|\bOption<|\?\s*;/g,
|
|
151
|
+
structuredLabel: "Result/Option",
|
|
152
|
+
inline: /\bunwrap\(\)|\bexpect\(/g,
|
|
153
|
+
inlineLabel: "unwrap/expect",
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
go: {
|
|
157
|
+
displayName: "Go",
|
|
158
|
+
extensions: [".go"],
|
|
159
|
+
asyncStyle: {
|
|
160
|
+
modern: /\bgo\s+func\b|\bgo\s+\w+\(/g,
|
|
161
|
+
modernLabel: "goroutines",
|
|
162
|
+
legacy: /\bchan\s+\w+|<-\s*\w+|\w+\s*<-/g,
|
|
163
|
+
legacyLabel: "channels",
|
|
164
|
+
},
|
|
165
|
+
errorHandling: {
|
|
166
|
+
structured: /\bif\s+err\s*!=\s*nil\b/g,
|
|
167
|
+
structuredLabel: "if err != nil",
|
|
168
|
+
inline: /\bpanic\(|\brecover\(\)/g,
|
|
169
|
+
inlineLabel: "panic/recover",
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
java: {
|
|
173
|
+
displayName: "Java",
|
|
174
|
+
extensions: [".java"],
|
|
175
|
+
asyncStyle: {
|
|
176
|
+
modern: /\bCompletableFuture<|\bCompletionStage<|\bthenApply\(/g,
|
|
177
|
+
modernLabel: "CompletableFuture",
|
|
178
|
+
legacy: /\bThread\s+\w+\s*=|\bnew\s+Thread\(|\bExecutorService\b/g,
|
|
179
|
+
legacyLabel: "threads/executors",
|
|
180
|
+
},
|
|
181
|
+
errorHandling: {
|
|
182
|
+
structured: /\btry\s*\{[\s\S]*?\bcatch\s*\(/g,
|
|
183
|
+
structuredLabel: "try/catch",
|
|
184
|
+
inline: /\bthrows\s+\w+Exception|\bthrow\s+new\s+\w+Exception/g,
|
|
185
|
+
inlineLabel: "throws/throw",
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
"java/kotlin": {
|
|
189
|
+
displayName: "Java/Kotlin",
|
|
190
|
+
extensions: [".java", ".kt", ".kts"],
|
|
191
|
+
asyncStyle: {
|
|
192
|
+
modern: /\bsuspend\s+fun\b|\blaunch\s*\{|\basync\s*\{|\bwithContext\(/g,
|
|
193
|
+
modernLabel: "coroutines",
|
|
194
|
+
legacy: /\bThread\s+\w+\s*=|\bnew\s+Thread\(|\bExecutorService\b|\bCompletableFuture</g,
|
|
195
|
+
legacyLabel: "threads/futures",
|
|
196
|
+
},
|
|
197
|
+
errorHandling: {
|
|
198
|
+
structured: /\btry\s*\{[\s\S]*?\bcatch\s*\(/g,
|
|
199
|
+
structuredLabel: "try/catch",
|
|
200
|
+
inline: /\bthrows\s+\w+Exception|\bthrow\s+\w+Exception|\brunCatching\s*\{/g,
|
|
201
|
+
inlineLabel: "throws/runCatching",
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
// ─── Core Functions ─────────────────────────────────────────────────────────────
|
|
206
|
+
/**
|
|
207
|
+
* Analyze the codebase and produce a structured brief.
|
|
208
|
+
*
|
|
209
|
+
* @param basePath - Root directory of the project
|
|
210
|
+
* @returns CodebaseBrief with tech stack, module structure, and patterns
|
|
211
|
+
*/
|
|
212
|
+
export async function analyzeCodebase(basePath) {
|
|
213
|
+
// Get project signals from detection.ts
|
|
214
|
+
const signals = detectProjectSignals(basePath);
|
|
215
|
+
// Detect module structure
|
|
216
|
+
const moduleStructure = detectModuleStructure(basePath);
|
|
217
|
+
// Sample files and extract patterns, passing primary language for language-aware detection
|
|
218
|
+
const sampledFiles = sampleSourceFiles(basePath, signals.primaryLanguage);
|
|
219
|
+
const patterns = extractPatterns(basePath, sampledFiles, signals.primaryLanguage);
|
|
220
|
+
return {
|
|
221
|
+
techStack: {
|
|
222
|
+
primaryLanguage: signals.primaryLanguage,
|
|
223
|
+
detectedFiles: signals.detectedFiles,
|
|
224
|
+
packageManager: signals.packageManager,
|
|
225
|
+
isMonorepo: signals.isMonorepo,
|
|
226
|
+
hasTests: signals.hasTests,
|
|
227
|
+
hasCI: signals.hasCI,
|
|
228
|
+
},
|
|
229
|
+
moduleStructure,
|
|
230
|
+
patterns,
|
|
231
|
+
sampledFiles,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Detect the module structure of the codebase.
|
|
236
|
+
*
|
|
237
|
+
* @param basePath - Root directory of the project
|
|
238
|
+
* @returns ModuleStructure with top-level and src subdirs
|
|
239
|
+
*/
|
|
240
|
+
function detectModuleStructure(basePath) {
|
|
241
|
+
const topLevelDirs = [];
|
|
242
|
+
const srcSubdirs = [];
|
|
243
|
+
try {
|
|
244
|
+
const entries = readdirSync(basePath, { withFileTypes: true });
|
|
245
|
+
for (const entry of entries) {
|
|
246
|
+
if (entry.isDirectory() && !entry.name.startsWith(".") && !SKIP_DIRS.has(entry.name)) {
|
|
247
|
+
topLevelDirs.push(entry.name);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
// Directory not readable
|
|
253
|
+
}
|
|
254
|
+
// Scan for subdirs in src/ or lib/
|
|
255
|
+
for (const srcDir of ["src", "lib", "app"]) {
|
|
256
|
+
const srcPath = join(basePath, srcDir);
|
|
257
|
+
try {
|
|
258
|
+
const entries = readdirSync(srcPath, { withFileTypes: true });
|
|
259
|
+
for (const entry of entries) {
|
|
260
|
+
if (entry.isDirectory() && !entry.name.startsWith(".") && !SKIP_DIRS.has(entry.name)) {
|
|
261
|
+
srcSubdirs.push(entry.name);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
// Directory doesn't exist or not readable
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
topLevelDirs,
|
|
271
|
+
srcSubdirs: [...new Set(srcSubdirs)], // Dedupe
|
|
272
|
+
totalFilesSampled: 0, // Will be set after sampling
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Sample source files from the codebase for pattern extraction.
|
|
277
|
+
*
|
|
278
|
+
* Prefers files in src/ directory, excludes test files and node_modules.
|
|
279
|
+
* Extension selection:
|
|
280
|
+
* - If language is in LANGUAGE_PATTERNS: use language-specific extensions
|
|
281
|
+
* - If language is undefined (no manifest): use JS/TS defaults (common case)
|
|
282
|
+
* - If language is set but not in LANGUAGE_PATTERNS: use UNIVERSAL_SOURCE_EXTENSIONS
|
|
283
|
+
* so we can still detect naming conventions even for unrecognized languages
|
|
284
|
+
*
|
|
285
|
+
* @param basePath - Root directory of the project
|
|
286
|
+
* @param primaryLanguage - Optional primary language identifier from detection.ts LANGUAGE_MAP
|
|
287
|
+
* @returns Array of relative file paths to sampled files
|
|
288
|
+
*/
|
|
289
|
+
function sampleSourceFiles(basePath, primaryLanguage) {
|
|
290
|
+
// Use scanProjectFiles from detection.ts for bounded recursion
|
|
291
|
+
const allFiles = scanProjectFiles(basePath);
|
|
292
|
+
// Get extensions to sample based on language detection status
|
|
293
|
+
const languageEntry = primaryLanguage ? LANGUAGE_PATTERNS[primaryLanguage] : undefined;
|
|
294
|
+
let extensionsToSample;
|
|
295
|
+
if (languageEntry) {
|
|
296
|
+
// Language is in registry — use its specific extensions
|
|
297
|
+
extensionsToSample = languageEntry.extensions;
|
|
298
|
+
}
|
|
299
|
+
else if (primaryLanguage === undefined) {
|
|
300
|
+
// No language detected (no manifest) — use JS/TS defaults
|
|
301
|
+
extensionsToSample = SAMPLE_EXTENSIONS;
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
// Language detected but not in registry (e.g., Ruby, Haskell)
|
|
305
|
+
// Use universal extensions so we can still detect naming conventions
|
|
306
|
+
extensionsToSample = UNIVERSAL_SOURCE_EXTENSIONS;
|
|
307
|
+
}
|
|
308
|
+
// Filter to target language files, excluding tests and dist
|
|
309
|
+
const candidates = allFiles.filter((file) => {
|
|
310
|
+
// Check extension
|
|
311
|
+
const hasValidExtension = extensionsToSample.some((ext) => file.endsWith(ext));
|
|
312
|
+
if (!hasValidExtension)
|
|
313
|
+
return false;
|
|
314
|
+
// Check exclusion patterns
|
|
315
|
+
for (const pattern of EXCLUDE_PATTERNS) {
|
|
316
|
+
if (pattern.test(file))
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
// Check for excluded directories in path
|
|
320
|
+
const parts = file.split(/[/\\]/);
|
|
321
|
+
for (const part of parts) {
|
|
322
|
+
if (SKIP_DIRS.has(part))
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
return true;
|
|
326
|
+
});
|
|
327
|
+
// Prioritize files in src/ directory
|
|
328
|
+
const srcFiles = candidates.filter((f) => f.startsWith("src/") || f.startsWith("src\\"));
|
|
329
|
+
const otherFiles = candidates.filter((f) => !f.startsWith("src/") && !f.startsWith("src\\"));
|
|
330
|
+
// Take SAMPLE_FILE_COUNT files, preferring src/
|
|
331
|
+
const sampled = [];
|
|
332
|
+
// First, add src files
|
|
333
|
+
for (const file of srcFiles) {
|
|
334
|
+
if (sampled.length >= SAMPLE_FILE_COUNT)
|
|
335
|
+
break;
|
|
336
|
+
sampled.push(file);
|
|
337
|
+
}
|
|
338
|
+
// Then add other files if needed
|
|
339
|
+
for (const file of otherFiles) {
|
|
340
|
+
if (sampled.length >= SAMPLE_FILE_COUNT)
|
|
341
|
+
break;
|
|
342
|
+
sampled.push(file);
|
|
343
|
+
}
|
|
344
|
+
return sampled;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Extract code patterns from sampled files.
|
|
348
|
+
*
|
|
349
|
+
* Pattern detection behavior:
|
|
350
|
+
* 1. When primaryLanguage exists in LANGUAGE_PATTERNS → uses language-specific patterns
|
|
351
|
+
* 2. When primaryLanguage is undefined (no manifest) → falls back to JS/TS patterns
|
|
352
|
+
* since the sampled files are filtered by JS/TS extensions anyway
|
|
353
|
+
* 3. When primaryLanguage is a known value NOT in LANGUAGE_PATTERNS (e.g., "haskell",
|
|
354
|
+
* "elixir") → returns "unknown" for language-specific patterns instead of running
|
|
355
|
+
* JS/TS patterns which would produce misleading results
|
|
356
|
+
*
|
|
357
|
+
* Universal patterns (naming convention) always run regardless of language.
|
|
358
|
+
*
|
|
359
|
+
* @param basePath - Root directory of the project
|
|
360
|
+
* @param sampledFiles - Array of relative file paths
|
|
361
|
+
* @param primaryLanguage - Optional primary language identifier from detection.ts LANGUAGE_MAP
|
|
362
|
+
* @returns CodePatterns with detected patterns and evidence
|
|
363
|
+
*/
|
|
364
|
+
function extractPatterns(basePath, sampledFiles, primaryLanguage) {
|
|
365
|
+
const evidence = {
|
|
366
|
+
asyncStyle: [],
|
|
367
|
+
errorHandling: [],
|
|
368
|
+
namingConvention: [],
|
|
369
|
+
};
|
|
370
|
+
const counts = {
|
|
371
|
+
asyncAwait: 0,
|
|
372
|
+
callbacks: 0,
|
|
373
|
+
promises: 0,
|
|
374
|
+
tryCatch: 0,
|
|
375
|
+
errorCallbacks: 0,
|
|
376
|
+
resultTypes: 0,
|
|
377
|
+
camelCase: 0,
|
|
378
|
+
snakeCase: 0,
|
|
379
|
+
pascalCase: 0,
|
|
380
|
+
};
|
|
381
|
+
// Track how many files contain each pattern type (for formatted output)
|
|
382
|
+
const fileCounts = {
|
|
383
|
+
asyncAwait: 0,
|
|
384
|
+
promises: 0,
|
|
385
|
+
callbacks: 0,
|
|
386
|
+
tryCatch: 0,
|
|
387
|
+
errorCallbacks: 0,
|
|
388
|
+
resultTypes: 0,
|
|
389
|
+
};
|
|
390
|
+
// Get language-specific patterns if available
|
|
391
|
+
// When primaryLanguage is undefined, fall back to JS/TS (sampled files are JS/TS extensions)
|
|
392
|
+
// When primaryLanguage is set but not in registry, skip language-specific patterns entirely
|
|
393
|
+
const languageEntry = primaryLanguage
|
|
394
|
+
? LANGUAGE_PATTERNS[primaryLanguage]
|
|
395
|
+
: LANGUAGE_PATTERNS["javascript/typescript"]; // Fallback for undefined only
|
|
396
|
+
// Language is "unsupported" only when it's explicitly set but not in our registry
|
|
397
|
+
// undefined → use JS/TS fallback (the sampled files are .ts/.js anyway)
|
|
398
|
+
// "haskell" → unsupported, don't run JS patterns against Haskell code
|
|
399
|
+
const languageUnsupported = primaryLanguage !== undefined && !LANGUAGE_PATTERNS[primaryLanguage];
|
|
400
|
+
// If language is explicitly set but not in registry, add evidence explaining why patterns aren't available
|
|
401
|
+
if (languageUnsupported) {
|
|
402
|
+
evidence.asyncStyle.push(`Language "${primaryLanguage}" not in pattern registry — async style detection not available`);
|
|
403
|
+
evidence.errorHandling.push(`Language "${primaryLanguage}" not in pattern registry — error handling detection not available`);
|
|
404
|
+
}
|
|
405
|
+
for (const file of sampledFiles) {
|
|
406
|
+
let content;
|
|
407
|
+
try {
|
|
408
|
+
const fullPath = join(basePath, file);
|
|
409
|
+
const buffer = Buffer.alloc(MAX_FILE_SAMPLE_BYTES);
|
|
410
|
+
const fd = openSync(fullPath, "r");
|
|
411
|
+
try {
|
|
412
|
+
const bytesRead = readSync(fd, buffer, 0, MAX_FILE_SAMPLE_BYTES, 0);
|
|
413
|
+
content = buffer.toString("utf-8", 0, bytesRead);
|
|
414
|
+
}
|
|
415
|
+
finally {
|
|
416
|
+
closeSync(fd);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
continue; // Skip unreadable files
|
|
421
|
+
}
|
|
422
|
+
// Only run language-specific patterns if we have a valid language entry
|
|
423
|
+
// This prevents misleading results from running JS/TS patterns against Haskell, etc.
|
|
424
|
+
if (!languageUnsupported && languageEntry) {
|
|
425
|
+
// Count async patterns using language-appropriate patterns
|
|
426
|
+
// Use String.match() to avoid mutating lastIndex on regex with /g flag
|
|
427
|
+
const asyncModernMatches = content.match(languageEntry.asyncStyle.modern) || [];
|
|
428
|
+
counts.asyncAwait += asyncModernMatches.length;
|
|
429
|
+
if (asyncModernMatches.length > 0) {
|
|
430
|
+
fileCounts.asyncAwait++;
|
|
431
|
+
if (evidence.asyncStyle.length < 3) {
|
|
432
|
+
evidence.asyncStyle.push(`${file}: ${languageEntry.asyncStyle.modernLabel} (${asyncModernMatches.length} occurrences)`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// For JS/TS, also check callbacks (universal pattern)
|
|
436
|
+
if (primaryLanguage === "javascript/typescript") {
|
|
437
|
+
const callbackMatches = content.match(CALLBACK_RE) || [];
|
|
438
|
+
counts.callbacks += callbackMatches.length;
|
|
439
|
+
if (callbackMatches.length > 0) {
|
|
440
|
+
fileCounts.callbacks++;
|
|
441
|
+
if (evidence.asyncStyle.length < 3) {
|
|
442
|
+
evidence.asyncStyle.push(`${file}: callbacks (${callbackMatches.length} occurrences)`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const asyncLegacyMatches = content.match(languageEntry.asyncStyle.legacy) || [];
|
|
447
|
+
counts.promises += asyncLegacyMatches.length;
|
|
448
|
+
if (asyncLegacyMatches.length > 0) {
|
|
449
|
+
fileCounts.promises++;
|
|
450
|
+
if (evidence.asyncStyle.length < 3) {
|
|
451
|
+
evidence.asyncStyle.push(`${file}: ${languageEntry.asyncStyle.legacyLabel} (${asyncLegacyMatches.length} occurrences)`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
// Count error handling patterns using language-appropriate patterns
|
|
455
|
+
const errorStructuredMatches = content.match(languageEntry.errorHandling.structured) || [];
|
|
456
|
+
counts.tryCatch += errorStructuredMatches.length;
|
|
457
|
+
if (errorStructuredMatches.length > 0) {
|
|
458
|
+
fileCounts.tryCatch++;
|
|
459
|
+
if (evidence.errorHandling.length < 3) {
|
|
460
|
+
evidence.errorHandling.push(`${file}: ${languageEntry.errorHandling.structuredLabel} (${errorStructuredMatches.length} occurrences)`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
const errorInlineMatches = content.match(languageEntry.errorHandling.inline) || [];
|
|
464
|
+
counts.errorCallbacks += errorInlineMatches.length;
|
|
465
|
+
if (errorInlineMatches.length > 0) {
|
|
466
|
+
fileCounts.errorCallbacks++;
|
|
467
|
+
if (evidence.errorHandling.length < 3) {
|
|
468
|
+
evidence.errorHandling.push(`${file}: ${languageEntry.errorHandling.inlineLabel} (${errorInlineMatches.length} occurrences)`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
// Result types are still useful for some languages (Rust, fp-ts)
|
|
472
|
+
const resultTypeMatches = content.match(RESULT_TYPE_RE) || [];
|
|
473
|
+
counts.resultTypes += resultTypeMatches.length;
|
|
474
|
+
if (resultTypeMatches.length > 0) {
|
|
475
|
+
fileCounts.resultTypes++;
|
|
476
|
+
if (evidence.errorHandling.length < 3) {
|
|
477
|
+
evidence.errorHandling.push(`${file}: result-types (${resultTypeMatches.length} occurrences)`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// Count naming convention patterns (universal across all languages)
|
|
482
|
+
// These patterns work regardless of whether the language is in the registry
|
|
483
|
+
const camelMatches = content.match(CAMEL_CASE_RE) || [];
|
|
484
|
+
counts.camelCase += camelMatches.length;
|
|
485
|
+
const snakeMatches = content.match(SNAKE_CASE_RE) || [];
|
|
486
|
+
counts.snakeCase += snakeMatches.length;
|
|
487
|
+
const pascalMatches = content.match(PASCAL_CASE_RE) || [];
|
|
488
|
+
counts.pascalCase += pascalMatches.length;
|
|
489
|
+
}
|
|
490
|
+
// Add naming evidence
|
|
491
|
+
if (counts.camelCase > 0) {
|
|
492
|
+
evidence.namingConvention.push(`camelCase: ${counts.camelCase} occurrences`);
|
|
493
|
+
}
|
|
494
|
+
if (counts.snakeCase > 0) {
|
|
495
|
+
evidence.namingConvention.push(`snake_case: ${counts.snakeCase} occurrences`);
|
|
496
|
+
}
|
|
497
|
+
if (counts.pascalCase > 0) {
|
|
498
|
+
evidence.namingConvention.push(`PascalCase: ${counts.pascalCase} occurrences`);
|
|
499
|
+
}
|
|
500
|
+
// For explicitly set but unrecognized languages, return "unknown" for language-specific patterns
|
|
501
|
+
// but still provide naming convention detection (which is universal)
|
|
502
|
+
if (languageUnsupported) {
|
|
503
|
+
return {
|
|
504
|
+
asyncStyle: "unknown",
|
|
505
|
+
errorHandling: "unknown",
|
|
506
|
+
namingConvention: determineNamingConvention(counts),
|
|
507
|
+
evidence,
|
|
508
|
+
fileCounts,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
return {
|
|
512
|
+
asyncStyle: determineAsyncStyle(counts),
|
|
513
|
+
errorHandling: determineErrorHandling(counts),
|
|
514
|
+
namingConvention: determineNamingConvention(counts),
|
|
515
|
+
evidence,
|
|
516
|
+
fileCounts,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Determine the primary async style based on pattern counts.
|
|
521
|
+
*/
|
|
522
|
+
function determineAsyncStyle(counts) {
|
|
523
|
+
const total = counts.asyncAwait + counts.callbacks + counts.promises;
|
|
524
|
+
if (total === 0)
|
|
525
|
+
return "unknown";
|
|
526
|
+
const asyncAwaitRatio = counts.asyncAwait / total;
|
|
527
|
+
const callbackRatio = counts.callbacks / total;
|
|
528
|
+
const promiseRatio = counts.promises / total;
|
|
529
|
+
// If one style dominates (>60%), report it
|
|
530
|
+
if (asyncAwaitRatio > 0.6)
|
|
531
|
+
return "async/await";
|
|
532
|
+
if (callbackRatio > 0.6)
|
|
533
|
+
return "callbacks";
|
|
534
|
+
if (promiseRatio > 0.6)
|
|
535
|
+
return "promises";
|
|
536
|
+
return "mixed";
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Determine the primary error handling style based on pattern counts.
|
|
540
|
+
*/
|
|
541
|
+
function determineErrorHandling(counts) {
|
|
542
|
+
const total = counts.tryCatch + counts.errorCallbacks + counts.resultTypes;
|
|
543
|
+
if (total === 0)
|
|
544
|
+
return "unknown";
|
|
545
|
+
const tryCatchRatio = counts.tryCatch / total;
|
|
546
|
+
const errorCallbackRatio = counts.errorCallbacks / total;
|
|
547
|
+
const resultTypeRatio = counts.resultTypes / total;
|
|
548
|
+
if (tryCatchRatio > 0.6)
|
|
549
|
+
return "try/catch";
|
|
550
|
+
if (errorCallbackRatio > 0.6)
|
|
551
|
+
return "error-callbacks";
|
|
552
|
+
if (resultTypeRatio > 0.6)
|
|
553
|
+
return "result-types";
|
|
554
|
+
return "mixed";
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Determine the primary naming convention based on pattern counts.
|
|
558
|
+
*/
|
|
559
|
+
function determineNamingConvention(counts) {
|
|
560
|
+
const total = counts.camelCase + counts.snakeCase + counts.pascalCase;
|
|
561
|
+
if (total === 0)
|
|
562
|
+
return "unknown";
|
|
563
|
+
// PascalCase is usually for types/classes, so we compare camelCase vs snake_case
|
|
564
|
+
const camelRatio = counts.camelCase / total;
|
|
565
|
+
const snakeRatio = counts.snakeCase / total;
|
|
566
|
+
if (camelRatio > 0.6)
|
|
567
|
+
return "camelCase";
|
|
568
|
+
if (snakeRatio > 0.6)
|
|
569
|
+
return "snake_case";
|
|
570
|
+
if (counts.pascalCase > counts.camelCase && counts.pascalCase > counts.snakeCase)
|
|
571
|
+
return "PascalCase";
|
|
572
|
+
return "mixed";
|
|
573
|
+
}
|
|
574
|
+
// ─── Formatting ─────────────────────────────────────────────────────────────────
|
|
575
|
+
/**
|
|
576
|
+
* Format a CodebaseBrief as LLM-readable markdown.
|
|
577
|
+
*
|
|
578
|
+
* @param brief - The codebase brief to format
|
|
579
|
+
* @returns Markdown string capped at MAX_CODEBASE_BRIEF_CHARS
|
|
580
|
+
*/
|
|
581
|
+
export function formatCodebaseBrief(brief) {
|
|
582
|
+
const sections = [];
|
|
583
|
+
// Tech Stack section
|
|
584
|
+
sections.push("## Tech Stack");
|
|
585
|
+
if (brief.techStack.primaryLanguage) {
|
|
586
|
+
sections.push(`- **Language:** ${brief.techStack.primaryLanguage}`);
|
|
587
|
+
}
|
|
588
|
+
if (brief.techStack.packageManager) {
|
|
589
|
+
sections.push(`- **Package Manager:** ${brief.techStack.packageManager}`);
|
|
590
|
+
}
|
|
591
|
+
if (brief.techStack.detectedFiles.length > 0) {
|
|
592
|
+
const files = brief.techStack.detectedFiles.slice(0, 10).join(", ");
|
|
593
|
+
sections.push(`- **Project Files:** ${files}`);
|
|
594
|
+
}
|
|
595
|
+
sections.push(`- **Monorepo:** ${brief.techStack.isMonorepo ? "Yes" : "No"}`);
|
|
596
|
+
sections.push(`- **Has Tests:** ${brief.techStack.hasTests ? "Yes" : "No"}`);
|
|
597
|
+
sections.push(`- **Has CI:** ${brief.techStack.hasCI ? "Yes" : "No"}`);
|
|
598
|
+
// Module Structure section
|
|
599
|
+
sections.push("");
|
|
600
|
+
sections.push("## Module Structure");
|
|
601
|
+
if (brief.moduleStructure.topLevelDirs.length > 0) {
|
|
602
|
+
sections.push(`- **Top-level dirs:** ${brief.moduleStructure.topLevelDirs.join(", ")}`);
|
|
603
|
+
}
|
|
604
|
+
if (brief.moduleStructure.srcSubdirs.length > 0) {
|
|
605
|
+
sections.push(`- **Source subdirs:** ${brief.moduleStructure.srcSubdirs.join(", ")}`);
|
|
606
|
+
}
|
|
607
|
+
// Code Patterns section
|
|
608
|
+
sections.push("");
|
|
609
|
+
sections.push("## Code Patterns");
|
|
610
|
+
// Format async style with file counts
|
|
611
|
+
const fc = brief.patterns.fileCounts;
|
|
612
|
+
if (brief.patterns.asyncStyle === "unknown") {
|
|
613
|
+
sections.push(`- **Async Style:** ${brief.patterns.asyncStyle}`);
|
|
614
|
+
}
|
|
615
|
+
else {
|
|
616
|
+
const asyncParts = [];
|
|
617
|
+
if (fc.asyncAwait > 0)
|
|
618
|
+
asyncParts.push(`${fc.asyncAwait} async/await`);
|
|
619
|
+
if (fc.promises > 0)
|
|
620
|
+
asyncParts.push(`${fc.promises} .then()`);
|
|
621
|
+
if (fc.callbacks > 0)
|
|
622
|
+
asyncParts.push(`${fc.callbacks} callback`);
|
|
623
|
+
const asyncDetail = asyncParts.length > 0 ? ` (${asyncParts.map(p => p + " files").join(" vs ")})` : "";
|
|
624
|
+
sections.push(`- **Async Style:** ${brief.patterns.asyncStyle}${asyncDetail}`);
|
|
625
|
+
}
|
|
626
|
+
// Format error handling with file counts
|
|
627
|
+
if (brief.patterns.errorHandling === "unknown") {
|
|
628
|
+
sections.push(`- **Error Handling:** ${brief.patterns.errorHandling}`);
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
const errorParts = [];
|
|
632
|
+
if (fc.tryCatch > 0)
|
|
633
|
+
errorParts.push(`${fc.tryCatch} try/catch`);
|
|
634
|
+
if (fc.errorCallbacks > 0)
|
|
635
|
+
errorParts.push(`${fc.errorCallbacks} error-callback`);
|
|
636
|
+
if (fc.resultTypes > 0)
|
|
637
|
+
errorParts.push(`${fc.resultTypes} result-type`);
|
|
638
|
+
const errorDetail = errorParts.length > 0 ? ` (${errorParts.map(p => p + " files").join(" vs ")})` : "";
|
|
639
|
+
sections.push(`- **Error Handling:** ${brief.patterns.errorHandling}${errorDetail}`);
|
|
640
|
+
}
|
|
641
|
+
sections.push(`- **Naming Convention:** ${brief.patterns.namingConvention}`);
|
|
642
|
+
let result = sections.join("\n");
|
|
643
|
+
// Truncate if necessary
|
|
644
|
+
if (result.length > MAX_CODEBASE_BRIEF_CHARS) {
|
|
645
|
+
result = result.slice(0, MAX_CODEBASE_BRIEF_CHARS - 3) + "...";
|
|
646
|
+
}
|
|
647
|
+
return result;
|
|
648
|
+
}
|
|
649
|
+
// ─── Prior Context Aggregation ──────────────────────────────────────────────────
|
|
650
|
+
/** Maximum characters per section in the prior context brief. */
|
|
651
|
+
const MAX_SECTION_CHARS = 2000;
|
|
652
|
+
/** Maximum total characters for the prior context brief. */
|
|
653
|
+
const MAX_PRIOR_CONTEXT_CHARS = 6000;
|
|
654
|
+
/**
|
|
655
|
+
* Aggregate prior context from GSD artifacts.
|
|
656
|
+
*
|
|
657
|
+
* Reads DECISIONS.md, REQUIREMENTS.md, KNOWLEDGE.md from the .gsd directory
|
|
658
|
+
* and milestone summaries from each milestone's MILESTONE-SUMMARY.md file.
|
|
659
|
+
*
|
|
660
|
+
* @param basePath - Root directory of the project (contains .gsd/)
|
|
661
|
+
* @returns PriorContextBrief with aggregated context
|
|
662
|
+
*/
|
|
663
|
+
export async function aggregatePriorContext(basePath) {
|
|
664
|
+
const gsdPath = join(basePath, ".gsd");
|
|
665
|
+
// Load decisions
|
|
666
|
+
const decisionsContent = await loadFile(join(gsdPath, "DECISIONS.md"));
|
|
667
|
+
const decisions = parseDecisions(decisionsContent);
|
|
668
|
+
// Load requirements
|
|
669
|
+
const requirementsContent = await loadFile(join(gsdPath, "REQUIREMENTS.md"));
|
|
670
|
+
const requirements = parseRequirements(requirementsContent);
|
|
671
|
+
// Load knowledge
|
|
672
|
+
const knowledgeContent = await loadFile(join(gsdPath, "KNOWLEDGE.md"));
|
|
673
|
+
const knowledge = truncateSection(knowledgeContent || "", MAX_SECTION_CHARS);
|
|
674
|
+
// Load milestone summaries
|
|
675
|
+
const summaries = await loadMilestoneSummaries(gsdPath);
|
|
676
|
+
return {
|
|
677
|
+
decisions,
|
|
678
|
+
requirements,
|
|
679
|
+
knowledge: knowledge || "No prior knowledge recorded.",
|
|
680
|
+
summaries: summaries || "No prior milestone summaries.",
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Parse decisions from DECISIONS.md content.
|
|
685
|
+
*
|
|
686
|
+
* Groups decisions by scope (e.g., "pattern", "architecture").
|
|
687
|
+
*/
|
|
688
|
+
function parseDecisions(content) {
|
|
689
|
+
const byScope = new Map();
|
|
690
|
+
if (!content) {
|
|
691
|
+
return { byScope, totalCount: 0 };
|
|
692
|
+
}
|
|
693
|
+
// Parse table rows: | D001 | M001/S01 | pattern | ... |
|
|
694
|
+
// Skip header rows (start with | # or |---)
|
|
695
|
+
const lines = content.split("\n");
|
|
696
|
+
let totalCount = 0;
|
|
697
|
+
for (const line of lines) {
|
|
698
|
+
const trimmed = line.trim();
|
|
699
|
+
// Skip non-table lines, header, and separator rows
|
|
700
|
+
if (!trimmed.startsWith("|"))
|
|
701
|
+
continue;
|
|
702
|
+
if (trimmed.startsWith("| #") || trimmed.startsWith("|---") || trimmed.startsWith("| -"))
|
|
703
|
+
continue;
|
|
704
|
+
// Parse: | D001 | M001/S01 | pattern | Decision | Choice | Rationale | Revisable? | Made By |
|
|
705
|
+
const cells = trimmed
|
|
706
|
+
.split("|")
|
|
707
|
+
.map((c) => c.trim())
|
|
708
|
+
.filter((c) => c.length > 0);
|
|
709
|
+
if (cells.length < 6)
|
|
710
|
+
continue;
|
|
711
|
+
const id = cells[0]; // D001
|
|
712
|
+
if (!id.match(/^D\d+$/))
|
|
713
|
+
continue; // Must be a decision ID
|
|
714
|
+
const scope = cells[2]; // pattern, architecture, etc.
|
|
715
|
+
const decision = cells[3];
|
|
716
|
+
const choice = cells[4];
|
|
717
|
+
const rationale = cells[5];
|
|
718
|
+
const entry = { id, scope, decision, choice, rationale };
|
|
719
|
+
if (!byScope.has(scope)) {
|
|
720
|
+
byScope.set(scope, []);
|
|
721
|
+
}
|
|
722
|
+
byScope.get(scope).push(entry);
|
|
723
|
+
totalCount++;
|
|
724
|
+
}
|
|
725
|
+
return { byScope, totalCount };
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Parse requirements from REQUIREMENTS.md content.
|
|
729
|
+
*
|
|
730
|
+
* Groups requirements by status (active, validated, deferred).
|
|
731
|
+
*/
|
|
732
|
+
function parseRequirements(content) {
|
|
733
|
+
const result = {
|
|
734
|
+
active: [],
|
|
735
|
+
validated: [],
|
|
736
|
+
deferred: [],
|
|
737
|
+
totalCount: 0,
|
|
738
|
+
};
|
|
739
|
+
if (!content) {
|
|
740
|
+
return result;
|
|
741
|
+
}
|
|
742
|
+
// Parse requirement entries: ### R101 — Description
|
|
743
|
+
// Look for Status: line to determine status
|
|
744
|
+
const reqBlocks = content.split(/(?=^### R\d+)/m);
|
|
745
|
+
for (const block of reqBlocks) {
|
|
746
|
+
const idMatch = block.match(/^### (R\d+)\s*—\s*(.+)/m);
|
|
747
|
+
if (!idMatch)
|
|
748
|
+
continue;
|
|
749
|
+
const id = idMatch[1];
|
|
750
|
+
const description = idMatch[2].trim();
|
|
751
|
+
// Extract status from "- Status: active" line
|
|
752
|
+
const statusMatch = block.match(/^-\s*Status:\s*(\w+)/m);
|
|
753
|
+
const statusRaw = statusMatch ? statusMatch[1].toLowerCase() : "active";
|
|
754
|
+
let status = "active";
|
|
755
|
+
if (statusRaw === "validated")
|
|
756
|
+
status = "validated";
|
|
757
|
+
else if (statusRaw === "deferred")
|
|
758
|
+
status = "deferred";
|
|
759
|
+
else if (statusRaw === "out-of-scope" || statusRaw === "outofscope")
|
|
760
|
+
status = "out-of-scope";
|
|
761
|
+
const entry = { id, description, status };
|
|
762
|
+
if (status === "active")
|
|
763
|
+
result.active.push(entry);
|
|
764
|
+
else if (status === "validated")
|
|
765
|
+
result.validated.push(entry);
|
|
766
|
+
else if (status === "deferred")
|
|
767
|
+
result.deferred.push(entry);
|
|
768
|
+
result.totalCount++;
|
|
769
|
+
}
|
|
770
|
+
return result;
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Load and combine milestone summaries from each milestone directory.
|
|
774
|
+
*
|
|
775
|
+
* Returns combined content, truncated to MAX_SECTION_CHARS.
|
|
776
|
+
*/
|
|
777
|
+
async function loadMilestoneSummaries(gsdPath) {
|
|
778
|
+
const milestonesPath = join(gsdPath, "milestones");
|
|
779
|
+
const summaries = [];
|
|
780
|
+
try {
|
|
781
|
+
const entries = readdirSyncNode(milestonesPath, { withFileTypes: true });
|
|
782
|
+
const milestoneIds = entries
|
|
783
|
+
.filter((e) => e.isDirectory() && e.name.match(/^M\d+/))
|
|
784
|
+
.map((e) => e.name)
|
|
785
|
+
.sort(); // Sort by milestone ID
|
|
786
|
+
for (const mid of milestoneIds) {
|
|
787
|
+
const summaryPath = join(milestonesPath, mid, "MILESTONE-SUMMARY.md");
|
|
788
|
+
const content = await loadFile(summaryPath);
|
|
789
|
+
if (content) {
|
|
790
|
+
// Extract the one-liner and first section for brevity
|
|
791
|
+
const oneLiner = extractOneLiner(content);
|
|
792
|
+
summaries.push(`### ${mid}\n${oneLiner}`);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
catch {
|
|
797
|
+
// Milestones directory doesn't exist or not readable
|
|
798
|
+
}
|
|
799
|
+
if (summaries.length === 0) {
|
|
800
|
+
return "";
|
|
801
|
+
}
|
|
802
|
+
return truncateSection(summaries.join("\n\n"), MAX_SECTION_CHARS);
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Extract the one-liner summary from a MILESTONE-SUMMARY.md.
|
|
806
|
+
*
|
|
807
|
+
* Looks for bold text on a line by itself (e.g., "**Completed X and Y**").
|
|
808
|
+
*/
|
|
809
|
+
function extractOneLiner(content) {
|
|
810
|
+
const lines = content.split("\n");
|
|
811
|
+
for (const line of lines) {
|
|
812
|
+
const trimmed = line.trim();
|
|
813
|
+
// Look for **bold text** that's the whole line
|
|
814
|
+
if (trimmed.startsWith("**") && trimmed.endsWith("**") && trimmed.length > 4) {
|
|
815
|
+
return trimmed.slice(2, -2);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
// Fallback: return first non-empty, non-heading line
|
|
819
|
+
for (const line of lines) {
|
|
820
|
+
const trimmed = line.trim();
|
|
821
|
+
if (trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("---")) {
|
|
822
|
+
return trimmed.slice(0, 200);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return "Summary available";
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Truncate content to maxChars without cutting mid-section.
|
|
829
|
+
*
|
|
830
|
+
* Prefers to cut at section boundaries (## headings) or paragraph breaks.
|
|
831
|
+
*/
|
|
832
|
+
function truncateSection(content, maxChars) {
|
|
833
|
+
if (content.length <= maxChars) {
|
|
834
|
+
return content;
|
|
835
|
+
}
|
|
836
|
+
const SECTION_SUFFIX = "\n\n[truncated]"; // 14 chars
|
|
837
|
+
const WORD_SUFFIX = "... [truncated]"; // 15 chars
|
|
838
|
+
// Reserve space for suffix in all slicing operations
|
|
839
|
+
const sectionMaxSlice = maxChars - SECTION_SUFFIX.length;
|
|
840
|
+
const wordMaxSlice = maxChars - WORD_SUFFIX.length;
|
|
841
|
+
// Try to cut at a section boundary
|
|
842
|
+
const truncated = content.slice(0, sectionMaxSlice);
|
|
843
|
+
const lastSection = truncated.lastIndexOf("\n## ");
|
|
844
|
+
if (lastSection > sectionMaxSlice * 0.5) {
|
|
845
|
+
return truncated.slice(0, lastSection).trim() + SECTION_SUFFIX;
|
|
846
|
+
}
|
|
847
|
+
// Try to cut at a paragraph break
|
|
848
|
+
const lastPara = truncated.lastIndexOf("\n\n");
|
|
849
|
+
if (lastPara > sectionMaxSlice * 0.5) {
|
|
850
|
+
return truncated.slice(0, lastPara).trim() + SECTION_SUFFIX;
|
|
851
|
+
}
|
|
852
|
+
// Last resort: cut at word boundary
|
|
853
|
+
const wordTruncated = content.slice(0, wordMaxSlice);
|
|
854
|
+
const lastSpace = wordTruncated.lastIndexOf(" ");
|
|
855
|
+
if (lastSpace > wordMaxSlice * 0.8) {
|
|
856
|
+
return wordTruncated.slice(0, lastSpace).trim() + WORD_SUFFIX;
|
|
857
|
+
}
|
|
858
|
+
return content.slice(0, wordMaxSlice) + WORD_SUFFIX;
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Format a PriorContextBrief as LLM-readable markdown.
|
|
862
|
+
*
|
|
863
|
+
* @param brief - The prior context brief to format
|
|
864
|
+
* @returns Markdown string capped at MAX_PRIOR_CONTEXT_CHARS
|
|
865
|
+
*/
|
|
866
|
+
export function formatPriorContextBrief(brief) {
|
|
867
|
+
const sections = [];
|
|
868
|
+
// Decisions section
|
|
869
|
+
sections.push("## Prior Decisions");
|
|
870
|
+
if (brief.decisions.totalCount === 0) {
|
|
871
|
+
sections.push("No prior decisions recorded.");
|
|
872
|
+
}
|
|
873
|
+
else {
|
|
874
|
+
sections.push(`${brief.decisions.totalCount} decisions recorded.`);
|
|
875
|
+
sections.push("");
|
|
876
|
+
// Group by scope
|
|
877
|
+
for (const [scope, entries] of brief.decisions.byScope) {
|
|
878
|
+
sections.push(`### ${scope}`);
|
|
879
|
+
for (const entry of entries.slice(0, 5)) { // Limit per scope
|
|
880
|
+
sections.push(`- **${entry.id}:** ${entry.decision} → ${entry.choice}`);
|
|
881
|
+
}
|
|
882
|
+
if (entries.length > 5) {
|
|
883
|
+
sections.push(`- _(${entries.length - 5} more in this scope)_`);
|
|
884
|
+
}
|
|
885
|
+
sections.push("");
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
// Requirements section
|
|
889
|
+
sections.push("## Prior Requirements");
|
|
890
|
+
const reqTotal = brief.requirements.totalCount;
|
|
891
|
+
if (reqTotal === 0) {
|
|
892
|
+
sections.push("No prior requirements recorded.");
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
sections.push(`${reqTotal} requirements: ${brief.requirements.active.length} active, ` +
|
|
896
|
+
`${brief.requirements.validated.length} validated, ` +
|
|
897
|
+
`${brief.requirements.deferred.length} deferred.`);
|
|
898
|
+
sections.push("");
|
|
899
|
+
// Show active requirements (most relevant)
|
|
900
|
+
if (brief.requirements.active.length > 0) {
|
|
901
|
+
sections.push("### Active");
|
|
902
|
+
for (const req of brief.requirements.active.slice(0, 10)) {
|
|
903
|
+
sections.push(`- **${req.id}:** ${req.description}`);
|
|
904
|
+
}
|
|
905
|
+
if (brief.requirements.active.length > 10) {
|
|
906
|
+
sections.push(`- _(${brief.requirements.active.length - 10} more active)_`);
|
|
907
|
+
}
|
|
908
|
+
sections.push("");
|
|
909
|
+
}
|
|
910
|
+
// Show validated (recently completed)
|
|
911
|
+
if (brief.requirements.validated.length > 0) {
|
|
912
|
+
sections.push("### Validated");
|
|
913
|
+
for (const req of brief.requirements.validated.slice(0, 5)) {
|
|
914
|
+
sections.push(`- **${req.id}:** ${req.description}`);
|
|
915
|
+
}
|
|
916
|
+
if (brief.requirements.validated.length > 5) {
|
|
917
|
+
sections.push(`- _(${brief.requirements.validated.length - 5} more validated)_`);
|
|
918
|
+
}
|
|
919
|
+
sections.push("");
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
// Knowledge section
|
|
923
|
+
sections.push("## Prior Knowledge");
|
|
924
|
+
if (brief.knowledge === "No prior knowledge recorded.") {
|
|
925
|
+
sections.push(brief.knowledge);
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
sections.push(truncateSection(brief.knowledge, MAX_SECTION_CHARS));
|
|
929
|
+
}
|
|
930
|
+
sections.push("");
|
|
931
|
+
// Summaries section
|
|
932
|
+
sections.push("## Prior Milestone Summaries");
|
|
933
|
+
if (brief.summaries === "No prior milestone summaries.") {
|
|
934
|
+
sections.push(brief.summaries);
|
|
935
|
+
}
|
|
936
|
+
else {
|
|
937
|
+
sections.push(truncateSection(brief.summaries, MAX_SECTION_CHARS));
|
|
938
|
+
}
|
|
939
|
+
let result = sections.join("\n");
|
|
940
|
+
// Final truncation if total exceeds max
|
|
941
|
+
if (result.length > MAX_PRIOR_CONTEXT_CHARS) {
|
|
942
|
+
result = truncateSection(result, MAX_PRIOR_CONTEXT_CHARS);
|
|
943
|
+
}
|
|
944
|
+
return result;
|
|
945
|
+
}
|
|
946
|
+
// ─── Ecosystem Research ─────────────────────────────────────────────────────────
|
|
947
|
+
/** Maximum characters for the ecosystem brief. */
|
|
948
|
+
const MAX_ECOSYSTEM_BRIEF_CHARS = 4000;
|
|
949
|
+
/**
|
|
950
|
+
* Research the ecosystem for best practices and known issues.
|
|
951
|
+
*
|
|
952
|
+
* Ecosystem research is now performed during the discussion session (between
|
|
953
|
+
* Layer 1 and Layer 2) using whatever web search tools are available to the
|
|
954
|
+
* LLM — native Anthropic web search for Claude, search-the-web for other
|
|
955
|
+
* providers. The preparation phase focuses on mechanical work only.
|
|
956
|
+
*
|
|
957
|
+
* @param _techStack - Array of technology names from codebase analysis (unused)
|
|
958
|
+
* @param _basePath - Root directory of the project (unused)
|
|
959
|
+
* @returns EcosystemBrief indicating research happens during discussion
|
|
960
|
+
*/
|
|
961
|
+
export async function researchEcosystem(_techStack, _basePath) {
|
|
962
|
+
return {
|
|
963
|
+
available: false,
|
|
964
|
+
queries: [],
|
|
965
|
+
findings: [],
|
|
966
|
+
skippedReason: "Ecosystem research is performed during the discussion using web search tools, not during preparation.",
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Run the preparation phase before a discussion session.
|
|
971
|
+
*
|
|
972
|
+
* Orchestrates all three analyzers (codebase, prior context, ecosystem)
|
|
973
|
+
* with TUI progress updates. Returns early if preparation is disabled.
|
|
974
|
+
*
|
|
975
|
+
* @param basePath - Root directory of the project
|
|
976
|
+
* @param ui - UI context for progress notifications (null = silent mode)
|
|
977
|
+
* @param prefs - Preferences controlling preparation behavior
|
|
978
|
+
* @returns PreparationResult with all briefs and metadata
|
|
979
|
+
*/
|
|
980
|
+
export async function runPreparation(basePath, ui, prefs) {
|
|
981
|
+
const startTime = performance.now();
|
|
982
|
+
// Check if preparation is disabled
|
|
983
|
+
const preparationEnabled = prefs.discuss_preparation !== false; // Default: true
|
|
984
|
+
if (!preparationEnabled) {
|
|
985
|
+
// Return minimal result with empty briefs
|
|
986
|
+
const emptyCodebase = {
|
|
987
|
+
techStack: {
|
|
988
|
+
primaryLanguage: undefined,
|
|
989
|
+
detectedFiles: [],
|
|
990
|
+
packageManager: undefined,
|
|
991
|
+
isMonorepo: false,
|
|
992
|
+
hasTests: false,
|
|
993
|
+
hasCI: false,
|
|
994
|
+
},
|
|
995
|
+
moduleStructure: {
|
|
996
|
+
topLevelDirs: [],
|
|
997
|
+
srcSubdirs: [],
|
|
998
|
+
totalFilesSampled: 0,
|
|
999
|
+
},
|
|
1000
|
+
patterns: {
|
|
1001
|
+
asyncStyle: "unknown",
|
|
1002
|
+
errorHandling: "unknown",
|
|
1003
|
+
namingConvention: "unknown",
|
|
1004
|
+
evidence: {
|
|
1005
|
+
asyncStyle: [],
|
|
1006
|
+
errorHandling: [],
|
|
1007
|
+
namingConvention: [],
|
|
1008
|
+
},
|
|
1009
|
+
fileCounts: {
|
|
1010
|
+
asyncAwait: 0,
|
|
1011
|
+
promises: 0,
|
|
1012
|
+
callbacks: 0,
|
|
1013
|
+
tryCatch: 0,
|
|
1014
|
+
errorCallbacks: 0,
|
|
1015
|
+
resultTypes: 0,
|
|
1016
|
+
},
|
|
1017
|
+
},
|
|
1018
|
+
sampledFiles: [],
|
|
1019
|
+
};
|
|
1020
|
+
const emptyPriorContext = {
|
|
1021
|
+
decisions: {
|
|
1022
|
+
byScope: new Map(),
|
|
1023
|
+
totalCount: 0,
|
|
1024
|
+
},
|
|
1025
|
+
requirements: {
|
|
1026
|
+
active: [],
|
|
1027
|
+
validated: [],
|
|
1028
|
+
deferred: [],
|
|
1029
|
+
totalCount: 0,
|
|
1030
|
+
},
|
|
1031
|
+
knowledge: "No prior knowledge recorded.",
|
|
1032
|
+
summaries: "No prior milestone summaries.",
|
|
1033
|
+
};
|
|
1034
|
+
const emptyEcosystem = {
|
|
1035
|
+
available: false,
|
|
1036
|
+
queries: [],
|
|
1037
|
+
findings: [],
|
|
1038
|
+
skippedReason: "Preparation phase disabled.",
|
|
1039
|
+
};
|
|
1040
|
+
return {
|
|
1041
|
+
codebase: emptyCodebase,
|
|
1042
|
+
codebaseBrief: "",
|
|
1043
|
+
priorContext: emptyPriorContext,
|
|
1044
|
+
priorContextBrief: "",
|
|
1045
|
+
ecosystem: emptyEcosystem,
|
|
1046
|
+
ecosystemBrief: "",
|
|
1047
|
+
enabled: false,
|
|
1048
|
+
ecosystemResearchPerformed: false,
|
|
1049
|
+
durationMs: performance.now() - startTime,
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
// --- Phase 1: Analyze codebase ---
|
|
1053
|
+
ui?.notify("Analyzing codebase...", "info");
|
|
1054
|
+
const codebase = await analyzeCodebase(basePath);
|
|
1055
|
+
const codebaseBrief = formatCodebaseBrief(codebase);
|
|
1056
|
+
ui?.notify("✓ Analyzed codebase", "success");
|
|
1057
|
+
// --- Phase 2: Review prior context ---
|
|
1058
|
+
ui?.notify("Reviewing prior context...", "info");
|
|
1059
|
+
const priorContext = await aggregatePriorContext(basePath);
|
|
1060
|
+
const priorContextBrief = formatPriorContextBrief(priorContext);
|
|
1061
|
+
ui?.notify("✓ Reviewed prior context", "success");
|
|
1062
|
+
// --- Ecosystem research ---
|
|
1063
|
+
// Ecosystem research is now performed during the discussion session (between
|
|
1064
|
+
// Layer 1 and Layer 2) using available web search tools. The preparation
|
|
1065
|
+
// phase focuses on mechanical work only.
|
|
1066
|
+
const ecosystem = await researchEcosystem([], basePath);
|
|
1067
|
+
const ecosystemBrief = formatEcosystemBrief(ecosystem);
|
|
1068
|
+
return {
|
|
1069
|
+
codebase,
|
|
1070
|
+
codebaseBrief,
|
|
1071
|
+
priorContext,
|
|
1072
|
+
priorContextBrief,
|
|
1073
|
+
ecosystem,
|
|
1074
|
+
ecosystemBrief,
|
|
1075
|
+
enabled: true,
|
|
1076
|
+
ecosystemResearchPerformed: false,
|
|
1077
|
+
durationMs: performance.now() - startTime,
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Format an EcosystemBrief as LLM-readable markdown.
|
|
1082
|
+
*
|
|
1083
|
+
* Since ecosystem research now always returns unavailable from the preparation
|
|
1084
|
+
* phase (research happens during discussion using web search tools), this
|
|
1085
|
+
* function returns a simple fixed message.
|
|
1086
|
+
*
|
|
1087
|
+
* @param _brief - The ecosystem brief (unused, always unavailable from preparation)
|
|
1088
|
+
* @returns Markdown string directing the LLM to perform research during discussion
|
|
1089
|
+
*/
|
|
1090
|
+
export function formatEcosystemBrief(_brief) {
|
|
1091
|
+
return "## Ecosystem Research\n\nEcosystem research is performed during the discussion using web search tools.";
|
|
1092
|
+
}
|