gsd-pi 2.77.0-dev.58d3d4d6c → 2.77.0-dev.cfd69e714
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/claude-cli-check.js +5 -1
- package/dist/headless.js +49 -4
- package/dist/resource-loader.d.ts +40 -0
- package/dist/resource-loader.js +32 -13
- package/dist/resources/extensions/browser-tools/capture.js +9 -0
- package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
- package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
- package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
- package/dist/resources/extensions/browser-tools/tools/forms.js +5 -1
- package/dist/resources/extensions/browser-tools/tools/intent.js +5 -1
- package/dist/resources/extensions/claude-code-cli/readiness.js +5 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +481 -17
- package/dist/resources/extensions/gsd/auto/loop.js +43 -0
- package/dist/resources/extensions/gsd/auto/phases.js +15 -21
- package/dist/resources/extensions/gsd/auto/session.js +0 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +102 -24
- package/dist/resources/extensions/gsd/auto-model-selection.js +124 -4
- package/dist/resources/extensions/gsd/auto-post-unit.js +71 -64
- package/dist/resources/extensions/gsd/auto-prompts.js +329 -102
- package/dist/resources/extensions/gsd/auto-recovery.js +195 -23
- package/dist/resources/extensions/gsd/auto-start.js +34 -24
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +47 -7
- package/dist/resources/extensions/gsd/auto-worktree.js +122 -26
- package/dist/resources/extensions/gsd/auto.js +31 -20
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +9 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +209 -0
- package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +3 -6
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -3
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +127 -9
- package/dist/resources/extensions/gsd/component-loader.js +447 -0
- package/dist/resources/extensions/gsd/component-types.js +69 -0
- package/dist/resources/extensions/gsd/detection.js +49 -1
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/dist/resources/extensions/gsd/gate-registry.js +2 -2
- package/dist/resources/extensions/gsd/git-constants.js +28 -1
- package/dist/resources/extensions/gsd/git-self-heal.js +27 -0
- package/dist/resources/extensions/gsd/git-service.js +126 -2
- package/dist/resources/extensions/gsd/gsd-db.js +6 -3
- package/dist/resources/extensions/gsd/guided-flow.js +17 -5
- package/dist/resources/extensions/gsd/memory-extractor.js +7 -1
- package/dist/resources/extensions/gsd/milestone-scope-classifier.js +299 -0
- package/dist/resources/extensions/gsd/model-cost-table.js +3 -0
- package/dist/resources/extensions/gsd/model-router.js +6 -0
- package/dist/resources/extensions/gsd/native-git-bridge.js +34 -4
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -2
- package/dist/resources/extensions/gsd/safety/git-checkpoint.js +11 -0
- package/dist/resources/extensions/gsd/service-tier.js +5 -2
- package/dist/resources/extensions/gsd/session-lock.js +19 -10
- package/dist/resources/extensions/gsd/skill-manifest.js +168 -0
- package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +278 -8
- package/dist/resources/extensions/gsd/state.js +44 -33
- package/dist/resources/extensions/gsd/sync-lock.js +98 -42
- package/dist/resources/extensions/gsd/unit-context-composer.js +147 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +370 -0
- package/dist/resources/extensions/gsd/uok/gate-runner.js +53 -5
- package/dist/resources/extensions/gsd/workflow-mcp.js +6 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +34 -8
- package/dist/resources/extensions/mcp-client/index.js +3 -1
- package/dist/resources/extensions/ollama/index.js +5 -1
- package/dist/resources/extensions/remote-questions/manager.js +11 -5
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +5 -5
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- 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.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- 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/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +5 -5
- package/dist/web/standalone/.next/server/chunks/1926.js +1 -1
- package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +2 -3
- package/packages/daemon/src/logger.ts +4 -3
- package/packages/mcp-server/dist/server.d.ts +24 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +88 -87
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/src/mcp-server.test.ts +25 -3
- package/packages/mcp-server/src/readers/graph.test.ts +87 -15
- package/packages/mcp-server/src/secure-env-collect.test.ts +232 -237
- package/packages/mcp-server/src/server.ts +131 -105
- package/packages/mcp-server/src/workflow-tools.test.ts +80 -39
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/package.json +1 -1
- package/packages/native/src/__tests__/_test-coverage-guard.test.mjs +98 -0
- package/packages/native/src/__tests__/module-compat.test.mjs +59 -27
- package/packages/native/src/__tests__/ps.test.mjs +14 -8
- package/packages/native/src/__tests__/stream-process.test.mjs +23 -2
- package/packages/native/src/__tests__/truncate.test.mjs +17 -2
- package/packages/pi-agent-core/src/agent-loop.test.ts +5 -15
- package/packages/pi-agent-core/src/agent.test.ts +96 -102
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/models/capability-patches.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/capability-patches.js +9 -2
- package/packages/pi-ai/dist/models/capability-patches.js.map +1 -1
- package/packages/pi-ai/dist/models/generated/index.d.ts +34 -0
- package/packages/pi-ai/dist/models/generated/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +17 -0
- package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/generated/openai-codex.js +17 -0
- package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -1
- package/packages/pi-ai/dist/models/generated/openai.d.ts +17 -0
- package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/generated/openai.js +17 -0
- package/packages/pi-ai/dist/models/generated/openai.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.test.js +43 -70
- package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
- package/packages/pi-ai/dist/models.test.js +36 -11
- package/packages/pi-ai/dist/models.test.js.map +1 -1
- package/packages/pi-ai/scripts/generate-models.ts +44 -0
- package/packages/pi-ai/src/models/capability-patches.ts +10 -2
- package/packages/pi-ai/src/models/generated/openai-codex.ts +17 -0
- package/packages/pi-ai/src/models/generated/openai.ts +17 -0
- package/packages/pi-ai/src/models.generated.test.ts +46 -73
- package/packages/pi-ai/src/models.test.ts +48 -11
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +96 -32
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js +75 -12
- package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +99 -31
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +61 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +30 -4
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +17 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js +76 -18
- package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js.map +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 +2 -6
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js +5 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts +18 -0
- package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/retryable-error-regex.js +18 -0
- package/packages/pi-coding-agent/dist/core/retryable-error-regex.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +20 -0
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +16 -2
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -0
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js +20 -13
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +18 -3
- 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 +125 -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-state.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
- 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 +105 -13
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js +130 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js.map +1 -0
- package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +113 -37
- package/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts +89 -17
- package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +112 -43
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +58 -0
- package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +35 -4
- package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +20 -0
- package/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +93 -28
- package/packages/pi-coding-agent/src/core/retry-handler.test.ts +5 -1
- package/packages/pi-coding-agent/src/core/retry-handler.ts +2 -8
- package/packages/pi-coding-agent/src/core/retryable-error-regex.ts +18 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +35 -1
- package/packages/pi-coding-agent/src/index.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +26 -20
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +146 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +20 -3
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +2 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +119 -13
- package/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts +157 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js +18 -8
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +128 -17
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +36 -12
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/tui.test.js +18 -30
- package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/input.test.js +10 -3
- package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/loader.test.js +53 -9
- package/packages/pi-tui/dist/components/__tests__/loader.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +6 -2
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -1
- package/packages/pi-tui/dist/components/editor.d.ts +14 -0
- package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/editor.js +19 -0
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/dist/components/image.test.js +6 -5
- package/packages/pi-tui/dist/components/image.test.js.map +1 -1
- package/packages/pi-tui/dist/editor-component.d.ts +2 -0
- package/packages/pi-tui/dist/editor-component.d.ts.map +1 -1
- package/packages/pi-tui/dist/editor-component.js.map +1 -1
- package/packages/pi-tui/src/__tests__/autocomplete.test.ts +24 -8
- package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +140 -17
- package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +41 -12
- package/packages/pi-tui/src/__tests__/tui.test.ts +18 -37
- package/packages/pi-tui/src/components/__tests__/input.test.ts +19 -3
- package/packages/pi-tui/src/components/__tests__/loader.test.ts +112 -35
- package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +9 -2
- package/packages/pi-tui/src/components/editor.ts +22 -0
- package/packages/pi-tui/src/components/image.test.ts +10 -5
- package/packages/pi-tui/src/editor-component.ts +3 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/packages/rpc-client/dist/rpc-client.test.js +101 -51
- package/packages/rpc-client/dist/rpc-client.test.js.map +1 -1
- package/packages/rpc-client/src/rpc-client.test.ts +109 -52
- package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
- package/scripts/install.js +15 -1
- package/src/resources/extensions/browser-tools/capture.ts +12 -0
- package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
- package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
- package/src/resources/extensions/browser-tools/tools/forms.ts +5 -1
- package/src/resources/extensions/browser-tools/tools/intent.ts +5 -1
- package/src/resources/extensions/claude-code-cli/readiness.ts +5 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +518 -19
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +919 -75
- package/src/resources/extensions/github-sync/tests/cli.test.ts +76 -7
- package/src/resources/extensions/github-sync/tests/templates.test.ts +33 -1
- package/src/resources/extensions/gsd/auto/loop.ts +47 -0
- package/src/resources/extensions/gsd/auto/phases.ts +16 -20
- package/src/resources/extensions/gsd/auto/session.ts +0 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +113 -24
- package/src/resources/extensions/gsd/auto-model-selection.ts +131 -4
- package/src/resources/extensions/gsd/auto-post-unit.ts +82 -73
- package/src/resources/extensions/gsd/auto-prompts.ts +330 -90
- package/src/resources/extensions/gsd/auto-recovery.ts +225 -24
- package/src/resources/extensions/gsd/auto-start.ts +54 -6
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +51 -7
- package/src/resources/extensions/gsd/auto-worktree.ts +130 -26
- package/src/resources/extensions/gsd/auto.ts +43 -22
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +9 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +221 -0
- package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +3 -7
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +7 -3
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +158 -9
- package/src/resources/extensions/gsd/component-loader.ts +598 -0
- package/src/resources/extensions/gsd/component-types.ts +362 -0
- package/src/resources/extensions/gsd/detection.ts +58 -1
- package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/src/resources/extensions/gsd/gate-registry.ts +2 -2
- package/src/resources/extensions/gsd/git-constants.ts +30 -1
- package/src/resources/extensions/gsd/git-self-heal.ts +31 -0
- package/src/resources/extensions/gsd/git-service.ts +133 -2
- package/src/resources/extensions/gsd/gsd-db.ts +6 -3
- package/src/resources/extensions/gsd/guided-flow.ts +20 -5
- package/src/resources/extensions/gsd/memory-extractor.ts +11 -3
- package/src/resources/extensions/gsd/milestone-scope-classifier.ts +366 -0
- package/src/resources/extensions/gsd/model-cost-table.ts +3 -0
- package/src/resources/extensions/gsd/model-router.ts +6 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +34 -4
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
- package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -2
- package/src/resources/extensions/gsd/safety/git-checkpoint.ts +15 -0
- package/src/resources/extensions/gsd/service-tier.ts +5 -2
- package/src/resources/extensions/gsd/session-lock.ts +20 -10
- package/src/resources/extensions/gsd/skill-manifest.ts +175 -0
- package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +309 -8
- package/src/resources/extensions/gsd/state.ts +49 -44
- package/src/resources/extensions/gsd/sync-lock.ts +97 -39
- package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +270 -0
- package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +341 -0
- package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +264 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +94 -289
- package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +742 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +78 -0
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +61 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +8 -197
- package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +15 -58
- package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +17 -21
- package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +263 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +192 -0
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +16 -8
- package/src/resources/extensions/gsd/tests/component-loader.test.ts +589 -0
- package/src/resources/extensions/gsd/tests/component-types.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +50 -1
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +91 -3
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +139 -129
- package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +9 -105
- package/src/resources/extensions/gsd/tests/gate-state-canonicalization.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/gate-storage.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/hook-key-parsing.test.ts +4 -55
- package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +7 -57
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +18 -2
- package/src/resources/extensions/gsd/tests/integration/queue-completed-milestone-perf.test.ts +10 -4
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +144 -7
- package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -16
- package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +6 -9
- package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +8 -37
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +5 -15
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +227 -62
- package/src/resources/extensions/gsd/tests/milestone-scope-classifier.test.ts +187 -0
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
- package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +6 -49
- package/src/resources/extensions/gsd/tests/notification-widget.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +273 -133
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +301 -0
- package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +32 -1
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +23 -24
- package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +75 -2
- package/src/resources/extensions/gsd/tests/reassess-default-optin.test.ts +132 -0
- package/src/resources/extensions/gsd/tests/recovery-attempts-reset.test.ts +8 -40
- package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +136 -256
- package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +114 -0
- package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +148 -0
- package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +55 -95
- package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +158 -0
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +120 -1
- package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +112 -0
- package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +164 -1
- package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +5 -5
- package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +11 -92
- package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +102 -101
- package/src/resources/extensions/gsd/tests/sync-lock.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/test-helpers.test.ts +12 -61
- package/src/resources/extensions/gsd/tests/test-helpers.ts +21 -8
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +61 -1
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +355 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +258 -0
- package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/uok-gitops-wiring.test.ts +49 -26
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +144 -81
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -54
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +342 -277
- package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +37 -29
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +226 -266
- package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +103 -67
- package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +92 -90
- package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +238 -59
- package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +113 -161
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +262 -0
- package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +7 -5
- package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +80 -96
- package/src/resources/extensions/gsd/types.ts +3 -3
- package/src/resources/extensions/gsd/unit-context-composer.ts +218 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +574 -0
- package/src/resources/extensions/gsd/uok/gate-runner.ts +65 -5
- package/src/resources/extensions/gsd/workflow-mcp.ts +6 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +55 -7
- package/src/resources/extensions/mcp-client/index.ts +3 -1
- package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +70 -36
- package/src/resources/extensions/ollama/index.ts +5 -1
- package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +123 -15
- package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +206 -19
- package/src/resources/extensions/remote-questions/manager.ts +36 -4
- package/src/resources/extensions/remote-questions/tests/command-polling.test.ts +200 -190
- package/src/resources/extensions/shared/tests/interview-preview.test.ts +11 -3
- package/src/resources/extensions/voice/tests/linux-ready.test.ts +129 -113
- package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts +0 -2
- package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts.map +0 -1
- package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js +0 -289
- package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js.map +0 -1
- package/packages/pi-ai/src/utils/oauth/oauth-providers.test.ts +0 -363
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +0 -144
- package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +0 -157
- package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +0 -107
- package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +0 -48
- package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +0 -159
- package/src/resources/extensions/gsd/tests/forensics-db-completion.test.ts +0 -96
- package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +0 -79
- package/src/resources/extensions/gsd/tests/forensics-hook-key-parse.test.ts +0 -75
- package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +0 -162
- package/src/resources/extensions/gsd/tests/forensics-worktree-telemetry.test.ts +0 -145
- package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +0 -38
- package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +0 -73
- package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +0 -130
- package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +0 -43
- /package/dist/web/standalone/.next/static/{Cev5xrAYA3ZGTRLyjR2fX → SvCJDZPQW104bR1KnBQg1}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{Cev5xrAYA3ZGTRLyjR2fX → SvCJDZPQW104bR1KnBQg1}/_ssgManifest.js +0 -0
|
@@ -13,13 +13,16 @@
|
|
|
13
13
|
* - Conflict check: file overlap between slice plans (slice-parallel-conflict.ts)
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { spawn, type ChildProcess } from "node:child_process";
|
|
16
|
+
import { execFileSync, spawn, type ChildProcess } from "node:child_process";
|
|
17
|
+
import { randomUUID } from "node:crypto";
|
|
17
18
|
import {
|
|
18
19
|
appendFileSync,
|
|
19
20
|
existsSync,
|
|
20
21
|
writeFileSync,
|
|
21
22
|
readFileSync,
|
|
22
23
|
mkdirSync,
|
|
24
|
+
renameSync,
|
|
25
|
+
unlinkSync,
|
|
23
26
|
} from "node:fs";
|
|
24
27
|
import { join, dirname } from "node:path";
|
|
25
28
|
import { fileURLToPath } from "node:url";
|
|
@@ -40,6 +43,8 @@ export interface SliceWorkerInfo {
|
|
|
40
43
|
milestoneId: string;
|
|
41
44
|
sliceId: string;
|
|
42
45
|
pid: number;
|
|
46
|
+
workerToken: string;
|
|
47
|
+
processStartFingerprint: string | null;
|
|
43
48
|
process: ChildProcess | null;
|
|
44
49
|
worktreePath: string;
|
|
45
50
|
startedAt: number;
|
|
@@ -69,13 +74,276 @@ export interface StartSliceParallelOpts {
|
|
|
69
74
|
|
|
70
75
|
let sliceState: SliceOrchestratorState | null = null;
|
|
71
76
|
|
|
77
|
+
// ─── Persisted State (crash recovery) ──────────────────────────────────────
|
|
78
|
+
//
|
|
79
|
+
// Mirrors parallel-orchestrator.ts. Without persistence, a coordinator crash
|
|
80
|
+
// leaves orphaned worktrees on disk with no way to detect or clean them up
|
|
81
|
+
// on next session start. (Issue #4980 HIGH-8)
|
|
82
|
+
|
|
83
|
+
const SLICE_ORCHESTRATOR_STATE_FILE = "slice-orchestrator.json";
|
|
84
|
+
const TMP_SUFFIX = ".tmp";
|
|
85
|
+
|
|
86
|
+
interface PersistedSliceWorker {
|
|
87
|
+
milestoneId: string;
|
|
88
|
+
sliceId: string;
|
|
89
|
+
pid: number;
|
|
90
|
+
workerToken?: string;
|
|
91
|
+
processStartFingerprint?: string | null;
|
|
92
|
+
worktreePath: string;
|
|
93
|
+
startedAt: number;
|
|
94
|
+
state: "running" | "stopped" | "error";
|
|
95
|
+
completedUnits: number;
|
|
96
|
+
cost: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
interface PersistedSliceState {
|
|
100
|
+
active: boolean;
|
|
101
|
+
workers: PersistedSliceWorker[];
|
|
102
|
+
totalCost: number;
|
|
103
|
+
budgetCeiling?: number;
|
|
104
|
+
maxWorkers: number;
|
|
105
|
+
startedAt: number;
|
|
106
|
+
basePath: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function sliceStateFilePath(basePath: string): string {
|
|
110
|
+
return join(gsdRoot(basePath), SLICE_ORCHESTRATOR_STATE_FILE);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function isPidAlive(pid: number): boolean {
|
|
114
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
115
|
+
try {
|
|
116
|
+
process.kill(pid, 0);
|
|
117
|
+
return true;
|
|
118
|
+
} catch (err) {
|
|
119
|
+
if ((err as NodeJS.ErrnoException).code === "EPERM") return true;
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function readLinuxProcessStartFingerprint(pid: number): string | null {
|
|
125
|
+
try {
|
|
126
|
+
const stat = readFileSync(`/proc/${pid}/stat`, "utf-8");
|
|
127
|
+
const afterCommand = stat.slice(stat.lastIndexOf(")") + 2).trim();
|
|
128
|
+
const fields = afterCommand.split(/\s+/);
|
|
129
|
+
const startTimeTicks = fields[19];
|
|
130
|
+
return startTimeTicks ? `linux-stat:${startTimeTicks}` : null;
|
|
131
|
+
} catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function readPsProcessStartFingerprint(pid: number): string | null {
|
|
137
|
+
try {
|
|
138
|
+
const raw = execFileSync("ps", ["-p", String(pid), "-o", "lstart="], {
|
|
139
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
140
|
+
encoding: "utf-8",
|
|
141
|
+
}).trim().replace(/\s+/g, " ");
|
|
142
|
+
return raw ? `ps-lstart:${raw}` : null;
|
|
143
|
+
} catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function readProcessStartFingerprint(pid: number): string | null {
|
|
149
|
+
if (!Number.isInteger(pid) || pid <= 0) return null;
|
|
150
|
+
return readLinuxProcessStartFingerprint(pid) ?? readPsProcessStartFingerprint(pid);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function linuxProcessEnvContains(pid: number, key: string, value: string): boolean | null {
|
|
154
|
+
if (process.platform !== "linux") return null;
|
|
155
|
+
try {
|
|
156
|
+
const env = readFileSync(`/proc/${pid}/environ`, "utf-8");
|
|
157
|
+
return env.split("\0").includes(`${key}=${value}`);
|
|
158
|
+
} catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function createWorkerToken(milestoneId: string, sliceId: string): string {
|
|
164
|
+
return `slice:${milestoneId}:${sliceId}:${Date.now()}:${randomUUID()}`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function isRecoveredSliceWorkerAlive(worker: {
|
|
168
|
+
pid: number;
|
|
169
|
+
workerToken?: string;
|
|
170
|
+
processStartFingerprint?: string | null;
|
|
171
|
+
}): boolean {
|
|
172
|
+
if (!isPidAlive(worker.pid)) return false;
|
|
173
|
+
if (!worker.processStartFingerprint) return false;
|
|
174
|
+
|
|
175
|
+
const currentFingerprint = readProcessStartFingerprint(worker.pid);
|
|
176
|
+
if (!currentFingerprint || currentFingerprint !== worker.processStartFingerprint) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (worker.workerToken) {
|
|
181
|
+
const envMatches = linuxProcessEnvContains(
|
|
182
|
+
worker.pid,
|
|
183
|
+
"GSD_SLICE_WORKER_TOKEN",
|
|
184
|
+
worker.workerToken,
|
|
185
|
+
);
|
|
186
|
+
if (envMatches === false) return false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Persist current slice orchestrator state. Atomic write (tmp + rename) to
|
|
194
|
+
* prevent partial reads if the coordinator dies mid-write.
|
|
195
|
+
*/
|
|
196
|
+
function persistSliceState(): void {
|
|
197
|
+
if (!sliceState) return;
|
|
198
|
+
try {
|
|
199
|
+
const dir = gsdRoot(sliceState.basePath);
|
|
200
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
201
|
+
|
|
202
|
+
const persisted: PersistedSliceState = {
|
|
203
|
+
active: sliceState.active,
|
|
204
|
+
workers: [...sliceState.workers.values()].map((w) => ({
|
|
205
|
+
milestoneId: w.milestoneId,
|
|
206
|
+
sliceId: w.sliceId,
|
|
207
|
+
pid: w.pid,
|
|
208
|
+
workerToken: w.workerToken,
|
|
209
|
+
processStartFingerprint: w.processStartFingerprint,
|
|
210
|
+
worktreePath: w.worktreePath,
|
|
211
|
+
startedAt: w.startedAt,
|
|
212
|
+
state: w.state,
|
|
213
|
+
completedUnits: w.completedUnits,
|
|
214
|
+
cost: w.cost,
|
|
215
|
+
})),
|
|
216
|
+
totalCost: sliceState.totalCost,
|
|
217
|
+
budgetCeiling: sliceState.budgetCeiling,
|
|
218
|
+
maxWorkers: sliceState.maxWorkers,
|
|
219
|
+
startedAt: sliceState.startedAt,
|
|
220
|
+
basePath: sliceState.basePath,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const dest = sliceStateFilePath(sliceState.basePath);
|
|
224
|
+
const tmp = dest + TMP_SUFFIX;
|
|
225
|
+
writeFileSync(tmp, JSON.stringify(persisted, null, 2), "utf-8");
|
|
226
|
+
renameSync(tmp, dest);
|
|
227
|
+
lastPersistTs = Date.now();
|
|
228
|
+
} catch {
|
|
229
|
+
/* non-fatal: persistence is best-effort */
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Throttled wrapper around `persistSliceState`. Skips if the last successful
|
|
235
|
+
* persist was less than `PERSIST_THROTTLE_MS` ago; otherwise persists
|
|
236
|
+
* immediately. Use this on hot paths (e.g. `message_end` events) where we
|
|
237
|
+
* receive many events per second per worker. Terminal events (worker exit,
|
|
238
|
+
* crash, stop) should call `persistSliceState()` directly to guarantee the
|
|
239
|
+
* final state hits disk regardless of timing.
|
|
240
|
+
*/
|
|
241
|
+
const PERSIST_THROTTLE_MS = 1000;
|
|
242
|
+
let lastPersistTs = 0;
|
|
243
|
+
|
|
244
|
+
function persistSliceStateThrottled(): void {
|
|
245
|
+
if (Date.now() - lastPersistTs < PERSIST_THROTTLE_MS) return;
|
|
246
|
+
persistSliceState();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function removeSliceStateFile(basePath: string): void {
|
|
250
|
+
try {
|
|
251
|
+
const p = sliceStateFilePath(basePath);
|
|
252
|
+
if (existsSync(p)) unlinkSync(p);
|
|
253
|
+
} catch {
|
|
254
|
+
/* non-fatal */
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Restore slice orchestrator state from disk. Filters dead-PID workers and
|
|
260
|
+
* removes their orphaned worktrees so a clean restart is possible.
|
|
261
|
+
*
|
|
262
|
+
* Returns null if no state file exists or no workers survive.
|
|
263
|
+
*/
|
|
264
|
+
export function restoreSliceState(basePath: string): PersistedSliceState | null {
|
|
265
|
+
try {
|
|
266
|
+
const p = sliceStateFilePath(basePath);
|
|
267
|
+
if (!existsSync(p)) return null;
|
|
268
|
+
const persisted = JSON.parse(readFileSync(p, "utf-8")) as PersistedSliceState;
|
|
269
|
+
|
|
270
|
+
const survivors: PersistedSliceWorker[] = [];
|
|
271
|
+
const dead: PersistedSliceWorker[] = [];
|
|
272
|
+
for (const w of persisted.workers) {
|
|
273
|
+
if (w.state === "running" && isRecoveredSliceWorkerAlive(w)) {
|
|
274
|
+
survivors.push(w);
|
|
275
|
+
} else if (w.state === "running") {
|
|
276
|
+
dead.push(w);
|
|
277
|
+
} else {
|
|
278
|
+
survivors.push(w);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Best-effort cleanup of orphaned worktrees from dead workers.
|
|
283
|
+
for (const w of dead) {
|
|
284
|
+
const wtName = `${w.milestoneId}-${w.sliceId}`;
|
|
285
|
+
try {
|
|
286
|
+
removeWorktree(persisted.basePath, wtName, { deleteBranch: true, force: true });
|
|
287
|
+
} catch {
|
|
288
|
+
/* worktree may already be gone */
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
persisted.workers = survivors;
|
|
293
|
+
|
|
294
|
+
if (survivors.length === 0) {
|
|
295
|
+
removeSliceStateFile(basePath);
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return persisted;
|
|
300
|
+
} catch {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
72
305
|
// ─── Public API ────────────────────────────────────────────────────────────
|
|
73
306
|
|
|
74
307
|
/**
|
|
75
308
|
* Check whether slice-level parallel is currently active.
|
|
309
|
+
*
|
|
310
|
+
* If in-memory state is unset but a persisted state file exists with at
|
|
311
|
+
* least one live-PID worker, treat as active and rehydrate so a coordinator
|
|
312
|
+
* crash followed by a fresh process is detectable. (Issue #4980 HIGH-8)
|
|
76
313
|
*/
|
|
77
|
-
export function isSliceParallelActive(): boolean {
|
|
78
|
-
|
|
314
|
+
export function isSliceParallelActive(basePath?: string): boolean {
|
|
315
|
+
if (sliceState?.active === true) return true;
|
|
316
|
+
if (!basePath) return false;
|
|
317
|
+
const restored = restoreSliceState(basePath);
|
|
318
|
+
if (!restored || restored.workers.length === 0) return false;
|
|
319
|
+
|
|
320
|
+
// Rehydrate in-memory state from disk; processes are detached so we have
|
|
321
|
+
// no ChildProcess handles, only PIDs.
|
|
322
|
+
sliceState = {
|
|
323
|
+
active: restored.active,
|
|
324
|
+
workers: new Map(),
|
|
325
|
+
totalCost: restored.totalCost,
|
|
326
|
+
budgetCeiling: restored.budgetCeiling,
|
|
327
|
+
maxWorkers: restored.maxWorkers,
|
|
328
|
+
startedAt: restored.startedAt,
|
|
329
|
+
basePath: restored.basePath,
|
|
330
|
+
};
|
|
331
|
+
for (const w of restored.workers) {
|
|
332
|
+
sliceState.workers.set(w.sliceId, {
|
|
333
|
+
milestoneId: w.milestoneId,
|
|
334
|
+
sliceId: w.sliceId,
|
|
335
|
+
pid: w.pid,
|
|
336
|
+
process: null,
|
|
337
|
+
worktreePath: w.worktreePath,
|
|
338
|
+
workerToken: w.workerToken ?? "",
|
|
339
|
+
processStartFingerprint: w.processStartFingerprint ?? null,
|
|
340
|
+
startedAt: w.startedAt,
|
|
341
|
+
state: w.state,
|
|
342
|
+
completedUnits: w.completedUnits,
|
|
343
|
+
cost: w.cost,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
return true;
|
|
79
347
|
}
|
|
80
348
|
|
|
81
349
|
/**
|
|
@@ -146,6 +414,8 @@ export async function startSliceParallel(
|
|
|
146
414
|
milestoneId,
|
|
147
415
|
sliceId: slice.id,
|
|
148
416
|
pid: 0,
|
|
417
|
+
workerToken: createWorkerToken(milestoneId, slice.id),
|
|
418
|
+
processStartFingerprint: null,
|
|
149
419
|
process: null,
|
|
150
420
|
worktreePath: wtPath,
|
|
151
421
|
startedAt: Date.now(),
|
|
@@ -162,12 +432,16 @@ export async function startSliceParallel(
|
|
|
162
432
|
started.push(slice.id);
|
|
163
433
|
} else {
|
|
164
434
|
errors.push({ sid: slice.id, error: "Failed to spawn worker process" });
|
|
165
|
-
|
|
435
|
+
sliceState.workers.delete(slice.id);
|
|
436
|
+
try {
|
|
437
|
+
removeWorktree(basePath, wtName, { deleteBranch: true, force: true });
|
|
438
|
+
} catch { /* ignore cleanup failures */ }
|
|
166
439
|
}
|
|
167
440
|
} catch (err) {
|
|
168
441
|
errors.push({ sid: slice.id, error: getErrorMessage(err) });
|
|
169
442
|
// Best-effort cleanup of partially created worktree
|
|
170
443
|
const wtName = `${milestoneId}-${slice.id}`;
|
|
444
|
+
sliceState.workers.delete(slice.id);
|
|
171
445
|
try {
|
|
172
446
|
removeWorktree(basePath, wtName, { deleteBranch: true, force: true });
|
|
173
447
|
} catch { /* ignore cleanup failures */ }
|
|
@@ -177,6 +451,10 @@ export async function startSliceParallel(
|
|
|
177
451
|
// If nothing started, deactivate
|
|
178
452
|
if (started.length === 0) {
|
|
179
453
|
sliceState.active = false;
|
|
454
|
+
removeSliceStateFile(basePath);
|
|
455
|
+
} else {
|
|
456
|
+
// Persist state for crash recovery (Issue #4980 HIGH-8).
|
|
457
|
+
persistSliceState();
|
|
180
458
|
}
|
|
181
459
|
|
|
182
460
|
return { started, errors };
|
|
@@ -187,13 +465,16 @@ export async function startSliceParallel(
|
|
|
187
465
|
*/
|
|
188
466
|
export function stopSliceParallel(): void {
|
|
189
467
|
if (!sliceState) return;
|
|
468
|
+
const basePath = sliceState.basePath;
|
|
190
469
|
|
|
191
470
|
for (const worker of sliceState.workers.values()) {
|
|
192
|
-
|
|
193
|
-
|
|
471
|
+
try {
|
|
472
|
+
if (worker.process) {
|
|
194
473
|
worker.process.kill("SIGTERM");
|
|
195
|
-
}
|
|
196
|
-
|
|
474
|
+
} else if (worker.state === "running" && isRecoveredSliceWorkerAlive(worker)) {
|
|
475
|
+
process.kill(worker.pid, "SIGTERM");
|
|
476
|
+
}
|
|
477
|
+
} catch { /* already dead */ }
|
|
197
478
|
worker.cleanup?.();
|
|
198
479
|
worker.cleanup = undefined;
|
|
199
480
|
worker.process = null;
|
|
@@ -207,6 +488,9 @@ export function stopSliceParallel(): void {
|
|
|
207
488
|
}
|
|
208
489
|
|
|
209
490
|
sliceState.active = false;
|
|
491
|
+
// Clear persisted state — clean shutdown means no recovery on next start.
|
|
492
|
+
// (Issue #4980 HIGH-8)
|
|
493
|
+
removeSliceStateFile(basePath);
|
|
210
494
|
}
|
|
211
495
|
|
|
212
496
|
/**
|
|
@@ -239,6 +523,7 @@ export function resetSliceOrchestrator(): void {
|
|
|
239
523
|
}
|
|
240
524
|
}
|
|
241
525
|
sliceState = null;
|
|
526
|
+
lastPersistTs = 0;
|
|
242
527
|
}
|
|
243
528
|
|
|
244
529
|
// ─── Internal: Conflict Filtering ──────────────────────────────────────────
|
|
@@ -339,6 +624,7 @@ function spawnSliceWorker(
|
|
|
339
624
|
GSD_MILESTONE_LOCK: milestoneId,
|
|
340
625
|
GSD_PROJECT_ROOT: basePath,
|
|
341
626
|
GSD_PARALLEL_WORKER: "1",
|
|
627
|
+
GSD_SLICE_WORKER_TOKEN: worker.workerToken,
|
|
342
628
|
},
|
|
343
629
|
stdio: ["ignore", "pipe", "pipe"],
|
|
344
630
|
detached: false,
|
|
@@ -357,9 +643,15 @@ function spawnSliceWorker(
|
|
|
357
643
|
|
|
358
644
|
worker.process = child;
|
|
359
645
|
worker.pid = child.pid ?? 0;
|
|
646
|
+
worker.processStartFingerprint = worker.pid > 0
|
|
647
|
+
? readProcessStartFingerprint(worker.pid)
|
|
648
|
+
: null;
|
|
360
649
|
|
|
361
650
|
if (!child.pid) {
|
|
362
651
|
worker.process = null;
|
|
652
|
+
worker.pid = 0;
|
|
653
|
+
worker.processStartFingerprint = null;
|
|
654
|
+
try { child.kill("SIGTERM"); } catch { /* best-effort */ }
|
|
363
655
|
return false;
|
|
364
656
|
}
|
|
365
657
|
|
|
@@ -438,6 +730,10 @@ function spawnSliceWorker(
|
|
|
438
730
|
startedAt: w.startedAt,
|
|
439
731
|
worktreePath: w.worktreePath,
|
|
440
732
|
});
|
|
733
|
+
|
|
734
|
+
// Persist worker terminal state for crash recovery.
|
|
735
|
+
// (Issue #4980 HIGH-8)
|
|
736
|
+
persistSliceState();
|
|
441
737
|
});
|
|
442
738
|
|
|
443
739
|
return true;
|
|
@@ -474,6 +770,11 @@ function processSliceWorkerLine(
|
|
|
474
770
|
sliceState.totalCost += usage.cost;
|
|
475
771
|
}
|
|
476
772
|
worker.completedUnits++;
|
|
773
|
+
// Persist cost / progress updates so a crash mid-run preserves them.
|
|
774
|
+
// Throttled (~1/s per process) so high-frequency message_end traffic
|
|
775
|
+
// does not saturate disk I/O. Worker exit / start / stop paths persist
|
|
776
|
+
// unthrottled to guarantee the terminal state lands. (Issue #4980 HIGH-8)
|
|
777
|
+
persistSliceStateThrottled();
|
|
477
778
|
}
|
|
478
779
|
}
|
|
479
780
|
}
|
|
@@ -267,7 +267,7 @@ export async function deriveState(basePath: string): Promise<GSDState> {
|
|
|
267
267
|
let synced = false;
|
|
268
268
|
for (const diskId of diskIds) {
|
|
269
269
|
if (!isGhostMilestone(basePath, diskId)) {
|
|
270
|
-
insertMilestone(
|
|
270
|
+
insertMilestone(diskMilestoneInsert(basePath, diskId));
|
|
271
271
|
synced = true;
|
|
272
272
|
}
|
|
273
273
|
}
|
|
@@ -327,6 +327,42 @@ function extractContextTitle(content: string | null, fallback: string): string {
|
|
|
327
327
|
// Alias kept for backward compatibility within this file.
|
|
328
328
|
const isStatusDone = isClosedStatus;
|
|
329
329
|
|
|
330
|
+
function loadSync(path: string | null): string | null {
|
|
331
|
+
if (!path) return null;
|
|
332
|
+
try {
|
|
333
|
+
return readFileSync(path, "utf-8");
|
|
334
|
+
} catch {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function diskMilestoneInsert(basePath: string, mid: string): {
|
|
340
|
+
id: string;
|
|
341
|
+
title?: string;
|
|
342
|
+
status: string;
|
|
343
|
+
depends_on: string[];
|
|
344
|
+
} {
|
|
345
|
+
const contextContent = loadSync(resolveMilestoneFile(basePath, mid, "CONTEXT"));
|
|
346
|
+
const draftContent = !contextContent ? loadSync(resolveMilestoneFile(basePath, mid, "CONTEXT-DRAFT")) : null;
|
|
347
|
+
const roadmapContent = loadSync(resolveMilestoneFile(basePath, mid, "ROADMAP"));
|
|
348
|
+
const summaryContent = loadSync(resolveMilestoneFile(basePath, mid, "SUMMARY"));
|
|
349
|
+
const roadmap = roadmapContent ? parseRoadmap(roadmapContent) : null;
|
|
350
|
+
const summary = summaryContent ? parseSummary(summaryContent) : null;
|
|
351
|
+
const summaryTerminal = summaryContent != null && isTerminalMilestoneSummaryContent(summaryContent);
|
|
352
|
+
const parked = resolveMilestoneFile(basePath, mid, "PARKED") !== null;
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
id: mid,
|
|
356
|
+
title: roadmap
|
|
357
|
+
? stripMilestonePrefix(roadmap.title)
|
|
358
|
+
: (contextContent || draftContent)
|
|
359
|
+
? extractContextTitle(contextContent || draftContent, mid)
|
|
360
|
+
: (summary?.title || mid),
|
|
361
|
+
status: parked ? "parked" : summaryTerminal ? "complete" : "active",
|
|
362
|
+
depends_on: parseContextDependsOn(contextContent ?? draftContent),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
330
366
|
/**
|
|
331
367
|
* Derive GSD state from the milestones/slices/tasks DB tables.
|
|
332
368
|
* Flag files (PARKED, VALIDATION, CONTINUE, REPLAN, REPLAN-TRIGGER, CONTEXT-DRAFT)
|
|
@@ -342,7 +378,7 @@ function reconcileDiskToDb(basePath: string): MilestoneRow[] {
|
|
|
342
378
|
let synced = false;
|
|
343
379
|
for (const diskId of diskIds) {
|
|
344
380
|
if (!dbIdSet.has(diskId) && !isGhostMilestone(basePath, diskId)) {
|
|
345
|
-
insertMilestone(
|
|
381
|
+
insertMilestone(diskMilestoneInsert(basePath, diskId));
|
|
346
382
|
synced = true;
|
|
347
383
|
}
|
|
348
384
|
}
|
|
@@ -677,32 +713,12 @@ function resolveSliceDependencies(activeMilestoneSlices: SliceRow[]): { activeSl
|
|
|
677
713
|
}
|
|
678
714
|
}
|
|
679
715
|
|
|
680
|
-
let bestFallback: SliceRow | null = null;
|
|
681
|
-
let bestFallbackSatisfied = -1;
|
|
682
|
-
|
|
683
716
|
for (const s of activeMilestoneSlices) {
|
|
684
717
|
if (isStatusDone(s.status)) continue;
|
|
685
718
|
if (isDeferredStatus(s.status)) continue;
|
|
686
719
|
if (s.depends.every(dep => doneSliceIds.has(dep))) {
|
|
687
720
|
return { activeSlice: { id: s.id, title: s.title }, activeSliceRow: s };
|
|
688
721
|
}
|
|
689
|
-
const satisfied = s.depends.filter(dep => doneSliceIds.has(dep)).length;
|
|
690
|
-
if (satisfied > bestFallbackSatisfied || (satisfied === bestFallbackSatisfied && !bestFallback)) {
|
|
691
|
-
bestFallback = s;
|
|
692
|
-
bestFallbackSatisfied = satisfied;
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
if (bestFallback) {
|
|
697
|
-
const unmet = bestFallback.depends.filter(dep => !doneSliceIds.has(dep));
|
|
698
|
-
logWarning(
|
|
699
|
-
"state",
|
|
700
|
-
`No slice has all deps satisfied — falling back to ${bestFallback.id} ` +
|
|
701
|
-
`(${bestFallbackSatisfied}/${bestFallback.depends.length} deps met, ` +
|
|
702
|
-
`unmet: ${unmet.join(", ")})`,
|
|
703
|
-
{ mid: activeMilestoneSlices[0]?.milestone_id, sid: bestFallback.id },
|
|
704
|
-
);
|
|
705
|
-
return { activeSlice: { id: bestFallback.id, title: bestFallback.title }, activeSliceRow: bestFallback };
|
|
706
722
|
}
|
|
707
723
|
|
|
708
724
|
return { activeSlice: null, activeSliceRow: null };
|
|
@@ -716,14 +732,19 @@ async function reconcileSliceTasks(
|
|
|
716
732
|
): Promise<TaskRow[]> {
|
|
717
733
|
let tasks = getSliceTasks(milestoneId, sliceId);
|
|
718
734
|
|
|
719
|
-
|
|
735
|
+
// #3600/#4974: import missing plan-file tasks even when the DB already has
|
|
736
|
+
// a partial task set. Existing DB task statuses stay authoritative.
|
|
737
|
+
if (planFile) {
|
|
720
738
|
try {
|
|
721
739
|
const planContent = await loadFile(planFile);
|
|
722
740
|
if (planContent) {
|
|
723
741
|
const diskPlan = parsePlan(planContent);
|
|
724
742
|
if (diskPlan.tasks.length > 0) {
|
|
743
|
+
const dbTaskIds = new Set(tasks.map(t => t.id));
|
|
744
|
+
let inserted = 0;
|
|
725
745
|
for (let i = 0; i < diskPlan.tasks.length; i++) {
|
|
726
746
|
const t = diskPlan.tasks[i];
|
|
747
|
+
if (dbTaskIds.has(t.id)) continue;
|
|
727
748
|
try {
|
|
728
749
|
insertTask({
|
|
729
750
|
id: t.id,
|
|
@@ -733,12 +754,15 @@ async function reconcileSliceTasks(
|
|
|
733
754
|
status: t.done ? 'complete' : 'pending',
|
|
734
755
|
sequence: i + 1,
|
|
735
756
|
});
|
|
757
|
+
inserted++;
|
|
736
758
|
} catch (insertErr) {
|
|
737
759
|
logWarning("reconcile", `failed to insert task ${t.id} from plan file: ${insertErr instanceof Error ? insertErr.message : String(insertErr)}`);
|
|
738
760
|
}
|
|
739
761
|
}
|
|
740
|
-
|
|
741
|
-
|
|
762
|
+
if (inserted > 0) {
|
|
763
|
+
tasks = getSliceTasks(milestoneId, sliceId);
|
|
764
|
+
logWarning("reconcile", `imported ${inserted} missing task(s) from plan file for ${milestoneId}/${sliceId}`, { mid: milestoneId, sid: sliceId });
|
|
765
|
+
}
|
|
742
766
|
}
|
|
743
767
|
}
|
|
744
768
|
} catch (err) {
|
|
@@ -1569,31 +1593,12 @@ export async function _deriveStateImpl(basePath: string): Promise<GSDState> {
|
|
|
1569
1593
|
};
|
|
1570
1594
|
}
|
|
1571
1595
|
} else {
|
|
1572
|
-
let bestFallbackLegacy: { id: string; title: string; depends: string[] } | null = null;
|
|
1573
|
-
let bestFallbackLegacySatisfied = -1;
|
|
1574
|
-
|
|
1575
1596
|
for (const s of activeRoadmap.slices) {
|
|
1576
1597
|
if (s.done) continue;
|
|
1577
1598
|
if (s.depends.every(dep => doneSliceIds.has(dep))) {
|
|
1578
1599
|
activeSlice = { id: s.id, title: s.title };
|
|
1579
1600
|
break;
|
|
1580
1601
|
}
|
|
1581
|
-
const satisfied = s.depends.filter(dep => doneSliceIds.has(dep)).length;
|
|
1582
|
-
if (satisfied > bestFallbackLegacySatisfied) {
|
|
1583
|
-
bestFallbackLegacy = s;
|
|
1584
|
-
bestFallbackLegacySatisfied = satisfied;
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
if (!activeSlice && bestFallbackLegacy) {
|
|
1589
|
-
const unmet = bestFallbackLegacy.depends.filter(dep => !doneSliceIds.has(dep));
|
|
1590
|
-
logWarning(
|
|
1591
|
-
"state",
|
|
1592
|
-
`No slice has all deps satisfied — falling back to ${bestFallbackLegacy.id} ` +
|
|
1593
|
-
`(${bestFallbackLegacySatisfied}/${bestFallbackLegacy.depends.length} deps met, ` +
|
|
1594
|
-
`unmet: ${unmet.join(", ")})`,
|
|
1595
|
-
);
|
|
1596
|
-
activeSlice = { id: bestFallbackLegacy.id, title: bestFallbackLegacy.title };
|
|
1597
1602
|
}
|
|
1598
1603
|
}
|
|
1599
1604
|
|