gsd-pi 2.44.0-dev.d25d507 → 2.45.0-dev.1afbdaa
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/help-text.js +1 -1
- package/dist/loader.js +34 -0
- package/dist/resources/extensions/gsd/activity-log.js +7 -0
- package/dist/resources/extensions/gsd/auto/infra-errors.js +3 -0
- package/dist/resources/extensions/gsd/auto/phases.js +63 -77
- package/dist/resources/extensions/gsd/auto/run-unit.js +6 -3
- package/dist/resources/extensions/gsd/auto/session.js +0 -11
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +112 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +25 -96
- package/dist/resources/extensions/gsd/auto-prompts.js +24 -1
- package/dist/resources/extensions/gsd/auto-start.js +23 -5
- package/dist/resources/extensions/gsd/auto-timers.js +57 -3
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +4 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +14 -10
- package/dist/resources/extensions/gsd/auto.js +42 -60
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +170 -11
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +18 -0
- package/dist/resources/extensions/gsd/commands/catalog.js +7 -1
- package/dist/resources/extensions/gsd/commands/context.js +0 -4
- package/dist/resources/extensions/gsd/commands/handlers/core.js +2 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +10 -0
- package/dist/resources/extensions/gsd/commands/handlers/parallel.js +1 -1
- package/dist/resources/extensions/gsd/commands-mcp-status.js +187 -0
- package/dist/resources/extensions/gsd/crash-recovery.js +2 -4
- package/dist/resources/extensions/gsd/dashboard-overlay.js +0 -44
- package/dist/resources/extensions/gsd/db-writer.js +40 -22
- package/dist/resources/extensions/gsd/doctor-checks.js +167 -2
- package/dist/resources/extensions/gsd/doctor.js +13 -3
- package/dist/resources/extensions/gsd/git-service.js +8 -3
- package/dist/resources/extensions/gsd/gsd-db.js +28 -4
- package/dist/resources/extensions/gsd/guided-flow.js +1 -2
- package/dist/resources/extensions/gsd/markdown-renderer.js +1 -1
- package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +5 -18
- package/dist/resources/extensions/gsd/preferences-types.js +2 -2
- package/dist/resources/extensions/gsd/preferences.js +8 -4
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +21 -10
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +10 -23
- package/dist/resources/extensions/gsd/prompts/discuss.md +2 -2
- package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -15
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -3
- package/dist/resources/extensions/gsd/prompts/queue.md +2 -2
- package/dist/resources/extensions/gsd/prompts/quick-task.md +2 -0
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -6
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +3 -14
- package/dist/resources/extensions/gsd/prompts/research-slice.md +3 -3
- package/dist/resources/extensions/gsd/prompts/rethink.md +83 -0
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +7 -37
- package/dist/resources/extensions/gsd/provider-error-pause.js +7 -0
- package/dist/resources/extensions/gsd/repo-identity.js +45 -7
- package/dist/resources/extensions/gsd/rethink.js +115 -0
- package/dist/resources/extensions/gsd/session-lock.js +1 -3
- package/dist/resources/extensions/gsd/state.js +48 -3
- package/dist/resources/extensions/gsd/sync-lock.js +89 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +61 -11
- package/dist/resources/extensions/gsd/tools/complete-slice.js +56 -11
- package/dist/resources/extensions/gsd/tools/complete-task.js +50 -2
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +37 -1
- package/dist/resources/extensions/gsd/tools/plan-slice.js +31 -1
- package/dist/resources/extensions/gsd/tools/plan-task.js +28 -1
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +32 -2
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +86 -0
- package/dist/resources/extensions/gsd/tools/reopen-task.js +90 -0
- package/dist/resources/extensions/gsd/tools/replan-slice.js +34 -2
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +88 -0
- package/dist/resources/extensions/gsd/unit-ownership.js +85 -0
- package/dist/resources/extensions/gsd/workflow-events.js +102 -0
- package/dist/resources/extensions/gsd/workflow-logger.js +193 -0
- package/dist/resources/extensions/gsd/workflow-manifest.js +244 -0
- package/dist/resources/extensions/gsd/workflow-migration.js +280 -0
- package/dist/resources/extensions/gsd/workflow-projections.js +373 -0
- package/dist/resources/extensions/gsd/workflow-reconcile.js +411 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +34 -3
- package/dist/resources/extensions/gsd/worktree-resolver.js +43 -0
- package/dist/resources/extensions/gsd/write-intercept.js +84 -0
- package/dist/resources/extensions/mcp-client/index.js +14 -0
- package/dist/resources/extensions/voice/index.js +11 -16
- package/dist/resources/extensions/voice/linux-ready.js +67 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +2 -2
- 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 +1 -1
- 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 +2 -2
- 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 +2 -2
- 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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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/export-data/route_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.js +1 -1
- 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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.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_client-reference-manifest.js +1 -1
- 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_client-reference-manifest.js +1 -1
- 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_client-reference-manifest.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_client-reference-manifest.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_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 +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/page.js +1 -1
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
- 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/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/4024.11ca5c01938e5948.js +9 -0
- package/dist/web/standalone/.next/static/chunks/{3721.bf31263de6d5fa46.js → 485.243af25f0cdf50d6.js} +2 -2
- package/dist/web/standalone/.next/static/chunks/app/{page-b9367c5ae13b99c6.js → page-6654a8cca61a3d1c.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/webpack-0a4cd455ec4197d2.js +1 -0
- package/dist/web/standalone/.next/static/css/dd4ae3f58ac9b600.css +1 -0
- package/package.json +2 -1
- package/packages/native/dist/stream-process/index.js +2 -2
- package/packages/native/src/__tests__/stream-process.test.mjs +34 -0
- package/packages/native/src/stream-process/index.ts +2 -2
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +15 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +2 -0
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts +4 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js +10 -5
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js +185 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/local-model-check.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/local-model-check.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/local-model-check.js +41 -0
- package/packages/pi-coding-agent/dist/core/local-model-check.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +239 -10
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +13 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +40 -3
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-commands.test.js +206 -195
- package/packages/pi-coding-agent/dist/core/package-commands.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +6 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +17 -0
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.js +32 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +3 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +8 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +12 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.d.ts +15 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.js +40 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.js.map +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 +4 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +5 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +13 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +17 -8
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.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 +7 -3
- 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/auth-storage.ts +15 -1
- package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +2 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -1
- package/packages/pi-coding-agent/src/core/lifecycle-hooks.test.ts +227 -0
- package/packages/pi-coding-agent/src/core/lifecycle-hooks.ts +11 -5
- package/packages/pi-coding-agent/src/core/local-model-check.ts +45 -0
- package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +297 -11
- package/packages/pi-coding-agent/src/core/model-registry.ts +51 -4
- package/packages/pi-coding-agent/src/core/package-commands.test.ts +227 -205
- package/packages/pi-coding-agent/src/core/settings-manager.ts +9 -0
- package/packages/pi-coding-agent/src/main.ts +19 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/timestamp.test.ts +38 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +10 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +15 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/timestamp.ts +48 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +3 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +18 -3
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +16 -7
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +8 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/activity-log.ts +1 -0
- package/src/resources/extensions/gsd/auto/infra-errors.ts +3 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +0 -19
- package/src/resources/extensions/gsd/auto/phases.ts +69 -91
- package/src/resources/extensions/gsd/auto/run-unit.ts +6 -3
- package/src/resources/extensions/gsd/auto/session.ts +0 -18
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +131 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +0 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +25 -106
- package/src/resources/extensions/gsd/auto-prompts.ts +24 -1
- package/src/resources/extensions/gsd/auto-start.ts +26 -5
- package/src/resources/extensions/gsd/auto-timers.ts +64 -3
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +5 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +17 -11
- package/src/resources/extensions/gsd/auto.ts +44 -86
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +162 -11
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
- package/src/resources/extensions/gsd/commands/catalog.ts +7 -1
- package/src/resources/extensions/gsd/commands/context.ts +0 -5
- package/src/resources/extensions/gsd/commands/handlers/core.ts +2 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +10 -0
- package/src/resources/extensions/gsd/commands/handlers/parallel.ts +1 -1
- package/src/resources/extensions/gsd/commands-mcp-status.ts +247 -0
- package/src/resources/extensions/gsd/crash-recovery.ts +1 -5
- package/src/resources/extensions/gsd/dashboard-overlay.ts +0 -50
- package/src/resources/extensions/gsd/db-writer.ts +41 -27
- package/src/resources/extensions/gsd/doctor-checks.ts +180 -2
- package/src/resources/extensions/gsd/doctor-types.ts +7 -1
- package/src/resources/extensions/gsd/doctor.ts +13 -4
- package/src/resources/extensions/gsd/git-service.ts +6 -2
- package/src/resources/extensions/gsd/gsd-db.ts +32 -4
- package/src/resources/extensions/gsd/guided-flow.ts +1 -2
- package/src/resources/extensions/gsd/journal.ts +6 -1
- package/src/resources/extensions/gsd/markdown-renderer.ts +1 -1
- package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +5 -21
- package/src/resources/extensions/gsd/preferences-types.ts +2 -2
- package/src/resources/extensions/gsd/preferences.ts +7 -3
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +21 -10
- package/src/resources/extensions/gsd/prompts/complete-slice.md +10 -23
- package/src/resources/extensions/gsd/prompts/discuss.md +2 -2
- package/src/resources/extensions/gsd/prompts/execute-task.md +5 -15
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -3
- package/src/resources/extensions/gsd/prompts/queue.md +2 -2
- package/src/resources/extensions/gsd/prompts/quick-task.md +2 -0
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -6
- package/src/resources/extensions/gsd/prompts/replan-slice.md +3 -14
- package/src/resources/extensions/gsd/prompts/research-slice.md +3 -3
- package/src/resources/extensions/gsd/prompts/rethink.md +83 -0
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +7 -37
- package/src/resources/extensions/gsd/provider-error-pause.ts +9 -0
- package/src/resources/extensions/gsd/repo-identity.ts +46 -7
- package/src/resources/extensions/gsd/rethink.ts +154 -0
- package/src/resources/extensions/gsd/session-lock.ts +0 -4
- package/src/resources/extensions/gsd/state.ts +49 -1
- package/src/resources/extensions/gsd/sync-lock.ts +94 -0
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +5 -13
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +6 -10
- package/src/resources/extensions/gsd/tests/auto-pr-bugs.test.ts +88 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +96 -0
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +264 -228
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +317 -250
- package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +114 -0
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +2 -8
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +121 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/est-annotation-timeout.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/infra-error.test.ts +20 -2
- package/src/resources/extensions/gsd/tests/inherited-repo-home-dir.test.ts +121 -0
- package/src/resources/extensions/gsd/tests/integration-proof.test.ts +15 -24
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +8 -9
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +42 -3
- package/src/resources/extensions/gsd/tests/parallel-budget-atomicity.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +0 -7
- package/src/resources/extensions/gsd/tests/parallel-merge.test.ts +7 -8
- package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +20 -24
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +0 -2
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +9 -6
- package/src/resources/extensions/gsd/tests/post-mutation-hook.test.ts +171 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +7 -9
- package/src/resources/extensions/gsd/tests/projection-regression.test.ts +174 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +26 -21
- package/src/resources/extensions/gsd/tests/recovery-attempts-reset.test.ts +176 -0
- package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +155 -0
- package/src/resources/extensions/gsd/tests/reopen-task.test.ts +165 -0
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +1 -4
- package/src/resources/extensions/gsd/tests/stop-auto-merge-back.test.ts +67 -0
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +108 -0
- package/src/resources/extensions/gsd/tests/sync-lock.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/unit-ownership.test.ts +175 -0
- package/src/resources/extensions/gsd/tests/workflow-events.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +275 -0
- package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +171 -0
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +220 -0
- package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/write-intercept.test.ts +76 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +74 -11
- package/src/resources/extensions/gsd/tools/complete-slice.ts +68 -11
- package/src/resources/extensions/gsd/tools/complete-task.ts +63 -1
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +45 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +40 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +37 -1
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +39 -1
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +125 -0
- package/src/resources/extensions/gsd/tools/reopen-task.ts +129 -0
- package/src/resources/extensions/gsd/tools/replan-slice.ts +41 -1
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +127 -0
- package/src/resources/extensions/gsd/types.ts +8 -0
- package/src/resources/extensions/gsd/unit-ownership.ts +104 -0
- package/src/resources/extensions/gsd/workflow-events.ts +154 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +243 -0
- package/src/resources/extensions/gsd/workflow-manifest.ts +334 -0
- package/src/resources/extensions/gsd/workflow-migration.ts +345 -0
- package/src/resources/extensions/gsd/workflow-projections.ts +425 -0
- package/src/resources/extensions/gsd/workflow-reconcile.ts +503 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +41 -5
- package/src/resources/extensions/gsd/worktree-resolver.ts +44 -0
- package/src/resources/extensions/gsd/write-intercept.ts +90 -0
- package/src/resources/extensions/mcp-client/index.ts +20 -0
- package/src/resources/extensions/voice/index.ts +11 -21
- package/src/resources/extensions/voice/linux-ready.ts +87 -0
- package/src/resources/extensions/voice/tests/linux-ready.test.ts +124 -0
- package/dist/web/standalone/.next/static/chunks/4024.0de81b543b28b9fe.js +0 -9
- package/dist/web/standalone/.next/static/chunks/webpack-9014b5adb127a98a.js +0 -1
- package/dist/web/standalone/.next/static/css/8a727f372cf53002.css +0 -1
- /package/dist/web/standalone/.next/static/{tokoGmfkYfWf1_Yl_Gz7i → j-BskPs0nxxPeYY-bSrab}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{tokoGmfkYfWf1_Yl_Gz7i → j-BskPs0nxxPeYY-bSrab}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* completed-units-metrics-sync.test.ts — Regression tests for #2313.
|
|
3
|
+
*
|
|
4
|
+
* 1. completed-units.json should be archived (not wiped) on milestone transition
|
|
5
|
+
* 2. metrics.json should be in the worktree → project root sync file list
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import test from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { readFileSync, mkdtempSync, mkdirSync, writeFileSync, existsSync, cpSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
|
|
14
|
+
// ─── Bug 1: completed-units.json should be archived, not wiped ─────────────
|
|
15
|
+
|
|
16
|
+
const phasesSrcPath = join(import.meta.dirname, "..", "auto", "phases.ts");
|
|
17
|
+
const phasesSrc = readFileSync(phasesSrcPath, "utf-8");
|
|
18
|
+
|
|
19
|
+
test("#2313: completed-units.json should not be blindly wiped to [] on milestone transition", () => {
|
|
20
|
+
// The milestone transition block should NOT write an empty array to completed-units.json
|
|
21
|
+
// without first archiving the existing data. Look for the archive/rename pattern.
|
|
22
|
+
const transitionIdx = phasesSrc.indexOf("Milestone transition");
|
|
23
|
+
assert.ok(transitionIdx !== -1, "Milestone transition section exists");
|
|
24
|
+
|
|
25
|
+
// Find the completed-units handling block
|
|
26
|
+
const completedUnitsIdx = phasesSrc.indexOf("completed-units", transitionIdx);
|
|
27
|
+
assert.ok(completedUnitsIdx !== -1, "completed-units handling exists in transition");
|
|
28
|
+
|
|
29
|
+
// Get a window around the completed-units handling (1200 chars to
|
|
30
|
+
// accommodate CRLF line endings on Windows which inflate byte offsets).
|
|
31
|
+
const windowStart = Math.max(0, completedUnitsIdx - 300);
|
|
32
|
+
const windowEnd = Math.min(phasesSrc.length, completedUnitsIdx + 900);
|
|
33
|
+
const window = phasesSrc.slice(windowStart, windowEnd).toLowerCase();
|
|
34
|
+
|
|
35
|
+
// Should archive/rename the old file before resetting
|
|
36
|
+
const hasArchive = window.includes("archive") ||
|
|
37
|
+
window.includes("rename") ||
|
|
38
|
+
window.includes("cpsync") ||
|
|
39
|
+
window.includes("safecopy") ||
|
|
40
|
+
window.includes("completed-units-");
|
|
41
|
+
|
|
42
|
+
assert.ok(
|
|
43
|
+
hasArchive,
|
|
44
|
+
"completed-units.json should be archived before reset during milestone transition",
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// ─── Bug 2: metrics.json should be in the sync file lists ──────────────────
|
|
49
|
+
|
|
50
|
+
test("#2313: syncStateToProjectRoot should sync metrics.json", () => {
|
|
51
|
+
const syncSrcPath = join(import.meta.dirname, "..", "auto-worktree-sync.ts");
|
|
52
|
+
const syncSrc = readFileSync(syncSrcPath, "utf-8");
|
|
53
|
+
|
|
54
|
+
// syncStateToProjectRoot should copy metrics.json from worktree to project root
|
|
55
|
+
assert.ok(
|
|
56
|
+
syncSrc.includes("metrics.json"),
|
|
57
|
+
"auto-worktree-sync.ts should reference metrics.json for sync",
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("#2313: syncWorktreeStateBack should include metrics.json in root files list", () => {
|
|
62
|
+
const autoWorktreeSrcPath = join(import.meta.dirname, "..", "auto-worktree.ts");
|
|
63
|
+
const autoWorktreeSrc = readFileSync(autoWorktreeSrcPath, "utf-8");
|
|
64
|
+
|
|
65
|
+
// Find the rootFiles array in syncWorktreeStateBack
|
|
66
|
+
const syncBackIdx = autoWorktreeSrc.indexOf("syncWorktreeStateBack");
|
|
67
|
+
assert.ok(syncBackIdx !== -1, "syncWorktreeStateBack exists");
|
|
68
|
+
|
|
69
|
+
const rootFilesIdx = autoWorktreeSrc.indexOf("rootFiles", syncBackIdx);
|
|
70
|
+
assert.ok(rootFilesIdx !== -1, "rootFiles list exists in syncWorktreeStateBack");
|
|
71
|
+
|
|
72
|
+
// Get the rootFiles array content
|
|
73
|
+
const arrayStart = autoWorktreeSrc.indexOf("[", rootFilesIdx);
|
|
74
|
+
const arrayEnd = autoWorktreeSrc.indexOf("]", arrayStart);
|
|
75
|
+
const rootFilesBlock = autoWorktreeSrc.slice(arrayStart, arrayEnd);
|
|
76
|
+
|
|
77
|
+
assert.ok(
|
|
78
|
+
rootFilesBlock.includes("metrics.json"),
|
|
79
|
+
"metrics.json should be in syncWorktreeStateBack rootFiles list",
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// ─── Functional test: completed-units archive ────────────────────────────────
|
|
84
|
+
|
|
85
|
+
test("#2313: functional — completed-units archive creates milestone-specific file", () => {
|
|
86
|
+
const tmpBase = mkdtempSync(join(tmpdir(), "gsd-completed-units-"));
|
|
87
|
+
const gsdDir = join(tmpBase, ".gsd");
|
|
88
|
+
mkdirSync(gsdDir, { recursive: true });
|
|
89
|
+
|
|
90
|
+
// Simulate existing completed-units.json with data
|
|
91
|
+
const existing = [
|
|
92
|
+
{ type: "task", id: "T01" },
|
|
93
|
+
{ type: "slice", id: "S01" },
|
|
94
|
+
];
|
|
95
|
+
const completedKeysPath = join(gsdDir, "completed-units.json");
|
|
96
|
+
writeFileSync(completedKeysPath, JSON.stringify(existing, null, 2));
|
|
97
|
+
|
|
98
|
+
// Simulate the archive behavior: copy to milestone-specific file
|
|
99
|
+
const milestoneId = "M001";
|
|
100
|
+
const archivePath = join(gsdDir, `completed-units-${milestoneId}.json`);
|
|
101
|
+
cpSync(completedKeysPath, archivePath);
|
|
102
|
+
|
|
103
|
+
// Reset the main file
|
|
104
|
+
writeFileSync(completedKeysPath, JSON.stringify([], null, 2));
|
|
105
|
+
|
|
106
|
+
// Verify archive exists with original data
|
|
107
|
+
assert.ok(existsSync(archivePath), "archive file should exist");
|
|
108
|
+
const archived = JSON.parse(readFileSync(archivePath, "utf-8"));
|
|
109
|
+
assert.deepEqual(archived, existing, "archived data should match original");
|
|
110
|
+
|
|
111
|
+
// Verify main file is reset
|
|
112
|
+
const current = JSON.parse(readFileSync(completedKeysPath, "utf-8"));
|
|
113
|
+
assert.deepEqual(current, [], "current completed-units should be empty after transition");
|
|
114
|
+
});
|
|
@@ -30,12 +30,11 @@ test("writeLock creates lock file and readCrashLock reads it", (t) => {
|
|
|
30
30
|
const base = makeTmpBase();
|
|
31
31
|
t.after(() => cleanup(base));
|
|
32
32
|
|
|
33
|
-
writeLock(base, "execute-task", "M001/S01/T01",
|
|
33
|
+
writeLock(base, "execute-task", "M001/S01/T01", "/tmp/session.jsonl");
|
|
34
34
|
const lock = readCrashLock(base);
|
|
35
35
|
assert.ok(lock, "lock should exist");
|
|
36
36
|
assert.equal(lock!.unitType, "execute-task");
|
|
37
37
|
assert.equal(lock!.unitId, "M001/S01/T01");
|
|
38
|
-
assert.equal(lock!.completedUnits, 3);
|
|
39
38
|
assert.equal(lock!.sessionFile, "/tmp/session.jsonl");
|
|
40
39
|
assert.equal(lock!.pid, process.pid);
|
|
41
40
|
});
|
|
@@ -54,7 +53,7 @@ test("clearLock removes existing lock file", (t) => {
|
|
|
54
53
|
const base = makeTmpBase();
|
|
55
54
|
t.after(() => cleanup(base));
|
|
56
55
|
|
|
57
|
-
writeLock(base, "plan-slice", "M001/S01"
|
|
56
|
+
writeLock(base, "plan-slice", "M001/S01");
|
|
58
57
|
assert.ok(readCrashLock(base), "lock should exist before clear");
|
|
59
58
|
clearLock(base);
|
|
60
59
|
assert.equal(readCrashLock(base), null, "lock should be gone after clear");
|
|
@@ -77,7 +76,6 @@ test("isLockProcessAlive returns true for current process (different pid)", () =
|
|
|
77
76
|
unitType: "execute-task",
|
|
78
77
|
unitId: "M001/S01/T01",
|
|
79
78
|
unitStartedAt: new Date().toISOString(),
|
|
80
|
-
completedUnits: 0,
|
|
81
79
|
};
|
|
82
80
|
assert.equal(isLockProcessAlive(lock), false, "own PID should return false");
|
|
83
81
|
});
|
|
@@ -89,7 +87,6 @@ test("isLockProcessAlive returns false for dead PID", () => {
|
|
|
89
87
|
unitType: "execute-task",
|
|
90
88
|
unitId: "M001/S01/T01",
|
|
91
89
|
unitStartedAt: new Date().toISOString(),
|
|
92
|
-
completedUnits: 0,
|
|
93
90
|
};
|
|
94
91
|
assert.equal(isLockProcessAlive(lock), false);
|
|
95
92
|
});
|
|
@@ -100,7 +97,6 @@ test("isLockProcessAlive returns false for invalid PIDs", () => {
|
|
|
100
97
|
unitType: "x",
|
|
101
98
|
unitId: "x",
|
|
102
99
|
unitStartedAt: new Date().toISOString(),
|
|
103
|
-
completedUnits: 0,
|
|
104
100
|
};
|
|
105
101
|
assert.equal(isLockProcessAlive({ ...base, pid: 0 } as LockData), false);
|
|
106
102
|
assert.equal(isLockProcessAlive({ ...base, pid: -1 } as LockData), false);
|
|
@@ -116,11 +112,9 @@ test("formatCrashInfo includes unit type, id, and PID", () => {
|
|
|
116
112
|
unitType: "complete-slice",
|
|
117
113
|
unitId: "M002/S03",
|
|
118
114
|
unitStartedAt: "2025-01-01T00:01:00.000Z",
|
|
119
|
-
completedUnits: 7,
|
|
120
115
|
};
|
|
121
116
|
const info = formatCrashInfo(lock);
|
|
122
117
|
assert.ok(info.includes("complete-slice"));
|
|
123
118
|
assert.ok(info.includes("M002/S03"));
|
|
124
119
|
assert.ok(info.includes("12345"));
|
|
125
|
-
assert.ok(info.includes("7"));
|
|
126
120
|
});
|
|
@@ -195,9 +195,6 @@ function makeMockDeps(overrides?: Partial<LoopDeps>): LoopDeps & { callLog: stri
|
|
|
195
195
|
getPriorSliceCompletionBlocker: () => null,
|
|
196
196
|
getMainBranch: () => "main",
|
|
197
197
|
closeoutUnit: async () => {},
|
|
198
|
-
verifyExpectedArtifact: () => true,
|
|
199
|
-
clearUnitRuntimeRecord: () => {},
|
|
200
|
-
writeUnitRuntimeRecord: () => {},
|
|
201
198
|
recordOutcome: () => {},
|
|
202
199
|
writeLock: () => {},
|
|
203
200
|
captureAvailableSkills: () => {},
|
|
@@ -483,6 +483,85 @@ describe('db-writer', () => {
|
|
|
483
483
|
}
|
|
484
484
|
});
|
|
485
485
|
|
|
486
|
+
test('saveArtifactToDb — shrinkage guard preserves larger existing file', async () => {
|
|
487
|
+
const tmpDir = makeTmpDir();
|
|
488
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
489
|
+
openDatabase(dbPath);
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
const fullContent = '# Full Research\n\n' + 'x'.repeat(20000) + '\n';
|
|
493
|
+
const abbreviatedContent = '# Summary\n\nShort version.\n';
|
|
494
|
+
|
|
495
|
+
// Pre-create the file with full content (simulating a prior `write` tool call)
|
|
496
|
+
const relPath = 'milestones/M001/M001-RESEARCH.md';
|
|
497
|
+
const filePath = path.join(tmpDir, '.gsd', relPath);
|
|
498
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
499
|
+
fs.writeFileSync(filePath, fullContent);
|
|
500
|
+
|
|
501
|
+
// Call saveArtifactToDb with abbreviated content — should trigger shrinkage guard
|
|
502
|
+
await saveArtifactToDb({
|
|
503
|
+
path: relPath,
|
|
504
|
+
artifact_type: 'RESEARCH',
|
|
505
|
+
content: abbreviatedContent,
|
|
506
|
+
milestone_id: 'M001',
|
|
507
|
+
}, tmpDir);
|
|
508
|
+
|
|
509
|
+
// Disk file should be preserved (not overwritten)
|
|
510
|
+
assert.deepStrictEqual(
|
|
511
|
+
fs.readFileSync(filePath, 'utf-8'),
|
|
512
|
+
fullContent,
|
|
513
|
+
'disk file preserved — shrinkage guard prevented overwrite',
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
// DB should contain the full disk content, not the abbreviated content
|
|
517
|
+
const adapter = _getAdapter();
|
|
518
|
+
const row = adapter!
|
|
519
|
+
.prepare('SELECT full_content FROM artifacts WHERE path = ?')
|
|
520
|
+
.get(relPath);
|
|
521
|
+
assert.deepStrictEqual(
|
|
522
|
+
row!['full_content'],
|
|
523
|
+
fullContent,
|
|
524
|
+
'DB stores the richer disk content instead of abbreviated content',
|
|
525
|
+
);
|
|
526
|
+
} finally {
|
|
527
|
+
closeDatabase();
|
|
528
|
+
cleanupDir(tmpDir);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test('saveArtifactToDb — allows overwrite when new content is similar size', async () => {
|
|
533
|
+
const tmpDir = makeTmpDir();
|
|
534
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
535
|
+
openDatabase(dbPath);
|
|
536
|
+
|
|
537
|
+
try {
|
|
538
|
+
const oldContent = '# Summary v1\n\nOriginal content here.\n';
|
|
539
|
+
const newContent = '# Summary v2\n\nUpdated content here with more details.\n';
|
|
540
|
+
|
|
541
|
+
const relPath = 'milestones/M001/M001-SUMMARY.md';
|
|
542
|
+
const filePath = path.join(tmpDir, '.gsd', relPath);
|
|
543
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
544
|
+
fs.writeFileSync(filePath, oldContent);
|
|
545
|
+
|
|
546
|
+
await saveArtifactToDb({
|
|
547
|
+
path: relPath,
|
|
548
|
+
artifact_type: 'SUMMARY',
|
|
549
|
+
content: newContent,
|
|
550
|
+
milestone_id: 'M001',
|
|
551
|
+
}, tmpDir);
|
|
552
|
+
|
|
553
|
+
// Disk file should be updated (new content is >=50% of old size)
|
|
554
|
+
assert.deepStrictEqual(
|
|
555
|
+
fs.readFileSync(filePath, 'utf-8'),
|
|
556
|
+
newContent,
|
|
557
|
+
'disk file updated when new content is similar size',
|
|
558
|
+
);
|
|
559
|
+
} finally {
|
|
560
|
+
closeDatabase();
|
|
561
|
+
cleanupDir(tmpDir);
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
|
|
486
565
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
487
566
|
// Full Round-Trip: DB → Markdown → Parse → Compare
|
|
488
567
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* derive-state-db-disk-reconcile.test.ts — #2416
|
|
3
|
+
*
|
|
4
|
+
* After migration to DB-backed state, milestones that exist on disk
|
|
5
|
+
* (in .gsd/milestones/) but were never imported into the DB become
|
|
6
|
+
* invisible to deriveStateFromDb(). This test verifies that
|
|
7
|
+
* deriveStateFromDb reconciles disk milestones with DB milestones.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
|
|
14
|
+
import { deriveStateFromDb, invalidateStateCache } from "../state.ts";
|
|
15
|
+
import {
|
|
16
|
+
openDatabase,
|
|
17
|
+
closeDatabase,
|
|
18
|
+
insertMilestone,
|
|
19
|
+
insertSlice,
|
|
20
|
+
insertTask,
|
|
21
|
+
} from "../gsd-db.ts";
|
|
22
|
+
import { createTestContext } from "./test-helpers.ts";
|
|
23
|
+
|
|
24
|
+
const { assertEq, assertTrue, report } = createTestContext();
|
|
25
|
+
|
|
26
|
+
function createFixtureBase(): string {
|
|
27
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-disk-reconcile-"));
|
|
28
|
+
mkdirSync(join(base, ".gsd", "milestones"), { recursive: true });
|
|
29
|
+
return base;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function writeFile(base: string, relativePath: string, content: string): void {
|
|
33
|
+
const full = join(base, ".gsd", relativePath);
|
|
34
|
+
mkdirSync(join(full, ".."), { recursive: true });
|
|
35
|
+
writeFileSync(full, content);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function cleanup(base: string): void {
|
|
39
|
+
rmSync(base, { recursive: true, force: true });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const CONTEXT_CONTENT = `# M002: Disk-Only Milestone
|
|
43
|
+
|
|
44
|
+
This milestone exists on disk but not in the DB.
|
|
45
|
+
|
|
46
|
+
## Must-Haves
|
|
47
|
+
- Something important
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
const ROADMAP_CONTENT = `# M002: Disk-Only Milestone
|
|
51
|
+
|
|
52
|
+
**Vision:** Test disk reconciliation.
|
|
53
|
+
|
|
54
|
+
## Slices
|
|
55
|
+
|
|
56
|
+
- [ ] **S01: First Slice** \`risk:low\` \`depends:[]\`
|
|
57
|
+
> Do something.
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
async function main(): Promise<void> {
|
|
61
|
+
console.log("\n=== #2416: deriveStateFromDb reconciles disk milestones ===");
|
|
62
|
+
|
|
63
|
+
// Set up: M001 in DB, M002 on disk only
|
|
64
|
+
const base = createFixtureBase();
|
|
65
|
+
const dbPath = join(base, ".gsd", "gsd.db");
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
openDatabase(dbPath);
|
|
69
|
+
|
|
70
|
+
// M001 is in the DB with a complete status
|
|
71
|
+
insertMilestone({ id: "M001", title: "M001: DB Milestone", status: "complete", depends_on: [] });
|
|
72
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Done Slice", status: "complete", depends: [] });
|
|
73
|
+
|
|
74
|
+
// Write M001 summary on disk (marks it complete on filesystem too)
|
|
75
|
+
writeFile(base, "milestones/M001/SUMMARY.md", "# M001: DB Milestone\n\nDone.");
|
|
76
|
+
|
|
77
|
+
// M002 exists ONLY on disk, not in DB
|
|
78
|
+
writeFile(base, "milestones/M002/CONTEXT.md", CONTEXT_CONTENT);
|
|
79
|
+
writeFile(base, "milestones/M002/ROADMAP.md", ROADMAP_CONTENT);
|
|
80
|
+
|
|
81
|
+
invalidateStateCache();
|
|
82
|
+
const state = await deriveStateFromDb(base);
|
|
83
|
+
|
|
84
|
+
// M002 should be visible in the registry
|
|
85
|
+
const m002Entry = state.registry.find((m) => m.id === "M002");
|
|
86
|
+
assertTrue(
|
|
87
|
+
m002Entry !== undefined,
|
|
88
|
+
"M002 (disk-only milestone) should appear in state.registry (#2416)",
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// M001 should still be in the registry
|
|
92
|
+
const m001Entry = state.registry.find((m) => m.id === "M001");
|
|
93
|
+
assertTrue(
|
|
94
|
+
m001Entry !== undefined,
|
|
95
|
+
"M001 (DB milestone) should still appear in state.registry",
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// The active milestone should be M002 (since M001 is complete)
|
|
99
|
+
assertTrue(
|
|
100
|
+
state.activeMilestone !== null,
|
|
101
|
+
"There should be an active milestone",
|
|
102
|
+
);
|
|
103
|
+
if (state.activeMilestone) {
|
|
104
|
+
assertEq(
|
|
105
|
+
state.activeMilestone.id,
|
|
106
|
+
"M002",
|
|
107
|
+
"Active milestone should be M002 (disk-only, not complete) (#2416)",
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
} finally {
|
|
111
|
+
closeDatabase();
|
|
112
|
+
cleanup(base);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
report();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
main().catch((err) => {
|
|
119
|
+
console.error(err);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
});
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
insertArtifact,
|
|
12
12
|
isDbAvailable,
|
|
13
13
|
insertMilestone,
|
|
14
|
+
getAllMilestones,
|
|
14
15
|
insertSlice,
|
|
15
16
|
insertTask,
|
|
16
17
|
} from '../gsd-db.ts';
|
|
@@ -962,4 +963,63 @@ describe('derive-state-db', async () => {
|
|
|
962
963
|
cleanup(base);
|
|
963
964
|
}
|
|
964
965
|
});
|
|
966
|
+
|
|
967
|
+
// ─── Regression: disk-only milestones synced into DB (#2416) ─────────
|
|
968
|
+
test('derive-state-db: disk-only milestone auto-synced into DB (#2416)', async () => {
|
|
969
|
+
const base = createFixtureBase();
|
|
970
|
+
try {
|
|
971
|
+
// M001 is complete and exists in DB. M002 was queued on disk only — no DB row.
|
|
972
|
+
writeFile(base, 'milestones/M001/M001-SUMMARY.md', '# M001 Summary\n\nDone.');
|
|
973
|
+
writeFile(base, 'milestones/M002/M002-CONTEXT.md', '# M002: Queued\n\nQueued milestone.');
|
|
974
|
+
|
|
975
|
+
openDatabase(':memory:');
|
|
976
|
+
// Only insert M001 — simulates the state after migration guard ran then /gsd queue added M002
|
|
977
|
+
insertMilestone({ id: 'M001', title: 'First', status: 'complete' });
|
|
978
|
+
|
|
979
|
+
invalidateStateCache();
|
|
980
|
+
const state = await deriveStateFromDb(base);
|
|
981
|
+
|
|
982
|
+
// Before the fix, M002 was invisible: getAllMilestones() returned only M001
|
|
983
|
+
// (complete) → phase='complete' → auto-mode stopped.
|
|
984
|
+
// After the fix, deriveStateFromDb reconciles disk dirs and inserts M002.
|
|
985
|
+
assert.deepStrictEqual(state.phase, 'pre-planning', 'disk-sync-2416: phase is pre-planning, not complete');
|
|
986
|
+
assert.deepStrictEqual(state.registry.length, 2, 'disk-sync-2416: both milestones visible in registry');
|
|
987
|
+
assert.deepStrictEqual(state.registry[0]?.id, 'M001', 'disk-sync-2416: registry[0] is M001');
|
|
988
|
+
assert.deepStrictEqual(state.registry[0]?.status, 'complete', 'disk-sync-2416: M001 is complete');
|
|
989
|
+
assert.deepStrictEqual(state.registry[1]?.id, 'M002', 'disk-sync-2416: registry[1] is M002');
|
|
990
|
+
assert.deepStrictEqual(state.registry[1]?.status, 'active', 'disk-sync-2416: M002 is active');
|
|
991
|
+
assert.deepStrictEqual(state.activeMilestone?.id, 'M002', 'disk-sync-2416: activeMilestone is M002');
|
|
992
|
+
|
|
993
|
+
closeDatabase();
|
|
994
|
+
} finally {
|
|
995
|
+
closeDatabase();
|
|
996
|
+
cleanup(base);
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
// ─── Queued milestone row not clobbered by later plan (#2416 root cause) ──
|
|
1001
|
+
test('derive-state-db: queued milestone row survives gsd_plan_milestone INSERT OR IGNORE', async () => {
|
|
1002
|
+
try {
|
|
1003
|
+
openDatabase(':memory:');
|
|
1004
|
+
|
|
1005
|
+
// Simulates gsd_milestone_generate_id inserting a minimal queued row
|
|
1006
|
+
insertMilestone({ id: 'M001', status: 'queued' });
|
|
1007
|
+
|
|
1008
|
+
const before = getAllMilestones();
|
|
1009
|
+
assert.equal(before.length, 1, 'queued-row: one row after generate_id');
|
|
1010
|
+
assert.equal(before[0]!.status, 'queued', 'queued-row: status is queued');
|
|
1011
|
+
|
|
1012
|
+
// Simulates gsd_plan_milestone calling insertMilestone (INSERT OR IGNORE)
|
|
1013
|
+
insertMilestone({ id: 'M001', title: 'Planned Title', status: 'active' });
|
|
1014
|
+
|
|
1015
|
+
const after = getAllMilestones();
|
|
1016
|
+
assert.equal(after.length, 1, 'queued-row: still one row after plan');
|
|
1017
|
+
// INSERT OR IGNORE keeps the original row — status stays 'queued'
|
|
1018
|
+
assert.equal(after[0]!.status, 'queued', 'queued-row: INSERT OR IGNORE preserves original status');
|
|
1019
|
+
|
|
1020
|
+
closeDatabase();
|
|
1021
|
+
} finally {
|
|
1022
|
+
closeDatabase();
|
|
1023
|
+
}
|
|
1024
|
+
});
|
|
965
1025
|
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* est-annotation-timeout.test.ts — Regression tests for #2243.
|
|
3
|
+
*
|
|
4
|
+
* Tasks with `est: 30m` or `est: 2h` annotations should get extended
|
|
5
|
+
* supervision timeouts. The parseEstimateMinutes helper should parse
|
|
6
|
+
* estimate strings, and startUnitSupervision should use them.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { readFileSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
|
|
14
|
+
const timersSrcPath = join(import.meta.dirname, "..", "auto-timers.ts");
|
|
15
|
+
const timersSrc = readFileSync(timersSrcPath, "utf-8");
|
|
16
|
+
|
|
17
|
+
// ─── Source analysis: parseEstimateMinutes exists and is exported ────────────
|
|
18
|
+
|
|
19
|
+
test("#2243: auto-timers.ts should export parseEstimateMinutes", () => {
|
|
20
|
+
assert.ok(
|
|
21
|
+
timersSrc.includes("export function parseEstimateMinutes"),
|
|
22
|
+
"parseEstimateMinutes should be exported from auto-timers.ts",
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// ─── Inline unit test of parseEstimateMinutes logic ─────────────────────────
|
|
27
|
+
// Since importing the module pulls in heavy deps, test the parsing logic inline.
|
|
28
|
+
|
|
29
|
+
function parseEstimateMinutes(estimate: string): number | null {
|
|
30
|
+
if (!estimate || typeof estimate !== "string") return null;
|
|
31
|
+
const trimmed = estimate.trim();
|
|
32
|
+
if (!trimmed) return null;
|
|
33
|
+
|
|
34
|
+
let totalMinutes = 0;
|
|
35
|
+
let matched = false;
|
|
36
|
+
|
|
37
|
+
const hoursMatch = trimmed.match(/(\d+)\s*h/i);
|
|
38
|
+
if (hoursMatch) {
|
|
39
|
+
totalMinutes += Number(hoursMatch[1]) * 60;
|
|
40
|
+
matched = true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const minutesMatch = trimmed.match(/(\d+)\s*m/i);
|
|
44
|
+
if (minutesMatch) {
|
|
45
|
+
totalMinutes += Number(minutesMatch[1]);
|
|
46
|
+
matched = true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return matched ? totalMinutes : null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
test("#2243: parseEstimateMinutes parses '30m' correctly", () => {
|
|
53
|
+
assert.equal(parseEstimateMinutes("30m"), 30);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("#2243: parseEstimateMinutes parses '2h' correctly", () => {
|
|
57
|
+
assert.equal(parseEstimateMinutes("2h"), 120);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("#2243: parseEstimateMinutes parses '1h30m' correctly", () => {
|
|
61
|
+
assert.equal(parseEstimateMinutes("1h30m"), 90);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("#2243: parseEstimateMinutes parses '15m' correctly", () => {
|
|
65
|
+
assert.equal(parseEstimateMinutes("15m"), 15);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("#2243: parseEstimateMinutes returns null for empty string", () => {
|
|
69
|
+
assert.equal(parseEstimateMinutes(""), null);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("#2243: parseEstimateMinutes returns null for invalid string", () => {
|
|
73
|
+
assert.equal(parseEstimateMinutes("not a time"), null);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// ─── Source analysis: startUnitSupervision uses task estimates ───────────────
|
|
77
|
+
|
|
78
|
+
test("#2243: startUnitSupervision should reference task estimates for timeout scaling", () => {
|
|
79
|
+
const usesEstimate =
|
|
80
|
+
timersSrc.includes("parseEstimateMinutes") &&
|
|
81
|
+
timersSrc.includes("estimateMinutes") &&
|
|
82
|
+
timersSrc.includes("taskEstimate");
|
|
83
|
+
|
|
84
|
+
assert.ok(
|
|
85
|
+
usesEstimate,
|
|
86
|
+
"startUnitSupervision should use task estimate annotations for timeout scaling",
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("#2243: SupervisionContext should accept an optional taskEstimate field", () => {
|
|
91
|
+
const ctxIdx = timersSrc.indexOf("SupervisionContext");
|
|
92
|
+
assert.ok(ctxIdx !== -1, "SupervisionContext interface exists");
|
|
93
|
+
|
|
94
|
+
const ctxEnd = timersSrc.indexOf("}", ctxIdx);
|
|
95
|
+
const ctxBlock = timersSrc.slice(ctxIdx, ctxEnd);
|
|
96
|
+
|
|
97
|
+
assert.ok(
|
|
98
|
+
ctxBlock.includes("taskEstimate"),
|
|
99
|
+
"SupervisionContext should include a taskEstimate field",
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("#2243: timeouts should be scaled by estimate (timeoutScale in source)", () => {
|
|
104
|
+
assert.ok(
|
|
105
|
+
timersSrc.includes("timeoutScale"),
|
|
106
|
+
"auto-timers.ts should use a timeoutScale factor derived from est: annotations",
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("#2243: idle timeout should NOT be scaled (idle is idle regardless of estimate)", () => {
|
|
111
|
+
// Find the idleTimeoutMs line
|
|
112
|
+
const idleIdx = timersSrc.indexOf("const idleTimeoutMs");
|
|
113
|
+
assert.ok(idleIdx !== -1, "idleTimeoutMs variable exists");
|
|
114
|
+
|
|
115
|
+
const idleLine = timersSrc.slice(idleIdx, timersSrc.indexOf("\n", idleIdx));
|
|
116
|
+
assert.ok(
|
|
117
|
+
!idleLine.includes("timeoutScale"),
|
|
118
|
+
"idleTimeoutMs should NOT be scaled — idle is idle",
|
|
119
|
+
);
|
|
120
|
+
});
|
|
@@ -64,7 +64,7 @@ describe('gsd-db', () => {
|
|
|
64
64
|
// Check schema_version table
|
|
65
65
|
const adapter = _getAdapter()!;
|
|
66
66
|
const version = adapter.prepare('SELECT MAX(version) as version FROM schema_version').get();
|
|
67
|
-
assert.deepStrictEqual(version?.['version'],
|
|
67
|
+
assert.deepStrictEqual(version?.['version'], 11, 'schema version should be 11');
|
|
68
68
|
|
|
69
69
|
// Check tables exist by querying them
|
|
70
70
|
const dRows = adapter.prepare('SELECT count(*) as cnt FROM decisions').get();
|
|
@@ -7,10 +7,13 @@ import { isInfrastructureError, INFRA_ERROR_CODES } from "../auto/infra-errors.j
|
|
|
7
7
|
// ── INFRA_ERROR_CODES constant ───────────────────────────────────────────────
|
|
8
8
|
|
|
9
9
|
test("INFRA_ERROR_CODES contains the expected codes", () => {
|
|
10
|
-
for (const code of [
|
|
10
|
+
for (const code of [
|
|
11
|
+
"ENOSPC", "ENOMEM", "EROFS", "EDQUOT", "EMFILE", "ENFILE",
|
|
12
|
+
"ECONNREFUSED", "ENOTFOUND", "ENETUNREACH",
|
|
13
|
+
]) {
|
|
11
14
|
assert.ok(INFRA_ERROR_CODES.has(code), `missing ${code}`);
|
|
12
15
|
}
|
|
13
|
-
assert.equal(INFRA_ERROR_CODES.size,
|
|
16
|
+
assert.equal(INFRA_ERROR_CODES.size, 9, "unexpected extra codes");
|
|
14
17
|
});
|
|
15
18
|
|
|
16
19
|
// ── isInfrastructureError: code property detection ───────────────────────────
|
|
@@ -45,6 +48,21 @@ test("detects ENFILE via code property", () => {
|
|
|
45
48
|
assert.equal(isInfrastructureError(err), "ENFILE");
|
|
46
49
|
});
|
|
47
50
|
|
|
51
|
+
test("detects ECONNREFUSED via code property", () => {
|
|
52
|
+
const err = Object.assign(new Error("connect ECONNREFUSED 127.0.0.1:3000"), { code: "ECONNREFUSED" });
|
|
53
|
+
assert.equal(isInfrastructureError(err), "ECONNREFUSED");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("detects ENOTFOUND via code property", () => {
|
|
57
|
+
const err = Object.assign(new Error("getaddrinfo ENOTFOUND api.example.com"), { code: "ENOTFOUND" });
|
|
58
|
+
assert.equal(isInfrastructureError(err), "ENOTFOUND");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("detects ENETUNREACH via code property", () => {
|
|
62
|
+
const err = Object.assign(new Error("connect ENETUNREACH 2607:f8b0:4004::"), { code: "ENETUNREACH" });
|
|
63
|
+
assert.equal(isInfrastructureError(err), "ENETUNREACH");
|
|
64
|
+
});
|
|
65
|
+
|
|
48
66
|
// ── isInfrastructureError: message fallback ──────────────────────────────────
|
|
49
67
|
|
|
50
68
|
test("falls back to message scanning when no code property", () => {
|