mstro-app 0.4.52 → 0.5.1
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/LICENSE +129 -190
- package/PRIVACY.md +3 -3
- package/README.md +15 -6
- package/bin/commands/config.js +0 -1
- package/bin/mstro.js +1 -2
- package/bin/postinstall.js +0 -1
- package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker-process.js +0 -1
- package/dist/server/cli/headless/claude-invoker-process.js.map +1 -1
- package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker-stall.js +7 -3
- package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -1
- package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker-stream.js +0 -1
- package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -1
- package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker-tools.js +0 -1
- package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -1
- package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker.js +1 -2
- package/dist/server/cli/headless/claude-invoker.js.map +1 -1
- package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -1
- package/dist/server/cli/headless/haiku-assessments.js +0 -1
- package/dist/server/cli/headless/haiku-assessments.js.map +1 -1
- package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
- package/dist/server/cli/headless/headless-logger.js +0 -1
- package/dist/server/cli/headless/headless-logger.js.map +1 -1
- package/dist/server/cli/headless/index.d.ts.map +1 -1
- package/dist/server/cli/headless/index.js +0 -1
- package/dist/server/cli/headless/index.js.map +1 -1
- package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -1
- package/dist/server/cli/headless/native-timeout-detector.js +0 -1
- package/dist/server/cli/headless/native-timeout-detector.js.map +1 -1
- package/dist/server/cli/headless/output-utils.d.ts.map +1 -1
- package/dist/server/cli/headless/output-utils.js +0 -1
- package/dist/server/cli/headless/output-utils.js.map +1 -1
- package/dist/server/cli/headless/prompt-utils.d.ts.map +1 -1
- package/dist/server/cli/headless/prompt-utils.js +0 -1
- package/dist/server/cli/headless/prompt-utils.js.map +1 -1
- package/dist/server/cli/headless/resilient-runner.d.ts.map +1 -1
- package/dist/server/cli/headless/resilient-runner.js +0 -1
- package/dist/server/cli/headless/resilient-runner.js.map +1 -1
- package/dist/server/cli/headless/retry-strategies.d.ts.map +1 -1
- package/dist/server/cli/headless/retry-strategies.js +0 -1
- package/dist/server/cli/headless/retry-strategies.js.map +1 -1
- package/dist/server/cli/headless/runner.d.ts.map +1 -1
- package/dist/server/cli/headless/runner.js +63 -68
- package/dist/server/cli/headless/runner.js.map +1 -1
- package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
- package/dist/server/cli/headless/stall-assessor.js +9 -5
- package/dist/server/cli/headless/stall-assessor.js.map +1 -1
- package/dist/server/cli/headless/tool-watchdog.d.ts.map +1 -1
- package/dist/server/cli/headless/tool-watchdog.js +0 -1
- package/dist/server/cli/headless/tool-watchdog.js.map +1 -1
- package/dist/server/cli/headless/types.d.ts.map +1 -1
- package/dist/server/cli/headless/types.js +0 -1
- package/dist/server/cli/headless/types.js.map +1 -1
- package/dist/server/cli/improvisation-attachments.d.ts.map +1 -1
- package/dist/server/cli/improvisation-attachments.js +0 -1
- package/dist/server/cli/improvisation-attachments.js.map +1 -1
- package/dist/server/cli/improvisation-history-store.d.ts +16 -0
- package/dist/server/cli/improvisation-history-store.d.ts.map +1 -0
- package/dist/server/cli/improvisation-history-store.js +51 -0
- package/dist/server/cli/improvisation-history-store.js.map +1 -0
- package/dist/server/cli/improvisation-movements.d.ts +31 -0
- package/dist/server/cli/improvisation-movements.d.ts.map +1 -0
- package/dist/server/cli/improvisation-movements.js +92 -0
- package/dist/server/cli/improvisation-movements.js.map +1 -0
- package/dist/server/cli/improvisation-output-queue.d.ts +13 -0
- package/dist/server/cli/improvisation-output-queue.d.ts.map +1 -0
- package/dist/server/cli/improvisation-output-queue.js +39 -0
- package/dist/server/cli/improvisation-output-queue.js.map +1 -0
- package/dist/server/cli/improvisation-retry.d.ts +21 -51
- package/dist/server/cli/improvisation-retry.d.ts.map +1 -1
- package/dist/server/cli/improvisation-retry.js +18 -434
- package/dist/server/cli/improvisation-retry.js.map +1 -1
- package/dist/server/cli/improvisation-session-manager.d.ts +10 -8
- package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
- package/dist/server/cli/improvisation-session-manager.js +53 -149
- package/dist/server/cli/improvisation-session-manager.js.map +1 -1
- package/dist/server/cli/improvisation-types.d.ts.map +1 -1
- package/dist/server/cli/improvisation-types.js +0 -1
- package/dist/server/cli/improvisation-types.js.map +1 -1
- package/dist/server/cli/retry/retry-best-result.d.ts +4 -0
- package/dist/server/cli/retry/retry-best-result.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-best-result.js +60 -0
- package/dist/server/cli/retry/retry-best-result.js.map +1 -0
- package/dist/server/cli/retry/retry-context-loss.d.ts +6 -0
- package/dist/server/cli/retry/retry-context-loss.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-context-loss.js +67 -0
- package/dist/server/cli/retry/retry-context-loss.js.map +1 -0
- package/dist/server/cli/retry/retry-premature-completion.d.ts +5 -0
- package/dist/server/cli/retry/retry-premature-completion.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-premature-completion.js +80 -0
- package/dist/server/cli/retry/retry-premature-completion.js.map +1 -0
- package/dist/server/cli/retry/retry-recovery-strategies.d.ts +13 -0
- package/dist/server/cli/retry/retry-recovery-strategies.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-recovery-strategies.js +165 -0
- package/dist/server/cli/retry/retry-recovery-strategies.js.map +1 -0
- package/dist/server/cli/retry/retry-resume-strategy.d.ts +12 -0
- package/dist/server/cli/retry/retry-resume-strategy.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-resume-strategy.js +21 -0
- package/dist/server/cli/retry/retry-resume-strategy.js.map +1 -0
- package/dist/server/cli/retry/retry-runner-factory.d.ts +11 -0
- package/dist/server/cli/retry/retry-runner-factory.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-runner-factory.js +59 -0
- package/dist/server/cli/retry/retry-runner-factory.js.map +1 -0
- package/dist/server/cli/retry/retry-tool-results.d.ts +9 -0
- package/dist/server/cli/retry/retry-tool-results.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-tool-results.js +23 -0
- package/dist/server/cli/retry/retry-tool-results.js.map +1 -0
- package/dist/server/cli/retry/retry-types.d.ts +30 -0
- package/dist/server/cli/retry/retry-types.d.ts.map +1 -0
- package/dist/server/cli/retry/retry-types.js +3 -0
- package/dist/server/cli/retry/retry-types.js.map +1 -0
- package/dist/server/index.js +21 -110
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp/bouncer-cli.js +0 -1
- package/dist/server/mcp/bouncer-cli.js.map +1 -1
- package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -1
- package/dist/server/mcp/bouncer-haiku.js +0 -1
- package/dist/server/mcp/bouncer-haiku.js.map +1 -1
- package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
- package/dist/server/mcp/bouncer-integration.js +0 -1
- package/dist/server/mcp/bouncer-integration.js.map +1 -1
- package/dist/server/mcp/security-analysis.d.ts.map +1 -1
- package/dist/server/mcp/security-analysis.js +0 -1
- package/dist/server/mcp/security-analysis.js.map +1 -1
- package/dist/server/mcp/security-audit.d.ts.map +1 -1
- package/dist/server/mcp/security-audit.js +0 -1
- package/dist/server/mcp/security-audit.js.map +1 -1
- package/dist/server/mcp/security-patterns.d.ts.map +1 -1
- package/dist/server/mcp/security-patterns.js +0 -1
- package/dist/server/mcp/security-patterns.js.map +1 -1
- package/dist/server/mcp/server.js +0 -1
- package/dist/server/mcp/server.js.map +1 -1
- package/dist/server/routes/files.d.ts.map +1 -1
- package/dist/server/routes/files.js +0 -1
- package/dist/server/routes/files.js.map +1 -1
- package/dist/server/routes/improvise.d.ts.map +1 -1
- package/dist/server/routes/improvise.js +0 -1
- package/dist/server/routes/improvise.js.map +1 -1
- package/dist/server/routes/index.d.ts.map +1 -1
- package/dist/server/routes/index.js +0 -1
- package/dist/server/routes/index.js.map +1 -1
- package/dist/server/routes/instances.d.ts.map +1 -1
- package/dist/server/routes/instances.js +0 -1
- package/dist/server/routes/instances.js.map +1 -1
- package/dist/server/routes/notifications.d.ts.map +1 -1
- package/dist/server/routes/notifications.js +0 -1
- package/dist/server/routes/notifications.js.map +1 -1
- package/dist/server/server-setup.d.ts +16 -1
- package/dist/server/server-setup.d.ts.map +1 -1
- package/dist/server/server-setup.js +107 -1
- package/dist/server/server-setup.js.map +1 -1
- package/dist/server/services/analytics.d.ts.map +1 -1
- package/dist/server/services/analytics.js +0 -1
- package/dist/server/services/analytics.js.map +1 -1
- package/dist/server/services/auth.d.ts.map +1 -1
- package/dist/server/services/auth.js +0 -1
- package/dist/server/services/auth.js.map +1 -1
- package/dist/server/services/client-id.d.ts.map +1 -1
- package/dist/server/services/client-id.js +0 -1
- package/dist/server/services/client-id.js.map +1 -1
- package/dist/server/services/file-explorer-ops.d.ts.map +1 -1
- package/dist/server/services/file-explorer-ops.js +0 -1
- package/dist/server/services/file-explorer-ops.js.map +1 -1
- package/dist/server/services/files.d.ts.map +1 -1
- package/dist/server/services/files.js +0 -1
- package/dist/server/services/files.js.map +1 -1
- package/dist/server/services/instances.d.ts.map +1 -1
- package/dist/server/services/instances.js +0 -1
- package/dist/server/services/instances.js.map +1 -1
- package/dist/server/services/pathUtils.d.ts.map +1 -1
- package/dist/server/services/pathUtils.js +0 -1
- package/dist/server/services/pathUtils.js.map +1 -1
- package/dist/server/services/plan/agent-loader.d.ts.map +1 -1
- package/dist/server/services/plan/agent-loader.js +0 -1
- package/dist/server/services/plan/agent-loader.js.map +1 -1
- package/dist/server/services/plan/board-config.d.ts +21 -0
- package/dist/server/services/plan/board-config.d.ts.map +1 -0
- package/dist/server/services/plan/board-config.js +111 -0
- package/dist/server/services/plan/board-config.js.map +1 -0
- package/dist/server/services/plan/composer.d.ts +1 -1
- package/dist/server/services/plan/composer.d.ts.map +1 -1
- package/dist/server/services/plan/composer.js +7 -6
- package/dist/server/services/plan/composer.js.map +1 -1
- package/dist/server/services/plan/config-installer.d.ts.map +1 -1
- package/dist/server/services/plan/config-installer.js +0 -1
- package/dist/server/services/plan/config-installer.js.map +1 -1
- package/dist/server/services/plan/dependency-resolver.d.ts.map +1 -1
- package/dist/server/services/plan/dependency-resolver.js +0 -1
- package/dist/server/services/plan/dependency-resolver.js.map +1 -1
- package/dist/server/services/plan/executor.d.ts +48 -48
- package/dist/server/services/plan/executor.d.ts.map +1 -1
- package/dist/server/services/plan/executor.js +202 -458
- package/dist/server/services/plan/executor.js.map +1 -1
- package/dist/server/services/plan/front-matter.d.ts.map +1 -1
- package/dist/server/services/plan/front-matter.js +0 -1
- package/dist/server/services/plan/front-matter.js.map +1 -1
- package/dist/server/services/plan/issue-classification.d.ts.map +1 -1
- package/dist/server/services/plan/issue-classification.js +0 -1
- package/dist/server/services/plan/issue-classification.js.map +1 -1
- package/dist/server/services/plan/issue-loader.d.ts +16 -0
- package/dist/server/services/plan/issue-loader.d.ts.map +1 -0
- package/dist/server/services/plan/issue-loader.js +45 -0
- package/dist/server/services/plan/issue-loader.js.map +1 -0
- package/dist/server/services/plan/issue-prompt-builder.d.ts.map +1 -1
- package/dist/server/services/plan/issue-prompt-builder.js +0 -1
- package/dist/server/services/plan/issue-prompt-builder.js.map +1 -1
- package/dist/server/services/plan/issue-retry.d.ts +3 -1
- package/dist/server/services/plan/issue-retry.d.ts.map +1 -1
- package/dist/server/services/plan/issue-retry.js +2 -1
- package/dist/server/services/plan/issue-retry.js.map +1 -1
- package/dist/server/services/plan/issue-writer.d.ts +34 -0
- package/dist/server/services/plan/issue-writer.d.ts.map +1 -0
- package/dist/server/services/plan/issue-writer.js +109 -0
- package/dist/server/services/plan/issue-writer.js.map +1 -0
- package/dist/server/services/plan/output-manager.js +2 -2
- package/dist/server/services/plan/output-manager.js.map +1 -1
- package/dist/server/services/plan/parser-core.d.ts.map +1 -1
- package/dist/server/services/plan/parser-core.js +0 -1
- package/dist/server/services/plan/parser-core.js.map +1 -1
- package/dist/server/services/plan/parser-migration.d.ts.map +1 -1
- package/dist/server/services/plan/parser-migration.js +0 -1
- package/dist/server/services/plan/parser-migration.js.map +1 -1
- package/dist/server/services/plan/parser.d.ts.map +1 -1
- package/dist/server/services/plan/parser.js +0 -1
- package/dist/server/services/plan/parser.js.map +1 -1
- package/dist/server/services/plan/progress-log.d.ts +11 -0
- package/dist/server/services/plan/progress-log.d.ts.map +1 -0
- package/dist/server/services/plan/progress-log.js +80 -0
- package/dist/server/services/plan/progress-log.js.map +1 -0
- package/dist/server/services/plan/prompt-builder.d.ts.map +1 -1
- package/dist/server/services/plan/prompt-builder.js +48 -32
- package/dist/server/services/plan/prompt-builder.js.map +1 -1
- package/dist/server/services/plan/readiness-planner.d.ts +15 -0
- package/dist/server/services/plan/readiness-planner.d.ts.map +1 -0
- package/dist/server/services/plan/readiness-planner.js +40 -0
- package/dist/server/services/plan/readiness-planner.js.map +1 -0
- package/dist/server/services/plan/review-gate.d.ts +31 -0
- package/dist/server/services/plan/review-gate.d.ts.map +1 -1
- package/dist/server/services/plan/review-gate.js +52 -3
- package/dist/server/services/plan/review-gate.js.map +1 -1
- package/dist/server/services/plan/state-reconciler.d.ts.map +1 -1
- package/dist/server/services/plan/state-reconciler.js +0 -1
- package/dist/server/services/plan/state-reconciler.js.map +1 -1
- package/dist/server/services/plan/types.d.ts.map +1 -1
- package/dist/server/services/plan/types.js +0 -1
- package/dist/server/services/plan/types.js.map +1 -1
- package/dist/server/services/plan/watcher.d.ts.map +1 -1
- package/dist/server/services/plan/watcher.js +0 -1
- package/dist/server/services/plan/watcher.js.map +1 -1
- package/dist/server/services/platform-credentials.d.ts.map +1 -1
- package/dist/server/services/platform-credentials.js +0 -1
- package/dist/server/services/platform-credentials.js.map +1 -1
- package/dist/server/services/platform-token-lifecycle.d.ts +70 -0
- package/dist/server/services/platform-token-lifecycle.d.ts.map +1 -0
- package/dist/server/services/platform-token-lifecycle.js +156 -0
- package/dist/server/services/platform-token-lifecycle.js.map +1 -0
- package/dist/server/services/platform.d.ts +25 -4
- package/dist/server/services/platform.d.ts.map +1 -1
- package/dist/server/services/platform.js +150 -92
- package/dist/server/services/platform.js.map +1 -1
- package/dist/server/services/sentry.d.ts.map +1 -1
- package/dist/server/services/sentry.js +0 -1
- package/dist/server/services/sentry.js.map +1 -1
- package/dist/server/services/settings.d.ts.map +1 -1
- package/dist/server/services/settings.js +0 -1
- package/dist/server/services/settings.js.map +1 -1
- package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
- package/dist/server/services/terminal/pty-manager.js +0 -1
- package/dist/server/services/terminal/pty-manager.js.map +1 -1
- package/dist/server/services/terminal/pty-utils.d.ts.map +1 -1
- package/dist/server/services/terminal/pty-utils.js +0 -1
- package/dist/server/services/terminal/pty-utils.js.map +1 -1
- package/dist/server/services/websocket/autocomplete.d.ts.map +1 -1
- package/dist/server/services/websocket/autocomplete.js +0 -1
- package/dist/server/services/websocket/autocomplete.js.map +1 -1
- package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/file-definition-handlers.js +0 -1
- package/dist/server/services/websocket/file-definition-handlers.js.map +1 -1
- package/dist/server/services/websocket/file-download-handler.d.ts +17 -0
- package/dist/server/services/websocket/file-download-handler.d.ts.map +1 -0
- package/dist/server/services/websocket/file-download-handler.js +164 -0
- package/dist/server/services/websocket/file-download-handler.js.map +1 -0
- package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/file-explorer-handlers.js +0 -1
- package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
- package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/file-search-handlers.js +0 -1
- package/dist/server/services/websocket/file-search-handlers.js.map +1 -1
- package/dist/server/services/websocket/file-upload-handler.d.ts +2 -3
- package/dist/server/services/websocket/file-upload-handler.d.ts.map +1 -1
- package/dist/server/services/websocket/file-upload-handler.js +4 -7
- package/dist/server/services/websocket/file-upload-handler.js.map +1 -1
- package/dist/server/services/websocket/file-utils.d.ts.map +1 -1
- package/dist/server/services/websocket/file-utils.js +0 -1
- package/dist/server/services/websocket/file-utils.js.map +1 -1
- package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-branch-handlers.js +0 -1
- package/dist/server/services/websocket/git-branch-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-diff-handlers.js +0 -1
- package/dist/server/services/websocket/git-diff-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-handlers.js +58 -6
- package/dist/server/services/websocket/git-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-head-watcher.d.ts.map +1 -1
- package/dist/server/services/websocket/git-head-watcher.js +0 -1
- package/dist/server/services/websocket/git-head-watcher.js.map +1 -1
- package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-log-handlers.js +0 -1
- package/dist/server/services/websocket/git-log-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-pr-handlers.js +0 -1
- package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-tag-handlers.js +0 -1
- package/dist/server/services/websocket/git-tag-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-utils.d.ts +18 -3
- package/dist/server/services/websocket/git-utils.d.ts.map +1 -1
- package/dist/server/services/websocket/git-utils.js +58 -8
- package/dist/server/services/websocket/git-utils.js.map +1 -1
- package/dist/server/services/websocket/git-worktree-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-worktree-handlers.js +258 -16
- package/dist/server/services/websocket/git-worktree-handlers.js.map +1 -1
- package/dist/server/services/websocket/handler-context.d.ts +15 -0
- package/dist/server/services/websocket/handler-context.d.ts.map +1 -1
- package/dist/server/services/websocket/handler-context.js +0 -1
- package/dist/server/services/websocket/handler-context.js.map +1 -1
- package/dist/server/services/websocket/handler.d.ts +7 -0
- package/dist/server/services/websocket/handler.d.ts.map +1 -1
- package/dist/server/services/websocket/handler.js +76 -15
- package/dist/server/services/websocket/handler.js.map +1 -1
- package/dist/server/services/websocket/index.d.ts.map +1 -1
- package/dist/server/services/websocket/index.js +0 -1
- package/dist/server/services/websocket/index.js.map +1 -1
- package/dist/server/services/websocket/msg-id-tracker.d.ts +21 -0
- package/dist/server/services/websocket/msg-id-tracker.d.ts.map +1 -0
- package/dist/server/services/websocket/msg-id-tracker.js +76 -0
- package/dist/server/services/websocket/msg-id-tracker.js.map +1 -0
- package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-board-handlers.js +0 -1
- package/dist/server/services/websocket/plan-board-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-execution-handlers.js +6 -2
- package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-handlers.js +0 -1
- package/dist/server/services/websocket/plan-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-helpers.js +0 -1
- package/dist/server/services/websocket/plan-helpers.js.map +1 -1
- package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-issue-handlers.js +0 -1
- package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-sprint-handlers.js +0 -1
- package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -1
- package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-complexity.js +0 -1
- package/dist/server/services/websocket/quality-complexity.js.map +1 -1
- package/dist/server/services/websocket/quality-grading.d.ts +46 -0
- package/dist/server/services/websocket/quality-grading.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-grading.js +482 -0
- package/dist/server/services/websocket/quality-grading.js.map +1 -0
- package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-handlers.js +15 -4
- package/dist/server/services/websocket/quality-handlers.js.map +1 -1
- package/dist/server/services/websocket/quality-linting.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-linting.js +0 -1
- package/dist/server/services/websocket/quality-linting.js.map +1 -1
- package/dist/server/services/websocket/quality-persistence.d.ts +14 -0
- package/dist/server/services/websocket/quality-persistence.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-persistence.js +28 -12
- package/dist/server/services/websocket/quality-persistence.js.map +1 -1
- package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-review-agent.js +2 -3
- package/dist/server/services/websocket/quality-review-agent.js.map +1 -1
- package/dist/server/services/websocket/quality-service.d.ts +3 -1
- package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-service.js +53 -58
- package/dist/server/services/websocket/quality-service.js.map +1 -1
- package/dist/server/services/websocket/quality-tools.d.ts +1 -1
- package/dist/server/services/websocket/quality-tools.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-tools.js +6 -3
- package/dist/server/services/websocket/quality-tools.js.map +1 -1
- package/dist/server/services/websocket/quality-types.d.ts +18 -2
- package/dist/server/services/websocket/quality-types.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-types.js +0 -1
- package/dist/server/services/websocket/quality-types.js.map +1 -1
- package/dist/server/services/websocket/session-handlers.d.ts +48 -2
- package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/session-handlers.js +204 -66
- package/dist/server/services/websocket/session-handlers.js.map +1 -1
- package/dist/server/services/websocket/session-history.d.ts.map +1 -1
- package/dist/server/services/websocket/session-history.js +0 -1
- package/dist/server/services/websocket/session-history.js.map +1 -1
- package/dist/server/services/websocket/session-initialization.d.ts +2 -2
- package/dist/server/services/websocket/session-initialization.d.ts.map +1 -1
- package/dist/server/services/websocket/session-initialization.js +75 -18
- package/dist/server/services/websocket/session-initialization.js.map +1 -1
- package/dist/server/services/websocket/session-registry.d.ts +29 -1
- package/dist/server/services/websocket/session-registry.d.ts.map +1 -1
- package/dist/server/services/websocket/session-registry.js +53 -5
- package/dist/server/services/websocket/session-registry.js.map +1 -1
- package/dist/server/services/websocket/settings-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/settings-handlers.js +0 -1
- package/dist/server/services/websocket/settings-handlers.js.map +1 -1
- package/dist/server/services/websocket/skill-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/skill-handlers.js +0 -1
- package/dist/server/services/websocket/skill-handlers.js.map +1 -1
- package/dist/server/services/websocket/skill-watcher.d.ts.map +1 -1
- package/dist/server/services/websocket/skill-watcher.js +0 -1
- package/dist/server/services/websocket/skill-watcher.js.map +1 -1
- package/dist/server/services/websocket/tab-broadcast.d.ts +24 -0
- package/dist/server/services/websocket/tab-broadcast.d.ts.map +1 -0
- package/dist/server/services/websocket/tab-broadcast.js +12 -0
- package/dist/server/services/websocket/tab-broadcast.js.map +1 -0
- package/dist/server/services/websocket/tab-event-buffer.d.ts +103 -0
- package/dist/server/services/websocket/tab-event-buffer.d.ts.map +1 -0
- package/dist/server/services/websocket/tab-event-buffer.js +106 -0
- package/dist/server/services/websocket/tab-event-buffer.js.map +1 -0
- package/dist/server/services/websocket/tab-event-replay.d.ts +20 -0
- package/dist/server/services/websocket/tab-event-replay.d.ts.map +1 -0
- package/dist/server/services/websocket/tab-event-replay.js +20 -0
- package/dist/server/services/websocket/tab-event-replay.js.map +1 -0
- package/dist/server/services/websocket/tab-handlers.d.ts +0 -1
- package/dist/server/services/websocket/tab-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/tab-handlers.js +2 -10
- package/dist/server/services/websocket/tab-handlers.js.map +1 -1
- package/dist/server/services/websocket/terminal-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/terminal-handlers.js +39 -4
- package/dist/server/services/websocket/terminal-handlers.js.map +1 -1
- package/dist/server/services/websocket/types.d.ts +17 -8
- package/dist/server/services/websocket/types.d.ts.map +1 -1
- package/dist/server/services/websocket/types.js +8 -7
- package/dist/server/services/websocket/types.js.map +1 -1
- package/dist/server/utils/agent-manager.d.ts.map +1 -1
- package/dist/server/utils/agent-manager.js +0 -1
- package/dist/server/utils/agent-manager.js.map +1 -1
- package/dist/server/utils/paths.d.ts.map +1 -1
- package/dist/server/utils/paths.js +0 -1
- package/dist/server/utils/paths.js.map +1 -1
- package/dist/server/utils/port-manager.d.ts.map +1 -1
- package/dist/server/utils/port-manager.js +0 -1
- package/dist/server/utils/port-manager.js.map +1 -1
- package/dist/server/utils/port.d.ts.map +1 -1
- package/dist/server/utils/port.js +0 -1
- package/dist/server/utils/port.js.map +1 -1
- package/package.json +2 -2
- package/server/README.md +1 -1
- package/server/cli/headless/claude-invoker-process.ts +0 -1
- package/server/cli/headless/claude-invoker-stall.ts +7 -3
- package/server/cli/headless/claude-invoker-stream.ts +0 -1
- package/server/cli/headless/claude-invoker-tools.ts +0 -1
- package/server/cli/headless/claude-invoker.ts +1 -2
- package/server/cli/headless/haiku-assessments.ts +0 -1
- package/server/cli/headless/headless-logger.ts +0 -1
- package/server/cli/headless/index.ts +0 -1
- package/server/cli/headless/native-timeout-detector.ts +0 -1
- package/server/cli/headless/output-utils.ts +0 -1
- package/server/cli/headless/prompt-utils.ts +0 -1
- package/server/cli/headless/resilient-runner.ts +0 -1
- package/server/cli/headless/retry-strategies.ts +0 -1
- package/server/cli/headless/runner.ts +67 -73
- package/server/cli/headless/stall-assessor.ts +9 -5
- package/server/cli/headless/tool-watchdog.ts +0 -1
- package/server/cli/headless/types.ts +1 -2
- package/server/cli/improvisation-attachments.ts +0 -1
- package/server/cli/improvisation-history-store.ts +61 -0
- package/server/cli/improvisation-movements.ts +119 -0
- package/server/cli/improvisation-output-queue.ts +41 -0
- package/server/cli/improvisation-retry.ts +25 -601
- package/server/cli/improvisation-session-manager.ts +74 -161
- package/server/cli/improvisation-types.ts +0 -1
- package/server/cli/retry/retry-best-result.ts +69 -0
- package/server/cli/retry/retry-context-loss.ts +86 -0
- package/server/cli/retry/retry-premature-completion.ts +112 -0
- package/server/cli/retry/retry-recovery-strategies.ts +246 -0
- package/server/cli/retry/retry-resume-strategy.ts +32 -0
- package/server/cli/retry/retry-runner-factory.ts +69 -0
- package/server/cli/retry/retry-tool-results.ts +30 -0
- package/server/cli/retry/retry-types.ts +31 -0
- package/server/index.ts +37 -124
- package/server/mcp/bouncer-cli.ts +0 -1
- package/server/mcp/bouncer-haiku.ts +0 -1
- package/server/mcp/bouncer-integration.ts +0 -1
- package/server/mcp/security-analysis.ts +0 -1
- package/server/mcp/security-audit.ts +0 -1
- package/server/mcp/security-patterns.ts +0 -1
- package/server/mcp/server.ts +0 -1
- package/server/routes/files.ts +0 -1
- package/server/routes/improvise.ts +0 -1
- package/server/routes/index.ts +0 -1
- package/server/routes/instances.ts +0 -1
- package/server/routes/notifications.ts +0 -1
- package/server/server-setup.ts +126 -2
- package/server/services/analytics.ts +0 -1
- package/server/services/auth.ts +0 -1
- package/server/services/client-id.ts +0 -1
- package/server/services/file-explorer-ops.ts +0 -1
- package/server/services/files.ts +0 -1
- package/server/services/instances.ts +0 -1
- package/server/services/pathUtils.ts +0 -1
- package/server/services/plan/agent-loader.ts +0 -1
- package/server/services/plan/agents/assess-stall.md +11 -4
- package/server/services/plan/agents/code-review.md +13 -11
- package/server/services/plan/board-config.ts +121 -0
- package/server/services/plan/composer.ts +7 -6
- package/server/services/plan/config-installer.ts +0 -1
- package/server/services/plan/dependency-resolver.ts +0 -1
- package/server/services/plan/executor.ts +259 -470
- package/server/services/plan/front-matter.ts +0 -1
- package/server/services/plan/issue-classification.ts +0 -1
- package/server/services/plan/issue-loader.ts +63 -0
- package/server/services/plan/issue-prompt-builder.ts +0 -1
- package/server/services/plan/issue-retry.ts +5 -2
- package/server/services/plan/issue-writer.ts +136 -0
- package/server/services/plan/output-manager.ts +2 -2
- package/server/services/plan/parser-core.ts +0 -1
- package/server/services/plan/parser-migration.ts +0 -1
- package/server/services/plan/parser.ts +0 -1
- package/server/services/plan/progress-log.ts +91 -0
- package/server/services/plan/prompt-builder.ts +73 -36
- package/server/services/plan/readiness-planner.ts +49 -0
- package/server/services/plan/review-gate.ts +102 -3
- package/server/services/plan/state-reconciler.ts +0 -1
- package/server/services/plan/types.ts +0 -1
- package/server/services/plan/watcher.ts +0 -1
- package/server/services/platform-credentials.ts +0 -1
- package/server/services/platform-token-lifecycle.ts +171 -0
- package/server/services/platform.ts +168 -105
- package/server/services/sentry.ts +0 -1
- package/server/services/settings.ts +0 -1
- package/server/services/terminal/pty-manager.ts +0 -1
- package/server/services/terminal/pty-utils.ts +0 -1
- package/server/services/websocket/autocomplete.ts +0 -1
- package/server/services/websocket/file-definition-handlers.ts +0 -1
- package/server/services/websocket/file-download-handler.ts +190 -0
- package/server/services/websocket/file-explorer-handlers.ts +0 -1
- package/server/services/websocket/file-search-handlers.ts +0 -1
- package/server/services/websocket/file-upload-handler.ts +6 -5
- package/server/services/websocket/file-utils.ts +0 -1
- package/server/services/websocket/git-branch-handlers.ts +0 -1
- package/server/services/websocket/git-diff-handlers.ts +0 -1
- package/server/services/websocket/git-handlers.ts +66 -10
- package/server/services/websocket/git-head-watcher.ts +0 -1
- package/server/services/websocket/git-log-handlers.ts +0 -1
- package/server/services/websocket/git-pr-handlers.ts +0 -1
- package/server/services/websocket/git-tag-handlers.ts +0 -1
- package/server/services/websocket/git-utils.ts +69 -9
- package/server/services/websocket/git-worktree-handlers.ts +289 -19
- package/server/services/websocket/handler-context.ts +15 -1
- package/server/services/websocket/handler.ts +79 -16
- package/server/services/websocket/index.ts +0 -1
- package/server/services/websocket/msg-id-tracker.ts +83 -0
- package/server/services/websocket/plan-board-handlers.ts +0 -1
- package/server/services/websocket/plan-execution-handlers.ts +6 -2
- package/server/services/websocket/plan-handlers.ts +0 -1
- package/server/services/websocket/plan-helpers.ts +0 -1
- package/server/services/websocket/plan-issue-handlers.ts +0 -1
- package/server/services/websocket/plan-sprint-handlers.ts +0 -1
- package/server/services/websocket/quality-complexity.ts +0 -1
- package/server/services/websocket/quality-grading.ts +611 -0
- package/server/services/websocket/quality-handlers.ts +16 -4
- package/server/services/websocket/quality-linting.ts +0 -1
- package/server/services/websocket/quality-persistence.ts +30 -8
- package/server/services/websocket/quality-review-agent.ts +2 -3
- package/server/services/websocket/quality-service.ts +54 -55
- package/server/services/websocket/quality-tools.ts +11 -3
- package/server/services/websocket/quality-types.ts +21 -3
- package/server/services/websocket/session-handlers.ts +213 -69
- package/server/services/websocket/session-history.ts +0 -1
- package/server/services/websocket/session-initialization.ts +83 -20
- package/server/services/websocket/session-registry.ts +61 -5
- package/server/services/websocket/settings-handlers.ts +0 -1
- package/server/services/websocket/skill-handlers.ts +0 -1
- package/server/services/websocket/skill-watcher.ts +0 -1
- package/server/services/websocket/tab-broadcast.ts +37 -0
- package/server/services/websocket/tab-event-buffer.ts +158 -0
- package/server/services/websocket/tab-event-replay.ts +41 -0
- package/server/services/websocket/tab-handlers.ts +2 -10
- package/server/services/websocket/terminal-handlers.ts +39 -3
- package/server/services/websocket/types.ts +19 -7
- package/server/utils/agent-manager.ts +0 -1
- package/server/utils/paths.ts +0 -1
- package/server/utils/port-manager.ts +0 -1
- package/server/utils/port.ts +0 -1
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// Copyright (c) 2025-present Mstro, Inc. All rights reserved.
|
|
2
|
-
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
2
|
/**
|
|
4
3
|
* Plan Executor — Wave-based execution with parallel headless Claude Code instances.
|
|
5
4
|
*
|
|
@@ -8,56 +7,84 @@
|
|
|
8
7
|
* reconciles state, and repeats.
|
|
9
8
|
*
|
|
10
9
|
* Implementation is split across focused modules:
|
|
10
|
+
* - board-config.ts — board.md metadata reads, workspace.json active board resolution
|
|
11
11
|
* - config-installer.ts — tool permissions install/uninstall
|
|
12
12
|
* - issue-prompt-builder.ts — per-issue prompt construction
|
|
13
|
+
* - issue-writer.ts — issue front-matter updates, recovery, revert, cancellation notes
|
|
13
14
|
* - output-manager.ts — output path resolution, listing, publishing
|
|
14
|
-
* -
|
|
15
|
-
* -
|
|
15
|
+
* - progress-log.ts — progress.md writer + output dir creation
|
|
16
|
+
* - review-gate.ts — AI-powered quality gate (review, parse, persist, full pipeline)
|
|
16
17
|
*/
|
|
17
18
|
import { EventEmitter } from 'node:events';
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
19
|
+
import { readFile } from 'node:fs/promises';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
21
|
import { runWithFileLogger } from '../../cli/headless/headless-logger.js';
|
|
22
|
+
import { DEFAULT_MAX_PARALLEL_AGENTS, getBoardMaxParallelAgents, resolveActiveBoardId, resolveBoardDir, tryCompleteBoardIfDone, } from './board-config.js';
|
|
22
23
|
import { ConfigInstaller } from './config-installer.js';
|
|
23
24
|
import { resolveReadyToWork } from './dependency-resolver.js';
|
|
24
|
-
import {
|
|
25
|
+
import { loadBoardIssues, loadProjectIssues } from './issue-loader.js';
|
|
25
26
|
import { buildIssuePrompt } from './issue-prompt-builder.js';
|
|
26
27
|
import { runIssueWithRetry } from './issue-retry.js';
|
|
28
|
+
import { extractIssueStatus, recoverStaleIssues, revertIncompleteIssues, updateIssueFrontMatter, validateIssuePath, } from './issue-writer.js';
|
|
27
29
|
import { listExistingDocs, publishOutputs, resolveOutputPath } from './output-manager.js';
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
+
import { resolvePmDir } from './parser.js';
|
|
31
|
+
import { appendProgressEntry, ensureOutputDirs } from './progress-log.js';
|
|
32
|
+
import { buildCompletionReason, detectDeadState, hasBlockedIssues } from './readiness-planner.js';
|
|
33
|
+
import { runReviewPipeline } from './review-gate.js';
|
|
30
34
|
import { reconcileState } from './state-reconciler.js';
|
|
31
|
-
/** Default max parallel agents when board doesn't specify. */
|
|
32
|
-
const DEFAULT_MAX_PARALLEL_AGENTS = 3;
|
|
33
35
|
/** Stop after this many consecutive waves with zero completions. */
|
|
34
36
|
const MAX_CONSECUTIVE_EMPTY_WAVES = 3;
|
|
37
|
+
/**
|
|
38
|
+
* Render a tool-call event as a short single-line label for the verbose
|
|
39
|
+
* Output stream — `Read · package.json`, `Bash · npm test`, etc. The web UI
|
|
40
|
+
* splits the label on the first ` · ` to render the tool name as a badge.
|
|
41
|
+
*/
|
|
42
|
+
function formatToolCallLine(toolName, input) {
|
|
43
|
+
const arg = pickPrimaryArg(toolName, input);
|
|
44
|
+
return arg ? `${toolName} · ${arg}` : toolName;
|
|
45
|
+
}
|
|
46
|
+
function pickPrimaryArg(toolName, input) {
|
|
47
|
+
if (!input)
|
|
48
|
+
return '';
|
|
49
|
+
// Map common Claude Code tools to their most informative single argument.
|
|
50
|
+
const candidates = {
|
|
51
|
+
Read: ['file_path'],
|
|
52
|
+
Write: ['file_path'],
|
|
53
|
+
Edit: ['file_path'],
|
|
54
|
+
Glob: ['pattern'],
|
|
55
|
+
Grep: ['pattern'],
|
|
56
|
+
Bash: ['command'],
|
|
57
|
+
WebFetch: ['url'],
|
|
58
|
+
WebSearch: ['query'],
|
|
59
|
+
Task: ['description'],
|
|
60
|
+
};
|
|
61
|
+
const keys = candidates[toolName] ?? ['command', 'file_path', 'path', 'pattern', 'url', 'query'];
|
|
62
|
+
for (const key of keys) {
|
|
63
|
+
const value = input[key];
|
|
64
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
65
|
+
return value.length > 120 ? `${value.slice(0, 117)}...` : value;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return '';
|
|
69
|
+
}
|
|
35
70
|
/** Per-issue stall timeouts (ms) — shorter than Agent Teams wave timeouts */
|
|
36
71
|
const ISSUE_STALL_WARNING_MS = 900_000; // 15 min
|
|
37
72
|
const ISSUE_STALL_KILL_MS = 1_800_000; // 30 min
|
|
38
|
-
const ISSUE_STALL_HARD_CAP_MS =
|
|
73
|
+
const ISSUE_STALL_HARD_CAP_MS = 14_400_000; // 4 hr backstop — only fires after stall signals flag the run
|
|
39
74
|
const ISSUE_STALL_MAX_EXTENSIONS = 10;
|
|
40
75
|
export class PlanExecutor extends EventEmitter {
|
|
41
76
|
status = 'idle';
|
|
42
77
|
workingDir;
|
|
78
|
+
extraEnv;
|
|
43
79
|
shouldStop = false;
|
|
44
80
|
shouldPause = false;
|
|
45
81
|
/** AbortController for killing running HeadlessRunner processes on stop. */
|
|
46
82
|
waveAbortController = null;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
boardDir = null;
|
|
52
|
-
/** Board ID being executed (e.g. "BOARD-001") */
|
|
53
|
-
boardId = null;
|
|
83
|
+
/** Resolved context for the current/last run — rebuilt each runStart(). */
|
|
84
|
+
context;
|
|
85
|
+
/** Options from the last run; replayed on resume() to preserve scope. */
|
|
86
|
+
lastStartOptions = {};
|
|
54
87
|
configInstaller;
|
|
55
|
-
/** Flag to prevent start() from clearing scope set by startBoard/startEpic */
|
|
56
|
-
_scopeSetByCall = false;
|
|
57
|
-
/** Extra environment variables forwarded to HeadlessRunner child processes (e.g. API keys) */
|
|
58
|
-
extraEnv;
|
|
59
|
-
/** Optional worktree directory for running AI agents. PM data is always read from workingDir. */
|
|
60
|
-
executionDir = null;
|
|
61
88
|
metrics = {
|
|
62
89
|
issuesCompleted: 0,
|
|
63
90
|
issuesAttempted: 0,
|
|
@@ -70,50 +97,49 @@ export class PlanExecutor extends EventEmitter {
|
|
|
70
97
|
this.workingDir = workingDir;
|
|
71
98
|
this.extraEnv = options?.extraEnv;
|
|
72
99
|
this.configInstaller = new ConfigInstaller(workingDir);
|
|
73
|
-
|
|
74
|
-
validateIssuePath(issuePath, baseDir) {
|
|
75
|
-
const resolvedBase = resolve(baseDir);
|
|
76
|
-
const resolvedFull = resolve(resolvedBase, issuePath);
|
|
77
|
-
const rel = relative(resolvedBase, resolvedFull);
|
|
78
|
-
if (rel === '' || rel.startsWith('..') || isAbsolute(rel)) {
|
|
79
|
-
throw new Error(`Invalid issue path: path traversal detected in "${issuePath}"`);
|
|
80
|
-
}
|
|
81
|
-
return resolvedFull;
|
|
100
|
+
this.context = this.buildContext({});
|
|
82
101
|
}
|
|
83
102
|
getStatus() { return this.status; }
|
|
84
103
|
getMetrics() { return { ...this.metrics }; }
|
|
85
|
-
|
|
86
|
-
this.
|
|
87
|
-
this._scopeSetByCall = true;
|
|
88
|
-
return this.start();
|
|
104
|
+
startEpic(epicPath) {
|
|
105
|
+
return this.runStart({ epic: epicPath });
|
|
89
106
|
}
|
|
90
107
|
/** Start execution, optionally scoped to a specific board. */
|
|
91
|
-
|
|
92
|
-
this.boardId
|
|
93
|
-
this.executionDir = executionDir ?? null;
|
|
94
|
-
this._scopeSetByCall = true;
|
|
95
|
-
return this.start();
|
|
108
|
+
startBoard(boardId, executionDir) {
|
|
109
|
+
return this.runStart({ board: boardId, executionDir });
|
|
96
110
|
}
|
|
97
|
-
|
|
111
|
+
start(options = {}) {
|
|
112
|
+
return this.runStart(options);
|
|
113
|
+
}
|
|
114
|
+
pause() { this.shouldPause = true; }
|
|
115
|
+
stop() {
|
|
116
|
+
this.shouldStop = true;
|
|
117
|
+
this.status = 'stopping';
|
|
118
|
+
this.emit('statusChanged', this.status);
|
|
119
|
+
// Kill all running HeadlessRunner processes in the current wave
|
|
120
|
+
this.waveAbortController?.abort();
|
|
121
|
+
}
|
|
122
|
+
resume() {
|
|
123
|
+
if (this.status !== 'paused')
|
|
124
|
+
return Promise.resolve();
|
|
125
|
+
this.shouldPause = false;
|
|
126
|
+
// Replay the options from the previous run to preserve epic/board scope.
|
|
127
|
+
return this.runStart(this.lastStartOptions);
|
|
128
|
+
}
|
|
129
|
+
// ── Run orchestration ────────────────────────────────────────
|
|
130
|
+
async runStart(options) {
|
|
98
131
|
if (this.status === 'executing' || this.status === 'starting')
|
|
99
132
|
return;
|
|
133
|
+
this.lastStartOptions = options;
|
|
100
134
|
this.shouldStop = false;
|
|
101
135
|
this.shouldPause = false;
|
|
102
|
-
// Reset scoping from previous runs unless explicitly set by startBoard/startEpic
|
|
103
|
-
if (!this._scopeSetByCall) {
|
|
104
|
-
this.epicScope = null;
|
|
105
|
-
this.boardId = null;
|
|
106
|
-
this.executionDir = null;
|
|
107
|
-
}
|
|
108
|
-
this._scopeSetByCall = false;
|
|
109
136
|
this.status = 'starting';
|
|
110
137
|
this.emit('statusChanged', this.status);
|
|
138
|
+
this.context = this.buildContext(options);
|
|
111
139
|
const startTime = Date.now();
|
|
112
140
|
this.status = 'executing';
|
|
113
141
|
this.emit('statusChanged', this.status);
|
|
114
|
-
this.
|
|
115
|
-
this.boardDir = this.resolveBoardDir();
|
|
116
|
-
await this.recoverStaleIssues();
|
|
142
|
+
await this.runStaleRecovery();
|
|
117
143
|
const stallResult = await this.runWaveLoop();
|
|
118
144
|
this.metrics.totalDuration = Date.now() - startTime;
|
|
119
145
|
if (stallResult === 'stalled' || stallResult === 'dead') {
|
|
@@ -135,10 +161,39 @@ export class PlanExecutor extends EventEmitter {
|
|
|
135
161
|
}
|
|
136
162
|
this.emit('statusChanged', this.status);
|
|
137
163
|
}
|
|
164
|
+
/** Build an immutable execution context from start options. */
|
|
165
|
+
buildContext(options) {
|
|
166
|
+
const pmDir = resolvePmDir(this.workingDir);
|
|
167
|
+
const boardId = options.board ?? null;
|
|
168
|
+
return {
|
|
169
|
+
workingDir: this.workingDir,
|
|
170
|
+
extraEnv: this.extraEnv,
|
|
171
|
+
epicScope: options.epic ?? null,
|
|
172
|
+
boardId,
|
|
173
|
+
executionDir: options.executionDir ?? null,
|
|
174
|
+
pmDir,
|
|
175
|
+
boardDir: resolveBoardDir(pmDir, boardId),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
// ── Warning / update helpers bound to the executor's event stream ──
|
|
179
|
+
/**
|
|
180
|
+
* Forward module-emitted warnings as executor 'output' events so they flow
|
|
181
|
+
* through to the WebSocket broadcast like inline warnings always have.
|
|
182
|
+
*/
|
|
183
|
+
emitWarn = (message, issueId) => {
|
|
184
|
+
this.emit('output', { issueId: issueId ?? 'system', text: message, boardId: this.context.boardId ?? null });
|
|
185
|
+
};
|
|
186
|
+
async setIssueStatus(issuePath, newStatus) {
|
|
187
|
+
const { pmDir } = this.context;
|
|
188
|
+
if (!pmDir)
|
|
189
|
+
return;
|
|
190
|
+
await updateIssueFrontMatter(pmDir, issuePath, newStatus, this.emitWarn);
|
|
191
|
+
}
|
|
192
|
+
// ── Wave loop ────────────────────────────────────────────────
|
|
138
193
|
/** Run waves until done, paused, stopped, or stalled. */
|
|
139
194
|
async runWaveLoop() {
|
|
140
195
|
let consecutiveZeroCompletions = 0;
|
|
141
|
-
const maxParallel = await this.
|
|
196
|
+
const maxParallel = await getBoardMaxParallelAgents(this.context.pmDir, this.effectiveBoardId(), this.emitWarn);
|
|
142
197
|
while (!this.shouldStop && !this.shouldPause) {
|
|
143
198
|
const readyIssues = await this.pickReadyIssues();
|
|
144
199
|
if (readyIssues.length === 0) {
|
|
@@ -156,34 +211,15 @@ export class PlanExecutor extends EventEmitter {
|
|
|
156
211
|
}
|
|
157
212
|
return 'done';
|
|
158
213
|
}
|
|
214
|
+
effectiveBoardId() {
|
|
215
|
+
return this.context.boardId ?? resolveActiveBoardId(this.context.pmDir);
|
|
216
|
+
}
|
|
159
217
|
async hasDeadIssues() {
|
|
160
|
-
const pmDir = this.
|
|
218
|
+
const { pmDir } = this.context;
|
|
161
219
|
if (!pmDir)
|
|
162
220
|
return false;
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
? await this.loadBoardIssues(pmDir, effectiveBoardId)
|
|
166
|
-
: this.loadProjectIssues();
|
|
167
|
-
if (!issues)
|
|
168
|
-
return false;
|
|
169
|
-
const terminalStatuses = new Set(['done', 'cancelled']);
|
|
170
|
-
return issues.some(i => i.type !== 'epic' && !terminalStatuses.has(i.status) && i.status !== 'todo');
|
|
171
|
-
}
|
|
172
|
-
pause() { this.shouldPause = true; }
|
|
173
|
-
stop() {
|
|
174
|
-
this.shouldStop = true;
|
|
175
|
-
this.status = 'stopping';
|
|
176
|
-
this.emit('statusChanged', this.status);
|
|
177
|
-
// Kill all running HeadlessRunner processes in the current wave
|
|
178
|
-
this.waveAbortController?.abort();
|
|
179
|
-
}
|
|
180
|
-
resume() {
|
|
181
|
-
if (this.status !== 'paused')
|
|
182
|
-
return Promise.resolve();
|
|
183
|
-
this.shouldPause = false;
|
|
184
|
-
// Preserve board/epic scope across resume by marking as a scoped call
|
|
185
|
-
this._scopeSetByCall = true;
|
|
186
|
-
return this.start();
|
|
221
|
+
const { issues } = await this.loadScopedIssues(pmDir);
|
|
222
|
+
return issues ? hasBlockedIssues(issues) : false;
|
|
187
223
|
}
|
|
188
224
|
// ── Wave execution ───────────────────────────────────────────
|
|
189
225
|
async executeWave(issues) {
|
|
@@ -195,13 +231,13 @@ export class PlanExecutor extends EventEmitter {
|
|
|
195
231
|
this.emit('waveStarted', { issueIds: waveIds });
|
|
196
232
|
// Create abort controller for this wave — stop() will abort it
|
|
197
233
|
this.waveAbortController = new AbortController();
|
|
198
|
-
await this.
|
|
234
|
+
await ensureOutputDirs(this.context.pmDir, this.context.boardDir);
|
|
199
235
|
this.configInstaller.installPermissions();
|
|
200
236
|
for (const issue of issues) {
|
|
201
|
-
await this.
|
|
237
|
+
await this.setIssueStatus(issue.path, 'in_progress');
|
|
202
238
|
}
|
|
203
|
-
const existingDocs = listExistingDocs(this.workingDir, this.boardDir);
|
|
204
|
-
const pmDir = this.
|
|
239
|
+
const existingDocs = listExistingDocs(this.workingDir, this.context.boardDir);
|
|
240
|
+
const { pmDir } = this.context;
|
|
205
241
|
let completedCount = 0;
|
|
206
242
|
try {
|
|
207
243
|
// Spawn one HeadlessRunner per issue in parallel
|
|
@@ -225,7 +261,8 @@ export class PlanExecutor extends EventEmitter {
|
|
|
225
261
|
issueIds: waveIds,
|
|
226
262
|
error: error instanceof Error ? error.message : String(error),
|
|
227
263
|
});
|
|
228
|
-
|
|
264
|
+
if (pmDir)
|
|
265
|
+
await revertIncompleteIssues(pmDir, issues, this.emitWarn);
|
|
229
266
|
}
|
|
230
267
|
finally {
|
|
231
268
|
this.configInstaller.uninstallPermissions();
|
|
@@ -237,17 +274,19 @@ export class PlanExecutor extends EventEmitter {
|
|
|
237
274
|
}
|
|
238
275
|
/** Run a single issue via its own headless Claude Code instance with retry logic. */
|
|
239
276
|
async runSingleIssue(issue, pmDir, existingDocs, waveLabel, abortSignal) {
|
|
240
|
-
const
|
|
241
|
-
const
|
|
277
|
+
const { executionDir, boardDir, workingDir } = this.context;
|
|
278
|
+
const effectiveDir = executionDir || workingDir;
|
|
279
|
+
const outputPath = resolveOutputPath(issue, workingDir, boardDir);
|
|
242
280
|
const prompt = buildIssuePrompt({
|
|
243
281
|
issue,
|
|
244
282
|
workingDir: effectiveDir,
|
|
245
283
|
pmDir,
|
|
246
|
-
boardDir
|
|
284
|
+
boardDir,
|
|
247
285
|
existingDocs,
|
|
248
286
|
outputPath,
|
|
249
287
|
});
|
|
250
|
-
const boardLogDir =
|
|
288
|
+
const boardLogDir = boardDir ? join(boardDir, 'logs') : undefined;
|
|
289
|
+
const emitOutput = (text) => this.emit('output', { issueId: issue.id, text });
|
|
251
290
|
const result = await runWithFileLogger(`pm-issue-${issue.id}`, () => runIssueWithRetry({
|
|
252
291
|
workingDir: effectiveDir,
|
|
253
292
|
prompt,
|
|
@@ -255,8 +294,17 @@ export class PlanExecutor extends EventEmitter {
|
|
|
255
294
|
stallKillMs: ISSUE_STALL_KILL_MS,
|
|
256
295
|
stallHardCapMs: ISSUE_STALL_HARD_CAP_MS,
|
|
257
296
|
stallMaxExtensions: ISSUE_STALL_MAX_EXTENSIONS,
|
|
258
|
-
outputCallback:
|
|
259
|
-
|
|
297
|
+
outputCallback: emitOutput,
|
|
298
|
+
// Thinking deltas: marker-prefix so the web UI can classify them as a
|
|
299
|
+
// separate verbose-only block type without needing a new message field.
|
|
300
|
+
thinkingCallback: (text) => emitOutput(`[[MSTRO_THINKING]]${text}`),
|
|
301
|
+
// Tool calls: emit a single line per completed tool with a short arg
|
|
302
|
+
// summary. The 'tool_complete' event fires after input has fully
|
|
303
|
+
// streamed in, so we can render the tool with its real arguments.
|
|
304
|
+
toolUseCallback: (event) => {
|
|
305
|
+
if (event.type !== 'tool_complete' || !event.toolName)
|
|
306
|
+
return;
|
|
307
|
+
emitOutput(`[[MSTRO_TOOL]]${formatToolCallLine(event.toolName, event.completeInput)}`);
|
|
260
308
|
},
|
|
261
309
|
extraEnv: this.extraEnv,
|
|
262
310
|
abortSignal,
|
|
@@ -270,8 +318,9 @@ export class PlanExecutor extends EventEmitter {
|
|
|
270
318
|
* doesn't prevent the others or kill the while loop in start().
|
|
271
319
|
*/
|
|
272
320
|
async finalizeWave(issues, waveStart, waveLabel) {
|
|
321
|
+
const { pmDir, boardDir, boardId } = this.context;
|
|
273
322
|
try {
|
|
274
|
-
reconcileState(this.workingDir,
|
|
323
|
+
reconcileState(this.workingDir, boardId ?? undefined);
|
|
275
324
|
this.emit('stateUpdated');
|
|
276
325
|
}
|
|
277
326
|
catch (err) {
|
|
@@ -281,7 +330,7 @@ export class PlanExecutor extends EventEmitter {
|
|
|
281
330
|
});
|
|
282
331
|
}
|
|
283
332
|
try {
|
|
284
|
-
publishOutputs(issues, this.workingDir,
|
|
333
|
+
publishOutputs(issues, this.workingDir, boardDir, {
|
|
285
334
|
onWarning: (issueId, text) => this.emit('output', { issueId, text: `Warning: ${text}` }),
|
|
286
335
|
});
|
|
287
336
|
}
|
|
@@ -292,7 +341,7 @@ export class PlanExecutor extends EventEmitter {
|
|
|
292
341
|
});
|
|
293
342
|
}
|
|
294
343
|
try {
|
|
295
|
-
await
|
|
344
|
+
await appendProgressEntry(pmDir, boardDir, issues, waveStart, this.emitWarn);
|
|
296
345
|
}
|
|
297
346
|
catch (err) {
|
|
298
347
|
this.emit('output', {
|
|
@@ -308,30 +357,21 @@ export class PlanExecutor extends EventEmitter {
|
|
|
308
357
|
* and either confirmed `done` (passed) or reverted to `todo` (failed).
|
|
309
358
|
*/
|
|
310
359
|
async reconcileWaveResults(issues) {
|
|
311
|
-
const pmDir = this.
|
|
360
|
+
const { pmDir } = this.context;
|
|
312
361
|
if (!pmDir)
|
|
313
362
|
return 0;
|
|
314
363
|
let completed = 0;
|
|
315
364
|
for (const issue of issues) {
|
|
316
|
-
const fullPath =
|
|
365
|
+
const fullPath = validateIssuePath(issue.path, pmDir);
|
|
317
366
|
try {
|
|
318
367
|
const content = await readFile(fullPath, 'utf-8');
|
|
319
|
-
const
|
|
320
|
-
const currentStatus = statusMatch?.[1] ?? 'unknown';
|
|
368
|
+
const currentStatus = extractIssueStatus(content) ?? 'unknown';
|
|
321
369
|
if (currentStatus === 'in_review' || currentStatus === 'done') {
|
|
322
|
-
if (issue
|
|
323
|
-
// Skip review gate — mark done directly
|
|
324
|
-
await this.updateIssueFrontMatter(issue.path, 'done');
|
|
325
|
-
this.metrics.issuesCompleted++;
|
|
326
|
-
this.emit('issueCompleted', issue);
|
|
370
|
+
if (await this.finalizeCompletedIssue(issue, pmDir))
|
|
327
371
|
completed++;
|
|
328
|
-
}
|
|
329
|
-
else {
|
|
330
|
-
completed += await this.runReviewGate(issue, pmDir);
|
|
331
|
-
}
|
|
332
372
|
}
|
|
333
373
|
else {
|
|
334
|
-
await this.
|
|
374
|
+
await this.setIssueStatus(issue.path, issue.status);
|
|
335
375
|
this.emit('issueError', {
|
|
336
376
|
issueId: issue.id,
|
|
337
377
|
error: 'Issue did not complete during wave execution',
|
|
@@ -344,52 +384,38 @@ export class PlanExecutor extends EventEmitter {
|
|
|
344
384
|
}
|
|
345
385
|
return completed;
|
|
346
386
|
}
|
|
347
|
-
/**
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
this.
|
|
355
|
-
this.
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
attempts,
|
|
359
|
-
});
|
|
360
|
-
this.emit('output', { issueId: issue.id, text: `Review: max attempts reached, cancelling issue to unblock dependents` });
|
|
361
|
-
return 0;
|
|
387
|
+
/**
|
|
388
|
+
* Finalize a single issue whose status reached `in_review`/`done`. Runs the
|
|
389
|
+
* review pipeline unless the issue opted out via `reviewGate: 'none'`.
|
|
390
|
+
* Returns true when the issue is confirmed done (counted toward completions).
|
|
391
|
+
*/
|
|
392
|
+
async finalizeCompletedIssue(issue, pmDir) {
|
|
393
|
+
if (issue.reviewGate === 'none') {
|
|
394
|
+
await this.setIssueStatus(issue.path, 'done');
|
|
395
|
+
this.metrics.issuesCompleted++;
|
|
396
|
+
this.emit('issueCompleted', issue);
|
|
397
|
+
return true;
|
|
362
398
|
}
|
|
363
|
-
await
|
|
364
|
-
this.emit('reviewProgress', { issueId: issue.id, status: 'reviewing' });
|
|
365
|
-
const outputPath = resolveOutputPath(issue, this.workingDir, this.boardDir);
|
|
366
|
-
const result = await reviewIssue({
|
|
367
|
-
workingDir: this.executionDir || this.workingDir,
|
|
399
|
+
const passed = await runReviewPipeline({
|
|
368
400
|
issue,
|
|
369
401
|
pmDir,
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
boardDir: this.boardDir,
|
|
402
|
+
workingDir: this.workingDir,
|
|
403
|
+
executionDir: this.context.executionDir,
|
|
404
|
+
boardDir: this.context.boardDir,
|
|
405
|
+
boardId: this.context.boardId,
|
|
375
406
|
extraEnv: this.extraEnv,
|
|
407
|
+
}, {
|
|
408
|
+
setStatus: (path, status) => this.setIssueStatus(path, status),
|
|
409
|
+
onOutput: (issueId, text) => this.emit('output', { issueId, text }),
|
|
410
|
+
onReviewProgress: (issueId, status) => this.emit('reviewProgress', { issueId, status }),
|
|
411
|
+
onIssueAbandoned: (issueId, reason, attempts) => this.emit('issueAbandoned', { issueId, reason, attempts }),
|
|
412
|
+
onIssueCompleted: (completedIssue) => this.emit('issueCompleted', completedIssue),
|
|
413
|
+
onIssueError: (issueId, error) => this.emit('issueError', { issueId, error }),
|
|
414
|
+
warn: this.emitWarn,
|
|
376
415
|
});
|
|
377
|
-
|
|
378
|
-
if (result.passed) {
|
|
379
|
-
await this.updateIssueFrontMatter(issue.path, 'done');
|
|
416
|
+
if (passed)
|
|
380
417
|
this.metrics.issuesCompleted++;
|
|
381
|
-
|
|
382
|
-
this.emit('issueCompleted', issue);
|
|
383
|
-
return 1;
|
|
384
|
-
}
|
|
385
|
-
await this.updateIssueFrontMatter(issue.path, 'todo');
|
|
386
|
-
appendReviewFeedback(pmDir, issue, result);
|
|
387
|
-
this.emit('reviewProgress', { issueId: issue.id, status: 'failed' });
|
|
388
|
-
this.emit('issueError', {
|
|
389
|
-
issueId: issue.id,
|
|
390
|
-
error: `Review failed: ${result.checks.filter(c => !c.passed).map(c => c.name).join(', ')}`,
|
|
391
|
-
});
|
|
392
|
-
return 0;
|
|
418
|
+
return passed;
|
|
393
419
|
}
|
|
394
420
|
// ── Recovery ─────────────────────────────────────────────────
|
|
395
421
|
/**
|
|
@@ -398,26 +424,14 @@ export class PlanExecutor extends EventEmitter {
|
|
|
398
424
|
* these issues block the dependency graph and cause the executor to
|
|
399
425
|
* find zero ready issues, making "Implement" appear to do nothing.
|
|
400
426
|
*/
|
|
401
|
-
async
|
|
402
|
-
const pmDir = this.
|
|
427
|
+
async runStaleRecovery() {
|
|
428
|
+
const { pmDir } = this.context;
|
|
403
429
|
if (!pmDir)
|
|
404
430
|
return;
|
|
405
|
-
const
|
|
406
|
-
const issues = effectiveBoardId
|
|
407
|
-
? await this.loadBoardIssues(pmDir, effectiveBoardId)
|
|
408
|
-
: this.loadProjectIssues();
|
|
431
|
+
const { issues } = await this.loadScopedIssues(pmDir);
|
|
409
432
|
if (!issues)
|
|
410
433
|
return;
|
|
411
|
-
const
|
|
412
|
-
const recovered = [];
|
|
413
|
-
for (const issue of issues) {
|
|
414
|
-
if (issue.type === 'epic')
|
|
415
|
-
continue;
|
|
416
|
-
if (staleStatuses.has(issue.status)) {
|
|
417
|
-
await this.updateIssueFrontMatter(issue.path, 'todo');
|
|
418
|
-
recovered.push(`${issue.id} (${issue.status} → todo)`);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
434
|
+
const recovered = await recoverStaleIssues(pmDir, issues, this.emitWarn);
|
|
421
435
|
if (recovered.length > 0) {
|
|
422
436
|
this.emit('output', {
|
|
423
437
|
issueId: 'recovery',
|
|
@@ -426,317 +440,47 @@ export class PlanExecutor extends EventEmitter {
|
|
|
426
440
|
this.emit('stateUpdated');
|
|
427
441
|
}
|
|
428
442
|
}
|
|
429
|
-
// ──
|
|
430
|
-
/** Read the board's maxParallelAgents setting, falling back to default. */
|
|
431
|
-
async getBoardMaxParallelAgents() {
|
|
432
|
-
const pmDir = this.pmDir;
|
|
433
|
-
if (!pmDir)
|
|
434
|
-
return DEFAULT_MAX_PARALLEL_AGENTS;
|
|
435
|
-
const effectiveBoardId = this.boardId ?? this.resolveActiveBoardId();
|
|
436
|
-
if (!effectiveBoardId)
|
|
437
|
-
return DEFAULT_MAX_PARALLEL_AGENTS;
|
|
438
|
-
const boardMdPath = join(pmDir, 'boards', effectiveBoardId, 'board.md');
|
|
439
|
-
if (!existsSync(boardMdPath))
|
|
440
|
-
return DEFAULT_MAX_PARALLEL_AGENTS;
|
|
441
|
-
try {
|
|
442
|
-
const content = await readFile(boardMdPath, 'utf-8');
|
|
443
|
-
const match = content.match(/^max_parallel_agents:\s*(\d+)/m);
|
|
444
|
-
return match ? Math.max(1, Math.min(Number(match[1]), 10)) : DEFAULT_MAX_PARALLEL_AGENTS;
|
|
445
|
-
}
|
|
446
|
-
catch (err) {
|
|
447
|
-
this.emit('output', { issueId: 'system', text: `Warning: failed to read board max_parallel_agents: ${err instanceof Error ? err.message : String(err)}`, boardId: this.boardId ?? null });
|
|
448
|
-
return DEFAULT_MAX_PARALLEL_AGENTS;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
/** Read the board's custom review criteria, if set. */
|
|
452
|
-
async getBoardReviewCriteria() {
|
|
453
|
-
const pmDir = this.pmDir;
|
|
454
|
-
if (!pmDir)
|
|
455
|
-
return undefined;
|
|
456
|
-
const effectiveBoardId = this.boardId ?? this.resolveActiveBoardId();
|
|
457
|
-
if (!effectiveBoardId)
|
|
458
|
-
return undefined;
|
|
459
|
-
const boardMdPath = join(pmDir, 'boards', effectiveBoardId, 'board.md');
|
|
460
|
-
if (!existsSync(boardMdPath))
|
|
461
|
-
return undefined;
|
|
462
|
-
try {
|
|
463
|
-
const content = await readFile(boardMdPath, 'utf-8');
|
|
464
|
-
const match = content.match(/^review_criteria:\s*"(.+)"/m);
|
|
465
|
-
if (!match)
|
|
466
|
-
return undefined;
|
|
467
|
-
const raw = match[1].replace(/\\"/g, '"').replace(/\\n/g, '\n').trim();
|
|
468
|
-
return raw || undefined;
|
|
469
|
-
}
|
|
470
|
-
catch (err) {
|
|
471
|
-
this.emit('output', { issueId: 'system', text: `Warning: failed to read board review criteria: ${err instanceof Error ? err.message : String(err)}`, boardId: this.boardId ?? null });
|
|
472
|
-
return undefined;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
443
|
+
// ── Issue loading & readiness ────────────────────────────────
|
|
475
444
|
async pickReadyIssues() {
|
|
476
|
-
const pmDir = this.
|
|
445
|
+
const { pmDir, epicScope } = this.context;
|
|
477
446
|
if (!pmDir) {
|
|
478
447
|
this.emit('error', 'No PM directory found');
|
|
479
448
|
return [];
|
|
480
449
|
}
|
|
481
|
-
const
|
|
482
|
-
const issues = effectiveBoardId
|
|
483
|
-
? await this.loadBoardIssues(pmDir, effectiveBoardId)
|
|
484
|
-
: this.loadProjectIssues();
|
|
450
|
+
const { issues, boardId } = await this.loadScopedIssues(pmDir);
|
|
485
451
|
if (!issues)
|
|
486
452
|
return [];
|
|
487
|
-
const readyIssues = resolveReadyToWork(issues,
|
|
453
|
+
const readyIssues = resolveReadyToWork(issues, epicScope ?? undefined);
|
|
488
454
|
if (readyIssues.length === 0) {
|
|
489
|
-
const deadState =
|
|
455
|
+
const deadState = detectDeadState(issues);
|
|
490
456
|
if (deadState) {
|
|
491
457
|
this.emit('error', deadState);
|
|
492
458
|
}
|
|
493
459
|
else {
|
|
494
|
-
this.emit('complete',
|
|
495
|
-
if (
|
|
496
|
-
await
|
|
460
|
+
this.emit('complete', buildCompletionReason(issues, epicScope));
|
|
461
|
+
if (boardId) {
|
|
462
|
+
await tryCompleteBoardIfDone(pmDir, boardId, issues, this.emitWarn);
|
|
497
463
|
}
|
|
498
464
|
}
|
|
499
465
|
}
|
|
500
466
|
return readyIssues;
|
|
501
467
|
}
|
|
502
|
-
/**
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}
|
|
516
|
-
else if (boardState.board.status !== 'active') {
|
|
517
|
-
this.emit('error', `Board ${boardId} is not active (status: ${boardState.board.status})`);
|
|
518
|
-
return null;
|
|
519
|
-
}
|
|
520
|
-
return boardState.issues;
|
|
521
|
-
}
|
|
522
|
-
/** Load project-level issues (legacy or no boards). Returns null on error. */
|
|
523
|
-
loadProjectIssues() {
|
|
524
|
-
const fullState = parsePlanDirectory(this.workingDir);
|
|
525
|
-
if (!fullState) {
|
|
526
|
-
this.emit('error', 'No PM directory found');
|
|
527
|
-
return null;
|
|
528
|
-
}
|
|
529
|
-
if (fullState.state.paused) {
|
|
530
|
-
this.emit('error', 'Project is paused');
|
|
531
|
-
return null;
|
|
532
|
-
}
|
|
533
|
-
return fullState.issues;
|
|
534
|
-
}
|
|
535
|
-
/** Activate a draft board by updating its status in board.md. */
|
|
536
|
-
async activateBoard(pmDir, boardId) {
|
|
537
|
-
const boardMdPath = join(pmDir, 'boards', boardId, 'board.md');
|
|
538
|
-
if (!existsSync(boardMdPath))
|
|
539
|
-
return;
|
|
540
|
-
try {
|
|
541
|
-
const content = await readFile(boardMdPath, 'utf-8');
|
|
542
|
-
await writeFile(boardMdPath, replaceFrontMatterField(content, 'status', 'active'), 'utf-8');
|
|
543
|
-
}
|
|
544
|
-
catch (err) {
|
|
545
|
-
this.emit('output', { issueId: 'system', text: `Warning: failed to activate board ${boardId}: ${err instanceof Error ? err.message : String(err)}`, boardId: this.boardId ?? null });
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
/** Check if all issues in a board are done and mark board as completed. */
|
|
549
|
-
async tryCompleteBoardIfDone(pmDir, boardId, issues) {
|
|
550
|
-
const allDone = issues.length > 0 && issues.every(i => i.status === 'done' || i.status === 'cancelled');
|
|
551
|
-
if (!allDone)
|
|
552
|
-
return;
|
|
553
|
-
const boardMdPath = join(pmDir, 'boards', boardId, 'board.md');
|
|
554
|
-
if (!existsSync(boardMdPath))
|
|
555
|
-
return;
|
|
556
|
-
try {
|
|
557
|
-
let content = await readFile(boardMdPath, 'utf-8');
|
|
558
|
-
content = replaceFrontMatterField(content, 'status', 'completed');
|
|
559
|
-
content = replaceFrontMatterField(content, 'completed_at', `"${new Date().toISOString()}"`);
|
|
560
|
-
await writeFile(boardMdPath, content, 'utf-8');
|
|
561
|
-
}
|
|
562
|
-
catch (err) {
|
|
563
|
-
this.emit('output', { issueId: 'system', text: `Warning: failed to mark board ${boardId} as completed: ${err instanceof Error ? err.message : String(err)}`, boardId: this.boardId ?? null });
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
resolveActiveBoardId() {
|
|
567
|
-
const pmDir = this.pmDir;
|
|
568
|
-
if (!pmDir)
|
|
569
|
-
return null;
|
|
570
|
-
try {
|
|
571
|
-
const workspacePath = join(pmDir, 'workspace.json');
|
|
572
|
-
if (!existsSync(workspacePath))
|
|
573
|
-
return null;
|
|
574
|
-
const workspace = JSON.parse(readFileSync(workspacePath, 'utf-8'));
|
|
575
|
-
return workspace.activeBoardId ?? null;
|
|
576
|
-
}
|
|
577
|
-
catch {
|
|
578
|
-
return null;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
buildCompletionReason(issues) {
|
|
582
|
-
const nonEpic = issues.filter(i => i.type !== 'epic');
|
|
583
|
-
const done = nonEpic.filter(i => i.status === 'done' || i.status === 'cancelled').length;
|
|
584
|
-
const blocked = nonEpic.filter(i => i.status === 'todo').length;
|
|
585
|
-
if (done === nonEpic.length)
|
|
586
|
-
return this.epicScope ? 'All epic issues are done' : 'All issues are done';
|
|
587
|
-
if (blocked > 0)
|
|
588
|
-
return `${done}/${nonEpic.length} issues done, ${blocked} blocked by incomplete dependencies`;
|
|
589
|
-
return this.epicScope ? 'All epic issues are done or blocked' : 'All work is done or blocked';
|
|
590
|
-
}
|
|
591
|
-
/** Detect issues stuck in non-terminal states with no path to completion. */
|
|
592
|
-
detectDeadState(issues) {
|
|
593
|
-
const nonEpic = issues.filter(i => i.type !== 'epic');
|
|
594
|
-
const terminalStatuses = new Set(['done', 'cancelled']);
|
|
595
|
-
const stuck = nonEpic.filter(i => !terminalStatuses.has(i.status) && i.status !== 'todo');
|
|
596
|
-
if (stuck.length === 0)
|
|
597
|
-
return null;
|
|
598
|
-
const stuckIds = stuck.map(i => `${i.id} (${i.status})`).join(', ');
|
|
599
|
-
const issueByPath = new Map(issues.map(i => [i.path, i]));
|
|
600
|
-
const blockedByStuck = nonEpic.filter(i => {
|
|
601
|
-
if (i.status !== 'todo')
|
|
602
|
-
return false;
|
|
603
|
-
return i.blockedBy.some(bp => {
|
|
604
|
-
const blocker = issueByPath.get(bp);
|
|
605
|
-
return blocker && !terminalStatuses.has(blocker.status);
|
|
606
|
-
});
|
|
607
|
-
});
|
|
608
|
-
const blockedIds = blockedByStuck.map(i => i.id).join(', ');
|
|
609
|
-
return `Board stuck: ${stuckIds} cannot progress${blockedIds ? `. Blocking: ${blockedIds}` : ''}`;
|
|
610
|
-
}
|
|
611
|
-
async revertIncompleteIssues(issues) {
|
|
612
|
-
const pmDir = this.pmDir;
|
|
613
|
-
if (!pmDir)
|
|
614
|
-
return;
|
|
615
|
-
for (const issue of issues) {
|
|
616
|
-
const fullPath = this.validateIssuePath(issue.path, pmDir);
|
|
617
|
-
try {
|
|
618
|
-
const content = await readFile(fullPath, 'utf-8');
|
|
619
|
-
if (content.match(/^status:\s*in_progress$/m)) {
|
|
620
|
-
await this.updateIssueFrontMatter(issue.path, issue.status);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
catch (err) {
|
|
624
|
-
this.emit('output', { issueId: issue.id, text: `Warning: failed to revert issue status: ${err instanceof Error ? err.message : String(err)}`, boardId: this.boardId ?? null });
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
async appendCancellationNote(issue, pmDir, reason) {
|
|
629
|
-
const fullPath = this.validateIssuePath(issue.path, pmDir);
|
|
630
|
-
try {
|
|
631
|
-
let content = await readFile(fullPath, 'utf-8');
|
|
632
|
-
const entry = `- Cancelled (${new Date().toISOString().split('T')[0]}): ${reason}`;
|
|
633
|
-
if (content.includes('## Activity')) {
|
|
634
|
-
content = content.replace(/## Activity/, `## Activity\n${entry}`);
|
|
635
|
-
}
|
|
636
|
-
else {
|
|
637
|
-
content += `\n\n## Activity\n${entry}`;
|
|
638
|
-
}
|
|
639
|
-
await writeFile(fullPath, content, 'utf-8');
|
|
640
|
-
}
|
|
641
|
-
catch (err) {
|
|
642
|
-
this.emit('output', { issueId: issue.id, text: `Warning: failed to append cancellation note: ${err instanceof Error ? err.message : String(err)}`, boardId: this.boardId ?? null });
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
async updateIssueFrontMatter(issuePath, newStatus) {
|
|
646
|
-
const pmDir = this.pmDir;
|
|
647
|
-
if (!pmDir)
|
|
648
|
-
return;
|
|
649
|
-
try {
|
|
650
|
-
const fullPath = this.validateIssuePath(issuePath, pmDir);
|
|
651
|
-
await setFrontMatterFieldAsync(fullPath, 'status', newStatus);
|
|
652
|
-
if (newStatus === 'done') {
|
|
653
|
-
const content = await readFile(fullPath, 'utf-8');
|
|
654
|
-
const updated = checkAllAcceptanceCriteria(content);
|
|
655
|
-
if (updated !== content)
|
|
656
|
-
await writeFile(fullPath, updated, 'utf-8');
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
catch (err) {
|
|
660
|
-
this.emit('output', { issueId: 'system', text: `Warning: failed to update issue front matter for ${issuePath}: ${err instanceof Error ? err.message : String(err)}`, boardId: this.boardId ?? null });
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
async ensureOutputDirs() {
|
|
664
|
-
if (this.boardDir) {
|
|
665
|
-
const boardOutDir = join(this.boardDir, 'out');
|
|
666
|
-
if (!existsSync(boardOutDir))
|
|
667
|
-
await mkdir(boardOutDir, { recursive: true });
|
|
668
|
-
}
|
|
669
|
-
else {
|
|
670
|
-
const pmDir = this.pmDir;
|
|
671
|
-
if (pmDir) {
|
|
672
|
-
const outDir = join(pmDir, 'out');
|
|
673
|
-
if (!existsSync(outDir))
|
|
674
|
-
await mkdir(outDir, { recursive: true });
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
async appendProgressEntry(issues, waveStart) {
|
|
679
|
-
const pmDir = this.pmDir;
|
|
680
|
-
if (!pmDir)
|
|
681
|
-
return;
|
|
682
|
-
const progressPath = this.boardDir
|
|
683
|
-
? join(this.boardDir, 'progress.md')
|
|
684
|
-
: join(pmDir, 'progress.md');
|
|
685
|
-
const durationMin = Math.round((Date.now() - waveStart) / 60_000);
|
|
686
|
-
const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 16);
|
|
687
|
-
const completed = [];
|
|
688
|
-
const failed = [];
|
|
689
|
-
for (const issue of issues) {
|
|
690
|
-
try {
|
|
691
|
-
const content = await readFile(this.validateIssuePath(issue.path, pmDir), 'utf-8');
|
|
692
|
-
const statusMatch = content.match(/^status:\s*(\S+)/m);
|
|
693
|
-
if (statusMatch?.[1] === 'done') {
|
|
694
|
-
completed.push(issue.id);
|
|
695
|
-
}
|
|
696
|
-
else {
|
|
697
|
-
failed.push(issue.id);
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
catch {
|
|
701
|
-
failed.push(issue.id);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
const lines = [
|
|
705
|
-
'',
|
|
706
|
-
`## ${timestamp} — Wave [${issues.map(i => i.id).join(', ')}]`,
|
|
707
|
-
'',
|
|
708
|
-
`- **Duration**: ${durationMin} min`,
|
|
709
|
-
`- **Completed**: ${completed.length}/${issues.length}${completed.length > 0 ? ` (${completed.join(', ')})` : ''}`,
|
|
710
|
-
];
|
|
711
|
-
if (failed.length > 0) {
|
|
712
|
-
lines.push(`- **Failed**: ${failed.join(', ')}`);
|
|
713
|
-
}
|
|
714
|
-
lines.push('');
|
|
715
|
-
await this.writeProgressLines(progressPath, lines);
|
|
716
|
-
}
|
|
717
|
-
async writeProgressLines(filePath, lines) {
|
|
718
|
-
try {
|
|
719
|
-
if (existsSync(filePath)) {
|
|
720
|
-
await appendFile(filePath, `\n${lines.join('\n')}`, 'utf-8');
|
|
721
|
-
}
|
|
722
|
-
else {
|
|
723
|
-
await writeFile(filePath, `# Board Progress\n${lines.join('\n')}`, 'utf-8');
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
catch (err) {
|
|
727
|
-
this.emit('output', { issueId: 'system', text: `Warning: failed to write progress log: ${err instanceof Error ? err.message : String(err)}`, boardId: this.boardId ?? null });
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
/** Resolve the active board's directory path for outputs, reviews, and progress. */
|
|
731
|
-
resolveBoardDir() {
|
|
732
|
-
const pmDir = this.pmDir;
|
|
733
|
-
if (!pmDir)
|
|
734
|
-
return null;
|
|
735
|
-
const effectiveBoardId = this.boardId ?? this.resolveActiveBoardId();
|
|
736
|
-
if (!effectiveBoardId)
|
|
737
|
-
return null;
|
|
738
|
-
const boardDir = join(pmDir, 'boards', effectiveBoardId);
|
|
739
|
-
return existsSync(boardDir) ? boardDir : null;
|
|
468
|
+
/**
|
|
469
|
+
* Load issues for the active execution scope. Returns the resolved boardId
|
|
470
|
+
* alongside the issues so callers can branch on board-specific logic without
|
|
471
|
+
* re-resolving the scope.
|
|
472
|
+
*/
|
|
473
|
+
async loadScopedIssues(pmDir) {
|
|
474
|
+
const boardId = this.effectiveBoardId();
|
|
475
|
+
const issues = boardId
|
|
476
|
+
? await loadBoardIssues(pmDir, boardId, {
|
|
477
|
+
onError: msg => this.emit('error', msg),
|
|
478
|
+
warn: this.emitWarn,
|
|
479
|
+
})
|
|
480
|
+
: loadProjectIssues(this.workingDir, { onError: msg => this.emit('error', msg) });
|
|
481
|
+
return { issues, boardId };
|
|
740
482
|
}
|
|
741
483
|
}
|
|
484
|
+
// Re-export for backwards compatibility with modules that imported the constant from here.
|
|
485
|
+
export { DEFAULT_MAX_PARALLEL_AGENTS };
|
|
742
486
|
//# sourceMappingURL=executor.js.map
|