gsd-pi 2.27.0 → 2.28.0-dev.346ee62
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 +39 -12
- 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 +40 -0
- package/dist/headless-query.js +77 -0
- 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 +67 -186
- package/dist/help-text.js +6 -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 +52 -95
- 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/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 +84 -3
- package/dist/resources/extensions/gsd/auto-idempotency.ts +150 -0
- package/dist/resources/extensions/gsd/auto-post-unit.ts +591 -0
- package/dist/resources/extensions/gsd/auto-prompts.ts +116 -22
- package/dist/resources/extensions/gsd/auto-recovery.ts +20 -9
- 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 +195 -0
- package/dist/resources/extensions/gsd/auto-worktree-sync.ts +8 -31
- package/dist/resources/extensions/gsd/auto-worktree.ts +83 -67
- package/dist/resources/extensions/gsd/auto.ts +364 -1873
- package/dist/resources/extensions/gsd/commands-config.ts +102 -0
- package/dist/resources/extensions/gsd/commands-handlers.ts +402 -0
- package/dist/resources/extensions/gsd/commands-inspect.ts +90 -0
- package/dist/resources/extensions/gsd/commands-logs.ts +537 -0
- package/dist/resources/extensions/gsd/commands-maintenance.ts +206 -0
- package/dist/resources/extensions/gsd/commands-prefs-wizard.ts +747 -0
- package/dist/resources/extensions/gsd/commands.ts +378 -1431
- 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 +4 -2
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +13 -4
- 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/docs/preferences-reference.md +83 -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 +119 -30
- 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 +206 -45
- package/dist/resources/extensions/gsd/init-wizard.ts +615 -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 +32 -5
- 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 -3
- package/dist/resources/extensions/gsd/plugin-importer.ts +3 -2
- package/dist/resources/extensions/gsd/preferences-hooks.ts +10 -0
- 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 +1 -2
- package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +0 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -2
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -2
- 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 +5 -2
- package/dist/resources/extensions/gsd/provider-error-pause.ts +59 -10
- package/dist/resources/extensions/gsd/queue-order.ts +1 -1
- 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/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/skills/gsd-headless/SKILL.md +80 -16
- package/dist/resources/extensions/gsd/skills/gsd-headless/references/answer-injection.md +35 -6
- package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +7 -2
- package/dist/resources/extensions/gsd/skills/gsd-headless/references/multi-session.md +4 -13
- 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/templates/preferences.md +34 -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-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-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-all.test.ts +105 -0
- package/dist/resources/extensions/gsd/tests/export-html-enhancements.test.ts +375 -0
- package/dist/resources/extensions/gsd/tests/extension-selector-separator.test.ts +122 -0
- package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +3 -0
- package/dist/resources/extensions/gsd/tests/headless-answers.test.ts +340 -0
- package/dist/resources/extensions/gsd/tests/headless-query.test.ts +162 -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/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/update-command.test.ts +67 -0
- 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-gate.test.ts +115 -1
- 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/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 +4 -1
- package/dist/resources/extensions/gsd/validate-directory.ts +164 -0
- package/dist/resources/extensions/gsd/verification-evidence.ts +7 -4
- package/dist/resources/extensions/gsd/verification-gate.ts +70 -5
- 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/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/mcporter/index.ts +27 -14
- 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 +91 -0
- package/dist/resources/extensions/remote-questions/remote-command.ts +1 -1
- package/dist/resources/extensions/remote-questions/store.ts +5 -1
- package/dist/resources/extensions/remote-questions/types.ts +26 -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 +30 -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/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/dist/update-check.js +1 -1
- package/package.json +20 -8
- package/packages/native/package.json +28 -0
- 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/dist/utils/oauth/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -0
- package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js +5 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/google-antigravity.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/google-antigravity.js +4 -0
- package/packages/pi-ai/dist/utils/oauth/google-antigravity.js.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.js +6 -0
- package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.js.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/index.js +2 -2
- package/packages/pi-ai/dist/utils/oauth/index.js.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/openai-codex.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/openai-codex.js +2 -0
- package/packages/pi-ai/dist/utils/oauth/openai-codex.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-ai/src/utils/oauth/anthropic.ts +2 -0
- package/packages/pi-ai/src/utils/oauth/github-copilot.ts +5 -1
- package/packages/pi-ai/src/utils/oauth/google-antigravity.ts +4 -0
- package/packages/pi-ai/src/utils/oauth/google-gemini-cli.ts +6 -0
- package/packages/pi-ai/src/utils/oauth/index.ts +2 -2
- package/packages/pi-ai/src/utils/oauth/openai-codex.ts +2 -0
- 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/bash-executor.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/bash-executor.js +23 -1
- package/packages/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/blob-store.d.ts +8 -0
- package/packages/pi-coding-agent/dist/core/blob-store.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/blob-store.js +50 -1
- package/packages/pi-coding-agent/dist/core/blob-store.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.js +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +32 -10
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +77 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/fs-utils.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/fs-utils.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/fs-utils.js +12 -0
- package/packages/pi-coding-agent/dist/core/fs-utils.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/fs-utils.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/fs-utils.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/fs-utils.test.js +67 -0
- package/packages/pi-coding-agent/dist/core/fs-utils.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +22 -2
- package/packages/pi-coding-agent/dist/core/lsp/client.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 +7 -0
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +108 -14
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +14 -5
- package/packages/pi-coding-agent/dist/core/settings-manager.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/index.d.ts +2 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +3 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +6 -0
- package/packages/pi-coding-agent/dist/main.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 +30 -2
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js +11 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.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 +0 -4
- 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/bash-executor.ts +23 -1
- package/packages/pi-coding-agent/src/core/blob-store.ts +46 -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 +1 -1
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +84 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +31 -10
- package/packages/pi-coding-agent/src/core/fs-utils.test.ts +66 -0
- package/packages/pi-coding-agent/src/core/fs-utils.ts +12 -0
- package/packages/pi-coding-agent/src/core/lsp/client.ts +22 -2
- 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 +113 -18
- package/packages/pi-coding-agent/src/core/settings-manager.ts +22 -5
- 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/index.ts +9 -0
- package/packages/pi-coding-agent/src/main.ts +7 -0
- 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 +32 -1
- package/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts +12 -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 +5 -1
- package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/packages/pi-tui/dist/autocomplete.js +6 -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 +8 -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/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 +84 -3
- package/src/resources/extensions/gsd/auto-idempotency.ts +150 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +591 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +116 -22
- package/src/resources/extensions/gsd/auto-recovery.ts +20 -9
- 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 +195 -0
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +8 -31
- package/src/resources/extensions/gsd/auto-worktree.ts +83 -67
- package/src/resources/extensions/gsd/auto.ts +364 -1873
- package/src/resources/extensions/gsd/commands-config.ts +102 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +402 -0
- package/src/resources/extensions/gsd/commands-inspect.ts +90 -0
- package/src/resources/extensions/gsd/commands-logs.ts +537 -0
- package/src/resources/extensions/gsd/commands-maintenance.ts +206 -0
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +747 -0
- package/src/resources/extensions/gsd/commands.ts +378 -1431
- 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 +4 -2
- package/src/resources/extensions/gsd/dashboard-overlay.ts +13 -4
- 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/docs/preferences-reference.md +83 -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 +119 -30
- 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 +206 -45
- package/src/resources/extensions/gsd/init-wizard.ts +615 -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 +32 -5
- 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 -3
- package/src/resources/extensions/gsd/plugin-importer.ts +3 -2
- package/src/resources/extensions/gsd/preferences-hooks.ts +10 -0
- 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 +1 -2
- package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +0 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -2
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -2
- 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 +5 -2
- package/src/resources/extensions/gsd/provider-error-pause.ts +59 -10
- package/src/resources/extensions/gsd/queue-order.ts +1 -1
- 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/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/skills/gsd-headless/SKILL.md +80 -16
- package/src/resources/extensions/gsd/skills/gsd-headless/references/answer-injection.md +35 -6
- package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +7 -2
- package/src/resources/extensions/gsd/skills/gsd-headless/references/multi-session.md +4 -13
- 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/templates/preferences.md +34 -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-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-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-all.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/export-html-enhancements.test.ts +375 -0
- package/src/resources/extensions/gsd/tests/extension-selector-separator.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/headless-answers.test.ts +340 -0
- package/src/resources/extensions/gsd/tests/headless-query.test.ts +162 -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/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/update-command.test.ts +67 -0
- 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-gate.test.ts +115 -1
- 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/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 +4 -1
- package/src/resources/extensions/gsd/validate-directory.ts +164 -0
- package/src/resources/extensions/gsd/verification-evidence.ts +7 -4
- package/src/resources/extensions/gsd/verification-gate.ts +70 -5
- 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/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/mcporter/index.ts +27 -14
- 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 +91 -0
- package/src/resources/extensions/remote-questions/remote-command.ts +1 -1
- package/src/resources/extensions/remote-questions/store.ts +5 -1
- package/src/resources/extensions/remote-questions/types.ts +26 -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 +30 -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/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/mcp-server.ts +0 -108
- 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/marketplace-discovery.test.ts +0 -202
- 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/shared/bundled-extension-paths.ts +0 -11
- package/src/resources/extensions/gsd/complexity.ts +0 -237
- package/src/resources/extensions/gsd/github-client.ts +0 -235
- package/src/resources/extensions/gsd/mcp-server.ts +0 -108
- 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/marketplace-discovery.test.ts +0 -202
- 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/shared/bundled-extension-paths.ts +0 -11
|
@@ -33,6 +33,12 @@ import { invalidateAllCaches } from "./cache.js";
|
|
|
33
33
|
import { saveActivityLog, clearActivityLogState } from "./activity-log.js";
|
|
34
34
|
import { synthesizeCrashRecovery, getDeepDiagnostic } from "./session-forensics.js";
|
|
35
35
|
import { writeLock, clearLock, readCrashLock, formatCrashInfo, isLockProcessAlive } from "./crash-recovery.js";
|
|
36
|
+
import {
|
|
37
|
+
acquireSessionLock,
|
|
38
|
+
validateSessionLock,
|
|
39
|
+
releaseSessionLock,
|
|
40
|
+
updateSessionLock,
|
|
41
|
+
} from "./session-lock.js";
|
|
36
42
|
import {
|
|
37
43
|
clearUnitRuntimeRecord,
|
|
38
44
|
inspectExecuteTaskDurability,
|
|
@@ -70,7 +76,6 @@ import {
|
|
|
70
76
|
checkResourcesStale,
|
|
71
77
|
escapeStaleWorktree,
|
|
72
78
|
} from "./auto-worktree-sync.js";
|
|
73
|
-
// complexity-classifier + model-router imports moved to auto-model-selection.ts
|
|
74
79
|
import { initRoutingHistory, resetRoutingHistory, recordOutcome } from "./routing-history.js";
|
|
75
80
|
import {
|
|
76
81
|
checkPostUnitHooks,
|
|
@@ -83,7 +88,6 @@ import {
|
|
|
83
88
|
restoreHookState,
|
|
84
89
|
clearPersistedHookState,
|
|
85
90
|
} from "./post-unit-hooks.js";
|
|
86
|
-
// observability-validator imports moved to auto-observability.ts
|
|
87
91
|
import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
|
|
88
92
|
import { runGSDDoctor, rebuildState, summarizeDoctorIssues } from "./doctor.js";
|
|
89
93
|
import {
|
|
@@ -101,10 +105,12 @@ import {
|
|
|
101
105
|
getProjectTotals, formatCost, formatTokenCount,
|
|
102
106
|
} from "./metrics.js";
|
|
103
107
|
import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.js";
|
|
108
|
+
import { GSDError, GSD_ARTIFACT_MISSING } from "./errors.js";
|
|
104
109
|
import { join } from "node:path";
|
|
105
110
|
import { sep as pathSep } from "node:path";
|
|
106
111
|
import { readdirSync, readFileSync, existsSync, mkdirSync, writeFileSync, unlinkSync, statSync } from "node:fs";
|
|
107
|
-
import {
|
|
112
|
+
import { atomicWriteSync } from "./atomic-write.js";
|
|
113
|
+
import { nativeIsRepo, nativeInit, nativeAddAll, nativeCommit } from "./native-git-bridge.js";
|
|
108
114
|
import {
|
|
109
115
|
autoCommitCurrentBranch,
|
|
110
116
|
captureIntegrationBranch,
|
|
@@ -130,7 +136,7 @@ import {
|
|
|
130
136
|
} from "./auto-worktree.js";
|
|
131
137
|
import { pruneQueueOrder } from "./queue-order.js";
|
|
132
138
|
import { consumeSignal } from "./session-status-io.js";
|
|
133
|
-
import { showNextAction } from "../shared/
|
|
139
|
+
import { showNextAction } from "../shared/mod.js";
|
|
134
140
|
import { debugLog, debugTime, debugCount, debugPeak, enableDebug, isDebugEnabled, writeDebugSummary, getDebugLogPath } from "./debug-logger.js";
|
|
135
141
|
import {
|
|
136
142
|
resolveExpectedArtifactPath,
|
|
@@ -147,7 +153,6 @@ import {
|
|
|
147
153
|
reconcileMergeState,
|
|
148
154
|
} from "./auto-recovery.js";
|
|
149
155
|
import { resolveDispatch, resetRewriteCircuitBreaker } from "./auto-dispatch.js";
|
|
150
|
-
// Prompt builders moved to auto-direct-dispatch.ts (only used there now)
|
|
151
156
|
import {
|
|
152
157
|
type AutoDashboardData,
|
|
153
158
|
updateProgressWidget as _updateProgressWidget,
|
|
@@ -168,6 +173,14 @@ import {
|
|
|
168
173
|
import { isDbAvailable } from "./gsd-db.js";
|
|
169
174
|
import { hasPendingCaptures, loadPendingCaptures, countPendingCaptures } from "./captures.js";
|
|
170
175
|
|
|
176
|
+
// ── Extracted modules ──────────────────────────────────────────────────────
|
|
177
|
+
import { startUnitSupervision, type SupervisionContext } from "./auto-timers.js";
|
|
178
|
+
import { checkIdempotency, type IdempotencyContext } from "./auto-idempotency.js";
|
|
179
|
+
import { checkStuckAndRecover, type StuckContext } from "./auto-stuck-detection.js";
|
|
180
|
+
import { runPostUnitVerification, type VerificationContext } from "./auto-verification.js";
|
|
181
|
+
import { postUnitPreVerification, postUnitPostVerification, type PostUnitContext } from "./auto-post-unit.js";
|
|
182
|
+
import { bootstrapAutoSession, type BootstrapDeps } from "./auto-start.js";
|
|
183
|
+
|
|
171
184
|
// Worktree sync, resource staleness, stale worktree escape → auto-worktree-sync.ts
|
|
172
185
|
|
|
173
186
|
// ─── Session State ─────────────────────────────────────────────────────────
|
|
@@ -176,14 +189,28 @@ import {
|
|
|
176
189
|
AutoSession,
|
|
177
190
|
MAX_UNIT_DISPATCHES, STUB_RECOVERY_THRESHOLD, MAX_LIFETIME_DISPATCHES,
|
|
178
191
|
MAX_CONSECUTIVE_SKIPS, DISPATCH_GAP_TIMEOUT_MS, MAX_SKIP_DEPTH,
|
|
192
|
+
NEW_SESSION_TIMEOUT_MS, DISPATCH_HANG_TIMEOUT_MS,
|
|
179
193
|
} from "./auto/session.js";
|
|
180
194
|
import type { CompletedUnit, CurrentUnit, UnitRouting, StartModel, PendingVerificationRetry } from "./auto/session.js";
|
|
181
195
|
export {
|
|
182
196
|
MAX_UNIT_DISPATCHES, STUB_RECOVERY_THRESHOLD, MAX_LIFETIME_DISPATCHES,
|
|
183
197
|
MAX_CONSECUTIVE_SKIPS, DISPATCH_GAP_TIMEOUT_MS, MAX_SKIP_DEPTH,
|
|
198
|
+
NEW_SESSION_TIMEOUT_MS, DISPATCH_HANG_TIMEOUT_MS,
|
|
184
199
|
} from "./auto/session.js";
|
|
185
200
|
export type { CompletedUnit, CurrentUnit, UnitRouting, StartModel } from "./auto/session.js";
|
|
186
201
|
|
|
202
|
+
// ── ENCAPSULATION INVARIANT ─────────────────────────────────────────────────
|
|
203
|
+
// ALL mutable auto-mode state lives in the AutoSession class (auto/session.ts).
|
|
204
|
+
// This file must NOT declare module-level `let` or `var` variables for state.
|
|
205
|
+
// The single `s` instance below is the only mutable module-level binding.
|
|
206
|
+
//
|
|
207
|
+
// When adding features or fixing bugs:
|
|
208
|
+
// - New mutable state → add a property to AutoSession, not a module-level variable
|
|
209
|
+
// - New constants → module-level `const` is fine (immutable)
|
|
210
|
+
// - New state that needs reset on stopAuto → add to AutoSession.reset()
|
|
211
|
+
//
|
|
212
|
+
// Tests in auto-session-encapsulation.test.ts enforce this invariant.
|
|
213
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
187
214
|
const s = new AutoSession();
|
|
188
215
|
|
|
189
216
|
/** Throttle STATE.md rebuilds — at most once per 30 seconds */
|
|
@@ -291,6 +318,15 @@ export function isAutoPaused(): boolean {
|
|
|
291
318
|
return s.paused;
|
|
292
319
|
}
|
|
293
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Return the model captured at auto-mode start for this session.
|
|
323
|
+
* Used by error-recovery to fall back to the session's own model
|
|
324
|
+
* instead of reading (potentially stale) preferences from disk (#1065).
|
|
325
|
+
*/
|
|
326
|
+
export function getAutoModeStartModel(): { provider: string; id: string } | null {
|
|
327
|
+
return s.autoModeStartModel;
|
|
328
|
+
}
|
|
329
|
+
|
|
294
330
|
// Tool tracking — delegates to auto-tool-tracking.ts
|
|
295
331
|
export function markToolStart(toolCallId: string): void {
|
|
296
332
|
_markToolStart(toolCallId, s.active);
|
|
@@ -396,8 +432,6 @@ function startDispatchGapWatchdog(ctx: ExtensionContext, pi: ExtensionAPI): void
|
|
|
396
432
|
s.dispatchGapHandle = null;
|
|
397
433
|
if (!s.active || !s.cmdCtx) return;
|
|
398
434
|
|
|
399
|
-
// Auto-mode is active but no unit was dispatched — the state machine stalled.
|
|
400
|
-
// Re-derive state and attempt a fresh dispatch.
|
|
401
435
|
if (s.verbose) {
|
|
402
436
|
ctx.ui.notify(
|
|
403
437
|
"Dispatch gap detected — re-evaluating state.",
|
|
@@ -413,9 +447,6 @@ function startDispatchGapWatchdog(ctx: ExtensionContext, pi: ExtensionAPI): void
|
|
|
413
447
|
return;
|
|
414
448
|
}
|
|
415
449
|
|
|
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
450
|
if (s.active && !s.unitTimeoutHandle && !s.wrapupWarningHandle) {
|
|
420
451
|
await stopAuto(ctx, pi, "Stalled — no dispatchable unit after retry");
|
|
421
452
|
}
|
|
@@ -426,7 +457,10 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
|
|
|
426
457
|
if (!s.active && !s.paused) return;
|
|
427
458
|
const reasonSuffix = reason ? ` — ${reason}` : "";
|
|
428
459
|
clearUnitTimeout();
|
|
429
|
-
if (lockBase())
|
|
460
|
+
if (lockBase()) {
|
|
461
|
+
releaseSessionLock(lockBase());
|
|
462
|
+
clearLock(lockBase());
|
|
463
|
+
}
|
|
430
464
|
clearSkillSnapshot();
|
|
431
465
|
resetSkillTelemetry();
|
|
432
466
|
s.dispatching = false;
|
|
@@ -436,13 +470,9 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
|
|
|
436
470
|
deregisterSigtermHandler();
|
|
437
471
|
|
|
438
472
|
// ── 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
473
|
if (s.currentMilestoneId && isInAutoWorktree(s.basePath)) {
|
|
443
474
|
try {
|
|
444
|
-
|
|
445
|
-
try { autoCommitCurrentBranch(s.basePath, "stop", s.currentMilestoneId); } catch { /* non-fatal */ }
|
|
475
|
+
try { autoCommitCurrentBranch(s.basePath, "stop", s.currentMilestoneId); } catch (e) { debugLog("stop-auto-commit-failed", { error: e instanceof Error ? e.message : String(e) }); }
|
|
446
476
|
teardownAutoWorktree(s.originalBasePath, s.currentMilestoneId, { preserveBranch: true });
|
|
447
477
|
s.basePath = s.originalBasePath;
|
|
448
478
|
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
@@ -460,13 +490,9 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
|
|
|
460
490
|
try {
|
|
461
491
|
const { closeDatabase } = await import("./gsd-db.js");
|
|
462
492
|
closeDatabase();
|
|
463
|
-
} catch {
|
|
493
|
+
} catch (e) { debugLog("db-close-failed", { error: e instanceof Error ? e.message : String(e) }); }
|
|
464
494
|
}
|
|
465
495
|
|
|
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
496
|
if (s.originalBasePath) {
|
|
471
497
|
s.basePath = s.originalBasePath;
|
|
472
498
|
try { process.chdir(s.basePath); } catch { /* best-effort */ }
|
|
@@ -483,12 +509,10 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
|
|
|
483
509
|
ctx?.ui.notify(`Auto-mode stopped${reasonSuffix}.`, "info");
|
|
484
510
|
}
|
|
485
511
|
|
|
486
|
-
// Sync disk state so next resume starts from accurate state
|
|
487
512
|
if (s.basePath) {
|
|
488
|
-
try { await rebuildState(s.basePath); } catch {
|
|
513
|
+
try { await rebuildState(s.basePath); } catch (e) { debugLog("stop-rebuild-state-failed", { error: e instanceof Error ? e.message : String(e) }); }
|
|
489
514
|
}
|
|
490
515
|
|
|
491
|
-
// Write debug summary before resetting state
|
|
492
516
|
if (isDebugEnabled()) {
|
|
493
517
|
const logPath = writeDebugSummary();
|
|
494
518
|
if (logPath) {
|
|
@@ -529,7 +553,6 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
|
|
|
529
553
|
ctx?.ui.setWidget("gsd-progress", undefined);
|
|
530
554
|
ctx?.ui.setFooter(undefined);
|
|
531
555
|
|
|
532
|
-
// Restore the user's original model
|
|
533
556
|
if (pi && ctx && s.originalModelId && s.originalModelProvider) {
|
|
534
557
|
const original = ctx.modelRegistry.find(s.originalModelProvider, s.originalModelId);
|
|
535
558
|
if (original) await pi.setModel(original);
|
|
@@ -549,22 +572,19 @@ export async function pauseAuto(ctx?: ExtensionContext, _pi?: ExtensionAPI): Pro
|
|
|
549
572
|
if (!s.active) return;
|
|
550
573
|
clearUnitTimeout();
|
|
551
574
|
|
|
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
575
|
s.pausedSessionFile = ctx?.sessionManager?.getSessionFile() ?? null;
|
|
555
576
|
|
|
556
|
-
if (lockBase())
|
|
577
|
+
if (lockBase()) {
|
|
578
|
+
releaseSessionLock(lockBase());
|
|
579
|
+
clearLock(lockBase());
|
|
580
|
+
}
|
|
557
581
|
|
|
558
|
-
// Remove SIGTERM handler registered at auto-mode start
|
|
559
582
|
deregisterSigtermHandler();
|
|
560
583
|
|
|
561
584
|
s.active = false;
|
|
562
585
|
s.paused = true;
|
|
563
586
|
s.pendingVerificationRetry = null;
|
|
564
587
|
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
588
|
ctx?.ui.setStatus("gsd-auto", "paused");
|
|
569
589
|
ctx?.ui.setWidget("gsd-progress", undefined);
|
|
570
590
|
ctx?.ui.setFooter(undefined);
|
|
@@ -586,31 +606,33 @@ export async function startAuto(
|
|
|
586
606
|
const requestedStepMode = options?.step ?? false;
|
|
587
607
|
|
|
588
608
|
// 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
609
|
base = escapeStaleWorktree(base);
|
|
592
610
|
|
|
593
611
|
// If resuming from paused state, just re-activate and dispatch next unit.
|
|
594
|
-
// The conversation is still intact — no need to reinitialize everything.
|
|
595
612
|
if (s.paused) {
|
|
613
|
+
// Re-acquire session lock before resuming
|
|
614
|
+
const resumeLock = acquireSessionLock(base);
|
|
615
|
+
if (!resumeLock.acquired) {
|
|
616
|
+
ctx.ui.notify(
|
|
617
|
+
`Cannot resume: ${resumeLock.reason}`,
|
|
618
|
+
"error",
|
|
619
|
+
);
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
|
|
596
623
|
s.paused = false;
|
|
597
624
|
s.active = true;
|
|
598
625
|
s.verbose = verboseMode;
|
|
599
|
-
// Allow switching between step/auto on resume
|
|
600
626
|
s.stepMode = requestedStepMode;
|
|
601
627
|
s.cmdCtx = ctx;
|
|
602
628
|
s.basePath = base;
|
|
603
629
|
s.unitDispatchCount.clear();
|
|
604
630
|
s.unitLifetimeDispatches.clear();
|
|
605
631
|
s.unitConsecutiveSkips.clear();
|
|
606
|
-
// Re-initialize metrics in case ledger was lost during pause
|
|
607
632
|
if (!getLedger()) initMetrics(base);
|
|
608
|
-
// Ensure milestone ID is set on git service for integration branch resolution
|
|
609
633
|
if (s.currentMilestoneId) setActiveMilestoneId(base, s.currentMilestoneId);
|
|
610
634
|
|
|
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).
|
|
635
|
+
// ── Auto-worktree: re-enter worktree on resume ──
|
|
614
636
|
if (s.currentMilestoneId && shouldUseWorktreeIsolation() && s.originalBasePath && !isInAutoWorktree(s.basePath) && !detectWorktreeName(s.basePath) && !detectWorktreeName(s.originalBasePath)) {
|
|
615
637
|
try {
|
|
616
638
|
const existingWtPath = getAutoWorktreePath(s.originalBasePath, s.currentMilestoneId);
|
|
@@ -620,7 +642,6 @@ export async function startAuto(
|
|
|
620
642
|
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
621
643
|
ctx.ui.notify(`Re-entered auto-worktree at ${wtPath}`, "info");
|
|
622
644
|
} else {
|
|
623
|
-
// Worktree was deleted while paused — recreate it.
|
|
624
645
|
const wtPath = createAutoWorktree(s.originalBasePath, s.currentMilestoneId);
|
|
625
646
|
s.basePath = wtPath;
|
|
626
647
|
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
@@ -634,28 +655,22 @@ export async function startAuto(
|
|
|
634
655
|
}
|
|
635
656
|
}
|
|
636
657
|
|
|
637
|
-
// Re-register SIGTERM handler for the resumed session (use original base for lock)
|
|
638
658
|
registerSigtermHandler(lockBase());
|
|
639
659
|
|
|
640
660
|
ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
|
|
641
661
|
ctx.ui.setFooter(hideFooter);
|
|
642
662
|
ctx.ui.notify(s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "info");
|
|
643
|
-
// Restore hook state from disk in case session was interrupted
|
|
644
663
|
restoreHookState(s.basePath);
|
|
645
|
-
|
|
646
|
-
try { await rebuildState(s.basePath); } catch { /* non-fatal */ }
|
|
664
|
+
try { await rebuildState(s.basePath); } catch (e) { debugLog("resume-rebuild-state-failed", { error: e instanceof Error ? e.message : String(e) }); }
|
|
647
665
|
try {
|
|
648
666
|
const report = await runGSDDoctor(s.basePath, { fix: true });
|
|
649
667
|
if (report.fixesApplied.length > 0) {
|
|
650
668
|
ctx.ui.notify(`Resume: applied ${report.fixesApplied.length} fix(es) to state.`, "info");
|
|
651
669
|
}
|
|
652
|
-
} catch {
|
|
653
|
-
// Self-heal: clear stale runtime records where artifacts already exist
|
|
670
|
+
} catch (e) { debugLog("resume-doctor-failed", { error: e instanceof Error ? e.message : String(e) }); }
|
|
654
671
|
await selfHealRuntimeRecords(s.basePath, ctx, s.completedKeySet);
|
|
655
672
|
invalidateAllCaches();
|
|
656
673
|
|
|
657
|
-
// Synthesize recovery briefing from the paused session so the next agent
|
|
658
|
-
// knows what already happened (reuses crash recovery infrastructure).
|
|
659
674
|
if (s.pausedSessionFile) {
|
|
660
675
|
const activityDir = join(gsdRoot(s.basePath), "activity");
|
|
661
676
|
const recovery = synthesizeCrashRecovery(
|
|
@@ -674,462 +689,54 @@ export async function startAuto(
|
|
|
674
689
|
s.pausedSessionFile = null;
|
|
675
690
|
}
|
|
676
691
|
|
|
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) {
|
|
692
|
+
// If resuming from a secrets pause, re-collect before dispatching (#1146)
|
|
693
|
+
if (s.pausedForSecrets && s.currentMilestoneId) {
|
|
703
694
|
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 {
|
|
695
|
+
const manifestStatus = await getManifestStatus(s.basePath, s.currentMilestoneId);
|
|
696
|
+
if (manifestStatus && manifestStatus.pending.length > 0) {
|
|
697
|
+
const result = await collectSecretsFromManifest(s.basePath, s.currentMilestoneId, ctx);
|
|
698
|
+
if (result && result.applied.length > 0) {
|
|
699
|
+
ctx.ui.notify(
|
|
700
|
+
`Secrets collected: ${result.applied.length} applied, ${result.skipped.length} skipped, ${result.existingSkipped.length} already set.`,
|
|
701
|
+
"info",
|
|
702
|
+
);
|
|
703
|
+
} else if (result && result.applied.length === 0 && result.skipped.length > 0) {
|
|
704
|
+
// All keys were skipped — still pending, re-pause
|
|
705
|
+
s.paused = true;
|
|
706
|
+
s.active = false;
|
|
707
|
+
ctx.ui.notify(
|
|
708
|
+
`All env variables were skipped. Auto-mode remains paused.\nCollect them with /gsd secrets, then resume with /gsd auto.`,
|
|
709
|
+
"warning",
|
|
710
|
+
);
|
|
711
|
+
ctx.ui.setStatus("gsd-auto", "paused");
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
} catch (err) {
|
|
751
716
|
ctx.ui.notify(
|
|
752
|
-
|
|
717
|
+
`Secrets check error: ${err instanceof Error ? err.message : String(err)}. Continuing without secrets.`,
|
|
753
718
|
"warning",
|
|
754
719
|
);
|
|
755
720
|
}
|
|
721
|
+
s.pausedForSecrets = false;
|
|
756
722
|
}
|
|
757
|
-
clearLock(base);
|
|
758
|
-
}
|
|
759
723
|
|
|
760
|
-
|
|
761
|
-
|
|
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
|
-
|
|
784
|
-
// ── Clean stale runtime unit files for completed milestones (#887) ───────
|
|
785
|
-
// After resource-update restart, stale runtime/units/*.json files from
|
|
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
|
-
}
|
|
724
|
+
updateSessionLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown", s.completedUnits.length);
|
|
725
|
+
writeLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown", s.completedUnits.length);
|
|
909
726
|
|
|
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 });
|
|
727
|
+
await dispatchNextUnit(ctx, pi);
|
|
917
728
|
return;
|
|
918
729
|
}
|
|
919
730
|
|
|
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);
|
|
731
|
+
// ── Fresh start path — delegated to auto-start.ts ──
|
|
732
|
+
const bootstrapDeps: BootstrapDeps = {
|
|
733
|
+
shouldUseWorktreeIsolation,
|
|
734
|
+
registerSigtermHandler,
|
|
735
|
+
lockBase,
|
|
973
736
|
};
|
|
974
737
|
|
|
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 */ }
|
|
738
|
+
const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps);
|
|
739
|
+
if (!ready) return;
|
|
1133
740
|
|
|
1134
741
|
// Dispatch the first unit
|
|
1135
742
|
await dispatchNextUnit(ctx, pi);
|
|
@@ -1137,19 +744,22 @@ export async function startAuto(
|
|
|
1137
744
|
|
|
1138
745
|
// ─── Agent End Handler ────────────────────────────────────────────────────────
|
|
1139
746
|
|
|
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. */
|
|
747
|
+
/** Guard against concurrent handleAgentEnd execution. */
|
|
1146
748
|
|
|
1147
749
|
export async function handleAgentEnd(
|
|
1148
750
|
ctx: ExtensionContext,
|
|
1149
751
|
pi: ExtensionAPI,
|
|
1150
752
|
): Promise<void> {
|
|
1151
753
|
if (!s.active || !s.cmdCtx) return;
|
|
1152
|
-
if (s.handlingAgentEnd)
|
|
754
|
+
if (s.handlingAgentEnd) {
|
|
755
|
+
// Another agent_end arrived while we're still processing the previous one.
|
|
756
|
+
// This happens when a unit dispatched inside handleAgentEnd (e.g. via hooks,
|
|
757
|
+
// triage, or quick-task early-dispatch paths) completes before the outer
|
|
758
|
+
// handleAgentEnd returns. Queue a retry so the completed unit's agent_end
|
|
759
|
+
// is not silently dropped (#1072).
|
|
760
|
+
s.pendingAgentEndRetry = true;
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
1153
763
|
s.handlingAgentEnd = true;
|
|
1154
764
|
|
|
1155
765
|
try {
|
|
@@ -1157,714 +767,94 @@ export async function handleAgentEnd(
|
|
|
1157
767
|
// Unit completed — clear its timeout
|
|
1158
768
|
clearUnitTimeout();
|
|
1159
769
|
|
|
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
|
-
}
|
|
1369
|
-
|
|
1370
|
-
// ── Path A fix: verify artifact and persist completion before re-entering dispatch ──
|
|
1371
|
-
// After doctor + rebuildState, check whether the just-completed unit actually
|
|
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
|
-
}
|
|
1408
|
-
|
|
1409
|
-
// ── Verification gate: run typecheck/lint/test after execute-task ──
|
|
1410
|
-
if (s.currentUnit && s.currentUnit.type === "execute-task") {
|
|
1411
|
-
try {
|
|
1412
|
-
const effectivePrefs = loadEffectiveGSDPreferences();
|
|
1413
|
-
const prefs = effectivePrefs?.preferences;
|
|
1414
|
-
|
|
1415
|
-
// Read task plan verify field from the current task's slice plan
|
|
1416
|
-
// unitId format is "M001/S01/T03" — extract mid, sid, tid
|
|
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);
|
|
1743
|
-
|
|
1744
|
-
if (!s.active) return;
|
|
1745
|
-
pi.sendMessage(
|
|
1746
|
-
{ customType: "gsd-auto", content: prompt, display: s.verbose },
|
|
1747
|
-
{ triggerTurn: true },
|
|
1748
|
-
);
|
|
1749
|
-
return; // handleAgentEnd will fire again when triage session completes
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
} catch {
|
|
1754
|
-
// Triage check failure is non-fatal — proceed to normal dispatch
|
|
1755
|
-
}
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
// ── Quick-task dispatch: execute queued quick-tasks from triage resolution ──
|
|
1759
|
-
// Quick-tasks are self-contained one-off tasks that don't modify the plan.
|
|
1760
|
-
// They're queued during post-triage resolution and dispatched here one at a time.
|
|
1761
|
-
if (
|
|
1762
|
-
!s.stepMode &&
|
|
1763
|
-
s.pendingQuickTasks.length > 0 &&
|
|
1764
|
-
s.currentUnit &&
|
|
1765
|
-
s.currentUnit.type !== "quick-task"
|
|
1766
|
-
) {
|
|
1767
|
-
try {
|
|
1768
|
-
const capture = s.pendingQuickTasks.shift()!;
|
|
1769
|
-
const { buildQuickTaskPrompt } = await import("./triage-resolution.js");
|
|
1770
|
-
const { markCaptureExecuted } = await import("./captures.js");
|
|
1771
|
-
const prompt = buildQuickTaskPrompt(capture);
|
|
1772
|
-
|
|
1773
|
-
ctx.ui.notify(
|
|
1774
|
-
`Executing quick-task: ${capture.id} — "${capture.text}"`,
|
|
1775
|
-
"info",
|
|
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);
|
|
770
|
+
// ── Pre-verification processing (commit, doctor, state rebuild, etc.) ──
|
|
771
|
+
const postUnitCtx: PostUnitContext = {
|
|
772
|
+
s,
|
|
773
|
+
ctx,
|
|
774
|
+
pi,
|
|
775
|
+
buildSnapshotOpts,
|
|
776
|
+
lockBase,
|
|
777
|
+
stopAuto,
|
|
778
|
+
pauseAuto,
|
|
779
|
+
updateProgressWidget,
|
|
780
|
+
};
|
|
1823
781
|
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
{ customType: "gsd-auto", content: prompt, display: s.verbose },
|
|
1827
|
-
{ triggerTurn: true },
|
|
1828
|
-
);
|
|
1829
|
-
return; // handleAgentEnd will fire again when quick-task session completes
|
|
1830
|
-
} catch {
|
|
1831
|
-
// Non-fatal — proceed to normal dispatch
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
782
|
+
const preResult = await postUnitPreVerification(postUnitCtx);
|
|
783
|
+
if (preResult === "dispatched") return;
|
|
1834
784
|
|
|
1835
|
-
//
|
|
1836
|
-
|
|
785
|
+
// ── Verification gate: run typecheck/lint/test after execute-task ──
|
|
786
|
+
const verificationResult = await runPostUnitVerification(
|
|
787
|
+
{ s, ctx, pi },
|
|
788
|
+
dispatchNextUnit,
|
|
789
|
+
startDispatchGapWatchdog,
|
|
790
|
+
pauseAuto,
|
|
791
|
+
);
|
|
792
|
+
if (verificationResult === "retry" || verificationResult === "pause") return;
|
|
793
|
+
|
|
794
|
+
// ── Post-verification processing (DB dual-write, hooks, triage, quick-tasks) ──
|
|
795
|
+
const postResult = await postUnitPostVerification(postUnitCtx);
|
|
796
|
+
if (postResult === "dispatched" || postResult === "stopped") return;
|
|
797
|
+
if (postResult === "step-wizard") {
|
|
1837
798
|
await showStepWizard(ctx, pi);
|
|
1838
799
|
return;
|
|
1839
800
|
}
|
|
1840
801
|
|
|
802
|
+
// ── Dispatch with hang detection (#1073) ────────────────────────────────
|
|
803
|
+
// Start a safety watchdog BEFORE calling dispatchNextUnit. If dispatch
|
|
804
|
+
// hangs at any await (newSession, model selection, etc.), the gap watchdog
|
|
805
|
+
// inside handleAgentEnd never fires because we never reach the check.
|
|
806
|
+
// This pre-dispatch watchdog ensures recovery even when dispatchNextUnit
|
|
807
|
+
// itself is permanently blocked.
|
|
808
|
+
const dispatchHangGuard = setTimeout(() => {
|
|
809
|
+
if (!s.active) return;
|
|
810
|
+
// dispatchNextUnit has been running for too long — it's likely hung.
|
|
811
|
+
// Start the gap watchdog which will retry dispatch from scratch.
|
|
812
|
+
if (!s.unitTimeoutHandle && !s.wrapupWarningHandle) {
|
|
813
|
+
ctx.ui.notify(
|
|
814
|
+
`Dispatch hang detected (${DISPATCH_HANG_TIMEOUT_MS / 1000}s without completion). Starting recovery watchdog.`,
|
|
815
|
+
"warning",
|
|
816
|
+
);
|
|
817
|
+
startDispatchGapWatchdog(ctx, pi);
|
|
818
|
+
}
|
|
819
|
+
}, DISPATCH_HANG_TIMEOUT_MS);
|
|
820
|
+
|
|
1841
821
|
try {
|
|
1842
822
|
await dispatchNextUnit(ctx, pi);
|
|
1843
823
|
} 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
824
|
const message = dispatchErr instanceof Error ? dispatchErr.message : String(dispatchErr);
|
|
1848
825
|
ctx.ui.notify(
|
|
1849
826
|
`Dispatch error after unit completion: ${message}. Retrying in ${DISPATCH_GAP_TIMEOUT_MS / 1000}s.`,
|
|
1850
827
|
"error",
|
|
1851
828
|
);
|
|
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
829
|
startDispatchGapWatchdog(ctx, pi);
|
|
1856
830
|
return;
|
|
831
|
+
} finally {
|
|
832
|
+
clearTimeout(dispatchHangGuard);
|
|
1857
833
|
}
|
|
1858
834
|
|
|
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
835
|
if (s.active && !s.unitTimeoutHandle && !s.wrapupWarningHandle) {
|
|
1863
836
|
startDispatchGapWatchdog(ctx, pi);
|
|
1864
837
|
}
|
|
1865
838
|
|
|
1866
839
|
} finally {
|
|
1867
840
|
s.handlingAgentEnd = false;
|
|
841
|
+
|
|
842
|
+
// If an agent_end event was dropped by the reentrancy guard while we were
|
|
843
|
+
// processing, re-enter handleAgentEnd on the next microtask. This prevents
|
|
844
|
+
// the summarizing phase stall (#1072) where a unit dispatched inside
|
|
845
|
+
// handleAgentEnd (hooks, triage, quick-task) completes before we return,
|
|
846
|
+
// and its agent_end is silently dropped — leaving auto-mode active but
|
|
847
|
+
// permanently stalled with no unit running and no watchdog set.
|
|
848
|
+
if (s.pendingAgentEndRetry) {
|
|
849
|
+
s.pendingAgentEndRetry = false;
|
|
850
|
+
setImmediate(() => {
|
|
851
|
+
handleAgentEnd(ctx, pi).catch((err) => {
|
|
852
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
853
|
+
ctx.ui.notify(`Deferred agent_end retry failed: ${msg}`, "error");
|
|
854
|
+
pauseAuto(ctx, pi).catch(() => {});
|
|
855
|
+
});
|
|
856
|
+
});
|
|
857
|
+
}
|
|
1868
858
|
}
|
|
1869
859
|
}
|
|
1870
860
|
|
|
@@ -1872,8 +862,6 @@ export async function handleAgentEnd(
|
|
|
1872
862
|
|
|
1873
863
|
/**
|
|
1874
864
|
* 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
865
|
*/
|
|
1878
866
|
async function showStepWizard(
|
|
1879
867
|
ctx: ExtensionContext,
|
|
@@ -1884,15 +872,13 @@ async function showStepWizard(
|
|
|
1884
872
|
const state = await deriveState(s.basePath);
|
|
1885
873
|
const mid = state.activeMilestone?.id;
|
|
1886
874
|
|
|
1887
|
-
// Build summary of what just completed
|
|
1888
875
|
const justFinished = s.currentUnit
|
|
1889
876
|
? `${unitVerb(s.currentUnit.type)} ${s.currentUnit.id}`
|
|
1890
877
|
: "previous unit";
|
|
1891
878
|
|
|
1892
|
-
// If no active milestone or everything is complete, stop
|
|
1893
879
|
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") {
|
|
880
|
+
const incomplete = state.registry.filter(m => m.status !== "complete" && m.status !== "parked");
|
|
881
|
+
if (incomplete.length > 0 && state.phase !== "complete" && state.phase !== "blocked" && state.phase !== "pre-planning") {
|
|
1896
882
|
const ids = incomplete.map(m => m.id).join(", ");
|
|
1897
883
|
const diag = `basePath=${s.basePath}, milestones=[${state.registry.map(m => `${m.id}:${m.status}`).join(", ")}], phase=${state.phase}`;
|
|
1898
884
|
ctx.ui.notify(`Unexpected: ${incomplete.length} incomplete milestone(s) (${ids}) but no active milestone.\n Diagnostic: ${diag}`, "error");
|
|
@@ -1903,7 +889,6 @@ async function showStepWizard(
|
|
|
1903
889
|
return;
|
|
1904
890
|
}
|
|
1905
891
|
|
|
1906
|
-
// Peek at what's next by examining state
|
|
1907
892
|
const nextDesc = _describeNextUnit(state);
|
|
1908
893
|
|
|
1909
894
|
const choice = await showNextAction(s.cmdCtx, {
|
|
@@ -1941,12 +926,10 @@ async function showStepWizard(
|
|
|
1941
926
|
ctx.ui.notify("Switched to auto-mode.", "info");
|
|
1942
927
|
await dispatchNextUnit(ctx, pi);
|
|
1943
928
|
} else if (choice === "status") {
|
|
1944
|
-
// Show status then re-show the wizard
|
|
1945
929
|
const { fireStatusViaCommand } = await import("./commands.js");
|
|
1946
930
|
await fireStatusViaCommand(ctx as ExtensionCommandContext);
|
|
1947
931
|
await showStepWizard(ctx, pi);
|
|
1948
932
|
} else {
|
|
1949
|
-
// "not_yet" — pause
|
|
1950
933
|
await pauseAuto(ctx, pi);
|
|
1951
934
|
}
|
|
1952
935
|
}
|
|
@@ -1978,15 +961,6 @@ const widgetStateAccessors: WidgetStateAccessors = {
|
|
|
1978
961
|
|
|
1979
962
|
// ─── Core Loop ────────────────────────────────────────────────────────────────
|
|
1980
963
|
|
|
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
964
|
async function dispatchNextUnit(
|
|
1991
965
|
ctx: ExtensionContext,
|
|
1992
966
|
pi: ExtensionAPI,
|
|
@@ -1999,44 +973,50 @@ async function dispatchNextUnit(
|
|
|
1999
973
|
return;
|
|
2000
974
|
}
|
|
2001
975
|
|
|
2002
|
-
//
|
|
2003
|
-
|
|
976
|
+
// ── Session lock validation: detect if another process has taken over ──
|
|
977
|
+
if (lockBase() && !validateSessionLock(lockBase())) {
|
|
978
|
+
debugLog("dispatchNextUnit session-lock-lost — another process may have taken over");
|
|
979
|
+
ctx.ui.notify(
|
|
980
|
+
"Session lock lost — another GSD process appears to have taken over. Stopping gracefully.",
|
|
981
|
+
"error",
|
|
982
|
+
);
|
|
983
|
+
// Don't call stopAuto here to avoid releasing the lock we don't own
|
|
984
|
+
s.active = false;
|
|
985
|
+
s.paused = false;
|
|
986
|
+
clearUnitTimeout();
|
|
987
|
+
deregisterSigtermHandler();
|
|
988
|
+
ctx.ui.setStatus("gsd-auto", undefined);
|
|
989
|
+
ctx.ui.setWidget("gsd-progress", undefined);
|
|
990
|
+
ctx.ui.setFooter(undefined);
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Reentrancy guard
|
|
2004
995
|
if (s.dispatching && s.skipDepth === 0) {
|
|
2005
996
|
debugLog("dispatchNextUnit reentrancy guard — another dispatch in progress, bailing");
|
|
2006
|
-
return;
|
|
997
|
+
return;
|
|
2007
998
|
}
|
|
2008
999
|
s.dispatching = true;
|
|
2009
1000
|
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.
|
|
1001
|
+
// Recursion depth guard
|
|
2013
1002
|
if (s.skipDepth > MAX_SKIP_DEPTH) {
|
|
2014
1003
|
s.skipDepth = 0;
|
|
2015
1004
|
ctx.ui.notify(`Skipped ${MAX_SKIP_DEPTH}+ completed units. Yielding to UI before continuing.`, "info");
|
|
2016
1005
|
await new Promise(r => setTimeout(r, 200));
|
|
2017
1006
|
}
|
|
2018
1007
|
|
|
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.
|
|
1008
|
+
// Resource version guard
|
|
2024
1009
|
const staleMsg = checkResourcesStale(s.resourceVersionOnStart);
|
|
2025
1010
|
if (staleMsg) {
|
|
2026
1011
|
await stopAuto(ctx, pi, staleMsg);
|
|
2027
1012
|
return;
|
|
2028
1013
|
}
|
|
2029
1014
|
|
|
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
1015
|
invalidateAllCaches();
|
|
2034
1016
|
s.lastPromptCharCount = undefined;
|
|
2035
1017
|
s.lastBaselineCharCount = undefined;
|
|
2036
1018
|
|
|
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.
|
|
1019
|
+
// ── Pre-dispatch health gate ──
|
|
2040
1020
|
try {
|
|
2041
1021
|
const healthGate = await preDispatchHealthGate(s.basePath);
|
|
2042
1022
|
if (healthGate.fixesApplied.length > 0) {
|
|
@@ -2048,13 +1028,10 @@ async function dispatchNextUnit(
|
|
|
2048
1028
|
return;
|
|
2049
1029
|
}
|
|
2050
1030
|
} catch {
|
|
2051
|
-
// Non-fatal
|
|
1031
|
+
// Non-fatal
|
|
2052
1032
|
}
|
|
2053
1033
|
|
|
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.
|
|
1034
|
+
// ── Sync project root artifacts into worktree ──
|
|
2058
1035
|
if (s.originalBasePath && s.basePath !== s.originalBasePath && s.currentMilestoneId) {
|
|
2059
1036
|
syncProjectRootToWorktree(s.originalBasePath, s.basePath, s.currentMilestoneId);
|
|
2060
1037
|
}
|
|
@@ -2077,12 +1054,10 @@ async function dispatchNextUnit(
|
|
|
2077
1054
|
"info",
|
|
2078
1055
|
);
|
|
2079
1056
|
sendDesktopNotification("GSD", `Milestone ${s.currentMilestoneId} complete!`, "success", "milestone");
|
|
2080
|
-
// Hint: visualizer available after milestone transition
|
|
2081
1057
|
const vizPrefs = loadEffectiveGSDPreferences()?.preferences;
|
|
2082
1058
|
if (vizPrefs?.auto_visualize) {
|
|
2083
1059
|
ctx.ui.notify("Run /gsd visualize to see progress overview.", "info");
|
|
2084
1060
|
}
|
|
2085
|
-
// Auto-generate HTML report snapshot on milestone completion (default: on, disable with auto_report: false)
|
|
2086
1061
|
if (vizPrefs?.auto_report !== false) {
|
|
2087
1062
|
try {
|
|
2088
1063
|
const { loadVisualizerData } = await import("./visualizer-data.js");
|
|
@@ -2135,20 +1110,15 @@ async function dispatchNextUnit(
|
|
|
2135
1110
|
s.unitRecoveryCount.clear();
|
|
2136
1111
|
s.unitConsecutiveSkips.clear();
|
|
2137
1112
|
s.unitLifetimeDispatches.clear();
|
|
2138
|
-
// Clear completed-units.json for the finished milestone
|
|
2139
1113
|
try {
|
|
2140
1114
|
const file = completedKeysPath(s.basePath);
|
|
2141
|
-
if (existsSync(file))
|
|
1115
|
+
if (existsSync(file)) {
|
|
1116
|
+
atomicWriteSync(file, JSON.stringify([]));
|
|
1117
|
+
}
|
|
2142
1118
|
s.completedKeySet.clear();
|
|
2143
|
-
} catch {
|
|
2144
|
-
|
|
2145
|
-
// ── Worktree lifecycle on milestone transition (#616)
|
|
2146
|
-
// When transitioning from M_old to M_new inside a worktree, we must:
|
|
2147
|
-
// 1. Merge the completed milestone's worktree back to main
|
|
2148
|
-
// 2. Re-derive state from the project root
|
|
2149
|
-
// 3. Create a new worktree for the incoming milestone
|
|
2150
|
-
// Without this, M_new runs inside M_old's worktree on the wrong branch,
|
|
2151
|
-
// and artifact paths resolve against the wrong .gsd/ directory.
|
|
1119
|
+
} catch (e) { debugLog("completed-keys-reset-failed", { error: e instanceof Error ? e.message : String(e) }); }
|
|
1120
|
+
|
|
1121
|
+
// ── Worktree lifecycle on milestone transition (#616) ──
|
|
2152
1122
|
if (isInAutoWorktree(s.basePath) && s.originalBasePath && shouldUseWorktreeIsolation()) {
|
|
2153
1123
|
try {
|
|
2154
1124
|
const roadmapPath = resolveMilestoneFile(s.originalBasePath, s.currentMilestoneId, "ROADMAP");
|
|
@@ -2160,7 +1130,6 @@ async function dispatchNextUnit(
|
|
|
2160
1130
|
"info",
|
|
2161
1131
|
);
|
|
2162
1132
|
} else {
|
|
2163
|
-
// No roadmap found — teardown worktree without merge
|
|
2164
1133
|
teardownAutoWorktree(s.originalBasePath, s.currentMilestoneId);
|
|
2165
1134
|
ctx.ui.notify(`Exited worktree for ${ s.currentMilestoneId } (no roadmap for merge).`, "info");
|
|
2166
1135
|
}
|
|
@@ -2169,23 +1138,19 @@ async function dispatchNextUnit(
|
|
|
2169
1138
|
`Milestone merge failed during transition: ${err instanceof Error ? err.message : String(err)}`,
|
|
2170
1139
|
"warning",
|
|
2171
1140
|
);
|
|
2172
|
-
// Force cwd back to project root even if merge failed
|
|
2173
1141
|
if (s.originalBasePath) {
|
|
2174
1142
|
try { process.chdir(s.originalBasePath); } catch { /* best-effort */ }
|
|
2175
1143
|
}
|
|
2176
1144
|
}
|
|
2177
1145
|
|
|
2178
|
-
// Update s.basePath to project root (mergeMilestoneToMain already chdir'd)
|
|
2179
1146
|
s.basePath = s.originalBasePath;
|
|
2180
1147
|
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
2181
1148
|
invalidateAllCaches();
|
|
2182
1149
|
|
|
2183
|
-
// Re-derive state from project root before creating new worktree
|
|
2184
1150
|
state = await deriveState(s.basePath);
|
|
2185
1151
|
mid = state.activeMilestone?.id;
|
|
2186
1152
|
midTitle = state.activeMilestone?.title;
|
|
2187
1153
|
|
|
2188
|
-
// Create new worktree for the incoming milestone
|
|
2189
1154
|
if (mid) {
|
|
2190
1155
|
captureIntegrationBranch(s.basePath, mid, { commitDocs: loadEffectiveGSDPreferences()?.preferences?.git?.commit_docs });
|
|
2191
1156
|
try {
|
|
@@ -2201,14 +1166,11 @@ async function dispatchNextUnit(
|
|
|
2201
1166
|
}
|
|
2202
1167
|
}
|
|
2203
1168
|
} else {
|
|
2204
|
-
// Not in worktree — capture integration branch for the new milestone (branch mode only).
|
|
2205
|
-
// In none mode there's no milestone branch to merge back to, so skip.
|
|
2206
1169
|
if (getIsolationMode() !== "none") {
|
|
2207
1170
|
captureIntegrationBranch(s.originalBasePath || s.basePath, mid, { commitDocs: loadEffectiveGSDPreferences()?.preferences?.git?.commit_docs });
|
|
2208
1171
|
}
|
|
2209
1172
|
}
|
|
2210
1173
|
|
|
2211
|
-
// Prune completed milestone from queue order file
|
|
2212
1174
|
const pendingIds = state.registry
|
|
2213
1175
|
.filter(m => m.status !== "complete")
|
|
2214
1176
|
.map(m => m.id);
|
|
@@ -2220,24 +1182,67 @@ async function dispatchNextUnit(
|
|
|
2220
1182
|
}
|
|
2221
1183
|
|
|
2222
1184
|
if (!mid) {
|
|
2223
|
-
// Save final session before stopping
|
|
2224
1185
|
if (s.currentUnit) {
|
|
2225
1186
|
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
2226
1187
|
}
|
|
2227
1188
|
|
|
2228
|
-
const incomplete = state.registry.filter(m => m.status !== "complete");
|
|
1189
|
+
const incomplete = state.registry.filter(m => m.status !== "complete" && m.status !== "parked");
|
|
2229
1190
|
if (incomplete.length === 0) {
|
|
2230
|
-
// Genuinely all complete
|
|
1191
|
+
// Genuinely all complete (parked milestones excluded) — merge milestone branch to main before stopping (#962)
|
|
1192
|
+
if (s.currentMilestoneId && isInAutoWorktree(s.basePath) && s.originalBasePath) {
|
|
1193
|
+
try {
|
|
1194
|
+
const roadmapPath = resolveMilestoneFile(s.originalBasePath, s.currentMilestoneId, "ROADMAP");
|
|
1195
|
+
if (roadmapPath) {
|
|
1196
|
+
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
1197
|
+
const mergeResult = mergeMilestoneToMain(s.originalBasePath, s.currentMilestoneId, roadmapContent);
|
|
1198
|
+
s.basePath = s.originalBasePath;
|
|
1199
|
+
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
1200
|
+
ctx.ui.notify(
|
|
1201
|
+
`Milestone ${ s.currentMilestoneId } merged to main.${mergeResult.pushed ? " Pushed to remote." : ""}`,
|
|
1202
|
+
"info",
|
|
1203
|
+
);
|
|
1204
|
+
}
|
|
1205
|
+
} catch (err) {
|
|
1206
|
+
ctx.ui.notify(
|
|
1207
|
+
`Milestone merge failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1208
|
+
"warning",
|
|
1209
|
+
);
|
|
1210
|
+
if (s.originalBasePath) {
|
|
1211
|
+
s.basePath = s.originalBasePath;
|
|
1212
|
+
try { process.chdir(s.basePath); } catch { /* best-effort */ }
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
} else if (s.currentMilestoneId && !isInAutoWorktree(s.basePath) && getIsolationMode() !== "none") {
|
|
1216
|
+
try {
|
|
1217
|
+
const currentBranch = getCurrentBranch(s.basePath);
|
|
1218
|
+
const milestoneBranch = autoWorktreeBranch(s.currentMilestoneId);
|
|
1219
|
+
if (currentBranch === milestoneBranch) {
|
|
1220
|
+
const roadmapPath = resolveMilestoneFile(s.basePath, s.currentMilestoneId, "ROADMAP");
|
|
1221
|
+
if (roadmapPath) {
|
|
1222
|
+
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
1223
|
+
const mergeResult = mergeMilestoneToMain(s.basePath, s.currentMilestoneId, roadmapContent);
|
|
1224
|
+
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
1225
|
+
ctx.ui.notify(
|
|
1226
|
+
`Milestone ${ s.currentMilestoneId } merged (branch mode).${mergeResult.pushed ? " Pushed to remote." : ""}`,
|
|
1227
|
+
"info",
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
} catch (err) {
|
|
1232
|
+
ctx.ui.notify(
|
|
1233
|
+
`Milestone merge failed (branch mode): ${err instanceof Error ? err.message : String(err)}`,
|
|
1234
|
+
"warning",
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
2231
1238
|
sendDesktopNotification("GSD", "All milestones complete!", "success", "milestone");
|
|
2232
1239
|
await stopAuto(ctx, pi, "All milestones complete");
|
|
2233
1240
|
} else if (state.phase === "blocked") {
|
|
2234
|
-
// Milestones exist but are dependency-blocked
|
|
2235
1241
|
const blockerMsg = `Blocked: ${state.blockers.join(", ")}`;
|
|
2236
1242
|
await stopAuto(ctx, pi, blockerMsg);
|
|
2237
1243
|
ctx.ui.notify(`${blockerMsg}. Fix and run /gsd auto.`, "warning");
|
|
2238
1244
|
sendDesktopNotification("GSD", blockerMsg, "error", "attention");
|
|
2239
1245
|
} else {
|
|
2240
|
-
// Milestones with remaining work exist but none became s.active — unexpected
|
|
2241
1246
|
const ids = incomplete.map(m => m.id).join(", ");
|
|
2242
1247
|
const diag = `basePath=${s.basePath}, milestones=[${state.registry.map(m => `${m.id}:${m.status}`).join(", ")}], phase=${state.phase}`;
|
|
2243
1248
|
ctx.ui.notify(`Unexpected: ${incomplete.length} incomplete milestone(s) (${ids}) but no active milestone.\n Diagnostic: ${diag}`, "error");
|
|
@@ -2246,15 +1251,12 @@ async function dispatchNextUnit(
|
|
|
2246
1251
|
return;
|
|
2247
1252
|
}
|
|
2248
1253
|
|
|
2249
|
-
// Guard: mid/midTitle must be defined strings from this point onward.
|
|
2250
|
-
// The !mid check above returns early if mid is falsy; midTitle comes from
|
|
2251
|
-
// the same object so it should always be present when mid is.
|
|
2252
1254
|
if (!midTitle) {
|
|
2253
|
-
midTitle = mid;
|
|
1255
|
+
midTitle = mid;
|
|
2254
1256
|
ctx.ui.notify(`Milestone ${mid} has no title in roadmap — using ID as fallback.`, "warning");
|
|
2255
1257
|
}
|
|
2256
1258
|
|
|
2257
|
-
// ── Mid-merge safety check
|
|
1259
|
+
// ── Mid-merge safety check ──
|
|
2258
1260
|
if (reconcileMergeState(s.basePath, ctx)) {
|
|
2259
1261
|
invalidateAllCaches();
|
|
2260
1262
|
state = await deriveState(s.basePath);
|
|
@@ -2262,7 +1264,6 @@ async function dispatchNextUnit(
|
|
|
2262
1264
|
midTitle = state.activeMilestone?.title;
|
|
2263
1265
|
}
|
|
2264
1266
|
|
|
2265
|
-
// After merge guard removal (branchless architecture), mid/midTitle could be undefined
|
|
2266
1267
|
if (!mid || !midTitle) {
|
|
2267
1268
|
if (s.currentUnit) {
|
|
2268
1269
|
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
@@ -2283,17 +1284,18 @@ async function dispatchNextUnit(
|
|
|
2283
1284
|
if (s.currentUnit) {
|
|
2284
1285
|
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
2285
1286
|
}
|
|
2286
|
-
// Clear completed-units.json for the finished milestone so it doesn't grow unbounded.
|
|
2287
1287
|
try {
|
|
2288
1288
|
const file = completedKeysPath(s.basePath);
|
|
2289
|
-
if (existsSync(file))
|
|
1289
|
+
if (existsSync(file)) {
|
|
1290
|
+
atomicWriteSync(file, JSON.stringify([]));
|
|
1291
|
+
}
|
|
2290
1292
|
s.completedKeySet.clear();
|
|
2291
|
-
} catch {
|
|
2292
|
-
// ── Milestone merge
|
|
1293
|
+
} catch (e) { debugLog("completed-keys-reset-failed", { error: e instanceof Error ? e.message : String(e) }); }
|
|
1294
|
+
// ── Milestone merge ──
|
|
2293
1295
|
if (s.currentMilestoneId && isInAutoWorktree(s.basePath) && s.originalBasePath) {
|
|
2294
1296
|
try {
|
|
2295
1297
|
const roadmapPath = resolveMilestoneFile(s.originalBasePath, s.currentMilestoneId, "ROADMAP");
|
|
2296
|
-
if (!roadmapPath) throw new
|
|
1298
|
+
if (!roadmapPath) throw new GSDError(GSD_ARTIFACT_MISSING, `Cannot resolve ROADMAP file for milestone ${ s.currentMilestoneId }`);
|
|
2297
1299
|
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
2298
1300
|
const mergeResult = mergeMilestoneToMain(s.originalBasePath, s.currentMilestoneId, roadmapContent);
|
|
2299
1301
|
s.basePath = s.originalBasePath;
|
|
@@ -2307,17 +1309,12 @@ async function dispatchNextUnit(
|
|
|
2307
1309
|
`Milestone merge failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2308
1310
|
"warning",
|
|
2309
1311
|
);
|
|
2310
|
-
// Ensure cwd is restored even if merge failed partway through (#608).
|
|
2311
|
-
// mergeMilestoneToMain may have chdir'd but then thrown, leaving us
|
|
2312
|
-
// in an indeterminate location.
|
|
2313
1312
|
if (s.originalBasePath) {
|
|
2314
1313
|
s.basePath = s.originalBasePath;
|
|
2315
1314
|
try { process.chdir(s.basePath); } catch { /* best-effort */ }
|
|
2316
1315
|
}
|
|
2317
1316
|
}
|
|
2318
1317
|
} else if (s.currentMilestoneId && !isInAutoWorktree(s.basePath) && getIsolationMode() !== "none") {
|
|
2319
|
-
// Branch isolation mode (#603): no worktree, but we may be on a milestone/* branch.
|
|
2320
|
-
// Squash-merge back to the integration branch (or main) before stopping.
|
|
2321
1318
|
try {
|
|
2322
1319
|
const currentBranch = getCurrentBranch(s.basePath);
|
|
2323
1320
|
const milestoneBranch = autoWorktreeBranch(s.currentMilestoneId);
|
|
@@ -2325,8 +1322,6 @@ async function dispatchNextUnit(
|
|
|
2325
1322
|
const roadmapPath = resolveMilestoneFile(s.basePath, s.currentMilestoneId, "ROADMAP");
|
|
2326
1323
|
if (roadmapPath) {
|
|
2327
1324
|
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
2328
|
-
// mergeMilestoneToMain handles: auto-commit, checkout integration branch,
|
|
2329
|
-
// squash merge, commit, optional push, branch deletion.
|
|
2330
1325
|
const mergeResult = mergeMilestoneToMain(s.basePath, s.currentMilestoneId, roadmapContent);
|
|
2331
1326
|
s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
2332
1327
|
ctx.ui.notify(
|
|
@@ -2358,11 +1353,9 @@ async function dispatchNextUnit(
|
|
|
2358
1353
|
return;
|
|
2359
1354
|
}
|
|
2360
1355
|
|
|
2361
|
-
//
|
|
2362
|
-
// Ensures the UAT file and slice summary are both on main when UAT runs.
|
|
1356
|
+
// Budget ceiling guard, context window guard, secrets gate, dispatch table
|
|
2363
1357
|
const prefs = loadEffectiveGSDPreferences()?.preferences;
|
|
2364
1358
|
|
|
2365
|
-
// Budget ceiling guard — enforce budget with configurable action
|
|
2366
1359
|
const budgetCeiling = prefs?.budget_ceiling;
|
|
2367
1360
|
if (budgetCeiling !== undefined && budgetCeiling > 0) {
|
|
2368
1361
|
const currentLedger = getLedger();
|
|
@@ -2409,8 +1402,7 @@ async function dispatchNextUnit(
|
|
|
2409
1402
|
s.lastBudgetAlertLevel = 0;
|
|
2410
1403
|
}
|
|
2411
1404
|
|
|
2412
|
-
|
|
2413
|
-
const contextThreshold = prefs?.context_pause_threshold ?? 0; // 0 = disabled by default
|
|
1405
|
+
const contextThreshold = prefs?.context_pause_threshold ?? 0;
|
|
2414
1406
|
if (contextThreshold > 0 && s.cmdCtx) {
|
|
2415
1407
|
const contextUsage = s.cmdCtx.getContextUsage();
|
|
2416
1408
|
if (contextUsage && contextUsage.percent !== null && contextUsage.percent >= contextThreshold) {
|
|
@@ -2422,11 +1414,7 @@ async function dispatchNextUnit(
|
|
|
2422
1414
|
}
|
|
2423
1415
|
}
|
|
2424
1416
|
|
|
2425
|
-
//
|
|
2426
|
-
// plan-milestone writes the milestone SECRETS file (e.g., M001-SECRETS.md) during its unit. By the time we
|
|
2427
|
-
// reach the next dispatchNextUnit call the manifest exists but hasn't been
|
|
2428
|
-
// presented to the user yet. Without this re-check the model would proceed
|
|
2429
|
-
// into plan-slice / execute-task with no real credentials and mock everything.
|
|
1417
|
+
// Secrets re-check gate
|
|
2430
1418
|
const runSecretsGate = async () => {
|
|
2431
1419
|
try {
|
|
2432
1420
|
const manifestStatus = await getManifestStatus(s.basePath, mid);
|
|
@@ -2451,7 +1439,7 @@ async function dispatchNextUnit(
|
|
|
2451
1439
|
|
|
2452
1440
|
await runSecretsGate();
|
|
2453
1441
|
|
|
2454
|
-
// ── Dispatch table
|
|
1442
|
+
// ── Dispatch table ──
|
|
2455
1443
|
const dispatchResult = await resolveDispatch({ basePath: s.basePath, mid, midTitle: midTitle!, state, prefs,
|
|
2456
1444
|
});
|
|
2457
1445
|
|
|
@@ -2464,7 +1452,6 @@ async function dispatchNextUnit(
|
|
|
2464
1452
|
}
|
|
2465
1453
|
|
|
2466
1454
|
if (dispatchResult.action !== "dispatch") {
|
|
2467
|
-
// skip action — yield and re-dispatch
|
|
2468
1455
|
await new Promise(r => setImmediate(r));
|
|
2469
1456
|
await dispatchNextUnit(ctx, pi);
|
|
2470
1457
|
return;
|
|
@@ -2475,7 +1462,7 @@ async function dispatchNextUnit(
|
|
|
2475
1462
|
prompt = dispatchResult.prompt;
|
|
2476
1463
|
let pauseAfterUatDispatch = dispatchResult.pauseAfterDispatch ?? false;
|
|
2477
1464
|
|
|
2478
|
-
// ── Pre-dispatch hooks
|
|
1465
|
+
// ── Pre-dispatch hooks ──
|
|
2479
1466
|
const preDispatchResult = runPreDispatchHooks(unitType, unitId, prompt, s.basePath);
|
|
2480
1467
|
if (preDispatchResult.firedHooks.length > 0) {
|
|
2481
1468
|
ctx.ui.notify(
|
|
@@ -2485,7 +1472,6 @@ async function dispatchNextUnit(
|
|
|
2485
1472
|
}
|
|
2486
1473
|
if (preDispatchResult.action === "skip") {
|
|
2487
1474
|
ctx.ui.notify(`Skipping ${unitType} ${unitId} (pre-dispatch hook).`, "info");
|
|
2488
|
-
// Yield then re-dispatch to advance to next unit
|
|
2489
1475
|
await new Promise(r => setImmediate(r));
|
|
2490
1476
|
await dispatchNextUnit(ctx, pi);
|
|
2491
1477
|
return;
|
|
@@ -2505,378 +1491,76 @@ async function dispatchNextUnit(
|
|
|
2505
1491
|
|
|
2506
1492
|
const observabilityIssues = await _collectObservabilityWarnings(ctx, s.basePath, unitType, unitId);
|
|
2507
1493
|
|
|
2508
|
-
// Idempotency
|
|
2509
|
-
const
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
// key so the next dispatch forces full reconciliation instead of looping.
|
|
2517
|
-
const skipCount = (s.unitConsecutiveSkips.get(idempotencyKey) ?? 0) + 1;
|
|
2518
|
-
s.unitConsecutiveSkips.set(idempotencyKey, skipCount);
|
|
2519
|
-
if (skipCount > MAX_CONSECUTIVE_SKIPS) {
|
|
2520
|
-
// Cross-check: verify deriveState actually returns this unit (#790).
|
|
2521
|
-
// If the unit's milestone is already complete, this is a phantom skip
|
|
2522
|
-
// loop from stale crash recovery context — don't evict.
|
|
2523
|
-
const skippedMid = unitId.split("/")[0];
|
|
2524
|
-
const skippedMilestoneComplete = skippedMid
|
|
2525
|
-
? !!resolveMilestoneFile(s.basePath, skippedMid, "SUMMARY")
|
|
2526
|
-
: false;
|
|
2527
|
-
if (skippedMilestoneComplete) {
|
|
2528
|
-
// Milestone is complete — evicting this key would fight self-heal.
|
|
2529
|
-
// Clear skip counter and re-dispatch from fresh state.
|
|
2530
|
-
s.unitConsecutiveSkips.delete(idempotencyKey);
|
|
2531
|
-
invalidateAllCaches();
|
|
2532
|
-
ctx.ui.notify(
|
|
2533
|
-
`Phantom skip loop cleared: ${unitType} ${unitId} belongs to completed milestone ${skippedMid}. Re-dispatching from fresh state.`,
|
|
2534
|
-
"info",
|
|
2535
|
-
);
|
|
2536
|
-
s.skipDepth++;
|
|
2537
|
-
await new Promise(r => setTimeout(r, 50));
|
|
2538
|
-
await dispatchNextUnit(ctx, pi);
|
|
2539
|
-
s.skipDepth = Math.max(0, s.skipDepth - 1);
|
|
2540
|
-
return;
|
|
2541
|
-
}
|
|
2542
|
-
s.unitConsecutiveSkips.delete(idempotencyKey);
|
|
2543
|
-
s.completedKeySet.delete(idempotencyKey);
|
|
2544
|
-
s.recentlyEvictedKeys.add(idempotencyKey);
|
|
2545
|
-
removePersistedKey(s.basePath, idempotencyKey);
|
|
2546
|
-
invalidateAllCaches();
|
|
2547
|
-
ctx.ui.notify(
|
|
2548
|
-
`Skip loop detected: ${unitType} ${unitId} skipped ${skipCount} times without advancing. Evicting completion record and forcing reconciliation.`,
|
|
2549
|
-
"warning",
|
|
2550
|
-
);
|
|
2551
|
-
if (!s.active) return;
|
|
2552
|
-
s.skipDepth++;
|
|
2553
|
-
await new Promise(r => setTimeout(r, 150));
|
|
2554
|
-
await dispatchNextUnit(ctx, pi);
|
|
2555
|
-
s.skipDepth = Math.max(0, s.skipDepth - 1);
|
|
2556
|
-
return;
|
|
2557
|
-
}
|
|
2558
|
-
// Count toward lifetime cap so hard-stop fires during skip loops (#792)
|
|
2559
|
-
const lifeSkip = (s.unitLifetimeDispatches.get(idempotencyKey) ?? 0) + 1;
|
|
2560
|
-
s.unitLifetimeDispatches.set(idempotencyKey, lifeSkip);
|
|
2561
|
-
if (lifeSkip > MAX_LIFETIME_DISPATCHES) {
|
|
2562
|
-
await stopAuto(ctx, pi, `Hard loop: ${unitType} ${unitId} (skip cycle)`);
|
|
2563
|
-
ctx.ui.notify(
|
|
2564
|
-
`Hard loop detected: ${unitType} ${unitId} hit lifetime cap during skip cycle (${lifeSkip} iterations).`,
|
|
2565
|
-
"error",
|
|
2566
|
-
);
|
|
2567
|
-
return;
|
|
2568
|
-
}
|
|
2569
|
-
ctx.ui.notify(
|
|
2570
|
-
`Skipping ${unitType} ${unitId} — already completed in a prior session. Advancing.`,
|
|
2571
|
-
"info",
|
|
2572
|
-
);
|
|
2573
|
-
if (!s.active) return;
|
|
2574
|
-
s.skipDepth++;
|
|
2575
|
-
await new Promise(r => setTimeout(r, 150));
|
|
2576
|
-
await dispatchNextUnit(ctx, pi);
|
|
2577
|
-
s.skipDepth = Math.max(0, s.skipDepth - 1);
|
|
2578
|
-
return;
|
|
2579
|
-
} else {
|
|
2580
|
-
// Stale completion record — artifact missing. Remove and re-run.
|
|
2581
|
-
s.completedKeySet.delete(idempotencyKey);
|
|
2582
|
-
removePersistedKey(s.basePath, idempotencyKey);
|
|
2583
|
-
ctx.ui.notify(
|
|
2584
|
-
`Re-running ${unitType} ${unitId} — marked complete but expected artifact missing.`,
|
|
2585
|
-
"warning",
|
|
2586
|
-
);
|
|
2587
|
-
}
|
|
2588
|
-
}
|
|
1494
|
+
// ── Idempotency check (delegated to auto-idempotency.ts) ──
|
|
1495
|
+
const idempotencyResult = checkIdempotency({
|
|
1496
|
+
s,
|
|
1497
|
+
unitType,
|
|
1498
|
+
unitId,
|
|
1499
|
+
basePath: s.basePath,
|
|
1500
|
+
notify: (msg, level) => ctx.ui.notify(msg, level),
|
|
1501
|
+
});
|
|
2589
1502
|
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
// Persist it now and skip re-dispatch. This prevents infinite loops where a task
|
|
2593
|
-
// completes successfully but the completion key was never written.
|
|
2594
|
-
//
|
|
2595
|
-
// EXCEPTION: if the key was just evicted by the skip-loop breaker above, do NOT
|
|
2596
|
-
// re-persist — that would recreate the exact loop the breaker was trying to break (#912).
|
|
2597
|
-
if (verifyExpectedArtifact(unitType, unitId, s.basePath) && !s.recentlyEvictedKeys.has(idempotencyKey)) {
|
|
2598
|
-
persistCompletedKey(s.basePath, idempotencyKey);
|
|
2599
|
-
s.completedKeySet.add(idempotencyKey);
|
|
2600
|
-
invalidateAllCaches();
|
|
2601
|
-
// Same consecutive-skip guard as the idempotency path above.
|
|
2602
|
-
const skipCount2 = (s.unitConsecutiveSkips.get(idempotencyKey) ?? 0) + 1;
|
|
2603
|
-
s.unitConsecutiveSkips.set(idempotencyKey, skipCount2);
|
|
2604
|
-
if (skipCount2 > MAX_CONSECUTIVE_SKIPS) {
|
|
2605
|
-
// Cross-check: verify the unit's milestone is still active (#790).
|
|
2606
|
-
const skippedMid2 = unitId.split("/")[0];
|
|
2607
|
-
const skippedMilestoneComplete2 = skippedMid2
|
|
2608
|
-
? !!resolveMilestoneFile(s.basePath, skippedMid2, "SUMMARY")
|
|
2609
|
-
: false;
|
|
2610
|
-
if (skippedMilestoneComplete2) {
|
|
2611
|
-
s.unitConsecutiveSkips.delete(idempotencyKey);
|
|
2612
|
-
invalidateAllCaches();
|
|
2613
|
-
ctx.ui.notify(
|
|
2614
|
-
`Phantom skip loop cleared: ${unitType} ${unitId} belongs to completed milestone ${skippedMid2}. Re-dispatching from fresh state.`,
|
|
2615
|
-
"info",
|
|
2616
|
-
);
|
|
2617
|
-
s.skipDepth++;
|
|
2618
|
-
await new Promise(r => setTimeout(r, 50));
|
|
2619
|
-
await dispatchNextUnit(ctx, pi);
|
|
2620
|
-
s.skipDepth = Math.max(0, s.skipDepth - 1);
|
|
2621
|
-
return;
|
|
2622
|
-
}
|
|
2623
|
-
s.unitConsecutiveSkips.delete(idempotencyKey);
|
|
2624
|
-
s.completedKeySet.delete(idempotencyKey);
|
|
2625
|
-
removePersistedKey(s.basePath, idempotencyKey);
|
|
2626
|
-
invalidateAllCaches();
|
|
2627
|
-
ctx.ui.notify(
|
|
2628
|
-
`Skip loop detected: ${unitType} ${unitId} skipped ${skipCount2} times without advancing. Evicting completion record and forcing reconciliation.`,
|
|
2629
|
-
"warning",
|
|
2630
|
-
);
|
|
1503
|
+
if (idempotencyResult.action === "skip") {
|
|
1504
|
+
if (idempotencyResult.reason === "completed" || idempotencyResult.reason === "fallback-persisted" || idempotencyResult.reason === "phantom-loop-cleared" || idempotencyResult.reason === "evicted") {
|
|
2631
1505
|
if (!s.active) return;
|
|
2632
1506
|
s.skipDepth++;
|
|
2633
|
-
await new Promise(r => setTimeout(r, 150));
|
|
1507
|
+
await new Promise(r => setTimeout(r, idempotencyResult.reason === "phantom-loop-cleared" ? 50 : 150));
|
|
2634
1508
|
await dispatchNextUnit(ctx, pi);
|
|
2635
1509
|
s.skipDepth = Math.max(0, s.skipDepth - 1);
|
|
2636
1510
|
return;
|
|
2637
1511
|
}
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
s.unitLifetimeDispatches.set(idempotencyKey, lifeSkip2);
|
|
2641
|
-
if (lifeSkip2 > MAX_LIFETIME_DISPATCHES) {
|
|
2642
|
-
await stopAuto(ctx, pi, `Hard loop: ${unitType} ${unitId} (skip cycle)`);
|
|
2643
|
-
ctx.ui.notify(
|
|
2644
|
-
`Hard loop detected: ${unitType} ${unitId} hit lifetime cap during skip cycle (${lifeSkip2} iterations).`,
|
|
2645
|
-
"error",
|
|
2646
|
-
);
|
|
2647
|
-
return;
|
|
2648
|
-
}
|
|
2649
|
-
ctx.ui.notify(
|
|
2650
|
-
`Skipping ${unitType} ${unitId} — artifact exists but completion key was missing. Repaired and advancing.`,
|
|
2651
|
-
"info",
|
|
2652
|
-
);
|
|
2653
|
-
if (!s.active) return;
|
|
2654
|
-
s.skipDepth++;
|
|
2655
|
-
await new Promise(r => setTimeout(r, 150));
|
|
2656
|
-
await dispatchNextUnit(ctx, pi);
|
|
2657
|
-
s.skipDepth = Math.max(0, s.skipDepth - 1);
|
|
2658
|
-
return;
|
|
2659
|
-
}
|
|
2660
|
-
|
|
2661
|
-
// Stuck detection — tracks total dispatches per unit (not just consecutive repeats).
|
|
2662
|
-
// Pattern A→B→A→B would reset retryCount every time; this map catches it.
|
|
2663
|
-
const dispatchKey = `${unitType}/${unitId}`;
|
|
2664
|
-
const prevCount = s.unitDispatchCount.get(dispatchKey) ?? 0;
|
|
2665
|
-
// Real dispatch reached — clear the consecutive-skip counter for this unit.
|
|
2666
|
-
s.unitConsecutiveSkips.delete(dispatchKey);
|
|
2667
|
-
|
|
2668
|
-
debugLog("dispatch-unit", {
|
|
2669
|
-
type: unitType,
|
|
2670
|
-
id: unitId,
|
|
2671
|
-
cycle: prevCount + 1,
|
|
2672
|
-
lifetime: (s.unitLifetimeDispatches.get(dispatchKey) ?? 0) + 1,
|
|
2673
|
-
});
|
|
2674
|
-
debugCount("dispatches");
|
|
2675
|
-
|
|
2676
|
-
// Hard lifetime cap — survives counter resets from loop-recovery/self-repair.
|
|
2677
|
-
// Catches the case where reconciliation "succeeds" (artifacts exist) but
|
|
2678
|
-
// deriveState keeps returning the same unit, creating an infinite cycle.
|
|
2679
|
-
const lifetimeCount = (s.unitLifetimeDispatches.get(dispatchKey) ?? 0) + 1;
|
|
2680
|
-
s.unitLifetimeDispatches.set(dispatchKey, lifetimeCount);
|
|
2681
|
-
if (lifetimeCount > MAX_LIFETIME_DISPATCHES) {
|
|
2682
|
-
if (s.currentUnit) {
|
|
2683
|
-
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
2684
|
-
} else {
|
|
2685
|
-
saveActivityLog(ctx, s.basePath, unitType, unitId);
|
|
2686
|
-
}
|
|
2687
|
-
const expected = diagnoseExpectedArtifact(unitType, unitId, s.basePath);
|
|
2688
|
-
await stopAuto(ctx, pi, `Hard loop: ${unitType} ${unitId}`);
|
|
1512
|
+
} else if (idempotencyResult.action === "stop") {
|
|
1513
|
+
await stopAuto(ctx, pi, idempotencyResult.reason);
|
|
2689
1514
|
ctx.ui.notify(
|
|
2690
|
-
`Hard loop detected: ${unitType} ${unitId}
|
|
1515
|
+
`Hard loop detected: ${unitType} ${unitId} hit lifetime cap during skip cycle.`,
|
|
2691
1516
|
"error",
|
|
2692
1517
|
);
|
|
2693
1518
|
return;
|
|
2694
1519
|
}
|
|
2695
|
-
|
|
2696
|
-
if (s.currentUnit) {
|
|
2697
|
-
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
2698
|
-
} else {
|
|
2699
|
-
saveActivityLog(ctx, s.basePath, unitType, unitId);
|
|
2700
|
-
}
|
|
2701
|
-
|
|
2702
|
-
// Final reconciliation pass for execute-task: write any missing durable
|
|
2703
|
-
// artifacts (summary placeholder + [x] checkbox) so the pipeline can
|
|
2704
|
-
// advance instead of stopping. This is the last resort before halting.
|
|
2705
|
-
if (unitType === "execute-task") {
|
|
2706
|
-
const [mid, sid, tid] = unitId.split("/");
|
|
2707
|
-
if (mid && sid && tid) {
|
|
2708
|
-
const status = await inspectExecuteTaskDurability(s.basePath, unitId);
|
|
2709
|
-
if (status) {
|
|
2710
|
-
const reconciled = skipExecuteTask(s.basePath, mid, sid, tid, status, "loop-recovery", prevCount);
|
|
2711
|
-
// reconciled: skipExecuteTask attempted to write missing artifacts.
|
|
2712
|
-
// verifyExpectedArtifact: confirms physical artifacts (summary + [x]) now exist on disk.
|
|
2713
|
-
// Both must pass before we clear the dispatch counter and advance.
|
|
2714
|
-
if (reconciled && verifyExpectedArtifact(unitType, unitId, s.basePath)) {
|
|
2715
|
-
ctx.ui.notify(
|
|
2716
|
-
`Loop recovery: ${unitId} reconciled after ${prevCount + 1} dispatches — blocker artifacts written, pipeline advancing.\n Review ${status.summaryPath} and replace the placeholder with real work.`,
|
|
2717
|
-
"warning",
|
|
2718
|
-
);
|
|
2719
|
-
// Persist completion so idempotency check prevents re-dispatch
|
|
2720
|
-
// if deriveState keeps returning this unit (#462).
|
|
2721
|
-
const reconciledKey = `${unitType}/${unitId}`;
|
|
2722
|
-
persistCompletedKey(s.basePath, reconciledKey);
|
|
2723
|
-
s.completedKeySet.add(reconciledKey);
|
|
2724
|
-
s.unitDispatchCount.delete(dispatchKey);
|
|
2725
|
-
invalidateAllCaches();
|
|
2726
|
-
await new Promise(r => setImmediate(r));
|
|
2727
|
-
await dispatchNextUnit(ctx, pi);
|
|
2728
|
-
return;
|
|
2729
|
-
}
|
|
2730
|
-
}
|
|
2731
|
-
}
|
|
2732
|
-
}
|
|
1520
|
+
// "rerun" and "proceed" fall through to stuck detection
|
|
2733
1521
|
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
ctx.ui.notify(
|
|
2744
|
-
`Loop recovery: ${unitType} ${unitId} — artifact verified after ${prevCount + 1} dispatches. Advancing.`,
|
|
2745
|
-
"info",
|
|
2746
|
-
);
|
|
2747
|
-
// Persist completion so the idempotency check prevents re-dispatch
|
|
2748
|
-
// if deriveState keeps returning this unit (see #462).
|
|
2749
|
-
persistCompletedKey(s.basePath, dispatchKey);
|
|
2750
|
-
s.completedKeySet.add(dispatchKey);
|
|
2751
|
-
s.unitDispatchCount.delete(dispatchKey);
|
|
2752
|
-
invalidateAllCaches();
|
|
2753
|
-
await new Promise(r => setImmediate(r));
|
|
2754
|
-
await dispatchNextUnit(ctx, pi);
|
|
2755
|
-
return;
|
|
2756
|
-
}
|
|
1522
|
+
// ── Stuck detection (delegated to auto-stuck-detection.ts) ──
|
|
1523
|
+
const stuckResult = await checkStuckAndRecover({
|
|
1524
|
+
s,
|
|
1525
|
+
ctx,
|
|
1526
|
+
unitType,
|
|
1527
|
+
unitId,
|
|
1528
|
+
basePath: s.basePath,
|
|
1529
|
+
buildSnapshotOpts: () => buildSnapshotOpts(unitType, unitId),
|
|
1530
|
+
});
|
|
2757
1531
|
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
try {
|
|
2763
|
-
const mPath = resolveMilestonePath(s.basePath, unitId);
|
|
2764
|
-
if (mPath) {
|
|
2765
|
-
const stubPath = join(mPath, `${unitId}-SUMMARY.md`);
|
|
2766
|
-
if (!existsSync(stubPath)) {
|
|
2767
|
-
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`);
|
|
2768
|
-
ctx.ui.notify(`Generated stub summary for ${unitId} to unblock pipeline. Review later.`, "warning");
|
|
2769
|
-
persistCompletedKey(s.basePath, dispatchKey);
|
|
2770
|
-
s.completedKeySet.add(dispatchKey);
|
|
2771
|
-
s.unitDispatchCount.delete(dispatchKey);
|
|
2772
|
-
invalidateAllCaches();
|
|
2773
|
-
await new Promise(r => setImmediate(r));
|
|
2774
|
-
await dispatchNextUnit(ctx, pi);
|
|
2775
|
-
return;
|
|
2776
|
-
}
|
|
2777
|
-
}
|
|
2778
|
-
} catch { /* non-fatal — fall through to normal stop */ }
|
|
1532
|
+
if (stuckResult.action === "stop") {
|
|
1533
|
+
await stopAuto(ctx, pi, stuckResult.reason);
|
|
1534
|
+
if (stuckResult.notifyMessage) {
|
|
1535
|
+
ctx.ui.notify(stuckResult.notifyMessage, "error");
|
|
2779
1536
|
}
|
|
2780
|
-
|
|
2781
|
-
const expected = diagnoseExpectedArtifact(unitType, unitId, s.basePath);
|
|
2782
|
-
const remediation = buildLoopRemediationSteps(unitType, unitId, s.basePath);
|
|
2783
|
-
await stopAuto(ctx, pi, `Loop: ${unitType} ${unitId}`);
|
|
2784
|
-
sendDesktopNotification("GSD", `Loop detected: ${unitType} ${unitId}`, "error", "error");
|
|
2785
|
-
ctx.ui.notify(
|
|
2786
|
-
`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."}`,
|
|
2787
|
-
"error",
|
|
2788
|
-
);
|
|
2789
1537
|
return;
|
|
2790
1538
|
}
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
const status = await inspectExecuteTaskDurability(s.basePath, unitId);
|
|
2796
|
-
const [mid, sid, tid] = unitId.split("/");
|
|
2797
|
-
if (status && mid && sid && tid) {
|
|
2798
|
-
if (status.summaryExists && !status.taskChecked) {
|
|
2799
|
-
// Retry 1+: summary exists but checkbox not marked — mark [x] and advance.
|
|
2800
|
-
const repaired = skipExecuteTask(s.basePath, mid, sid, tid, status, "self-repair", 0);
|
|
2801
|
-
// repaired: skipExecuteTask updated metadata (returned early-true even if regex missed).
|
|
2802
|
-
// verifyExpectedArtifact: confirms the physical artifact (summary + [x]) now exists.
|
|
2803
|
-
if (repaired && verifyExpectedArtifact(unitType, unitId, s.basePath)) {
|
|
2804
|
-
ctx.ui.notify(
|
|
2805
|
-
`Self-repaired ${unitId}: summary existed but checkbox was unmarked. Marked [x] and advancing.`,
|
|
2806
|
-
"warning",
|
|
2807
|
-
);
|
|
2808
|
-
// Persist completion so idempotency check prevents re-dispatch (#462).
|
|
2809
|
-
const repairedKey = `${unitType}/${unitId}`;
|
|
2810
|
-
persistCompletedKey(s.basePath, repairedKey);
|
|
2811
|
-
s.completedKeySet.add(repairedKey);
|
|
2812
|
-
s.unitDispatchCount.delete(dispatchKey);
|
|
2813
|
-
invalidateAllCaches();
|
|
2814
|
-
await new Promise(r => setImmediate(r));
|
|
2815
|
-
await dispatchNextUnit(ctx, pi);
|
|
2816
|
-
return;
|
|
2817
|
-
}
|
|
2818
|
-
} else if (prevCount >= STUB_RECOVERY_THRESHOLD && !status.summaryExists) {
|
|
2819
|
-
// Retry STUB_RECOVERY_THRESHOLD+: summary still missing after multiple attempts.
|
|
2820
|
-
// Write a minimal stub summary so the next agent session has a recovery artifact
|
|
2821
|
-
// to overwrite, rather than starting from scratch again.
|
|
2822
|
-
const tasksDir = resolveTasksDir(s.basePath, mid, sid);
|
|
2823
|
-
const sDir = resolveSlicePath(s.basePath, mid, sid);
|
|
2824
|
-
const targetDir = tasksDir ?? (sDir ? join(sDir, "tasks") : null);
|
|
2825
|
-
if (targetDir) {
|
|
2826
|
-
if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });
|
|
2827
|
-
const summaryPath = join(targetDir, buildTaskFileName(tid, "SUMMARY"));
|
|
2828
|
-
if (!existsSync(summaryPath)) {
|
|
2829
|
-
const stubContent = [
|
|
2830
|
-
`# PARTIAL RECOVERY — attempt ${prevCount + 1} of ${MAX_UNIT_DISPATCHES}`,
|
|
2831
|
-
``,
|
|
2832
|
-
`Task \`${tid}\` in slice \`${sid}\` (milestone \`${mid}\`) has not yet produced a real summary.`,
|
|
2833
|
-
`This placeholder was written by auto-mode after ${prevCount} dispatch attempts.`,
|
|
2834
|
-
``,
|
|
2835
|
-
`The next agent session will retry this task. Replace this file with real work when done.`,
|
|
2836
|
-
].join("\n");
|
|
2837
|
-
writeFileSync(summaryPath, stubContent, "utf-8");
|
|
2838
|
-
ctx.ui.notify(
|
|
2839
|
-
`Stub recovery (attempt ${prevCount + 1}/${MAX_UNIT_DISPATCHES}): ${unitId} stub summary placeholder written. Retrying with recovery context.`,
|
|
2840
|
-
"warning",
|
|
2841
|
-
);
|
|
2842
|
-
}
|
|
2843
|
-
}
|
|
2844
|
-
}
|
|
2845
|
-
}
|
|
2846
|
-
}
|
|
2847
|
-
ctx.ui.notify(
|
|
2848
|
-
`${unitType} ${unitId} didn't produce expected artifact. Retrying (${prevCount + 1}/${MAX_UNIT_DISPATCHES}).`,
|
|
2849
|
-
"warning",
|
|
2850
|
-
);
|
|
1539
|
+
if (stuckResult.action === "recovered" && stuckResult.dispatchAgain) {
|
|
1540
|
+
await new Promise(r => setImmediate(r));
|
|
1541
|
+
await dispatchNextUnit(ctx, pi);
|
|
1542
|
+
return;
|
|
2851
1543
|
}
|
|
1544
|
+
|
|
2852
1545
|
// Snapshot metrics + activity log for the PREVIOUS unit before we reassign.
|
|
2853
|
-
// The session still holds the previous unit's data (newSession hasn't fired yet).
|
|
2854
1546
|
if (s.currentUnit) {
|
|
2855
1547
|
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
2856
1548
|
|
|
2857
|
-
// Record routing outcome for adaptive learning
|
|
2858
1549
|
if (s.currentUnitRouting) {
|
|
2859
1550
|
const isRetry = s.currentUnit.type === unitType && s.currentUnit.id === unitId;
|
|
2860
1551
|
recordOutcome(
|
|
2861
1552
|
s.currentUnit.type,
|
|
2862
1553
|
s.currentUnitRouting.tier as "light" | "standard" | "heavy",
|
|
2863
|
-
!isRetry,
|
|
1554
|
+
!isRetry,
|
|
2864
1555
|
);
|
|
2865
1556
|
}
|
|
2866
1557
|
|
|
2867
|
-
// Only mark the previous unit as completed if:
|
|
2868
|
-
// 1. We're not about to re-dispatch the same unit (retry scenario)
|
|
2869
|
-
// 2. The expected artifact actually exists on disk
|
|
2870
|
-
// For hook units, skip artifact verification — hooks don't produce standard
|
|
2871
|
-
// artifacts and their runtime records were already finalized in handleAgentEnd.
|
|
2872
1558
|
const closeoutKey = `${s.currentUnit.type}/${s.currentUnit.id}`;
|
|
2873
1559
|
const incomingKey = `${unitType}/${unitId}`;
|
|
2874
1560
|
const isHookUnit = s.currentUnit.type.startsWith("hook/");
|
|
2875
1561
|
const artifactVerified = isHookUnit || verifyExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.basePath);
|
|
2876
1562
|
if (closeoutKey !== incomingKey && artifactVerified) {
|
|
2877
1563
|
if (!isHookUnit) {
|
|
2878
|
-
// Only persist completion keys for real units — hook keys are
|
|
2879
|
-
// ephemeral and should not pollute the idempotency set.
|
|
2880
1564
|
persistCompletedKey(s.basePath, closeoutKey);
|
|
2881
1565
|
s.completedKeySet.add(closeoutKey);
|
|
2882
1566
|
}
|
|
@@ -2887,7 +1571,6 @@ async function dispatchNextUnit(
|
|
|
2887
1571
|
startedAt: s.currentUnit.startedAt,
|
|
2888
1572
|
finishedAt: Date.now(),
|
|
2889
1573
|
});
|
|
2890
|
-
// Cap to last 200 entries to prevent unbounded growth (#611)
|
|
2891
1574
|
if (s.completedUnits.length > 200) {
|
|
2892
1575
|
s.completedUnits = s.completedUnits.slice(-200);
|
|
2893
1576
|
}
|
|
@@ -2897,7 +1580,7 @@ async function dispatchNextUnit(
|
|
|
2897
1580
|
}
|
|
2898
1581
|
}
|
|
2899
1582
|
s.currentUnit = { type: unitType, id: unitId, startedAt: Date.now() };
|
|
2900
|
-
captureAvailableSkills();
|
|
1583
|
+
captureAvailableSkills();
|
|
2901
1584
|
writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
|
|
2902
1585
|
phase: "dispatched",
|
|
2903
1586
|
wrapupWarningSent: false,
|
|
@@ -2912,34 +1595,42 @@ async function dispatchNextUnit(
|
|
|
2912
1595
|
if (mid) updateSliceProgressCache(s.basePath, mid, state.activeSlice?.id);
|
|
2913
1596
|
updateProgressWidget(ctx, unitType, unitId, state);
|
|
2914
1597
|
|
|
2915
|
-
// Ensure preconditions — create directories, branches, etc.
|
|
2916
|
-
// so the LLM doesn't have to get these right
|
|
2917
1598
|
ensurePreconditions(unitType, unitId, s.basePath, state);
|
|
2918
1599
|
|
|
2919
|
-
// Fresh session
|
|
2920
|
-
|
|
1600
|
+
// Fresh session — with timeout to prevent permanent hangs (#1073).
|
|
1601
|
+
// If newSession() hangs (e.g., session manager deadlock, network issue),
|
|
1602
|
+
// without this timeout the entire dispatch chain stalls permanently: no
|
|
1603
|
+
// timeouts are set, no gap watchdog fires, and auto-mode is left active
|
|
1604
|
+
// but idle until the user Ctrl+C's.
|
|
1605
|
+
let result: { cancelled: boolean };
|
|
1606
|
+
try {
|
|
1607
|
+
const sessionPromise = s.cmdCtx!.newSession();
|
|
1608
|
+
const timeoutPromise = new Promise<{ cancelled: true }>((resolve) =>
|
|
1609
|
+
setTimeout(() => resolve({ cancelled: true }), NEW_SESSION_TIMEOUT_MS),
|
|
1610
|
+
);
|
|
1611
|
+
result = await Promise.race([sessionPromise, timeoutPromise]);
|
|
1612
|
+
} catch (sessionErr) {
|
|
1613
|
+
const msg = sessionErr instanceof Error ? sessionErr.message : String(sessionErr);
|
|
1614
|
+
ctx.ui.notify(`Session creation failed: ${msg}. Retrying via watchdog.`, "error");
|
|
1615
|
+
throw new Error(`newSession() failed: ${msg}`);
|
|
1616
|
+
}
|
|
2921
1617
|
if (result.cancelled) {
|
|
2922
|
-
|
|
1618
|
+
ctx.ui.notify(
|
|
1619
|
+
`Session creation timed out or was cancelled for ${unitType} ${unitId}. Will retry.`,
|
|
1620
|
+
"warning",
|
|
1621
|
+
);
|
|
1622
|
+
await stopAuto(ctx, pi, "Session creation failed");
|
|
2923
1623
|
return;
|
|
2924
1624
|
}
|
|
2925
1625
|
|
|
2926
|
-
// Branchless architecture: all work commits sequentially on the milestone
|
|
2927
|
-
// branch — no per-slice branches or slice-level merges. Milestone merge
|
|
2928
|
-
// happens when phase === "complete" (see mergeMilestoneToMain above).
|
|
2929
|
-
|
|
2930
|
-
// Write lock AFTER newSession so we capture the session file path.
|
|
2931
|
-
// Pi appends entries incrementally via appendFileSync, so on crash the
|
|
2932
|
-
// session file survives with every tool call up to the crash point.
|
|
2933
1626
|
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
1627
|
+
updateSessionLock(lockBase(), unitType, unitId, s.completedUnits.length, sessionFile);
|
|
2934
1628
|
writeLock(lockBase(), unitType, unitId, s.completedUnits.length, sessionFile);
|
|
2935
1629
|
|
|
2936
|
-
//
|
|
2937
|
-
// On retry (stuck detection), prepend deep diagnostic from last attempt
|
|
2938
|
-
// Cap injected content to prevent unbounded prompt growth → OOM
|
|
1630
|
+
// Prompt injection
|
|
2939
1631
|
const MAX_RECOVERY_CHARS = 50_000;
|
|
2940
1632
|
let finalPrompt = prompt;
|
|
2941
1633
|
|
|
2942
|
-
// Verification retry — inject failure context so the agent can auto-fix
|
|
2943
1634
|
if (s.pendingVerificationRetry) {
|
|
2944
1635
|
const retryCtx = s.pendingVerificationRetry;
|
|
2945
1636
|
s.pendingVerificationRetry = null;
|
|
@@ -2965,14 +1656,12 @@ async function dispatchNextUnit(
|
|
|
2965
1656
|
}
|
|
2966
1657
|
}
|
|
2967
1658
|
|
|
2968
|
-
// Inject observability repair instructions so the agent fixes gaps before
|
|
2969
|
-
// proceeding with the unit (see #174).
|
|
2970
1659
|
const repairBlock = buildObservabilityRepairBlock(observabilityIssues);
|
|
2971
1660
|
if (repairBlock) {
|
|
2972
1661
|
finalPrompt = `${finalPrompt}${repairBlock}`;
|
|
2973
1662
|
}
|
|
2974
1663
|
|
|
2975
|
-
// ── Prompt char measurement
|
|
1664
|
+
// ── Prompt char measurement ──
|
|
2976
1665
|
s.lastPromptCharCount = finalPrompt.length;
|
|
2977
1666
|
s.lastBaselineCharCount = undefined;
|
|
2978
1667
|
if (isDbAvailable()) {
|
|
@@ -2988,221 +1677,44 @@ async function dispatchNextUnit(
|
|
|
2988
1677
|
(requirementsContent?.length ?? 0) +
|
|
2989
1678
|
(projectContent?.length ?? 0);
|
|
2990
1679
|
} catch {
|
|
2991
|
-
// Non-fatal
|
|
1680
|
+
// Non-fatal
|
|
2992
1681
|
}
|
|
2993
1682
|
}
|
|
2994
1683
|
|
|
2995
|
-
//
|
|
1684
|
+
// Cache-optimize prompt section ordering
|
|
1685
|
+
try {
|
|
1686
|
+
const { reorderForCaching } = await import("./prompt-ordering.js");
|
|
1687
|
+
finalPrompt = reorderForCaching(finalPrompt);
|
|
1688
|
+
} catch (reorderErr) {
|
|
1689
|
+
const msg = reorderErr instanceof Error ? reorderErr.message : String(reorderErr);
|
|
1690
|
+
process.stderr.write(`[gsd] prompt reorder failed (non-fatal): ${msg}\n`);
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
// Select and apply model
|
|
2996
1694
|
const modelResult = await selectAndApplyModel(ctx, pi, unitType, unitId, s.basePath, prefs, s.verbose, s.autoModeStartModel);
|
|
2997
1695
|
s.currentUnitRouting = modelResult.routing;
|
|
2998
1696
|
|
|
2999
|
-
// Start
|
|
3000
|
-
// a larger hard ceiling. Productive long-running tasks may continue past the
|
|
3001
|
-
// soft timeout; only idle/stalled tasks pause early.
|
|
1697
|
+
// ── Start unit supervision (delegated to auto-timers.ts) ──
|
|
3002
1698
|
clearUnitTimeout();
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
});
|
|
3015
|
-
pi.sendMessage(
|
|
3016
|
-
{
|
|
3017
|
-
customType: "gsd-auto-wrapup",
|
|
3018
|
-
display: s.verbose,
|
|
3019
|
-
content: [
|
|
3020
|
-
"**TIME BUDGET WARNING — keep going only if progress is real.**",
|
|
3021
|
-
"This unit crossed the soft time budget.",
|
|
3022
|
-
"If you are making progress, continue. If not, switch to wrap-up mode now:",
|
|
3023
|
-
"1. rerun the minimal required verification",
|
|
3024
|
-
"2. write or update the required durable artifacts",
|
|
3025
|
-
"3. mark task or slice state on disk correctly",
|
|
3026
|
-
"4. leave precise resume notes if anything remains unfinished",
|
|
3027
|
-
].join("\n"),
|
|
3028
|
-
},
|
|
3029
|
-
{ triggerTurn: true },
|
|
3030
|
-
);
|
|
3031
|
-
}, softTimeoutMs);
|
|
3032
|
-
|
|
3033
|
-
s.idleWatchdogHandle = setInterval(async () => {
|
|
3034
|
-
try {
|
|
3035
|
-
if (!s.active || !s.currentUnit) return;
|
|
3036
|
-
const runtime = readUnitRuntimeRecord(s.basePath, unitType, unitId);
|
|
3037
|
-
if (!runtime) return;
|
|
3038
|
-
if (Date.now() - runtime.lastProgressAt < idleTimeoutMs) return;
|
|
3039
|
-
|
|
3040
|
-
// Agent has tool calls currently executing (await_job, long bash, etc.) —
|
|
3041
|
-
// not idle, just waiting for tool completion. But only suppress recovery
|
|
3042
|
-
// if the tool started recently. A tool in-flight for longer than the idle
|
|
3043
|
-
// timeout is likely stuck — e.g., `python -m http.server 8080 &` keeps the
|
|
3044
|
-
// shell's stdout/stderr open, causing the Bash tool to hang indefinitely.
|
|
3045
|
-
if (getInFlightToolCount() > 0) {
|
|
3046
|
-
const oldestStart = getOldestInFlightToolStart()!;
|
|
3047
|
-
const toolAgeMs = Date.now() - oldestStart;
|
|
3048
|
-
if (toolAgeMs < idleTimeoutMs) {
|
|
3049
|
-
writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
|
|
3050
|
-
lastProgressAt: Date.now(),
|
|
3051
|
-
lastProgressKind: "tool-in-flight",
|
|
3052
|
-
});
|
|
3053
|
-
return;
|
|
3054
|
-
}
|
|
3055
|
-
// Oldest tool has been running >= idleTimeoutMs — treat as a stuck/hung
|
|
3056
|
-
// tool (e.g., background process holding stdout open). Fall through to
|
|
3057
|
-
// idle recovery without resetting the progress clock.
|
|
3058
|
-
ctx.ui.notify(
|
|
3059
|
-
`Stalled tool detected: a tool has been in-flight for ${Math.round(toolAgeMs / 60000)}min. Treating as hung — attempting idle recovery.`,
|
|
3060
|
-
"warning",
|
|
3061
|
-
);
|
|
3062
|
-
}
|
|
3063
|
-
|
|
3064
|
-
// Before triggering recovery, check if the agent is actually producing
|
|
3065
|
-
// work on disk. `git status --porcelain` is cheap and catches any
|
|
3066
|
-
// staged/unstaged/untracked changes the agent made since lastProgressAt.
|
|
3067
|
-
if (detectWorkingTreeActivity(s.basePath)) {
|
|
3068
|
-
writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
|
|
3069
|
-
lastProgressAt: Date.now(),
|
|
3070
|
-
lastProgressKind: "filesystem-activity",
|
|
3071
|
-
});
|
|
3072
|
-
return;
|
|
3073
|
-
}
|
|
3074
|
-
|
|
3075
|
-
if (s.currentUnit) {
|
|
3076
|
-
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
3077
|
-
} else {
|
|
3078
|
-
saveActivityLog(ctx, s.basePath, unitType, unitId);
|
|
3079
|
-
}
|
|
3080
|
-
|
|
3081
|
-
const recovery = await recoverTimedOutUnit(ctx, pi, unitType, unitId, "idle", buildRecoveryContext());
|
|
3082
|
-
if (recovery === "recovered") return;
|
|
3083
|
-
|
|
3084
|
-
writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
|
|
3085
|
-
phase: "paused",
|
|
3086
|
-
});
|
|
3087
|
-
ctx.ui.notify(
|
|
3088
|
-
`Unit ${unitType} ${unitId} made no meaningful progress for ${supervisor.idle_timeout_minutes}min. Pausing auto-mode.`,
|
|
3089
|
-
"warning",
|
|
3090
|
-
);
|
|
3091
|
-
await pauseAuto(ctx, pi);
|
|
3092
|
-
} catch (err) {
|
|
3093
|
-
// Guard against unhandled rejections in the async interval callback.
|
|
3094
|
-
// Without this, a thrown error leaves the interval running forever
|
|
3095
|
-
// while the auto-mode state becomes inconsistent.
|
|
3096
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
3097
|
-
console.error(`[idle-watchdog] Unhandled error: ${message}`);
|
|
3098
|
-
try {
|
|
3099
|
-
ctx.ui.notify(`Idle watchdog error: ${message}`, "warning");
|
|
3100
|
-
} catch { /* best effort */ }
|
|
3101
|
-
}
|
|
3102
|
-
}, 15000);
|
|
3103
|
-
|
|
3104
|
-
s.unitTimeoutHandle = setTimeout(async () => {
|
|
3105
|
-
try {
|
|
3106
|
-
s.unitTimeoutHandle = null;
|
|
3107
|
-
if (!s.active) return;
|
|
3108
|
-
if (s.currentUnit) {
|
|
3109
|
-
writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
|
|
3110
|
-
phase: "timeout",
|
|
3111
|
-
timeoutAt: Date.now(),
|
|
3112
|
-
});
|
|
3113
|
-
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
3114
|
-
} else {
|
|
3115
|
-
saveActivityLog(ctx, s.basePath, unitType, unitId);
|
|
3116
|
-
}
|
|
3117
|
-
|
|
3118
|
-
const recovery = await recoverTimedOutUnit(ctx, pi, unitType, unitId, "hard", buildRecoveryContext());
|
|
3119
|
-
if (recovery === "recovered") return;
|
|
3120
|
-
|
|
3121
|
-
ctx.ui.notify(
|
|
3122
|
-
`Unit ${unitType} ${unitId} exceeded ${supervisor.hard_timeout_minutes}min hard timeout. Pausing auto-mode.`,
|
|
3123
|
-
"warning",
|
|
3124
|
-
);
|
|
3125
|
-
await pauseAuto(ctx, pi);
|
|
3126
|
-
} catch (err) {
|
|
3127
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
3128
|
-
console.error(`[hard-timeout] Unhandled error: ${message}`);
|
|
3129
|
-
try {
|
|
3130
|
-
ctx.ui.notify(`Hard timeout error: ${message}`, "warning");
|
|
3131
|
-
} catch { /* best effort */ }
|
|
3132
|
-
}
|
|
3133
|
-
}, hardTimeoutMs);
|
|
3134
|
-
|
|
3135
|
-
// ── Continue-here context-pressure monitor ────────────────────────────
|
|
3136
|
-
// Polls context usage every 15s. When usage hits the continue-here
|
|
3137
|
-
// threshold (70%), sends a one-shot wrap-up signal so the agent finishes
|
|
3138
|
-
// gracefully and the next unit gets a fresh session. This is softer than
|
|
3139
|
-
// context_pause_threshold which hard-pauses auto-mode entirely.
|
|
3140
|
-
if (s.continueHereHandle) {
|
|
3141
|
-
clearInterval(s.continueHereHandle);
|
|
3142
|
-
s.continueHereHandle = null;
|
|
3143
|
-
}
|
|
3144
|
-
const executorContextWindow = resolveExecutorContextWindow(
|
|
3145
|
-
ctx.modelRegistry as Parameters<typeof resolveExecutorContextWindow>[0],
|
|
3146
|
-
prefs as Parameters<typeof resolveExecutorContextWindow>[1],
|
|
3147
|
-
ctx.model?.contextWindow,
|
|
3148
|
-
);
|
|
3149
|
-
const continueHereThreshold = computeBudgets(executorContextWindow).continueThresholdPercent;
|
|
3150
|
-
s.continueHereHandle = setInterval(() => {
|
|
3151
|
-
if (!s.active || !s.currentUnit || !s.cmdCtx) return;
|
|
3152
|
-
// One-shot guard: skip if already fired for this unit
|
|
3153
|
-
const runtime = readUnitRuntimeRecord(s.basePath, unitType, unitId);
|
|
3154
|
-
if (runtime?.continueHereFired) return;
|
|
3155
|
-
|
|
3156
|
-
const contextUsage = s.cmdCtx.getContextUsage();
|
|
3157
|
-
if (!contextUsage || contextUsage.percent == null || contextUsage.percent < continueHereThreshold) return;
|
|
3158
|
-
|
|
3159
|
-
// Fire once — mark runtime record and send wrap-up message
|
|
3160
|
-
writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit!.startedAt, {
|
|
3161
|
-
continueHereFired: true,
|
|
3162
|
-
});
|
|
3163
|
-
|
|
3164
|
-
if (s.verbose) {
|
|
3165
|
-
ctx.ui.notify(
|
|
3166
|
-
`Context at ${contextUsage.percent}% (threshold: ${continueHereThreshold}%) — sending wrap-up signal.`,
|
|
3167
|
-
"info",
|
|
3168
|
-
);
|
|
3169
|
-
}
|
|
3170
|
-
|
|
3171
|
-
pi.sendMessage(
|
|
3172
|
-
{
|
|
3173
|
-
customType: "gsd-auto-wrapup",
|
|
3174
|
-
display: s.verbose,
|
|
3175
|
-
content: [
|
|
3176
|
-
"**CONTEXT BUDGET WARNING — wrap up this unit now.**",
|
|
3177
|
-
`Context window is at ${contextUsage.percent}% (threshold: ${continueHereThreshold}%).`,
|
|
3178
|
-
"The next unit needs a fresh context to work effectively. Wrap up now:",
|
|
3179
|
-
"1. Finish any in-progress file writes",
|
|
3180
|
-
"2. Write or update the required durable artifacts (summary, checkboxes)",
|
|
3181
|
-
"3. Mark task state on disk correctly",
|
|
3182
|
-
"4. Leave precise resume notes if anything remains unfinished",
|
|
3183
|
-
"Do NOT start new sub-tasks or investigations.",
|
|
3184
|
-
].join("\n"),
|
|
3185
|
-
},
|
|
3186
|
-
{ triggerTurn: true },
|
|
3187
|
-
);
|
|
3188
|
-
|
|
3189
|
-
// Clear the interval after firing — no need to keep polling
|
|
3190
|
-
if (s.continueHereHandle) {
|
|
3191
|
-
clearInterval(s.continueHereHandle);
|
|
3192
|
-
s.continueHereHandle = null;
|
|
3193
|
-
}
|
|
3194
|
-
}, 15_000);
|
|
1699
|
+
startUnitSupervision({
|
|
1700
|
+
s,
|
|
1701
|
+
ctx,
|
|
1702
|
+
pi,
|
|
1703
|
+
unitType,
|
|
1704
|
+
unitId,
|
|
1705
|
+
prefs,
|
|
1706
|
+
buildSnapshotOpts: () => buildSnapshotOpts(unitType, unitId),
|
|
1707
|
+
buildRecoveryContext: () => buildRecoveryContext(),
|
|
1708
|
+
pauseAuto,
|
|
1709
|
+
});
|
|
3195
1710
|
|
|
3196
|
-
// Inject prompt
|
|
1711
|
+
// Inject prompt
|
|
3197
1712
|
if (!s.active) return;
|
|
3198
1713
|
pi.sendMessage(
|
|
3199
1714
|
{ customType: "gsd-auto", content: finalPrompt, display: s.verbose },
|
|
3200
1715
|
{ triggerTurn: true },
|
|
3201
1716
|
);
|
|
3202
1717
|
|
|
3203
|
-
// For non-artifact-driven UAT types, pause auto-mode after sending the prompt.
|
|
3204
|
-
// The agent will write the UAT result file surfacing it for human review,
|
|
3205
|
-
// then on resume the result file exists and run-uat is skipped automatically.
|
|
3206
1718
|
if (pauseAfterUatDispatch) {
|
|
3207
1719
|
ctx.ui.notify(
|
|
3208
1720
|
"UAT requires human execution. Auto-mode will pause after this unit writes the result file.",
|
|
@@ -3227,32 +1739,26 @@ function ensurePreconditions(
|
|
|
3227
1739
|
const parts = unitId.split("/");
|
|
3228
1740
|
const mid = parts[0]!;
|
|
3229
1741
|
|
|
3230
|
-
// Always ensure milestone dir exists
|
|
3231
1742
|
const mDir = resolveMilestonePath(base, mid);
|
|
3232
1743
|
if (!mDir) {
|
|
3233
1744
|
const newDir = join(milestonesDir(base), mid);
|
|
3234
1745
|
mkdirSync(join(newDir, "slices"), { recursive: true });
|
|
3235
1746
|
}
|
|
3236
1747
|
|
|
3237
|
-
// For slice-level units, ensure slice dir exists
|
|
3238
1748
|
if (parts.length >= 2) {
|
|
3239
1749
|
const sid = parts[1]!;
|
|
3240
1750
|
|
|
3241
|
-
// Re-resolve milestone path after potential creation
|
|
3242
1751
|
const mDirResolved = resolveMilestonePath(base, mid);
|
|
3243
1752
|
if (mDirResolved) {
|
|
3244
1753
|
const slicesDir = join(mDirResolved, "slices");
|
|
3245
1754
|
const sDir = resolveDir(slicesDir, sid);
|
|
3246
1755
|
if (!sDir) {
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
if (!existsSync(tasksDir)) {
|
|
3254
|
-
mkdirSync(tasksDir, { recursive: true });
|
|
3255
|
-
}
|
|
1756
|
+
mkdirSync(join(slicesDir, sid, "tasks"), { recursive: true });
|
|
1757
|
+
}
|
|
1758
|
+
const resolvedSliceDir = resolveDir(slicesDir, sid) ?? sid;
|
|
1759
|
+
const tasksDir = join(slicesDir, resolvedSliceDir, "tasks");
|
|
1760
|
+
if (!existsSync(tasksDir)) {
|
|
1761
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
3256
1762
|
}
|
|
3257
1763
|
}
|
|
3258
1764
|
}
|
|
@@ -3261,10 +1767,6 @@ function ensurePreconditions(
|
|
|
3261
1767
|
|
|
3262
1768
|
// ─── Diagnostics ──────────────────────────────────────────────────────────────
|
|
3263
1769
|
|
|
3264
|
-
// collectObservabilityWarnings + buildObservabilityRepairBlock → auto-observability.ts
|
|
3265
|
-
|
|
3266
|
-
// recoverTimedOutUnit → auto-timeout-recovery.ts
|
|
3267
|
-
|
|
3268
1770
|
/** Build recovery context from module state for recoverTimedOutUnit */
|
|
3269
1771
|
function buildRecoveryContext(): import("./auto-timeout-recovery.js").RecoveryContext {
|
|
3270
1772
|
return { basePath: s.basePath, verbose: s.verbose,
|
|
@@ -3288,7 +1790,6 @@ export {
|
|
|
3288
1790
|
*/
|
|
3289
1791
|
export function _getUnitConsecutiveSkips(): Map<string, number> { return s.unitConsecutiveSkips; }
|
|
3290
1792
|
export function _resetUnitConsecutiveSkips(): void { s.unitConsecutiveSkips.clear(); }
|
|
3291
|
-
// MAX_CONSECUTIVE_SKIPS re-exported from auto/session.ts at top of file
|
|
3292
1793
|
|
|
3293
1794
|
/**
|
|
3294
1795
|
* Dispatch a hook unit directly, bypassing normal pre-dispatch hooks.
|
|
@@ -3304,9 +1805,7 @@ export async function dispatchHookUnit(
|
|
|
3304
1805
|
hookModel: string | undefined,
|
|
3305
1806
|
targetBasePath: string,
|
|
3306
1807
|
): Promise<boolean> {
|
|
3307
|
-
// Ensure auto-mode is s.active
|
|
3308
1808
|
if (!s.active) {
|
|
3309
|
-
// Initialize auto-mode state minimally
|
|
3310
1809
|
s.active = true;
|
|
3311
1810
|
s.stepMode = true;
|
|
3312
1811
|
s.cmdCtx = ctx as ExtensionCommandContext;
|
|
@@ -3319,21 +1818,17 @@ export async function dispatchHookUnit(
|
|
|
3319
1818
|
|
|
3320
1819
|
const hookUnitType = `hook/${hookName}`;
|
|
3321
1820
|
const hookStartedAt = Date.now();
|
|
3322
|
-
|
|
3323
|
-
// Set up the trigger unit as the "current" unit so post-unit hooks can reference it
|
|
1821
|
+
|
|
3324
1822
|
s.currentUnit = { type: triggerUnitType, id: triggerUnitId, startedAt: hookStartedAt };
|
|
3325
|
-
|
|
3326
|
-
// Create a new session for the hook
|
|
1823
|
+
|
|
3327
1824
|
const result = await s.cmdCtx!.newSession();
|
|
3328
1825
|
if (result.cancelled) {
|
|
3329
1826
|
await stopAuto(ctx, pi);
|
|
3330
1827
|
return false;
|
|
3331
1828
|
}
|
|
3332
1829
|
|
|
3333
|
-
// Update current unit to the hook unit
|
|
3334
1830
|
s.currentUnit = { type: hookUnitType, id: triggerUnitId, startedAt: hookStartedAt };
|
|
3335
|
-
|
|
3336
|
-
// Write runtime record
|
|
1831
|
+
|
|
3337
1832
|
writeUnitRuntimeRecord(s.basePath, hookUnitType, triggerUnitId, hookStartedAt, {
|
|
3338
1833
|
phase: "dispatched",
|
|
3339
1834
|
wrapupWarningSent: false,
|
|
@@ -3343,7 +1838,6 @@ export async function dispatchHookUnit(
|
|
|
3343
1838
|
lastProgressKind: "dispatch",
|
|
3344
1839
|
});
|
|
3345
1840
|
|
|
3346
|
-
// Switch model if specified
|
|
3347
1841
|
if (hookModel) {
|
|
3348
1842
|
const availableModels = ctx.modelRegistry.getAvailable();
|
|
3349
1843
|
const match = availableModels.find(m =>
|
|
@@ -3352,15 +1846,14 @@ export async function dispatchHookUnit(
|
|
|
3352
1846
|
if (match) {
|
|
3353
1847
|
try {
|
|
3354
1848
|
await pi.setModel(match);
|
|
3355
|
-
} catch { /* non-fatal
|
|
1849
|
+
} catch { /* non-fatal */ }
|
|
3356
1850
|
}
|
|
3357
1851
|
}
|
|
3358
1852
|
|
|
3359
|
-
// Write lock
|
|
3360
1853
|
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
1854
|
+
updateSessionLock(lockBase(), hookUnitType, triggerUnitId, s.completedUnits.length, sessionFile);
|
|
3361
1855
|
writeLock(lockBase(), hookUnitType, triggerUnitId, s.completedUnits.length, sessionFile);
|
|
3362
1856
|
|
|
3363
|
-
// Set up timeout
|
|
3364
1857
|
clearUnitTimeout();
|
|
3365
1858
|
const supervisor = resolveAutoSupervisorConfig();
|
|
3366
1859
|
const hookHardTimeoutMs = (supervisor.hard_timeout_minutes ?? 30) * 60 * 1000;
|
|
@@ -3381,18 +1874,16 @@ export async function dispatchHookUnit(
|
|
|
3381
1874
|
await pauseAuto(ctx, pi);
|
|
3382
1875
|
}, hookHardTimeoutMs);
|
|
3383
1876
|
|
|
3384
|
-
// Update status
|
|
3385
1877
|
ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
|
|
3386
1878
|
ctx.ui.notify(`Running post-unit hook: ${hookName}`, "info");
|
|
3387
1879
|
|
|
3388
|
-
// Send the hook prompt
|
|
3389
1880
|
console.log(`[dispatchHookUnit] Sending prompt of length ${hookPrompt.length}`);
|
|
3390
1881
|
console.log(`[dispatchHookUnit] Prompt preview: ${hookPrompt.substring(0, 200)}...`);
|
|
3391
1882
|
pi.sendMessage(
|
|
3392
1883
|
{ customType: "gsd-auto", content: hookPrompt, display: true },
|
|
3393
1884
|
{ triggerTurn: true },
|
|
3394
1885
|
);
|
|
3395
|
-
|
|
1886
|
+
|
|
3396
1887
|
return true;
|
|
3397
1888
|
}
|
|
3398
1889
|
|