mstro-app 0.4.2 → 0.4.4
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/bin/mstro.js +119 -40
- package/dist/server/cli/headless/claude-invoker-process.d.ts +11 -0
- package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker-process.js +140 -0
- package/dist/server/cli/headless/claude-invoker-process.js.map +1 -0
- package/dist/server/cli/headless/claude-invoker-stall.d.ts +40 -0
- package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker-stall.js +98 -0
- package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -0
- package/dist/server/cli/headless/claude-invoker-stream.d.ts +44 -0
- package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker-stream.js +276 -0
- package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -0
- package/dist/server/cli/headless/claude-invoker-tools.d.ts +21 -0
- package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker-tools.js +137 -0
- package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -0
- package/dist/server/cli/headless/claude-invoker.d.ts +6 -4
- package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker.js +10 -804
- package/dist/server/cli/headless/claude-invoker.js.map +1 -1
- package/dist/server/cli/headless/haiku-assessments.d.ts +62 -0
- package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -0
- package/dist/server/cli/headless/haiku-assessments.js +281 -0
- package/dist/server/cli/headless/haiku-assessments.js.map +1 -0
- package/dist/server/cli/headless/headless-logger.d.ts +3 -2
- package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
- package/dist/server/cli/headless/headless-logger.js +28 -5
- package/dist/server/cli/headless/headless-logger.js.map +1 -1
- package/dist/server/cli/headless/native-timeout-detector.d.ts +44 -0
- package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -0
- package/dist/server/cli/headless/native-timeout-detector.js +99 -0
- package/dist/server/cli/headless/native-timeout-detector.js.map +1 -0
- package/dist/server/cli/headless/stall-assessor.d.ts +2 -110
- package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
- package/dist/server/cli/headless/stall-assessor.js +65 -457
- package/dist/server/cli/headless/stall-assessor.js.map +1 -1
- package/dist/server/cli/headless/types.d.ts +4 -1
- package/dist/server/cli/headless/types.d.ts.map +1 -1
- package/dist/server/cli/improvisation-attachments.d.ts +21 -0
- package/dist/server/cli/improvisation-attachments.d.ts.map +1 -0
- package/dist/server/cli/improvisation-attachments.js +116 -0
- package/dist/server/cli/improvisation-attachments.js.map +1 -0
- package/dist/server/cli/improvisation-retry.d.ts +52 -0
- package/dist/server/cli/improvisation-retry.d.ts.map +1 -0
- package/dist/server/cli/improvisation-retry.js +434 -0
- package/dist/server/cli/improvisation-retry.js.map +1 -0
- package/dist/server/cli/improvisation-session-manager.d.ts +10 -266
- package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
- package/dist/server/cli/improvisation-session-manager.js +117 -1079
- package/dist/server/cli/improvisation-session-manager.js.map +1 -1
- package/dist/server/cli/improvisation-types.d.ts +86 -0
- package/dist/server/cli/improvisation-types.d.ts.map +1 -0
- package/dist/server/cli/improvisation-types.js +10 -0
- package/dist/server/cli/improvisation-types.js.map +1 -0
- package/dist/server/cli/prompt-builders.d.ts +68 -0
- package/dist/server/cli/prompt-builders.d.ts.map +1 -0
- package/dist/server/cli/prompt-builders.js +312 -0
- package/dist/server/cli/prompt-builders.js.map +1 -0
- package/dist/server/index.js +33 -212
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp/bouncer-haiku.d.ts +10 -0
- package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -0
- package/dist/server/mcp/bouncer-haiku.js +152 -0
- package/dist/server/mcp/bouncer-haiku.js.map +1 -0
- package/dist/server/mcp/bouncer-integration.d.ts +3 -4
- package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
- package/dist/server/mcp/bouncer-integration.js +50 -196
- package/dist/server/mcp/bouncer-integration.js.map +1 -1
- package/dist/server/mcp/security-analysis.d.ts +38 -0
- package/dist/server/mcp/security-analysis.d.ts.map +1 -0
- package/dist/server/mcp/security-analysis.js +183 -0
- package/dist/server/mcp/security-analysis.js.map +1 -0
- package/dist/server/mcp/security-audit.d.ts +1 -1
- package/dist/server/mcp/security-audit.d.ts.map +1 -1
- package/dist/server/mcp/security-patterns.d.ts +1 -25
- package/dist/server/mcp/security-patterns.d.ts.map +1 -1
- package/dist/server/mcp/security-patterns.js +55 -260
- package/dist/server/mcp/security-patterns.js.map +1 -1
- package/dist/server/server-setup.d.ts +22 -0
- package/dist/server/server-setup.d.ts.map +1 -0
- package/dist/server/server-setup.js +101 -0
- package/dist/server/server-setup.js.map +1 -0
- package/dist/server/services/file-explorer-ops.d.ts +24 -0
- package/dist/server/services/file-explorer-ops.d.ts.map +1 -0
- package/dist/server/services/file-explorer-ops.js +211 -0
- package/dist/server/services/file-explorer-ops.js.map +1 -0
- package/dist/server/services/files.d.ts +2 -85
- package/dist/server/services/files.d.ts.map +1 -1
- package/dist/server/services/files.js +7 -427
- package/dist/server/services/files.js.map +1 -1
- 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 +118 -32
- package/dist/server/services/plan/composer.js.map +1 -1
- package/dist/server/services/plan/config-installer.d.ts +25 -0
- package/dist/server/services/plan/config-installer.d.ts.map +1 -0
- package/dist/server/services/plan/config-installer.js +182 -0
- package/dist/server/services/plan/config-installer.js.map +1 -0
- package/dist/server/services/plan/dependency-resolver.d.ts +1 -1
- package/dist/server/services/plan/dependency-resolver.d.ts.map +1 -1
- package/dist/server/services/plan/dependency-resolver.js +4 -1
- package/dist/server/services/plan/dependency-resolver.js.map +1 -1
- package/dist/server/services/plan/executor.d.ts +38 -74
- package/dist/server/services/plan/executor.d.ts.map +1 -1
- package/dist/server/services/plan/executor.js +274 -460
- package/dist/server/services/plan/executor.js.map +1 -1
- package/dist/server/services/plan/front-matter.d.ts +18 -0
- package/dist/server/services/plan/front-matter.d.ts.map +1 -0
- package/dist/server/services/plan/front-matter.js +44 -0
- package/dist/server/services/plan/front-matter.js.map +1 -0
- package/dist/server/services/plan/output-manager.d.ts +22 -0
- package/dist/server/services/plan/output-manager.d.ts.map +1 -0
- package/dist/server/services/plan/output-manager.js +97 -0
- package/dist/server/services/plan/output-manager.js.map +1 -0
- package/dist/server/services/plan/parser-core.d.ts +20 -0
- package/dist/server/services/plan/parser-core.d.ts.map +1 -0
- package/dist/server/services/plan/parser-core.js +350 -0
- package/dist/server/services/plan/parser-core.js.map +1 -0
- package/dist/server/services/plan/parser-migration.d.ts +5 -0
- package/dist/server/services/plan/parser-migration.d.ts.map +1 -0
- package/dist/server/services/plan/parser-migration.js +124 -0
- package/dist/server/services/plan/parser-migration.js.map +1 -0
- package/dist/server/services/plan/parser.d.ts +11 -3
- package/dist/server/services/plan/parser.d.ts.map +1 -1
- package/dist/server/services/plan/parser.js +184 -369
- package/dist/server/services/plan/parser.js.map +1 -1
- package/dist/server/services/plan/prompt-builder.d.ts +17 -0
- package/dist/server/services/plan/prompt-builder.d.ts.map +1 -0
- package/dist/server/services/plan/prompt-builder.js +137 -0
- package/dist/server/services/plan/prompt-builder.js.map +1 -0
- package/dist/server/services/plan/review-gate.d.ts +28 -0
- package/dist/server/services/plan/review-gate.d.ts.map +1 -0
- package/dist/server/services/plan/review-gate.js +191 -0
- package/dist/server/services/plan/review-gate.js.map +1 -0
- package/dist/server/services/plan/state-reconciler.d.ts +1 -1
- package/dist/server/services/plan/state-reconciler.d.ts.map +1 -1
- package/dist/server/services/plan/state-reconciler.js +59 -7
- package/dist/server/services/plan/state-reconciler.js.map +1 -1
- package/dist/server/services/plan/types.d.ts +68 -0
- package/dist/server/services/plan/types.d.ts.map +1 -1
- package/dist/server/services/platform-credentials.d.ts +24 -0
- package/dist/server/services/platform-credentials.d.ts.map +1 -0
- package/dist/server/services/platform-credentials.js +68 -0
- package/dist/server/services/platform-credentials.js.map +1 -0
- package/dist/server/services/platform.d.ts +1 -31
- package/dist/server/services/platform.d.ts.map +1 -1
- package/dist/server/services/platform.js +11 -109
- package/dist/server/services/platform.js.map +1 -1
- package/dist/server/services/terminal/pty-manager.d.ts +7 -97
- package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
- package/dist/server/services/terminal/pty-manager.js +53 -266
- package/dist/server/services/terminal/pty-manager.js.map +1 -1
- package/dist/server/services/terminal/pty-utils.d.ts +57 -0
- package/dist/server/services/terminal/pty-utils.d.ts.map +1 -0
- package/dist/server/services/terminal/pty-utils.js +141 -0
- package/dist/server/services/terminal/pty-utils.js.map +1 -0
- package/dist/server/services/websocket/file-definition-handlers.d.ts +4 -0
- package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/file-definition-handlers.js +153 -0
- package/dist/server/services/websocket/file-definition-handlers.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 +52 -391
- package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
- package/dist/server/services/websocket/file-search-handlers.d.ts +5 -0
- package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/file-search-handlers.js +238 -0
- package/dist/server/services/websocket/file-search-handlers.js.map +1 -0
- package/dist/server/services/websocket/file-utils.js +3 -3
- package/dist/server/services/websocket/file-utils.js.map +1 -1
- package/dist/server/services/websocket/git-branch-handlers.d.ts +7 -0
- package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/git-branch-handlers.js +110 -0
- package/dist/server/services/websocket/git-branch-handlers.js.map +1 -0
- package/dist/server/services/websocket/git-diff-handlers.d.ts +6 -0
- package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/git-diff-handlers.js +123 -0
- package/dist/server/services/websocket/git-diff-handlers.js.map +1 -0
- package/dist/server/services/websocket/git-handlers.d.ts +2 -31
- package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-handlers.js +35 -541
- package/dist/server/services/websocket/git-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-log-handlers.d.ts +6 -0
- package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/git-log-handlers.js +128 -0
- package/dist/server/services/websocket/git-log-handlers.js.map +1 -0
- package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-pr-handlers.js +13 -53
- package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-tag-handlers.d.ts +6 -0
- package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/git-tag-handlers.js +76 -0
- package/dist/server/services/websocket/git-tag-handlers.js.map +1 -0
- package/dist/server/services/websocket/git-utils.d.ts +43 -0
- package/dist/server/services/websocket/git-utils.d.ts.map +1 -0
- package/dist/server/services/websocket/git-utils.js +201 -0
- package/dist/server/services/websocket/git-utils.js.map +1 -0
- package/dist/server/services/websocket/handler.d.ts +2 -0
- package/dist/server/services/websocket/handler.d.ts.map +1 -1
- package/dist/server/services/websocket/handler.js +37 -112
- package/dist/server/services/websocket/handler.js.map +1 -1
- package/dist/server/services/websocket/plan-board-handlers.d.ts +11 -0
- package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-board-handlers.js +218 -0
- package/dist/server/services/websocket/plan-board-handlers.js.map +1 -0
- package/dist/server/services/websocket/plan-execution-handlers.d.ts +9 -0
- package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-execution-handlers.js +142 -0
- package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -0
- package/dist/server/services/websocket/plan-handlers.d.ts +7 -2
- package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-handlers.js +21 -462
- package/dist/server/services/websocket/plan-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-helpers.d.ts +19 -0
- package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-helpers.js +199 -0
- package/dist/server/services/websocket/plan-helpers.js.map +1 -0
- package/dist/server/services/websocket/plan-issue-handlers.d.ts +12 -0
- package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-issue-handlers.js +162 -0
- package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -0
- package/dist/server/services/websocket/plan-sprint-handlers.d.ts +7 -0
- package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-sprint-handlers.js +206 -0
- package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -0
- package/dist/server/services/websocket/quality-complexity.d.ts +14 -0
- package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-complexity.js +262 -0
- package/dist/server/services/websocket/quality-complexity.js.map +1 -0
- package/dist/server/services/websocket/quality-fix-agent.d.ts +16 -0
- package/dist/server/services/websocket/quality-fix-agent.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-fix-agent.js +140 -0
- package/dist/server/services/websocket/quality-fix-agent.js.map +1 -0
- package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-handlers.js +34 -346
- package/dist/server/services/websocket/quality-handlers.js.map +1 -1
- package/dist/server/services/websocket/quality-linting.d.ts +9 -0
- package/dist/server/services/websocket/quality-linting.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-linting.js +178 -0
- package/dist/server/services/websocket/quality-linting.js.map +1 -0
- package/dist/server/services/websocket/quality-review-agent.d.ts +19 -0
- package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-review-agent.js +206 -0
- package/dist/server/services/websocket/quality-review-agent.js.map +1 -0
- package/dist/server/services/websocket/quality-service.d.ts +3 -51
- package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-service.js +9 -651
- package/dist/server/services/websocket/quality-service.js.map +1 -1
- package/dist/server/services/websocket/quality-tools.d.ts +23 -0
- package/dist/server/services/websocket/quality-tools.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-tools.js +208 -0
- package/dist/server/services/websocket/quality-tools.js.map +1 -0
- package/dist/server/services/websocket/quality-types.d.ts +59 -0
- package/dist/server/services/websocket/quality-types.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-types.js +101 -0
- package/dist/server/services/websocket/quality-types.js.map +1 -0
- package/dist/server/services/websocket/session-handlers.d.ts +3 -4
- package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/session-handlers.js +3 -378
- package/dist/server/services/websocket/session-handlers.js.map +1 -1
- package/dist/server/services/websocket/session-history.d.ts +4 -0
- package/dist/server/services/websocket/session-history.d.ts.map +1 -0
- package/dist/server/services/websocket/session-history.js +208 -0
- package/dist/server/services/websocket/session-history.js.map +1 -0
- package/dist/server/services/websocket/session-initialization.d.ts +5 -0
- package/dist/server/services/websocket/session-initialization.d.ts.map +1 -0
- package/dist/server/services/websocket/session-initialization.js +163 -0
- package/dist/server/services/websocket/session-initialization.js.map +1 -0
- package/dist/server/services/websocket/types.d.ts +12 -2
- package/dist/server/services/websocket/types.d.ts.map +1 -1
- package/package.json +1 -2
- package/server/cli/headless/claude-invoker-process.ts +204 -0
- package/server/cli/headless/claude-invoker-stall.ts +164 -0
- package/server/cli/headless/claude-invoker-stream.ts +353 -0
- package/server/cli/headless/claude-invoker-tools.ts +187 -0
- package/server/cli/headless/claude-invoker.ts +15 -1092
- package/server/cli/headless/haiku-assessments.ts +365 -0
- package/server/cli/headless/headless-logger.ts +26 -5
- package/server/cli/headless/native-timeout-detector.ts +117 -0
- package/server/cli/headless/stall-assessor.ts +65 -618
- package/server/cli/headless/types.ts +4 -1
- package/server/cli/improvisation-attachments.ts +148 -0
- package/server/cli/improvisation-retry.ts +602 -0
- package/server/cli/improvisation-session-manager.ts +140 -1349
- package/server/cli/improvisation-types.ts +98 -0
- package/server/cli/prompt-builders.ts +370 -0
- package/server/index.ts +35 -246
- package/server/mcp/bouncer-haiku.ts +182 -0
- package/server/mcp/bouncer-integration.ts +87 -248
- package/server/mcp/security-analysis.ts +217 -0
- package/server/mcp/security-audit.ts +1 -1
- package/server/mcp/security-patterns.ts +60 -283
- package/server/server-setup.ts +114 -0
- package/server/services/file-explorer-ops.ts +293 -0
- package/server/services/files.ts +20 -532
- package/server/services/plan/composer.ts +140 -35
- package/server/services/plan/config-installer.ts +187 -0
- package/server/services/plan/dependency-resolver.ts +4 -1
- package/server/services/plan/executor.ts +281 -488
- package/server/services/plan/front-matter.ts +48 -0
- package/server/services/plan/output-manager.ts +113 -0
- package/server/services/plan/parser-core.ts +406 -0
- package/server/services/plan/parser-migration.ts +128 -0
- package/server/services/plan/parser.ts +188 -394
- package/server/services/plan/prompt-builder.ts +161 -0
- package/server/services/plan/review-gate.ts +212 -0
- package/server/services/plan/state-reconciler.ts +68 -7
- package/server/services/plan/types.ts +101 -1
- package/server/services/platform-credentials.ts +83 -0
- package/server/services/platform.ts +16 -131
- package/server/services/terminal/pty-manager.ts +66 -313
- package/server/services/terminal/pty-utils.ts +176 -0
- package/server/services/websocket/file-definition-handlers.ts +165 -0
- package/server/services/websocket/file-explorer-handlers.ts +37 -452
- package/server/services/websocket/file-search-handlers.ts +291 -0
- package/server/services/websocket/file-utils.ts +3 -3
- package/server/services/websocket/git-branch-handlers.ts +130 -0
- package/server/services/websocket/git-diff-handlers.ts +140 -0
- package/server/services/websocket/git-handlers.ts +40 -625
- package/server/services/websocket/git-log-handlers.ts +149 -0
- package/server/services/websocket/git-pr-handlers.ts +17 -62
- package/server/services/websocket/git-tag-handlers.ts +91 -0
- package/server/services/websocket/git-utils.ts +230 -0
- package/server/services/websocket/handler.ts +39 -112
- package/server/services/websocket/plan-board-handlers.ts +277 -0
- package/server/services/websocket/plan-execution-handlers.ts +184 -0
- package/server/services/websocket/plan-handlers.ts +23 -544
- package/server/services/websocket/plan-helpers.ts +215 -0
- package/server/services/websocket/plan-issue-handlers.ts +204 -0
- package/server/services/websocket/plan-sprint-handlers.ts +252 -0
- package/server/services/websocket/quality-complexity.ts +294 -0
- package/server/services/websocket/quality-fix-agent.ts +181 -0
- package/server/services/websocket/quality-handlers.ts +36 -404
- package/server/services/websocket/quality-linting.ts +187 -0
- package/server/services/websocket/quality-review-agent.ts +246 -0
- package/server/services/websocket/quality-service.ts +11 -762
- package/server/services/websocket/quality-tools.ts +209 -0
- package/server/services/websocket/quality-types.ts +169 -0
- package/server/services/websocket/session-handlers.ts +5 -437
- package/server/services/websocket/session-history.ts +222 -0
- package/server/services/websocket/session-initialization.ts +209 -0
- package/server/services/websocket/types.ts +46 -2
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Copyright (c) 2025-present Mstro, Inc. All rights reserved.
|
|
2
|
+
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Prompt Builder — Constructs the Agent Teams coordinator prompt.
|
|
6
|
+
*
|
|
7
|
+
* Builds a structured prompt for the team lead that spawns teammates
|
|
8
|
+
* using Agent Teams, waits for completion, and verifies outputs.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import type { Issue } from './types.js';
|
|
13
|
+
|
|
14
|
+
export interface CoordinatorPromptOptions {
|
|
15
|
+
issues: Issue[];
|
|
16
|
+
workingDir: string;
|
|
17
|
+
pmDir: string | null;
|
|
18
|
+
/** Board directory path when executing a board (e.g. /path/.pm/boards/BOARD-001). */
|
|
19
|
+
boardDir: string | null;
|
|
20
|
+
existingDocs: string[];
|
|
21
|
+
resolveOutputPath: (issue: Issue) => string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Build the team lead prompt for a wave of issues.
|
|
26
|
+
* Uses Agent Teams for true parallel execution — each teammate gets
|
|
27
|
+
* its own context window and sends idle notifications when done.
|
|
28
|
+
*/
|
|
29
|
+
export function buildCoordinatorPrompt(options: CoordinatorPromptOptions): string {
|
|
30
|
+
const { issues, workingDir, pmDir, boardDir, existingDocs, resolveOutputPath } = options;
|
|
31
|
+
const outDir = boardDir ? join(boardDir, 'out') : pmDir ? join(pmDir, 'out') : join(workingDir, '.mstro', 'pm', 'out');
|
|
32
|
+
|
|
33
|
+
const issueBlocks = issues.map(issue => {
|
|
34
|
+
const criteria = issue.acceptanceCriteria
|
|
35
|
+
.map(c => `- [${c.checked ? 'x' : ' '}] ${c.text}`)
|
|
36
|
+
.join('\n');
|
|
37
|
+
|
|
38
|
+
const files = issue.filesToModify.length > 0
|
|
39
|
+
? `\nFiles to modify:\n${issue.filesToModify.map(f => `- ${f}`).join('\n')}`
|
|
40
|
+
: '';
|
|
41
|
+
|
|
42
|
+
const predecessorDocs = resolvePredecessorDocs(issue, existingDocs);
|
|
43
|
+
const predecessorSection = predecessorDocs.length > 0
|
|
44
|
+
? `\nPredecessor outputs to read:\n${predecessorDocs.map(d => `- ${d}`).join('\n')}`
|
|
45
|
+
: '';
|
|
46
|
+
|
|
47
|
+
return `### ${issue.id}: ${issue.title}
|
|
48
|
+
|
|
49
|
+
**Type**: ${issue.type} | **Priority**: ${issue.priority} | **Estimate**: ${issue.estimate ?? 'unestimated'}
|
|
50
|
+
|
|
51
|
+
**Description**:
|
|
52
|
+
${issue.description}
|
|
53
|
+
|
|
54
|
+
**Acceptance Criteria**:
|
|
55
|
+
${criteria || 'No specific criteria defined.'}
|
|
56
|
+
|
|
57
|
+
**Technical Notes**:
|
|
58
|
+
${issue.technicalNotes || 'None'}
|
|
59
|
+
${files}${predecessorSection}
|
|
60
|
+
|
|
61
|
+
**Output file**: ${resolveOutputPath(issue)}`;
|
|
62
|
+
}).join('\n\n---\n\n');
|
|
63
|
+
|
|
64
|
+
const teamName = `pm-wave-${Date.now()}`;
|
|
65
|
+
|
|
66
|
+
const teammateSpawns = issues.map(issue => {
|
|
67
|
+
const predecessorDocs = resolvePredecessorDocs(issue, existingDocs);
|
|
68
|
+
const predInstr = predecessorDocs.length > 0
|
|
69
|
+
? `Read these predecessor output docs before starting: ${predecessorDocs.join(', ')}. `
|
|
70
|
+
: '';
|
|
71
|
+
|
|
72
|
+
const outputFile = resolveOutputPath(issue);
|
|
73
|
+
|
|
74
|
+
const fileOwnership = issue.filesToModify.length > 0
|
|
75
|
+
? `\n> FILE OWNERSHIP: You own these files exclusively: ${issue.filesToModify.join(', ')}. Other teammates own all other files.`
|
|
76
|
+
: '';
|
|
77
|
+
|
|
78
|
+
return `Spawn teammate **${issue.id.toLowerCase()}** using the **Agent** tool with \`team_name: "${teamName}"\` and \`name: "${issue.id.toLowerCase()}"\`:
|
|
79
|
+
> ${predInstr}Work on issue ${issue.id}: ${issue.title}.
|
|
80
|
+
> Read the full spec at ${pmDir ? join(pmDir, issue.path) : issue.path}.
|
|
81
|
+
> Execute all acceptance criteria.
|
|
82
|
+
> Write all output and results to ${outputFile} — this is the handoff artifact for downstream issues.
|
|
83
|
+
> After writing output, update the issue front matter: change \`status: in_progress\` to \`status: done\`.
|
|
84
|
+
> The orchestrator manages STATE.md. Stay within this issue's scope.${fileOwnership}`;
|
|
85
|
+
}).join('\n\n');
|
|
86
|
+
|
|
87
|
+
return `You are the team lead coordinating ${issues.length} issue${issues.length > 1 ? 's' : ''} using Agent Teams.
|
|
88
|
+
|
|
89
|
+
## Project Directory
|
|
90
|
+
Working directory: ${workingDir}
|
|
91
|
+
Plan directory: ${pmDir || '.mstro/pm/'}
|
|
92
|
+
|
|
93
|
+
## Issues to Execute
|
|
94
|
+
|
|
95
|
+
${issueBlocks}
|
|
96
|
+
|
|
97
|
+
## Execution Protocol — Agent Teams
|
|
98
|
+
|
|
99
|
+
All team coordination uses exactly two tools:
|
|
100
|
+
- **Agent** — spawn teammates (include \`team_name\` and \`name\` in each call)
|
|
101
|
+
- **SendMessage** — message teammates after they are spawned
|
|
102
|
+
|
|
103
|
+
### Step 1: Spawn all teammates in one message
|
|
104
|
+
|
|
105
|
+
Send a single message containing ${issues.length} **Agent** tool calls. Include \`team_name: "${teamName}"\` and a unique \`name\` in each call. The team starts automatically when the first teammate is spawned — the \`team_name\` parameter handles all setup.
|
|
106
|
+
|
|
107
|
+
${teammateSpawns}
|
|
108
|
+
|
|
109
|
+
### Step 2: Wait for every teammate to finish
|
|
110
|
+
|
|
111
|
+
After spawning, idle notifications arrive automatically as messages — you will be notified when each teammate finishes. Between notifications, you have nothing to do. Simply state that you are waiting and let the system deliver notifications to you.
|
|
112
|
+
|
|
113
|
+
Your first action after spawning all teammates: output a brief status message listing all teammates and confirming you are waiting for their idle notifications. Then wait.
|
|
114
|
+
|
|
115
|
+
Track completion against this checklist — proceed to Step 3 only after all are checked:
|
|
116
|
+
${issues.map(i => `- [ ] ${i.id.toLowerCase()}`).join('\n')}
|
|
117
|
+
|
|
118
|
+
Exact teammate names for SendMessage (messages to any other name are silently dropped):
|
|
119
|
+
${issues.map(i => `- \`${i.id.toLowerCase()}\``).join('\n')}
|
|
120
|
+
|
|
121
|
+
When you receive an idle notification from a teammate:
|
|
122
|
+
- Check off that teammate in the checklist above
|
|
123
|
+
- Verify their output file exists on disk using the **Read** tool
|
|
124
|
+
|
|
125
|
+
If 15 minutes pass without an idle notification from a specific teammate, send them a progress check via **SendMessage** using the exact name from the list above. After 5 more minutes with no response, check their output file and issue status on disk — if the output exists and status is \`done\`, mark them complete. Otherwise, update the issue status based on whatever partial work exists, then continue.
|
|
126
|
+
|
|
127
|
+
Staying active until all teammates finish is essential — when the lead exits, all teammate processes stop and their in-progress work is lost. When unsure whether a teammate is still working, keep waiting.
|
|
128
|
+
|
|
129
|
+
### Step 3: Verify outputs
|
|
130
|
+
|
|
131
|
+
Once every teammate has completed or been handled:
|
|
132
|
+
1. Verify each output file exists in ${outDir}/ using **Read** or **Glob**
|
|
133
|
+
2. Verify each issue's front matter status is \`done\`
|
|
134
|
+
3. For any missing output or status update, write it yourself
|
|
135
|
+
4. The orchestrator manages STATE.md separately — focus on output files and issue front matter only
|
|
136
|
+
|
|
137
|
+
### Step 4: Clean up and exit
|
|
138
|
+
|
|
139
|
+
After all outputs are verified:
|
|
140
|
+
- Send each remaining active teammate a shutdown message via **SendMessage**
|
|
141
|
+
- Then exit — the orchestrator handles the next wave
|
|
142
|
+
|
|
143
|
+
## Coordination Rules
|
|
144
|
+
|
|
145
|
+
- The team starts implicitly when you spawn the first teammate with \`team_name\`. Cleanup happens automatically when all teammates exit or the lead exits.
|
|
146
|
+
- Wait for idle notifications from all ${issues.length} teammates before exiting — this ensures all work is saved to disk.
|
|
147
|
+
- Each teammate writes its output to disk (the handoff artifact for downstream issues). Research kept only in conversation is lost when the teammate exits.
|
|
148
|
+
- Each teammate updates its issue front matter status to \`done\` when finished.
|
|
149
|
+
- One issue per teammate — each teammate stays within its assigned scope.
|
|
150
|
+
- Use only the exact teammate names listed above for SendMessage.`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Find predecessor output docs that an issue should read based on its blockedBy dependencies. */
|
|
154
|
+
function resolvePredecessorDocs(issue: Issue, existingDocs: string[]): string[] {
|
|
155
|
+
return issue.blockedBy
|
|
156
|
+
.map(bp => {
|
|
157
|
+
const blockerId = bp.replace(/^backlog\//, '').replace(/\.md$/, '');
|
|
158
|
+
return existingDocs.find(d => d.toLowerCase().includes(blockerId.toLowerCase()));
|
|
159
|
+
})
|
|
160
|
+
.filter(Boolean) as string[];
|
|
161
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// Copyright (c) 2025-present Mstro, Inc. All rights reserved.
|
|
2
|
+
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Review Gate — AI-powered quality gate for completed issues.
|
|
6
|
+
*
|
|
7
|
+
* Code tasks: reads modified files, checks acceptance criteria, looks for bugs.
|
|
8
|
+
* Non-code tasks: reads output doc, checks criteria and completeness.
|
|
9
|
+
* Auto-passes on infrastructure failures to avoid blocking execution.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { runWithFileLogger } from '../../cli/headless/headless-logger.js';
|
|
15
|
+
import { HeadlessRunner } from '../../cli/headless/index.js';
|
|
16
|
+
import type { Issue, ReviewCheck, ReviewResult } from './types.js';
|
|
17
|
+
|
|
18
|
+
/** Max review attempts per issue per sprint before giving up */
|
|
19
|
+
export const MAX_REVIEW_ATTEMPTS = 3;
|
|
20
|
+
|
|
21
|
+
/** Review runner stall timeouts (ms) */
|
|
22
|
+
const REVIEW_STALL_WARNING_MS = 300_000; // 5 min
|
|
23
|
+
const REVIEW_STALL_KILL_MS = 600_000; // 10 min
|
|
24
|
+
const REVIEW_STALL_HARD_CAP_MS = 900_000; // 15 min
|
|
25
|
+
|
|
26
|
+
export interface ReviewIssueOptions {
|
|
27
|
+
workingDir: string;
|
|
28
|
+
issue: Issue;
|
|
29
|
+
pmDir: string;
|
|
30
|
+
outputPath: string;
|
|
31
|
+
onOutput?: (text: string) => void;
|
|
32
|
+
/** Board-scoped log directory for execution logs. Falls back to global ~/.mstro/logs/headless/ */
|
|
33
|
+
logDir?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Run an AI review for a completed issue.
|
|
38
|
+
* Returns auto-pass on infrastructure failures to avoid blocking execution.
|
|
39
|
+
*/
|
|
40
|
+
export async function reviewIssue(options: ReviewIssueOptions): Promise<ReviewResult> {
|
|
41
|
+
const { workingDir, issue, pmDir, outputPath, onOutput, logDir } = options;
|
|
42
|
+
const isCodeTask = issue.filesToModify.length > 0;
|
|
43
|
+
const issueType: ReviewResult['issueType'] = isCodeTask ? 'code' : 'non-code';
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const prompt = buildReviewPrompt(issue, pmDir, outputPath, isCodeTask);
|
|
47
|
+
|
|
48
|
+
const runner = new HeadlessRunner({
|
|
49
|
+
workingDir,
|
|
50
|
+
directPrompt: prompt,
|
|
51
|
+
stallWarningMs: REVIEW_STALL_WARNING_MS,
|
|
52
|
+
stallKillMs: REVIEW_STALL_KILL_MS,
|
|
53
|
+
stallHardCapMs: REVIEW_STALL_HARD_CAP_MS,
|
|
54
|
+
verbose: false,
|
|
55
|
+
outputCallback: onOutput ? (text: string) => onOutput(`Review: ${text}`) : undefined,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const result = await runWithFileLogger('pm-review', () => runner.run(), logDir);
|
|
59
|
+
|
|
60
|
+
if (result.completed && result.assistantResponse) {
|
|
61
|
+
return parseReviewOutput(issue.id, issueType, result.assistantResponse);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return autoPassResult(issue.id, issueType, 'Review runner did not complete');
|
|
65
|
+
} catch {
|
|
66
|
+
return autoPassResult(issue.id, issueType, 'Review threw an error');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Count existing review result files for this issue in the board/sprint directory. */
|
|
71
|
+
export function getReviewAttemptCount(boardOrSandboxDir: string | null, issue: Issue): number {
|
|
72
|
+
if (!boardOrSandboxDir) return 0;
|
|
73
|
+
const reviewsDir = join(boardOrSandboxDir, 'reviews');
|
|
74
|
+
if (!existsSync(reviewsDir)) return 0;
|
|
75
|
+
try {
|
|
76
|
+
return readdirSync(reviewsDir).filter(f => f.startsWith(issue.id) && f.endsWith('.json')).length;
|
|
77
|
+
} catch {
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Persist a review result as JSON in the board/sprint reviews directory. */
|
|
83
|
+
export function persistReviewResult(boardOrSandboxDir: string | null, issue: Issue, result: ReviewResult): void {
|
|
84
|
+
if (!boardOrSandboxDir) return;
|
|
85
|
+
const reviewsDir = join(boardOrSandboxDir, 'reviews');
|
|
86
|
+
if (!existsSync(reviewsDir)) mkdirSync(reviewsDir, { recursive: true });
|
|
87
|
+
try {
|
|
88
|
+
writeFileSync(
|
|
89
|
+
join(reviewsDir, `${issue.id}-${Date.now()}.json`),
|
|
90
|
+
JSON.stringify(result, null, 2),
|
|
91
|
+
'utf-8',
|
|
92
|
+
);
|
|
93
|
+
} catch { /* non-fatal */ }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Append failed review checks to an issue's Activity section. */
|
|
97
|
+
export function appendReviewFeedback(pmDir: string, issue: Issue, result: ReviewResult): void {
|
|
98
|
+
const fullPath = join(pmDir, issue.path);
|
|
99
|
+
try {
|
|
100
|
+
let content = readFileSync(fullPath, 'utf-8');
|
|
101
|
+
const failedChecks = result.checks.filter(c => !c.passed);
|
|
102
|
+
const feedback = failedChecks.map(c => ` - ${c.name}: ${c.details}`).join('\n');
|
|
103
|
+
const entry = `- Review failed (${new Date().toISOString().split('T')[0]}): ${failedChecks.length} check(s) failed\n${feedback}`;
|
|
104
|
+
|
|
105
|
+
if (content.includes('## Activity')) {
|
|
106
|
+
content = content.replace(/## Activity/, `## Activity\n${entry}`);
|
|
107
|
+
} else {
|
|
108
|
+
content += `\n\n## Activity\n${entry}`;
|
|
109
|
+
}
|
|
110
|
+
writeFileSync(fullPath, content, 'utf-8');
|
|
111
|
+
} catch { /* non-fatal */ }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Extract the outermost JSON object from AI output using brace balancing. */
|
|
115
|
+
function extractJsonObject(text: string): string | null {
|
|
116
|
+
const start = text.indexOf('{');
|
|
117
|
+
if (start === -1) return null;
|
|
118
|
+
let depth = 0;
|
|
119
|
+
for (let i = start; i < text.length; i++) {
|
|
120
|
+
if (text[i] === '{') depth++;
|
|
121
|
+
else if (text[i] === '}') depth--;
|
|
122
|
+
if (depth === 0) return text.slice(start, i + 1);
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Parse structured JSON review output from AI response. */
|
|
128
|
+
export function parseReviewOutput(issueId: string, issueType: ReviewResult['issueType'], output: string): ReviewResult {
|
|
129
|
+
const jsonStr = extractJsonObject(output);
|
|
130
|
+
if (jsonStr) {
|
|
131
|
+
try {
|
|
132
|
+
const parsed = JSON.parse(jsonStr);
|
|
133
|
+
if (typeof parsed.passed === 'boolean' || typeof parsed.passed === 'number') {
|
|
134
|
+
return {
|
|
135
|
+
issueId,
|
|
136
|
+
issueType,
|
|
137
|
+
passed: !!parsed.passed,
|
|
138
|
+
checks: Array.isArray(parsed.checks) ? parsed.checks.map((c: Record<string, unknown>) => ({
|
|
139
|
+
name: String(c.name ?? 'unknown'),
|
|
140
|
+
passed: !!c.passed,
|
|
141
|
+
details: String(c.details ?? ''),
|
|
142
|
+
} satisfies ReviewCheck)) : [],
|
|
143
|
+
reviewedAt: new Date().toISOString(),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
} catch { /* fall through */ }
|
|
147
|
+
}
|
|
148
|
+
return autoPassResult(issueId, issueType, 'Could not parse review output');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Create a passing review result for infrastructure failures. */
|
|
152
|
+
export function autoPassResult(issueId: string, issueType: ReviewResult['issueType'], reason: string): ReviewResult {
|
|
153
|
+
return {
|
|
154
|
+
issueId,
|
|
155
|
+
issueType,
|
|
156
|
+
passed: true,
|
|
157
|
+
autoPass: true,
|
|
158
|
+
checks: [{ name: 'review_infrastructure', passed: true, details: `${reason}; auto-passing` }],
|
|
159
|
+
reviewedAt: new Date().toISOString(),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ── Private helpers ─────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
function buildReviewPrompt(issue: Issue, pmDir: string, outputPath: string, isCodeTask: boolean): string {
|
|
166
|
+
const criteria = issue.acceptanceCriteria
|
|
167
|
+
.map(c => `- [${c.checked ? 'x' : ' '}] ${c.text}`)
|
|
168
|
+
.join('\n');
|
|
169
|
+
|
|
170
|
+
if (isCodeTask) {
|
|
171
|
+
return `You are a code reviewer. Review the work done for issue ${issue.id}: ${issue.title}.
|
|
172
|
+
|
|
173
|
+
## Files Modified
|
|
174
|
+
${issue.filesToModify.map(f => `- ${f}`).join('\n')}
|
|
175
|
+
|
|
176
|
+
## Acceptance Criteria
|
|
177
|
+
${criteria || 'No specific criteria defined.'}
|
|
178
|
+
|
|
179
|
+
## Instructions
|
|
180
|
+
1. Read each modified file listed above
|
|
181
|
+
2. Check if all acceptance criteria are met by the code changes
|
|
182
|
+
3. Look for obvious bugs, security vulnerabilities, or code quality issues
|
|
183
|
+
4. Check if the output artifact exists at: ${outputPath}
|
|
184
|
+
|
|
185
|
+
Output EXACTLY one JSON object on its own line (no markdown fencing):
|
|
186
|
+
{"passed": true, "checks": [{"name": "criteria_met", "passed": true, "details": "..."}]}
|
|
187
|
+
|
|
188
|
+
Include checks for: criteria_met, code_quality, no_obvious_bugs.`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return `You are a quality reviewer. Review the work done for issue ${issue.id}: ${issue.title}.
|
|
192
|
+
|
|
193
|
+
## Output File
|
|
194
|
+
${outputPath}
|
|
195
|
+
|
|
196
|
+
## Issue Spec
|
|
197
|
+
${join(pmDir, issue.path)}
|
|
198
|
+
|
|
199
|
+
## Acceptance Criteria
|
|
200
|
+
${criteria || 'No specific criteria defined.'}
|
|
201
|
+
|
|
202
|
+
## Instructions
|
|
203
|
+
1. Read the output file at the path above
|
|
204
|
+
2. Read the full issue spec
|
|
205
|
+
3. Check if all acceptance criteria are met
|
|
206
|
+
4. Check for completeness and quality of the output
|
|
207
|
+
|
|
208
|
+
Output EXACTLY one JSON object on its own line (no markdown fencing):
|
|
209
|
+
{"passed": true, "checks": [{"name": "criteria_met", "passed": true, "details": "..."}]}
|
|
210
|
+
|
|
211
|
+
Include checks for: criteria_met, output_quality, completeness.`;
|
|
212
|
+
}
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
12
12
|
import { join } from 'node:path';
|
|
13
13
|
import { resolveReadyToWork } from './dependency-resolver.js';
|
|
14
|
-
import {
|
|
14
|
+
import { replaceFrontMatterField, replaceYamlField } from './front-matter.js';
|
|
15
|
+
import { isBoardCentricFormat, parseBoardDirectory, parsePlanDirectory, resolvePmDir } from './parser.js';
|
|
15
16
|
import type { Issue, Sprint } from './types.js';
|
|
16
17
|
|
|
17
18
|
interface CategorizedIssues {
|
|
@@ -69,6 +70,7 @@ function buildStateMarkdown(
|
|
|
69
70
|
categories: CategorizedIssues,
|
|
70
71
|
warnings: string[],
|
|
71
72
|
issueByPath: Map<string, Issue>,
|
|
73
|
+
header = '# Project State',
|
|
72
74
|
): string {
|
|
73
75
|
const formatSummary = (issue: Issue, index: number): string => {
|
|
74
76
|
return `${index + 1}. [${issue.id}](${issue.path}) — ${issue.title} (${issue.priority})`;
|
|
@@ -83,7 +85,7 @@ function buildStateMarkdown(
|
|
|
83
85
|
};
|
|
84
86
|
|
|
85
87
|
const sections = [
|
|
86
|
-
|
|
88
|
+
header,
|
|
87
89
|
'',
|
|
88
90
|
'## Current Focus',
|
|
89
91
|
'',
|
|
@@ -138,7 +140,14 @@ function reconcileSprintStatuses(pmDir: string, sprints: Sprint[], issueByPath:
|
|
|
138
140
|
const sprintPath = join(pmDir, sprint.path);
|
|
139
141
|
try {
|
|
140
142
|
let content = readFileSync(sprintPath, 'utf-8');
|
|
141
|
-
content = content
|
|
143
|
+
content = replaceFrontMatterField(content, 'status', derived);
|
|
144
|
+
|
|
145
|
+
// Write completed_at when transitioning to completed
|
|
146
|
+
if (derived === 'completed' && !content.match(/^completed_at:/m)) {
|
|
147
|
+
const timestamp = new Date().toISOString().split('T')[0];
|
|
148
|
+
content = replaceFrontMatterField(content, 'completed_at', timestamp);
|
|
149
|
+
}
|
|
150
|
+
|
|
142
151
|
writeFileSync(sprintPath, content, 'utf-8');
|
|
143
152
|
} catch {
|
|
144
153
|
// Sprint file may be missing or unwritable
|
|
@@ -146,9 +155,17 @@ function reconcileSprintStatuses(pmDir: string, sprints: Sprint[], issueByPath:
|
|
|
146
155
|
}
|
|
147
156
|
}
|
|
148
157
|
|
|
149
|
-
export function reconcileState(workingDir: string): void {
|
|
158
|
+
export function reconcileState(workingDir: string, boardId?: string): void {
|
|
150
159
|
const pmDir = resolvePmDir(workingDir);
|
|
151
160
|
if (!pmDir) return;
|
|
161
|
+
|
|
162
|
+
// Board-centric: reconcile a specific board's STATE.md
|
|
163
|
+
if (isBoardCentricFormat(pmDir)) {
|
|
164
|
+
reconcileBoardState(pmDir, workingDir, boardId);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Legacy sprint-based reconciliation
|
|
152
169
|
const statePath = join(pmDir, 'STATE.md');
|
|
153
170
|
if (!existsSync(statePath)) return;
|
|
154
171
|
|
|
@@ -166,9 +183,53 @@ export function reconcileState(workingDir: string): void {
|
|
|
166
183
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
167
184
|
const frontMatter = fmMatch ? fmMatch[1] : `project: "${project.name}"\ncurrent_sprint: null\nactive_milestone: null\npaused: false\nlast_session: null`;
|
|
168
185
|
|
|
169
|
-
const newContent = buildStateMarkdown(frontMatter, categories, warnings, issueByPath);
|
|
170
|
-
writeFileSync(statePath, newContent, 'utf-8');
|
|
171
|
-
|
|
172
186
|
// Reconcile sprint statuses from actual issue statuses
|
|
173
187
|
reconcileSprintStatuses(pmDir, sprints, issueByPath);
|
|
188
|
+
|
|
189
|
+
// Update current_sprint in front matter based on actual sprint statuses
|
|
190
|
+
const recomputedActive = sprints.find(s => {
|
|
191
|
+
const derived = deriveSprintStatus(s, issueByPath);
|
|
192
|
+
return (derived ?? s.status) === 'active';
|
|
193
|
+
});
|
|
194
|
+
const updatedFM = replaceYamlField(frontMatter, 'current_sprint', recomputedActive ? recomputedActive.id : 'null');
|
|
195
|
+
|
|
196
|
+
const newContent = buildStateMarkdown(updatedFM, categories, warnings, issueByPath);
|
|
197
|
+
writeFileSync(statePath, newContent, 'utf-8');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function reconcileBoardState(pmDir: string, _workingDir: string, boardId?: string): void {
|
|
201
|
+
// Determine which board to reconcile
|
|
202
|
+
const effectiveBoardId = boardId ?? resolveActiveBoardId(pmDir);
|
|
203
|
+
if (!effectiveBoardId) return;
|
|
204
|
+
|
|
205
|
+
const boardState = parseBoardDirectory(pmDir, effectiveBoardId);
|
|
206
|
+
if (!boardState) return;
|
|
207
|
+
|
|
208
|
+
const statePath = join(pmDir, 'boards', effectiveBoardId, 'STATE.md');
|
|
209
|
+
if (!existsSync(statePath)) return;
|
|
210
|
+
|
|
211
|
+
const { board, issues } = boardState;
|
|
212
|
+
|
|
213
|
+
const issueByPath = new Map(issues.map(i => [i.path, i]));
|
|
214
|
+
const categories = categorizeIssues(issues, issueByPath);
|
|
215
|
+
const warnings = computeWarnings(issues);
|
|
216
|
+
|
|
217
|
+
// Read existing front matter from the board's STATE.md
|
|
218
|
+
const content = readFileSync(statePath, 'utf-8');
|
|
219
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
220
|
+
const frontMatter = fmMatch ? fmMatch[1] : `board: "${board.id}"\npaused: false\nlast_session: null`;
|
|
221
|
+
|
|
222
|
+
const newContent = buildStateMarkdown(frontMatter, categories, warnings, issueByPath, '# Board State');
|
|
223
|
+
writeFileSync(statePath, newContent, 'utf-8');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function resolveActiveBoardId(pmDir: string): string | null {
|
|
227
|
+
const wsPath = join(pmDir, 'workspace.json');
|
|
228
|
+
if (!existsSync(wsPath)) return null;
|
|
229
|
+
try {
|
|
230
|
+
const parsed = JSON.parse(readFileSync(wsPath, 'utf-8'));
|
|
231
|
+
return typeof parsed.activeBoardId === 'string' ? parsed.activeBoardId : null;
|
|
232
|
+
} catch {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
174
235
|
}
|
|
@@ -92,6 +92,8 @@ export interface Issue {
|
|
|
92
92
|
children: string[];
|
|
93
93
|
// Progress (for epics)
|
|
94
94
|
progress: string | null;
|
|
95
|
+
// Review gate mode (none = skip review, auto = AI review, required = human review)
|
|
96
|
+
reviewGate: 'none' | 'auto' | 'required';
|
|
95
97
|
// Planned output file path (from front matter output_file, relative to working dir)
|
|
96
98
|
outputFile: string | null;
|
|
97
99
|
// Full markdown body
|
|
@@ -106,7 +108,39 @@ export interface AcceptanceCriterion {
|
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
// ============================================================================
|
|
109
|
-
//
|
|
111
|
+
// Board (boards/BOARD-N/board.md)
|
|
112
|
+
// ============================================================================
|
|
113
|
+
|
|
114
|
+
export interface Board {
|
|
115
|
+
id: string;
|
|
116
|
+
title: string;
|
|
117
|
+
status: 'draft' | 'active' | 'completed' | 'archived';
|
|
118
|
+
created: string;
|
|
119
|
+
completedAt: string | null;
|
|
120
|
+
goal: string;
|
|
121
|
+
executionSummary: BoardExecutionSummary | null;
|
|
122
|
+
path: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface BoardExecutionSummary {
|
|
126
|
+
totalIssues: number;
|
|
127
|
+
completedIssues: number;
|
|
128
|
+
failedIssues: number;
|
|
129
|
+
totalDuration: number;
|
|
130
|
+
waves: number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ============================================================================
|
|
134
|
+
// Workspace (workspace.json)
|
|
135
|
+
// ============================================================================
|
|
136
|
+
|
|
137
|
+
export interface Workspace {
|
|
138
|
+
activeBoardId: string | null;
|
|
139
|
+
boardOrder: string[];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ============================================================================
|
|
143
|
+
// Sprint (sprints/*.md) — legacy, kept for migration
|
|
110
144
|
// ============================================================================
|
|
111
145
|
|
|
112
146
|
export interface Sprint {
|
|
@@ -121,6 +155,16 @@ export interface Sprint {
|
|
|
121
155
|
completed: number | null;
|
|
122
156
|
issues: SprintIssueSummary[];
|
|
123
157
|
path: string;
|
|
158
|
+
completedAt: string | null;
|
|
159
|
+
executionSummary: SprintExecutionSummary | null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface SprintExecutionSummary {
|
|
163
|
+
totalIssues: number;
|
|
164
|
+
completedIssues: number;
|
|
165
|
+
failedIssues: number;
|
|
166
|
+
totalDuration: number;
|
|
167
|
+
waves: number;
|
|
124
168
|
}
|
|
125
169
|
|
|
126
170
|
export interface SprintIssueSummary {
|
|
@@ -153,6 +197,57 @@ export interface MilestoneEpicSummary {
|
|
|
153
197
|
progress: string;
|
|
154
198
|
}
|
|
155
199
|
|
|
200
|
+
// ============================================================================
|
|
201
|
+
// Review (sprint quality gate)
|
|
202
|
+
// ============================================================================
|
|
203
|
+
|
|
204
|
+
export interface ReviewResult {
|
|
205
|
+
issueId: string;
|
|
206
|
+
issueType: 'code' | 'non-code';
|
|
207
|
+
passed: boolean;
|
|
208
|
+
/** True when the review passed due to infrastructure failure, not genuine quality check */
|
|
209
|
+
autoPass?: boolean;
|
|
210
|
+
checks: ReviewCheck[];
|
|
211
|
+
reviewedAt: string;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export interface ReviewCheck {
|
|
215
|
+
name: string;
|
|
216
|
+
passed: boolean;
|
|
217
|
+
details: string;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ============================================================================
|
|
221
|
+
// Board Artifacts (board-scoped execution data)
|
|
222
|
+
// ============================================================================
|
|
223
|
+
|
|
224
|
+
export interface BoardArtifacts {
|
|
225
|
+
boardId: string;
|
|
226
|
+
progressLog: string;
|
|
227
|
+
outputFiles: string[];
|
|
228
|
+
reviewResults: ReviewResult[];
|
|
229
|
+
/** Log file names from boards/BOARD-NNN/logs/ (wave execution + review logs) */
|
|
230
|
+
executionLogs: string[];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** @deprecated Use BoardArtifacts — kept for migration compatibility */
|
|
234
|
+
export interface SprintArtifacts {
|
|
235
|
+
sprintId: string;
|
|
236
|
+
progressLog: string;
|
|
237
|
+
outputFiles: string[];
|
|
238
|
+
reviewResults: ReviewResult[];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ============================================================================
|
|
242
|
+
// Board full state (per-board data sent on board load)
|
|
243
|
+
// ============================================================================
|
|
244
|
+
|
|
245
|
+
export interface BoardFullState {
|
|
246
|
+
board: Board;
|
|
247
|
+
state: ProjectState;
|
|
248
|
+
issues: Issue[];
|
|
249
|
+
}
|
|
250
|
+
|
|
156
251
|
// ============================================================================
|
|
157
252
|
// Plan full state (sent on planInit)
|
|
158
253
|
// ============================================================================
|
|
@@ -160,6 +255,11 @@ export interface MilestoneEpicSummary {
|
|
|
160
255
|
export interface PlanFullState {
|
|
161
256
|
project: ProjectConfig;
|
|
162
257
|
state: ProjectState;
|
|
258
|
+
boards: Board[];
|
|
259
|
+
workspace: Workspace;
|
|
260
|
+
// Active board's data (loaded eagerly for the focused tab)
|
|
261
|
+
activeBoard: BoardFullState | null;
|
|
262
|
+
// Legacy fields — populated during migration, normally empty
|
|
163
263
|
issues: Issue[];
|
|
164
264
|
sprints: Sprint[];
|
|
165
265
|
milestones: Milestone[];
|