gsd-pi 2.28.0 → 2.29.0-dev.2ccf3fb
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 +34 -4
- package/dist/cli.js +20 -21
- package/dist/extension-discovery.d.ts +16 -0
- package/dist/extension-discovery.js +66 -0
- package/dist/headless-answers.d.ts +51 -0
- package/dist/headless-answers.js +224 -0
- package/dist/headless-context.d.ts +18 -0
- package/dist/headless-context.js +44 -0
- package/dist/headless-events.d.ts +28 -0
- package/dist/headless-events.js +59 -0
- package/dist/headless-query.d.ts +4 -0
- package/dist/headless-query.js +22 -4
- package/dist/headless-ui.d.ts +23 -0
- package/dist/headless-ui.js +103 -0
- package/dist/headless.d.ts +2 -0
- package/dist/headless.js +61 -183
- package/dist/help-text.js +4 -0
- package/dist/loader.js +18 -59
- package/dist/onboarding.js +8 -7
- package/dist/remote-questions-config.js +8 -3
- package/dist/resource-loader.d.ts +1 -6
- package/dist/resource-loader.js +125 -96
- package/dist/resources/extensions/ask-user-questions.ts +3 -2
- package/dist/resources/extensions/bg-shell/bg-shell-command.ts +219 -0
- package/dist/resources/extensions/bg-shell/bg-shell-lifecycle.ts +400 -0
- package/dist/resources/extensions/bg-shell/bg-shell-tool.ts +985 -0
- package/dist/resources/extensions/bg-shell/index.ts +17 -1561
- package/dist/resources/extensions/bg-shell/overlay.ts +4 -0
- package/dist/resources/extensions/bg-shell/process-manager.ts +13 -0
- package/dist/resources/extensions/bg-shell/utilities.ts +4 -16
- package/dist/resources/extensions/browser-tools/capture.ts +34 -2
- package/dist/resources/extensions/browser-tools/lifecycle.ts +5 -5
- package/dist/resources/extensions/browser-tools/settle.ts +1 -1
- package/dist/resources/extensions/browser-tools/state.ts +5 -5
- package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +1 -1
- package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +3 -3
- package/dist/resources/extensions/browser-tools/tools/assertions.ts +1 -1
- package/dist/resources/extensions/browser-tools/tools/device.ts +1 -1
- package/dist/resources/extensions/browser-tools/tools/extract.ts +1 -1
- package/dist/resources/extensions/browser-tools/tools/navigation.ts +6 -6
- package/dist/resources/extensions/browser-tools/tools/network-mock.ts +1 -1
- package/dist/resources/extensions/browser-tools/tools/pages.ts +1 -1
- package/dist/resources/extensions/browser-tools/tools/screenshot.ts +28 -10
- package/dist/resources/extensions/browser-tools/tools/state-persistence.ts +1 -1
- package/dist/resources/extensions/browser-tools/tools/visual-diff.ts +1 -1
- package/dist/resources/extensions/browser-tools/utils.ts +5 -5
- package/dist/resources/extensions/get-secrets-from-user.ts +1 -1
- package/dist/resources/extensions/google-search/index.ts +21 -8
- package/dist/resources/extensions/gsd/activity-log.ts +2 -1
- package/dist/resources/extensions/gsd/atomic-write.ts +35 -0
- package/dist/resources/extensions/gsd/auto/session.ts +12 -0
- package/dist/resources/extensions/gsd/auto-dashboard.ts +264 -62
- package/dist/resources/extensions/gsd/auto-idempotency.ts +150 -0
- package/dist/resources/extensions/gsd/auto-post-unit.ts +594 -0
- package/dist/resources/extensions/gsd/auto-prompts.ts +116 -22
- package/dist/resources/extensions/gsd/auto-recovery.ts +36 -31
- package/dist/resources/extensions/gsd/auto-start.ts +500 -0
- package/dist/resources/extensions/gsd/auto-stuck-detection.ts +220 -0
- package/dist/resources/extensions/gsd/auto-timers.ts +223 -0
- package/dist/resources/extensions/gsd/auto-unit-closeout.ts +3 -1
- package/dist/resources/extensions/gsd/auto-verification.ts +229 -0
- package/dist/resources/extensions/gsd/auto-worktree-sync.ts +29 -37
- package/dist/resources/extensions/gsd/auto-worktree.ts +83 -67
- package/dist/resources/extensions/gsd/auto.ts +375 -1890
- package/dist/resources/extensions/gsd/commands-config.ts +102 -0
- package/dist/resources/extensions/gsd/commands-handlers.ts +394 -0
- package/dist/resources/extensions/gsd/commands-inspect.ts +90 -0
- package/dist/resources/extensions/gsd/commands-logs.ts +536 -0
- package/dist/resources/extensions/gsd/commands-maintenance.ts +206 -0
- package/dist/resources/extensions/gsd/commands-prefs-wizard.ts +790 -0
- package/dist/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
- package/dist/resources/extensions/gsd/commands.ts +417 -1495
- package/dist/resources/extensions/gsd/constants.ts +21 -0
- package/dist/resources/extensions/gsd/context-budget.ts +25 -2
- package/dist/resources/extensions/gsd/crash-recovery.ts +3 -4
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +15 -5
- package/dist/resources/extensions/gsd/db-writer.ts +21 -2
- package/dist/resources/extensions/gsd/detection.ts +469 -0
- package/dist/resources/extensions/gsd/diff-context.ts +2 -1
- package/dist/resources/extensions/gsd/dispatch-guard.ts +4 -0
- package/dist/resources/extensions/gsd/doctor-checks.ts +564 -0
- package/dist/resources/extensions/gsd/doctor-format.ts +78 -0
- package/dist/resources/extensions/gsd/doctor-types.ts +72 -0
- package/dist/resources/extensions/gsd/doctor.ts +64 -701
- package/dist/resources/extensions/gsd/errors.ts +0 -2
- package/dist/resources/extensions/gsd/export-html.ts +367 -11
- package/dist/resources/extensions/gsd/export.ts +31 -5
- package/dist/resources/extensions/gsd/files.ts +8 -126
- package/dist/resources/extensions/gsd/forensics.ts +2 -12
- package/dist/resources/extensions/gsd/git-constants.ts +11 -0
- package/dist/resources/extensions/gsd/git-service.ts +13 -9
- package/dist/resources/extensions/gsd/gsd-db.ts +26 -6
- package/dist/resources/extensions/gsd/guided-flow-queue.ts +451 -0
- package/dist/resources/extensions/gsd/guided-flow.ts +231 -514
- package/dist/resources/extensions/gsd/history.ts +2 -20
- package/dist/resources/extensions/gsd/index.ts +208 -46
- package/dist/resources/extensions/gsd/init-wizard.ts +615 -0
- package/dist/resources/extensions/gsd/json-persistence.ts +67 -0
- package/dist/resources/extensions/gsd/jsonl-utils.ts +21 -0
- package/dist/resources/extensions/gsd/key-manager.ts +995 -0
- package/dist/resources/extensions/gsd/metrics.ts +49 -36
- package/dist/resources/extensions/gsd/migrate/command.ts +1 -1
- package/dist/resources/extensions/gsd/migrate/parsers.ts +10 -95
- package/dist/resources/extensions/gsd/milestone-actions.ts +126 -0
- package/dist/resources/extensions/gsd/milestone-ids.ts +95 -0
- package/dist/resources/extensions/gsd/native-git-bridge.ts +5 -10
- package/dist/resources/extensions/gsd/parallel-eligibility.ts +3 -3
- package/dist/resources/extensions/gsd/paths.ts +1 -11
- package/dist/resources/extensions/gsd/plugin-importer.ts +3 -2
- package/dist/resources/extensions/gsd/preferences-models.ts +323 -0
- package/dist/resources/extensions/gsd/preferences-skills.ts +169 -0
- package/dist/resources/extensions/gsd/preferences-types.ts +223 -0
- package/dist/resources/extensions/gsd/preferences-validation.ts +597 -0
- package/dist/resources/extensions/gsd/preferences.ts +219 -1305
- package/dist/resources/extensions/gsd/prompt-cache-optimizer.ts +213 -0
- package/dist/resources/extensions/gsd/prompt-compressor.ts +508 -0
- package/dist/resources/extensions/gsd/prompt-loader.ts +4 -2
- package/dist/resources/extensions/gsd/prompt-ordering.ts +200 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -2
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +2 -4
- package/dist/resources/extensions/gsd/prompts/discuss.md +13 -5
- package/dist/resources/extensions/gsd/prompts/execute-task.md +0 -1
- package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +0 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +0 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +0 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +30 -0
- package/dist/resources/extensions/gsd/prompts/quick-task.md +0 -6
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +0 -1
- package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +0 -1
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +28 -0
- package/dist/resources/extensions/gsd/provider-error-pause.ts +59 -10
- package/dist/resources/extensions/gsd/queue-order.ts +11 -12
- package/dist/resources/extensions/gsd/queue-reorder-ui.ts +15 -2
- package/dist/resources/extensions/gsd/quick.ts +18 -15
- package/dist/resources/extensions/gsd/reports.ts +1 -7
- package/dist/resources/extensions/gsd/routing-history.ts +13 -17
- package/dist/resources/extensions/gsd/safe-fs.ts +47 -0
- package/dist/resources/extensions/gsd/semantic-chunker.ts +336 -0
- package/dist/resources/extensions/gsd/session-forensics.ts +8 -23
- package/dist/resources/extensions/gsd/session-lock.ts +284 -0
- package/dist/resources/extensions/gsd/session-status-io.ts +23 -41
- package/dist/resources/extensions/gsd/skills/gsd-headless/SKILL.md +38 -1
- package/dist/resources/extensions/gsd/skills/gsd-headless/references/answer-injection.md +35 -6
- package/dist/resources/extensions/gsd/state.ts +54 -2
- package/dist/resources/extensions/gsd/structured-data-formatter.ts +144 -0
- package/dist/resources/extensions/gsd/summary-distiller.ts +258 -0
- package/dist/resources/extensions/gsd/tests/activity-log.test.ts +213 -0
- package/dist/resources/extensions/gsd/tests/agent-end-retry.test.ts +107 -0
- package/dist/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +197 -0
- package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +33 -39
- package/dist/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +108 -2
- package/dist/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +257 -0
- package/dist/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +3 -0
- package/dist/resources/extensions/gsd/tests/commands-logs.test.ts +241 -0
- package/dist/resources/extensions/gsd/tests/context-budget.test.ts +69 -0
- package/dist/resources/extensions/gsd/tests/detection.test.ts +398 -0
- package/dist/resources/extensions/gsd/tests/discuss-prompt.test.ts +12 -24
- package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +118 -94
- package/dist/resources/extensions/gsd/tests/dispatch-stall-guard.test.ts +126 -0
- package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +7 -3
- package/dist/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +75 -0
- package/dist/resources/extensions/gsd/tests/doctor-git.test.ts +17 -55
- package/dist/resources/extensions/gsd/tests/export-html-enhancements.test.ts +375 -0
- package/dist/resources/extensions/gsd/tests/extension-selector-separator.test.ts +144 -0
- package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +3 -0
- package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/headless-answers.test.ts +340 -0
- package/dist/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +24 -82
- package/dist/resources/extensions/gsd/tests/init-wizard.test.ts +197 -0
- package/dist/resources/extensions/gsd/tests/key-manager.test.ts +414 -0
- package/dist/resources/extensions/gsd/tests/metrics.test.ts +173 -305
- package/dist/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +3 -0
- package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +59 -1
- package/dist/resources/extensions/gsd/tests/next-milestone-id.test.ts +18 -61
- package/dist/resources/extensions/gsd/tests/none-mode-gates.test.ts +17 -8
- package/dist/resources/extensions/gsd/tests/parallel-merge.test.ts +3 -0
- package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/park-edge-cases.test.ts +276 -0
- package/dist/resources/extensions/gsd/tests/park-milestone.test.ts +401 -0
- package/dist/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +23 -47
- package/dist/resources/extensions/gsd/tests/preferences.test.ts +284 -0
- package/dist/resources/extensions/gsd/tests/prompt-cache-optimizer.test.ts +314 -0
- package/dist/resources/extensions/gsd/tests/prompt-compressor.test.ts +529 -0
- package/dist/resources/extensions/gsd/tests/prompt-ordering.test.ts +296 -0
- package/dist/resources/extensions/gsd/tests/provider-errors.test.ts +338 -0
- package/dist/resources/extensions/gsd/tests/reassess-detection.test.ts +154 -0
- package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/remote-status.test.ts +2 -2
- package/dist/resources/extensions/gsd/tests/roadmap-slices.test.ts +43 -60
- package/dist/resources/extensions/gsd/tests/semantic-chunker.test.ts +426 -0
- package/dist/resources/extensions/gsd/tests/session-lock.test.ts +315 -0
- package/dist/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +3 -0
- package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +8 -5
- package/dist/resources/extensions/gsd/tests/structured-data-formatter.test.ts +365 -0
- package/dist/resources/extensions/gsd/tests/summary-distiller.test.ts +323 -0
- package/dist/resources/extensions/gsd/tests/token-counter.test.ts +129 -0
- package/dist/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +1272 -0
- package/dist/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +164 -0
- package/dist/resources/extensions/gsd/tests/token-profile.test.ts +8 -1
- package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +69 -73
- package/dist/resources/extensions/gsd/tests/validate-directory.test.ts +222 -0
- package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +55 -0
- package/dist/resources/extensions/gsd/tests/verification-evidence.test.ts +26 -24
- package/dist/resources/extensions/gsd/tests/verification-gate.test.ts +251 -8
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +2 -2
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +2 -1
- package/dist/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
- package/dist/resources/extensions/gsd/tests/workspace-index.test.ts +24 -61
- package/dist/resources/extensions/gsd/tests/worktree-e2e.test.ts +5 -2
- package/dist/resources/extensions/gsd/tests/write-gate.test.ts +132 -43
- package/dist/resources/extensions/gsd/token-counter.ts +20 -0
- package/dist/resources/extensions/gsd/triage-ui.ts +1 -1
- package/dist/resources/extensions/gsd/types.ts +5 -1
- package/dist/resources/extensions/gsd/unit-runtime.ts +16 -13
- package/dist/resources/extensions/gsd/validate-directory.ts +164 -0
- package/dist/resources/extensions/gsd/verification-evidence.ts +9 -4
- package/dist/resources/extensions/gsd/verification-gate.ts +83 -7
- package/dist/resources/extensions/gsd/visualizer-data.ts +1 -1
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +5 -2
- package/dist/resources/extensions/gsd/visualizer-views.ts +2 -3
- package/dist/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
- package/dist/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
- package/dist/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
- package/dist/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
- package/dist/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
- package/dist/resources/extensions/gsd/workflow-templates/registry.json +85 -0
- package/dist/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
- package/dist/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
- package/dist/resources/extensions/gsd/workflow-templates/spike.md +69 -0
- package/dist/resources/extensions/gsd/workflow-templates.ts +241 -0
- package/dist/resources/extensions/gsd/worktree-command.ts +4 -51
- package/dist/resources/extensions/gsd/worktree-manager.ts +7 -9
- package/dist/resources/extensions/gsd/worktree.ts +41 -1
- package/dist/resources/extensions/mcp-client/index.ts +459 -0
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +9 -20
- package/dist/resources/extensions/remote-questions/http-client.ts +76 -0
- package/dist/resources/extensions/remote-questions/manager.ts +6 -24
- package/dist/resources/extensions/remote-questions/mod.ts +16 -0
- package/dist/resources/extensions/remote-questions/notify.ts +90 -0
- package/dist/resources/extensions/remote-questions/remote-command.ts +1 -1
- package/dist/resources/extensions/remote-questions/slack-adapter.ts +11 -18
- package/dist/resources/extensions/remote-questions/store.ts +5 -1
- package/dist/resources/extensions/remote-questions/telegram-adapter.ts +8 -20
- package/dist/resources/extensions/remote-questions/types.ts +29 -3
- package/dist/resources/extensions/search-the-web/native-search.ts +7 -0
- package/dist/resources/extensions/search-the-web/provider.ts +15 -3
- package/dist/resources/extensions/search-the-web/tool-llm-context.ts +1 -13
- package/dist/resources/extensions/search-the-web/tool-search.ts +1 -13
- package/dist/resources/extensions/shared/format-utils.ts +53 -0
- package/dist/resources/extensions/shared/frontmatter.ts +117 -0
- package/dist/resources/extensions/shared/mod.ts +33 -0
- package/dist/resources/extensions/shared/sanitize.ts +19 -0
- package/dist/resources/extensions/slash-commands/create-extension.ts +1 -1
- package/dist/resources/extensions/slash-commands/create-slash-command.ts +1 -1
- package/dist/resources/extensions/subagent/index.ts +1 -2
- package/dist/resources/extensions/ttsr/index.ts +5 -0
- package/dist/resources/extensions/ttsr/rule-loader.ts +4 -51
- package/dist/resources/extensions/universal-config/discovery.ts +37 -15
- package/dist/resources/extensions/voice/index.ts +1 -1
- package/dist/resources/skills/accessibility/SKILL.md +522 -0
- package/dist/resources/skills/accessibility/references/WCAG.md +162 -0
- package/dist/resources/skills/agent-browser/SKILL.md +517 -0
- package/dist/resources/skills/agent-browser/references/authentication.md +202 -0
- package/dist/resources/skills/agent-browser/references/commands.md +263 -0
- package/dist/resources/skills/agent-browser/references/profiling.md +120 -0
- package/dist/resources/skills/agent-browser/references/proxy-support.md +194 -0
- package/dist/resources/skills/agent-browser/references/session-management.md +193 -0
- package/dist/resources/skills/agent-browser/references/snapshot-refs.md +194 -0
- package/dist/resources/skills/agent-browser/references/video-recording.md +173 -0
- package/dist/resources/skills/agent-browser/templates/authenticated-session.sh +105 -0
- package/dist/resources/skills/agent-browser/templates/capture-workflow.sh +69 -0
- package/dist/resources/skills/agent-browser/templates/form-automation.sh +62 -0
- package/dist/resources/skills/best-practices/SKILL.md +583 -0
- package/dist/resources/skills/code-optimizer/SKILL.md +160 -0
- package/dist/resources/skills/code-optimizer/references/algorithmic-complexity.md +66 -0
- package/dist/resources/skills/code-optimizer/references/build-compilation.md +90 -0
- package/dist/resources/skills/code-optimizer/references/bundle-dependencies.md +82 -0
- package/dist/resources/skills/code-optimizer/references/caching-memoization.md +76 -0
- package/dist/resources/skills/code-optimizer/references/concurrency-async.md +80 -0
- package/dist/resources/skills/code-optimizer/references/config-infra.md +71 -0
- package/dist/resources/skills/code-optimizer/references/data-structures.md +80 -0
- package/dist/resources/skills/code-optimizer/references/database-queries.md +76 -0
- package/dist/resources/skills/code-optimizer/references/dead-code-redundancy.md +84 -0
- package/dist/resources/skills/code-optimizer/references/error-resilience.md +80 -0
- package/dist/resources/skills/code-optimizer/references/io-network.md +89 -0
- package/dist/resources/skills/code-optimizer/references/logging-observability.md +64 -0
- package/dist/resources/skills/code-optimizer/references/memory-resources.md +66 -0
- package/dist/resources/skills/code-optimizer/references/rendering-ui.md +90 -0
- package/dist/resources/skills/code-optimizer/references/security-performance.md +68 -0
- package/dist/resources/skills/core-web-vitals/SKILL.md +441 -0
- package/dist/resources/skills/core-web-vitals/references/LCP.md +208 -0
- package/dist/resources/skills/create-gsd-extension/SKILL.md +87 -0
- package/dist/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
- package/dist/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
- package/dist/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
- package/dist/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
- package/dist/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
- package/dist/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
- package/dist/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
- package/dist/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
- package/dist/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
- package/dist/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
- package/dist/resources/skills/create-gsd-extension/references/state-management.md +70 -0
- package/dist/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
- package/dist/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
- package/dist/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
- package/dist/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
- package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
- package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
- package/dist/resources/skills/create-skill/SKILL.md +184 -0
- package/dist/resources/skills/create-skill/references/api-security.md +226 -0
- package/dist/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
- package/dist/resources/skills/create-skill/references/common-patterns.md +595 -0
- package/dist/resources/skills/create-skill/references/core-principles.md +437 -0
- package/dist/resources/skills/create-skill/references/executable-code.md +175 -0
- package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
- package/dist/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
- package/dist/resources/skills/create-skill/references/recommended-structure.md +168 -0
- package/dist/resources/skills/create-skill/references/skill-structure.md +372 -0
- package/dist/resources/skills/create-skill/references/use-xml-tags.md +466 -0
- package/dist/resources/skills/create-skill/references/using-scripts.md +113 -0
- package/dist/resources/skills/create-skill/references/using-templates.md +112 -0
- package/dist/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
- package/dist/resources/skills/create-skill/templates/router-skill.md +73 -0
- package/dist/resources/skills/create-skill/templates/simple-skill.md +33 -0
- package/dist/resources/skills/create-skill/workflows/add-reference.md +96 -0
- package/dist/resources/skills/create-skill/workflows/add-script.md +93 -0
- package/dist/resources/skills/create-skill/workflows/add-template.md +74 -0
- package/dist/resources/skills/create-skill/workflows/add-workflow.md +120 -0
- package/dist/resources/skills/create-skill/workflows/audit-skill.md +148 -0
- package/dist/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
- package/dist/resources/skills/create-skill/workflows/get-guidance.md +121 -0
- package/dist/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
- package/dist/resources/skills/create-skill/workflows/verify-skill.md +204 -0
- package/dist/resources/skills/make-interfaces-feel-better/SKILL.md +122 -0
- package/dist/resources/skills/make-interfaces-feel-better/animations.md +379 -0
- package/dist/resources/skills/make-interfaces-feel-better/performance.md +88 -0
- package/dist/resources/skills/make-interfaces-feel-better/surfaces.md +247 -0
- package/dist/resources/skills/make-interfaces-feel-better/typography.md +123 -0
- package/dist/resources/skills/react-best-practices/README.md +123 -0
- package/dist/resources/skills/react-best-practices/SKILL.md +136 -0
- package/dist/resources/skills/react-best-practices/metadata.json +15 -0
- package/dist/resources/skills/react-best-practices/rules/_sections.md +46 -0
- package/dist/resources/skills/react-best-practices/rules/_template.md +28 -0
- package/dist/resources/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/dist/resources/skills/react-best-practices/rules/advanced-init-once.md +42 -0
- package/dist/resources/skills/react-best-practices/rules/advanced-use-latest.md +39 -0
- package/dist/resources/skills/react-best-practices/rules/async-api-routes.md +38 -0
- package/dist/resources/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/dist/resources/skills/react-best-practices/rules/async-dependencies.md +51 -0
- package/dist/resources/skills/react-best-practices/rules/async-parallel.md +28 -0
- package/dist/resources/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/dist/resources/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/dist/resources/skills/react-best-practices/rules/bundle-conditional.md +31 -0
- package/dist/resources/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/dist/resources/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/dist/resources/skills/react-best-practices/rules/bundle-preload.md +50 -0
- package/dist/resources/skills/react-best-practices/rules/client-event-listeners.md +74 -0
- package/dist/resources/skills/react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/dist/resources/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/dist/resources/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/dist/resources/skills/react-best-practices/rules/js-batch-dom-css.md +107 -0
- package/dist/resources/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/dist/resources/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/dist/resources/skills/react-best-practices/rules/js-cache-storage.md +70 -0
- package/dist/resources/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/dist/resources/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/dist/resources/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/dist/resources/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/dist/resources/skills/react-best-practices/rules/js-length-check-first.md +49 -0
- package/dist/resources/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/dist/resources/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/dist/resources/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/dist/resources/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/dist/resources/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/dist/resources/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/dist/resources/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/dist/resources/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/dist/resources/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/dist/resources/skills/react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
- package/dist/resources/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/dist/resources/skills/react-best-practices/rules/rendering-usetransition-loading.md +75 -0
- package/dist/resources/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/dist/resources/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/dist/resources/skills/react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/dist/resources/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/dist/resources/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/dist/resources/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/dist/resources/skills/react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
- package/dist/resources/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/dist/resources/skills/react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/dist/resources/skills/react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/dist/resources/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/dist/resources/skills/react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/dist/resources/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/dist/resources/skills/react-best-practices/rules/server-auth-actions.md +96 -0
- package/dist/resources/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/dist/resources/skills/react-best-practices/rules/server-cache-react.md +76 -0
- package/dist/resources/skills/react-best-practices/rules/server-dedup-props.md +65 -0
- package/dist/resources/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/dist/resources/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/dist/resources/skills/userinterface-wiki/SKILL.md +253 -0
- package/dist/resources/skills/userinterface-wiki/rules/_sections.md +66 -0
- package/dist/resources/skills/userinterface-wiki/rules/_template.md +24 -0
- package/dist/resources/skills/userinterface-wiki/rules/a11y-reduced-motion-check.md +30 -0
- package/dist/resources/skills/userinterface-wiki/rules/a11y-toggle-setting.md +30 -0
- package/dist/resources/skills/userinterface-wiki/rules/a11y-visual-equivalent.md +36 -0
- package/dist/resources/skills/userinterface-wiki/rules/a11y-volume-control.md +28 -0
- package/dist/resources/skills/userinterface-wiki/rules/appropriate-confirmations-only.md +19 -0
- package/dist/resources/skills/userinterface-wiki/rules/appropriate-errors-warnings.md +18 -0
- package/dist/resources/skills/userinterface-wiki/rules/appropriate-no-decorative.md +21 -0
- package/dist/resources/skills/userinterface-wiki/rules/appropriate-no-high-frequency.md +28 -0
- package/dist/resources/skills/userinterface-wiki/rules/appropriate-no-punishing.md +27 -0
- package/dist/resources/skills/userinterface-wiki/rules/container-callback-ref.md +31 -0
- package/dist/resources/skills/userinterface-wiki/rules/container-guard-initial-zero.md +25 -0
- package/dist/resources/skills/userinterface-wiki/rules/container-no-excessive-use.md +13 -0
- package/dist/resources/skills/userinterface-wiki/rules/container-overflow-hidden.md +25 -0
- package/dist/resources/skills/userinterface-wiki/rules/container-transition-delay.md +21 -0
- package/dist/resources/skills/userinterface-wiki/rules/container-two-div-pattern.md +35 -0
- package/dist/resources/skills/userinterface-wiki/rules/container-use-resize-observer.md +48 -0
- package/dist/resources/skills/userinterface-wiki/rules/context-cleanup-nodes.md +25 -0
- package/dist/resources/skills/userinterface-wiki/rules/context-resume-suspended.md +28 -0
- package/dist/resources/skills/userinterface-wiki/rules/context-reuse-single.md +30 -0
- package/dist/resources/skills/userinterface-wiki/rules/design-filter-for-character.md +25 -0
- package/dist/resources/skills/userinterface-wiki/rules/design-noise-for-percussion.md +26 -0
- package/dist/resources/skills/userinterface-wiki/rules/design-oscillator-for-tonal.md +22 -0
- package/dist/resources/skills/userinterface-wiki/rules/duration-max-300ms.md +21 -0
- package/dist/resources/skills/userinterface-wiki/rules/duration-press-hover.md +21 -0
- package/dist/resources/skills/userinterface-wiki/rules/duration-shorten-before-curve.md +21 -0
- package/dist/resources/skills/userinterface-wiki/rules/duration-small-state.md +15 -0
- package/dist/resources/skills/userinterface-wiki/rules/easing-entrance-ease-out.md +21 -0
- package/dist/resources/skills/userinterface-wiki/rules/easing-exit-ease-in.md +21 -0
- package/dist/resources/skills/userinterface-wiki/rules/easing-for-state-change.md +27 -0
- package/dist/resources/skills/userinterface-wiki/rules/easing-linear-only-progress.md +21 -0
- package/dist/resources/skills/userinterface-wiki/rules/easing-natural-decay.md +22 -0
- package/dist/resources/skills/userinterface-wiki/rules/easing-no-linear-motion.md +22 -0
- package/dist/resources/skills/userinterface-wiki/rules/easing-transition-ease-in-out.md +15 -0
- package/dist/resources/skills/userinterface-wiki/rules/envelope-exponential-decay.md +21 -0
- package/dist/resources/skills/userinterface-wiki/rules/envelope-no-zero-target.md +21 -0
- package/dist/resources/skills/userinterface-wiki/rules/envelope-set-initial-value.md +22 -0
- package/dist/resources/skills/userinterface-wiki/rules/exit-key-required.md +29 -0
- package/dist/resources/skills/userinterface-wiki/rules/exit-matches-initial.md +29 -0
- package/dist/resources/skills/userinterface-wiki/rules/exit-prop-required.md +33 -0
- package/dist/resources/skills/userinterface-wiki/rules/exit-requires-wrapper.md +27 -0
- package/dist/resources/skills/userinterface-wiki/rules/impl-default-subtle.md +21 -0
- package/dist/resources/skills/userinterface-wiki/rules/impl-preload-audio.md +34 -0
- package/dist/resources/skills/userinterface-wiki/rules/impl-reset-current-time.md +26 -0
- package/dist/resources/skills/userinterface-wiki/rules/mode-pop-layout-for-lists.md +25 -0
- package/dist/resources/skills/userinterface-wiki/rules/mode-sync-layout-conflict.md +29 -0
- package/dist/resources/skills/userinterface-wiki/rules/mode-wait-doubles-duration.md +25 -0
- package/dist/resources/skills/userinterface-wiki/rules/morphing-aria-hidden.md +21 -0
- package/dist/resources/skills/userinterface-wiki/rules/morphing-consistent-viewbox.md +23 -0
- package/dist/resources/skills/userinterface-wiki/rules/morphing-group-variants.md +33 -0
- package/dist/resources/skills/userinterface-wiki/rules/morphing-jump-non-grouped.md +29 -0
- package/dist/resources/skills/userinterface-wiki/rules/morphing-reduced-motion.md +28 -0
- package/dist/resources/skills/userinterface-wiki/rules/morphing-spring-rotation.md +23 -0
- package/dist/resources/skills/userinterface-wiki/rules/morphing-strokelinecap-round.md +21 -0
- package/dist/resources/skills/userinterface-wiki/rules/morphing-three-lines.md +32 -0
- package/dist/resources/skills/userinterface-wiki/rules/morphing-use-collapsed.md +33 -0
- package/dist/resources/skills/userinterface-wiki/rules/native-backdrop-styling.md +27 -0
- package/dist/resources/skills/userinterface-wiki/rules/native-placeholder-styling.md +27 -0
- package/dist/resources/skills/userinterface-wiki/rules/native-selection-styling.md +18 -0
- package/dist/resources/skills/userinterface-wiki/rules/nested-consistent-timing.md +25 -0
- package/dist/resources/skills/userinterface-wiki/rules/nested-propagate-required.md +41 -0
- package/dist/resources/skills/userinterface-wiki/rules/none-context-menu-entrance.md +25 -0
- package/dist/resources/skills/userinterface-wiki/rules/none-high-frequency.md +29 -0
- package/dist/resources/skills/userinterface-wiki/rules/none-keyboard-navigation.md +32 -0
- package/dist/resources/skills/userinterface-wiki/rules/param-click-duration.md +21 -0
- package/dist/resources/skills/userinterface-wiki/rules/param-filter-frequency-range.md +21 -0
- package/dist/resources/skills/userinterface-wiki/rules/param-q-value-range.md +21 -0
- package/dist/resources/skills/userinterface-wiki/rules/param-reasonable-gain.md +21 -0
- package/dist/resources/skills/userinterface-wiki/rules/physics-active-state.md +23 -0
- package/dist/resources/skills/userinterface-wiki/rules/physics-no-excessive-stagger.md +22 -0
- package/dist/resources/skills/userinterface-wiki/rules/physics-spring-for-overshoot.md +23 -0
- package/dist/resources/skills/userinterface-wiki/rules/physics-subtle-deformation.md +22 -0
- package/dist/resources/skills/userinterface-wiki/rules/prefetch-hit-slop.md +27 -0
- package/dist/resources/skills/userinterface-wiki/rules/prefetch-keyboard-tab.md +19 -0
- package/dist/resources/skills/userinterface-wiki/rules/prefetch-not-everything.md +22 -0
- package/dist/resources/skills/userinterface-wiki/rules/prefetch-touch-fallback.md +34 -0
- package/dist/resources/skills/userinterface-wiki/rules/prefetch-trajectory-over-hover.md +32 -0
- package/dist/resources/skills/userinterface-wiki/rules/prefetch-use-selectively.md +13 -0
- package/dist/resources/skills/userinterface-wiki/rules/presence-disable-interactions.md +31 -0
- package/dist/resources/skills/userinterface-wiki/rules/presence-hook-in-child.md +31 -0
- package/dist/resources/skills/userinterface-wiki/rules/presence-safe-to-remove.md +37 -0
- package/dist/resources/skills/userinterface-wiki/rules/pseudo-content-required.md +28 -0
- package/dist/resources/skills/userinterface-wiki/rules/pseudo-first-line-styling.md +27 -0
- package/dist/resources/skills/userinterface-wiki/rules/pseudo-hit-target-expansion.md +31 -0
- package/dist/resources/skills/userinterface-wiki/rules/pseudo-marker-styling.md +28 -0
- package/dist/resources/skills/userinterface-wiki/rules/pseudo-over-dom-node.md +32 -0
- package/dist/resources/skills/userinterface-wiki/rules/pseudo-position-relative-parent.md +33 -0
- package/dist/resources/skills/userinterface-wiki/rules/pseudo-z-index-layering.md +37 -0
- package/dist/resources/skills/userinterface-wiki/rules/spring-for-gestures.md +27 -0
- package/dist/resources/skills/userinterface-wiki/rules/spring-for-interruptible.md +27 -0
- package/dist/resources/skills/userinterface-wiki/rules/spring-params-balanced.md +29 -0
- package/dist/resources/skills/userinterface-wiki/rules/spring-preserves-velocity.md +28 -0
- package/dist/resources/skills/userinterface-wiki/rules/staging-dim-background.md +22 -0
- package/dist/resources/skills/userinterface-wiki/rules/staging-one-focal-point.md +24 -0
- package/dist/resources/skills/userinterface-wiki/rules/staging-z-index-hierarchy.md +22 -0
- package/dist/resources/skills/userinterface-wiki/rules/timing-consistent.md +24 -0
- package/dist/resources/skills/userinterface-wiki/rules/timing-no-entrance-context-menu.md +22 -0
- package/dist/resources/skills/userinterface-wiki/rules/timing-under-300ms.md +22 -0
- package/dist/resources/skills/userinterface-wiki/rules/transition-name-cleanup.md +28 -0
- package/dist/resources/skills/userinterface-wiki/rules/transition-name-required.md +27 -0
- package/dist/resources/skills/userinterface-wiki/rules/transition-name-unique.md +24 -0
- package/dist/resources/skills/userinterface-wiki/rules/transition-over-js-library.md +32 -0
- package/dist/resources/skills/userinterface-wiki/rules/transition-style-pseudo-elements.md +24 -0
- package/dist/resources/skills/userinterface-wiki/rules/type-antialiased-on-retina.md +18 -0
- package/dist/resources/skills/userinterface-wiki/rules/type-disambiguation-stylistic-set.md +15 -0
- package/dist/resources/skills/userinterface-wiki/rules/type-font-display-swap.md +28 -0
- package/dist/resources/skills/userinterface-wiki/rules/type-justify-with-hyphens.md +24 -0
- package/dist/resources/skills/userinterface-wiki/rules/type-letter-spacing-uppercase.md +28 -0
- package/dist/resources/skills/userinterface-wiki/rules/type-no-font-synthesis.md +18 -0
- package/dist/resources/skills/userinterface-wiki/rules/type-oldstyle-nums-for-prose.md +21 -0
- package/dist/resources/skills/userinterface-wiki/rules/type-opentype-contextual-alternates.md +15 -0
- package/dist/resources/skills/userinterface-wiki/rules/type-optical-sizing-auto.md +25 -0
- package/dist/resources/skills/userinterface-wiki/rules/type-proper-fractions.md +15 -0
- package/dist/resources/skills/userinterface-wiki/rules/type-slashed-zero.md +17 -0
- package/dist/resources/skills/userinterface-wiki/rules/type-tabular-nums-for-data.md +21 -0
- package/dist/resources/skills/userinterface-wiki/rules/type-text-wrap-balance-headings.md +21 -0
- package/dist/resources/skills/userinterface-wiki/rules/type-text-wrap-pretty.md +16 -0
- package/dist/resources/skills/userinterface-wiki/rules/type-underline-offset.md +25 -0
- package/dist/resources/skills/userinterface-wiki/rules/type-variable-weight-continuous.md +23 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-aesthetic-usability.md +32 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-cognitive-load-reduce.md +49 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-common-region-boundaries.md +50 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-doherty-perceived-speed.md +29 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-doherty-under-400ms.md +30 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-fitts-hit-area.md +32 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-fitts-target-size.md +31 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-goal-gradient-progress.md +33 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-hicks-minimize-choices.md +45 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-jakobs-familiar-patterns.md +37 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-millers-chunking.md +23 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-pareto-prioritize-features.md +36 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-peak-end-finish-strong.md +35 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-postels-accept-messy-input.md +45 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-pragnanz-simplify.md +33 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-progressive-disclosure.md +41 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-proximity-grouping.md +38 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-serial-position.md +31 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-similarity-consistency.md +35 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-teslers-complexity.md +28 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-uniform-connectedness.md +43 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-von-restorff-emphasis.md +29 -0
- package/dist/resources/skills/userinterface-wiki/rules/ux-zeigarnik-show-incomplete.md +36 -0
- package/dist/resources/skills/userinterface-wiki/rules/visual-animate-shadow-pseudo.md +49 -0
- package/dist/resources/skills/userinterface-wiki/rules/visual-border-alpha-colors.md +25 -0
- package/dist/resources/skills/userinterface-wiki/rules/visual-button-shadow-anatomy.md +49 -0
- package/dist/resources/skills/userinterface-wiki/rules/visual-concentric-radius.md +40 -0
- package/dist/resources/skills/userinterface-wiki/rules/visual-consistent-spacing-scale.md +35 -0
- package/dist/resources/skills/userinterface-wiki/rules/visual-layered-shadows.md +30 -0
- package/dist/resources/skills/userinterface-wiki/rules/visual-no-pure-black-shadow.md +25 -0
- package/dist/resources/skills/userinterface-wiki/rules/visual-shadow-direction.md +25 -0
- package/dist/resources/skills/userinterface-wiki/rules/visual-shadow-matches-elevation.md +23 -0
- package/dist/resources/skills/userinterface-wiki/rules/weight-duration-matches-action.md +29 -0
- package/dist/resources/skills/userinterface-wiki/rules/weight-match-action.md +32 -0
- package/dist/resources/skills/web-design-guidelines/SKILL.md +39 -0
- package/dist/resources/skills/web-quality-audit/SKILL.md +170 -0
- package/dist/resources/skills/web-quality-audit/scripts/analyze.sh +91 -0
- package/package.json +22 -8
- package/packages/native/dist/native.d.ts +2 -0
- package/packages/native/dist/native.js +19 -5
- package/packages/native/package.json +28 -0
- package/packages/native/src/native.ts +23 -9
- package/packages/pi-agent-core/package.json +6 -0
- package/packages/pi-ai/dist/models.generated.d.ts +43 -11
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +34 -26
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +3 -2
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +14 -4
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/oauth.d.ts +1 -0
- package/packages/pi-ai/oauth.js +1 -0
- package/packages/pi-ai/package.json +2 -2
- package/packages/pi-ai/src/models.generated.ts +42 -34
- package/packages/pi-ai/src/providers/anthropic.ts +3 -2
- package/packages/pi-ai/src/providers/openai-codex-responses.ts +15 -4
- package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -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 +2 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js +2 -1
- package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js +3 -2
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/utils.js +2 -2
- package/packages/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/constants.d.ts +29 -0
- package/packages/pi-coding-agent/dist/core/constants.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/constants.js +44 -0
- package/packages/pi-coding-agent/dist/core/constants.js.map +1 -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 +14 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.js +30 -4
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.js +13 -5
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +14 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resolve-config-value.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resolve-config-value.js +12 -4
- package/packages/pi-coding-agent/dist/core/resolve-config-value.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +49 -0
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +25 -2
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -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 +22 -5
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +10 -0
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.js +2 -2
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/find.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/find.js +2 -1
- package/packages/pi-coding-agent/dist/core/tools/find.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/hashline-edit.js +4 -4
- package/packages/pi-coding-agent/dist/core/tools/hashline-edit.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/hashline.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/tools/hashline.js +1 -1
- package/packages/pi-coding-agent/dist/core/tools/hashline.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/hashline.test.js +8 -8
- package/packages/pi-coding-agent/dist/core/tools/hashline.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/path-utils.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/tools/path-utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/path-utils.js +1 -1
- package/packages/pi-coding-agent/dist/core/tools/path-utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/truncate.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/truncate.js +2 -1
- package/packages/pi-coding-agent/dist/core/tools/truncate.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-selector.d.ts +9 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-selector.js +47 -5
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +4 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.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/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +25 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +12 -2
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/pipeline.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/pipeline.js +25 -3
- package/packages/pi-coding-agent/dist/resources/extensions/memory/pipeline.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -5
- package/packages/pi-coding-agent/scripts/copy-assets.cjs +39 -8
- package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.ts +2 -1
- package/packages/pi-coding-agent/src/core/compaction/branch-summarization.ts +2 -1
- package/packages/pi-coding-agent/src/core/compaction/compaction.ts +3 -2
- package/packages/pi-coding-agent/src/core/compaction/utils.ts +2 -2
- package/packages/pi-coding-agent/src/core/constants.ts +59 -0
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +14 -1
- package/packages/pi-coding-agent/src/core/lsp/index.ts +31 -5
- package/packages/pi-coding-agent/src/core/lsp/lspmux.ts +13 -5
- package/packages/pi-coding-agent/src/core/model-resolver.ts +14 -0
- package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +58 -0
- package/packages/pi-coding-agent/src/core/resolve-config-value.ts +14 -4
- package/packages/pi-coding-agent/src/core/session-manager.ts +29 -6
- package/packages/pi-coding-agent/src/core/settings-manager.ts +33 -5
- package/packages/pi-coding-agent/src/core/system-prompt.ts +11 -0
- package/packages/pi-coding-agent/src/core/tools/edit-diff.ts +2 -2
- package/packages/pi-coding-agent/src/core/tools/find.ts +2 -1
- package/packages/pi-coding-agent/src/core/tools/hashline-edit.ts +4 -4
- package/packages/pi-coding-agent/src/core/tools/hashline.test.ts +8 -8
- package/packages/pi-coding-agent/src/core/tools/hashline.ts +1 -1
- package/packages/pi-coding-agent/src/core/tools/index.ts +1 -1
- package/packages/pi-coding-agent/src/core/tools/path-utils.ts +1 -1
- package/packages/pi-coding-agent/src/core/tools/truncate.ts +3 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/extension-selector.ts +49 -5
- package/packages/pi-coding-agent/src/modes/interactive/components/index.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +4 -4
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +15 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +26 -0
- package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +11 -2
- package/packages/pi-coding-agent/src/resources/extensions/memory/pipeline.ts +23 -3
- package/packages/pi-tui/dist/__tests__/autocomplete.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/autocomplete.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js +149 -0
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -0
- package/packages/pi-tui/dist/__tests__/fuzzy.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/fuzzy.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/fuzzy.test.js +94 -0
- package/packages/pi-tui/dist/__tests__/fuzzy.test.js.map +1 -0
- package/packages/pi-tui/dist/autocomplete.d.ts +8 -1
- package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/packages/pi-tui/dist/autocomplete.js +20 -2
- package/packages/pi-tui/dist/autocomplete.js.map +1 -1
- package/packages/pi-tui/package.json +8 -2
- package/packages/pi-tui/src/__tests__/autocomplete.test.ts +186 -0
- package/packages/pi-tui/src/__tests__/fuzzy.test.ts +112 -0
- package/packages/pi-tui/src/autocomplete.ts +26 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/ask-user-questions.ts +3 -2
- package/src/resources/extensions/bg-shell/bg-shell-command.ts +219 -0
- package/src/resources/extensions/bg-shell/bg-shell-lifecycle.ts +400 -0
- package/src/resources/extensions/bg-shell/bg-shell-tool.ts +985 -0
- package/src/resources/extensions/bg-shell/index.ts +17 -1561
- package/src/resources/extensions/bg-shell/overlay.ts +4 -0
- package/src/resources/extensions/bg-shell/process-manager.ts +13 -0
- package/src/resources/extensions/bg-shell/utilities.ts +4 -16
- package/src/resources/extensions/browser-tools/capture.ts +34 -2
- package/src/resources/extensions/browser-tools/lifecycle.ts +5 -5
- package/src/resources/extensions/browser-tools/settle.ts +1 -1
- package/src/resources/extensions/browser-tools/state.ts +5 -5
- package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +1 -1
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +3 -3
- package/src/resources/extensions/browser-tools/tools/assertions.ts +1 -1
- package/src/resources/extensions/browser-tools/tools/device.ts +1 -1
- package/src/resources/extensions/browser-tools/tools/extract.ts +1 -1
- package/src/resources/extensions/browser-tools/tools/navigation.ts +6 -6
- package/src/resources/extensions/browser-tools/tools/network-mock.ts +1 -1
- package/src/resources/extensions/browser-tools/tools/pages.ts +1 -1
- package/src/resources/extensions/browser-tools/tools/screenshot.ts +28 -10
- package/src/resources/extensions/browser-tools/tools/state-persistence.ts +1 -1
- package/src/resources/extensions/browser-tools/tools/visual-diff.ts +1 -1
- package/src/resources/extensions/browser-tools/utils.ts +5 -5
- package/src/resources/extensions/get-secrets-from-user.ts +1 -1
- package/src/resources/extensions/google-search/index.ts +21 -8
- package/src/resources/extensions/gsd/activity-log.ts +2 -1
- package/src/resources/extensions/gsd/atomic-write.ts +35 -0
- package/src/resources/extensions/gsd/auto/session.ts +12 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +264 -62
- package/src/resources/extensions/gsd/auto-idempotency.ts +150 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +594 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +116 -22
- package/src/resources/extensions/gsd/auto-recovery.ts +36 -31
- package/src/resources/extensions/gsd/auto-start.ts +500 -0
- package/src/resources/extensions/gsd/auto-stuck-detection.ts +220 -0
- package/src/resources/extensions/gsd/auto-timers.ts +223 -0
- package/src/resources/extensions/gsd/auto-unit-closeout.ts +3 -1
- package/src/resources/extensions/gsd/auto-verification.ts +229 -0
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +29 -37
- package/src/resources/extensions/gsd/auto-worktree.ts +83 -67
- package/src/resources/extensions/gsd/auto.ts +375 -1890
- package/src/resources/extensions/gsd/commands-config.ts +102 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +394 -0
- package/src/resources/extensions/gsd/commands-inspect.ts +90 -0
- package/src/resources/extensions/gsd/commands-logs.ts +536 -0
- package/src/resources/extensions/gsd/commands-maintenance.ts +206 -0
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +790 -0
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
- package/src/resources/extensions/gsd/commands.ts +417 -1495
- package/src/resources/extensions/gsd/constants.ts +21 -0
- package/src/resources/extensions/gsd/context-budget.ts +25 -2
- package/src/resources/extensions/gsd/crash-recovery.ts +3 -4
- package/src/resources/extensions/gsd/dashboard-overlay.ts +15 -5
- package/src/resources/extensions/gsd/db-writer.ts +21 -2
- package/src/resources/extensions/gsd/detection.ts +469 -0
- package/src/resources/extensions/gsd/diff-context.ts +2 -1
- package/src/resources/extensions/gsd/dispatch-guard.ts +4 -0
- package/src/resources/extensions/gsd/doctor-checks.ts +564 -0
- package/src/resources/extensions/gsd/doctor-format.ts +78 -0
- package/src/resources/extensions/gsd/doctor-types.ts +72 -0
- package/src/resources/extensions/gsd/doctor.ts +64 -701
- package/src/resources/extensions/gsd/errors.ts +0 -2
- package/src/resources/extensions/gsd/export-html.ts +367 -11
- package/src/resources/extensions/gsd/export.ts +31 -5
- package/src/resources/extensions/gsd/files.ts +8 -126
- package/src/resources/extensions/gsd/forensics.ts +2 -12
- package/src/resources/extensions/gsd/git-constants.ts +11 -0
- package/src/resources/extensions/gsd/git-service.ts +13 -9
- package/src/resources/extensions/gsd/gsd-db.ts +26 -6
- package/src/resources/extensions/gsd/guided-flow-queue.ts +451 -0
- package/src/resources/extensions/gsd/guided-flow.ts +231 -514
- package/src/resources/extensions/gsd/history.ts +2 -20
- package/src/resources/extensions/gsd/index.ts +208 -46
- package/src/resources/extensions/gsd/init-wizard.ts +615 -0
- package/src/resources/extensions/gsd/json-persistence.ts +67 -0
- package/src/resources/extensions/gsd/jsonl-utils.ts +21 -0
- package/src/resources/extensions/gsd/key-manager.ts +995 -0
- package/src/resources/extensions/gsd/metrics.ts +49 -36
- package/src/resources/extensions/gsd/migrate/command.ts +1 -1
- package/src/resources/extensions/gsd/migrate/parsers.ts +10 -95
- package/src/resources/extensions/gsd/milestone-actions.ts +126 -0
- package/src/resources/extensions/gsd/milestone-ids.ts +95 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +5 -10
- package/src/resources/extensions/gsd/parallel-eligibility.ts +3 -3
- package/src/resources/extensions/gsd/paths.ts +1 -11
- package/src/resources/extensions/gsd/plugin-importer.ts +3 -2
- package/src/resources/extensions/gsd/preferences-models.ts +323 -0
- package/src/resources/extensions/gsd/preferences-skills.ts +169 -0
- package/src/resources/extensions/gsd/preferences-types.ts +223 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +597 -0
- package/src/resources/extensions/gsd/preferences.ts +219 -1305
- package/src/resources/extensions/gsd/prompt-cache-optimizer.ts +213 -0
- package/src/resources/extensions/gsd/prompt-compressor.ts +508 -0
- package/src/resources/extensions/gsd/prompt-loader.ts +4 -2
- package/src/resources/extensions/gsd/prompt-ordering.ts +200 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -2
- package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +2 -4
- package/src/resources/extensions/gsd/prompts/discuss.md +13 -5
- package/src/resources/extensions/gsd/prompts/execute-task.md +0 -1
- package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +0 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +0 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +0 -1
- package/src/resources/extensions/gsd/prompts/queue.md +30 -0
- package/src/resources/extensions/gsd/prompts/quick-task.md +0 -6
- package/src/resources/extensions/gsd/prompts/replan-slice.md +0 -1
- package/src/resources/extensions/gsd/prompts/rewrite-docs.md +0 -1
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/prompts/workflow-start.md +28 -0
- package/src/resources/extensions/gsd/provider-error-pause.ts +59 -10
- package/src/resources/extensions/gsd/queue-order.ts +11 -12
- package/src/resources/extensions/gsd/queue-reorder-ui.ts +15 -2
- package/src/resources/extensions/gsd/quick.ts +18 -15
- package/src/resources/extensions/gsd/reports.ts +1 -7
- package/src/resources/extensions/gsd/routing-history.ts +13 -17
- package/src/resources/extensions/gsd/safe-fs.ts +47 -0
- package/src/resources/extensions/gsd/semantic-chunker.ts +336 -0
- package/src/resources/extensions/gsd/session-forensics.ts +8 -23
- package/src/resources/extensions/gsd/session-lock.ts +284 -0
- package/src/resources/extensions/gsd/session-status-io.ts +23 -41
- package/src/resources/extensions/gsd/skills/gsd-headless/SKILL.md +38 -1
- package/src/resources/extensions/gsd/skills/gsd-headless/references/answer-injection.md +35 -6
- package/src/resources/extensions/gsd/state.ts +54 -2
- package/src/resources/extensions/gsd/structured-data-formatter.ts +144 -0
- package/src/resources/extensions/gsd/summary-distiller.ts +258 -0
- package/src/resources/extensions/gsd/tests/activity-log.test.ts +213 -0
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +197 -0
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +33 -39
- package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +108 -2
- package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +257 -0
- package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/commands-logs.test.ts +241 -0
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/detection.test.ts +398 -0
- package/src/resources/extensions/gsd/tests/discuss-prompt.test.ts +12 -24
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +118 -94
- package/src/resources/extensions/gsd/tests/dispatch-stall-guard.test.ts +126 -0
- package/src/resources/extensions/gsd/tests/dist-redirect.mjs +7 -3
- package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/doctor-git.test.ts +17 -55
- package/src/resources/extensions/gsd/tests/export-html-enhancements.test.ts +375 -0
- package/src/resources/extensions/gsd/tests/extension-selector-separator.test.ts +144 -0
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/headless-answers.test.ts +340 -0
- package/src/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +24 -82
- package/src/resources/extensions/gsd/tests/init-wizard.test.ts +197 -0
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +414 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +173 -305
- package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/model-isolation.test.ts +59 -1
- package/src/resources/extensions/gsd/tests/next-milestone-id.test.ts +18 -61
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +17 -8
- package/src/resources/extensions/gsd/tests/parallel-merge.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/park-edge-cases.test.ts +276 -0
- package/src/resources/extensions/gsd/tests/park-milestone.test.ts +401 -0
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +23 -47
- package/src/resources/extensions/gsd/tests/preferences.test.ts +284 -0
- package/src/resources/extensions/gsd/tests/prompt-cache-optimizer.test.ts +314 -0
- package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +529 -0
- package/src/resources/extensions/gsd/tests/prompt-ordering.test.ts +296 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +338 -0
- package/src/resources/extensions/gsd/tests/reassess-detection.test.ts +154 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/remote-status.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +43 -60
- package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +426 -0
- package/src/resources/extensions/gsd/tests/session-lock.test.ts +315 -0
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +8 -5
- package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +365 -0
- package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +323 -0
- package/src/resources/extensions/gsd/tests/token-counter.test.ts +129 -0
- package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +1272 -0
- package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +164 -0
- package/src/resources/extensions/gsd/tests/token-profile.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +69 -73
- package/src/resources/extensions/gsd/tests/validate-directory.test.ts +222 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +26 -24
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +251 -8
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
- package/src/resources/extensions/gsd/tests/workspace-index.test.ts +24 -61
- package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +5 -2
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +132 -43
- package/src/resources/extensions/gsd/token-counter.ts +20 -0
- package/src/resources/extensions/gsd/triage-ui.ts +1 -1
- package/src/resources/extensions/gsd/types.ts +5 -1
- package/src/resources/extensions/gsd/unit-runtime.ts +16 -13
- package/src/resources/extensions/gsd/validate-directory.ts +164 -0
- package/src/resources/extensions/gsd/verification-evidence.ts +9 -4
- package/src/resources/extensions/gsd/verification-gate.ts +83 -7
- package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
- package/src/resources/extensions/gsd/visualizer-overlay.ts +5 -2
- package/src/resources/extensions/gsd/visualizer-views.ts +2 -3
- package/src/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
- package/src/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
- package/src/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
- package/src/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
- package/src/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
- package/src/resources/extensions/gsd/workflow-templates/registry.json +85 -0
- package/src/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
- package/src/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
- package/src/resources/extensions/gsd/workflow-templates/spike.md +69 -0
- package/src/resources/extensions/gsd/workflow-templates.ts +241 -0
- package/src/resources/extensions/gsd/worktree-command.ts +4 -51
- package/src/resources/extensions/gsd/worktree-manager.ts +7 -9
- package/src/resources/extensions/gsd/worktree.ts +41 -1
- package/src/resources/extensions/mcp-client/index.ts +459 -0
- package/src/resources/extensions/remote-questions/discord-adapter.ts +9 -20
- package/src/resources/extensions/remote-questions/http-client.ts +76 -0
- package/src/resources/extensions/remote-questions/manager.ts +6 -24
- package/src/resources/extensions/remote-questions/mod.ts +16 -0
- package/src/resources/extensions/remote-questions/notify.ts +90 -0
- package/src/resources/extensions/remote-questions/remote-command.ts +1 -1
- package/src/resources/extensions/remote-questions/slack-adapter.ts +11 -18
- package/src/resources/extensions/remote-questions/store.ts +5 -1
- package/src/resources/extensions/remote-questions/telegram-adapter.ts +8 -20
- package/src/resources/extensions/remote-questions/types.ts +29 -3
- package/src/resources/extensions/search-the-web/native-search.ts +7 -0
- package/src/resources/extensions/search-the-web/provider.ts +15 -3
- package/src/resources/extensions/search-the-web/tool-llm-context.ts +1 -13
- package/src/resources/extensions/search-the-web/tool-search.ts +1 -13
- package/src/resources/extensions/shared/format-utils.ts +53 -0
- package/src/resources/extensions/shared/frontmatter.ts +117 -0
- package/src/resources/extensions/shared/mod.ts +33 -0
- package/src/resources/extensions/shared/sanitize.ts +19 -0
- package/src/resources/extensions/slash-commands/create-extension.ts +1 -1
- package/src/resources/extensions/slash-commands/create-slash-command.ts +1 -1
- package/src/resources/extensions/subagent/index.ts +1 -2
- package/src/resources/extensions/ttsr/index.ts +5 -0
- package/src/resources/extensions/ttsr/rule-loader.ts +4 -51
- package/src/resources/extensions/universal-config/discovery.ts +37 -15
- package/src/resources/extensions/voice/index.ts +1 -1
- package/src/resources/skills/accessibility/SKILL.md +522 -0
- package/src/resources/skills/accessibility/references/WCAG.md +162 -0
- package/src/resources/skills/agent-browser/SKILL.md +517 -0
- package/src/resources/skills/agent-browser/references/authentication.md +202 -0
- package/src/resources/skills/agent-browser/references/commands.md +263 -0
- package/src/resources/skills/agent-browser/references/profiling.md +120 -0
- package/src/resources/skills/agent-browser/references/proxy-support.md +194 -0
- package/src/resources/skills/agent-browser/references/session-management.md +193 -0
- package/src/resources/skills/agent-browser/references/snapshot-refs.md +194 -0
- package/src/resources/skills/agent-browser/references/video-recording.md +173 -0
- package/src/resources/skills/agent-browser/templates/authenticated-session.sh +105 -0
- package/src/resources/skills/agent-browser/templates/capture-workflow.sh +69 -0
- package/src/resources/skills/agent-browser/templates/form-automation.sh +62 -0
- package/src/resources/skills/best-practices/SKILL.md +583 -0
- package/src/resources/skills/code-optimizer/SKILL.md +160 -0
- package/src/resources/skills/code-optimizer/references/algorithmic-complexity.md +66 -0
- package/src/resources/skills/code-optimizer/references/build-compilation.md +90 -0
- package/src/resources/skills/code-optimizer/references/bundle-dependencies.md +82 -0
- package/src/resources/skills/code-optimizer/references/caching-memoization.md +76 -0
- package/src/resources/skills/code-optimizer/references/concurrency-async.md +80 -0
- package/src/resources/skills/code-optimizer/references/config-infra.md +71 -0
- package/src/resources/skills/code-optimizer/references/data-structures.md +80 -0
- package/src/resources/skills/code-optimizer/references/database-queries.md +76 -0
- package/src/resources/skills/code-optimizer/references/dead-code-redundancy.md +84 -0
- package/src/resources/skills/code-optimizer/references/error-resilience.md +80 -0
- package/src/resources/skills/code-optimizer/references/io-network.md +89 -0
- package/src/resources/skills/code-optimizer/references/logging-observability.md +64 -0
- package/src/resources/skills/code-optimizer/references/memory-resources.md +66 -0
- package/src/resources/skills/code-optimizer/references/rendering-ui.md +90 -0
- package/src/resources/skills/code-optimizer/references/security-performance.md +68 -0
- package/src/resources/skills/core-web-vitals/SKILL.md +441 -0
- package/src/resources/skills/core-web-vitals/references/LCP.md +208 -0
- package/src/resources/skills/create-gsd-extension/SKILL.md +87 -0
- package/src/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
- package/src/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
- package/src/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
- package/src/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
- package/src/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
- package/src/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
- package/src/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
- package/src/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
- package/src/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
- package/src/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
- package/src/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
- package/src/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
- package/src/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
- package/src/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
- package/src/resources/skills/create-gsd-extension/references/state-management.md +70 -0
- package/src/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
- package/src/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
- package/src/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
- package/src/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
- package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
- package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
- package/src/resources/skills/create-skill/SKILL.md +184 -0
- package/src/resources/skills/create-skill/references/api-security.md +226 -0
- package/src/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
- package/src/resources/skills/create-skill/references/common-patterns.md +595 -0
- package/src/resources/skills/create-skill/references/core-principles.md +437 -0
- package/src/resources/skills/create-skill/references/executable-code.md +175 -0
- package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
- package/src/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
- package/src/resources/skills/create-skill/references/recommended-structure.md +168 -0
- package/src/resources/skills/create-skill/references/skill-structure.md +372 -0
- package/src/resources/skills/create-skill/references/use-xml-tags.md +466 -0
- package/src/resources/skills/create-skill/references/using-scripts.md +113 -0
- package/src/resources/skills/create-skill/references/using-templates.md +112 -0
- package/src/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
- package/src/resources/skills/create-skill/templates/router-skill.md +73 -0
- package/src/resources/skills/create-skill/templates/simple-skill.md +33 -0
- package/src/resources/skills/create-skill/workflows/add-reference.md +96 -0
- package/src/resources/skills/create-skill/workflows/add-script.md +93 -0
- package/src/resources/skills/create-skill/workflows/add-template.md +74 -0
- package/src/resources/skills/create-skill/workflows/add-workflow.md +120 -0
- package/src/resources/skills/create-skill/workflows/audit-skill.md +148 -0
- package/src/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
- package/src/resources/skills/create-skill/workflows/get-guidance.md +121 -0
- package/src/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
- package/src/resources/skills/create-skill/workflows/verify-skill.md +204 -0
- package/src/resources/skills/make-interfaces-feel-better/SKILL.md +122 -0
- package/src/resources/skills/make-interfaces-feel-better/animations.md +379 -0
- package/src/resources/skills/make-interfaces-feel-better/performance.md +88 -0
- package/src/resources/skills/make-interfaces-feel-better/surfaces.md +247 -0
- package/src/resources/skills/make-interfaces-feel-better/typography.md +123 -0
- package/src/resources/skills/react-best-practices/README.md +123 -0
- package/src/resources/skills/react-best-practices/SKILL.md +136 -0
- package/src/resources/skills/react-best-practices/metadata.json +15 -0
- package/src/resources/skills/react-best-practices/rules/_sections.md +46 -0
- package/src/resources/skills/react-best-practices/rules/_template.md +28 -0
- package/src/resources/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/src/resources/skills/react-best-practices/rules/advanced-init-once.md +42 -0
- package/src/resources/skills/react-best-practices/rules/advanced-use-latest.md +39 -0
- package/src/resources/skills/react-best-practices/rules/async-api-routes.md +38 -0
- package/src/resources/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/src/resources/skills/react-best-practices/rules/async-dependencies.md +51 -0
- package/src/resources/skills/react-best-practices/rules/async-parallel.md +28 -0
- package/src/resources/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/src/resources/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/src/resources/skills/react-best-practices/rules/bundle-conditional.md +31 -0
- package/src/resources/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/src/resources/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/src/resources/skills/react-best-practices/rules/bundle-preload.md +50 -0
- package/src/resources/skills/react-best-practices/rules/client-event-listeners.md +74 -0
- package/src/resources/skills/react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/src/resources/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/src/resources/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/src/resources/skills/react-best-practices/rules/js-batch-dom-css.md +107 -0
- package/src/resources/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/src/resources/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/src/resources/skills/react-best-practices/rules/js-cache-storage.md +70 -0
- package/src/resources/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/src/resources/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/src/resources/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/src/resources/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/src/resources/skills/react-best-practices/rules/js-length-check-first.md +49 -0
- package/src/resources/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/src/resources/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/src/resources/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/src/resources/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/src/resources/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/src/resources/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/src/resources/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/src/resources/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/src/resources/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/src/resources/skills/react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
- package/src/resources/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/src/resources/skills/react-best-practices/rules/rendering-usetransition-loading.md +75 -0
- package/src/resources/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/src/resources/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/src/resources/skills/react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/src/resources/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/src/resources/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/src/resources/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/src/resources/skills/react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
- package/src/resources/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/src/resources/skills/react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/src/resources/skills/react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/src/resources/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/src/resources/skills/react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/src/resources/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/src/resources/skills/react-best-practices/rules/server-auth-actions.md +96 -0
- package/src/resources/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/src/resources/skills/react-best-practices/rules/server-cache-react.md +76 -0
- package/src/resources/skills/react-best-practices/rules/server-dedup-props.md +65 -0
- package/src/resources/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/src/resources/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/src/resources/skills/userinterface-wiki/SKILL.md +253 -0
- package/src/resources/skills/userinterface-wiki/rules/_sections.md +66 -0
- package/src/resources/skills/userinterface-wiki/rules/_template.md +24 -0
- package/src/resources/skills/userinterface-wiki/rules/a11y-reduced-motion-check.md +30 -0
- package/src/resources/skills/userinterface-wiki/rules/a11y-toggle-setting.md +30 -0
- package/src/resources/skills/userinterface-wiki/rules/a11y-visual-equivalent.md +36 -0
- package/src/resources/skills/userinterface-wiki/rules/a11y-volume-control.md +28 -0
- package/src/resources/skills/userinterface-wiki/rules/appropriate-confirmations-only.md +19 -0
- package/src/resources/skills/userinterface-wiki/rules/appropriate-errors-warnings.md +18 -0
- package/src/resources/skills/userinterface-wiki/rules/appropriate-no-decorative.md +21 -0
- package/src/resources/skills/userinterface-wiki/rules/appropriate-no-high-frequency.md +28 -0
- package/src/resources/skills/userinterface-wiki/rules/appropriate-no-punishing.md +27 -0
- package/src/resources/skills/userinterface-wiki/rules/container-callback-ref.md +31 -0
- package/src/resources/skills/userinterface-wiki/rules/container-guard-initial-zero.md +25 -0
- package/src/resources/skills/userinterface-wiki/rules/container-no-excessive-use.md +13 -0
- package/src/resources/skills/userinterface-wiki/rules/container-overflow-hidden.md +25 -0
- package/src/resources/skills/userinterface-wiki/rules/container-transition-delay.md +21 -0
- package/src/resources/skills/userinterface-wiki/rules/container-two-div-pattern.md +35 -0
- package/src/resources/skills/userinterface-wiki/rules/container-use-resize-observer.md +48 -0
- package/src/resources/skills/userinterface-wiki/rules/context-cleanup-nodes.md +25 -0
- package/src/resources/skills/userinterface-wiki/rules/context-resume-suspended.md +28 -0
- package/src/resources/skills/userinterface-wiki/rules/context-reuse-single.md +30 -0
- package/src/resources/skills/userinterface-wiki/rules/design-filter-for-character.md +25 -0
- package/src/resources/skills/userinterface-wiki/rules/design-noise-for-percussion.md +26 -0
- package/src/resources/skills/userinterface-wiki/rules/design-oscillator-for-tonal.md +22 -0
- package/src/resources/skills/userinterface-wiki/rules/duration-max-300ms.md +21 -0
- package/src/resources/skills/userinterface-wiki/rules/duration-press-hover.md +21 -0
- package/src/resources/skills/userinterface-wiki/rules/duration-shorten-before-curve.md +21 -0
- package/src/resources/skills/userinterface-wiki/rules/duration-small-state.md +15 -0
- package/src/resources/skills/userinterface-wiki/rules/easing-entrance-ease-out.md +21 -0
- package/src/resources/skills/userinterface-wiki/rules/easing-exit-ease-in.md +21 -0
- package/src/resources/skills/userinterface-wiki/rules/easing-for-state-change.md +27 -0
- package/src/resources/skills/userinterface-wiki/rules/easing-linear-only-progress.md +21 -0
- package/src/resources/skills/userinterface-wiki/rules/easing-natural-decay.md +22 -0
- package/src/resources/skills/userinterface-wiki/rules/easing-no-linear-motion.md +22 -0
- package/src/resources/skills/userinterface-wiki/rules/easing-transition-ease-in-out.md +15 -0
- package/src/resources/skills/userinterface-wiki/rules/envelope-exponential-decay.md +21 -0
- package/src/resources/skills/userinterface-wiki/rules/envelope-no-zero-target.md +21 -0
- package/src/resources/skills/userinterface-wiki/rules/envelope-set-initial-value.md +22 -0
- package/src/resources/skills/userinterface-wiki/rules/exit-key-required.md +29 -0
- package/src/resources/skills/userinterface-wiki/rules/exit-matches-initial.md +29 -0
- package/src/resources/skills/userinterface-wiki/rules/exit-prop-required.md +33 -0
- package/src/resources/skills/userinterface-wiki/rules/exit-requires-wrapper.md +27 -0
- package/src/resources/skills/userinterface-wiki/rules/impl-default-subtle.md +21 -0
- package/src/resources/skills/userinterface-wiki/rules/impl-preload-audio.md +34 -0
- package/src/resources/skills/userinterface-wiki/rules/impl-reset-current-time.md +26 -0
- package/src/resources/skills/userinterface-wiki/rules/mode-pop-layout-for-lists.md +25 -0
- package/src/resources/skills/userinterface-wiki/rules/mode-sync-layout-conflict.md +29 -0
- package/src/resources/skills/userinterface-wiki/rules/mode-wait-doubles-duration.md +25 -0
- package/src/resources/skills/userinterface-wiki/rules/morphing-aria-hidden.md +21 -0
- package/src/resources/skills/userinterface-wiki/rules/morphing-consistent-viewbox.md +23 -0
- package/src/resources/skills/userinterface-wiki/rules/morphing-group-variants.md +33 -0
- package/src/resources/skills/userinterface-wiki/rules/morphing-jump-non-grouped.md +29 -0
- package/src/resources/skills/userinterface-wiki/rules/morphing-reduced-motion.md +28 -0
- package/src/resources/skills/userinterface-wiki/rules/morphing-spring-rotation.md +23 -0
- package/src/resources/skills/userinterface-wiki/rules/morphing-strokelinecap-round.md +21 -0
- package/src/resources/skills/userinterface-wiki/rules/morphing-three-lines.md +32 -0
- package/src/resources/skills/userinterface-wiki/rules/morphing-use-collapsed.md +33 -0
- package/src/resources/skills/userinterface-wiki/rules/native-backdrop-styling.md +27 -0
- package/src/resources/skills/userinterface-wiki/rules/native-placeholder-styling.md +27 -0
- package/src/resources/skills/userinterface-wiki/rules/native-selection-styling.md +18 -0
- package/src/resources/skills/userinterface-wiki/rules/nested-consistent-timing.md +25 -0
- package/src/resources/skills/userinterface-wiki/rules/nested-propagate-required.md +41 -0
- package/src/resources/skills/userinterface-wiki/rules/none-context-menu-entrance.md +25 -0
- package/src/resources/skills/userinterface-wiki/rules/none-high-frequency.md +29 -0
- package/src/resources/skills/userinterface-wiki/rules/none-keyboard-navigation.md +32 -0
- package/src/resources/skills/userinterface-wiki/rules/param-click-duration.md +21 -0
- package/src/resources/skills/userinterface-wiki/rules/param-filter-frequency-range.md +21 -0
- package/src/resources/skills/userinterface-wiki/rules/param-q-value-range.md +21 -0
- package/src/resources/skills/userinterface-wiki/rules/param-reasonable-gain.md +21 -0
- package/src/resources/skills/userinterface-wiki/rules/physics-active-state.md +23 -0
- package/src/resources/skills/userinterface-wiki/rules/physics-no-excessive-stagger.md +22 -0
- package/src/resources/skills/userinterface-wiki/rules/physics-spring-for-overshoot.md +23 -0
- package/src/resources/skills/userinterface-wiki/rules/physics-subtle-deformation.md +22 -0
- package/src/resources/skills/userinterface-wiki/rules/prefetch-hit-slop.md +27 -0
- package/src/resources/skills/userinterface-wiki/rules/prefetch-keyboard-tab.md +19 -0
- package/src/resources/skills/userinterface-wiki/rules/prefetch-not-everything.md +22 -0
- package/src/resources/skills/userinterface-wiki/rules/prefetch-touch-fallback.md +34 -0
- package/src/resources/skills/userinterface-wiki/rules/prefetch-trajectory-over-hover.md +32 -0
- package/src/resources/skills/userinterface-wiki/rules/prefetch-use-selectively.md +13 -0
- package/src/resources/skills/userinterface-wiki/rules/presence-disable-interactions.md +31 -0
- package/src/resources/skills/userinterface-wiki/rules/presence-hook-in-child.md +31 -0
- package/src/resources/skills/userinterface-wiki/rules/presence-safe-to-remove.md +37 -0
- package/src/resources/skills/userinterface-wiki/rules/pseudo-content-required.md +28 -0
- package/src/resources/skills/userinterface-wiki/rules/pseudo-first-line-styling.md +27 -0
- package/src/resources/skills/userinterface-wiki/rules/pseudo-hit-target-expansion.md +31 -0
- package/src/resources/skills/userinterface-wiki/rules/pseudo-marker-styling.md +28 -0
- package/src/resources/skills/userinterface-wiki/rules/pseudo-over-dom-node.md +32 -0
- package/src/resources/skills/userinterface-wiki/rules/pseudo-position-relative-parent.md +33 -0
- package/src/resources/skills/userinterface-wiki/rules/pseudo-z-index-layering.md +37 -0
- package/src/resources/skills/userinterface-wiki/rules/spring-for-gestures.md +27 -0
- package/src/resources/skills/userinterface-wiki/rules/spring-for-interruptible.md +27 -0
- package/src/resources/skills/userinterface-wiki/rules/spring-params-balanced.md +29 -0
- package/src/resources/skills/userinterface-wiki/rules/spring-preserves-velocity.md +28 -0
- package/src/resources/skills/userinterface-wiki/rules/staging-dim-background.md +22 -0
- package/src/resources/skills/userinterface-wiki/rules/staging-one-focal-point.md +24 -0
- package/src/resources/skills/userinterface-wiki/rules/staging-z-index-hierarchy.md +22 -0
- package/src/resources/skills/userinterface-wiki/rules/timing-consistent.md +24 -0
- package/src/resources/skills/userinterface-wiki/rules/timing-no-entrance-context-menu.md +22 -0
- package/src/resources/skills/userinterface-wiki/rules/timing-under-300ms.md +22 -0
- package/src/resources/skills/userinterface-wiki/rules/transition-name-cleanup.md +28 -0
- package/src/resources/skills/userinterface-wiki/rules/transition-name-required.md +27 -0
- package/src/resources/skills/userinterface-wiki/rules/transition-name-unique.md +24 -0
- package/src/resources/skills/userinterface-wiki/rules/transition-over-js-library.md +32 -0
- package/src/resources/skills/userinterface-wiki/rules/transition-style-pseudo-elements.md +24 -0
- package/src/resources/skills/userinterface-wiki/rules/type-antialiased-on-retina.md +18 -0
- package/src/resources/skills/userinterface-wiki/rules/type-disambiguation-stylistic-set.md +15 -0
- package/src/resources/skills/userinterface-wiki/rules/type-font-display-swap.md +28 -0
- package/src/resources/skills/userinterface-wiki/rules/type-justify-with-hyphens.md +24 -0
- package/src/resources/skills/userinterface-wiki/rules/type-letter-spacing-uppercase.md +28 -0
- package/src/resources/skills/userinterface-wiki/rules/type-no-font-synthesis.md +18 -0
- package/src/resources/skills/userinterface-wiki/rules/type-oldstyle-nums-for-prose.md +21 -0
- package/src/resources/skills/userinterface-wiki/rules/type-opentype-contextual-alternates.md +15 -0
- package/src/resources/skills/userinterface-wiki/rules/type-optical-sizing-auto.md +25 -0
- package/src/resources/skills/userinterface-wiki/rules/type-proper-fractions.md +15 -0
- package/src/resources/skills/userinterface-wiki/rules/type-slashed-zero.md +17 -0
- package/src/resources/skills/userinterface-wiki/rules/type-tabular-nums-for-data.md +21 -0
- package/src/resources/skills/userinterface-wiki/rules/type-text-wrap-balance-headings.md +21 -0
- package/src/resources/skills/userinterface-wiki/rules/type-text-wrap-pretty.md +16 -0
- package/src/resources/skills/userinterface-wiki/rules/type-underline-offset.md +25 -0
- package/src/resources/skills/userinterface-wiki/rules/type-variable-weight-continuous.md +23 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-aesthetic-usability.md +32 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-cognitive-load-reduce.md +49 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-common-region-boundaries.md +50 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-doherty-perceived-speed.md +29 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-doherty-under-400ms.md +30 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-fitts-hit-area.md +32 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-fitts-target-size.md +31 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-goal-gradient-progress.md +33 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-hicks-minimize-choices.md +45 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-jakobs-familiar-patterns.md +37 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-millers-chunking.md +23 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-pareto-prioritize-features.md +36 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-peak-end-finish-strong.md +35 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-postels-accept-messy-input.md +45 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-pragnanz-simplify.md +33 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-progressive-disclosure.md +41 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-proximity-grouping.md +38 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-serial-position.md +31 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-similarity-consistency.md +35 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-teslers-complexity.md +28 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-uniform-connectedness.md +43 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-von-restorff-emphasis.md +29 -0
- package/src/resources/skills/userinterface-wiki/rules/ux-zeigarnik-show-incomplete.md +36 -0
- package/src/resources/skills/userinterface-wiki/rules/visual-animate-shadow-pseudo.md +49 -0
- package/src/resources/skills/userinterface-wiki/rules/visual-border-alpha-colors.md +25 -0
- package/src/resources/skills/userinterface-wiki/rules/visual-button-shadow-anatomy.md +49 -0
- package/src/resources/skills/userinterface-wiki/rules/visual-concentric-radius.md +40 -0
- package/src/resources/skills/userinterface-wiki/rules/visual-consistent-spacing-scale.md +35 -0
- package/src/resources/skills/userinterface-wiki/rules/visual-layered-shadows.md +30 -0
- package/src/resources/skills/userinterface-wiki/rules/visual-no-pure-black-shadow.md +25 -0
- package/src/resources/skills/userinterface-wiki/rules/visual-shadow-direction.md +25 -0
- package/src/resources/skills/userinterface-wiki/rules/visual-shadow-matches-elevation.md +23 -0
- package/src/resources/skills/userinterface-wiki/rules/weight-duration-matches-action.md +29 -0
- package/src/resources/skills/userinterface-wiki/rules/weight-match-action.md +32 -0
- package/src/resources/skills/web-design-guidelines/SKILL.md +39 -0
- package/src/resources/skills/web-quality-audit/SKILL.md +170 -0
- package/src/resources/skills/web-quality-audit/scripts/analyze.sh +91 -0
- package/dist/resources/extensions/gsd/complexity.ts +0 -237
- package/dist/resources/extensions/gsd/github-client.ts +0 -235
- package/dist/resources/extensions/gsd/tests/activity-log-prune.test.ts +0 -297
- package/dist/resources/extensions/gsd/tests/activity-log-save.test.ts +0 -127
- package/dist/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +0 -110
- package/dist/resources/extensions/gsd/tests/auto-draft-pause.test.ts +0 -115
- package/dist/resources/extensions/gsd/tests/complexity-routing.test.ts +0 -294
- package/dist/resources/extensions/gsd/tests/metrics-io.test.ts +0 -176
- package/dist/resources/extensions/gsd/tests/network-error-fallback.test.ts +0 -104
- package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -120
- package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -226
- package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +0 -110
- package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -207
- package/dist/resources/extensions/gsd/tests/preferences-schema-validation.test.ts +0 -183
- package/dist/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +0 -168
- package/dist/resources/extensions/mcporter/index.ts +0 -512
- package/dist/resources/extensions/shared/progress-widget.ts +0 -282
- package/dist/resources/extensions/shared/thinking-widget.ts +0 -107
- package/src/resources/extensions/gsd/complexity.ts +0 -237
- package/src/resources/extensions/gsd/github-client.ts +0 -235
- package/src/resources/extensions/gsd/tests/activity-log-prune.test.ts +0 -297
- package/src/resources/extensions/gsd/tests/activity-log-save.test.ts +0 -127
- package/src/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +0 -110
- package/src/resources/extensions/gsd/tests/auto-draft-pause.test.ts +0 -115
- package/src/resources/extensions/gsd/tests/complexity-routing.test.ts +0 -294
- package/src/resources/extensions/gsd/tests/metrics-io.test.ts +0 -176
- package/src/resources/extensions/gsd/tests/network-error-fallback.test.ts +0 -104
- package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -120
- package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -226
- package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +0 -110
- package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -207
- package/src/resources/extensions/gsd/tests/preferences-schema-validation.test.ts +0 -183
- package/src/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +0 -168
- package/src/resources/extensions/mcporter/index.ts +0 -512
- package/src/resources/extensions/shared/progress-widget.ts +0 -282
- package/src/resources/extensions/shared/thinking-widget.ts +0 -107
|
@@ -22,7 +22,6 @@ import { loadFile, getManifestStatus, resolveAllOverrides, parsePlan, parseSumma
|
|
|
22
22
|
import { loadPrompt } from "./prompt-loader.js";
|
|
23
23
|
import { runVerificationGate, formatFailureContext, captureRuntimeErrors, runDependencyAudit } from "./verification-gate.js";
|
|
24
24
|
import { writeVerificationJSON } from "./verification-evidence.js";
|
|
25
|
-
export { inlinePriorMilestoneSummary } from "./files.js";
|
|
26
25
|
import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
|
|
27
26
|
import {
|
|
28
27
|
gsdRoot, resolveMilestoneFile, resolveSliceFile, resolveSlicePath,
|
|
@@ -33,6 +32,12 @@ import { invalidateAllCaches } from "./cache.js";
|
|
|
33
32
|
import { saveActivityLog, clearActivityLogState } from "./activity-log.js";
|
|
34
33
|
import { synthesizeCrashRecovery, getDeepDiagnostic } from "./session-forensics.js";
|
|
35
34
|
import { writeLock, clearLock, readCrashLock, formatCrashInfo, isLockProcessAlive } from "./crash-recovery.js";
|
|
35
|
+
import {
|
|
36
|
+
acquireSessionLock,
|
|
37
|
+
validateSessionLock,
|
|
38
|
+
releaseSessionLock,
|
|
39
|
+
updateSessionLock,
|
|
40
|
+
} from "./session-lock.js";
|
|
36
41
|
import {
|
|
37
42
|
clearUnitRuntimeRecord,
|
|
38
43
|
inspectExecuteTaskDurability,
|
|
@@ -70,7 +75,6 @@ import {
|
|
|
70
75
|
checkResourcesStale,
|
|
71
76
|
escapeStaleWorktree,
|
|
72
77
|
} from "./auto-worktree-sync.js";
|
|
73
|
-
// complexity-classifier + model-router imports moved to auto-model-selection.ts
|
|
74
78
|
import { initRoutingHistory, resetRoutingHistory, recordOutcome } from "./routing-history.js";
|
|
75
79
|
import {
|
|
76
80
|
checkPostUnitHooks,
|
|
@@ -83,7 +87,6 @@ import {
|
|
|
83
87
|
restoreHookState,
|
|
84
88
|
clearPersistedHookState,
|
|
85
89
|
} from "./post-unit-hooks.js";
|
|
86
|
-
// observability-validator imports moved to auto-observability.ts
|
|
87
90
|
import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
|
|
88
91
|
import { runGSDDoctor, rebuildState, summarizeDoctorIssues } from "./doctor.js";
|
|
89
92
|
import {
|
|
@@ -101,10 +104,12 @@ import {
|
|
|
101
104
|
getProjectTotals, formatCost, formatTokenCount,
|
|
102
105
|
} from "./metrics.js";
|
|
103
106
|
import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.js";
|
|
107
|
+
import { GSDError, GSD_ARTIFACT_MISSING } from "./errors.js";
|
|
104
108
|
import { join } from "node:path";
|
|
105
109
|
import { sep as pathSep } from "node:path";
|
|
106
|
-
import { readdirSync, readFileSync, existsSync, mkdirSync, writeFileSync,
|
|
107
|
-
import {
|
|
110
|
+
import { readdirSync, readFileSync, existsSync, mkdirSync, writeFileSync, unlinkSync, statSync } from "node:fs";
|
|
111
|
+
import { atomicWriteSync } from "./atomic-write.js";
|
|
112
|
+
import { nativeIsRepo, nativeInit, nativeAddAll, nativeCommit } from "./native-git-bridge.js";
|
|
108
113
|
import {
|
|
109
114
|
autoCommitCurrentBranch,
|
|
110
115
|
captureIntegrationBranch,
|
|
@@ -130,7 +135,7 @@ import {
|
|
|
130
135
|
} from "./auto-worktree.js";
|
|
131
136
|
import { pruneQueueOrder } from "./queue-order.js";
|
|
132
137
|
import { consumeSignal } from "./session-status-io.js";
|
|
133
|
-
import { showNextAction } from "../shared/
|
|
138
|
+
import { showNextAction } from "../shared/mod.js";
|
|
134
139
|
import { debugLog, debugTime, debugCount, debugPeak, enableDebug, isDebugEnabled, writeDebugSummary, getDebugLogPath } from "./debug-logger.js";
|
|
135
140
|
import {
|
|
136
141
|
resolveExpectedArtifactPath,
|
|
@@ -147,7 +152,6 @@ import {
|
|
|
147
152
|
reconcileMergeState,
|
|
148
153
|
} from "./auto-recovery.js";
|
|
149
154
|
import { resolveDispatch, resetRewriteCircuitBreaker } from "./auto-dispatch.js";
|
|
150
|
-
// Prompt builders moved to auto-direct-dispatch.ts (only used there now)
|
|
151
155
|
import {
|
|
152
156
|
type AutoDashboardData,
|
|
153
157
|
updateProgressWidget as _updateProgressWidget,
|
|
@@ -168,6 +172,14 @@ import {
|
|
|
168
172
|
import { isDbAvailable } from "./gsd-db.js";
|
|
169
173
|
import { hasPendingCaptures, loadPendingCaptures, countPendingCaptures } from "./captures.js";
|
|
170
174
|
|
|
175
|
+
// ── Extracted modules ──────────────────────────────────────────────────────
|
|
176
|
+
import { startUnitSupervision, type SupervisionContext } from "./auto-timers.js";
|
|
177
|
+
import { checkIdempotency, type IdempotencyContext } from "./auto-idempotency.js";
|
|
178
|
+
import { checkStuckAndRecover, type StuckContext } from "./auto-stuck-detection.js";
|
|
179
|
+
import { runPostUnitVerification, type VerificationContext } from "./auto-verification.js";
|
|
180
|
+
import { postUnitPreVerification, postUnitPostVerification, type PostUnitContext } from "./auto-post-unit.js";
|
|
181
|
+
import { bootstrapAutoSession, type BootstrapDeps } from "./auto-start.js";
|
|
182
|
+
|
|
171
183
|
// Worktree sync, resource staleness, stale worktree escape → auto-worktree-sync.ts
|
|
172
184
|
|
|
173
185
|
// ─── Session State ─────────────────────────────────────────────────────────
|
|
@@ -176,14 +188,22 @@ import {
|
|
|
176
188
|
AutoSession,
|
|
177
189
|
MAX_UNIT_DISPATCHES, STUB_RECOVERY_THRESHOLD, MAX_LIFETIME_DISPATCHES,
|
|
178
190
|
MAX_CONSECUTIVE_SKIPS, DISPATCH_GAP_TIMEOUT_MS, MAX_SKIP_DEPTH,
|
|
191
|
+
NEW_SESSION_TIMEOUT_MS, DISPATCH_HANG_TIMEOUT_MS,
|
|
179
192
|
} from "./auto/session.js";
|
|
180
193
|
import type { CompletedUnit, CurrentUnit, UnitRouting, StartModel, PendingVerificationRetry } from "./auto/session.js";
|
|
181
|
-
export {
|
|
182
|
-
MAX_UNIT_DISPATCHES, STUB_RECOVERY_THRESHOLD, MAX_LIFETIME_DISPATCHES,
|
|
183
|
-
MAX_CONSECUTIVE_SKIPS, DISPATCH_GAP_TIMEOUT_MS, MAX_SKIP_DEPTH,
|
|
184
|
-
} from "./auto/session.js";
|
|
185
|
-
export type { CompletedUnit, CurrentUnit, UnitRouting, StartModel } from "./auto/session.js";
|
|
186
194
|
|
|
195
|
+
// ── ENCAPSULATION INVARIANT ─────────────────────────────────────────────────
|
|
196
|
+
// ALL mutable auto-mode state lives in the AutoSession class (auto/session.ts).
|
|
197
|
+
// This file must NOT declare module-level `let` or `var` variables for state.
|
|
198
|
+
// The single `s` instance below is the only mutable module-level binding.
|
|
199
|
+
//
|
|
200
|
+
// When adding features or fixing bugs:
|
|
201
|
+
// - New mutable state → add a property to AutoSession, not a module-level variable
|
|
202
|
+
// - New constants → module-level `const` is fine (immutable)
|
|
203
|
+
// - New state that needs reset on stopAuto → add to AutoSession.reset()
|
|
204
|
+
//
|
|
205
|
+
// Tests in auto-session-encapsulation.test.ts enforce this invariant.
|
|
206
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
187
207
|
const s = new AutoSession();
|
|
188
208
|
|
|
189
209
|
/** Throttle STATE.md rebuilds — at most once per 30 seconds */
|
|
@@ -241,8 +261,6 @@ export function shouldUseWorktreeIsolation(): boolean {
|
|
|
241
261
|
* Maps toolCallId → start timestamp (ms) so the idle watchdog can detect tools that have been
|
|
242
262
|
* running suspiciously long (e.g., a Bash command hung because `&` kept stdout open).
|
|
243
263
|
*/
|
|
244
|
-
// Re-export budget utilities for external consumers
|
|
245
|
-
export { getBudgetAlertLevel, getNewBudgetAlertLevel, getBudgetEnforcementAction } from "./auto-budget.js";
|
|
246
264
|
|
|
247
265
|
/** Wrapper: register SIGTERM handler and store reference. */
|
|
248
266
|
function registerSigtermHandler(currentBasePath: string): void {
|
|
@@ -255,8 +273,6 @@ function deregisterSigtermHandler(): void {
|
|
|
255
273
|
s.sigtermHandler = null;
|
|
256
274
|
}
|
|
257
275
|
|
|
258
|
-
export { type AutoDashboardData } from "./auto-dashboard.js";
|
|
259
|
-
|
|
260
276
|
export function getAutoDashboardData(): AutoDashboardData {
|
|
261
277
|
const ledger = getLedger();
|
|
262
278
|
const totals = ledger ? getProjectTotals(ledger.units) : null;
|
|
@@ -291,6 +307,15 @@ export function isAutoPaused(): boolean {
|
|
|
291
307
|
return s.paused;
|
|
292
308
|
}
|
|
293
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Return the model captured at auto-mode start for this session.
|
|
312
|
+
* Used by error-recovery to fall back to the session's own model
|
|
313
|
+
* instead of reading (potentially stale) preferences from disk (#1065).
|
|
314
|
+
*/
|
|
315
|
+
export function getAutoModeStartModel(): { provider: string; id: string } | null {
|
|
316
|
+
return s.autoModeStartModel;
|
|
317
|
+
}
|
|
318
|
+
|
|
294
319
|
// Tool tracking — delegates to auto-tool-tracking.ts
|
|
295
320
|
export function markToolStart(toolCallId: string): void {
|
|
296
321
|
_markToolStart(toolCallId, s.active);
|
|
@@ -396,8 +421,6 @@ function startDispatchGapWatchdog(ctx: ExtensionContext, pi: ExtensionAPI): void
|
|
|
396
421
|
s.dispatchGapHandle = null;
|
|
397
422
|
if (!s.active || !s.cmdCtx) return;
|
|
398
423
|
|
|
399
|
-
// Auto-mode is active but no unit was dispatched — the state machine stalled.
|
|
400
|
-
// Re-derive state and attempt a fresh dispatch.
|
|
401
424
|
if (s.verbose) {
|
|
402
425
|
ctx.ui.notify(
|
|
403
426
|
"Dispatch gap detected — re-evaluating state.",
|
|
@@ -413,9 +436,6 @@ function startDispatchGapWatchdog(ctx: ExtensionContext, pi: ExtensionAPI): void
|
|
|
413
436
|
return;
|
|
414
437
|
}
|
|
415
438
|
|
|
416
|
-
// If dispatchNextUnit returned normally but still didn't dispatch a unit
|
|
417
|
-
// (no sendMessage called → no timeout set), auto-mode is permanently
|
|
418
|
-
// stalled. Stop cleanly instead of leaving it s.active but idle (#537).
|
|
419
439
|
if (s.active && !s.unitTimeoutHandle && !s.wrapupWarningHandle) {
|
|
420
440
|
await stopAuto(ctx, pi, "Stalled — no dispatchable unit after retry");
|
|
421
441
|
}
|
|
@@ -426,7 +446,10 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
|
|
|
426
446
|
if (!s.active && !s.paused) return;
|
|
427
447
|
const reasonSuffix = reason ? ` — ${reason}` : "";
|
|
428
448
|
clearUnitTimeout();
|
|
429
|
-
if (lockBase())
|
|
449
|
+
if (lockBase()) {
|
|
450
|
+
releaseSessionLock(lockBase());
|
|
451
|
+
clearLock(lockBase());
|
|
452
|
+
}
|
|
430
453
|
clearSkillSnapshot();
|
|
431
454
|
resetSkillTelemetry();
|
|
432
455
|
s.dispatching = false;
|
|
@@ -436,13 +459,9 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
|
|
|
436
459
|
deregisterSigtermHandler();
|
|
437
460
|
|
|
438
461
|
// ── Auto-worktree: exit worktree and reset s.basePath on stop ──
|
|
439
|
-
// Preserve the milestone branch so the next /gsd auto can re-enter
|
|
440
|
-
// where it left off. The branch is only deleted during milestone
|
|
441
|
-
// completion (mergeMilestoneToMain) after the work has been squash-merged.
|
|
442
462
|
if (s.currentMilestoneId && isInAutoWorktree(s.basePath)) {
|
|
443
463
|
try {
|
|
444
|
-
|
|
445
|
-
try { autoCommitCurrentBranch(s.basePath, "stop", s.currentMilestoneId); } catch { /* non-fatal */ }
|
|
464
|
+
try { autoCommitCurrentBranch(s.basePath, "stop", s.currentMilestoneId); } catch (e) { debugLog("stop-auto-commit-failed", { error: e instanceof Error ? e.message : String(e) }); }
|
|
446
465
|
teardownAutoWorktree(s.originalBasePath, s.currentMilestoneId, { preserveBranch: true });
|
|
447
466
|
s.basePath = s.originalBasePath;
|
|
448
467
|
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
@@ -460,13 +479,9 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
|
|
|
460
479
|
try {
|
|
461
480
|
const { closeDatabase } = await import("./gsd-db.js");
|
|
462
481
|
closeDatabase();
|
|
463
|
-
} catch {
|
|
482
|
+
} catch (e) { debugLog("db-close-failed", { error: e instanceof Error ? e.message : String(e) }); }
|
|
464
483
|
}
|
|
465
484
|
|
|
466
|
-
// Always restore cwd to project root on stop (#608).
|
|
467
|
-
// Even if isInAutoWorktree returned false (e.g., module state was already
|
|
468
|
-
// cleared by mergeMilestoneToMain), the process cwd may still be inside
|
|
469
|
-
// the worktree directory. Force it back to s.originalBasePath.
|
|
470
485
|
if (s.originalBasePath) {
|
|
471
486
|
s.basePath = s.originalBasePath;
|
|
472
487
|
try { process.chdir(s.basePath); } catch { /* best-effort */ }
|
|
@@ -483,12 +498,10 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
|
|
|
483
498
|
ctx?.ui.notify(`Auto-mode stopped${reasonSuffix}.`, "info");
|
|
484
499
|
}
|
|
485
500
|
|
|
486
|
-
// Sync disk state so next resume starts from accurate state
|
|
487
501
|
if (s.basePath) {
|
|
488
|
-
try { await rebuildState(s.basePath); } catch {
|
|
502
|
+
try { await rebuildState(s.basePath); } catch (e) { debugLog("stop-rebuild-state-failed", { error: e instanceof Error ? e.message : String(e) }); }
|
|
489
503
|
}
|
|
490
504
|
|
|
491
|
-
// Write debug summary before resetting state
|
|
492
505
|
if (isDebugEnabled()) {
|
|
493
506
|
const logPath = writeDebugSummary();
|
|
494
507
|
if (logPath) {
|
|
@@ -529,7 +542,6 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
|
|
|
529
542
|
ctx?.ui.setWidget("gsd-progress", undefined);
|
|
530
543
|
ctx?.ui.setFooter(undefined);
|
|
531
544
|
|
|
532
|
-
// Restore the user's original model
|
|
533
545
|
if (pi && ctx && s.originalModelId && s.originalModelProvider) {
|
|
534
546
|
const original = ctx.modelRegistry.find(s.originalModelProvider, s.originalModelId);
|
|
535
547
|
if (original) await pi.setModel(original);
|
|
@@ -549,22 +561,19 @@ export async function pauseAuto(ctx?: ExtensionContext, _pi?: ExtensionAPI): Pro
|
|
|
549
561
|
if (!s.active) return;
|
|
550
562
|
clearUnitTimeout();
|
|
551
563
|
|
|
552
|
-
// Capture the current session file before clearing state — used for
|
|
553
|
-
// recovery briefing on resume so the next agent knows what already happened.
|
|
554
564
|
s.pausedSessionFile = ctx?.sessionManager?.getSessionFile() ?? null;
|
|
555
565
|
|
|
556
|
-
if (lockBase())
|
|
566
|
+
if (lockBase()) {
|
|
567
|
+
releaseSessionLock(lockBase());
|
|
568
|
+
clearLock(lockBase());
|
|
569
|
+
}
|
|
557
570
|
|
|
558
|
-
// Remove SIGTERM handler registered at auto-mode start
|
|
559
571
|
deregisterSigtermHandler();
|
|
560
572
|
|
|
561
573
|
s.active = false;
|
|
562
574
|
s.paused = true;
|
|
563
575
|
s.pendingVerificationRetry = null;
|
|
564
576
|
s.verificationRetryCount.clear();
|
|
565
|
-
// Preserve: s.unitDispatchCount, s.currentUnit, s.basePath, s.verbose, s.cmdCtx,
|
|
566
|
-
// s.completedUnits, s.autoStartTime, s.currentMilestoneId, s.originalModelId
|
|
567
|
-
// — all needed for resume and dashboard display
|
|
568
577
|
ctx?.ui.setStatus("gsd-auto", "paused");
|
|
569
578
|
ctx?.ui.setWidget("gsd-progress", undefined);
|
|
570
579
|
ctx?.ui.setFooter(undefined);
|
|
@@ -586,31 +595,33 @@ export async function startAuto(
|
|
|
586
595
|
const requestedStepMode = options?.step ?? false;
|
|
587
596
|
|
|
588
597
|
// Escape stale worktree cwd from a previous milestone (#608).
|
|
589
|
-
// After milestone merge + worktree removal, the process cwd may still point
|
|
590
|
-
// inside .gsd/worktrees/<MID>/ — detect and chdir back to project root.
|
|
591
598
|
base = escapeStaleWorktree(base);
|
|
592
599
|
|
|
593
600
|
// If resuming from paused state, just re-activate and dispatch next unit.
|
|
594
|
-
// The conversation is still intact — no need to reinitialize everything.
|
|
595
601
|
if (s.paused) {
|
|
602
|
+
// Re-acquire session lock before resuming
|
|
603
|
+
const resumeLock = acquireSessionLock(base);
|
|
604
|
+
if (!resumeLock.acquired) {
|
|
605
|
+
ctx.ui.notify(
|
|
606
|
+
`Cannot resume: ${resumeLock.reason}`,
|
|
607
|
+
"error",
|
|
608
|
+
);
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
|
|
596
612
|
s.paused = false;
|
|
597
613
|
s.active = true;
|
|
598
614
|
s.verbose = verboseMode;
|
|
599
|
-
// Allow switching between step/auto on resume
|
|
600
615
|
s.stepMode = requestedStepMode;
|
|
601
616
|
s.cmdCtx = ctx;
|
|
602
617
|
s.basePath = base;
|
|
603
618
|
s.unitDispatchCount.clear();
|
|
604
619
|
s.unitLifetimeDispatches.clear();
|
|
605
620
|
s.unitConsecutiveSkips.clear();
|
|
606
|
-
// Re-initialize metrics in case ledger was lost during pause
|
|
607
621
|
if (!getLedger()) initMetrics(base);
|
|
608
|
-
// Ensure milestone ID is set on git service for integration branch resolution
|
|
609
622
|
if (s.currentMilestoneId) setActiveMilestoneId(base, s.currentMilestoneId);
|
|
610
623
|
|
|
611
|
-
// ── Auto-worktree: re-enter worktree on resume
|
|
612
|
-
// Skip if already inside a worktree (manual /worktree) to prevent nesting.
|
|
613
|
-
// Skip entirely in branch or none isolation mode (#531).
|
|
624
|
+
// ── Auto-worktree: re-enter worktree on resume ──
|
|
614
625
|
if (s.currentMilestoneId && shouldUseWorktreeIsolation() && s.originalBasePath && !isInAutoWorktree(s.basePath) && !detectWorktreeName(s.basePath) && !detectWorktreeName(s.originalBasePath)) {
|
|
615
626
|
try {
|
|
616
627
|
const existingWtPath = getAutoWorktreePath(s.originalBasePath, s.currentMilestoneId);
|
|
@@ -620,7 +631,6 @@ export async function startAuto(
|
|
|
620
631
|
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
621
632
|
ctx.ui.notify(`Re-entered auto-worktree at ${wtPath}`, "info");
|
|
622
633
|
} else {
|
|
623
|
-
// Worktree was deleted while paused — recreate it.
|
|
624
634
|
const wtPath = createAutoWorktree(s.originalBasePath, s.currentMilestoneId);
|
|
625
635
|
s.basePath = wtPath;
|
|
626
636
|
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
@@ -634,28 +644,22 @@ export async function startAuto(
|
|
|
634
644
|
}
|
|
635
645
|
}
|
|
636
646
|
|
|
637
|
-
// Re-register SIGTERM handler for the resumed session (use original base for lock)
|
|
638
647
|
registerSigtermHandler(lockBase());
|
|
639
648
|
|
|
640
649
|
ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
|
|
641
650
|
ctx.ui.setFooter(hideFooter);
|
|
642
651
|
ctx.ui.notify(s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "info");
|
|
643
|
-
// Restore hook state from disk in case session was interrupted
|
|
644
652
|
restoreHookState(s.basePath);
|
|
645
|
-
|
|
646
|
-
try { await rebuildState(s.basePath); } catch { /* non-fatal */ }
|
|
653
|
+
try { await rebuildState(s.basePath); } catch (e) { debugLog("resume-rebuild-state-failed", { error: e instanceof Error ? e.message : String(e) }); }
|
|
647
654
|
try {
|
|
648
655
|
const report = await runGSDDoctor(s.basePath, { fix: true });
|
|
649
656
|
if (report.fixesApplied.length > 0) {
|
|
650
657
|
ctx.ui.notify(`Resume: applied ${report.fixesApplied.length} fix(es) to state.`, "info");
|
|
651
658
|
}
|
|
652
|
-
} catch {
|
|
653
|
-
// Self-heal: clear stale runtime records where artifacts already exist
|
|
659
|
+
} catch (e) { debugLog("resume-doctor-failed", { error: e instanceof Error ? e.message : String(e) }); }
|
|
654
660
|
await selfHealRuntimeRecords(s.basePath, ctx, s.completedKeySet);
|
|
655
661
|
invalidateAllCaches();
|
|
656
662
|
|
|
657
|
-
// Synthesize recovery briefing from the paused session so the next agent
|
|
658
|
-
// knows what already happened (reuses crash recovery infrastructure).
|
|
659
663
|
if (s.pausedSessionFile) {
|
|
660
664
|
const activityDir = join(gsdRoot(s.basePath), "activity");
|
|
661
665
|
const recovery = synthesizeCrashRecovery(
|
|
@@ -674,462 +678,54 @@ export async function startAuto(
|
|
|
674
678
|
s.pausedSessionFile = null;
|
|
675
679
|
}
|
|
676
680
|
|
|
677
|
-
//
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
await dispatchNextUnit(ctx, pi);
|
|
681
|
-
return;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
// Ensure git repo exists — GSD needs it for commits and state tracking
|
|
685
|
-
if (!nativeIsRepo(base)) {
|
|
686
|
-
const mainBranch = loadEffectiveGSDPreferences()?.preferences?.git?.main_branch || "main";
|
|
687
|
-
nativeInit(base, mainBranch);
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
// Ensure .gitignore has baseline patterns
|
|
691
|
-
const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
692
|
-
const commitDocs = gitPrefs?.commit_docs;
|
|
693
|
-
const manageGitignore = gitPrefs?.manage_gitignore;
|
|
694
|
-
ensureGitignore(base, { commitDocs, manageGitignore });
|
|
695
|
-
if (manageGitignore !== false) untrackRuntimeFiles(base);
|
|
696
|
-
|
|
697
|
-
// Bootstrap .gsd/ if it doesn't exist
|
|
698
|
-
const gsdDir = join(base, ".gsd");
|
|
699
|
-
if (!existsSync(gsdDir)) {
|
|
700
|
-
mkdirSync(join(gsdDir, "milestones"), { recursive: true });
|
|
701
|
-
// Only commit .gsd/ init when commit_docs is not explicitly false
|
|
702
|
-
if (commitDocs !== false) {
|
|
681
|
+
// If resuming from a secrets pause, re-collect before dispatching (#1146)
|
|
682
|
+
if (s.pausedForSecrets && s.currentMilestoneId) {
|
|
703
683
|
try {
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
// Stale lock from a dead process — validate before synthesizing recovery context.
|
|
726
|
-
// If the recovered unit belongs to a fully-completed milestone (SUMMARY exists),
|
|
727
|
-
// discard recovery context to prevent phantom skip loops (#790).
|
|
728
|
-
const recoveredMid = crashLock.unitId.split("/")[0];
|
|
729
|
-
const milestoneAlreadyComplete = recoveredMid
|
|
730
|
-
? !!resolveMilestoneFile(base, recoveredMid, "SUMMARY")
|
|
731
|
-
: false;
|
|
732
|
-
|
|
733
|
-
if (milestoneAlreadyComplete) {
|
|
734
|
-
ctx.ui.notify(
|
|
735
|
-
`Crash recovery: discarding stale context for ${crashLock.unitId} — milestone ${recoveredMid} is already complete.`,
|
|
736
|
-
"info",
|
|
737
|
-
);
|
|
738
|
-
} else {
|
|
739
|
-
const activityDir = join(gsdRoot(base), "activity");
|
|
740
|
-
const recovery = synthesizeCrashRecovery(
|
|
741
|
-
base, crashLock.unitType, crashLock.unitId,
|
|
742
|
-
crashLock.sessionFile, activityDir,
|
|
743
|
-
);
|
|
744
|
-
if (recovery && recovery.trace.toolCallCount > 0) {
|
|
745
|
-
s.pendingCrashRecovery = recovery.prompt;
|
|
746
|
-
ctx.ui.notify(
|
|
747
|
-
`${formatCrashInfo(crashLock)}\nRecovered ${recovery.trace.toolCallCount} tool calls from crashed session. Resuming with full context.`,
|
|
748
|
-
"warning",
|
|
749
|
-
);
|
|
750
|
-
} else {
|
|
684
|
+
const manifestStatus = await getManifestStatus(s.basePath, s.currentMilestoneId);
|
|
685
|
+
if (manifestStatus && manifestStatus.pending.length > 0) {
|
|
686
|
+
const result = await collectSecretsFromManifest(s.basePath, s.currentMilestoneId, ctx);
|
|
687
|
+
if (result && result.applied.length > 0) {
|
|
688
|
+
ctx.ui.notify(
|
|
689
|
+
`Secrets collected: ${result.applied.length} applied, ${result.skipped.length} skipped, ${result.existingSkipped.length} already set.`,
|
|
690
|
+
"info",
|
|
691
|
+
);
|
|
692
|
+
} else if (result && result.applied.length === 0 && result.skipped.length > 0) {
|
|
693
|
+
// All keys were skipped — still pending, re-pause
|
|
694
|
+
s.paused = true;
|
|
695
|
+
s.active = false;
|
|
696
|
+
ctx.ui.notify(
|
|
697
|
+
`All env variables were skipped. Auto-mode remains paused.\nCollect them with /gsd secrets, then resume with /gsd auto.`,
|
|
698
|
+
"warning",
|
|
699
|
+
);
|
|
700
|
+
ctx.ui.setStatus("gsd-auto", "paused");
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
} catch (err) {
|
|
751
705
|
ctx.ui.notify(
|
|
752
|
-
|
|
706
|
+
`Secrets check error: ${err instanceof Error ? err.message : String(err)}. Continuing without secrets.`,
|
|
753
707
|
"warning",
|
|
754
708
|
);
|
|
755
709
|
}
|
|
710
|
+
s.pausedForSecrets = false;
|
|
756
711
|
}
|
|
757
|
-
clearLock(base);
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
// ── Debug mode: env-var activation ──────────────────────────────────────
|
|
761
|
-
if (!isDebugEnabled() && process.env.GSD_DEBUG === "1") {
|
|
762
|
-
enableDebug(base);
|
|
763
|
-
}
|
|
764
|
-
if (isDebugEnabled()) {
|
|
765
|
-
const { isNativeParserAvailable } = await import("./native-parser-bridge.js");
|
|
766
|
-
debugLog("debug-start", {
|
|
767
|
-
platform: process.platform,
|
|
768
|
-
arch: process.arch,
|
|
769
|
-
node: process.version,
|
|
770
|
-
model: ctx.model?.id ?? "unknown",
|
|
771
|
-
provider: ctx.model?.provider ?? "unknown",
|
|
772
|
-
nativeParser: isNativeParserAvailable(),
|
|
773
|
-
cwd: base,
|
|
774
|
-
});
|
|
775
|
-
ctx.ui.notify(`Debug logging enabled → ${getDebugLogPath()}`, "info");
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
// Invalidate all caches before initial state derivation to ensure we read
|
|
779
|
-
// fresh disk state. Without this, a stale cache from a prior session (e.g.
|
|
780
|
-
// after a discussion that wrote new artifacts) may cause deriveState to
|
|
781
|
-
// return pre-planning when the roadmap already exists (#800).
|
|
782
|
-
invalidateAllCaches();
|
|
783
712
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
// previously completed milestones can cause deriveState to resume the wrong
|
|
787
|
-
// milestone. If a milestone has a SUMMARY file, its unit files are stale.
|
|
788
|
-
try {
|
|
789
|
-
const runtimeUnitsDir = join(gsdRoot(base), "runtime", "units");
|
|
790
|
-
if (existsSync(runtimeUnitsDir)) {
|
|
791
|
-
for (const file of readdirSync(runtimeUnitsDir)) {
|
|
792
|
-
if (!file.endsWith(".json")) continue;
|
|
793
|
-
const midMatch = file.match(/(M\d+(?:-[a-z0-9]{6})?)/);
|
|
794
|
-
if (!midMatch) continue;
|
|
795
|
-
const mid = midMatch[1];
|
|
796
|
-
if (resolveMilestoneFile(base, mid, "SUMMARY")) {
|
|
797
|
-
try { unlinkSync(join(runtimeUnitsDir, file)); } catch { /* non-fatal */ }
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
} catch { /* non-fatal — don't block startup */ }
|
|
802
|
-
|
|
803
|
-
let state = await deriveState(base);
|
|
804
|
-
|
|
805
|
-
// ── Stale worktree state recovery (#654) ─────────────────────────────────
|
|
806
|
-
// When auto-mode was previously stopped and restarted, the project root's
|
|
807
|
-
// .gsd/ directory may have stale metadata (completed units showing as
|
|
808
|
-
// incomplete). If an auto-worktree exists for the active milestone, it has
|
|
809
|
-
// the current state — re-derive from there to avoid re-dispatching
|
|
810
|
-
// finished work.
|
|
811
|
-
if (
|
|
812
|
-
state.activeMilestone &&
|
|
813
|
-
shouldUseWorktreeIsolation() &&
|
|
814
|
-
!detectWorktreeName(base)
|
|
815
|
-
) {
|
|
816
|
-
const wtPath = getAutoWorktreePath(base, state.activeMilestone.id);
|
|
817
|
-
if (wtPath) {
|
|
818
|
-
state = await deriveState(wtPath);
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
// ── Milestone branch recovery (#601) ─────────────────────────────────────
|
|
823
|
-
// When auto-mode was previously stopped, the milestone branch is preserved
|
|
824
|
-
// but the worktree is removed. The project root (integration branch) may
|
|
825
|
-
// not have the roadmap/artifacts — they live on the milestone branch.
|
|
826
|
-
// If state looks like pre-planning but a milestone branch exists with prior
|
|
827
|
-
// work, skip the early-return checks and let worktree setup + dispatch
|
|
828
|
-
// handle it correctly from the branch's state.
|
|
829
|
-
let hasSurvivorBranch = false;
|
|
830
|
-
if (
|
|
831
|
-
state.activeMilestone &&
|
|
832
|
-
(state.phase === "pre-planning" || state.phase === "needs-discussion") &&
|
|
833
|
-
shouldUseWorktreeIsolation() &&
|
|
834
|
-
!detectWorktreeName(base) &&
|
|
835
|
-
!base.includes(`${pathSep}.gsd${pathSep}worktrees${pathSep}`)
|
|
836
|
-
) {
|
|
837
|
-
const milestoneBranch = `milestone/${state.activeMilestone.id}`;
|
|
838
|
-
const { nativeBranchExists } = await import("./native-git-bridge.js");
|
|
839
|
-
hasSurvivorBranch = nativeBranchExists(base, milestoneBranch);
|
|
840
|
-
if (hasSurvivorBranch) {
|
|
841
|
-
ctx.ui.notify(
|
|
842
|
-
`Found prior session branch ${milestoneBranch}. Resuming.`,
|
|
843
|
-
"info",
|
|
844
|
-
);
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
if (!hasSurvivorBranch) {
|
|
849
|
-
// No active work at all — start a new milestone via the discuss flow.
|
|
850
|
-
// After discussion completes, checkAutoStartAfterDiscuss() (fired from
|
|
851
|
-
// agent_end) will detect the new CONTEXT.md and restart auto mode.
|
|
852
|
-
// If the LLM didn't follow the discussion protocol (e.g. started editing
|
|
853
|
-
// files directly for a simple task), we re-derive state and either proceed
|
|
854
|
-
// with what was created or notify the user clearly (#609).
|
|
855
|
-
if (!state.activeMilestone || state.phase === "complete") {
|
|
856
|
-
const { showSmartEntry } = await import("./guided-flow.js");
|
|
857
|
-
await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
|
|
858
|
-
|
|
859
|
-
// Re-derive state after discussion — the LLM may have created artifacts
|
|
860
|
-
// even if it didn't follow the full protocol.
|
|
861
|
-
invalidateAllCaches();
|
|
862
|
-
const postState = await deriveState(base);
|
|
863
|
-
if (postState.activeMilestone && postState.phase !== "complete" && postState.phase !== "pre-planning") {
|
|
864
|
-
state = postState;
|
|
865
|
-
} else if (postState.activeMilestone && postState.phase === "pre-planning") {
|
|
866
|
-
const contextFile = resolveMilestoneFile(base, postState.activeMilestone.id, "CONTEXT");
|
|
867
|
-
const hasContext = !!(contextFile && await loadFile(contextFile));
|
|
868
|
-
if (hasContext) {
|
|
869
|
-
state = postState;
|
|
870
|
-
} else {
|
|
871
|
-
ctx.ui.notify(
|
|
872
|
-
"Discussion completed but no milestone context was written. Run /gsd to try the discussion again, or /gsd auto after creating the milestone manually.",
|
|
873
|
-
"warning",
|
|
874
|
-
);
|
|
875
|
-
return;
|
|
876
|
-
}
|
|
877
|
-
} else {
|
|
878
|
-
return;
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
// Active milestone exists but has no roadmap — check if context exists.
|
|
883
|
-
// If context was pre-written (multi-milestone planning), auto-mode can
|
|
884
|
-
// research and plan it. If no context either, need user discussion.
|
|
885
|
-
if (state.phase === "pre-planning") {
|
|
886
|
-
const mid = state.activeMilestone!.id;
|
|
887
|
-
const contextFile = resolveMilestoneFile(base, mid, "CONTEXT");
|
|
888
|
-
const hasContext = !!(contextFile && await loadFile(contextFile));
|
|
889
|
-
if (!hasContext) {
|
|
890
|
-
const { showSmartEntry } = await import("./guided-flow.js");
|
|
891
|
-
await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
|
|
892
|
-
|
|
893
|
-
// Same re-derive pattern as above
|
|
894
|
-
invalidateAllCaches();
|
|
895
|
-
const postState = await deriveState(base);
|
|
896
|
-
if (postState.activeMilestone && postState.phase !== "pre-planning") {
|
|
897
|
-
state = postState;
|
|
898
|
-
} else {
|
|
899
|
-
ctx.ui.notify(
|
|
900
|
-
"Discussion completed but milestone context is still missing. Run /gsd to try again.",
|
|
901
|
-
"warning",
|
|
902
|
-
);
|
|
903
|
-
return;
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
// Has context, no roadmap — auto-mode will research + plan it
|
|
907
|
-
}
|
|
908
|
-
}
|
|
713
|
+
updateSessionLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown", s.completedUnits.length);
|
|
714
|
+
writeLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown", s.completedUnits.length);
|
|
909
715
|
|
|
910
|
-
|
|
911
|
-
// hasSurvivorBranch is true (which requires activeMilestone) or
|
|
912
|
-
// the !activeMilestone early-return above would have fired.
|
|
913
|
-
if (!state.activeMilestone) {
|
|
914
|
-
// Unreachable — satisfies TypeScript's null check
|
|
915
|
-
const { showSmartEntry } = await import("./guided-flow.js");
|
|
916
|
-
await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
|
|
716
|
+
await dispatchNextUnit(ctx, pi);
|
|
917
717
|
return;
|
|
918
718
|
}
|
|
919
719
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
s.unitDispatchCount.clear();
|
|
926
|
-
s.unitRecoveryCount.clear();
|
|
927
|
-
s.unitConsecutiveSkips.clear();
|
|
928
|
-
s.lastBudgetAlertLevel = 0;
|
|
929
|
-
s.unitLifetimeDispatches.clear();
|
|
930
|
-
s.completedKeySet.clear();
|
|
931
|
-
loadPersistedKeys(base, s.completedKeySet);
|
|
932
|
-
resetHookState();
|
|
933
|
-
restoreHookState(base);
|
|
934
|
-
resetProactiveHealing();
|
|
935
|
-
s.autoStartTime = Date.now();
|
|
936
|
-
s.resourceVersionOnStart = readResourceVersion();
|
|
937
|
-
s.completedUnits = [];
|
|
938
|
-
s.pendingQuickTasks = [];
|
|
939
|
-
s.currentUnit = null;
|
|
940
|
-
s.currentMilestoneId = state.activeMilestone?.id ?? null;
|
|
941
|
-
s.originalModelId = ctx.model?.id ?? null;
|
|
942
|
-
s.originalModelProvider = ctx.model?.provider ?? null;
|
|
943
|
-
|
|
944
|
-
// Register a SIGTERM handler so `kill <pid>` cleans up the lock and exits.
|
|
945
|
-
registerSigtermHandler(base);
|
|
946
|
-
|
|
947
|
-
// Capture the integration branch — records the branch the user was on when
|
|
948
|
-
// auto-mode started. Slice branches will merge back to this branch instead
|
|
949
|
-
// of the repo's default (main/master). Idempotent when the branch is the
|
|
950
|
-
// same; updates the record when started from a different branch (#300).
|
|
951
|
-
if (s.currentMilestoneId) {
|
|
952
|
-
if (getIsolationMode() !== "none") {
|
|
953
|
-
captureIntegrationBranch(base, s.currentMilestoneId, { commitDocs });
|
|
954
|
-
}
|
|
955
|
-
setActiveMilestoneId(base, s.currentMilestoneId);
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
// ── Auto-worktree: create or enter worktree for the active milestone ──
|
|
959
|
-
// Store the original project root before any chdir so we can restore on stop.
|
|
960
|
-
// Skip if already inside a worktree (manual /worktree or another auto-worktree)
|
|
961
|
-
// to prevent nested worktree creation.
|
|
962
|
-
s.originalBasePath = base;
|
|
963
|
-
|
|
964
|
-
const isUnderGsdWorktrees = (p: string): boolean => {
|
|
965
|
-
// Prevent creating nested auto-worktrees when running from within any
|
|
966
|
-
// `.gsd/worktrees/...` directory (including manual worktrees).
|
|
967
|
-
const marker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
|
|
968
|
-
if (p.includes(marker)) {
|
|
969
|
-
return true;
|
|
970
|
-
}
|
|
971
|
-
const worktreesSuffix = `${pathSep}.gsd${pathSep}worktrees`;
|
|
972
|
-
return p.endsWith(worktreesSuffix);
|
|
720
|
+
// ── Fresh start path — delegated to auto-start.ts ──
|
|
721
|
+
const bootstrapDeps: BootstrapDeps = {
|
|
722
|
+
shouldUseWorktreeIsolation,
|
|
723
|
+
registerSigtermHandler,
|
|
724
|
+
lockBase,
|
|
973
725
|
};
|
|
974
726
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
const existingWtPath = getAutoWorktreePath(base, s.currentMilestoneId);
|
|
978
|
-
if (existingWtPath) {
|
|
979
|
-
// Worktree already exists (e.g., previous session created it) — enter it.
|
|
980
|
-
const wtPath = enterAutoWorktree(base, s.currentMilestoneId);
|
|
981
|
-
s.basePath = wtPath;
|
|
982
|
-
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
983
|
-
ctx.ui.notify(`Entered auto-worktree at ${wtPath}`, "info");
|
|
984
|
-
} else {
|
|
985
|
-
// Fresh start — create worktree and enter it.
|
|
986
|
-
const wtPath = createAutoWorktree(base, s.currentMilestoneId);
|
|
987
|
-
s.basePath = wtPath;
|
|
988
|
-
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
989
|
-
ctx.ui.notify(`Created auto-worktree at ${wtPath}`, "info");
|
|
990
|
-
}
|
|
991
|
-
// Re-register SIGTERM handler with the original s.basePath (lock lives there)
|
|
992
|
-
registerSigtermHandler(s.originalBasePath);
|
|
993
|
-
|
|
994
|
-
// After worktree entry, load completed keys from BOTH locations (project root
|
|
995
|
-
// + worktree) so the in-memory set is the union. Prevents re-dispatch of units
|
|
996
|
-
// completed in either location after crash/restart (#769).
|
|
997
|
-
if (s.basePath !== s.originalBasePath) {
|
|
998
|
-
loadPersistedKeys(s.basePath, s.completedKeySet);
|
|
999
|
-
}
|
|
1000
|
-
} catch (err) {
|
|
1001
|
-
// Worktree creation is non-fatal — continue in the project root.
|
|
1002
|
-
ctx.ui.notify(
|
|
1003
|
-
`Auto-worktree setup failed: ${err instanceof Error ? err.message : String(err)}. Continuing in project root.`,
|
|
1004
|
-
"warning",
|
|
1005
|
-
);
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
// ── DB lifecycle: auto-migrate or open existing database ──
|
|
1010
|
-
const gsdDbPath = join(s.basePath, ".gsd", "gsd.db");
|
|
1011
|
-
const gsdDirPath = join(s.basePath, ".gsd");
|
|
1012
|
-
if (existsSync(gsdDirPath) && !existsSync(gsdDbPath)) {
|
|
1013
|
-
const hasDecisions = existsSync(join(gsdDirPath, "DECISIONS.md"));
|
|
1014
|
-
const hasRequirements = existsSync(join(gsdDirPath, "REQUIREMENTS.md"));
|
|
1015
|
-
const hasMilestones = existsSync(join(gsdDirPath, "milestones"));
|
|
1016
|
-
if (hasDecisions || hasRequirements || hasMilestones) {
|
|
1017
|
-
try {
|
|
1018
|
-
const { openDatabase: openDb } = await import("./gsd-db.js");
|
|
1019
|
-
const { migrateFromMarkdown } = await import("./md-importer.js");
|
|
1020
|
-
openDb(gsdDbPath);
|
|
1021
|
-
migrateFromMarkdown(s.basePath);
|
|
1022
|
-
} catch (err) {
|
|
1023
|
-
process.stderr.write(`gsd-migrate: auto-migration failed: ${(err as Error).message}\n`);
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
if (existsSync(gsdDbPath) && !isDbAvailable()) {
|
|
1028
|
-
try {
|
|
1029
|
-
const { openDatabase: openDb } = await import("./gsd-db.js");
|
|
1030
|
-
openDb(gsdDbPath);
|
|
1031
|
-
} catch (err) {
|
|
1032
|
-
process.stderr.write(`gsd-db: failed to open existing database: ${(err as Error).message}\n`);
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
// Initialize metrics — loads existing ledger from disk.
|
|
1037
|
-
// Use s.basePath (not base) so worktree-mode reads the worktree ledger (#769).
|
|
1038
|
-
initMetrics(s.basePath);
|
|
1039
|
-
|
|
1040
|
-
// Initialize routing history for adaptive learning
|
|
1041
|
-
initRoutingHistory(s.basePath);
|
|
1042
|
-
|
|
1043
|
-
// Capture the session's current model at auto-mode start (#650).
|
|
1044
|
-
// This prevents model bleed when multiple GSD instances share the
|
|
1045
|
-
// same global settings.json — each instance remembers its own model.
|
|
1046
|
-
const currentModel = ctx.model;
|
|
1047
|
-
if (currentModel) {
|
|
1048
|
-
s.autoModeStartModel = { provider: currentModel.provider, id: currentModel.id };
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
// Snapshot installed skills so we can detect new ones after research
|
|
1052
|
-
if (resolveSkillDiscoveryMode() !== "off") {
|
|
1053
|
-
snapshotSkills();
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
|
|
1057
|
-
ctx.ui.setFooter(hideFooter);
|
|
1058
|
-
const modeLabel = s.stepMode ? "Step-mode" : "Auto-mode";
|
|
1059
|
-
const pendingCount = state.registry.filter(m => m.status !== 'complete').length;
|
|
1060
|
-
const scopeMsg = pendingCount > 1
|
|
1061
|
-
? `Will loop through ${pendingCount} milestones.`
|
|
1062
|
-
: "Will loop until milestone complete.";
|
|
1063
|
-
ctx.ui.notify(`${modeLabel} started. ${scopeMsg}`, "info");
|
|
1064
|
-
|
|
1065
|
-
// Write initial lock file immediately so cross-process status detection
|
|
1066
|
-
// works even before the first unit is dispatched (#723).
|
|
1067
|
-
// The lock is updated with unit-specific info on each dispatch and cleared on stop.
|
|
1068
|
-
writeLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown", 0);
|
|
1069
|
-
|
|
1070
|
-
// Secrets collection gate — collect pending secrets before first dispatch
|
|
1071
|
-
const mid = state.activeMilestone!.id;
|
|
1072
|
-
try {
|
|
1073
|
-
const manifestStatus = await getManifestStatus(base, mid);
|
|
1074
|
-
if (manifestStatus && manifestStatus.pending.length > 0) {
|
|
1075
|
-
const result = await collectSecretsFromManifest(base, mid, ctx);
|
|
1076
|
-
if (result && result.applied && result.skipped && result.existingSkipped) {
|
|
1077
|
-
ctx.ui.notify(
|
|
1078
|
-
`Secrets collected: ${result.applied.length} applied, ${result.skipped.length} skipped, ${result.existingSkipped.length} already set.`,
|
|
1079
|
-
"info",
|
|
1080
|
-
);
|
|
1081
|
-
} else {
|
|
1082
|
-
ctx.ui.notify("Secrets collection skipped.", "info");
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
} catch (err) {
|
|
1086
|
-
ctx.ui.notify(
|
|
1087
|
-
`Secrets collection error: ${err instanceof Error ? err.message : String(err)}. Continuing with next task.`,
|
|
1088
|
-
"warning",
|
|
1089
|
-
);
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
// Self-heal: clear stale runtime records where artifacts already exist.
|
|
1093
|
-
// Use s.basePath (not base) — in worktree mode, s.basePath points to the worktree
|
|
1094
|
-
// where runtime records and artifacts actually live (#769).
|
|
1095
|
-
await selfHealRuntimeRecords(s.basePath, ctx, s.completedKeySet);
|
|
1096
|
-
|
|
1097
|
-
// Self-heal: remove stale .git/index.lock from prior crash.
|
|
1098
|
-
// A stale lock file blocks all git operations (commit, merge, checkout).
|
|
1099
|
-
// Only remove if older than 60 seconds (not from a concurrent process).
|
|
1100
|
-
try {
|
|
1101
|
-
const gitLockFile = join(base, ".git", "index.lock");
|
|
1102
|
-
if (existsSync(gitLockFile)) {
|
|
1103
|
-
const lockAge = Date.now() - statSync(gitLockFile).mtimeMs;
|
|
1104
|
-
if (lockAge > 60_000) {
|
|
1105
|
-
unlinkSync(gitLockFile);
|
|
1106
|
-
ctx.ui.notify("Removed stale .git/index.lock from prior crash.", "info");
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
} catch { /* non-fatal */ }
|
|
1110
|
-
|
|
1111
|
-
// Pre-flight: validate milestone queue for multi-milestone runs.
|
|
1112
|
-
// Warn about issues that will cause auto-mode to pause or block.
|
|
1113
|
-
try {
|
|
1114
|
-
const msDir = join(base, ".gsd", "milestones");
|
|
1115
|
-
if (existsSync(msDir)) {
|
|
1116
|
-
const milestoneIds = readdirSync(msDir, { withFileTypes: true })
|
|
1117
|
-
.filter(d => d.isDirectory() && /^M\d{3}/.test(d.name))
|
|
1118
|
-
.map(d => d.name.match(/^(M\d{3})/)?.[1] ?? d.name);
|
|
1119
|
-
if (milestoneIds.length > 1) {
|
|
1120
|
-
const issues: string[] = [];
|
|
1121
|
-
for (const id of milestoneIds) {
|
|
1122
|
-
const draft = resolveMilestoneFile(base, id, "CONTEXT-DRAFT");
|
|
1123
|
-
if (draft) issues.push(`${id}: has CONTEXT-DRAFT.md (will pause for discussion)`);
|
|
1124
|
-
}
|
|
1125
|
-
if (issues.length > 0) {
|
|
1126
|
-
ctx.ui.notify(`Pre-flight: ${milestoneIds.length} milestones queued.\n${issues.map(i => ` ⚠ ${i}`).join("\n")}`, "warning");
|
|
1127
|
-
} else {
|
|
1128
|
-
ctx.ui.notify(`Pre-flight: ${milestoneIds.length} milestones queued. All have full context.`, "info");
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
} catch { /* non-fatal — pre-flight should never block auto-mode */ }
|
|
727
|
+
const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps);
|
|
728
|
+
if (!ready) return;
|
|
1133
729
|
|
|
1134
730
|
// Dispatch the first unit
|
|
1135
731
|
await dispatchNextUnit(ctx, pi);
|
|
@@ -1137,19 +733,22 @@ export async function startAuto(
|
|
|
1137
733
|
|
|
1138
734
|
// ─── Agent End Handler ────────────────────────────────────────────────────────
|
|
1139
735
|
|
|
1140
|
-
/** Guard against concurrent handleAgentEnd execution.
|
|
1141
|
-
* notifications and other system messages can trigger multiple agent_end
|
|
1142
|
-
* events before the first handler finishes (the handler yields at every
|
|
1143
|
-
* await). Without this guard, concurrent dispatchNextUnit calls race on
|
|
1144
|
-
* newSession(), causing one to cancel the other and silently stopping
|
|
1145
|
-
* auto-mode. */
|
|
736
|
+
/** Guard against concurrent handleAgentEnd execution. */
|
|
1146
737
|
|
|
1147
738
|
export async function handleAgentEnd(
|
|
1148
739
|
ctx: ExtensionContext,
|
|
1149
740
|
pi: ExtensionAPI,
|
|
1150
741
|
): Promise<void> {
|
|
1151
742
|
if (!s.active || !s.cmdCtx) return;
|
|
1152
|
-
if (s.handlingAgentEnd)
|
|
743
|
+
if (s.handlingAgentEnd) {
|
|
744
|
+
// Another agent_end arrived while we're still processing the previous one.
|
|
745
|
+
// This happens when a unit dispatched inside handleAgentEnd (e.g. via hooks,
|
|
746
|
+
// triage, or quick-task early-dispatch paths) completes before the outer
|
|
747
|
+
// handleAgentEnd returns. Queue a retry so the completed unit's agent_end
|
|
748
|
+
// is not silently dropped (#1072).
|
|
749
|
+
s.pendingAgentEndRetry = true;
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
1153
752
|
s.handlingAgentEnd = true;
|
|
1154
753
|
|
|
1155
754
|
try {
|
|
@@ -1157,714 +756,94 @@ export async function handleAgentEnd(
|
|
|
1157
756
|
// Unit completed — clear its timeout
|
|
1158
757
|
clearUnitTimeout();
|
|
1159
758
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
}
|
|
1172
|
-
if (signal.signal === "pause") {
|
|
1173
|
-
s.handlingAgentEnd = false;
|
|
1174
|
-
await pauseAuto(ctx, pi);
|
|
1175
|
-
return;
|
|
1176
|
-
}
|
|
1177
|
-
// "resume" and "rebase" signals are handled elsewhere or no-op here
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
// Invalidate all caches — the unit just completed and may have
|
|
1182
|
-
// written planning files (task summaries, roadmap checkboxes, etc.)
|
|
1183
|
-
invalidateAllCaches();
|
|
1184
|
-
|
|
1185
|
-
// Small delay to let files settle (git commits, file writes)
|
|
1186
|
-
await new Promise(r => setTimeout(r, 500));
|
|
1187
|
-
|
|
1188
|
-
// Commit any dirty files the LLM left behind on the current branch.
|
|
1189
|
-
// For execute-task units, build a meaningful commit message from the
|
|
1190
|
-
// task summary (one-liner, key_files, inferred type). For other unit
|
|
1191
|
-
// types, fall back to the generic chore() message.
|
|
1192
|
-
if (s.currentUnit) {
|
|
1193
|
-
try {
|
|
1194
|
-
let taskContext: TaskCommitContext | undefined;
|
|
1195
|
-
|
|
1196
|
-
if (s.currentUnit.type === "execute-task") {
|
|
1197
|
-
const parts = s.currentUnit.id.split("/");
|
|
1198
|
-
const [mid, sid, tid] = parts;
|
|
1199
|
-
if (mid && sid && tid) {
|
|
1200
|
-
const summaryPath = resolveTaskFile(s.basePath, mid, sid, tid, "SUMMARY");
|
|
1201
|
-
if (summaryPath) {
|
|
1202
|
-
try {
|
|
1203
|
-
const summaryContent = await loadFile(summaryPath);
|
|
1204
|
-
if (summaryContent) {
|
|
1205
|
-
const summary = parseSummary(summaryContent);
|
|
1206
|
-
taskContext = {
|
|
1207
|
-
taskId: `${sid}/${tid}`,
|
|
1208
|
-
taskTitle: summary.title?.replace(/^T\d+:\s*/, "") || tid,
|
|
1209
|
-
oneLiner: summary.oneLiner || undefined,
|
|
1210
|
-
keyFiles: summary.frontmatter.key_files?.filter(f => !f.includes("{{")) || undefined,
|
|
1211
|
-
};
|
|
1212
|
-
}
|
|
1213
|
-
} catch {
|
|
1214
|
-
// Non-fatal — fall back to generic message
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
const commitMsg = autoCommitCurrentBranch(s.basePath, s.currentUnit.type, s.currentUnit.id, taskContext);
|
|
1221
|
-
if (commitMsg) {
|
|
1222
|
-
ctx.ui.notify(`Committed: ${commitMsg.split("\n")[0]}`, "info");
|
|
1223
|
-
}
|
|
1224
|
-
} catch {
|
|
1225
|
-
// Non-fatal
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
// Post-hook: fix mechanical bookkeeping the LLM may have skipped.
|
|
1229
|
-
// 1. Doctor handles: checkbox marking (task-level bookkeeping).
|
|
1230
|
-
// 2. STATE.md is always rebuilt from disk state (purely derived, no LLM needed).
|
|
1231
|
-
// fixLevel:"task" ensures doctor only fixes task-level issues (e.g. marking
|
|
1232
|
-
// checkboxes). Slice/milestone completion transitions (summary stubs,
|
|
1233
|
-
// roadmap [x] marking) are left for the complete-slice dispatch unit.
|
|
1234
|
-
// Exception: after complete-slice itself, use fixLevel:"all" so roadmap
|
|
1235
|
-
// checkboxes get fixed even if complete-slice crashed (#839).
|
|
1236
|
-
try {
|
|
1237
|
-
const scopeParts = s.currentUnit.id.split("/").slice(0, 2);
|
|
1238
|
-
const doctorScope = scopeParts.join("/");
|
|
1239
|
-
const effectiveFixLevel = s.currentUnit.type === "complete-slice" ? "all" as const : "task" as const;
|
|
1240
|
-
const report = await runGSDDoctor(s.basePath, { fix: true, scope: doctorScope, fixLevel: effectiveFixLevel });
|
|
1241
|
-
if (report.fixesApplied.length > 0) {
|
|
1242
|
-
ctx.ui.notify(`Post-hook: applied ${report.fixesApplied.length} fix(es).`, "info");
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
// ── Proactive health tracking ──────────────────────────────────────
|
|
1246
|
-
// Record health snapshot for trend analysis and escalation logic.
|
|
1247
|
-
const summary = summarizeDoctorIssues(report.issues);
|
|
1248
|
-
recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length);
|
|
1249
|
-
|
|
1250
|
-
// Check if we should escalate to LLM-assisted heal
|
|
1251
|
-
if (summary.errors > 0) {
|
|
1252
|
-
const unresolvedErrors = report.issues
|
|
1253
|
-
.filter(i => i.severity === "error" && !i.fixable)
|
|
1254
|
-
.map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
|
|
1255
|
-
const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
|
|
1256
|
-
if (escalation.shouldEscalate) {
|
|
1257
|
-
ctx.ui.notify(
|
|
1258
|
-
`Doctor heal escalation: ${escalation.reason}. Dispatching LLM-assisted heal.`,
|
|
1259
|
-
"warning",
|
|
1260
|
-
);
|
|
1261
|
-
try {
|
|
1262
|
-
const { formatDoctorIssuesForPrompt, formatDoctorReport } = await import("./doctor.js");
|
|
1263
|
-
const { dispatchDoctorHeal } = await import("./commands.js");
|
|
1264
|
-
const actionable = report.issues.filter(i => i.severity === "error");
|
|
1265
|
-
const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
|
|
1266
|
-
const structuredIssues = formatDoctorIssuesForPrompt(actionable);
|
|
1267
|
-
dispatchDoctorHeal(pi, doctorScope, reportText, structuredIssues);
|
|
1268
|
-
} catch {
|
|
1269
|
-
// Non-fatal — escalation dispatch failure
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
} catch {
|
|
1274
|
-
// Non-fatal — doctor failure should never block dispatch
|
|
1275
|
-
}
|
|
1276
|
-
// Throttle STATE.md rebuilds to reduce I/O spikes on long sessions.
|
|
1277
|
-
// STATE.md is a derived diagnostic artifact — skipping a rebuild is safe;
|
|
1278
|
-
// the next unit or stop/pause will rebuild it.
|
|
1279
|
-
const now = Date.now();
|
|
1280
|
-
if (now - s.lastStateRebuildAt >= STATE_REBUILD_MIN_INTERVAL_MS) {
|
|
1281
|
-
try {
|
|
1282
|
-
await rebuildState(s.basePath);
|
|
1283
|
-
s.lastStateRebuildAt = now;
|
|
1284
|
-
// State rebuild commit is bookkeeping — generic message is appropriate
|
|
1285
|
-
autoCommitCurrentBranch(s.basePath, "state-rebuild", s.currentUnit.id);
|
|
1286
|
-
} catch {
|
|
1287
|
-
// Non-fatal
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
// ── Prune dead bg-shell processes ──────────────────────────────────────
|
|
1292
|
-
// Dead processes retain ~500KB-1MB of output buffers each. Without pruning,
|
|
1293
|
-
// they accumulate during long auto-mode sessions causing memory pressure.
|
|
1294
|
-
try {
|
|
1295
|
-
const { pruneDeadProcesses } = await import("../bg-shell/process-manager.js");
|
|
1296
|
-
pruneDeadProcesses();
|
|
1297
|
-
} catch {
|
|
1298
|
-
// Non-fatal — bg-shell may not be available
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
// ── Sync worktree state back to project root ──────────────────────────
|
|
1302
|
-
// Ensures that if auto-mode restarts, deriveState(projectRoot) reads
|
|
1303
|
-
// current milestone progress instead of stale pre-worktree state (#654).
|
|
1304
|
-
if (s.originalBasePath && s.originalBasePath !== s.basePath) {
|
|
1305
|
-
try {
|
|
1306
|
-
syncStateToProjectRoot(s.basePath, s.originalBasePath, s.currentMilestoneId);
|
|
1307
|
-
} catch {
|
|
1308
|
-
// Non-fatal — stale state is the existing behavior, sync is an improvement
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1312
|
-
// ── Rewrite-docs completion: resolve overrides and reset circuit breaker ──
|
|
1313
|
-
if (s.currentUnit.type === "rewrite-docs") {
|
|
1314
|
-
try {
|
|
1315
|
-
await resolveAllOverrides(s.basePath);
|
|
1316
|
-
resetRewriteCircuitBreaker();
|
|
1317
|
-
ctx.ui.notify("Override(s) resolved — rewrite-docs completed.", "info");
|
|
1318
|
-
} catch {
|
|
1319
|
-
// Non-fatal — verifyExpectedArtifact will catch unresolved overrides
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
// ── Post-triage: execute actionable resolutions (inject, replan, queue quick-tasks) ──
|
|
1324
|
-
// After a triage-captures unit completes, the LLM has classified captures and
|
|
1325
|
-
// updated CAPTURES.md. Now we execute those classifications: inject tasks into
|
|
1326
|
-
// the plan, write replan triggers, and queue quick-tasks for dispatch.
|
|
1327
|
-
if (s.currentUnit.type === "triage-captures") {
|
|
1328
|
-
try {
|
|
1329
|
-
const { executeTriageResolutions } = await import("./triage-resolution.js");
|
|
1330
|
-
const state = await deriveState(s.basePath);
|
|
1331
|
-
const mid = state.activeMilestone?.id;
|
|
1332
|
-
const sid = state.activeSlice?.id;
|
|
1333
|
-
|
|
1334
|
-
if (mid && sid) {
|
|
1335
|
-
const triageResult = executeTriageResolutions(s.basePath, mid, sid);
|
|
1336
|
-
|
|
1337
|
-
if (triageResult.injected > 0) {
|
|
1338
|
-
ctx.ui.notify(
|
|
1339
|
-
`Triage: injected ${triageResult.injected} task${triageResult.injected === 1 ? "" : "s"} into ${sid} plan.`,
|
|
1340
|
-
"info",
|
|
1341
|
-
);
|
|
1342
|
-
}
|
|
1343
|
-
if (triageResult.replanned > 0) {
|
|
1344
|
-
ctx.ui.notify(
|
|
1345
|
-
`Triage: replan trigger written for ${sid} — next dispatch will enter replanning.`,
|
|
1346
|
-
"info",
|
|
1347
|
-
);
|
|
1348
|
-
}
|
|
1349
|
-
if (triageResult.quickTasks.length > 0) {
|
|
1350
|
-
// Queue quick-tasks for dispatch. They'll be picked up by the
|
|
1351
|
-
// quick-task dispatch block below the triage check.
|
|
1352
|
-
for (const qt of triageResult.quickTasks) {
|
|
1353
|
-
s.pendingQuickTasks.push(qt);
|
|
1354
|
-
}
|
|
1355
|
-
ctx.ui.notify(
|
|
1356
|
-
`Triage: ${triageResult.quickTasks.length} quick-task${triageResult.quickTasks.length === 1 ? "" : "s"} queued for execution.`,
|
|
1357
|
-
"info",
|
|
1358
|
-
);
|
|
1359
|
-
}
|
|
1360
|
-
for (const action of triageResult.actions) {
|
|
1361
|
-
process.stderr.write(`gsd-triage: ${action}\n`);
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
} catch (err) {
|
|
1365
|
-
// Non-fatal — triage resolution failure shouldn't block dispatch
|
|
1366
|
-
process.stderr.write(`gsd-triage: resolution execution failed: ${(err as Error).message}\n`);
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
759
|
+
// ── Pre-verification processing (commit, doctor, state rebuild, etc.) ──
|
|
760
|
+
const postUnitCtx: PostUnitContext = {
|
|
761
|
+
s,
|
|
762
|
+
ctx,
|
|
763
|
+
pi,
|
|
764
|
+
buildSnapshotOpts,
|
|
765
|
+
lockBase,
|
|
766
|
+
stopAuto,
|
|
767
|
+
pauseAuto,
|
|
768
|
+
updateProgressWidget,
|
|
769
|
+
};
|
|
1369
770
|
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
// produced its expected artifact. If so, persist the completion key now so the
|
|
1373
|
-
// idempotency check at the top of dispatchNextUnit() skips it — even if
|
|
1374
|
-
// deriveState() still returns this unit as s.active (e.g. branch mismatch).
|
|
1375
|
-
//
|
|
1376
|
-
// IMPORTANT: For non-hook units, defer persistence until after the hook check.
|
|
1377
|
-
// If a post-unit hook requests a retry, we need to remove the completion key
|
|
1378
|
-
// so dispatchNextUnit re-dispatches the trigger unit.
|
|
1379
|
-
let triggerArtifactVerified = false;
|
|
1380
|
-
if (!s.currentUnit.type.startsWith("hook/")) {
|
|
1381
|
-
try {
|
|
1382
|
-
triggerArtifactVerified = verifyExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.basePath);
|
|
1383
|
-
if (triggerArtifactVerified) {
|
|
1384
|
-
const completionKey = `${s.currentUnit.type}/${s.currentUnit.id}`;
|
|
1385
|
-
if (!s.completedKeySet.has(completionKey)) {
|
|
1386
|
-
persistCompletedKey(s.basePath, completionKey);
|
|
1387
|
-
s.completedKeySet.add(completionKey);
|
|
1388
|
-
}
|
|
1389
|
-
invalidateAllCaches();
|
|
1390
|
-
}
|
|
1391
|
-
} catch {
|
|
1392
|
-
// Non-fatal — worst case we fall through to normal dispatch which has its own checks
|
|
1393
|
-
}
|
|
1394
|
-
} else {
|
|
1395
|
-
// Hook unit completed — finalize its runtime record and clear it
|
|
1396
|
-
try {
|
|
1397
|
-
writeUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, {
|
|
1398
|
-
phase: "finalized",
|
|
1399
|
-
progressCount: 1,
|
|
1400
|
-
lastProgressKind: "hook-completed",
|
|
1401
|
-
});
|
|
1402
|
-
clearUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id);
|
|
1403
|
-
} catch {
|
|
1404
|
-
// Non-fatal
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
}
|
|
771
|
+
const preResult = await postUnitPreVerification(postUnitCtx);
|
|
772
|
+
if (preResult === "dispatched") return;
|
|
1408
773
|
|
|
1409
774
|
// ── Verification gate: run typecheck/lint/test after execute-task ──
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
const parts = s.currentUnit.id.split("/");
|
|
1418
|
-
let taskPlanVerify: string | undefined;
|
|
1419
|
-
if (parts.length >= 3) {
|
|
1420
|
-
const [mid, sid, tid] = parts;
|
|
1421
|
-
const planFile = resolveSliceFile(s.basePath, mid, sid, "PLAN");
|
|
1422
|
-
if (planFile) {
|
|
1423
|
-
const planContent = await loadFile(planFile);
|
|
1424
|
-
if (planContent) {
|
|
1425
|
-
const slicePlan = parsePlan(planContent);
|
|
1426
|
-
const taskEntry = slicePlan?.tasks?.find(t => t.id === tid);
|
|
1427
|
-
taskPlanVerify = taskEntry?.verify;
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
const result = runVerificationGate({ basePath: s.basePath,
|
|
1433
|
-
unitId: s.currentUnit.id,
|
|
1434
|
-
cwd: s.basePath,
|
|
1435
|
-
preferenceCommands: prefs?.verification_commands,
|
|
1436
|
-
taskPlanVerify,
|
|
1437
|
-
});
|
|
1438
|
-
|
|
1439
|
-
// Capture runtime errors from bg-shell and browser console
|
|
1440
|
-
const runtimeErrors = await captureRuntimeErrors();
|
|
1441
|
-
if (runtimeErrors.length > 0) {
|
|
1442
|
-
result.runtimeErrors = runtimeErrors;
|
|
1443
|
-
// Blocking runtime errors override gate pass
|
|
1444
|
-
if (runtimeErrors.some(e => e.blocking)) {
|
|
1445
|
-
result.passed = false;
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
// Conditional dependency audit (R008)
|
|
1450
|
-
const auditWarnings = runDependencyAudit(s.basePath);
|
|
1451
|
-
if (auditWarnings.length > 0) {
|
|
1452
|
-
result.auditWarnings = auditWarnings;
|
|
1453
|
-
process.stderr.write(`verification-gate: ${auditWarnings.length} audit warning(s)\n`);
|
|
1454
|
-
for (const w of auditWarnings) {
|
|
1455
|
-
process.stderr.write(` [${w.severity}] ${w.name}: ${w.title}\n`);
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
// Auto-fix retry preferences (R005 / D005)
|
|
1460
|
-
const autoFixEnabled = prefs?.verification_auto_fix !== false; // default true
|
|
1461
|
-
const maxRetries = typeof prefs?.verification_max_retries === "number" ? prefs.verification_max_retries : 2;
|
|
1462
|
-
const completionKey = `${s.currentUnit.type}/${s.currentUnit.id}`;
|
|
1463
|
-
|
|
1464
|
-
if (result.checks.length > 0) {
|
|
1465
|
-
const passCount = result.checks.filter(c => c.exitCode === 0).length;
|
|
1466
|
-
const total = result.checks.length;
|
|
1467
|
-
if (result.passed) {
|
|
1468
|
-
ctx.ui.notify(`Verification gate: ${passCount}/${total} checks passed`);
|
|
1469
|
-
} else {
|
|
1470
|
-
const failures = result.checks.filter(c => c.exitCode !== 0);
|
|
1471
|
-
const failNames = failures.map(f => f.command).join(", ");
|
|
1472
|
-
ctx.ui.notify(`Verification gate: FAILED — ${failNames}`);
|
|
1473
|
-
process.stderr.write(`verification-gate: ${total - passCount}/${total} checks failed\n`);
|
|
1474
|
-
for (const f of failures) {
|
|
1475
|
-
process.stderr.write(` ${f.command} exited ${f.exitCode}\n`);
|
|
1476
|
-
if (f.stderr) process.stderr.write(` stderr: ${f.stderr.slice(0, 500)}\n`);
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
// Log blocking runtime errors to stderr
|
|
1482
|
-
if (result.runtimeErrors?.some(e => e.blocking)) {
|
|
1483
|
-
const blockingErrors = result.runtimeErrors.filter(e => e.blocking);
|
|
1484
|
-
process.stderr.write(`verification-gate: ${blockingErrors.length} blocking runtime error(s) detected\n`);
|
|
1485
|
-
for (const err of blockingErrors) {
|
|
1486
|
-
process.stderr.write(` [${err.source}] ${err.severity}: ${err.message.slice(0, 200)}\n`);
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
// Write verification evidence JSON artifact
|
|
1491
|
-
const attempt = s.verificationRetryCount.get(s.currentUnit.id) ?? 0;
|
|
1492
|
-
if (parts.length >= 3) {
|
|
1493
|
-
try {
|
|
1494
|
-
const [mid, sid, tid] = parts;
|
|
1495
|
-
const sDir = resolveSlicePath(s.basePath, mid, sid);
|
|
1496
|
-
if (sDir) {
|
|
1497
|
-
const tasksDir = join(sDir, "tasks");
|
|
1498
|
-
if (result.passed) {
|
|
1499
|
-
writeVerificationJSON(result, tasksDir, tid, s.currentUnit.id);
|
|
1500
|
-
} else {
|
|
1501
|
-
const nextAttempt = attempt + 1;
|
|
1502
|
-
writeVerificationJSON(result, tasksDir, tid, s.currentUnit.id, nextAttempt, maxRetries);
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
} catch (evidenceErr) {
|
|
1506
|
-
process.stderr.write(`verification-evidence: write error — ${(evidenceErr as Error).message}\n`);
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
// ── Auto-fix retry logic ──
|
|
1511
|
-
if (result.passed) {
|
|
1512
|
-
// Gate passed — clear retry state and continue normal flow
|
|
1513
|
-
s.verificationRetryCount.delete(s.currentUnit.id);
|
|
1514
|
-
s.pendingVerificationRetry = null;
|
|
1515
|
-
} else if (autoFixEnabled && attempt + 1 <= maxRetries) {
|
|
1516
|
-
// Gate failed, retries remaining — set up retry and return early
|
|
1517
|
-
const nextAttempt = attempt + 1;
|
|
1518
|
-
s.verificationRetryCount.set(s.currentUnit.id, nextAttempt);
|
|
1519
|
-
s.pendingVerificationRetry = {
|
|
1520
|
-
unitId: s.currentUnit.id,
|
|
1521
|
-
failureContext: formatFailureContext(result),
|
|
1522
|
-
attempt: nextAttempt,
|
|
1523
|
-
};
|
|
1524
|
-
ctx.ui.notify(`Verification failed — auto-fix attempt ${nextAttempt}/${maxRetries}`, "warning");
|
|
1525
|
-
// Remove completion key so dispatchNextUnit re-dispatches this unit
|
|
1526
|
-
s.completedKeySet.delete(completionKey);
|
|
1527
|
-
removePersistedKey(s.basePath, completionKey);
|
|
1528
|
-
return; // ← Critical: exit before DB dual-write and post-unit hooks
|
|
1529
|
-
} else {
|
|
1530
|
-
// Gate failed, retries exhausted (or auto-fix disabled) — pause for human review
|
|
1531
|
-
const exhaustedAttempt = attempt + 1;
|
|
1532
|
-
s.verificationRetryCount.delete(s.currentUnit.id);
|
|
1533
|
-
s.pendingVerificationRetry = null;
|
|
1534
|
-
ctx.ui.notify(
|
|
1535
|
-
`Verification gate FAILED after ${exhaustedAttempt > maxRetries ? exhaustedAttempt - 1 : exhaustedAttempt} retries — pausing for human review`,
|
|
1536
|
-
"error",
|
|
1537
|
-
);
|
|
1538
|
-
await pauseAuto(ctx, pi);
|
|
1539
|
-
return;
|
|
1540
|
-
}
|
|
1541
|
-
} catch (err) {
|
|
1542
|
-
// Gate errors are non-fatal — log and continue
|
|
1543
|
-
process.stderr.write(`verification-gate: error — ${(err as Error).message}\n`);
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
// ── DB dual-write: re-import changed markdown files so next unit's prompts use fresh data ──
|
|
1548
|
-
if (isDbAvailable()) {
|
|
1549
|
-
try {
|
|
1550
|
-
const { migrateFromMarkdown } = await import("./md-importer.js");
|
|
1551
|
-
migrateFromMarkdown(s.basePath);
|
|
1552
|
-
} catch (err) {
|
|
1553
|
-
process.stderr.write(`gsd-db: re-import failed: ${(err as Error).message}\n`);
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
// ── Post-unit hooks: check if a configured hook should run before normal dispatch ──
|
|
1558
|
-
if (s.currentUnit && !s.stepMode) {
|
|
1559
|
-
const hookUnit = checkPostUnitHooks(s.currentUnit.type, s.currentUnit.id, s.basePath);
|
|
1560
|
-
if (hookUnit) {
|
|
1561
|
-
// Dispatch the hook unit instead of normal flow
|
|
1562
|
-
const hookStartedAt = Date.now();
|
|
1563
|
-
if (s.currentUnit) {
|
|
1564
|
-
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
1565
|
-
}
|
|
1566
|
-
s.currentUnit = { type: hookUnit.unitType, id: hookUnit.unitId, startedAt: hookStartedAt };
|
|
1567
|
-
writeUnitRuntimeRecord(s.basePath, hookUnit.unitType, hookUnit.unitId, hookStartedAt, {
|
|
1568
|
-
phase: "dispatched",
|
|
1569
|
-
wrapupWarningSent: false,
|
|
1570
|
-
timeoutAt: null,
|
|
1571
|
-
lastProgressAt: hookStartedAt,
|
|
1572
|
-
progressCount: 0,
|
|
1573
|
-
lastProgressKind: "dispatch",
|
|
1574
|
-
});
|
|
1575
|
-
|
|
1576
|
-
const state = await deriveState(s.basePath);
|
|
1577
|
-
updateProgressWidget(ctx, hookUnit.unitType, hookUnit.unitId, state);
|
|
1578
|
-
const hookState = getActiveHook();
|
|
1579
|
-
ctx.ui.notify(
|
|
1580
|
-
`Running post-unit hook: ${hookUnit.hookName} (cycle ${hookState?.cycle ?? 1})`,
|
|
1581
|
-
"info",
|
|
1582
|
-
);
|
|
1583
|
-
|
|
1584
|
-
// Switch model if the hook specifies one
|
|
1585
|
-
if (hookUnit.model) {
|
|
1586
|
-
const availableModels = ctx.modelRegistry.getAvailable();
|
|
1587
|
-
const match = availableModels.find(m =>
|
|
1588
|
-
m.id === hookUnit.model || `${m.provider}/${m.id}` === hookUnit.model,
|
|
1589
|
-
);
|
|
1590
|
-
if (match) {
|
|
1591
|
-
try {
|
|
1592
|
-
await pi.setModel(match);
|
|
1593
|
-
} catch { /* non-fatal — use current model */ }
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
|
-
const result = await s.cmdCtx!.newSession();
|
|
1598
|
-
if (result.cancelled) {
|
|
1599
|
-
resetHookState();
|
|
1600
|
-
await stopAuto(ctx, pi, "Hook session cancelled");
|
|
1601
|
-
return;
|
|
1602
|
-
}
|
|
1603
|
-
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
1604
|
-
writeLock(lockBase(), hookUnit.unitType, hookUnit.unitId, s.completedUnits.length, sessionFile);
|
|
1605
|
-
// Persist hook state so cycle counts survive crashes
|
|
1606
|
-
persistHookState(s.basePath);
|
|
1607
|
-
|
|
1608
|
-
// Start supervision timers for hook units — hooks can get stuck just
|
|
1609
|
-
// like normal units, and without a watchdog auto-mode would hang forever.
|
|
1610
|
-
clearUnitTimeout();
|
|
1611
|
-
const supervisor = resolveAutoSupervisorConfig();
|
|
1612
|
-
const hookHardTimeoutMs = (supervisor.hard_timeout_minutes ?? 30) * 60 * 1000;
|
|
1613
|
-
s.unitTimeoutHandle = setTimeout(async () => {
|
|
1614
|
-
s.unitTimeoutHandle = null;
|
|
1615
|
-
if (!s.active) return;
|
|
1616
|
-
if (s.currentUnit) {
|
|
1617
|
-
writeUnitRuntimeRecord(s.basePath, hookUnit.unitType, hookUnit.unitId, s.currentUnit.startedAt, {
|
|
1618
|
-
phase: "timeout",
|
|
1619
|
-
timeoutAt: Date.now(),
|
|
1620
|
-
});
|
|
1621
|
-
}
|
|
1622
|
-
ctx.ui.notify(
|
|
1623
|
-
`Hook ${hookUnit.hookName} exceeded ${supervisor.hard_timeout_minutes ?? 30}min timeout. Pausing auto-mode.`,
|
|
1624
|
-
"warning",
|
|
1625
|
-
);
|
|
1626
|
-
resetHookState();
|
|
1627
|
-
await pauseAuto(ctx, pi);
|
|
1628
|
-
}, hookHardTimeoutMs);
|
|
1629
|
-
|
|
1630
|
-
// Guard against race with timeout/pause before sending
|
|
1631
|
-
if (!s.active) return;
|
|
1632
|
-
pi.sendMessage(
|
|
1633
|
-
{ customType: "gsd-auto", content: hookUnit.prompt, display: s.verbose },
|
|
1634
|
-
{ triggerTurn: true },
|
|
1635
|
-
);
|
|
1636
|
-
return; // handleAgentEnd will fire again when hook session completes
|
|
1637
|
-
}
|
|
1638
|
-
|
|
1639
|
-
// Check if a hook requested a retry of the trigger unit
|
|
1640
|
-
if (isRetryPending()) {
|
|
1641
|
-
const trigger = consumeRetryTrigger();
|
|
1642
|
-
if (trigger) {
|
|
1643
|
-
// Remove the trigger unit's completion key so dispatchNextUnit
|
|
1644
|
-
// will re-dispatch it instead of skipping it as already-complete.
|
|
1645
|
-
const triggerKey = `${trigger.unitType}/${trigger.unitId}`;
|
|
1646
|
-
s.completedKeySet.delete(triggerKey);
|
|
1647
|
-
removePersistedKey(s.basePath, triggerKey);
|
|
1648
|
-
ctx.ui.notify(
|
|
1649
|
-
`Hook requested retry of ${trigger.unitType} ${trigger.unitId}.`,
|
|
1650
|
-
"info",
|
|
1651
|
-
);
|
|
1652
|
-
// Fall through to normal dispatchNextUnit — state derivation will
|
|
1653
|
-
// re-select the same unit since it hasn't been marked complete
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
}
|
|
1657
|
-
|
|
1658
|
-
// ── Triage check: dispatch triage unit if pending captures exist ──────────
|
|
1659
|
-
// Fires after hooks complete, before normal dispatch. Follows the same
|
|
1660
|
-
// early-dispatch-and-return pattern as hooks and fix-merge.
|
|
1661
|
-
// Skip for: step mode (shows wizard instead), triage units (prevent triage-on-triage),
|
|
1662
|
-
// hook units (hooks run before triage conceptually).
|
|
1663
|
-
if (
|
|
1664
|
-
!s.stepMode &&
|
|
1665
|
-
s.currentUnit &&
|
|
1666
|
-
!s.currentUnit.type.startsWith("hook/") &&
|
|
1667
|
-
s.currentUnit.type !== "triage-captures" &&
|
|
1668
|
-
s.currentUnit.type !== "quick-task"
|
|
1669
|
-
) {
|
|
1670
|
-
try {
|
|
1671
|
-
if (hasPendingCaptures(s.basePath)) {
|
|
1672
|
-
const pending = loadPendingCaptures(s.basePath);
|
|
1673
|
-
if (pending.length > 0) {
|
|
1674
|
-
const state = await deriveState(s.basePath);
|
|
1675
|
-
const mid = state.activeMilestone?.id;
|
|
1676
|
-
const sid = state.activeSlice?.id;
|
|
1677
|
-
|
|
1678
|
-
if (mid && sid) {
|
|
1679
|
-
// Build triage prompt with current context
|
|
1680
|
-
let currentPlan = "";
|
|
1681
|
-
let roadmapContext = "";
|
|
1682
|
-
const planFile = resolveSliceFile(s.basePath, mid, sid, "PLAN");
|
|
1683
|
-
if (planFile) currentPlan = (await loadFile(planFile)) ?? "";
|
|
1684
|
-
const roadmapFile = resolveMilestoneFile(s.basePath, mid, "ROADMAP");
|
|
1685
|
-
if (roadmapFile) roadmapContext = (await loadFile(roadmapFile)) ?? "";
|
|
1686
|
-
|
|
1687
|
-
const capturesList = pending.map(c =>
|
|
1688
|
-
`- **${c.id}**: "${c.text}" (captured: ${c.timestamp})`
|
|
1689
|
-
).join("\n");
|
|
1690
|
-
|
|
1691
|
-
const prompt = loadPrompt("triage-captures", {
|
|
1692
|
-
pendingCaptures: capturesList,
|
|
1693
|
-
currentPlan: currentPlan || "(no active slice plan)",
|
|
1694
|
-
roadmapContext: roadmapContext || "(no active roadmap)",
|
|
1695
|
-
});
|
|
1696
|
-
|
|
1697
|
-
ctx.ui.notify(
|
|
1698
|
-
`Triaging ${pending.length} pending capture${pending.length === 1 ? "" : "s"}...`,
|
|
1699
|
-
"info",
|
|
1700
|
-
);
|
|
1701
|
-
|
|
1702
|
-
// Close out previous unit metrics
|
|
1703
|
-
if (s.currentUnit) {
|
|
1704
|
-
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt);
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1707
|
-
// Dispatch triage as a new unit (early-dispatch-and-return)
|
|
1708
|
-
const triageUnitType = "triage-captures";
|
|
1709
|
-
const triageUnitId = `${mid}/${sid}/triage`;
|
|
1710
|
-
const triageStartedAt = Date.now();
|
|
1711
|
-
s.currentUnit = { type: triageUnitType, id: triageUnitId, startedAt: triageStartedAt };
|
|
1712
|
-
writeUnitRuntimeRecord(s.basePath, triageUnitType, triageUnitId, triageStartedAt, {
|
|
1713
|
-
phase: "dispatched",
|
|
1714
|
-
wrapupWarningSent: false,
|
|
1715
|
-
timeoutAt: null,
|
|
1716
|
-
lastProgressAt: triageStartedAt,
|
|
1717
|
-
progressCount: 0,
|
|
1718
|
-
lastProgressKind: "dispatch",
|
|
1719
|
-
});
|
|
1720
|
-
updateProgressWidget(ctx, triageUnitType, triageUnitId, state);
|
|
1721
|
-
|
|
1722
|
-
const result = await s.cmdCtx!.newSession();
|
|
1723
|
-
if (result.cancelled) {
|
|
1724
|
-
await stopAuto(ctx, pi);
|
|
1725
|
-
return;
|
|
1726
|
-
}
|
|
1727
|
-
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
1728
|
-
writeLock(lockBase(), triageUnitType, triageUnitId, s.completedUnits.length, sessionFile);
|
|
1729
|
-
|
|
1730
|
-
// Start unit timeout for triage (use same supervisor config as hooks)
|
|
1731
|
-
clearUnitTimeout();
|
|
1732
|
-
const supervisor = resolveAutoSupervisorConfig();
|
|
1733
|
-
const triageTimeoutMs = (supervisor.hard_timeout_minutes ?? 30) * 60 * 1000;
|
|
1734
|
-
s.unitTimeoutHandle = setTimeout(async () => {
|
|
1735
|
-
s.unitTimeoutHandle = null;
|
|
1736
|
-
if (!s.active) return;
|
|
1737
|
-
ctx.ui.notify(
|
|
1738
|
-
`Triage unit exceeded timeout. Pausing auto-mode.`,
|
|
1739
|
-
"warning",
|
|
1740
|
-
);
|
|
1741
|
-
await pauseAuto(ctx, pi);
|
|
1742
|
-
}, triageTimeoutMs);
|
|
775
|
+
const verificationResult = await runPostUnitVerification(
|
|
776
|
+
{ s, ctx, pi },
|
|
777
|
+
dispatchNextUnit,
|
|
778
|
+
startDispatchGapWatchdog,
|
|
779
|
+
pauseAuto,
|
|
780
|
+
);
|
|
781
|
+
if (verificationResult === "retry" || verificationResult === "pause") return;
|
|
1743
782
|
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
} catch {
|
|
1754
|
-
// Triage check failure is non-fatal — proceed to normal dispatch
|
|
1755
|
-
}
|
|
783
|
+
// ── Post-verification processing (DB dual-write, hooks, triage, quick-tasks) ──
|
|
784
|
+
const postResult = await postUnitPostVerification(postUnitCtx);
|
|
785
|
+
if (postResult === "dispatched" || postResult === "stopped") return;
|
|
786
|
+
if (postResult === "step-wizard") {
|
|
787
|
+
await showStepWizard(ctx, pi);
|
|
788
|
+
return;
|
|
1756
789
|
}
|
|
1757
790
|
|
|
1758
|
-
// ──
|
|
1759
|
-
//
|
|
1760
|
-
//
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
s.
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
const { buildQuickTaskPrompt } = await import("./triage-resolution.js");
|
|
1770
|
-
const { markCaptureExecuted } = await import("./captures.js");
|
|
1771
|
-
const prompt = buildQuickTaskPrompt(capture);
|
|
1772
|
-
|
|
791
|
+
// ── Dispatch with hang detection (#1073) ────────────────────────────────
|
|
792
|
+
// Start a safety watchdog BEFORE calling dispatchNextUnit. If dispatch
|
|
793
|
+
// hangs at any await (newSession, model selection, etc.), the gap watchdog
|
|
794
|
+
// inside handleAgentEnd never fires because we never reach the check.
|
|
795
|
+
// This pre-dispatch watchdog ensures recovery even when dispatchNextUnit
|
|
796
|
+
// itself is permanently blocked.
|
|
797
|
+
const dispatchHangGuard = setTimeout(() => {
|
|
798
|
+
if (!s.active) return;
|
|
799
|
+
// dispatchNextUnit has been running for too long — it's likely hung.
|
|
800
|
+
// Start the gap watchdog which will retry dispatch from scratch.
|
|
801
|
+
if (!s.unitTimeoutHandle && !s.wrapupWarningHandle) {
|
|
1773
802
|
ctx.ui.notify(
|
|
1774
|
-
`
|
|
1775
|
-
"
|
|
1776
|
-
);
|
|
1777
|
-
|
|
1778
|
-
// Close out previous unit metrics
|
|
1779
|
-
if (s.currentUnit) {
|
|
1780
|
-
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt);
|
|
1781
|
-
}
|
|
1782
|
-
|
|
1783
|
-
// Dispatch quick-task as a new unit
|
|
1784
|
-
const qtUnitType = "quick-task";
|
|
1785
|
-
const qtUnitId = `${ s.currentMilestoneId }/${capture.id}`;
|
|
1786
|
-
const qtStartedAt = Date.now();
|
|
1787
|
-
s.currentUnit = { type: qtUnitType, id: qtUnitId, startedAt: qtStartedAt };
|
|
1788
|
-
writeUnitRuntimeRecord(s.basePath, qtUnitType, qtUnitId, qtStartedAt, {
|
|
1789
|
-
phase: "dispatched",
|
|
1790
|
-
wrapupWarningSent: false,
|
|
1791
|
-
timeoutAt: null,
|
|
1792
|
-
lastProgressAt: qtStartedAt,
|
|
1793
|
-
progressCount: 0,
|
|
1794
|
-
lastProgressKind: "dispatch",
|
|
1795
|
-
});
|
|
1796
|
-
const state = await deriveState(s.basePath);
|
|
1797
|
-
updateProgressWidget(ctx, qtUnitType, qtUnitId, state);
|
|
1798
|
-
|
|
1799
|
-
const result = await s.cmdCtx!.newSession();
|
|
1800
|
-
if (result.cancelled) {
|
|
1801
|
-
await stopAuto(ctx, pi);
|
|
1802
|
-
return;
|
|
1803
|
-
}
|
|
1804
|
-
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
1805
|
-
writeLock(lockBase(), qtUnitType, qtUnitId, s.completedUnits.length, sessionFile);
|
|
1806
|
-
|
|
1807
|
-
// Mark capture as executed now that the unit is dispatched
|
|
1808
|
-
markCaptureExecuted(s.basePath, capture.id);
|
|
1809
|
-
|
|
1810
|
-
// Start unit timeout for quick-task
|
|
1811
|
-
clearUnitTimeout();
|
|
1812
|
-
const supervisor = resolveAutoSupervisorConfig();
|
|
1813
|
-
const qtTimeoutMs = (supervisor.hard_timeout_minutes ?? 30) * 60 * 1000;
|
|
1814
|
-
s.unitTimeoutHandle = setTimeout(async () => {
|
|
1815
|
-
s.unitTimeoutHandle = null;
|
|
1816
|
-
if (!s.active) return;
|
|
1817
|
-
ctx.ui.notify(
|
|
1818
|
-
`Quick-task ${capture.id} exceeded timeout. Pausing auto-mode.`,
|
|
1819
|
-
"warning",
|
|
1820
|
-
);
|
|
1821
|
-
await pauseAuto(ctx, pi);
|
|
1822
|
-
}, qtTimeoutMs);
|
|
1823
|
-
|
|
1824
|
-
if (!s.active) return;
|
|
1825
|
-
pi.sendMessage(
|
|
1826
|
-
{ customType: "gsd-auto", content: prompt, display: s.verbose },
|
|
1827
|
-
{ triggerTurn: true },
|
|
803
|
+
`Dispatch hang detected (${DISPATCH_HANG_TIMEOUT_MS / 1000}s without completion). Starting recovery watchdog.`,
|
|
804
|
+
"warning",
|
|
1828
805
|
);
|
|
1829
|
-
|
|
1830
|
-
} catch {
|
|
1831
|
-
// Non-fatal — proceed to normal dispatch
|
|
806
|
+
startDispatchGapWatchdog(ctx, pi);
|
|
1832
807
|
}
|
|
1833
|
-
}
|
|
1834
|
-
|
|
1835
|
-
// In step mode, pause and show a wizard instead of immediately dispatching
|
|
1836
|
-
if (s.stepMode) {
|
|
1837
|
-
await showStepWizard(ctx, pi);
|
|
1838
|
-
return;
|
|
1839
|
-
}
|
|
808
|
+
}, DISPATCH_HANG_TIMEOUT_MS);
|
|
1840
809
|
|
|
1841
810
|
try {
|
|
1842
811
|
await dispatchNextUnit(ctx, pi);
|
|
1843
812
|
} catch (dispatchErr) {
|
|
1844
|
-
// dispatchNextUnit threw — without this catch the error would propagate
|
|
1845
|
-
// to the pi event emitter which may silently swallow async rejections,
|
|
1846
|
-
// leaving auto-mode s.active but permanently stalled (see #381).
|
|
1847
813
|
const message = dispatchErr instanceof Error ? dispatchErr.message : String(dispatchErr);
|
|
1848
814
|
ctx.ui.notify(
|
|
1849
815
|
`Dispatch error after unit completion: ${message}. Retrying in ${DISPATCH_GAP_TIMEOUT_MS / 1000}s.`,
|
|
1850
816
|
"error",
|
|
1851
817
|
);
|
|
1852
|
-
|
|
1853
|
-
// Start the dispatch gap watchdog to retry after a delay.
|
|
1854
|
-
// This gives transient issues (dirty working tree, branch state) time to settle.
|
|
1855
818
|
startDispatchGapWatchdog(ctx, pi);
|
|
1856
819
|
return;
|
|
820
|
+
} finally {
|
|
821
|
+
clearTimeout(dispatchHangGuard);
|
|
1857
822
|
}
|
|
1858
823
|
|
|
1859
|
-
// If dispatchNextUnit returned normally but auto-mode is still s.active and
|
|
1860
|
-
// no new unit timeout was set (meaning sendMessage was never called), start
|
|
1861
|
-
// the dispatch gap watchdog as a safety net.
|
|
1862
824
|
if (s.active && !s.unitTimeoutHandle && !s.wrapupWarningHandle) {
|
|
1863
825
|
startDispatchGapWatchdog(ctx, pi);
|
|
1864
826
|
}
|
|
1865
827
|
|
|
1866
828
|
} finally {
|
|
1867
829
|
s.handlingAgentEnd = false;
|
|
830
|
+
|
|
831
|
+
// If an agent_end event was dropped by the reentrancy guard while we were
|
|
832
|
+
// processing, re-enter handleAgentEnd on the next microtask. This prevents
|
|
833
|
+
// the summarizing phase stall (#1072) where a unit dispatched inside
|
|
834
|
+
// handleAgentEnd (hooks, triage, quick-task) completes before we return,
|
|
835
|
+
// and its agent_end is silently dropped — leaving auto-mode active but
|
|
836
|
+
// permanently stalled with no unit running and no watchdog set.
|
|
837
|
+
if (s.pendingAgentEndRetry) {
|
|
838
|
+
s.pendingAgentEndRetry = false;
|
|
839
|
+
setImmediate(() => {
|
|
840
|
+
handleAgentEnd(ctx, pi).catch((err) => {
|
|
841
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
842
|
+
ctx.ui.notify(`Deferred agent_end retry failed: ${msg}`, "error");
|
|
843
|
+
pauseAuto(ctx, pi).catch(() => {});
|
|
844
|
+
});
|
|
845
|
+
});
|
|
846
|
+
}
|
|
1868
847
|
}
|
|
1869
848
|
}
|
|
1870
849
|
|
|
@@ -1872,8 +851,6 @@ export async function handleAgentEnd(
|
|
|
1872
851
|
|
|
1873
852
|
/**
|
|
1874
853
|
* Show the step-mode wizard after a unit completes.
|
|
1875
|
-
* Derives the next unit from disk state and presents it to the user.
|
|
1876
|
-
* If the user confirms, dispatches the next unit. If not, pauses.
|
|
1877
854
|
*/
|
|
1878
855
|
async function showStepWizard(
|
|
1879
856
|
ctx: ExtensionContext,
|
|
@@ -1884,15 +861,13 @@ async function showStepWizard(
|
|
|
1884
861
|
const state = await deriveState(s.basePath);
|
|
1885
862
|
const mid = state.activeMilestone?.id;
|
|
1886
863
|
|
|
1887
|
-
// Build summary of what just completed
|
|
1888
864
|
const justFinished = s.currentUnit
|
|
1889
865
|
? `${unitVerb(s.currentUnit.type)} ${s.currentUnit.id}`
|
|
1890
866
|
: "previous unit";
|
|
1891
867
|
|
|
1892
|
-
// If no active milestone or everything is complete, stop
|
|
1893
868
|
if (!mid || state.phase === "complete") {
|
|
1894
|
-
const incomplete = state.registry.filter(m => m.status !== "complete");
|
|
1895
|
-
if (incomplete.length > 0 && state.phase !== "complete" && state.phase !== "blocked") {
|
|
869
|
+
const incomplete = (state.registry ?? []).filter(m => m.status !== "complete" && m.status !== "parked");
|
|
870
|
+
if (incomplete.length > 0 && state.phase !== "complete" && state.phase !== "blocked" && state.phase !== "pre-planning") {
|
|
1896
871
|
const ids = incomplete.map(m => m.id).join(", ");
|
|
1897
872
|
const diag = `basePath=${s.basePath}, milestones=[${state.registry.map(m => `${m.id}:${m.status}`).join(", ")}], phase=${state.phase}`;
|
|
1898
873
|
ctx.ui.notify(`Unexpected: ${incomplete.length} incomplete milestone(s) (${ids}) but no active milestone.\n Diagnostic: ${diag}`, "error");
|
|
@@ -1903,7 +878,6 @@ async function showStepWizard(
|
|
|
1903
878
|
return;
|
|
1904
879
|
}
|
|
1905
880
|
|
|
1906
|
-
// Peek at what's next by examining state
|
|
1907
881
|
const nextDesc = _describeNextUnit(state);
|
|
1908
882
|
|
|
1909
883
|
const choice = await showNextAction(s.cmdCtx, {
|
|
@@ -1941,18 +915,14 @@ async function showStepWizard(
|
|
|
1941
915
|
ctx.ui.notify("Switched to auto-mode.", "info");
|
|
1942
916
|
await dispatchNextUnit(ctx, pi);
|
|
1943
917
|
} else if (choice === "status") {
|
|
1944
|
-
// Show status then re-show the wizard
|
|
1945
918
|
const { fireStatusViaCommand } = await import("./commands.js");
|
|
1946
919
|
await fireStatusViaCommand(ctx as ExtensionCommandContext);
|
|
1947
920
|
await showStepWizard(ctx, pi);
|
|
1948
921
|
} else {
|
|
1949
|
-
// "not_yet" — pause
|
|
1950
922
|
await pauseAuto(ctx, pi);
|
|
1951
923
|
}
|
|
1952
924
|
}
|
|
1953
925
|
|
|
1954
|
-
// describeNextUnit is imported from auto-dashboard.ts and re-exported
|
|
1955
|
-
export { describeNextUnit } from "./auto-dashboard.js";
|
|
1956
926
|
|
|
1957
927
|
/** Thin wrapper: delegates to auto-dashboard.ts, passing state accessors. */
|
|
1958
928
|
function updateProgressWidget(
|
|
@@ -1978,15 +948,6 @@ const widgetStateAccessors: WidgetStateAccessors = {
|
|
|
1978
948
|
|
|
1979
949
|
// ─── Core Loop ────────────────────────────────────────────────────────────────
|
|
1980
950
|
|
|
1981
|
-
/** Tracks recursive skip depth to prevent TUI freeze on cascading completed-unit skips */
|
|
1982
|
-
|
|
1983
|
-
/** Reentrancy guard for dispatchNextUnit itself (not just handleAgentEnd).
|
|
1984
|
-
* Prevents concurrent dispatch from watchdog timers, step wizard, and direct calls
|
|
1985
|
-
* that bypass the s.handlingAgentEnd guard. Recursive calls (from skip paths) are
|
|
1986
|
-
* allowed via s.skipDepth > 0. */
|
|
1987
|
-
|
|
1988
|
-
/** Keys recently evicted by skip-loop breaker — prevents re-persistence in the fallback path (#912). */
|
|
1989
|
-
|
|
1990
951
|
async function dispatchNextUnit(
|
|
1991
952
|
ctx: ExtensionContext,
|
|
1992
953
|
pi: ExtensionAPI,
|
|
@@ -1999,44 +960,50 @@ async function dispatchNextUnit(
|
|
|
1999
960
|
return;
|
|
2000
961
|
}
|
|
2001
962
|
|
|
2002
|
-
//
|
|
2003
|
-
|
|
963
|
+
// ── Session lock validation: detect if another process has taken over ──
|
|
964
|
+
if (lockBase() && !validateSessionLock(lockBase())) {
|
|
965
|
+
debugLog("dispatchNextUnit session-lock-lost — another process may have taken over");
|
|
966
|
+
ctx.ui.notify(
|
|
967
|
+
"Session lock lost — another GSD process appears to have taken over. Stopping gracefully.",
|
|
968
|
+
"error",
|
|
969
|
+
);
|
|
970
|
+
// Don't call stopAuto here to avoid releasing the lock we don't own
|
|
971
|
+
s.active = false;
|
|
972
|
+
s.paused = false;
|
|
973
|
+
clearUnitTimeout();
|
|
974
|
+
deregisterSigtermHandler();
|
|
975
|
+
ctx.ui.setStatus("gsd-auto", undefined);
|
|
976
|
+
ctx.ui.setWidget("gsd-progress", undefined);
|
|
977
|
+
ctx.ui.setFooter(undefined);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// Reentrancy guard
|
|
2004
982
|
if (s.dispatching && s.skipDepth === 0) {
|
|
2005
983
|
debugLog("dispatchNextUnit reentrancy guard — another dispatch in progress, bailing");
|
|
2006
|
-
return;
|
|
984
|
+
return;
|
|
2007
985
|
}
|
|
2008
986
|
s.dispatching = true;
|
|
2009
987
|
try {
|
|
2010
|
-
// Recursion depth guard
|
|
2011
|
-
// crash recovery with 10+ completed units), recursive dispatchNextUnit calls
|
|
2012
|
-
// can freeze the TUI or overflow the stack. Yield generously after MAX_SKIP_DEPTH.
|
|
988
|
+
// Recursion depth guard
|
|
2013
989
|
if (s.skipDepth > MAX_SKIP_DEPTH) {
|
|
2014
990
|
s.skipDepth = 0;
|
|
2015
991
|
ctx.ui.notify(`Skipped ${MAX_SKIP_DEPTH}+ completed units. Yielding to UI before continuing.`, "info");
|
|
2016
992
|
await new Promise(r => setTimeout(r, 200));
|
|
2017
993
|
}
|
|
2018
994
|
|
|
2019
|
-
// Resource version guard
|
|
2020
|
-
// Templates are read from disk on each dispatch but extension code is loaded
|
|
2021
|
-
// once at startup. If resources were re-synced (e.g. /gsd:update, npm update,
|
|
2022
|
-
// or dev copy-resources), templates may expect variables the in-memory code
|
|
2023
|
-
// doesn't provide. Stop gracefully instead of crashing.
|
|
995
|
+
// Resource version guard
|
|
2024
996
|
const staleMsg = checkResourcesStale(s.resourceVersionOnStart);
|
|
2025
997
|
if (staleMsg) {
|
|
2026
998
|
await stopAuto(ctx, pi, staleMsg);
|
|
2027
999
|
return;
|
|
2028
1000
|
}
|
|
2029
1001
|
|
|
2030
|
-
// Clear all caches so deriveState sees fresh disk state (#431).
|
|
2031
|
-
// Parse cache is also cleared — doctor may have re-populated it with
|
|
2032
|
-
// stale data between handleAgentEnd and this dispatch call (Path B fix).
|
|
2033
1002
|
invalidateAllCaches();
|
|
2034
1003
|
s.lastPromptCharCount = undefined;
|
|
2035
1004
|
s.lastBaselineCharCount = undefined;
|
|
2036
1005
|
|
|
2037
|
-
// ── Pre-dispatch health gate
|
|
2038
|
-
// Lightweight check for critical issues that would cause the next unit
|
|
2039
|
-
// to fail or corrupt state. Auto-heals what it can, blocks on the rest.
|
|
1006
|
+
// ── Pre-dispatch health gate ──
|
|
2040
1007
|
try {
|
|
2041
1008
|
const healthGate = await preDispatchHealthGate(s.basePath);
|
|
2042
1009
|
if (healthGate.fixesApplied.length > 0) {
|
|
@@ -2048,13 +1015,10 @@ async function dispatchNextUnit(
|
|
|
2048
1015
|
return;
|
|
2049
1016
|
}
|
|
2050
1017
|
} catch {
|
|
2051
|
-
// Non-fatal
|
|
1018
|
+
// Non-fatal
|
|
2052
1019
|
}
|
|
2053
1020
|
|
|
2054
|
-
// ── Sync project root artifacts into worktree
|
|
2055
|
-
// When the LLM writes artifacts to the main repo filesystem instead of
|
|
2056
|
-
// the worktree, the worktree's gsd.db becomes stale. Sync before
|
|
2057
|
-
// deriveState to ensure the worktree has the latest artifacts.
|
|
1021
|
+
// ── Sync project root artifacts into worktree ──
|
|
2058
1022
|
if (s.originalBasePath && s.basePath !== s.originalBasePath && s.currentMilestoneId) {
|
|
2059
1023
|
syncProjectRootToWorktree(s.originalBasePath, s.basePath, s.currentMilestoneId);
|
|
2060
1024
|
}
|
|
@@ -2077,12 +1041,10 @@ async function dispatchNextUnit(
|
|
|
2077
1041
|
"info",
|
|
2078
1042
|
);
|
|
2079
1043
|
sendDesktopNotification("GSD", `Milestone ${s.currentMilestoneId} complete!`, "success", "milestone");
|
|
2080
|
-
// Hint: visualizer available after milestone transition
|
|
2081
1044
|
const vizPrefs = loadEffectiveGSDPreferences()?.preferences;
|
|
2082
1045
|
if (vizPrefs?.auto_visualize) {
|
|
2083
1046
|
ctx.ui.notify("Run /gsd visualize to see progress overview.", "info");
|
|
2084
1047
|
}
|
|
2085
|
-
// Auto-generate HTML report snapshot on milestone completion (default: on, disable with auto_report: false)
|
|
2086
1048
|
if (vizPrefs?.auto_report !== false) {
|
|
2087
1049
|
try {
|
|
2088
1050
|
const { loadVisualizerData } = await import("./visualizer-data.js");
|
|
@@ -2135,24 +1097,15 @@ async function dispatchNextUnit(
|
|
|
2135
1097
|
s.unitRecoveryCount.clear();
|
|
2136
1098
|
s.unitConsecutiveSkips.clear();
|
|
2137
1099
|
s.unitLifetimeDispatches.clear();
|
|
2138
|
-
// Clear completed-units.json for the finished milestone
|
|
2139
1100
|
try {
|
|
2140
1101
|
const file = completedKeysPath(s.basePath);
|
|
2141
1102
|
if (existsSync(file)) {
|
|
2142
|
-
|
|
2143
|
-
writeFileSync(tmpFile, JSON.stringify([]), "utf-8");
|
|
2144
|
-
renameSync(tmpFile, file);
|
|
1103
|
+
atomicWriteSync(file, JSON.stringify([]));
|
|
2145
1104
|
}
|
|
2146
1105
|
s.completedKeySet.clear();
|
|
2147
|
-
} catch {
|
|
2148
|
-
|
|
2149
|
-
// ── Worktree lifecycle on milestone transition (#616)
|
|
2150
|
-
// When transitioning from M_old to M_new inside a worktree, we must:
|
|
2151
|
-
// 1. Merge the completed milestone's worktree back to main
|
|
2152
|
-
// 2. Re-derive state from the project root
|
|
2153
|
-
// 3. Create a new worktree for the incoming milestone
|
|
2154
|
-
// Without this, M_new runs inside M_old's worktree on the wrong branch,
|
|
2155
|
-
// and artifact paths resolve against the wrong .gsd/ directory.
|
|
1106
|
+
} catch (e) { debugLog("completed-keys-reset-failed", { error: e instanceof Error ? e.message : String(e) }); }
|
|
1107
|
+
|
|
1108
|
+
// ── Worktree lifecycle on milestone transition (#616) ──
|
|
2156
1109
|
if (isInAutoWorktree(s.basePath) && s.originalBasePath && shouldUseWorktreeIsolation()) {
|
|
2157
1110
|
try {
|
|
2158
1111
|
const roadmapPath = resolveMilestoneFile(s.originalBasePath, s.currentMilestoneId, "ROADMAP");
|
|
@@ -2164,7 +1117,6 @@ async function dispatchNextUnit(
|
|
|
2164
1117
|
"info",
|
|
2165
1118
|
);
|
|
2166
1119
|
} else {
|
|
2167
|
-
// No roadmap found — teardown worktree without merge
|
|
2168
1120
|
teardownAutoWorktree(s.originalBasePath, s.currentMilestoneId);
|
|
2169
1121
|
ctx.ui.notify(`Exited worktree for ${ s.currentMilestoneId } (no roadmap for merge).`, "info");
|
|
2170
1122
|
}
|
|
@@ -2173,23 +1125,19 @@ async function dispatchNextUnit(
|
|
|
2173
1125
|
`Milestone merge failed during transition: ${err instanceof Error ? err.message : String(err)}`,
|
|
2174
1126
|
"warning",
|
|
2175
1127
|
);
|
|
2176
|
-
// Force cwd back to project root even if merge failed
|
|
2177
1128
|
if (s.originalBasePath) {
|
|
2178
1129
|
try { process.chdir(s.originalBasePath); } catch { /* best-effort */ }
|
|
2179
1130
|
}
|
|
2180
1131
|
}
|
|
2181
1132
|
|
|
2182
|
-
// Update s.basePath to project root (mergeMilestoneToMain already chdir'd)
|
|
2183
1133
|
s.basePath = s.originalBasePath;
|
|
2184
1134
|
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
2185
1135
|
invalidateAllCaches();
|
|
2186
1136
|
|
|
2187
|
-
// Re-derive state from project root before creating new worktree
|
|
2188
1137
|
state = await deriveState(s.basePath);
|
|
2189
1138
|
mid = state.activeMilestone?.id;
|
|
2190
1139
|
midTitle = state.activeMilestone?.title;
|
|
2191
1140
|
|
|
2192
|
-
// Create new worktree for the incoming milestone
|
|
2193
1141
|
if (mid) {
|
|
2194
1142
|
captureIntegrationBranch(s.basePath, mid, { commitDocs: loadEffectiveGSDPreferences()?.preferences?.git?.commit_docs });
|
|
2195
1143
|
try {
|
|
@@ -2205,15 +1153,12 @@ async function dispatchNextUnit(
|
|
|
2205
1153
|
}
|
|
2206
1154
|
}
|
|
2207
1155
|
} else {
|
|
2208
|
-
// Not in worktree — capture integration branch for the new milestone (branch mode only).
|
|
2209
|
-
// In none mode there's no milestone branch to merge back to, so skip.
|
|
2210
1156
|
if (getIsolationMode() !== "none") {
|
|
2211
1157
|
captureIntegrationBranch(s.originalBasePath || s.basePath, mid, { commitDocs: loadEffectiveGSDPreferences()?.preferences?.git?.commit_docs });
|
|
2212
1158
|
}
|
|
2213
1159
|
}
|
|
2214
1160
|
|
|
2215
|
-
|
|
2216
|
-
const pendingIds = state.registry
|
|
1161
|
+
const pendingIds = (state.registry ?? [])
|
|
2217
1162
|
.filter(m => m.status !== "complete")
|
|
2218
1163
|
.map(m => m.id);
|
|
2219
1164
|
pruneQueueOrder(s.basePath, pendingIds);
|
|
@@ -2224,24 +1169,67 @@ async function dispatchNextUnit(
|
|
|
2224
1169
|
}
|
|
2225
1170
|
|
|
2226
1171
|
if (!mid) {
|
|
2227
|
-
// Save final session before stopping
|
|
2228
1172
|
if (s.currentUnit) {
|
|
2229
1173
|
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
2230
1174
|
}
|
|
2231
1175
|
|
|
2232
|
-
const incomplete = state.registry.filter(m => m.status !== "complete");
|
|
1176
|
+
const incomplete = (state.registry ?? []).filter(m => m.status !== "complete" && m.status !== "parked");
|
|
2233
1177
|
if (incomplete.length === 0) {
|
|
2234
|
-
// Genuinely all complete
|
|
1178
|
+
// Genuinely all complete (parked milestones excluded) — merge milestone branch to main before stopping (#962)
|
|
1179
|
+
if (s.currentMilestoneId && isInAutoWorktree(s.basePath) && s.originalBasePath) {
|
|
1180
|
+
try {
|
|
1181
|
+
const roadmapPath = resolveMilestoneFile(s.originalBasePath, s.currentMilestoneId, "ROADMAP");
|
|
1182
|
+
if (roadmapPath) {
|
|
1183
|
+
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
1184
|
+
const mergeResult = mergeMilestoneToMain(s.originalBasePath, s.currentMilestoneId, roadmapContent);
|
|
1185
|
+
s.basePath = s.originalBasePath;
|
|
1186
|
+
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
1187
|
+
ctx.ui.notify(
|
|
1188
|
+
`Milestone ${ s.currentMilestoneId } merged to main.${mergeResult.pushed ? " Pushed to remote." : ""}`,
|
|
1189
|
+
"info",
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
} catch (err) {
|
|
1193
|
+
ctx.ui.notify(
|
|
1194
|
+
`Milestone merge failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1195
|
+
"warning",
|
|
1196
|
+
);
|
|
1197
|
+
if (s.originalBasePath) {
|
|
1198
|
+
s.basePath = s.originalBasePath;
|
|
1199
|
+
try { process.chdir(s.basePath); } catch { /* best-effort */ }
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
} else if (s.currentMilestoneId && !isInAutoWorktree(s.basePath) && getIsolationMode() === "branch") {
|
|
1203
|
+
try {
|
|
1204
|
+
const currentBranch = getCurrentBranch(s.basePath);
|
|
1205
|
+
const milestoneBranch = autoWorktreeBranch(s.currentMilestoneId);
|
|
1206
|
+
if (currentBranch === milestoneBranch) {
|
|
1207
|
+
const roadmapPath = resolveMilestoneFile(s.basePath, s.currentMilestoneId, "ROADMAP");
|
|
1208
|
+
if (roadmapPath) {
|
|
1209
|
+
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
1210
|
+
const mergeResult = mergeMilestoneToMain(s.basePath, s.currentMilestoneId, roadmapContent);
|
|
1211
|
+
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
1212
|
+
ctx.ui.notify(
|
|
1213
|
+
`Milestone ${ s.currentMilestoneId } merged (branch mode).${mergeResult.pushed ? " Pushed to remote." : ""}`,
|
|
1214
|
+
"info",
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
} catch (err) {
|
|
1219
|
+
ctx.ui.notify(
|
|
1220
|
+
`Milestone merge failed (branch mode): ${err instanceof Error ? err.message : String(err)}`,
|
|
1221
|
+
"warning",
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
2235
1225
|
sendDesktopNotification("GSD", "All milestones complete!", "success", "milestone");
|
|
2236
1226
|
await stopAuto(ctx, pi, "All milestones complete");
|
|
2237
1227
|
} else if (state.phase === "blocked") {
|
|
2238
|
-
// Milestones exist but are dependency-blocked
|
|
2239
1228
|
const blockerMsg = `Blocked: ${state.blockers.join(", ")}`;
|
|
2240
1229
|
await stopAuto(ctx, pi, blockerMsg);
|
|
2241
1230
|
ctx.ui.notify(`${blockerMsg}. Fix and run /gsd auto.`, "warning");
|
|
2242
1231
|
sendDesktopNotification("GSD", blockerMsg, "error", "attention");
|
|
2243
1232
|
} else {
|
|
2244
|
-
// Milestones with remaining work exist but none became s.active — unexpected
|
|
2245
1233
|
const ids = incomplete.map(m => m.id).join(", ");
|
|
2246
1234
|
const diag = `basePath=${s.basePath}, milestones=[${state.registry.map(m => `${m.id}:${m.status}`).join(", ")}], phase=${state.phase}`;
|
|
2247
1235
|
ctx.ui.notify(`Unexpected: ${incomplete.length} incomplete milestone(s) (${ids}) but no active milestone.\n Diagnostic: ${diag}`, "error");
|
|
@@ -2250,15 +1238,12 @@ async function dispatchNextUnit(
|
|
|
2250
1238
|
return;
|
|
2251
1239
|
}
|
|
2252
1240
|
|
|
2253
|
-
// Guard: mid/midTitle must be defined strings from this point onward.
|
|
2254
|
-
// The !mid check above returns early if mid is falsy; midTitle comes from
|
|
2255
|
-
// the same object so it should always be present when mid is.
|
|
2256
1241
|
if (!midTitle) {
|
|
2257
|
-
midTitle = mid;
|
|
1242
|
+
midTitle = mid;
|
|
2258
1243
|
ctx.ui.notify(`Milestone ${mid} has no title in roadmap — using ID as fallback.`, "warning");
|
|
2259
1244
|
}
|
|
2260
1245
|
|
|
2261
|
-
// ── Mid-merge safety check
|
|
1246
|
+
// ── Mid-merge safety check ──
|
|
2262
1247
|
if (reconcileMergeState(s.basePath, ctx)) {
|
|
2263
1248
|
invalidateAllCaches();
|
|
2264
1249
|
state = await deriveState(s.basePath);
|
|
@@ -2266,7 +1251,6 @@ async function dispatchNextUnit(
|
|
|
2266
1251
|
midTitle = state.activeMilestone?.title;
|
|
2267
1252
|
}
|
|
2268
1253
|
|
|
2269
|
-
// After merge guard removal (branchless architecture), mid/midTitle could be undefined
|
|
2270
1254
|
if (!mid || !midTitle) {
|
|
2271
1255
|
if (s.currentUnit) {
|
|
2272
1256
|
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
@@ -2287,21 +1271,18 @@ async function dispatchNextUnit(
|
|
|
2287
1271
|
if (s.currentUnit) {
|
|
2288
1272
|
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
2289
1273
|
}
|
|
2290
|
-
// Clear completed-units.json for the finished milestone so it doesn't grow unbounded.
|
|
2291
1274
|
try {
|
|
2292
1275
|
const file = completedKeysPath(s.basePath);
|
|
2293
1276
|
if (existsSync(file)) {
|
|
2294
|
-
|
|
2295
|
-
writeFileSync(tmpFile, JSON.stringify([]), "utf-8");
|
|
2296
|
-
renameSync(tmpFile, file);
|
|
1277
|
+
atomicWriteSync(file, JSON.stringify([]));
|
|
2297
1278
|
}
|
|
2298
1279
|
s.completedKeySet.clear();
|
|
2299
|
-
} catch {
|
|
2300
|
-
// ── Milestone merge
|
|
1280
|
+
} catch (e) { debugLog("completed-keys-reset-failed", { error: e instanceof Error ? e.message : String(e) }); }
|
|
1281
|
+
// ── Milestone merge ──
|
|
2301
1282
|
if (s.currentMilestoneId && isInAutoWorktree(s.basePath) && s.originalBasePath) {
|
|
2302
1283
|
try {
|
|
2303
1284
|
const roadmapPath = resolveMilestoneFile(s.originalBasePath, s.currentMilestoneId, "ROADMAP");
|
|
2304
|
-
if (!roadmapPath) throw new
|
|
1285
|
+
if (!roadmapPath) throw new GSDError(GSD_ARTIFACT_MISSING, `Cannot resolve ROADMAP file for milestone ${ s.currentMilestoneId }`);
|
|
2305
1286
|
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
2306
1287
|
const mergeResult = mergeMilestoneToMain(s.originalBasePath, s.currentMilestoneId, roadmapContent);
|
|
2307
1288
|
s.basePath = s.originalBasePath;
|
|
@@ -2315,17 +1296,12 @@ async function dispatchNextUnit(
|
|
|
2315
1296
|
`Milestone merge failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2316
1297
|
"warning",
|
|
2317
1298
|
);
|
|
2318
|
-
// Ensure cwd is restored even if merge failed partway through (#608).
|
|
2319
|
-
// mergeMilestoneToMain may have chdir'd but then thrown, leaving us
|
|
2320
|
-
// in an indeterminate location.
|
|
2321
1299
|
if (s.originalBasePath) {
|
|
2322
1300
|
s.basePath = s.originalBasePath;
|
|
2323
1301
|
try { process.chdir(s.basePath); } catch { /* best-effort */ }
|
|
2324
1302
|
}
|
|
2325
1303
|
}
|
|
2326
|
-
} else if (s.currentMilestoneId && !isInAutoWorktree(s.basePath) && getIsolationMode()
|
|
2327
|
-
// Branch isolation mode (#603): no worktree, but we may be on a milestone/* branch.
|
|
2328
|
-
// Squash-merge back to the integration branch (or main) before stopping.
|
|
1304
|
+
} else if (s.currentMilestoneId && !isInAutoWorktree(s.basePath) && getIsolationMode() === "branch") {
|
|
2329
1305
|
try {
|
|
2330
1306
|
const currentBranch = getCurrentBranch(s.basePath);
|
|
2331
1307
|
const milestoneBranch = autoWorktreeBranch(s.currentMilestoneId);
|
|
@@ -2333,8 +1309,6 @@ async function dispatchNextUnit(
|
|
|
2333
1309
|
const roadmapPath = resolveMilestoneFile(s.basePath, s.currentMilestoneId, "ROADMAP");
|
|
2334
1310
|
if (roadmapPath) {
|
|
2335
1311
|
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
2336
|
-
// mergeMilestoneToMain handles: auto-commit, checkout integration branch,
|
|
2337
|
-
// squash merge, commit, optional push, branch deletion.
|
|
2338
1312
|
const mergeResult = mergeMilestoneToMain(s.basePath, s.currentMilestoneId, roadmapContent);
|
|
2339
1313
|
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
2340
1314
|
ctx.ui.notify(
|
|
@@ -2366,11 +1340,9 @@ async function dispatchNextUnit(
|
|
|
2366
1340
|
return;
|
|
2367
1341
|
}
|
|
2368
1342
|
|
|
2369
|
-
//
|
|
2370
|
-
// Ensures the UAT file and slice summary are both on main when UAT runs.
|
|
1343
|
+
// Budget ceiling guard, context window guard, secrets gate, dispatch table
|
|
2371
1344
|
const prefs = loadEffectiveGSDPreferences()?.preferences;
|
|
2372
1345
|
|
|
2373
|
-
// Budget ceiling guard — enforce budget with configurable action
|
|
2374
1346
|
const budgetCeiling = prefs?.budget_ceiling;
|
|
2375
1347
|
if (budgetCeiling !== undefined && budgetCeiling > 0) {
|
|
2376
1348
|
const currentLedger = getLedger();
|
|
@@ -2417,8 +1389,7 @@ async function dispatchNextUnit(
|
|
|
2417
1389
|
s.lastBudgetAlertLevel = 0;
|
|
2418
1390
|
}
|
|
2419
1391
|
|
|
2420
|
-
|
|
2421
|
-
const contextThreshold = prefs?.context_pause_threshold ?? 0; // 0 = disabled by default
|
|
1392
|
+
const contextThreshold = prefs?.context_pause_threshold ?? 0;
|
|
2422
1393
|
if (contextThreshold > 0 && s.cmdCtx) {
|
|
2423
1394
|
const contextUsage = s.cmdCtx.getContextUsage();
|
|
2424
1395
|
if (contextUsage && contextUsage.percent !== null && contextUsage.percent >= contextThreshold) {
|
|
@@ -2430,11 +1401,7 @@ async function dispatchNextUnit(
|
|
|
2430
1401
|
}
|
|
2431
1402
|
}
|
|
2432
1403
|
|
|
2433
|
-
//
|
|
2434
|
-
// plan-milestone writes the milestone SECRETS file (e.g., M001-SECRETS.md) during its unit. By the time we
|
|
2435
|
-
// reach the next dispatchNextUnit call the manifest exists but hasn't been
|
|
2436
|
-
// presented to the user yet. Without this re-check the model would proceed
|
|
2437
|
-
// into plan-slice / execute-task with no real credentials and mock everything.
|
|
1404
|
+
// Secrets re-check gate
|
|
2438
1405
|
const runSecretsGate = async () => {
|
|
2439
1406
|
try {
|
|
2440
1407
|
const manifestStatus = await getManifestStatus(s.basePath, mid);
|
|
@@ -2459,7 +1426,24 @@ async function dispatchNextUnit(
|
|
|
2459
1426
|
|
|
2460
1427
|
await runSecretsGate();
|
|
2461
1428
|
|
|
2462
|
-
// ──
|
|
1429
|
+
// ── Interactive discussion gate ──
|
|
1430
|
+
// If the active milestone needs discussion (has CONTEXT-DRAFT.md but no roadmap),
|
|
1431
|
+
// stop auto-mode and route to the interactive discussion flow. The guided-flow
|
|
1432
|
+
// handles needs-discussion correctly — it just needs to be called instead of
|
|
1433
|
+
// letting the dispatch table fire "needs-discussion → stop" (#1170).
|
|
1434
|
+
if (state.phase === "needs-discussion") {
|
|
1435
|
+
if (s.currentUnit) {
|
|
1436
|
+
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
1437
|
+
}
|
|
1438
|
+
const cmdCtx = s.cmdCtx!;
|
|
1439
|
+
const basePath = s.basePath;
|
|
1440
|
+
await stopAuto(ctx, pi, `${mid}: ${midTitle} needs discussion before planning.`);
|
|
1441
|
+
const { showSmartEntry } = await import("./guided-flow.js");
|
|
1442
|
+
await showSmartEntry(cmdCtx, pi, basePath);
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
// ── Dispatch table ──
|
|
2463
1447
|
const dispatchResult = await resolveDispatch({ basePath: s.basePath, mid, midTitle: midTitle!, state, prefs,
|
|
2464
1448
|
});
|
|
2465
1449
|
|
|
@@ -2472,7 +1456,6 @@ async function dispatchNextUnit(
|
|
|
2472
1456
|
}
|
|
2473
1457
|
|
|
2474
1458
|
if (dispatchResult.action !== "dispatch") {
|
|
2475
|
-
// skip action — yield and re-dispatch
|
|
2476
1459
|
await new Promise(r => setImmediate(r));
|
|
2477
1460
|
await dispatchNextUnit(ctx, pi);
|
|
2478
1461
|
return;
|
|
@@ -2483,7 +1466,7 @@ async function dispatchNextUnit(
|
|
|
2483
1466
|
prompt = dispatchResult.prompt;
|
|
2484
1467
|
let pauseAfterUatDispatch = dispatchResult.pauseAfterDispatch ?? false;
|
|
2485
1468
|
|
|
2486
|
-
// ── Pre-dispatch hooks
|
|
1469
|
+
// ── Pre-dispatch hooks ──
|
|
2487
1470
|
const preDispatchResult = runPreDispatchHooks(unitType, unitId, prompt, s.basePath);
|
|
2488
1471
|
if (preDispatchResult.firedHooks.length > 0) {
|
|
2489
1472
|
ctx.ui.notify(
|
|
@@ -2493,7 +1476,6 @@ async function dispatchNextUnit(
|
|
|
2493
1476
|
}
|
|
2494
1477
|
if (preDispatchResult.action === "skip") {
|
|
2495
1478
|
ctx.ui.notify(`Skipping ${unitType} ${unitId} (pre-dispatch hook).`, "info");
|
|
2496
|
-
// Yield then re-dispatch to advance to next unit
|
|
2497
1479
|
await new Promise(r => setImmediate(r));
|
|
2498
1480
|
await dispatchNextUnit(ctx, pi);
|
|
2499
1481
|
return;
|
|
@@ -2513,378 +1495,76 @@ async function dispatchNextUnit(
|
|
|
2513
1495
|
|
|
2514
1496
|
const observabilityIssues = await _collectObservabilityWarnings(ctx, s.basePath, unitType, unitId);
|
|
2515
1497
|
|
|
2516
|
-
// Idempotency
|
|
2517
|
-
const
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
// key so the next dispatch forces full reconciliation instead of looping.
|
|
2525
|
-
const skipCount = (s.unitConsecutiveSkips.get(idempotencyKey) ?? 0) + 1;
|
|
2526
|
-
s.unitConsecutiveSkips.set(idempotencyKey, skipCount);
|
|
2527
|
-
if (skipCount > MAX_CONSECUTIVE_SKIPS) {
|
|
2528
|
-
// Cross-check: verify deriveState actually returns this unit (#790).
|
|
2529
|
-
// If the unit's milestone is already complete, this is a phantom skip
|
|
2530
|
-
// loop from stale crash recovery context — don't evict.
|
|
2531
|
-
const skippedMid = unitId.split("/")[0];
|
|
2532
|
-
const skippedMilestoneComplete = skippedMid
|
|
2533
|
-
? !!resolveMilestoneFile(s.basePath, skippedMid, "SUMMARY")
|
|
2534
|
-
: false;
|
|
2535
|
-
if (skippedMilestoneComplete) {
|
|
2536
|
-
// Milestone is complete — evicting this key would fight self-heal.
|
|
2537
|
-
// Clear skip counter and re-dispatch from fresh state.
|
|
2538
|
-
s.unitConsecutiveSkips.delete(idempotencyKey);
|
|
2539
|
-
invalidateAllCaches();
|
|
2540
|
-
ctx.ui.notify(
|
|
2541
|
-
`Phantom skip loop cleared: ${unitType} ${unitId} belongs to completed milestone ${skippedMid}. Re-dispatching from fresh state.`,
|
|
2542
|
-
"info",
|
|
2543
|
-
);
|
|
2544
|
-
s.skipDepth++;
|
|
2545
|
-
await new Promise(r => setTimeout(r, 50));
|
|
2546
|
-
await dispatchNextUnit(ctx, pi);
|
|
2547
|
-
s.skipDepth = Math.max(0, s.skipDepth - 1);
|
|
2548
|
-
return;
|
|
2549
|
-
}
|
|
2550
|
-
s.unitConsecutiveSkips.delete(idempotencyKey);
|
|
2551
|
-
s.completedKeySet.delete(idempotencyKey);
|
|
2552
|
-
s.recentlyEvictedKeys.add(idempotencyKey);
|
|
2553
|
-
removePersistedKey(s.basePath, idempotencyKey);
|
|
2554
|
-
invalidateAllCaches();
|
|
2555
|
-
ctx.ui.notify(
|
|
2556
|
-
`Skip loop detected: ${unitType} ${unitId} skipped ${skipCount} times without advancing. Evicting completion record and forcing reconciliation.`,
|
|
2557
|
-
"warning",
|
|
2558
|
-
);
|
|
2559
|
-
if (!s.active) return;
|
|
2560
|
-
s.skipDepth++;
|
|
2561
|
-
await new Promise(r => setTimeout(r, 150));
|
|
2562
|
-
await dispatchNextUnit(ctx, pi);
|
|
2563
|
-
s.skipDepth = Math.max(0, s.skipDepth - 1);
|
|
2564
|
-
return;
|
|
2565
|
-
}
|
|
2566
|
-
// Count toward lifetime cap so hard-stop fires during skip loops (#792)
|
|
2567
|
-
const lifeSkip = (s.unitLifetimeDispatches.get(idempotencyKey) ?? 0) + 1;
|
|
2568
|
-
s.unitLifetimeDispatches.set(idempotencyKey, lifeSkip);
|
|
2569
|
-
if (lifeSkip > MAX_LIFETIME_DISPATCHES) {
|
|
2570
|
-
await stopAuto(ctx, pi, `Hard loop: ${unitType} ${unitId} (skip cycle)`);
|
|
2571
|
-
ctx.ui.notify(
|
|
2572
|
-
`Hard loop detected: ${unitType} ${unitId} hit lifetime cap during skip cycle (${lifeSkip} iterations).`,
|
|
2573
|
-
"error",
|
|
2574
|
-
);
|
|
2575
|
-
return;
|
|
2576
|
-
}
|
|
2577
|
-
ctx.ui.notify(
|
|
2578
|
-
`Skipping ${unitType} ${unitId} — already completed in a prior session. Advancing.`,
|
|
2579
|
-
"info",
|
|
2580
|
-
);
|
|
2581
|
-
if (!s.active) return;
|
|
2582
|
-
s.skipDepth++;
|
|
2583
|
-
await new Promise(r => setTimeout(r, 150));
|
|
2584
|
-
await dispatchNextUnit(ctx, pi);
|
|
2585
|
-
s.skipDepth = Math.max(0, s.skipDepth - 1);
|
|
2586
|
-
return;
|
|
2587
|
-
} else {
|
|
2588
|
-
// Stale completion record — artifact missing. Remove and re-run.
|
|
2589
|
-
s.completedKeySet.delete(idempotencyKey);
|
|
2590
|
-
removePersistedKey(s.basePath, idempotencyKey);
|
|
2591
|
-
ctx.ui.notify(
|
|
2592
|
-
`Re-running ${unitType} ${unitId} — marked complete but expected artifact missing.`,
|
|
2593
|
-
"warning",
|
|
2594
|
-
);
|
|
2595
|
-
}
|
|
2596
|
-
}
|
|
1498
|
+
// ── Idempotency check (delegated to auto-idempotency.ts) ──
|
|
1499
|
+
const idempotencyResult = checkIdempotency({
|
|
1500
|
+
s,
|
|
1501
|
+
unitType,
|
|
1502
|
+
unitId,
|
|
1503
|
+
basePath: s.basePath,
|
|
1504
|
+
notify: (msg, level) => ctx.ui.notify(msg, level),
|
|
1505
|
+
});
|
|
2597
1506
|
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
// Persist it now and skip re-dispatch. This prevents infinite loops where a task
|
|
2601
|
-
// completes successfully but the completion key was never written.
|
|
2602
|
-
//
|
|
2603
|
-
// EXCEPTION: if the key was just evicted by the skip-loop breaker above, do NOT
|
|
2604
|
-
// re-persist — that would recreate the exact loop the breaker was trying to break (#912).
|
|
2605
|
-
if (verifyExpectedArtifact(unitType, unitId, s.basePath) && !s.recentlyEvictedKeys.has(idempotencyKey)) {
|
|
2606
|
-
persistCompletedKey(s.basePath, idempotencyKey);
|
|
2607
|
-
s.completedKeySet.add(idempotencyKey);
|
|
2608
|
-
invalidateAllCaches();
|
|
2609
|
-
// Same consecutive-skip guard as the idempotency path above.
|
|
2610
|
-
const skipCount2 = (s.unitConsecutiveSkips.get(idempotencyKey) ?? 0) + 1;
|
|
2611
|
-
s.unitConsecutiveSkips.set(idempotencyKey, skipCount2);
|
|
2612
|
-
if (skipCount2 > MAX_CONSECUTIVE_SKIPS) {
|
|
2613
|
-
// Cross-check: verify the unit's milestone is still active (#790).
|
|
2614
|
-
const skippedMid2 = unitId.split("/")[0];
|
|
2615
|
-
const skippedMilestoneComplete2 = skippedMid2
|
|
2616
|
-
? !!resolveMilestoneFile(s.basePath, skippedMid2, "SUMMARY")
|
|
2617
|
-
: false;
|
|
2618
|
-
if (skippedMilestoneComplete2) {
|
|
2619
|
-
s.unitConsecutiveSkips.delete(idempotencyKey);
|
|
2620
|
-
invalidateAllCaches();
|
|
2621
|
-
ctx.ui.notify(
|
|
2622
|
-
`Phantom skip loop cleared: ${unitType} ${unitId} belongs to completed milestone ${skippedMid2}. Re-dispatching from fresh state.`,
|
|
2623
|
-
"info",
|
|
2624
|
-
);
|
|
2625
|
-
s.skipDepth++;
|
|
2626
|
-
await new Promise(r => setTimeout(r, 50));
|
|
2627
|
-
await dispatchNextUnit(ctx, pi);
|
|
2628
|
-
s.skipDepth = Math.max(0, s.skipDepth - 1);
|
|
2629
|
-
return;
|
|
2630
|
-
}
|
|
2631
|
-
s.unitConsecutiveSkips.delete(idempotencyKey);
|
|
2632
|
-
s.completedKeySet.delete(idempotencyKey);
|
|
2633
|
-
removePersistedKey(s.basePath, idempotencyKey);
|
|
2634
|
-
invalidateAllCaches();
|
|
2635
|
-
ctx.ui.notify(
|
|
2636
|
-
`Skip loop detected: ${unitType} ${unitId} skipped ${skipCount2} times without advancing. Evicting completion record and forcing reconciliation.`,
|
|
2637
|
-
"warning",
|
|
2638
|
-
);
|
|
1507
|
+
if (idempotencyResult.action === "skip") {
|
|
1508
|
+
if (idempotencyResult.reason === "completed" || idempotencyResult.reason === "fallback-persisted" || idempotencyResult.reason === "phantom-loop-cleared" || idempotencyResult.reason === "evicted") {
|
|
2639
1509
|
if (!s.active) return;
|
|
2640
1510
|
s.skipDepth++;
|
|
2641
|
-
await new Promise(r => setTimeout(r, 150));
|
|
1511
|
+
await new Promise(r => setTimeout(r, idempotencyResult.reason === "phantom-loop-cleared" ? 50 : 150));
|
|
2642
1512
|
await dispatchNextUnit(ctx, pi);
|
|
2643
1513
|
s.skipDepth = Math.max(0, s.skipDepth - 1);
|
|
2644
1514
|
return;
|
|
2645
1515
|
}
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
s.unitLifetimeDispatches.set(idempotencyKey, lifeSkip2);
|
|
2649
|
-
if (lifeSkip2 > MAX_LIFETIME_DISPATCHES) {
|
|
2650
|
-
await stopAuto(ctx, pi, `Hard loop: ${unitType} ${unitId} (skip cycle)`);
|
|
2651
|
-
ctx.ui.notify(
|
|
2652
|
-
`Hard loop detected: ${unitType} ${unitId} hit lifetime cap during skip cycle (${lifeSkip2} iterations).`,
|
|
2653
|
-
"error",
|
|
2654
|
-
);
|
|
2655
|
-
return;
|
|
2656
|
-
}
|
|
2657
|
-
ctx.ui.notify(
|
|
2658
|
-
`Skipping ${unitType} ${unitId} — artifact exists but completion key was missing. Repaired and advancing.`,
|
|
2659
|
-
"info",
|
|
2660
|
-
);
|
|
2661
|
-
if (!s.active) return;
|
|
2662
|
-
s.skipDepth++;
|
|
2663
|
-
await new Promise(r => setTimeout(r, 150));
|
|
2664
|
-
await dispatchNextUnit(ctx, pi);
|
|
2665
|
-
s.skipDepth = Math.max(0, s.skipDepth - 1);
|
|
2666
|
-
return;
|
|
2667
|
-
}
|
|
2668
|
-
|
|
2669
|
-
// Stuck detection — tracks total dispatches per unit (not just consecutive repeats).
|
|
2670
|
-
// Pattern A→B→A→B would reset retryCount every time; this map catches it.
|
|
2671
|
-
const dispatchKey = `${unitType}/${unitId}`;
|
|
2672
|
-
const prevCount = s.unitDispatchCount.get(dispatchKey) ?? 0;
|
|
2673
|
-
// Real dispatch reached — clear the consecutive-skip counter for this unit.
|
|
2674
|
-
s.unitConsecutiveSkips.delete(dispatchKey);
|
|
2675
|
-
|
|
2676
|
-
debugLog("dispatch-unit", {
|
|
2677
|
-
type: unitType,
|
|
2678
|
-
id: unitId,
|
|
2679
|
-
cycle: prevCount + 1,
|
|
2680
|
-
lifetime: (s.unitLifetimeDispatches.get(dispatchKey) ?? 0) + 1,
|
|
2681
|
-
});
|
|
2682
|
-
debugCount("dispatches");
|
|
2683
|
-
|
|
2684
|
-
// Hard lifetime cap — survives counter resets from loop-recovery/self-repair.
|
|
2685
|
-
// Catches the case where reconciliation "succeeds" (artifacts exist) but
|
|
2686
|
-
// deriveState keeps returning the same unit, creating an infinite cycle.
|
|
2687
|
-
const lifetimeCount = (s.unitLifetimeDispatches.get(dispatchKey) ?? 0) + 1;
|
|
2688
|
-
s.unitLifetimeDispatches.set(dispatchKey, lifetimeCount);
|
|
2689
|
-
if (lifetimeCount > MAX_LIFETIME_DISPATCHES) {
|
|
2690
|
-
if (s.currentUnit) {
|
|
2691
|
-
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
2692
|
-
} else {
|
|
2693
|
-
saveActivityLog(ctx, s.basePath, unitType, unitId);
|
|
2694
|
-
}
|
|
2695
|
-
const expected = diagnoseExpectedArtifact(unitType, unitId, s.basePath);
|
|
2696
|
-
await stopAuto(ctx, pi, `Hard loop: ${unitType} ${unitId}`);
|
|
1516
|
+
} else if (idempotencyResult.action === "stop") {
|
|
1517
|
+
await stopAuto(ctx, pi, idempotencyResult.reason);
|
|
2697
1518
|
ctx.ui.notify(
|
|
2698
|
-
`Hard loop detected: ${unitType} ${unitId}
|
|
1519
|
+
`Hard loop detected: ${unitType} ${unitId} hit lifetime cap during skip cycle.`,
|
|
2699
1520
|
"error",
|
|
2700
1521
|
);
|
|
2701
1522
|
return;
|
|
2702
1523
|
}
|
|
2703
|
-
|
|
2704
|
-
if (s.currentUnit) {
|
|
2705
|
-
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
2706
|
-
} else {
|
|
2707
|
-
saveActivityLog(ctx, s.basePath, unitType, unitId);
|
|
2708
|
-
}
|
|
2709
|
-
|
|
2710
|
-
// Final reconciliation pass for execute-task: write any missing durable
|
|
2711
|
-
// artifacts (summary placeholder + [x] checkbox) so the pipeline can
|
|
2712
|
-
// advance instead of stopping. This is the last resort before halting.
|
|
2713
|
-
if (unitType === "execute-task") {
|
|
2714
|
-
const [mid, sid, tid] = unitId.split("/");
|
|
2715
|
-
if (mid && sid && tid) {
|
|
2716
|
-
const status = await inspectExecuteTaskDurability(s.basePath, unitId);
|
|
2717
|
-
if (status) {
|
|
2718
|
-
const reconciled = skipExecuteTask(s.basePath, mid, sid, tid, status, "loop-recovery", prevCount);
|
|
2719
|
-
// reconciled: skipExecuteTask attempted to write missing artifacts.
|
|
2720
|
-
// verifyExpectedArtifact: confirms physical artifacts (summary + [x]) now exist on disk.
|
|
2721
|
-
// Both must pass before we clear the dispatch counter and advance.
|
|
2722
|
-
if (reconciled && verifyExpectedArtifact(unitType, unitId, s.basePath)) {
|
|
2723
|
-
ctx.ui.notify(
|
|
2724
|
-
`Loop recovery: ${unitId} reconciled after ${prevCount + 1} dispatches — blocker artifacts written, pipeline advancing.\n Review ${status.summaryPath} and replace the placeholder with real work.`,
|
|
2725
|
-
"warning",
|
|
2726
|
-
);
|
|
2727
|
-
// Persist completion so idempotency check prevents re-dispatch
|
|
2728
|
-
// if deriveState keeps returning this unit (#462).
|
|
2729
|
-
const reconciledKey = `${unitType}/${unitId}`;
|
|
2730
|
-
persistCompletedKey(s.basePath, reconciledKey);
|
|
2731
|
-
s.completedKeySet.add(reconciledKey);
|
|
2732
|
-
s.unitDispatchCount.delete(dispatchKey);
|
|
2733
|
-
invalidateAllCaches();
|
|
2734
|
-
await new Promise(r => setImmediate(r));
|
|
2735
|
-
await dispatchNextUnit(ctx, pi);
|
|
2736
|
-
return;
|
|
2737
|
-
}
|
|
2738
|
-
}
|
|
2739
|
-
}
|
|
2740
|
-
}
|
|
1524
|
+
// "rerun" and "proceed" fall through to stuck detection
|
|
2741
1525
|
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
ctx.ui.notify(
|
|
2752
|
-
`Loop recovery: ${unitType} ${unitId} — artifact verified after ${prevCount + 1} dispatches. Advancing.`,
|
|
2753
|
-
"info",
|
|
2754
|
-
);
|
|
2755
|
-
// Persist completion so the idempotency check prevents re-dispatch
|
|
2756
|
-
// if deriveState keeps returning this unit (see #462).
|
|
2757
|
-
persistCompletedKey(s.basePath, dispatchKey);
|
|
2758
|
-
s.completedKeySet.add(dispatchKey);
|
|
2759
|
-
s.unitDispatchCount.delete(dispatchKey);
|
|
2760
|
-
invalidateAllCaches();
|
|
2761
|
-
await new Promise(r => setImmediate(r));
|
|
2762
|
-
await dispatchNextUnit(ctx, pi);
|
|
2763
|
-
return;
|
|
2764
|
-
}
|
|
1526
|
+
// ── Stuck detection (delegated to auto-stuck-detection.ts) ──
|
|
1527
|
+
const stuckResult = await checkStuckAndRecover({
|
|
1528
|
+
s,
|
|
1529
|
+
ctx,
|
|
1530
|
+
unitType,
|
|
1531
|
+
unitId,
|
|
1532
|
+
basePath: s.basePath,
|
|
1533
|
+
buildSnapshotOpts: () => buildSnapshotOpts(unitType, unitId),
|
|
1534
|
+
});
|
|
2765
1535
|
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
try {
|
|
2771
|
-
const mPath = resolveMilestonePath(s.basePath, unitId);
|
|
2772
|
-
if (mPath) {
|
|
2773
|
-
const stubPath = join(mPath, `${unitId}-SUMMARY.md`);
|
|
2774
|
-
if (!existsSync(stubPath)) {
|
|
2775
|
-
writeFileSync(stubPath, `# ${unitId} Summary\n\nAuto-generated stub — milestone tasks completed but summary generation failed after ${prevCount + 1} attempts.\nReview and replace this stub with a proper summary.\n`);
|
|
2776
|
-
ctx.ui.notify(`Generated stub summary for ${unitId} to unblock pipeline. Review later.`, "warning");
|
|
2777
|
-
persistCompletedKey(s.basePath, dispatchKey);
|
|
2778
|
-
s.completedKeySet.add(dispatchKey);
|
|
2779
|
-
s.unitDispatchCount.delete(dispatchKey);
|
|
2780
|
-
invalidateAllCaches();
|
|
2781
|
-
await new Promise(r => setImmediate(r));
|
|
2782
|
-
await dispatchNextUnit(ctx, pi);
|
|
2783
|
-
return;
|
|
2784
|
-
}
|
|
2785
|
-
}
|
|
2786
|
-
} catch { /* non-fatal — fall through to normal stop */ }
|
|
1536
|
+
if (stuckResult.action === "stop") {
|
|
1537
|
+
await stopAuto(ctx, pi, stuckResult.reason);
|
|
1538
|
+
if (stuckResult.notifyMessage) {
|
|
1539
|
+
ctx.ui.notify(stuckResult.notifyMessage, "error");
|
|
2787
1540
|
}
|
|
2788
|
-
|
|
2789
|
-
const expected = diagnoseExpectedArtifact(unitType, unitId, s.basePath);
|
|
2790
|
-
const remediation = buildLoopRemediationSteps(unitType, unitId, s.basePath);
|
|
2791
|
-
await stopAuto(ctx, pi, `Loop: ${unitType} ${unitId}`);
|
|
2792
|
-
sendDesktopNotification("GSD", `Loop detected: ${unitType} ${unitId}`, "error", "error");
|
|
2793
|
-
ctx.ui.notify(
|
|
2794
|
-
`Loop detected: ${unitType} ${unitId} dispatched ${prevCount + 1} times total. Expected artifact not found.${expected ? `\n Expected: ${expected}` : ""}${remediation ? `\n\n Remediation steps:\n${remediation}` : "\n Check branch state and .gsd/ artifacts."}`,
|
|
2795
|
-
"error",
|
|
2796
|
-
);
|
|
2797
1541
|
return;
|
|
2798
1542
|
}
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
const status = await inspectExecuteTaskDurability(s.basePath, unitId);
|
|
2804
|
-
const [mid, sid, tid] = unitId.split("/");
|
|
2805
|
-
if (status && mid && sid && tid) {
|
|
2806
|
-
if (status.summaryExists && !status.taskChecked) {
|
|
2807
|
-
// Retry 1+: summary exists but checkbox not marked — mark [x] and advance.
|
|
2808
|
-
const repaired = skipExecuteTask(s.basePath, mid, sid, tid, status, "self-repair", 0);
|
|
2809
|
-
// repaired: skipExecuteTask updated metadata (returned early-true even if regex missed).
|
|
2810
|
-
// verifyExpectedArtifact: confirms the physical artifact (summary + [x]) now exists.
|
|
2811
|
-
if (repaired && verifyExpectedArtifact(unitType, unitId, s.basePath)) {
|
|
2812
|
-
ctx.ui.notify(
|
|
2813
|
-
`Self-repaired ${unitId}: summary existed but checkbox was unmarked. Marked [x] and advancing.`,
|
|
2814
|
-
"warning",
|
|
2815
|
-
);
|
|
2816
|
-
// Persist completion so idempotency check prevents re-dispatch (#462).
|
|
2817
|
-
const repairedKey = `${unitType}/${unitId}`;
|
|
2818
|
-
persistCompletedKey(s.basePath, repairedKey);
|
|
2819
|
-
s.completedKeySet.add(repairedKey);
|
|
2820
|
-
s.unitDispatchCount.delete(dispatchKey);
|
|
2821
|
-
invalidateAllCaches();
|
|
2822
|
-
await new Promise(r => setImmediate(r));
|
|
2823
|
-
await dispatchNextUnit(ctx, pi);
|
|
2824
|
-
return;
|
|
2825
|
-
}
|
|
2826
|
-
} else if (prevCount >= STUB_RECOVERY_THRESHOLD && !status.summaryExists) {
|
|
2827
|
-
// Retry STUB_RECOVERY_THRESHOLD+: summary still missing after multiple attempts.
|
|
2828
|
-
// Write a minimal stub summary so the next agent session has a recovery artifact
|
|
2829
|
-
// to overwrite, rather than starting from scratch again.
|
|
2830
|
-
const tasksDir = resolveTasksDir(s.basePath, mid, sid);
|
|
2831
|
-
const sDir = resolveSlicePath(s.basePath, mid, sid);
|
|
2832
|
-
const targetDir = tasksDir ?? (sDir ? join(sDir, "tasks") : null);
|
|
2833
|
-
if (targetDir) {
|
|
2834
|
-
if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });
|
|
2835
|
-
const summaryPath = join(targetDir, buildTaskFileName(tid, "SUMMARY"));
|
|
2836
|
-
if (!existsSync(summaryPath)) {
|
|
2837
|
-
const stubContent = [
|
|
2838
|
-
`# PARTIAL RECOVERY — attempt ${prevCount + 1} of ${MAX_UNIT_DISPATCHES}`,
|
|
2839
|
-
``,
|
|
2840
|
-
`Task \`${tid}\` in slice \`${sid}\` (milestone \`${mid}\`) has not yet produced a real summary.`,
|
|
2841
|
-
`This placeholder was written by auto-mode after ${prevCount} dispatch attempts.`,
|
|
2842
|
-
``,
|
|
2843
|
-
`The next agent session will retry this task. Replace this file with real work when done.`,
|
|
2844
|
-
].join("\n");
|
|
2845
|
-
writeFileSync(summaryPath, stubContent, "utf-8");
|
|
2846
|
-
ctx.ui.notify(
|
|
2847
|
-
`Stub recovery (attempt ${prevCount + 1}/${MAX_UNIT_DISPATCHES}): ${unitId} stub summary placeholder written. Retrying with recovery context.`,
|
|
2848
|
-
"warning",
|
|
2849
|
-
);
|
|
2850
|
-
}
|
|
2851
|
-
}
|
|
2852
|
-
}
|
|
2853
|
-
}
|
|
2854
|
-
}
|
|
2855
|
-
ctx.ui.notify(
|
|
2856
|
-
`${unitType} ${unitId} didn't produce expected artifact. Retrying (${prevCount + 1}/${MAX_UNIT_DISPATCHES}).`,
|
|
2857
|
-
"warning",
|
|
2858
|
-
);
|
|
1543
|
+
if (stuckResult.action === "recovered" && stuckResult.dispatchAgain) {
|
|
1544
|
+
await new Promise(r => setImmediate(r));
|
|
1545
|
+
await dispatchNextUnit(ctx, pi);
|
|
1546
|
+
return;
|
|
2859
1547
|
}
|
|
1548
|
+
|
|
2860
1549
|
// Snapshot metrics + activity log for the PREVIOUS unit before we reassign.
|
|
2861
|
-
// The session still holds the previous unit's data (newSession hasn't fired yet).
|
|
2862
1550
|
if (s.currentUnit) {
|
|
2863
1551
|
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
2864
1552
|
|
|
2865
|
-
// Record routing outcome for adaptive learning
|
|
2866
1553
|
if (s.currentUnitRouting) {
|
|
2867
1554
|
const isRetry = s.currentUnit.type === unitType && s.currentUnit.id === unitId;
|
|
2868
1555
|
recordOutcome(
|
|
2869
1556
|
s.currentUnit.type,
|
|
2870
1557
|
s.currentUnitRouting.tier as "light" | "standard" | "heavy",
|
|
2871
|
-
!isRetry,
|
|
1558
|
+
!isRetry,
|
|
2872
1559
|
);
|
|
2873
1560
|
}
|
|
2874
1561
|
|
|
2875
|
-
// Only mark the previous unit as completed if:
|
|
2876
|
-
// 1. We're not about to re-dispatch the same unit (retry scenario)
|
|
2877
|
-
// 2. The expected artifact actually exists on disk
|
|
2878
|
-
// For hook units, skip artifact verification — hooks don't produce standard
|
|
2879
|
-
// artifacts and their runtime records were already finalized in handleAgentEnd.
|
|
2880
1562
|
const closeoutKey = `${s.currentUnit.type}/${s.currentUnit.id}`;
|
|
2881
1563
|
const incomingKey = `${unitType}/${unitId}`;
|
|
2882
1564
|
const isHookUnit = s.currentUnit.type.startsWith("hook/");
|
|
2883
1565
|
const artifactVerified = isHookUnit || verifyExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.basePath);
|
|
2884
1566
|
if (closeoutKey !== incomingKey && artifactVerified) {
|
|
2885
1567
|
if (!isHookUnit) {
|
|
2886
|
-
// Only persist completion keys for real units — hook keys are
|
|
2887
|
-
// ephemeral and should not pollute the idempotency set.
|
|
2888
1568
|
persistCompletedKey(s.basePath, closeoutKey);
|
|
2889
1569
|
s.completedKeySet.add(closeoutKey);
|
|
2890
1570
|
}
|
|
@@ -2895,7 +1575,6 @@ async function dispatchNextUnit(
|
|
|
2895
1575
|
startedAt: s.currentUnit.startedAt,
|
|
2896
1576
|
finishedAt: Date.now(),
|
|
2897
1577
|
});
|
|
2898
|
-
// Cap to last 200 entries to prevent unbounded growth (#611)
|
|
2899
1578
|
if (s.completedUnits.length > 200) {
|
|
2900
1579
|
s.completedUnits = s.completedUnits.slice(-200);
|
|
2901
1580
|
}
|
|
@@ -2905,7 +1584,7 @@ async function dispatchNextUnit(
|
|
|
2905
1584
|
}
|
|
2906
1585
|
}
|
|
2907
1586
|
s.currentUnit = { type: unitType, id: unitId, startedAt: Date.now() };
|
|
2908
|
-
captureAvailableSkills();
|
|
1587
|
+
captureAvailableSkills();
|
|
2909
1588
|
writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
|
|
2910
1589
|
phase: "dispatched",
|
|
2911
1590
|
wrapupWarningSent: false,
|
|
@@ -2920,34 +1599,42 @@ async function dispatchNextUnit(
|
|
|
2920
1599
|
if (mid) updateSliceProgressCache(s.basePath, mid, state.activeSlice?.id);
|
|
2921
1600
|
updateProgressWidget(ctx, unitType, unitId, state);
|
|
2922
1601
|
|
|
2923
|
-
// Ensure preconditions — create directories, branches, etc.
|
|
2924
|
-
// so the LLM doesn't have to get these right
|
|
2925
1602
|
ensurePreconditions(unitType, unitId, s.basePath, state);
|
|
2926
1603
|
|
|
2927
|
-
// Fresh session
|
|
2928
|
-
|
|
1604
|
+
// Fresh session — with timeout to prevent permanent hangs (#1073).
|
|
1605
|
+
// If newSession() hangs (e.g., session manager deadlock, network issue),
|
|
1606
|
+
// without this timeout the entire dispatch chain stalls permanently: no
|
|
1607
|
+
// timeouts are set, no gap watchdog fires, and auto-mode is left active
|
|
1608
|
+
// but idle until the user Ctrl+C's.
|
|
1609
|
+
let result: { cancelled: boolean };
|
|
1610
|
+
try {
|
|
1611
|
+
const sessionPromise = s.cmdCtx!.newSession();
|
|
1612
|
+
const timeoutPromise = new Promise<{ cancelled: true }>((resolve) =>
|
|
1613
|
+
setTimeout(() => resolve({ cancelled: true }), NEW_SESSION_TIMEOUT_MS),
|
|
1614
|
+
);
|
|
1615
|
+
result = await Promise.race([sessionPromise, timeoutPromise]);
|
|
1616
|
+
} catch (sessionErr) {
|
|
1617
|
+
const msg = sessionErr instanceof Error ? sessionErr.message : String(sessionErr);
|
|
1618
|
+
ctx.ui.notify(`Session creation failed: ${msg}. Retrying via watchdog.`, "error");
|
|
1619
|
+
throw new Error(`newSession() failed: ${msg}`);
|
|
1620
|
+
}
|
|
2929
1621
|
if (result.cancelled) {
|
|
2930
|
-
|
|
1622
|
+
ctx.ui.notify(
|
|
1623
|
+
`Session creation timed out or was cancelled for ${unitType} ${unitId}. Will retry.`,
|
|
1624
|
+
"warning",
|
|
1625
|
+
);
|
|
1626
|
+
await stopAuto(ctx, pi, "Session creation failed");
|
|
2931
1627
|
return;
|
|
2932
1628
|
}
|
|
2933
1629
|
|
|
2934
|
-
// Branchless architecture: all work commits sequentially on the milestone
|
|
2935
|
-
// branch — no per-slice branches or slice-level merges. Milestone merge
|
|
2936
|
-
// happens when phase === "complete" (see mergeMilestoneToMain above).
|
|
2937
|
-
|
|
2938
|
-
// Write lock AFTER newSession so we capture the session file path.
|
|
2939
|
-
// Pi appends entries incrementally via appendFileSync, so on crash the
|
|
2940
|
-
// session file survives with every tool call up to the crash point.
|
|
2941
1630
|
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
1631
|
+
updateSessionLock(lockBase(), unitType, unitId, s.completedUnits.length, sessionFile);
|
|
2942
1632
|
writeLock(lockBase(), unitType, unitId, s.completedUnits.length, sessionFile);
|
|
2943
1633
|
|
|
2944
|
-
//
|
|
2945
|
-
// On retry (stuck detection), prepend deep diagnostic from last attempt
|
|
2946
|
-
// Cap injected content to prevent unbounded prompt growth → OOM
|
|
1634
|
+
// Prompt injection
|
|
2947
1635
|
const MAX_RECOVERY_CHARS = 50_000;
|
|
2948
1636
|
let finalPrompt = prompt;
|
|
2949
1637
|
|
|
2950
|
-
// Verification retry — inject failure context so the agent can auto-fix
|
|
2951
1638
|
if (s.pendingVerificationRetry) {
|
|
2952
1639
|
const retryCtx = s.pendingVerificationRetry;
|
|
2953
1640
|
s.pendingVerificationRetry = null;
|
|
@@ -2973,14 +1660,12 @@ async function dispatchNextUnit(
|
|
|
2973
1660
|
}
|
|
2974
1661
|
}
|
|
2975
1662
|
|
|
2976
|
-
// Inject observability repair instructions so the agent fixes gaps before
|
|
2977
|
-
// proceeding with the unit (see #174).
|
|
2978
1663
|
const repairBlock = buildObservabilityRepairBlock(observabilityIssues);
|
|
2979
1664
|
if (repairBlock) {
|
|
2980
1665
|
finalPrompt = `${finalPrompt}${repairBlock}`;
|
|
2981
1666
|
}
|
|
2982
1667
|
|
|
2983
|
-
// ── Prompt char measurement
|
|
1668
|
+
// ── Prompt char measurement ──
|
|
2984
1669
|
s.lastPromptCharCount = finalPrompt.length;
|
|
2985
1670
|
s.lastBaselineCharCount = undefined;
|
|
2986
1671
|
if (isDbAvailable()) {
|
|
@@ -2996,221 +1681,44 @@ async function dispatchNextUnit(
|
|
|
2996
1681
|
(requirementsContent?.length ?? 0) +
|
|
2997
1682
|
(projectContent?.length ?? 0);
|
|
2998
1683
|
} catch {
|
|
2999
|
-
// Non-fatal
|
|
1684
|
+
// Non-fatal
|
|
3000
1685
|
}
|
|
3001
1686
|
}
|
|
3002
1687
|
|
|
3003
|
-
//
|
|
1688
|
+
// Cache-optimize prompt section ordering
|
|
1689
|
+
try {
|
|
1690
|
+
const { reorderForCaching } = await import("./prompt-ordering.js");
|
|
1691
|
+
finalPrompt = reorderForCaching(finalPrompt);
|
|
1692
|
+
} catch (reorderErr) {
|
|
1693
|
+
const msg = reorderErr instanceof Error ? reorderErr.message : String(reorderErr);
|
|
1694
|
+
process.stderr.write(`[gsd] prompt reorder failed (non-fatal): ${msg}\n`);
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
// Select and apply model
|
|
3004
1698
|
const modelResult = await selectAndApplyModel(ctx, pi, unitType, unitId, s.basePath, prefs, s.verbose, s.autoModeStartModel);
|
|
3005
1699
|
s.currentUnitRouting = modelResult.routing;
|
|
3006
1700
|
|
|
3007
|
-
// Start
|
|
3008
|
-
// a larger hard ceiling. Productive long-running tasks may continue past the
|
|
3009
|
-
// soft timeout; only idle/stalled tasks pause early.
|
|
1701
|
+
// ── Start unit supervision (delegated to auto-timers.ts) ──
|
|
3010
1702
|
clearUnitTimeout();
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
});
|
|
3023
|
-
pi.sendMessage(
|
|
3024
|
-
{
|
|
3025
|
-
customType: "gsd-auto-wrapup",
|
|
3026
|
-
display: s.verbose,
|
|
3027
|
-
content: [
|
|
3028
|
-
"**TIME BUDGET WARNING — keep going only if progress is real.**",
|
|
3029
|
-
"This unit crossed the soft time budget.",
|
|
3030
|
-
"If you are making progress, continue. If not, switch to wrap-up mode now:",
|
|
3031
|
-
"1. rerun the minimal required verification",
|
|
3032
|
-
"2. write or update the required durable artifacts",
|
|
3033
|
-
"3. mark task or slice state on disk correctly",
|
|
3034
|
-
"4. leave precise resume notes if anything remains unfinished",
|
|
3035
|
-
].join("\n"),
|
|
3036
|
-
},
|
|
3037
|
-
{ triggerTurn: true },
|
|
3038
|
-
);
|
|
3039
|
-
}, softTimeoutMs);
|
|
3040
|
-
|
|
3041
|
-
s.idleWatchdogHandle = setInterval(async () => {
|
|
3042
|
-
try {
|
|
3043
|
-
if (!s.active || !s.currentUnit) return;
|
|
3044
|
-
const runtime = readUnitRuntimeRecord(s.basePath, unitType, unitId);
|
|
3045
|
-
if (!runtime) return;
|
|
3046
|
-
if (Date.now() - runtime.lastProgressAt < idleTimeoutMs) return;
|
|
3047
|
-
|
|
3048
|
-
// Agent has tool calls currently executing (await_job, long bash, etc.) —
|
|
3049
|
-
// not idle, just waiting for tool completion. But only suppress recovery
|
|
3050
|
-
// if the tool started recently. A tool in-flight for longer than the idle
|
|
3051
|
-
// timeout is likely stuck — e.g., `python -m http.server 8080 &` keeps the
|
|
3052
|
-
// shell's stdout/stderr open, causing the Bash tool to hang indefinitely.
|
|
3053
|
-
if (getInFlightToolCount() > 0) {
|
|
3054
|
-
const oldestStart = getOldestInFlightToolStart()!;
|
|
3055
|
-
const toolAgeMs = Date.now() - oldestStart;
|
|
3056
|
-
if (toolAgeMs < idleTimeoutMs) {
|
|
3057
|
-
writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
|
|
3058
|
-
lastProgressAt: Date.now(),
|
|
3059
|
-
lastProgressKind: "tool-in-flight",
|
|
3060
|
-
});
|
|
3061
|
-
return;
|
|
3062
|
-
}
|
|
3063
|
-
// Oldest tool has been running >= idleTimeoutMs — treat as a stuck/hung
|
|
3064
|
-
// tool (e.g., background process holding stdout open). Fall through to
|
|
3065
|
-
// idle recovery without resetting the progress clock.
|
|
3066
|
-
ctx.ui.notify(
|
|
3067
|
-
`Stalled tool detected: a tool has been in-flight for ${Math.round(toolAgeMs / 60000)}min. Treating as hung — attempting idle recovery.`,
|
|
3068
|
-
"warning",
|
|
3069
|
-
);
|
|
3070
|
-
}
|
|
3071
|
-
|
|
3072
|
-
// Before triggering recovery, check if the agent is actually producing
|
|
3073
|
-
// work on disk. `git status --porcelain` is cheap and catches any
|
|
3074
|
-
// staged/unstaged/untracked changes the agent made since lastProgressAt.
|
|
3075
|
-
if (detectWorkingTreeActivity(s.basePath)) {
|
|
3076
|
-
writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
|
|
3077
|
-
lastProgressAt: Date.now(),
|
|
3078
|
-
lastProgressKind: "filesystem-activity",
|
|
3079
|
-
});
|
|
3080
|
-
return;
|
|
3081
|
-
}
|
|
3082
|
-
|
|
3083
|
-
if (s.currentUnit) {
|
|
3084
|
-
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
3085
|
-
} else {
|
|
3086
|
-
saveActivityLog(ctx, s.basePath, unitType, unitId);
|
|
3087
|
-
}
|
|
3088
|
-
|
|
3089
|
-
const recovery = await recoverTimedOutUnit(ctx, pi, unitType, unitId, "idle", buildRecoveryContext());
|
|
3090
|
-
if (recovery === "recovered") return;
|
|
3091
|
-
|
|
3092
|
-
writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
|
|
3093
|
-
phase: "paused",
|
|
3094
|
-
});
|
|
3095
|
-
ctx.ui.notify(
|
|
3096
|
-
`Unit ${unitType} ${unitId} made no meaningful progress for ${supervisor.idle_timeout_minutes}min. Pausing auto-mode.`,
|
|
3097
|
-
"warning",
|
|
3098
|
-
);
|
|
3099
|
-
await pauseAuto(ctx, pi);
|
|
3100
|
-
} catch (err) {
|
|
3101
|
-
// Guard against unhandled rejections in the async interval callback.
|
|
3102
|
-
// Without this, a thrown error leaves the interval running forever
|
|
3103
|
-
// while the auto-mode state becomes inconsistent.
|
|
3104
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
3105
|
-
console.error(`[idle-watchdog] Unhandled error: ${message}`);
|
|
3106
|
-
try {
|
|
3107
|
-
ctx.ui.notify(`Idle watchdog error: ${message}`, "warning");
|
|
3108
|
-
} catch { /* best effort */ }
|
|
3109
|
-
}
|
|
3110
|
-
}, 15000);
|
|
3111
|
-
|
|
3112
|
-
s.unitTimeoutHandle = setTimeout(async () => {
|
|
3113
|
-
try {
|
|
3114
|
-
s.unitTimeoutHandle = null;
|
|
3115
|
-
if (!s.active) return;
|
|
3116
|
-
if (s.currentUnit) {
|
|
3117
|
-
writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
|
|
3118
|
-
phase: "timeout",
|
|
3119
|
-
timeoutAt: Date.now(),
|
|
3120
|
-
});
|
|
3121
|
-
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
3122
|
-
} else {
|
|
3123
|
-
saveActivityLog(ctx, s.basePath, unitType, unitId);
|
|
3124
|
-
}
|
|
3125
|
-
|
|
3126
|
-
const recovery = await recoverTimedOutUnit(ctx, pi, unitType, unitId, "hard", buildRecoveryContext());
|
|
3127
|
-
if (recovery === "recovered") return;
|
|
3128
|
-
|
|
3129
|
-
ctx.ui.notify(
|
|
3130
|
-
`Unit ${unitType} ${unitId} exceeded ${supervisor.hard_timeout_minutes}min hard timeout. Pausing auto-mode.`,
|
|
3131
|
-
"warning",
|
|
3132
|
-
);
|
|
3133
|
-
await pauseAuto(ctx, pi);
|
|
3134
|
-
} catch (err) {
|
|
3135
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
3136
|
-
console.error(`[hard-timeout] Unhandled error: ${message}`);
|
|
3137
|
-
try {
|
|
3138
|
-
ctx.ui.notify(`Hard timeout error: ${message}`, "warning");
|
|
3139
|
-
} catch { /* best effort */ }
|
|
3140
|
-
}
|
|
3141
|
-
}, hardTimeoutMs);
|
|
3142
|
-
|
|
3143
|
-
// ── Continue-here context-pressure monitor ────────────────────────────
|
|
3144
|
-
// Polls context usage every 15s. When usage hits the continue-here
|
|
3145
|
-
// threshold (70%), sends a one-shot wrap-up signal so the agent finishes
|
|
3146
|
-
// gracefully and the next unit gets a fresh session. This is softer than
|
|
3147
|
-
// context_pause_threshold which hard-pauses auto-mode entirely.
|
|
3148
|
-
if (s.continueHereHandle) {
|
|
3149
|
-
clearInterval(s.continueHereHandle);
|
|
3150
|
-
s.continueHereHandle = null;
|
|
3151
|
-
}
|
|
3152
|
-
const executorContextWindow = resolveExecutorContextWindow(
|
|
3153
|
-
ctx.modelRegistry as Parameters<typeof resolveExecutorContextWindow>[0],
|
|
3154
|
-
prefs as Parameters<typeof resolveExecutorContextWindow>[1],
|
|
3155
|
-
ctx.model?.contextWindow,
|
|
3156
|
-
);
|
|
3157
|
-
const continueHereThreshold = computeBudgets(executorContextWindow).continueThresholdPercent;
|
|
3158
|
-
s.continueHereHandle = setInterval(() => {
|
|
3159
|
-
if (!s.active || !s.currentUnit || !s.cmdCtx) return;
|
|
3160
|
-
// One-shot guard: skip if already fired for this unit
|
|
3161
|
-
const runtime = readUnitRuntimeRecord(s.basePath, unitType, unitId);
|
|
3162
|
-
if (runtime?.continueHereFired) return;
|
|
3163
|
-
|
|
3164
|
-
const contextUsage = s.cmdCtx.getContextUsage();
|
|
3165
|
-
if (!contextUsage || contextUsage.percent == null || contextUsage.percent < continueHereThreshold) return;
|
|
3166
|
-
|
|
3167
|
-
// Fire once — mark runtime record and send wrap-up message
|
|
3168
|
-
writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit!.startedAt, {
|
|
3169
|
-
continueHereFired: true,
|
|
3170
|
-
});
|
|
3171
|
-
|
|
3172
|
-
if (s.verbose) {
|
|
3173
|
-
ctx.ui.notify(
|
|
3174
|
-
`Context at ${contextUsage.percent}% (threshold: ${continueHereThreshold}%) — sending wrap-up signal.`,
|
|
3175
|
-
"info",
|
|
3176
|
-
);
|
|
3177
|
-
}
|
|
3178
|
-
|
|
3179
|
-
pi.sendMessage(
|
|
3180
|
-
{
|
|
3181
|
-
customType: "gsd-auto-wrapup",
|
|
3182
|
-
display: s.verbose,
|
|
3183
|
-
content: [
|
|
3184
|
-
"**CONTEXT BUDGET WARNING — wrap up this unit now.**",
|
|
3185
|
-
`Context window is at ${contextUsage.percent}% (threshold: ${continueHereThreshold}%).`,
|
|
3186
|
-
"The next unit needs a fresh context to work effectively. Wrap up now:",
|
|
3187
|
-
"1. Finish any in-progress file writes",
|
|
3188
|
-
"2. Write or update the required durable artifacts (summary, checkboxes)",
|
|
3189
|
-
"3. Mark task state on disk correctly",
|
|
3190
|
-
"4. Leave precise resume notes if anything remains unfinished",
|
|
3191
|
-
"Do NOT start new sub-tasks or investigations.",
|
|
3192
|
-
].join("\n"),
|
|
3193
|
-
},
|
|
3194
|
-
{ triggerTurn: true },
|
|
3195
|
-
);
|
|
3196
|
-
|
|
3197
|
-
// Clear the interval after firing — no need to keep polling
|
|
3198
|
-
if (s.continueHereHandle) {
|
|
3199
|
-
clearInterval(s.continueHereHandle);
|
|
3200
|
-
s.continueHereHandle = null;
|
|
3201
|
-
}
|
|
3202
|
-
}, 15_000);
|
|
1703
|
+
startUnitSupervision({
|
|
1704
|
+
s,
|
|
1705
|
+
ctx,
|
|
1706
|
+
pi,
|
|
1707
|
+
unitType,
|
|
1708
|
+
unitId,
|
|
1709
|
+
prefs,
|
|
1710
|
+
buildSnapshotOpts: () => buildSnapshotOpts(unitType, unitId),
|
|
1711
|
+
buildRecoveryContext: () => buildRecoveryContext(),
|
|
1712
|
+
pauseAuto,
|
|
1713
|
+
});
|
|
3203
1714
|
|
|
3204
|
-
// Inject prompt
|
|
1715
|
+
// Inject prompt
|
|
3205
1716
|
if (!s.active) return;
|
|
3206
1717
|
pi.sendMessage(
|
|
3207
1718
|
{ customType: "gsd-auto", content: finalPrompt, display: s.verbose },
|
|
3208
1719
|
{ triggerTurn: true },
|
|
3209
1720
|
);
|
|
3210
1721
|
|
|
3211
|
-
// For non-artifact-driven UAT types, pause auto-mode after sending the prompt.
|
|
3212
|
-
// The agent will write the UAT result file surfacing it for human review,
|
|
3213
|
-
// then on resume the result file exists and run-uat is skipped automatically.
|
|
3214
1722
|
if (pauseAfterUatDispatch) {
|
|
3215
1723
|
ctx.ui.notify(
|
|
3216
1724
|
"UAT requires human execution. Auto-mode will pause after this unit writes the result file.",
|
|
@@ -3235,32 +1743,26 @@ function ensurePreconditions(
|
|
|
3235
1743
|
const parts = unitId.split("/");
|
|
3236
1744
|
const mid = parts[0]!;
|
|
3237
1745
|
|
|
3238
|
-
// Always ensure milestone dir exists
|
|
3239
1746
|
const mDir = resolveMilestonePath(base, mid);
|
|
3240
1747
|
if (!mDir) {
|
|
3241
1748
|
const newDir = join(milestonesDir(base), mid);
|
|
3242
1749
|
mkdirSync(join(newDir, "slices"), { recursive: true });
|
|
3243
1750
|
}
|
|
3244
1751
|
|
|
3245
|
-
// For slice-level units, ensure slice dir exists
|
|
3246
1752
|
if (parts.length >= 2) {
|
|
3247
1753
|
const sid = parts[1]!;
|
|
3248
1754
|
|
|
3249
|
-
// Re-resolve milestone path after potential creation
|
|
3250
1755
|
const mDirResolved = resolveMilestonePath(base, mid);
|
|
3251
1756
|
if (mDirResolved) {
|
|
3252
1757
|
const slicesDir = join(mDirResolved, "slices");
|
|
3253
1758
|
const sDir = resolveDir(slicesDir, sid);
|
|
3254
1759
|
if (!sDir) {
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
if (!existsSync(tasksDir)) {
|
|
3262
|
-
mkdirSync(tasksDir, { recursive: true });
|
|
3263
|
-
}
|
|
1760
|
+
mkdirSync(join(slicesDir, sid, "tasks"), { recursive: true });
|
|
1761
|
+
}
|
|
1762
|
+
const resolvedSliceDir = resolveDir(slicesDir, sid) ?? sid;
|
|
1763
|
+
const tasksDir = join(slicesDir, resolvedSliceDir, "tasks");
|
|
1764
|
+
if (!existsSync(tasksDir)) {
|
|
1765
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
3264
1766
|
}
|
|
3265
1767
|
}
|
|
3266
1768
|
}
|
|
@@ -3269,10 +1771,6 @@ function ensurePreconditions(
|
|
|
3269
1771
|
|
|
3270
1772
|
// ─── Diagnostics ──────────────────────────────────────────────────────────────
|
|
3271
1773
|
|
|
3272
|
-
// collectObservabilityWarnings + buildObservabilityRepairBlock → auto-observability.ts
|
|
3273
|
-
|
|
3274
|
-
// recoverTimedOutUnit → auto-timeout-recovery.ts
|
|
3275
|
-
|
|
3276
1774
|
/** Build recovery context from module state for recoverTimedOutUnit */
|
|
3277
1775
|
function buildRecoveryContext(): import("./auto-timeout-recovery.js").RecoveryContext {
|
|
3278
1776
|
return { basePath: s.basePath, verbose: s.verbose,
|
|
@@ -3296,7 +1794,6 @@ export {
|
|
|
3296
1794
|
*/
|
|
3297
1795
|
export function _getUnitConsecutiveSkips(): Map<string, number> { return s.unitConsecutiveSkips; }
|
|
3298
1796
|
export function _resetUnitConsecutiveSkips(): void { s.unitConsecutiveSkips.clear(); }
|
|
3299
|
-
// MAX_CONSECUTIVE_SKIPS re-exported from auto/session.ts at top of file
|
|
3300
1797
|
|
|
3301
1798
|
/**
|
|
3302
1799
|
* Dispatch a hook unit directly, bypassing normal pre-dispatch hooks.
|
|
@@ -3312,9 +1809,7 @@ export async function dispatchHookUnit(
|
|
|
3312
1809
|
hookModel: string | undefined,
|
|
3313
1810
|
targetBasePath: string,
|
|
3314
1811
|
): Promise<boolean> {
|
|
3315
|
-
// Ensure auto-mode is s.active
|
|
3316
1812
|
if (!s.active) {
|
|
3317
|
-
// Initialize auto-mode state minimally
|
|
3318
1813
|
s.active = true;
|
|
3319
1814
|
s.stepMode = true;
|
|
3320
1815
|
s.cmdCtx = ctx as ExtensionCommandContext;
|
|
@@ -3327,21 +1822,17 @@ export async function dispatchHookUnit(
|
|
|
3327
1822
|
|
|
3328
1823
|
const hookUnitType = `hook/${hookName}`;
|
|
3329
1824
|
const hookStartedAt = Date.now();
|
|
3330
|
-
|
|
3331
|
-
// Set up the trigger unit as the "current" unit so post-unit hooks can reference it
|
|
1825
|
+
|
|
3332
1826
|
s.currentUnit = { type: triggerUnitType, id: triggerUnitId, startedAt: hookStartedAt };
|
|
3333
|
-
|
|
3334
|
-
// Create a new session for the hook
|
|
1827
|
+
|
|
3335
1828
|
const result = await s.cmdCtx!.newSession();
|
|
3336
1829
|
if (result.cancelled) {
|
|
3337
1830
|
await stopAuto(ctx, pi);
|
|
3338
1831
|
return false;
|
|
3339
1832
|
}
|
|
3340
1833
|
|
|
3341
|
-
// Update current unit to the hook unit
|
|
3342
1834
|
s.currentUnit = { type: hookUnitType, id: triggerUnitId, startedAt: hookStartedAt };
|
|
3343
|
-
|
|
3344
|
-
// Write runtime record
|
|
1835
|
+
|
|
3345
1836
|
writeUnitRuntimeRecord(s.basePath, hookUnitType, triggerUnitId, hookStartedAt, {
|
|
3346
1837
|
phase: "dispatched",
|
|
3347
1838
|
wrapupWarningSent: false,
|
|
@@ -3351,7 +1842,6 @@ export async function dispatchHookUnit(
|
|
|
3351
1842
|
lastProgressKind: "dispatch",
|
|
3352
1843
|
});
|
|
3353
1844
|
|
|
3354
|
-
// Switch model if specified
|
|
3355
1845
|
if (hookModel) {
|
|
3356
1846
|
const availableModels = ctx.modelRegistry.getAvailable();
|
|
3357
1847
|
const match = availableModels.find(m =>
|
|
@@ -3360,15 +1850,14 @@ export async function dispatchHookUnit(
|
|
|
3360
1850
|
if (match) {
|
|
3361
1851
|
try {
|
|
3362
1852
|
await pi.setModel(match);
|
|
3363
|
-
} catch { /* non-fatal
|
|
1853
|
+
} catch { /* non-fatal */ }
|
|
3364
1854
|
}
|
|
3365
1855
|
}
|
|
3366
1856
|
|
|
3367
|
-
// Write lock
|
|
3368
1857
|
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
1858
|
+
updateSessionLock(lockBase(), hookUnitType, triggerUnitId, s.completedUnits.length, sessionFile);
|
|
3369
1859
|
writeLock(lockBase(), hookUnitType, triggerUnitId, s.completedUnits.length, sessionFile);
|
|
3370
1860
|
|
|
3371
|
-
// Set up timeout
|
|
3372
1861
|
clearUnitTimeout();
|
|
3373
1862
|
const supervisor = resolveAutoSupervisorConfig();
|
|
3374
1863
|
const hookHardTimeoutMs = (supervisor.hard_timeout_minutes ?? 30) * 60 * 1000;
|
|
@@ -3389,21 +1878,17 @@ export async function dispatchHookUnit(
|
|
|
3389
1878
|
await pauseAuto(ctx, pi);
|
|
3390
1879
|
}, hookHardTimeoutMs);
|
|
3391
1880
|
|
|
3392
|
-
// Update status
|
|
3393
1881
|
ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
|
|
3394
1882
|
ctx.ui.notify(`Running post-unit hook: ${hookName}`, "info");
|
|
3395
1883
|
|
|
3396
|
-
// Send the hook prompt
|
|
3397
1884
|
console.log(`[dispatchHookUnit] Sending prompt of length ${hookPrompt.length}`);
|
|
3398
1885
|
console.log(`[dispatchHookUnit] Prompt preview: ${hookPrompt.substring(0, 200)}...`);
|
|
3399
1886
|
pi.sendMessage(
|
|
3400
1887
|
{ customType: "gsd-auto", content: hookPrompt, display: true },
|
|
3401
1888
|
{ triggerTurn: true },
|
|
3402
1889
|
);
|
|
3403
|
-
|
|
1890
|
+
|
|
3404
1891
|
return true;
|
|
3405
1892
|
}
|
|
3406
1893
|
|
|
3407
1894
|
|
|
3408
|
-
// Direct phase dispatch → auto-direct-dispatch.ts
|
|
3409
|
-
export { dispatchDirectPhase } from "./auto-direct-dispatch.js";
|