agentic-orchestrator 0.1.27 → 0.2.0
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/.claude/settings.local.json +46 -1
- package/.cortexrc +28 -0
- package/.github/agents/copilot-instructions.md +29 -0
- package/.github/copilot-instructions.md +93 -0
- package/.vscode/settings.json +13 -0
- package/.vscode/tms.code-snippets +223 -0
- package/AGENTS.md +72 -1
- package/Agentic-Orchestrator.iml +12 -11
- package/CLAUDE.md +72 -1
- package/CONSTITUTION.md +504 -0
- package/FUTURE-ENHANCEMENTS.md +85 -0
- package/NEXT-TASKS.md +25 -0
- package/PROMPTS.md +161 -0
- package/README.md +126 -29
- package/agentic/orchestrator/agents.yaml +4 -3
- package/agentic/orchestrator/defaults/policy.defaults.yaml +39 -3
- package/agentic/orchestrator/gates.yaml +15 -3
- package/agentic/orchestrator/policy.yaml +47 -3
- package/agentic/orchestrator/prompts/builder.system.md +69 -20
- package/agentic/orchestrator/prompts/planner-intake.system.md +149 -0
- package/agentic/orchestrator/prompts/planner.system.md +113 -40
- package/agentic/orchestrator/prompts/qa.system.md +73 -18
- package/agentic/orchestrator/prompts/reconciler.system.md +119 -0
- package/agentic/orchestrator/schemas/agents.schema.json +89 -1
- package/agentic/orchestrator/schemas/execution-control.schema.json +242 -0
- package/agentic/orchestrator/schemas/index.schema.json +234 -0
- package/agentic/orchestrator/schemas/intake.review.schema.json +82 -0
- package/agentic/orchestrator/schemas/organizer-ordering-artifact.schema.json +75 -0
- package/agentic/orchestrator/schemas/plan.schema.json +44 -0
- package/agentic/orchestrator/schemas/policy.schema.json +238 -9
- package/agentic/orchestrator/schemas/policy.user.schema.json +129 -1
- package/agentic/orchestrator/schemas/spec.manifest.bootstrap.schema.json +101 -0
- package/agentic/orchestrator/schemas/spec.manifest.verified.schema.json +80 -0
- package/agentic/orchestrator/schemas/state.schema.json +298 -3
- package/agentic/orchestrator/tools/catalog.json +145 -15
- package/agentic/orchestrator/tools/schemas/input/doctor.run.input.schema.json +18 -0
- package/agentic/orchestrator/tools/schemas/input/evidence.latest.input.schema.json +4 -0
- package/agentic/orchestrator/tools/schemas/input/evidence.verify_chain.input.schema.json +13 -0
- package/agentic/orchestrator/tools/schemas/input/feature.intake_submit.input.schema.json +11 -0
- package/agentic/orchestrator/tools/schemas/input/feature.question_answer.input.schema.json +15 -0
- package/agentic/orchestrator/tools/schemas/input/feature.question_create.input.schema.json +21 -0
- package/agentic/orchestrator/tools/schemas/input/feature.question_list.input.schema.json +13 -0
- package/agentic/orchestrator/tools/schemas/input/feature.ready_to_merge.input.schema.json +5 -0
- package/agentic/orchestrator/tools/schemas/input/feature.send_message.input.schema.json +1 -1
- package/agentic/orchestrator/tools/schemas/input/replay.timeline_get.input.schema.json +32 -0
- package/agentic/orchestrator/tools/schemas/input/repo.conflict_abort.input.schema.json +16 -0
- package/agentic/orchestrator/tools/schemas/input/repo.conflict_files.input.schema.json +16 -0
- package/agentic/orchestrator/tools/schemas/input/repo.reconcile_mainline.input.schema.json +37 -0
- package/agentic/orchestrator/tools/schemas/input/repo.resolve_conflict.input.schema.json +40 -0
- package/agentic/orchestrator/tools/schemas/input/runtime.execution_request_list.input.schema.json +7 -0
- package/agentic/orchestrator/tools/schemas/input/runtime.execution_request_submit.input.schema.json +25 -0
- package/agentic/orchestrator/tools/schemas/output/doctor.run.output.schema.json +34 -0
- package/agentic/orchestrator/tools/schemas/output/evidence.verify_chain.output.schema.json +23 -0
- package/agentic/orchestrator/tools/schemas/output/feature.get_context.output.schema.json +62 -2
- package/agentic/orchestrator/tools/schemas/output/feature.intake_submit.output.schema.json +24 -0
- package/agentic/orchestrator/tools/schemas/output/feature.question_answer.output.schema.json +21 -0
- package/agentic/orchestrator/tools/schemas/output/feature.question_create.output.schema.json +12 -0
- package/agentic/orchestrator/tools/schemas/output/feature.question_list.output.schema.json +14 -0
- package/agentic/orchestrator/tools/schemas/output/feature.ready_to_merge.output.schema.json +31 -0
- package/agentic/orchestrator/tools/schemas/output/feature.send_message.output.schema.json +8 -18
- package/agentic/orchestrator/tools/schemas/output/replay.timeline_get.output.schema.json +64 -0
- package/agentic/orchestrator/tools/schemas/output/repo.conflict_abort.output.schema.json +16 -0
- package/agentic/orchestrator/tools/schemas/output/repo.conflict_files.output.schema.json +22 -0
- package/agentic/orchestrator/tools/schemas/output/repo.reconcile_mainline.output.schema.json +61 -0
- package/agentic/orchestrator/tools/schemas/output/repo.resolve_conflict.output.schema.json +19 -0
- package/agentic/orchestrator/tools/schemas/output/report.dashboard.output.schema.json +26 -0
- package/agentic/orchestrator/tools/schemas/output/runtime.execution_request_list.output.schema.json +17 -0
- package/agentic/orchestrator/tools/schemas/output/runtime.execution_request_submit.output.schema.json +24 -0
- package/agentic/orchestrator/tools.md +13 -0
- package/apps/control-plane/scripts/validate-mcp-contracts.ts +1 -1
- package/apps/control-plane/src/application/kernel-tool-wiring.ts +140 -2
- package/apps/control-plane/src/application/services/activity-monitor-service.ts +44 -1
- package/apps/control-plane/src/application/services/bootstrap-manifest-generator-service.ts +251 -0
- package/apps/control-plane/src/application/services/checkpoint-service.ts +87 -27
- package/apps/control-plane/src/application/services/collision-override-service.ts +906 -0
- package/apps/control-plane/src/application/services/collision-queue-service.ts +129 -38
- package/apps/control-plane/src/application/services/cost-tracking-service.ts +94 -0
- package/apps/control-plane/src/application/services/execution-control-service.ts +599 -0
- package/apps/control-plane/src/application/services/feature-deletion-service.ts +37 -1
- package/apps/control-plane/src/application/services/feature-lifecycle-service.ts +182 -4
- package/apps/control-plane/src/application/services/feature-send-message-service.ts +17 -8
- package/apps/control-plane/src/application/services/feature-state-service.ts +191 -6
- package/apps/control-plane/src/application/services/gate-service.ts +121 -2
- package/apps/control-plane/src/application/services/git-reconciliation-service.ts +1591 -0
- package/apps/control-plane/src/application/services/intake-service.ts +1468 -0
- package/apps/control-plane/src/application/services/merge-service.ts +308 -17
- package/apps/control-plane/src/application/services/notifier-service.ts +3 -1
- package/apps/control-plane/src/application/services/performance-analytics-service.ts +75 -0
- package/apps/control-plane/src/application/services/plan-service.ts +336 -20
- package/apps/control-plane/src/application/services/question-service.ts +693 -0
- package/apps/control-plane/src/application/services/reactions-service.ts +73 -17
- package/apps/control-plane/src/application/services/replay-timeline-service.ts +295 -0
- package/apps/control-plane/src/application/services/reporting-service.ts +194 -10
- package/apps/control-plane/src/application/services/run-lease-service.ts +121 -5
- package/apps/control-plane/src/application/services/worktree-watchdog-service.ts +95 -8
- package/apps/control-plane/src/application/tools/tool-metadata.ts +7 -0
- package/apps/control-plane/src/application/usage-types.ts +138 -0
- package/apps/control-plane/src/cli/add-command-handler.ts +162 -0
- package/apps/control-plane/src/cli/answer-command-handler.ts +113 -0
- package/apps/control-plane/src/cli/attach-command-handler.ts +12 -3
- package/apps/control-plane/src/cli/cli-argument-parser.ts +133 -11
- package/apps/control-plane/src/cli/collision-command-handler.ts +113 -0
- package/apps/control-plane/src/cli/command-catalog.ts +479 -0
- package/apps/control-plane/src/cli/complete-command-handler.ts +23 -0
- package/apps/control-plane/src/cli/completion-command-handler.ts +25 -0
- package/apps/control-plane/src/cli/completion-resolver.ts +319 -0
- package/apps/control-plane/src/cli/completion-shell-renderer.ts +58 -0
- package/apps/control-plane/src/cli/dashboard-command-handler.ts +110 -1
- package/apps/control-plane/src/cli/dashboard-runtime-runner.ts +1036 -0
- package/apps/control-plane/src/cli/dashboard-runtime.ts +31 -0
- package/apps/control-plane/src/cli/help-command-handler.ts +17 -185
- package/apps/control-plane/src/cli/init-command-handler.ts +51 -6
- package/apps/control-plane/src/cli/merge-command-handler.ts +200 -0
- package/apps/control-plane/src/cli/questions-command-handler.ts +70 -0
- package/apps/control-plane/src/cli/replay-command-handler.ts +98 -0
- package/apps/control-plane/src/cli/resume-command-handler.ts +252 -18
- package/apps/control-plane/src/cli/retry-command-handler.ts +229 -17
- package/apps/control-plane/src/cli/retry-resume-decision.ts +75 -0
- package/apps/control-plane/src/cli/rollback-command-handler.ts +4 -2
- package/apps/control-plane/src/cli/run-command-handler.ts +35 -1
- package/apps/control-plane/src/cli/spec-ingestion-service.ts +45 -55
- package/apps/control-plane/src/cli/spec-preparation.ts +114 -0
- package/apps/control-plane/src/cli/spec-utils.ts +90 -11
- package/apps/control-plane/src/cli/status-command-handler.ts +122 -0
- package/apps/control-plane/src/cli/types.ts +41 -3
- package/apps/control-plane/src/core/collisions.ts +150 -31
- package/apps/control-plane/src/core/constants.ts +18 -1
- package/apps/control-plane/src/core/error-codes.ts +39 -0
- package/apps/control-plane/src/core/execution-control.ts +56 -0
- package/apps/control-plane/src/core/feature-resume-phase.ts +118 -0
- package/apps/control-plane/src/core/gate-freshness.ts +359 -0
- package/apps/control-plane/src/core/gate-log-extractor.ts +97 -0
- package/apps/control-plane/src/core/gates.ts +90 -1
- package/apps/control-plane/src/core/intake-artifacts.ts +295 -0
- package/apps/control-plane/src/core/kernel-types.ts +11 -0
- package/apps/control-plane/src/core/kernel.ts +604 -16
- package/apps/control-plane/src/core/mainline-conflict.ts +22 -0
- package/apps/control-plane/src/core/merge-repair.ts +149 -0
- package/apps/control-plane/src/core/path-layout.ts +46 -2
- package/apps/control-plane/src/core/path-rules.ts +11 -3
- package/apps/control-plane/src/core/plan-submit-recovery.ts +130 -0
- package/apps/control-plane/src/core/questions.ts +49 -0
- package/apps/control-plane/src/core/runtime-sessions.ts +4 -0
- package/apps/control-plane/src/core/schemas.ts +40 -1
- package/apps/control-plane/src/core/tool-caller.ts +25 -1
- package/apps/control-plane/src/core/utils/index-normalizer.ts +25 -4
- package/apps/control-plane/src/core/worktree-diff.ts +66 -0
- package/apps/control-plane/src/index.ts +29 -1
- package/apps/control-plane/src/interfaces/cli/bootstrap.ts +300 -6
- package/apps/control-plane/src/mcp/kernel-tool-executor.ts +17 -0
- package/apps/control-plane/src/mcp/tool-runtime.ts +63 -4
- package/apps/control-plane/src/providers/api-worker-provider.ts +62 -15
- package/apps/control-plane/src/providers/cli-worker-provider.ts +1037 -61
- package/apps/control-plane/src/providers/output-parsers/generic-output-parser.ts +99 -1
- package/apps/control-plane/src/providers/output-parsers/types.ts +2 -0
- package/apps/control-plane/src/providers/provider-defaults.ts +116 -7
- package/apps/control-plane/src/providers/providers.ts +225 -21
- package/apps/control-plane/src/providers/worker-provider-factory.ts +26 -2
- package/apps/control-plane/src/supervisor/artifact-stager.ts +52 -0
- package/apps/control-plane/src/supervisor/build-wave-executor.ts +477 -166
- package/apps/control-plane/src/supervisor/execution-enrollment-service.ts +408 -0
- package/apps/control-plane/src/supervisor/organizer-enrollment-scheduler.ts +117 -0
- package/apps/control-plane/src/supervisor/organizer-sidecar-service.ts +394 -0
- package/apps/control-plane/src/supervisor/plan-conformance-scorer.ts +2 -5
- package/apps/control-plane/src/supervisor/planner-phase.ts +85 -0
- package/apps/control-plane/src/supervisor/planning-wave-executor.ts +993 -64
- package/apps/control-plane/src/supervisor/prompt-bundle-loader.ts +20 -1
- package/apps/control-plane/src/supervisor/qa-wave-executor.ts +384 -177
- package/apps/control-plane/src/supervisor/run-coordinator.ts +801 -43
- package/apps/control-plane/src/supervisor/runtime.ts +485 -9
- package/apps/control-plane/src/supervisor/session-orchestrator.ts +220 -1
- package/apps/control-plane/src/supervisor/types.ts +152 -1
- package/apps/control-plane/src/supervisor/worker-decision-loop.ts +1030 -92
- package/apps/control-plane/test/activity-monitor.spec.ts +76 -0
- package/apps/control-plane/test/add-command-handler.spec.ts +189 -0
- package/apps/control-plane/test/application/services/feature-state-service.spec.ts +208 -0
- package/apps/control-plane/test/artifact-stager.spec.ts +93 -0
- package/apps/control-plane/test/batch-operations.spec.ts +58 -0
- package/apps/control-plane/test/bootstrap-edge-cases.spec.ts +50 -2
- package/apps/control-plane/test/bootstrap-manifest-generator-service.spec.ts +99 -0
- package/apps/control-plane/test/bootstrap.spec.ts +177 -4
- package/apps/control-plane/test/checkpoint-service.spec.ts +977 -29
- package/apps/control-plane/test/cli-argument-parser.spec.ts +119 -0
- package/apps/control-plane/test/cli-helpers.spec.ts +1202 -12
- package/apps/control-plane/test/cli.unit.spec.ts +797 -16
- package/apps/control-plane/test/collision-command-handler.spec.ts +182 -0
- package/apps/control-plane/test/collision-override-service.spec.ts +878 -0
- package/apps/control-plane/test/collision-queue.spec.ts +430 -2
- package/apps/control-plane/test/collisions.spec.ts +209 -1
- package/apps/control-plane/test/core-utils.spec.ts +61 -0
- package/apps/control-plane/test/cost-tracking.spec.ts +224 -0
- package/apps/control-plane/test/dashboard-api.integration.spec.ts +185 -5
- package/apps/control-plane/test/dashboard-client.spec.ts +948 -0
- package/apps/control-plane/test/dashboard-command.spec.ts +138 -6
- package/apps/control-plane/test/dashboard-runtime-runner.spec.ts +1550 -0
- package/apps/control-plane/test/dashboard-runtime.spec.ts +138 -0
- package/apps/control-plane/test/dashboard-ui-utils.spec.ts +56 -12
- package/apps/control-plane/test/dependency-scheduler.spec.ts +7 -1
- package/apps/control-plane/test/env-file.spec.ts +76 -0
- package/apps/control-plane/test/execution-control-service.spec.ts +535 -0
- package/apps/control-plane/test/execution-enrollment-service.spec.ts +648 -0
- package/apps/control-plane/test/feature-lifecycle.spec.ts +126 -0
- package/apps/control-plane/test/feature-resume-phase.spec.ts +164 -0
- package/apps/control-plane/test/feature-send-message-service.spec.ts +161 -0
- package/apps/control-plane/test/feature-state-service.spec.ts +295 -0
- package/apps/control-plane/test/fs.spec.ts +80 -0
- package/apps/control-plane/test/gate-freshness.spec.ts +590 -0
- package/apps/control-plane/test/gate-log-extractor.spec.ts +170 -0
- package/apps/control-plane/test/gates.spec.ts +108 -0
- package/apps/control-plane/test/git-reconciliation-service.spec.ts +2307 -0
- package/apps/control-plane/test/helpers.ts +65 -0
- package/apps/control-plane/test/incremental-gates.spec.ts +271 -0
- package/apps/control-plane/test/index-normalizer.spec.ts +98 -0
- package/apps/control-plane/test/init-wizard.spec.ts +17 -0
- package/apps/control-plane/test/intake-artifacts.spec.ts +203 -0
- package/apps/control-plane/test/intake-service.spec.ts +3176 -0
- package/apps/control-plane/test/kernel-collision-replay.spec.ts +3 -2
- package/apps/control-plane/test/kernel-tool-executor.spec.ts +77 -0
- package/apps/control-plane/test/kernel-tool-wiring.spec.ts +279 -0
- package/apps/control-plane/test/kernel.branches.spec.ts +15 -2
- package/apps/control-plane/test/kernel.coverage.spec.ts +7 -3
- package/apps/control-plane/test/kernel.coverage2.spec.ts +731 -2
- package/apps/control-plane/test/kernel.spec.ts +464 -2
- package/apps/control-plane/test/mainline-conflict.spec.ts +66 -0
- package/apps/control-plane/test/mcp-helpers.spec.ts +79 -0
- package/apps/control-plane/test/mcp.spec.ts +177 -13
- package/apps/control-plane/test/merge-command-handler.spec.ts +531 -0
- package/apps/control-plane/test/merge-service.spec.ts +570 -4
- package/apps/control-plane/test/notifier-service.spec.ts +26 -0
- package/apps/control-plane/test/organizer-enrollment-scheduler.spec.ts +340 -0
- package/apps/control-plane/test/organizer-ordering-artifact.spec.ts +95 -0
- package/apps/control-plane/test/organizer-sidecar-service.spec.ts +468 -0
- package/apps/control-plane/test/output-loop-detector.spec.ts +6 -0
- package/apps/control-plane/test/path-layout.spec.ts +70 -0
- package/apps/control-plane/test/path-normalizers.spec.ts +41 -0
- package/apps/control-plane/test/performance-analytics.spec.ts +124 -0
- package/apps/control-plane/test/plan-conformance-scorer.spec.ts +53 -0
- package/apps/control-plane/test/plan-service.spec.ts +686 -4
- package/apps/control-plane/test/planning-wave-executor.spec.ts +3272 -86
- package/apps/control-plane/test/policy-loader-service.spec.ts +5 -0
- package/apps/control-plane/test/prompt-overlay.spec.ts +65 -0
- package/apps/control-plane/test/provider-command-runner-epipe.spec.ts +64 -0
- package/apps/control-plane/test/providers/api-worker-provider.spec.ts +129 -0
- package/apps/control-plane/test/providers/cli-worker-provider.spec.ts +148 -0
- package/apps/control-plane/test/providers/usage-types.spec.ts +98 -0
- package/apps/control-plane/test/providers.spec.ts +293 -16
- package/apps/control-plane/test/question-command-handlers.spec.ts +156 -0
- package/apps/control-plane/test/question-service.spec.ts +1119 -0
- package/apps/control-plane/test/reactions.spec.ts +114 -0
- package/apps/control-plane/test/replay-command-handler.spec.ts +144 -0
- package/apps/control-plane/test/replay-timeline-service.spec.ts +459 -0
- package/apps/control-plane/test/response.spec.ts +31 -0
- package/apps/control-plane/test/resume-command.spec.ts +786 -9
- package/apps/control-plane/test/retry-resume-decision.spec.ts +133 -0
- package/apps/control-plane/test/rollback-command-handler.spec.ts +334 -0
- package/apps/control-plane/test/rollback-command.spec.ts +120 -0
- package/apps/control-plane/test/run-coordinator.spec.ts +3141 -364
- package/apps/control-plane/test/schemas/state.schema.spec.ts +71 -0
- package/apps/control-plane/test/service-retry-paths.spec.ts +112 -0
- package/apps/control-plane/test/services.spec.ts +472 -2
- package/apps/control-plane/test/session-management.spec.ts +346 -1
- package/apps/control-plane/test/spec-ingestion.spec.ts +102 -28
- package/apps/control-plane/test/spec-preparation.spec.ts +182 -0
- package/apps/control-plane/test/supervisor-collaborators.spec.ts +191 -3
- package/apps/control-plane/test/supervisor.calltool.spec.ts +198 -0
- package/apps/control-plane/test/supervisor.spec.ts +95 -16
- package/apps/control-plane/test/supervisor.unit.spec.ts +385 -18
- package/apps/control-plane/test/tool-runtime.spec.ts +122 -0
- package/apps/control-plane/test/worker-decision-loop.spec.ts +3479 -476
- package/apps/control-plane/test/worker-execution-policy.spec.ts +1416 -6
- package/apps/control-plane/test/worker-provider-adapters.spec.ts +1894 -37
- package/apps/control-plane/test/worker-provider-factory.spec.ts +81 -0
- package/apps/control-plane/test/worktree-watchdog-service.spec.ts +125 -0
- package/apps/control-plane/vitest.config.ts +5 -0
- package/config/agentic/orchestrator/agents.yaml +23 -2
- package/config/agentic/orchestrator/gates.yaml +24 -7
- package/config/agentic/orchestrator/policy.yaml +23 -1
- package/config/agentic/orchestrator/prompts/builder.system.md +69 -20
- package/config/agentic/orchestrator/prompts/organizer.system.md +85 -0
- package/config/agentic/orchestrator/prompts/overrides/builder.claude.md +28 -0
- package/config/agentic/orchestrator/prompts/overrides/builder.codex.md +28 -0
- package/config/agentic/orchestrator/prompts/overrides/planner.claude.md +20 -0
- package/config/agentic/orchestrator/prompts/overrides/planner.codex.md +20 -0
- package/config/agentic/orchestrator/prompts/planner-intake.system.md +149 -0
- package/config/agentic/orchestrator/prompts/planner.system.md +113 -40
- package/config/agentic/orchestrator/prompts/qa.system.md +75 -18
- package/config/agentic/orchestrator/prompts/reconciler.system.md +119 -0
- package/dist/apps/control-plane/application/kernel-tool-wiring.d.ts +26 -2
- package/dist/apps/control-plane/application/kernel-tool-wiring.js +40 -2
- package/dist/apps/control-plane/application/kernel-tool-wiring.js.map +1 -1
- package/dist/apps/control-plane/application/services/activity-monitor-service.js +37 -1
- package/dist/apps/control-plane/application/services/activity-monitor-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/bootstrap-manifest-generator-service.d.ts +4 -0
- package/dist/apps/control-plane/application/services/bootstrap-manifest-generator-service.js +188 -0
- package/dist/apps/control-plane/application/services/bootstrap-manifest-generator-service.js.map +1 -0
- package/dist/apps/control-plane/application/services/checkpoint-service.d.ts +5 -0
- package/dist/apps/control-plane/application/services/checkpoint-service.js +69 -24
- package/dist/apps/control-plane/application/services/checkpoint-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/collision-override-service.d.ts +139 -0
- package/dist/apps/control-plane/application/services/collision-override-service.js +568 -0
- package/dist/apps/control-plane/application/services/collision-override-service.js.map +1 -0
- package/dist/apps/control-plane/application/services/collision-queue-service.d.ts +15 -0
- package/dist/apps/control-plane/application/services/collision-queue-service.js +92 -33
- package/dist/apps/control-plane/application/services/collision-queue-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/cost-tracking-service.d.ts +11 -0
- package/dist/apps/control-plane/application/services/cost-tracking-service.js +75 -0
- package/dist/apps/control-plane/application/services/cost-tracking-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/execution-control-service.d.ts +75 -0
- package/dist/apps/control-plane/application/services/execution-control-service.js +421 -0
- package/dist/apps/control-plane/application/services/execution-control-service.js.map +1 -0
- package/dist/apps/control-plane/application/services/feature-deletion-service.d.ts +1 -0
- package/dist/apps/control-plane/application/services/feature-deletion-service.js +23 -1
- package/dist/apps/control-plane/application/services/feature-deletion-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/feature-lifecycle-service.d.ts +24 -1
- package/dist/apps/control-plane/application/services/feature-lifecycle-service.js +132 -3
- package/dist/apps/control-plane/application/services/feature-lifecycle-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/feature-send-message-service.js +16 -8
- package/dist/apps/control-plane/application/services/feature-send-message-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/feature-state-service.d.ts +36 -0
- package/dist/apps/control-plane/application/services/feature-state-service.js +163 -6
- package/dist/apps/control-plane/application/services/feature-state-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/gate-service.d.ts +2 -1
- package/dist/apps/control-plane/application/services/gate-service.js +95 -5
- package/dist/apps/control-plane/application/services/gate-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/git-reconciliation-service.d.ts +92 -0
- package/dist/apps/control-plane/application/services/git-reconciliation-service.js +1097 -0
- package/dist/apps/control-plane/application/services/git-reconciliation-service.js.map +1 -0
- package/dist/apps/control-plane/application/services/intake-service.d.ts +63 -0
- package/dist/apps/control-plane/application/services/intake-service.js +1050 -0
- package/dist/apps/control-plane/application/services/intake-service.js.map +1 -0
- package/dist/apps/control-plane/application/services/merge-service.d.ts +5 -1
- package/dist/apps/control-plane/application/services/merge-service.js +233 -18
- package/dist/apps/control-plane/application/services/merge-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/notifier-service.d.ts +1 -1
- package/dist/apps/control-plane/application/services/notifier-service.js +1 -0
- package/dist/apps/control-plane/application/services/notifier-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/performance-analytics-service.d.ts +11 -0
- package/dist/apps/control-plane/application/services/performance-analytics-service.js +59 -0
- package/dist/apps/control-plane/application/services/performance-analytics-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/plan-service.d.ts +5 -0
- package/dist/apps/control-plane/application/services/plan-service.js +254 -15
- package/dist/apps/control-plane/application/services/plan-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/question-service.d.ts +72 -0
- package/dist/apps/control-plane/application/services/question-service.js +507 -0
- package/dist/apps/control-plane/application/services/question-service.js.map +1 -0
- package/dist/apps/control-plane/application/services/reactions-service.d.ts +2 -0
- package/dist/apps/control-plane/application/services/reactions-service.js +60 -17
- package/dist/apps/control-plane/application/services/reactions-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/replay-timeline-service.d.ts +39 -0
- package/dist/apps/control-plane/application/services/replay-timeline-service.js +205 -0
- package/dist/apps/control-plane/application/services/replay-timeline-service.js.map +1 -0
- package/dist/apps/control-plane/application/services/reporting-service.d.ts +59 -0
- package/dist/apps/control-plane/application/services/reporting-service.js +121 -9
- package/dist/apps/control-plane/application/services/reporting-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/run-lease-service.d.ts +20 -0
- package/dist/apps/control-plane/application/services/run-lease-service.js +81 -4
- package/dist/apps/control-plane/application/services/run-lease-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/worktree-watchdog-service.d.ts +10 -0
- package/dist/apps/control-plane/application/services/worktree-watchdog-service.js +65 -8
- package/dist/apps/control-plane/application/services/worktree-watchdog-service.js.map +1 -1
- package/dist/apps/control-plane/application/tools/tool-metadata.js +7 -0
- package/dist/apps/control-plane/application/tools/tool-metadata.js.map +1 -1
- package/dist/apps/control-plane/application/usage-types.d.ts +65 -0
- package/dist/apps/control-plane/application/usage-types.js +75 -0
- package/dist/apps/control-plane/application/usage-types.js.map +1 -0
- package/dist/apps/control-plane/cli/add-command-handler.d.ts +18 -0
- package/dist/apps/control-plane/cli/add-command-handler.js +110 -0
- package/dist/apps/control-plane/cli/add-command-handler.js.map +1 -0
- package/dist/apps/control-plane/cli/answer-command-handler.d.ts +8 -0
- package/dist/apps/control-plane/cli/answer-command-handler.js +96 -0
- package/dist/apps/control-plane/cli/answer-command-handler.js.map +1 -0
- package/dist/apps/control-plane/cli/attach-command-handler.js +8 -3
- package/dist/apps/control-plane/cli/attach-command-handler.js.map +1 -1
- package/dist/apps/control-plane/cli/cli-argument-parser.js +131 -11
- package/dist/apps/control-plane/cli/cli-argument-parser.js.map +1 -1
- package/dist/apps/control-plane/cli/collision-command-handler.d.ts +8 -0
- package/dist/apps/control-plane/cli/collision-command-handler.js +90 -0
- package/dist/apps/control-plane/cli/collision-command-handler.js.map +1 -0
- package/dist/apps/control-plane/cli/command-catalog.d.ts +21 -0
- package/dist/apps/control-plane/cli/command-catalog.js +416 -0
- package/dist/apps/control-plane/cli/command-catalog.js.map +1 -0
- package/dist/apps/control-plane/cli/complete-command-handler.d.ts +15 -0
- package/dist/apps/control-plane/cli/complete-command-handler.js +26 -0
- package/dist/apps/control-plane/cli/complete-command-handler.js.map +1 -0
- package/dist/apps/control-plane/cli/completion-command-handler.d.ts +8 -0
- package/dist/apps/control-plane/cli/completion-command-handler.js +20 -0
- package/dist/apps/control-plane/cli/completion-command-handler.js.map +1 -0
- package/dist/apps/control-plane/cli/completion-resolver.d.ts +1 -0
- package/dist/apps/control-plane/cli/completion-resolver.js +250 -0
- package/dist/apps/control-plane/cli/completion-resolver.js.map +1 -0
- package/dist/apps/control-plane/cli/completion-shell-renderer.d.ts +3 -0
- package/dist/apps/control-plane/cli/completion-shell-renderer.js +53 -0
- package/dist/apps/control-plane/cli/completion-shell-renderer.js.map +1 -0
- package/dist/apps/control-plane/cli/dashboard-command-handler.d.ts +1 -0
- package/dist/apps/control-plane/cli/dashboard-command-handler.js +83 -1
- package/dist/apps/control-plane/cli/dashboard-command-handler.js.map +1 -1
- package/dist/apps/control-plane/cli/dashboard-runtime-runner.d.ts +81 -0
- package/dist/apps/control-plane/cli/dashboard-runtime-runner.js +724 -0
- package/dist/apps/control-plane/cli/dashboard-runtime-runner.js.map +1 -0
- package/dist/apps/control-plane/cli/dashboard-runtime.d.ts +1 -0
- package/dist/apps/control-plane/cli/dashboard-runtime.js +26 -0
- package/dist/apps/control-plane/cli/dashboard-runtime.js.map +1 -0
- package/dist/apps/control-plane/cli/help-command-handler.js +13 -172
- package/dist/apps/control-plane/cli/help-command-handler.js.map +1 -1
- package/dist/apps/control-plane/cli/init-command-handler.js +51 -6
- package/dist/apps/control-plane/cli/init-command-handler.js.map +1 -1
- package/dist/apps/control-plane/cli/merge-command-handler.d.ts +8 -0
- package/dist/apps/control-plane/cli/merge-command-handler.js +139 -0
- package/dist/apps/control-plane/cli/merge-command-handler.js.map +1 -0
- package/dist/apps/control-plane/cli/questions-command-handler.d.ts +8 -0
- package/dist/apps/control-plane/cli/questions-command-handler.js +59 -0
- package/dist/apps/control-plane/cli/questions-command-handler.js.map +1 -0
- package/dist/apps/control-plane/cli/replay-command-handler.d.ts +15 -0
- package/dist/apps/control-plane/cli/replay-command-handler.js +55 -0
- package/dist/apps/control-plane/cli/replay-command-handler.js.map +1 -0
- package/dist/apps/control-plane/cli/resume-command-handler.d.ts +2 -0
- package/dist/apps/control-plane/cli/resume-command-handler.js +196 -19
- package/dist/apps/control-plane/cli/resume-command-handler.js.map +1 -1
- package/dist/apps/control-plane/cli/retry-command-handler.js +202 -16
- package/dist/apps/control-plane/cli/retry-command-handler.js.map +1 -1
- package/dist/apps/control-plane/cli/retry-resume-decision.d.ts +26 -0
- package/dist/apps/control-plane/cli/retry-resume-decision.js +61 -0
- package/dist/apps/control-plane/cli/retry-resume-decision.js.map +1 -0
- package/dist/apps/control-plane/cli/rollback-command-handler.js +3 -2
- package/dist/apps/control-plane/cli/rollback-command-handler.js.map +1 -1
- package/dist/apps/control-plane/cli/run-command-handler.js +26 -2
- package/dist/apps/control-plane/cli/run-command-handler.js.map +1 -1
- package/dist/apps/control-plane/cli/spec-ingestion-service.d.ts +2 -0
- package/dist/apps/control-plane/cli/spec-ingestion-service.js +37 -48
- package/dist/apps/control-plane/cli/spec-ingestion-service.js.map +1 -1
- package/dist/apps/control-plane/cli/spec-preparation.d.ts +14 -0
- package/dist/apps/control-plane/cli/spec-preparation.js +81 -0
- package/dist/apps/control-plane/cli/spec-preparation.js.map +1 -0
- package/dist/apps/control-plane/cli/spec-utils.d.ts +4 -0
- package/dist/apps/control-plane/cli/spec-utils.js +70 -11
- package/dist/apps/control-plane/cli/spec-utils.js.map +1 -1
- package/dist/apps/control-plane/cli/status-command-handler.js +69 -0
- package/dist/apps/control-plane/cli/status-command-handler.js.map +1 -1
- package/dist/apps/control-plane/cli/types.d.ts +41 -4
- package/dist/apps/control-plane/cli/types.js +9 -1
- package/dist/apps/control-plane/cli/types.js.map +1 -1
- package/dist/apps/control-plane/core/collisions.d.ts +37 -19
- package/dist/apps/control-plane/core/collisions.js +87 -12
- package/dist/apps/control-plane/core/collisions.js.map +1 -1
- package/dist/apps/control-plane/core/constants.d.ts +17 -1
- package/dist/apps/control-plane/core/constants.js +18 -1
- package/dist/apps/control-plane/core/constants.js.map +1 -1
- package/dist/apps/control-plane/core/error-codes.d.ts +39 -0
- package/dist/apps/control-plane/core/error-codes.js +39 -0
- package/dist/apps/control-plane/core/error-codes.js.map +1 -1
- package/dist/apps/control-plane/core/execution-control.d.ts +45 -0
- package/dist/apps/control-plane/core/execution-control.js +2 -0
- package/dist/apps/control-plane/core/execution-control.js.map +1 -0
- package/dist/apps/control-plane/core/feature-resume-phase.d.ts +3 -0
- package/dist/apps/control-plane/core/feature-resume-phase.js +88 -0
- package/dist/apps/control-plane/core/feature-resume-phase.js.map +1 -0
- package/dist/apps/control-plane/core/gate-freshness.d.ts +48 -0
- package/dist/apps/control-plane/core/gate-freshness.js +267 -0
- package/dist/apps/control-plane/core/gate-freshness.js.map +1 -0
- package/dist/apps/control-plane/core/gate-log-extractor.d.ts +22 -0
- package/dist/apps/control-plane/core/gate-log-extractor.js +66 -0
- package/dist/apps/control-plane/core/gate-log-extractor.js.map +1 -0
- package/dist/apps/control-plane/core/gates.d.ts +11 -2
- package/dist/apps/control-plane/core/gates.js +67 -3
- package/dist/apps/control-plane/core/gates.js.map +1 -1
- package/dist/apps/control-plane/core/intake-artifacts.d.ts +109 -0
- package/dist/apps/control-plane/core/intake-artifacts.js +143 -0
- package/dist/apps/control-plane/core/intake-artifacts.js.map +1 -0
- package/dist/apps/control-plane/core/kernel-types.d.ts +8 -0
- package/dist/apps/control-plane/core/kernel.d.ts +256 -8
- package/dist/apps/control-plane/core/kernel.js +400 -14
- package/dist/apps/control-plane/core/kernel.js.map +1 -1
- package/dist/apps/control-plane/core/mainline-conflict.d.ts +7 -0
- package/dist/apps/control-plane/core/mainline-conflict.js +20 -0
- package/dist/apps/control-plane/core/mainline-conflict.js.map +1 -0
- package/dist/apps/control-plane/core/merge-repair.d.ts +35 -0
- package/dist/apps/control-plane/core/merge-repair.js +99 -0
- package/dist/apps/control-plane/core/merge-repair.js.map +1 -0
- package/dist/apps/control-plane/core/path-layout.d.ts +10 -0
- package/dist/apps/control-plane/core/path-layout.js +32 -2
- package/dist/apps/control-plane/core/path-layout.js.map +1 -1
- package/dist/apps/control-plane/core/path-rules.js +9 -3
- package/dist/apps/control-plane/core/path-rules.js.map +1 -1
- package/dist/apps/control-plane/core/plan-submit-recovery.d.ts +22 -0
- package/dist/apps/control-plane/core/plan-submit-recovery.js +78 -0
- package/dist/apps/control-plane/core/plan-submit-recovery.js.map +1 -0
- package/dist/apps/control-plane/core/questions.d.ts +40 -0
- package/dist/apps/control-plane/core/questions.js +2 -0
- package/dist/apps/control-plane/core/questions.js.map +1 -0
- package/dist/apps/control-plane/core/runtime-sessions.d.ts +4 -0
- package/dist/apps/control-plane/core/schemas.d.ts +2 -0
- package/dist/apps/control-plane/core/schemas.js +31 -1
- package/dist/apps/control-plane/core/schemas.js.map +1 -1
- package/dist/apps/control-plane/core/tool-caller.d.ts +18 -1
- package/dist/apps/control-plane/core/utils/index-normalizer.js +17 -4
- package/dist/apps/control-plane/core/utils/index-normalizer.js.map +1 -1
- package/dist/apps/control-plane/core/worktree-diff.d.ts +4 -0
- package/dist/apps/control-plane/core/worktree-diff.js +52 -0
- package/dist/apps/control-plane/core/worktree-diff.js.map +1 -0
- package/dist/apps/control-plane/index.d.ts +10 -2
- package/dist/apps/control-plane/index.js +9 -2
- package/dist/apps/control-plane/index.js.map +1 -1
- package/dist/apps/control-plane/interfaces/cli/bootstrap.js +236 -6
- package/dist/apps/control-plane/interfaces/cli/bootstrap.js.map +1 -1
- package/dist/apps/control-plane/mcp/kernel-tool-executor.js +16 -0
- package/dist/apps/control-plane/mcp/kernel-tool-executor.js.map +1 -1
- package/dist/apps/control-plane/mcp/tool-runtime.d.ts +5 -0
- package/dist/apps/control-plane/mcp/tool-runtime.js +40 -5
- package/dist/apps/control-plane/mcp/tool-runtime.js.map +1 -1
- package/dist/apps/control-plane/providers/api-worker-provider.d.ts +2 -2
- package/dist/apps/control-plane/providers/api-worker-provider.js +40 -9
- package/dist/apps/control-plane/providers/api-worker-provider.js.map +1 -1
- package/dist/apps/control-plane/providers/cli-worker-provider.d.ts +59 -3
- package/dist/apps/control-plane/providers/cli-worker-provider.js +758 -46
- package/dist/apps/control-plane/providers/cli-worker-provider.js.map +1 -1
- package/dist/apps/control-plane/providers/output-parsers/generic-output-parser.js +91 -1
- package/dist/apps/control-plane/providers/output-parsers/generic-output-parser.js.map +1 -1
- package/dist/apps/control-plane/providers/output-parsers/types.d.ts +2 -0
- package/dist/apps/control-plane/providers/provider-defaults.d.ts +12 -0
- package/dist/apps/control-plane/providers/provider-defaults.js +103 -7
- package/dist/apps/control-plane/providers/provider-defaults.js.map +1 -1
- package/dist/apps/control-plane/providers/providers.d.ts +50 -4
- package/dist/apps/control-plane/providers/providers.js +145 -14
- package/dist/apps/control-plane/providers/providers.js.map +1 -1
- package/dist/apps/control-plane/providers/worker-provider-factory.d.ts +2 -0
- package/dist/apps/control-plane/providers/worker-provider-factory.js +8 -1
- package/dist/apps/control-plane/providers/worker-provider-factory.js.map +1 -1
- package/dist/apps/control-plane/supervisor/artifact-stager.d.ts +5 -0
- package/dist/apps/control-plane/supervisor/artifact-stager.js +45 -0
- package/dist/apps/control-plane/supervisor/artifact-stager.js.map +1 -0
- package/dist/apps/control-plane/supervisor/build-wave-executor.d.ts +24 -1
- package/dist/apps/control-plane/supervisor/build-wave-executor.js +362 -150
- package/dist/apps/control-plane/supervisor/build-wave-executor.js.map +1 -1
- package/dist/apps/control-plane/supervisor/execution-enrollment-service.d.ts +41 -0
- package/dist/apps/control-plane/supervisor/execution-enrollment-service.js +311 -0
- package/dist/apps/control-plane/supervisor/execution-enrollment-service.js.map +1 -0
- package/dist/apps/control-plane/supervisor/organizer-enrollment-scheduler.d.ts +15 -0
- package/dist/apps/control-plane/supervisor/organizer-enrollment-scheduler.js +93 -0
- package/dist/apps/control-plane/supervisor/organizer-enrollment-scheduler.js.map +1 -0
- package/dist/apps/control-plane/supervisor/organizer-sidecar-service.d.ts +44 -0
- package/dist/apps/control-plane/supervisor/organizer-sidecar-service.js +311 -0
- package/dist/apps/control-plane/supervisor/organizer-sidecar-service.js.map +1 -0
- package/dist/apps/control-plane/supervisor/plan-conformance-scorer.js +2 -5
- package/dist/apps/control-plane/supervisor/plan-conformance-scorer.js.map +1 -1
- package/dist/apps/control-plane/supervisor/planner-phase.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/planner-phase.js +70 -0
- package/dist/apps/control-plane/supervisor/planner-phase.js.map +1 -0
- package/dist/apps/control-plane/supervisor/planning-wave-executor.d.ts +42 -0
- package/dist/apps/control-plane/supervisor/planning-wave-executor.js +753 -55
- package/dist/apps/control-plane/supervisor/planning-wave-executor.js.map +1 -1
- package/dist/apps/control-plane/supervisor/prompt-bundle-loader.js +19 -1
- package/dist/apps/control-plane/supervisor/prompt-bundle-loader.js.map +1 -1
- package/dist/apps/control-plane/supervisor/qa-wave-executor.d.ts +21 -0
- package/dist/apps/control-plane/supervisor/qa-wave-executor.js +287 -156
- package/dist/apps/control-plane/supervisor/qa-wave-executor.js.map +1 -1
- package/dist/apps/control-plane/supervisor/run-coordinator.d.ts +33 -1
- package/dist/apps/control-plane/supervisor/run-coordinator.js +631 -39
- package/dist/apps/control-plane/supervisor/run-coordinator.js.map +1 -1
- package/dist/apps/control-plane/supervisor/runtime.d.ts +84 -0
- package/dist/apps/control-plane/supervisor/runtime.js +393 -3
- package/dist/apps/control-plane/supervisor/runtime.js.map +1 -1
- package/dist/apps/control-plane/supervisor/session-orchestrator.d.ts +54 -0
- package/dist/apps/control-plane/supervisor/session-orchestrator.js +176 -1
- package/dist/apps/control-plane/supervisor/session-orchestrator.js.map +1 -1
- package/dist/apps/control-plane/supervisor/types.d.ts +142 -1
- package/dist/apps/control-plane/supervisor/types.js.map +1 -1
- package/dist/apps/control-plane/supervisor/worker-decision-loop.d.ts +68 -2
- package/dist/apps/control-plane/supervisor/worker-decision-loop.js +723 -89
- package/dist/apps/control-plane/supervisor/worker-decision-loop.js.map +1 -1
- package/docs/core/ARCHITECTURE.md +227 -0
- package/docs/core/DECISIONS.md +94 -0
- package/docs/core/DOMAIN-LOGIC.md +60 -0
- package/docs/core/PATTERNS.md +201 -0
- package/docs/core/TROUBLESHOOTING.md +347 -0
- package/docs/core/intentgraph-dependencies.json +39860 -0
- package/docs/core/intentgraph.index.json +46580 -0
- package/docs/plans/2026-03-10-gate-failure-targeted-repair-design.md +224 -0
- package/docs/plans/2026-03-10-gate-failure-targeted-repair.md +1032 -0
- package/docs/superpowers/plans/2026-03-16-provider-cli-config.md +743 -0
- package/docs/superpowers/plans/2026-03-23-reconcile-divergence-fix.md +777 -0
- package/docs/superpowers/plans/2026-03-28-ordering-agent-implementation.md +1754 -0
- package/docs/superpowers/plans/2026-03-29-drop-zone-and-provider-optimization.md +1108 -0
- package/docs/superpowers/plans/2026-03-29-merge-target-feature-branch.md +685 -0
- package/docs/superpowers/plans/2026-03-29-organizer-sidecar-runtime-loop.md +1289 -0
- package/docs/superpowers/specs/2026-03-23-reconcile-divergence-fix-design.md +118 -0
- package/docs/superpowers/specs/2026-03-28-ordering-agent-spec-audit-design.md +50 -0
- package/docs/superpowers/specs/2026-03-29-drop-zone-and-provider-optimization-design.md +254 -0
- package/docs/superpowers/specs/2026-03-29-merge-target-feature-branch-design.md +152 -0
- package/docs/superpowers/specs/2026-03-29-organizer-sidecar-runtime-loop-design.md +225 -0
- package/package.json +3 -2
- package/packages/web-dashboard/package.json +2 -1
- package/packages/web-dashboard/src/app/analytics/page.tsx +36 -2
- package/packages/web-dashboard/src/app/api/actions/route.ts +274 -63
- package/packages/web-dashboard/src/app/api/actions/status/route.ts +35 -0
- package/packages/web-dashboard/src/app/api/analytics/provider/route.ts +18 -0
- package/packages/web-dashboard/src/app/api/collisions/approve/route.ts +58 -0
- package/packages/web-dashboard/src/app/api/features/[id]/checkpoint-diff/route.ts +36 -0
- package/packages/web-dashboard/src/app/api/features/[id]/checkpoints/route.ts +29 -0
- package/packages/web-dashboard/src/app/api/features/[id]/conflicts/abort/route.ts +29 -0
- package/packages/web-dashboard/src/app/api/features/[id]/conflicts/files/route.ts +30 -0
- package/packages/web-dashboard/src/app/api/features/[id]/conflicts/resolve/route.ts +51 -0
- package/packages/web-dashboard/src/app/api/features/[id]/conflicts/route.ts +75 -0
- package/packages/web-dashboard/src/app/api/features/[id]/diff/route.ts +16 -2
- package/packages/web-dashboard/src/app/api/features/[id]/files/route.ts +26 -0
- package/packages/web-dashboard/src/app/api/features/[id]/gate-history/route.ts +27 -0
- package/packages/web-dashboard/src/app/api/features/[id]/genealogy/route.ts +26 -0
- package/packages/web-dashboard/src/app/api/features/[id]/history/run/[runId]/route.ts +20 -0
- package/packages/web-dashboard/src/app/api/features/[id]/history/runs/route.ts +34 -0
- package/packages/web-dashboard/src/app/api/features/[id]/intake-workspace/route.ts +20 -0
- package/packages/web-dashboard/src/app/api/features/[id]/live-output/route.ts +74 -0
- package/packages/web-dashboard/src/app/api/features/[id]/plan/amend/route.ts +21 -0
- package/packages/web-dashboard/src/app/api/features/[id]/plan-progress/route.ts +20 -0
- package/packages/web-dashboard/src/app/api/features/[id]/planner-artifacts/[artifact]/route.ts +78 -0
- package/packages/web-dashboard/src/app/api/features/[id]/planner-lifecycle/route.ts +20 -0
- package/packages/web-dashboard/src/app/api/features/[id]/planning-workspace/route.ts +20 -0
- package/packages/web-dashboard/src/app/api/features/[id]/questions/[questionId]/answer/route.ts +27 -0
- package/packages/web-dashboard/src/app/api/features/[id]/questions/route.ts +18 -0
- package/packages/web-dashboard/src/app/api/features/[id]/review/route.ts +14 -7
- package/packages/web-dashboard/src/app/api/features/[id]/route.ts +57 -2
- package/packages/web-dashboard/src/app/api/features/[id]/spec/route.ts +30 -0
- package/packages/web-dashboard/src/app/api/features/[id]/triage/route.ts +83 -0
- package/packages/web-dashboard/src/app/api/features/[id]/worker-events/route.ts +40 -0
- package/packages/web-dashboard/src/app/api/launch/preview/route.ts +86 -0
- package/packages/web-dashboard/src/app/api/launch/submit/route.ts +180 -0
- package/packages/web-dashboard/src/app/api/mainline/status/route.ts +74 -0
- package/packages/web-dashboard/src/app/api/merge-queue/route.ts +13 -0
- package/packages/web-dashboard/src/app/api/policy/budget/route.ts +14 -0
- package/packages/web-dashboard/src/app/api/projects/route.ts +11 -7
- package/packages/web-dashboard/src/app/api/reconciler/queue/route.ts +47 -0
- package/packages/web-dashboard/src/app/api/run/route.ts +26 -2
- package/packages/web-dashboard/src/app/api/runtime/events/route.ts +227 -0
- package/packages/web-dashboard/src/app/api/runtime/operations/route.ts +269 -0
- package/packages/web-dashboard/src/app/api/runtime/questions/route.ts +11 -0
- package/packages/web-dashboard/src/app/api/runtime/runs/route.ts +80 -0
- package/packages/web-dashboard/src/app/api/status/route.ts +4 -2
- package/packages/web-dashboard/src/app/feature/[id]/page.tsx +32 -42
- package/packages/web-dashboard/src/app/globals.css +34 -3
- package/packages/web-dashboard/src/app/launch/page.tsx +357 -0
- package/packages/web-dashboard/src/app/layout.tsx +23 -1
- package/packages/web-dashboard/src/app/page.tsx +263 -272
- package/packages/web-dashboard/src/components/dashboard/attention-strip.tsx +52 -0
- package/packages/web-dashboard/src/components/dashboard/collision-approval-drawer.tsx +185 -0
- package/packages/web-dashboard/src/components/dashboard/command-center-header.tsx +102 -0
- package/packages/web-dashboard/src/components/dashboard/mainline-status-banner.tsx +84 -0
- package/packages/web-dashboard/src/components/dashboard/merged-archive.tsx +36 -0
- package/packages/web-dashboard/src/components/dashboard/prioritized-queues.tsx +98 -0
- package/packages/web-dashboard/src/components/dashboard/reconciler-queue-card.tsx +115 -0
- package/packages/web-dashboard/src/components/dashboard/secondary-diagnostics-rail.tsx +48 -0
- package/packages/web-dashboard/src/components/dashboard/task-filter-bar.tsx +74 -0
- package/packages/web-dashboard/src/components/dashboard/triage-drawer.tsx +455 -0
- package/packages/web-dashboard/src/components/diff-viewer.tsx +19 -3
- package/packages/web-dashboard/src/components/evidence-viewer.tsx +65 -51
- package/packages/web-dashboard/src/components/feature-card.tsx +90 -7
- package/packages/web-dashboard/src/components/feature-cost-panel.tsx +112 -11
- package/packages/web-dashboard/src/components/feature-list-view.tsx +25 -4
- package/packages/web-dashboard/src/components/features/runtime-inspector/EventsTimelineView.tsx +260 -0
- package/packages/web-dashboard/src/components/features/runtime-inspector/OperationsListView.tsx +172 -0
- package/packages/web-dashboard/src/components/features/runtime-inspector/RuntimeInspectorPanel.tsx +896 -0
- package/packages/web-dashboard/src/components/filter-bar.tsx +7 -39
- package/packages/web-dashboard/src/components/focus/ActionableRiskList.tsx +46 -0
- package/packages/web-dashboard/src/components/focus/AgentRolePerformanceCard.tsx +200 -0
- package/packages/web-dashboard/src/components/focus/BlockedGuidanceBanner.tsx +149 -0
- package/packages/web-dashboard/src/components/focus/CheckpointInspector.tsx +123 -0
- package/packages/web-dashboard/src/components/focus/CheckpointRail.tsx +118 -0
- package/packages/web-dashboard/src/components/focus/CheckpointScrubber.tsx +249 -0
- package/packages/web-dashboard/src/components/focus/CollisionApprovalBanner.tsx +192 -0
- package/packages/web-dashboard/src/components/focus/CollisionRadar.tsx +136 -0
- package/packages/web-dashboard/src/components/focus/ConflictStatusCard.tsx +52 -0
- package/packages/web-dashboard/src/components/focus/ContextSidebar.tsx +108 -0
- package/packages/web-dashboard/src/components/focus/DiagnosisPanel.tsx +68 -0
- package/packages/web-dashboard/src/components/focus/FeatureDecisionBanner.tsx +68 -0
- package/packages/web-dashboard/src/components/focus/FeatureQuestionAnswerPanel.tsx +167 -0
- package/packages/web-dashboard/src/components/focus/FocusHeader.tsx +54 -0
- package/packages/web-dashboard/src/components/focus/FocusLayout.tsx +283 -0
- package/packages/web-dashboard/src/components/focus/GateFlakinessSummary.tsx +144 -0
- package/packages/web-dashboard/src/components/focus/GenealogyTree.tsx +34 -0
- package/packages/web-dashboard/src/components/focus/HeroBlock.tsx +67 -0
- package/packages/web-dashboard/src/components/focus/LiveAgentConsole.tsx +277 -0
- package/packages/web-dashboard/src/components/focus/MergeQueueCard.tsx +78 -0
- package/packages/web-dashboard/src/components/focus/OperationalSummaryCard.tsx +227 -0
- package/packages/web-dashboard/src/components/focus/PinnedActions.tsx +96 -0
- package/packages/web-dashboard/src/components/focus/PlanAmendmentPanel.tsx +250 -0
- package/packages/web-dashboard/src/components/focus/PlanProgressPanel.tsx +133 -0
- package/packages/web-dashboard/src/components/focus/PlannerArtifactViewer.tsx +158 -0
- package/packages/web-dashboard/src/components/focus/PlannerLifecycleHeader.tsx +141 -0
- package/packages/web-dashboard/src/components/focus/ProgressSnapshotCard.tsx +113 -0
- package/packages/web-dashboard/src/components/focus/RecentMaterialChanges.tsx +69 -0
- package/packages/web-dashboard/src/components/focus/RoleLogViewer.tsx +436 -0
- package/packages/web-dashboard/src/components/focus/RunHistoryBrowser.tsx +62 -0
- package/packages/web-dashboard/src/components/focus/SpecViewer.tsx +172 -0
- package/packages/web-dashboard/src/components/focus/TabBar.tsx +33 -0
- package/packages/web-dashboard/src/components/focus/UsageBurnChart.tsx +212 -0
- package/packages/web-dashboard/src/components/focus/VerificationSummaryCard.tsx +122 -0
- package/packages/web-dashboard/src/components/focus/tabs/ChangesTab.tsx +325 -0
- package/packages/web-dashboard/src/components/focus/tabs/ConflictsTab.tsx +395 -0
- package/packages/web-dashboard/src/components/focus/tabs/GatesQaTab.tsx +38 -0
- package/packages/web-dashboard/src/components/focus/tabs/HistoryTab.tsx +213 -0
- package/packages/web-dashboard/src/components/focus/tabs/IntakeTab.tsx +429 -0
- package/packages/web-dashboard/src/components/focus/tabs/OverviewTab.tsx +217 -0
- package/packages/web-dashboard/src/components/focus/tabs/PlanningTab.tsx +390 -0
- package/packages/web-dashboard/src/components/focus/tabs/ReviewTab.tsx +497 -0
- package/packages/web-dashboard/src/components/focus/tabs/RuntimeTab.tsx +213 -0
- package/packages/web-dashboard/src/components/focus/tabs/TranscriptTab.tsx +315 -0
- package/packages/web-dashboard/src/components/gate-results.tsx +2 -2
- package/packages/web-dashboard/src/components/human-input-panel.tsx +33 -57
- package/packages/web-dashboard/src/components/kanban-board.tsx +4 -0
- package/packages/web-dashboard/src/components/launch/launch-draft-card.tsx +131 -0
- package/packages/web-dashboard/src/components/plan-viewer.tsx +147 -69
- package/packages/web-dashboard/src/components/quick-launch-panel.tsx +20 -47
- package/packages/web-dashboard/src/components/summary-bar.tsx +30 -76
- package/packages/web-dashboard/src/lib/aop-client.ts +2484 -36
- package/packages/web-dashboard/src/lib/blocked-state-guidance.ts +475 -0
- package/packages/web-dashboard/src/lib/collision-radar.ts +136 -0
- package/packages/web-dashboard/src/lib/dashboard-action-states.ts +204 -0
- package/packages/web-dashboard/src/lib/dashboard-runtime-client.ts +439 -0
- package/packages/web-dashboard/src/lib/dashboard-utils.ts +179 -18
- package/packages/web-dashboard/src/lib/drop-zone-utils.ts +92 -0
- package/packages/web-dashboard/src/lib/focus-detail-derivations.ts +958 -0
- package/packages/web-dashboard/src/lib/focus-view.ts +300 -0
- package/packages/web-dashboard/src/lib/health-diagnosis.ts +356 -0
- package/packages/web-dashboard/src/lib/launch-contracts.ts +77 -0
- package/packages/web-dashboard/src/lib/launch-markdown.ts +107 -0
- package/packages/web-dashboard/src/lib/launch-page-preview.ts +89 -0
- package/packages/web-dashboard/src/lib/live-feed.ts +1 -1
- package/packages/web-dashboard/src/lib/multi-project-config.ts +33 -0
- package/packages/web-dashboard/src/lib/orchestrator-tools.ts +845 -59
- package/packages/web-dashboard/src/lib/planner-workspace.ts +1285 -0
- package/packages/web-dashboard/src/lib/review-contracts.ts +5 -3
- package/packages/web-dashboard/src/lib/runtime-files.ts +285 -0
- package/packages/web-dashboard/src/lib/tool-catalog.ts +51 -0
- package/packages/web-dashboard/src/lib/types.ts +731 -3
- package/packages/web-dashboard/src/lib/usage-burn.ts +175 -0
- package/packages/web-dashboard/src/lib/worktree-diff.ts +128 -0
- package/packages/web-dashboard/src/styles/dashboard.module.css +1742 -459
- package/packages/web-dashboard/test/api/actions/route.spec.ts +675 -0
- package/packages/web-dashboard/test/api/features/diff.route.spec.ts +57 -0
- package/packages/web-dashboard/test/api/features/feature.route.spec.ts +99 -0
- package/packages/web-dashboard/test/api/features/live-output.route.spec.ts +123 -0
- package/packages/web-dashboard/test/api/features/plan-amend.route.spec.ts +95 -0
- package/packages/web-dashboard/test/api/features/planner-workspaces.route.spec.ts +162 -0
- package/packages/web-dashboard/test/api/features/question-answer.route.spec.ts +99 -0
- package/packages/web-dashboard/test/api/features/triage.route.spec.ts +195 -0
- package/packages/web-dashboard/test/api/launch/preview.route.spec.ts +149 -0
- package/packages/web-dashboard/test/api/launch/submit.route.spec.ts +382 -0
- package/packages/web-dashboard/test/api/runtime/events/route.spec.ts +164 -0
- package/packages/web-dashboard/test/api/runtime/operations/route.spec.ts +156 -0
- package/packages/web-dashboard/test/api/runtime/runs/route.spec.ts +112 -0
- package/packages/web-dashboard/test/components/changes-tab.spec.tsx +76 -0
- package/packages/web-dashboard/test/components/command-center-root.spec.tsx +87 -0
- package/packages/web-dashboard/test/components/diagnosis-panel.spec.tsx +59 -0
- package/packages/web-dashboard/test/components/feature-card.spec.tsx +45 -0
- package/packages/web-dashboard/test/components/focus-layout.spec.tsx +299 -0
- package/packages/web-dashboard/test/components/gate-results.spec.tsx +39 -0
- package/packages/web-dashboard/test/components/gates-qa-tab.spec.tsx +118 -0
- package/packages/web-dashboard/test/components/human-input-panel.spec.tsx +54 -0
- package/packages/web-dashboard/test/components/intake-tab.spec.tsx +210 -0
- package/packages/web-dashboard/test/components/kanban-board.spec.tsx +35 -0
- package/packages/web-dashboard/test/components/launch-draft-card.spec.tsx +54 -0
- package/packages/web-dashboard/test/components/launch-page.spec.tsx +79 -0
- package/packages/web-dashboard/test/components/overview-tab.spec.tsx +236 -0
- package/packages/web-dashboard/test/components/planning-tab.spec.tsx +202 -0
- package/packages/web-dashboard/test/components/review-tab.spec.tsx +169 -0
- package/packages/web-dashboard/test/components/role-log-viewer.spec.ts +42 -0
- package/packages/web-dashboard/test/components/runtime-inspector.spec.tsx +22 -0
- package/packages/web-dashboard/test/components/runtime-tab.spec.tsx +133 -0
- package/packages/web-dashboard/test/components/transcript-tab.spec.tsx +46 -0
- package/packages/web-dashboard/test/components/triage-drawer.spec.tsx +159 -0
- package/packages/web-dashboard/test/lib/aop-client.spec.ts +235 -0
- package/packages/web-dashboard/test/lib/dashboard-runtime-client.spec.ts +144 -0
- package/packages/web-dashboard/test/lib/focus-detail-derivations.spec.ts +314 -0
- package/packages/web-dashboard/test/lib/focus-view.spec.ts +248 -0
- package/packages/web-dashboard/test/lib/health-diagnosis.spec.ts +277 -0
- package/packages/web-dashboard/test/lib/launch-markdown.spec.ts +36 -0
- package/packages/web-dashboard/test/lib/multi-project-config.spec.ts +54 -0
- package/packages/web-dashboard/test/lib/orchestrator-tools.spec.ts +352 -0
- package/packages/web-dashboard/test/lib/planner-workspace.spec.ts +289 -0
- package/packages/web-dashboard/test/lib/worktree-diff.spec.ts +119 -0
- package/packages/web-dashboard/vitest.config.ts +2 -0
- package/spec-files/completed/agentic_orchestrator_add_feature_to_active_execution_spec.md +557 -0
- package/spec-files/completed/agentic_orchestrator_dashboard_command_center_redesign_spec.md +1147 -0
- package/spec-files/completed/agentic_orchestrator_execution_mode_spec.md +18 -16
- package/spec-files/completed/agentic_orchestrator_feature_focus_view_track_a_spec.md +672 -0
- package/spec-files/completed/agentic_orchestrator_feature_focus_view_track_b_spec.md +794 -0
- package/spec-files/completed/agentic_orchestrator_feature_focus_view_track_c_decision_centric_remediation_spec.md +1037 -0
- package/spec-files/completed/agentic_orchestrator_feature_focus_view_ux_redesign_spec.md +1432 -0
- package/spec-files/completed/agentic_orchestrator_focus_plan_tab_intake_planning_workspace_spec.md +921 -0
- package/spec-files/completed/agentic_orchestrator_intentional_collision_override_spec.md +584 -0
- package/spec-files/completed/agentic_orchestrator_interactive_planning_intake_and_requirements_verification_spec.md +1185 -0
- package/spec-files/completed/agentic_orchestrator_reactive_execution_enrollment_spec.md +864 -0
- package/spec-files/{outstanding → completed}/agentic_orchestrator_runtime_inspection_spec.md +92 -19
- package/spec-files/completed/agentic_orchestrator_scope_aware_run_lease_spec.md +408 -0
- package/spec-files/completed/git-reconciliation-engine.md +827 -0
- package/spec-files/outstanding/agentic_orchestrator_dashboard_quick_launch_and_control_surface_spec.md +331 -0
- package/spec-files/outstanding/agentic_orchestrator_enterprise_governance_dashboard_spec.md +16 -6
- package/spec-files/outstanding/agentic_orchestrator_evidence_integrity_doctor_spec.md +60 -9
- package/spec-files/outstanding/agentic_orchestrator_focus_plan_tab_execution_contract_workspace_spec.md +616 -0
- package/spec-files/outstanding/agentic_orchestrator_headless_standby_dashboard_runtime_spec.md +310 -0
- package/spec-files/outstanding/agentic_orchestrator_human_input_interaction_protocol_spec.md +175 -72
- package/spec-files/outstanding/agentic_orchestrator_interactive_rename_cleanup_spec.md +197 -0
- package/spec-files/outstanding/agentic_orchestrator_interactive_resume_and_reconciliation_disposition_spec.md +412 -0
- package/spec-files/outstanding/agentic_orchestrator_knowledge_canary_spec.md +166 -137
- package/spec-files/outstanding/agentic_orchestrator_observability_replay_spec.md +3 -3
- package/spec-files/outstanding/agentic_orchestrator_phase_specific_agent_profiles_and_token_telemetry_spec.md +303 -0
- package/spec-files/outstanding/agentic_orchestrator_planning_review_quality_spec.md +18 -5
- package/spec-files/outstanding/agentic_orchestrator_policy_stratification_spec.md +225 -0
- package/spec-files/outstanding/agentic_orchestrator_quality_adoption_execution_spec.md +77 -50
- package/spec-files/outstanding/agentic_orchestrator_ready_to_merge_branch_handoff_spec.md +724 -0
- package/spec-files/outstanding/agentic_orchestrator_remove_deterministic_mode_spec.md +263 -0
- package/spec-files/outstanding/agentic_orchestrator_request_more_context_and_dashboard_human_input_spec.md +456 -0
- package/spec-files/outstanding/agentic_orchestrator_spec_coverage_and_reconciliation_enforcement_spec.md +1411 -0
- package/spec-files/outstanding/agentic_orchestrator_spec_ordering_agent_spec.md +370 -0
- package/spec-files/outstanding/shadow_workspace_implementation_spec.md +1 -1
- package/spec-files/progress.md +2045 -87
- package/specs/001-runtime-inspection/checklists/requirements.md +35 -0
- package/specs/001-runtime-inspection/design.md +338 -0
- package/specs/001-runtime-inspection/spec.md +95 -0
- package/specs/002-scope-aware-lease/checklists/requirements.md +35 -0
- package/specs/002-scope-aware-lease/contracts/lease-registry.schema.json +101 -0
- package/specs/002-scope-aware-lease/data-model.md +236 -0
- package/specs/002-scope-aware-lease/plan.md +766 -0
- package/specs/002-scope-aware-lease/quickstart.md +150 -0
- package/specs/002-scope-aware-lease/research.md +135 -0
- package/specs/002-scope-aware-lease/spec.md +128 -0
- package/specs/002-scope-aware-lease/tasks.md +767 -0
- package/tsconfig.json +1 -1
- package/vitest.config.ts +28 -0
- package/ARCHITECTURE_ADHERENCE_ANALYSIS.md +0 -871
- package/packages/web-dashboard/next-env.d.ts +0 -6
- package/packages/web-dashboard/src/components/detail-panel.tsx +0 -1124
- package/packages/web-dashboard/src/components/review-workspace.tsx +0 -1162
- /package/spec-files/{outstanding → completed}/agentic_orchestrator_artifact_database_publishing_spec.md +0 -0
- /package/spec-files/{outstanding → completed}/agentic_orchestrator_cli_shell_tab_completion_spec.md +0 -0
- /package/spec-files/{outstanding → completed}/agentic_orchestrator_dashboard_diff_and_agent_console_spec.md +0 -0
- /package/spec-files/{outstanding → completed}/agentic_orchestrator_performance_improvements_spec.md +0 -0
- /package/spec-files/{outstanding → completed}/agentic_orchestrator_persistent_worker_runtime_spec.md +0 -0
- /package/spec-files/{outstanding → completed}/agentic_orchestrator_provider_auth_bootstrap_spec.md +0 -0
- /package/spec-files/{outstanding → completed}/agentic_orchestrator_real_worker_provider_execution_spec.md +0 -0
|
@@ -1,40 +1,92 @@
|
|
|
1
1
|
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import YAML from 'yaml';
|
|
4
|
+
import { buildCollisionRadar } from '@/lib/collision-radar.js';
|
|
4
5
|
import { getProjectByName, readMultiProjectConfig } from '@/lib/multi-project-config.js';
|
|
5
|
-
import { parseUnifiedDiff } from '@/lib/dashboard-utils.js';
|
|
6
|
+
import { hasGateFailure, normalizeCiStatus, parseUnifiedDiff } from '@/lib/dashboard-utils.js';
|
|
7
|
+
import {
|
|
8
|
+
buildActionableRisks,
|
|
9
|
+
buildPlanProgressSummary,
|
|
10
|
+
buildRevisionSummary,
|
|
11
|
+
buildVerificationSummary,
|
|
12
|
+
summarizeFeatureSpec,
|
|
13
|
+
} from '@/lib/focus-detail-derivations.js';
|
|
14
|
+
import {
|
|
15
|
+
buildFeatureIntakeWorkspace,
|
|
16
|
+
buildFeaturePlanningWorkspace,
|
|
17
|
+
buildPlannerLifecycleSummary,
|
|
18
|
+
normalizeAcceptedPlanRecord,
|
|
19
|
+
normalizeBootstrapManifestRecord,
|
|
20
|
+
normalizeIntakeReviewRecord,
|
|
21
|
+
normalizeVerifiedManifestRecord,
|
|
22
|
+
} from '@/lib/planner-workspace.js';
|
|
23
|
+
import { readPolicyYaml } from '@/lib/policy-reader.js';
|
|
24
|
+
import { buildUsageBurnProjection } from '@/lib/usage-burn.js';
|
|
6
25
|
import type {
|
|
7
26
|
AgentLogEntry,
|
|
27
|
+
AgentRolePerformanceCardData,
|
|
28
|
+
AgentRolePerformanceMetric,
|
|
8
29
|
AgentSessionCluster,
|
|
9
30
|
AgentPipelineStatus,
|
|
31
|
+
CollisionRadarSnapshot,
|
|
10
32
|
CostSummary,
|
|
11
33
|
DashboardStatusPayload,
|
|
12
34
|
EvidenceArtifact,
|
|
35
|
+
FeatureBudgetState,
|
|
36
|
+
FeatureCheckpointsPage,
|
|
13
37
|
FeatureCheckpointComparison,
|
|
14
38
|
FeatureCheckpointDiff,
|
|
15
39
|
FeatureDetail,
|
|
40
|
+
FeatureFileIndex,
|
|
41
|
+
FeatureGenealogy,
|
|
42
|
+
FeatureGenealogyNode,
|
|
43
|
+
FeatureIntakeWorkspace,
|
|
16
44
|
FeatureLog,
|
|
17
45
|
FeatureLogEntry,
|
|
46
|
+
FeaturePlanningWorkspace,
|
|
47
|
+
FeatureQuestion,
|
|
48
|
+
FeatureRunHistoryDetail,
|
|
49
|
+
FeatureRunHistoryEntry,
|
|
50
|
+
FeatureSpecSummary,
|
|
18
51
|
FeaturesIndex,
|
|
52
|
+
GateHistoryEntry,
|
|
19
53
|
FeatureSummary,
|
|
54
|
+
FeatureTriageDetail,
|
|
20
55
|
FeatureCheckpoint,
|
|
21
56
|
FeatureExecutionMode,
|
|
57
|
+
FeatureIntakeReviewSummary,
|
|
58
|
+
FeatureIntakeSummary,
|
|
22
59
|
GateRunEvidence,
|
|
23
60
|
InteractiveExecutionModeMetrics,
|
|
24
61
|
InteractivePerformanceLatestCheckpoint,
|
|
25
62
|
InteractivePerformanceMetrics,
|
|
63
|
+
LiveAgentOutputSnapshot,
|
|
64
|
+
LiveAgentOutputStatusSnapshot,
|
|
26
65
|
LockLease,
|
|
66
|
+
MergeQueueEntry,
|
|
67
|
+
MergeQueueSnapshot,
|
|
68
|
+
PlannerArtifactView,
|
|
69
|
+
PlannerLifecycleSummary,
|
|
70
|
+
RolePerformanceSummary,
|
|
27
71
|
QaTestIndex,
|
|
28
72
|
RawLogFileMeta,
|
|
29
73
|
ReviewBrief,
|
|
30
74
|
RoleStatus,
|
|
31
75
|
RuntimeSession,
|
|
76
|
+
TokenCostConversion,
|
|
77
|
+
ProviderAnalytics,
|
|
32
78
|
WorkerEventEntry,
|
|
79
|
+
WorkerEventSummary,
|
|
80
|
+
OrganizerStatusProjection,
|
|
33
81
|
} from '@/lib/types.js';
|
|
34
82
|
|
|
35
83
|
const AOP_ROOT = process.env.AOP_ROOT ?? process.cwd();
|
|
36
|
-
const LOG_FILENAME_PATTERN = /^(planner|builder|qa)-(\d{13})\.txt$/;
|
|
84
|
+
const LOG_FILENAME_PATTERN = /^(planner|builder|qa|reconciler)-(\d{13})\.txt$/;
|
|
37
85
|
const CHECKPOINT_METRICS_HISTORY_LIMIT = 200;
|
|
86
|
+
const TRACKED_AGENT_ROLES = ['planner', 'builder', 'qa', 'reconciler'] as const;
|
|
87
|
+
export const DEFAULT_LIVE_AGENT_OUTPUT_LINE_LIMIT = 500;
|
|
88
|
+
|
|
89
|
+
type TrackedAgentRole = (typeof TRACKED_AGENT_ROLES)[number];
|
|
38
90
|
|
|
39
91
|
interface ParsedFrontMatter {
|
|
40
92
|
frontMatter: Record<string, unknown>;
|
|
@@ -72,9 +124,11 @@ interface InteractiveMetricsFileShape {
|
|
|
72
124
|
}
|
|
73
125
|
|
|
74
126
|
const STATUS_TO_PHASE: Record<string, FeatureSummary['phase']> = {
|
|
127
|
+
intake: 'intake',
|
|
75
128
|
planning: 'planning',
|
|
76
129
|
building: 'building',
|
|
77
130
|
qa: 'qa',
|
|
131
|
+
awaiting_input: 'awaiting_input',
|
|
78
132
|
ready_to_merge: 'ready_to_merge',
|
|
79
133
|
merged: 'merged',
|
|
80
134
|
blocked: 'blocked',
|
|
@@ -83,11 +137,21 @@ const STATUS_TO_PHASE: Record<string, FeatureSummary['phase']> = {
|
|
|
83
137
|
};
|
|
84
138
|
|
|
85
139
|
const ACTIVE_PHASES = new Set<FeatureSummary['phase']>([
|
|
140
|
+
'intake',
|
|
86
141
|
'planning',
|
|
87
142
|
'building',
|
|
88
143
|
'qa',
|
|
89
144
|
'ready_to_merge',
|
|
90
145
|
]);
|
|
146
|
+
const DEFAULT_BUDGET_ALERT_THRESHOLD = 0.8;
|
|
147
|
+
const STALE_FEATURE_MS = 20 * 60_000;
|
|
148
|
+
const RECENT_CHANGE_MS = 24 * 60_000 * 60;
|
|
149
|
+
|
|
150
|
+
interface BudgetConfig {
|
|
151
|
+
per_feature_limit_tokens: number | null;
|
|
152
|
+
alert_threshold: number;
|
|
153
|
+
token_cost_conversion: TokenCostConversion | null;
|
|
154
|
+
}
|
|
91
155
|
|
|
92
156
|
function isActivityState(value: unknown): value is NonNullable<FeatureSummary['activity_state']> {
|
|
93
157
|
return (
|
|
@@ -100,6 +164,331 @@ function isActivityState(value: unknown): value is NonNullable<FeatureSummary['a
|
|
|
100
164
|
);
|
|
101
165
|
}
|
|
102
166
|
|
|
167
|
+
function parseBudgetConfig(policy: Record<string, unknown> | null): BudgetConfig {
|
|
168
|
+
const budget =
|
|
169
|
+
policy && typeof policy.budget === 'object' ? (policy.budget as Record<string, unknown>) : null;
|
|
170
|
+
const tokenCostConversion =
|
|
171
|
+
budget?.token_cost_conversion &&
|
|
172
|
+
typeof budget.token_cost_conversion === 'object' &&
|
|
173
|
+
typeof (budget.token_cost_conversion as Record<string, unknown>).usd_per_1k_tokens === 'number'
|
|
174
|
+
? {
|
|
175
|
+
usd_per_1k_tokens: (budget.token_cost_conversion as Record<string, number>)
|
|
176
|
+
.usd_per_1k_tokens,
|
|
177
|
+
}
|
|
178
|
+
: null;
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
per_feature_limit_tokens:
|
|
182
|
+
typeof budget?.per_feature_limit_tokens === 'number' ? budget.per_feature_limit_tokens : null,
|
|
183
|
+
alert_threshold:
|
|
184
|
+
typeof budget?.alert_threshold === 'number'
|
|
185
|
+
? budget.alert_threshold
|
|
186
|
+
: DEFAULT_BUDGET_ALERT_THRESHOLD,
|
|
187
|
+
token_cost_conversion: tokenCostConversion,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
interface LiveAgentOutputReadOptions {
|
|
192
|
+
tailLines?: number;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function tailTextByLines(content: string, lineLimit: number | undefined): string {
|
|
196
|
+
if (!lineLimit || lineLimit < 1 || content.length === 0) {
|
|
197
|
+
return content;
|
|
198
|
+
}
|
|
199
|
+
let newlineCount = 0;
|
|
200
|
+
let start = 0;
|
|
201
|
+
for (let index = content.length - 1; index >= 0; index -= 1) {
|
|
202
|
+
if (content.charCodeAt(index) !== 10) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (index === content.length - 1) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
newlineCount += 1;
|
|
209
|
+
if (newlineCount >= lineLimit) {
|
|
210
|
+
start = index + 1;
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return content.slice(start);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function deriveBudgetState(
|
|
218
|
+
tokensUsed: number | null,
|
|
219
|
+
budgetConfig: BudgetConfig,
|
|
220
|
+
): FeatureBudgetState {
|
|
221
|
+
if (tokensUsed == null || budgetConfig.per_feature_limit_tokens == null) {
|
|
222
|
+
return 'unknown';
|
|
223
|
+
}
|
|
224
|
+
if (budgetConfig.per_feature_limit_tokens <= 0) {
|
|
225
|
+
return 'unknown';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const ratio = tokensUsed / budgetConfig.per_feature_limit_tokens;
|
|
229
|
+
if (ratio >= 1) {
|
|
230
|
+
return 'exceeded';
|
|
231
|
+
}
|
|
232
|
+
if (ratio >= 0.9) {
|
|
233
|
+
return 'critical';
|
|
234
|
+
}
|
|
235
|
+
if (ratio >= budgetConfig.alert_threshold) {
|
|
236
|
+
return 'warning';
|
|
237
|
+
}
|
|
238
|
+
return 'normal';
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function deriveCostFromTokens(
|
|
242
|
+
tokensUsed: number | null,
|
|
243
|
+
tokenCostConversion: TokenCostConversion | null,
|
|
244
|
+
): number | null {
|
|
245
|
+
if (tokensUsed == null || tokenCostConversion == null) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
return Number(((tokensUsed / 1000) * tokenCostConversion.usd_per_1k_tokens).toFixed(4));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function lastMeaningfulChange(frontMatter: Record<string, unknown>): string | undefined {
|
|
252
|
+
const recovery =
|
|
253
|
+
frontMatter.recovery && typeof frontMatter.recovery === 'object'
|
|
254
|
+
? (frontMatter.recovery as Record<string, unknown>)
|
|
255
|
+
: null;
|
|
256
|
+
const candidates = [
|
|
257
|
+
recovery?.last_attempt_at,
|
|
258
|
+
frontMatter.last_meaningful_change_at,
|
|
259
|
+
frontMatter.activity_last_event_at,
|
|
260
|
+
frontMatter.last_updated,
|
|
261
|
+
];
|
|
262
|
+
for (const candidate of candidates) {
|
|
263
|
+
if (typeof candidate === 'string' && candidate.trim().length > 0) {
|
|
264
|
+
return candidate;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function isRecentChange(iso: string | undefined, nowMs = Date.now()): boolean {
|
|
271
|
+
if (!iso) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
const timestamp = Date.parse(iso);
|
|
275
|
+
if (Number.isNaN(timestamp) || timestamp > nowMs) {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
return nowMs - timestamp <= RECENT_CHANGE_MS;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function isStalledFeature(feature: FeatureSummary, nowMs = Date.now()): boolean {
|
|
282
|
+
if (!ACTIVE_PHASES.has(feature.phase ?? 'unknown')) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
if (feature.activity_state === 'waiting_input' || feature.activity_state === 'blocked') {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
const changeAt = feature.last_meaningful_change_at ?? feature.last_updated;
|
|
289
|
+
if (!changeAt) {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
const timestamp = Date.parse(changeAt);
|
|
293
|
+
if (Number.isNaN(timestamp) || timestamp > nowMs) {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
return nowMs - timestamp >= STALE_FEATURE_MS;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const STALE_RECOVERY_MS = 10 * 60 * 1000;
|
|
300
|
+
|
|
301
|
+
function isRetryingRecovery(
|
|
302
|
+
feature: Pick<FeatureSummary, 'phase' | 'recovery' | 'status_reason' | 'activity_state'>,
|
|
303
|
+
nowMs = Date.now(),
|
|
304
|
+
): boolean {
|
|
305
|
+
if (!feature.recovery || feature.recovery.state !== 'retrying') {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
if (feature.phase === 'merged' || feature.phase === 'ready_to_merge') {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
// If the feature is not actively running and the last recovery attempt was
|
|
312
|
+
// long ago, the recovery is stale — the reconciler or agent is no longer
|
|
313
|
+
// working on it.
|
|
314
|
+
if (feature.activity_state !== 'active' && feature.recovery.last_attempt_at) {
|
|
315
|
+
const lastAttempt = Date.parse(feature.recovery.last_attempt_at);
|
|
316
|
+
if (!Number.isNaN(lastAttempt) && nowMs - lastAttempt > STALE_RECOVERY_MS) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const cause = feature.recovery.cause?.trim().toLowerCase();
|
|
321
|
+
if (!cause) {
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
if (feature.phase !== 'blocked') {
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
return feature.status_reason?.trim().toLowerCase().startsWith(`${cause}:`) ?? false;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function formatRecoveryStatusLabel(feature: Pick<FeatureSummary, 'recovery'>): string {
|
|
331
|
+
const role = feature.recovery?.role?.trim();
|
|
332
|
+
if (!role) {
|
|
333
|
+
return 'Recovering';
|
|
334
|
+
}
|
|
335
|
+
return `${role.charAt(0).toUpperCase()}${role.slice(1)} Retrying`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function deriveClientStatusLabel(feature: FeatureSummary, dependencyBlocked: boolean): string {
|
|
339
|
+
if (feature.status === 'failed') {
|
|
340
|
+
return 'Failed';
|
|
341
|
+
}
|
|
342
|
+
// Plan collision — user must approve overlap before the feature can proceed
|
|
343
|
+
if (feature.collision) {
|
|
344
|
+
return 'Plan Collision';
|
|
345
|
+
}
|
|
346
|
+
// Blocked by mainline divergence — user may need to commit outstanding changes
|
|
347
|
+
if (
|
|
348
|
+
feature.status === 'blocked' &&
|
|
349
|
+
feature.recovery?.state === 'retrying' &&
|
|
350
|
+
feature.recovery.cause === 'mainline_divergence'
|
|
351
|
+
) {
|
|
352
|
+
return 'Merge Conflict';
|
|
353
|
+
}
|
|
354
|
+
if (feature.phase === 'intake') {
|
|
355
|
+
return feature.intake_status === 'awaiting_input' ? 'Needs Input' : 'In Intake';
|
|
356
|
+
}
|
|
357
|
+
if (feature.phase === 'ready_to_merge') {
|
|
358
|
+
return 'Ready';
|
|
359
|
+
}
|
|
360
|
+
if (feature.phase === 'merged') {
|
|
361
|
+
return 'Merged';
|
|
362
|
+
}
|
|
363
|
+
if (feature.activity_state === 'waiting_input' || (feature.waiting_input_count ?? 0) > 0) {
|
|
364
|
+
return 'Needs Input';
|
|
365
|
+
}
|
|
366
|
+
if (dependencyBlocked) {
|
|
367
|
+
return 'Waiting On Dependencies';
|
|
368
|
+
}
|
|
369
|
+
if (isRetryingRecovery(feature)) {
|
|
370
|
+
return formatRecoveryStatusLabel(feature);
|
|
371
|
+
}
|
|
372
|
+
if (normalizeCiStatus(feature.pr?.ci_status) === 'pending') {
|
|
373
|
+
return 'Waiting On CI';
|
|
374
|
+
}
|
|
375
|
+
if (feature.phase === 'blocked') {
|
|
376
|
+
return 'Blocked';
|
|
377
|
+
}
|
|
378
|
+
if (
|
|
379
|
+
hasGateFailure(feature) ||
|
|
380
|
+
feature.budget_state === 'critical' ||
|
|
381
|
+
feature.budget_state === 'exceeded'
|
|
382
|
+
) {
|
|
383
|
+
return 'At Risk';
|
|
384
|
+
}
|
|
385
|
+
if (isStalledFeature(feature)) {
|
|
386
|
+
return 'Stalled';
|
|
387
|
+
}
|
|
388
|
+
if (feature.activity_state === 'active') {
|
|
389
|
+
return 'In Progress';
|
|
390
|
+
}
|
|
391
|
+
if (feature.activity_state === 'idle') {
|
|
392
|
+
return 'Idle';
|
|
393
|
+
}
|
|
394
|
+
return feature.status.replace(/_/g, ' ');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function deriveAttentionReasons(
|
|
398
|
+
feature: FeatureSummary,
|
|
399
|
+
dependencyBlocked: boolean,
|
|
400
|
+
nowMs = Date.now(),
|
|
401
|
+
): string[] {
|
|
402
|
+
const reasons: string[] = [];
|
|
403
|
+
const ciStatus = normalizeCiStatus(feature.pr?.ci_status);
|
|
404
|
+
|
|
405
|
+
if (feature.activity_state === 'waiting_input' || (feature.waiting_input_count ?? 0) > 0) {
|
|
406
|
+
reasons.push('Needs Me');
|
|
407
|
+
}
|
|
408
|
+
if (feature.phase === 'intake' && feature.activity_state === 'active') {
|
|
409
|
+
reasons.push('Requirements Intake');
|
|
410
|
+
}
|
|
411
|
+
if (isRetryingRecovery(feature)) {
|
|
412
|
+
reasons.push('Recovering');
|
|
413
|
+
}
|
|
414
|
+
if (feature.collision) {
|
|
415
|
+
reasons.push('Plan Collision');
|
|
416
|
+
}
|
|
417
|
+
if (
|
|
418
|
+
(feature.phase === 'blocked' && !isRetryingRecovery(feature)) ||
|
|
419
|
+
hasGateFailure(feature) ||
|
|
420
|
+
feature.budget_state === 'critical' ||
|
|
421
|
+
feature.budget_state === 'exceeded'
|
|
422
|
+
) {
|
|
423
|
+
reasons.push('At Risk');
|
|
424
|
+
}
|
|
425
|
+
if (feature.phase === 'ready_to_merge') {
|
|
426
|
+
reasons.push('Ready');
|
|
427
|
+
}
|
|
428
|
+
if (isStalledFeature(feature, nowMs)) {
|
|
429
|
+
reasons.push('Stalled');
|
|
430
|
+
}
|
|
431
|
+
if (dependencyBlocked) {
|
|
432
|
+
reasons.push('Waiting On Dependencies');
|
|
433
|
+
}
|
|
434
|
+
if (ciStatus === 'pending') {
|
|
435
|
+
reasons.push('Waiting On CI');
|
|
436
|
+
}
|
|
437
|
+
if (isRecentChange(feature.last_meaningful_change_at, nowMs)) {
|
|
438
|
+
reasons.push('Recently Changed');
|
|
439
|
+
}
|
|
440
|
+
return reasons;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function enrichFeatureSummary(
|
|
444
|
+
feature: FeatureSummary,
|
|
445
|
+
options: {
|
|
446
|
+
cost: CostSummary | null;
|
|
447
|
+
budgetConfig: BudgetConfig;
|
|
448
|
+
dependencyBlocked: boolean;
|
|
449
|
+
nowMs?: number;
|
|
450
|
+
},
|
|
451
|
+
): FeatureSummary {
|
|
452
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
453
|
+
const usageTokens = options.cost?.tokens_used ?? null;
|
|
454
|
+
const budgetState = deriveBudgetState(usageTokens, options.budgetConfig);
|
|
455
|
+
const hasWaitingInputSignal =
|
|
456
|
+
feature.status === 'awaiting_input' ||
|
|
457
|
+
feature.phase === 'awaiting_input' ||
|
|
458
|
+
feature.activity_state === 'waiting_input' ||
|
|
459
|
+
feature.status_reason?.includes('waiting_input') ||
|
|
460
|
+
feature.status_reason?.includes('awaiting_input') ||
|
|
461
|
+
false;
|
|
462
|
+
const enriched: FeatureSummary = {
|
|
463
|
+
...feature,
|
|
464
|
+
usage_tokens: usageTokens,
|
|
465
|
+
budget_state: budgetState,
|
|
466
|
+
waiting_input_count: hasWaitingInputSignal ? (feature.open_question_count ?? 1) : 0,
|
|
467
|
+
last_meaningful_change_at:
|
|
468
|
+
feature.last_meaningful_change_at ?? feature.activity_last_event_at ?? feature.last_updated,
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
enriched.attention_reasons = deriveAttentionReasons(enriched, options.dependencyBlocked, nowMs);
|
|
472
|
+
enriched.client_status_label = deriveClientStatusLabel(enriched, options.dependencyBlocked);
|
|
473
|
+
return enriched;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function summarizeBlocker(feature: FeatureSummary, dependencySummary: string[]): string | null {
|
|
477
|
+
if (dependencySummary.length > 0) {
|
|
478
|
+
return `Waiting on ${dependencySummary.join(', ')}`;
|
|
479
|
+
}
|
|
480
|
+
if (feature.status_reason && feature.status_reason.trim().length > 0) {
|
|
481
|
+
return feature.status_reason;
|
|
482
|
+
}
|
|
483
|
+
if (feature.phase === 'blocked') {
|
|
484
|
+
return 'Blocked and needs operator attention';
|
|
485
|
+
}
|
|
486
|
+
if (feature.activity_state === 'waiting_input') {
|
|
487
|
+
return 'Waiting on operator input';
|
|
488
|
+
}
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
|
|
103
492
|
function normalizeRoleStatus(value: unknown): RoleStatus {
|
|
104
493
|
if (typeof value !== 'string') {
|
|
105
494
|
return 'unknown';
|
|
@@ -169,7 +558,12 @@ function parseSessionCluster(frontMatter: Record<string, unknown>): AgentSession
|
|
|
169
558
|
}
|
|
170
559
|
|
|
171
560
|
function firstValidTimestamp(frontMatter: Record<string, unknown>): number | null {
|
|
561
|
+
const recovery =
|
|
562
|
+
frontMatter.recovery && typeof frontMatter.recovery === 'object'
|
|
563
|
+
? (frontMatter.recovery as Record<string, unknown>)
|
|
564
|
+
: null;
|
|
172
565
|
const candidates = [
|
|
566
|
+
recovery?.last_attempt_at,
|
|
173
567
|
frontMatter.activity_last_event_at,
|
|
174
568
|
frontMatter.last_heartbeat_at,
|
|
175
569
|
frontMatter.last_updated,
|
|
@@ -190,12 +584,55 @@ function inferActivityState(
|
|
|
190
584
|
frontMatter: Record<string, unknown>,
|
|
191
585
|
phase: FeatureSummary['phase'],
|
|
192
586
|
): NonNullable<FeatureSummary['activity_state']> | undefined {
|
|
193
|
-
|
|
194
|
-
|
|
587
|
+
const status = typeof frontMatter.status === 'string' ? frontMatter.status : '';
|
|
588
|
+
const statusReason =
|
|
589
|
+
typeof frontMatter.status_reason === 'string' ? frontMatter.status_reason.toLowerCase() : '';
|
|
590
|
+
const persistedActivity = isActivityState(frontMatter.activity_state)
|
|
591
|
+
? frontMatter.activity_state
|
|
592
|
+
: undefined;
|
|
593
|
+
if (persistedActivity === 'waiting_input') {
|
|
594
|
+
if (
|
|
595
|
+
status === 'awaiting_input' ||
|
|
596
|
+
phase === 'awaiting_input' ||
|
|
597
|
+
statusReason.startsWith('waiting_input') ||
|
|
598
|
+
statusReason.startsWith('question_awaiting_input') ||
|
|
599
|
+
statusReason.includes('awaiting_input')
|
|
600
|
+
) {
|
|
601
|
+
return 'waiting_input';
|
|
602
|
+
}
|
|
603
|
+
} else if (persistedActivity) {
|
|
604
|
+
return persistedActivity;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (status === 'awaiting_input' || phase === 'awaiting_input') {
|
|
608
|
+
return 'waiting_input';
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (
|
|
612
|
+
statusReason.startsWith('waiting_input') ||
|
|
613
|
+
statusReason.startsWith('question_awaiting_input')
|
|
614
|
+
) {
|
|
615
|
+
return 'waiting_input';
|
|
195
616
|
}
|
|
196
617
|
|
|
197
|
-
const status = typeof frontMatter.status === 'string' ? frontMatter.status : '';
|
|
198
618
|
if (phase === 'blocked') {
|
|
619
|
+
const recovery =
|
|
620
|
+
frontMatter.recovery && typeof frontMatter.recovery === 'object'
|
|
621
|
+
? (frontMatter.recovery as Record<string, unknown>)
|
|
622
|
+
: null;
|
|
623
|
+
const recoveryState = typeof recovery?.state === 'string' ? recovery.state : null;
|
|
624
|
+
const recoveryCause = typeof recovery?.cause === 'string' ? recovery.cause.toLowerCase() : null;
|
|
625
|
+
if (
|
|
626
|
+
recoveryState === 'retrying' &&
|
|
627
|
+
(!recoveryCause || statusReason.startsWith(`${recoveryCause}:`))
|
|
628
|
+
) {
|
|
629
|
+
const timestamp = firstValidTimestamp(frontMatter);
|
|
630
|
+
if (timestamp == null) {
|
|
631
|
+
return 'blocked';
|
|
632
|
+
}
|
|
633
|
+
const ageMs = Date.now() - timestamp;
|
|
634
|
+
return ageMs >= 0 && ageMs <= 120_000 ? 'active' : 'blocked';
|
|
635
|
+
}
|
|
199
636
|
return 'blocked';
|
|
200
637
|
}
|
|
201
638
|
if (phase === 'merged' || status === 'failed') {
|
|
@@ -239,12 +676,141 @@ function parseFrontMatter(raw: string): ParsedFrontMatter {
|
|
|
239
676
|
return { frontMatter, body: normalized.slice(match[0].length) };
|
|
240
677
|
}
|
|
241
678
|
|
|
679
|
+
function normalizeIntakeStatus(value: unknown): FeatureIntakeSummary['status'] | null {
|
|
680
|
+
return value === 'not_started' ||
|
|
681
|
+
value === 'in_progress' ||
|
|
682
|
+
value === 'awaiting_input' ||
|
|
683
|
+
value === 'verified'
|
|
684
|
+
? value
|
|
685
|
+
: null;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function normalizeWholeNumber(value: unknown): number | null {
|
|
689
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
690
|
+
? Math.max(0, Math.floor(value))
|
|
691
|
+
: null;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function normalizeFeatureRecoverySummary(
|
|
695
|
+
frontMatter: Record<string, unknown>,
|
|
696
|
+
): FeatureSummary['recovery'] {
|
|
697
|
+
const raw = frontMatter.recovery;
|
|
698
|
+
if (!raw || typeof raw !== 'object') {
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
const recovery = raw as Record<string, unknown>;
|
|
702
|
+
const state =
|
|
703
|
+
recovery.state === 'retrying' || recovery.state === 'awaiting_operator' ? recovery.state : null;
|
|
704
|
+
if (!state) {
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
return {
|
|
708
|
+
state,
|
|
709
|
+
cause: typeof recovery.cause === 'string' ? recovery.cause : null,
|
|
710
|
+
role: typeof recovery.role === 'string' ? recovery.role : null,
|
|
711
|
+
resume_phase: typeof recovery.resume_phase === 'string' ? recovery.resume_phase : null,
|
|
712
|
+
attempt:
|
|
713
|
+
typeof recovery.attempt === 'number' && Number.isFinite(recovery.attempt)
|
|
714
|
+
? Math.max(0, Math.floor(recovery.attempt))
|
|
715
|
+
: null,
|
|
716
|
+
run_id: typeof recovery.run_id === 'string' ? recovery.run_id : null,
|
|
717
|
+
started_at: typeof recovery.started_at === 'string' ? recovery.started_at : null,
|
|
718
|
+
last_attempt_at: typeof recovery.last_attempt_at === 'string' ? recovery.last_attempt_at : null,
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function normalizeFeatureIntakeSummary(
|
|
723
|
+
frontMatter: Record<string, unknown>,
|
|
724
|
+
): FeatureIntakeSummary | null {
|
|
725
|
+
const intake = asRecord(frontMatter.intake);
|
|
726
|
+
if (!intake) {
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
return {
|
|
730
|
+
status:
|
|
731
|
+
normalizeIntakeStatus(intake.status) ??
|
|
732
|
+
(frontMatter.status === 'awaiting_input' ? 'awaiting_input' : 'in_progress'),
|
|
733
|
+
bootstrap_manifest_version: normalizeWholeNumber(intake.bootstrap_manifest_version),
|
|
734
|
+
verified_manifest_version: normalizeWholeNumber(intake.verified_manifest_version),
|
|
735
|
+
open_ambiguity_count: normalizeWholeNumber(intake.open_ambiguity_count) ?? 0,
|
|
736
|
+
last_verified_at: typeof intake.last_verified_at === 'string' ? intake.last_verified_at : null,
|
|
737
|
+
promotion_basis: typeof intake.promotion_basis === 'string' ? intake.promotion_basis : null,
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function normalizeMergeRepairSummary(frontMatter: Record<string, unknown>) {
|
|
742
|
+
const raw = asRecord(frontMatter.merge_repair);
|
|
743
|
+
if (!raw) {
|
|
744
|
+
return null;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
const attempts = normalizeWholeNumber(raw.attempts) ?? 0;
|
|
748
|
+
const maxAttempts = Math.max(1, normalizeWholeNumber(raw.max_attempts) ?? 5);
|
|
749
|
+
const summary = {
|
|
750
|
+
attempts: Math.min(attempts, maxAttempts),
|
|
751
|
+
max_attempts: maxAttempts,
|
|
752
|
+
active: raw.active === true,
|
|
753
|
+
requested_at: typeof raw.requested_at === 'string' ? raw.requested_at : null,
|
|
754
|
+
last_failed_at: typeof raw.last_failed_at === 'string' ? raw.last_failed_at : null,
|
|
755
|
+
last_gate_evidence_path:
|
|
756
|
+
typeof raw.last_gate_evidence_path === 'string' ? raw.last_gate_evidence_path : null,
|
|
757
|
+
last_failure_summary:
|
|
758
|
+
typeof raw.last_failure_summary === 'string' ? raw.last_failure_summary : null,
|
|
759
|
+
reviewer_message: typeof raw.reviewer_message === 'string' ? raw.reviewer_message : null,
|
|
760
|
+
exhausted_at: typeof raw.exhausted_at === 'string' ? raw.exhausted_at : null,
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
if (
|
|
764
|
+
!summary.active &&
|
|
765
|
+
summary.attempts === 0 &&
|
|
766
|
+
summary.last_failed_at == null &&
|
|
767
|
+
summary.exhausted_at == null
|
|
768
|
+
) {
|
|
769
|
+
return null;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
return summary;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function normalizeIntakeReviewSummary(value: unknown): FeatureIntakeReviewSummary | null {
|
|
776
|
+
const record = asRecord(value);
|
|
777
|
+
if (!record) {
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
const ambiguities = Array.isArray(record.ambiguities) ? record.ambiguities : [];
|
|
781
|
+
return {
|
|
782
|
+
status: typeof record.status === 'string' ? record.status : null,
|
|
783
|
+
questions_open: normalizeWholeNumber(record.questions_open) ?? 0,
|
|
784
|
+
questions_resolved: normalizeWholeNumber(record.questions_resolved) ?? 0,
|
|
785
|
+
ambiguity_count: ambiguities.length,
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function readQuestionAmbiguityId(details: Record<string, unknown>): string | null {
|
|
790
|
+
return typeof details.ambiguity_id === 'string' ? details.ambiguity_id : null;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function readQuestionObligationIds(details: Record<string, unknown>): string[] {
|
|
794
|
+
const raw = Array.isArray(details.obligation_ids) ? details.obligation_ids : [];
|
|
795
|
+
return raw.filter((item): item is string => typeof item === 'string');
|
|
796
|
+
}
|
|
797
|
+
|
|
242
798
|
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
243
799
|
return value && typeof value === 'object' ? (value as Record<string, unknown>) : null;
|
|
244
800
|
}
|
|
245
801
|
|
|
246
802
|
function normalizeFeatureExecutionMode(value: unknown): FeatureExecutionMode | null {
|
|
247
|
-
|
|
803
|
+
// Feature surfaces are interactive-only even when older state artifacts still
|
|
804
|
+
// record the legacy deterministic mode.
|
|
805
|
+
return value === 'interactive' || value === 'deterministic' ? 'interactive' : 'interactive';
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function isIdleCheckpoint(checkpoint: FeatureCheckpoint): boolean {
|
|
809
|
+
return (
|
|
810
|
+
checkpoint.validation_status === 'skipped' &&
|
|
811
|
+
checkpoint.files_changed.length === 0 &&
|
|
812
|
+
checkpoint.net_new_worktree_change === false
|
|
813
|
+
);
|
|
248
814
|
}
|
|
249
815
|
|
|
250
816
|
function normalizeFeatureCheckpoints(frontMatter: Record<string, unknown>): FeatureCheckpoint[] {
|
|
@@ -286,6 +852,17 @@ function normalizeFeatureCheckpoints(frontMatter: Record<string, unknown>): Feat
|
|
|
286
852
|
record.severity === 'critical'
|
|
287
853
|
? record.severity
|
|
288
854
|
: undefined;
|
|
855
|
+
const headSha = typeof record.head_sha === 'string' ? record.head_sha.trim() : '';
|
|
856
|
+
const workspaceFingerprint =
|
|
857
|
+
typeof record.workspace_fingerprint === 'string' ? record.workspace_fingerprint.trim() : '';
|
|
858
|
+
const duplicateOfCheckpointId =
|
|
859
|
+
typeof record.duplicate_of_checkpoint_id === 'string'
|
|
860
|
+
? record.duplicate_of_checkpoint_id.trim()
|
|
861
|
+
: '';
|
|
862
|
+
const netNewWorktreeChange =
|
|
863
|
+
typeof record.net_new_worktree_change === 'boolean'
|
|
864
|
+
? record.net_new_worktree_change
|
|
865
|
+
: undefined;
|
|
289
866
|
checkpoints.push({
|
|
290
867
|
checkpoint_id: checkpointId,
|
|
291
868
|
timestamp,
|
|
@@ -294,6 +871,10 @@ function normalizeFeatureCheckpoints(frontMatter: Record<string, unknown>): Feat
|
|
|
294
871
|
violations,
|
|
295
872
|
severity,
|
|
296
873
|
diff_snapshot: diffSnapshot,
|
|
874
|
+
head_sha: headSha || undefined,
|
|
875
|
+
workspace_fingerprint: workspaceFingerprint || undefined,
|
|
876
|
+
duplicate_of_checkpoint_id: duplicateOfCheckpointId || undefined,
|
|
877
|
+
net_new_worktree_change: netNewWorktreeChange,
|
|
297
878
|
});
|
|
298
879
|
}
|
|
299
880
|
|
|
@@ -345,6 +926,19 @@ function truncateNumber(value: number | null, digits = 2): number | null {
|
|
|
345
926
|
return Math.round(value * factor) / factor;
|
|
346
927
|
}
|
|
347
928
|
|
|
929
|
+
function averageNumber(values: number[]): number | null {
|
|
930
|
+
if (values.length === 0) {
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
933
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function uniqueSorted(values: string[]): string[] {
|
|
937
|
+
return Array.from(new Set(values.filter((value) => value.trim().length > 0))).sort(
|
|
938
|
+
(left, right) => left.localeCompare(right),
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
|
|
348
942
|
function resolveRepoRelativePath(repoRoot: string, relativePath: string): string | null {
|
|
349
943
|
const resolved = path.resolve(repoRoot, relativePath);
|
|
350
944
|
const normalizedRoot = path.resolve(repoRoot);
|
|
@@ -379,6 +973,145 @@ async function readJsonFile<T>(filePath: string): Promise<T | null> {
|
|
|
379
973
|
}
|
|
380
974
|
}
|
|
381
975
|
|
|
976
|
+
function normalizeQuestionRecord(value: unknown): FeatureQuestion | null {
|
|
977
|
+
const record = asRecord(value);
|
|
978
|
+
if (!record) {
|
|
979
|
+
return null;
|
|
980
|
+
}
|
|
981
|
+
const questionId = typeof record.question_id === 'string' ? record.question_id : null;
|
|
982
|
+
const prompt = typeof record.prompt === 'string' ? record.prompt : null;
|
|
983
|
+
const status =
|
|
984
|
+
record.status === 'open' ||
|
|
985
|
+
record.status === 'answered' ||
|
|
986
|
+
record.status === 'expired' ||
|
|
987
|
+
record.status === 'withdrawn'
|
|
988
|
+
? record.status
|
|
989
|
+
: null;
|
|
990
|
+
if (!questionId || !prompt || !status) {
|
|
991
|
+
return null;
|
|
992
|
+
}
|
|
993
|
+
return {
|
|
994
|
+
feature_id: typeof record.feature_id === 'string' ? record.feature_id : undefined,
|
|
995
|
+
question_id: questionId,
|
|
996
|
+
status,
|
|
997
|
+
blocking: record.blocking !== false,
|
|
998
|
+
question_type:
|
|
999
|
+
typeof record.question_type === 'string' ? record.question_type : 'clarification',
|
|
1000
|
+
role: typeof record.role === 'string' ? record.role : 'unknown',
|
|
1001
|
+
phase: typeof record.phase === 'string' ? record.phase : 'unknown',
|
|
1002
|
+
request_action:
|
|
1003
|
+
typeof record.request_action === 'string' ? record.request_action : 'ask_user_input',
|
|
1004
|
+
session_id: typeof record.session_id === 'string' ? record.session_id : undefined,
|
|
1005
|
+
prompt,
|
|
1006
|
+
details: asRecord(record.details) ?? {},
|
|
1007
|
+
expected_answer:
|
|
1008
|
+
record.expected_answer && typeof record.expected_answer === 'object'
|
|
1009
|
+
? (record.expected_answer as FeatureQuestion['expected_answer'])
|
|
1010
|
+
: null,
|
|
1011
|
+
created_at:
|
|
1012
|
+
typeof record.created_at === 'string' ? record.created_at : new Date().toISOString(),
|
|
1013
|
+
expires_at: typeof record.expires_at === 'string' ? record.expires_at : null,
|
|
1014
|
+
answer:
|
|
1015
|
+
typeof record.answer === 'string' || (record.answer && typeof record.answer === 'object')
|
|
1016
|
+
? (record.answer as FeatureQuestion['answer'])
|
|
1017
|
+
: null,
|
|
1018
|
+
answered_at: typeof record.answered_at === 'string' ? record.answered_at : null,
|
|
1019
|
+
answered_by: typeof record.answered_by === 'string' ? record.answered_by : null,
|
|
1020
|
+
resume_status: typeof record.resume_status === 'string' ? record.resume_status : null,
|
|
1021
|
+
resume_phase: typeof record.resume_phase === 'string' ? record.resume_phase : null,
|
|
1022
|
+
ambiguity_id: readQuestionAmbiguityId(asRecord(record.details) ?? {}),
|
|
1023
|
+
obligation_ids: readQuestionObligationIds(asRecord(record.details) ?? {}),
|
|
1024
|
+
intake_context: null,
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
function enrichQuestionRecord(
|
|
1029
|
+
question: FeatureQuestion,
|
|
1030
|
+
feature: FeatureSummary | null,
|
|
1031
|
+
): FeatureQuestion {
|
|
1032
|
+
return {
|
|
1033
|
+
...question,
|
|
1034
|
+
feature_id: feature?.feature_id ?? question.feature_id,
|
|
1035
|
+
intake_context:
|
|
1036
|
+
feature?.intake_status != null
|
|
1037
|
+
? {
|
|
1038
|
+
status: feature.intake_status,
|
|
1039
|
+
bootstrap_manifest_version: feature.bootstrap_manifest_version ?? null,
|
|
1040
|
+
verified_manifest_version: feature.verified_manifest_version ?? null,
|
|
1041
|
+
open_ambiguity_count: feature.open_ambiguity_count ?? 0,
|
|
1042
|
+
last_verified_at: feature.last_verified_at ?? null,
|
|
1043
|
+
promotion_basis: feature.promotion_basis ?? null,
|
|
1044
|
+
}
|
|
1045
|
+
: null,
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
function isOperatorReadyQuestion(
|
|
1050
|
+
feature: FeatureSummary | null,
|
|
1051
|
+
question: FeatureQuestion,
|
|
1052
|
+
): boolean {
|
|
1053
|
+
const baselineReady =
|
|
1054
|
+
question.status === 'open' &&
|
|
1055
|
+
question.blocking &&
|
|
1056
|
+
feature?.status === 'awaiting_input' &&
|
|
1057
|
+
feature.open_question_id === question.question_id;
|
|
1058
|
+
if (!baselineReady) {
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
const resumesIntoIntake =
|
|
1062
|
+
question.resume_status === 'intake' ||
|
|
1063
|
+
question.resume_phase === 'intake' ||
|
|
1064
|
+
question.phase === 'intake';
|
|
1065
|
+
if (!resumesIntoIntake) {
|
|
1066
|
+
return true;
|
|
1067
|
+
}
|
|
1068
|
+
return feature.resume_status === 'intake' && feature.intake_status === 'awaiting_input';
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
function selectOperatorReadyOpenQuestions(
|
|
1072
|
+
feature: FeatureSummary | null,
|
|
1073
|
+
questions: FeatureQuestion[],
|
|
1074
|
+
): FeatureQuestion[] {
|
|
1075
|
+
return questions.filter((question) => isOperatorReadyQuestion(feature, question));
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
export async function readFeatureQuestions(
|
|
1079
|
+
featureId: string,
|
|
1080
|
+
repoRoot = AOP_ROOT,
|
|
1081
|
+
feature: FeatureSummary | null = null,
|
|
1082
|
+
): Promise<FeatureQuestion[]> {
|
|
1083
|
+
const questionsPath = path.join(getFeatureRoot(featureId, repoRoot), 'questions.json');
|
|
1084
|
+
const parsed = await readJsonFile<Record<string, unknown>>(questionsPath);
|
|
1085
|
+
if (!parsed || !Array.isArray(parsed.items)) {
|
|
1086
|
+
return [];
|
|
1087
|
+
}
|
|
1088
|
+
return parsed.items
|
|
1089
|
+
.map((item) => normalizeQuestionRecord(item))
|
|
1090
|
+
.filter((item): item is FeatureQuestion => item !== null)
|
|
1091
|
+
.map((item) => enrichQuestionRecord({ ...item, feature_id: featureId }, feature))
|
|
1092
|
+
.sort((left, right) => left.created_at.localeCompare(right.created_at));
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
export async function readPendingQuestions(repoRoot = AOP_ROOT): Promise<FeatureQuestion[]> {
|
|
1096
|
+
const index = await readFeaturesIndex(repoRoot);
|
|
1097
|
+
const featureIds = new Set<string>([
|
|
1098
|
+
...(index.active ?? []),
|
|
1099
|
+
...(index.blocked ?? []),
|
|
1100
|
+
...(index.merged ?? []),
|
|
1101
|
+
...(index.blocked_queue ?? []).map((entry) => entry.feature_id),
|
|
1102
|
+
]);
|
|
1103
|
+
const questions = await Promise.all(
|
|
1104
|
+
[...featureIds]
|
|
1105
|
+
.sort((a, b) => a.localeCompare(b))
|
|
1106
|
+
.map(async (featureId) => {
|
|
1107
|
+
const feature = await readFeatureState(featureId, repoRoot);
|
|
1108
|
+
const items = await readFeatureQuestions(featureId, repoRoot, feature);
|
|
1109
|
+
return selectOperatorReadyOpenQuestions(feature, items);
|
|
1110
|
+
}),
|
|
1111
|
+
);
|
|
1112
|
+
return questions.flat();
|
|
1113
|
+
}
|
|
1114
|
+
|
|
382
1115
|
function normalizeRuntimeSession(value: unknown): RuntimeSession | undefined {
|
|
383
1116
|
const record = asRecord(value);
|
|
384
1117
|
if (!record) {
|
|
@@ -459,19 +1192,55 @@ function normalizeDepBlocked(index: FeaturesIndex): Array<{
|
|
|
459
1192
|
);
|
|
460
1193
|
}
|
|
461
1194
|
|
|
462
|
-
function
|
|
1195
|
+
function resolveActiveCollision(
|
|
463
1196
|
featureId: string,
|
|
464
|
-
frontMatter: Record<string, unknown>,
|
|
465
1197
|
index: FeaturesIndex,
|
|
466
|
-
|
|
467
|
-
):
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
1198
|
+
): FeatureSummary['collision'] {
|
|
1199
|
+
const records = Array.isArray(index.collision_records) ? index.collision_records : [];
|
|
1200
|
+
// Find the LATEST collision record for this feature to check current state.
|
|
1201
|
+
// Older records may still be 'rejected' while the latest is already resolved.
|
|
1202
|
+
const featureRecords = records
|
|
1203
|
+
.filter((r) => r.feature_id === featureId)
|
|
1204
|
+
.sort((a, b) => (b.detected_at ?? '').localeCompare(a.detected_at ?? ''));
|
|
1205
|
+
const latest = featureRecords[0];
|
|
1206
|
+
if (!latest) {
|
|
1207
|
+
return null;
|
|
1208
|
+
}
|
|
1209
|
+
// Only report a collision if the most recent record is still unresolved.
|
|
1210
|
+
if (latest.resolution !== 'rejected' && latest.resolution !== 'blocked') {
|
|
1211
|
+
return null;
|
|
1212
|
+
}
|
|
1213
|
+
const record = latest;
|
|
1214
|
+
const withIds = Array.isArray(record.with_feature_ids) ? record.with_feature_ids : [];
|
|
1215
|
+
const filePaths: string[] = [];
|
|
1216
|
+
if (record.collisions?.files) {
|
|
1217
|
+
for (const entry of record.collisions.files) {
|
|
1218
|
+
if (Array.isArray(entry.paths)) {
|
|
1219
|
+
filePaths.push(...entry.paths);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
return {
|
|
1224
|
+
with_feature_ids: withIds,
|
|
1225
|
+
collision_fingerprint: record.collision_fingerprint ?? '',
|
|
1226
|
+
file_paths: filePaths,
|
|
1227
|
+
detected_at: record.detected_at ?? '',
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
function normalizeFeatureSummary(
|
|
1232
|
+
featureId: string,
|
|
1233
|
+
frontMatter: Record<string, unknown>,
|
|
1234
|
+
index: FeaturesIndex,
|
|
1235
|
+
repoRoot = AOP_ROOT,
|
|
1236
|
+
): FeatureSummary {
|
|
1237
|
+
const status = typeof frontMatter.status === 'string' ? frontMatter.status : 'unknown';
|
|
1238
|
+
const blockedQueue = Array.isArray(index.blocked_queue)
|
|
1239
|
+
? index.blocked_queue.map((entry) => entry.feature_id)
|
|
1240
|
+
: [];
|
|
1241
|
+
const phase = index.merged?.includes(featureId)
|
|
1242
|
+
? 'merged'
|
|
1243
|
+
: index.blocked?.includes(featureId) || blockedQueue.includes(featureId)
|
|
475
1244
|
? 'blocked'
|
|
476
1245
|
: (STATUS_TO_PHASE[status] ?? (index.active?.includes(featureId) ? 'planning' : 'unknown'));
|
|
477
1246
|
const branchRaw =
|
|
@@ -484,6 +1253,10 @@ function normalizeFeatureSummary(
|
|
|
484
1253
|
const gates =
|
|
485
1254
|
rawGates && typeof rawGates === 'object'
|
|
486
1255
|
? {
|
|
1256
|
+
plan:
|
|
1257
|
+
typeof (rawGates as Record<string, unknown>).plan === 'string'
|
|
1258
|
+
? ((rawGates as Record<string, unknown>).plan as string)
|
|
1259
|
+
: undefined,
|
|
487
1260
|
fast:
|
|
488
1261
|
typeof (rawGates as Record<string, unknown>).fast === 'string'
|
|
489
1262
|
? ((rawGates as Record<string, unknown>).fast as string)
|
|
@@ -526,6 +1299,13 @@ function normalizeFeatureSummary(
|
|
|
526
1299
|
: typeof revisionOfRaw === 'string'
|
|
527
1300
|
? Number.parseInt(revisionOfRaw, 10)
|
|
528
1301
|
: null;
|
|
1302
|
+
const humanInput =
|
|
1303
|
+
frontMatter.human_input && typeof frontMatter.human_input === 'object'
|
|
1304
|
+
? (frontMatter.human_input as Record<string, unknown>)
|
|
1305
|
+
: null;
|
|
1306
|
+
const intakeSummary = normalizeFeatureIntakeSummary(frontMatter);
|
|
1307
|
+
const mergeRepairSummary = normalizeMergeRepairSummary(frontMatter);
|
|
1308
|
+
const recovery = normalizeFeatureRecoverySummary(frontMatter);
|
|
529
1309
|
return {
|
|
530
1310
|
id: featureId,
|
|
531
1311
|
feature_id: featureId,
|
|
@@ -551,13 +1331,35 @@ function normalizeFeatureSummary(
|
|
|
551
1331
|
: path.join(repoRoot, '.worktrees', featureId),
|
|
552
1332
|
last_updated:
|
|
553
1333
|
typeof frontMatter.last_updated === 'string' ? frontMatter.last_updated : undefined,
|
|
1334
|
+
phase_entered_at:
|
|
1335
|
+
typeof frontMatter.phase_entered_at === 'string' ? frontMatter.phase_entered_at : undefined,
|
|
554
1336
|
status_reason:
|
|
555
1337
|
typeof frontMatter.status_reason === 'string' ? frontMatter.status_reason : undefined,
|
|
1338
|
+
recovery,
|
|
556
1339
|
activity_state: inferActivityState(frontMatter, phase),
|
|
1340
|
+
open_question_id:
|
|
1341
|
+
typeof humanInput?.open_question_id === 'string' ? humanInput.open_question_id : null,
|
|
1342
|
+
open_question_count:
|
|
1343
|
+
typeof humanInput?.open_question_count === 'number'
|
|
1344
|
+
? humanInput.open_question_count
|
|
1345
|
+
: undefined,
|
|
1346
|
+
awaiting_since:
|
|
1347
|
+
typeof humanInput?.awaiting_since === 'string' ? humanInput.awaiting_since : null,
|
|
1348
|
+
requested_by_role:
|
|
1349
|
+
typeof humanInput?.requested_by_role === 'string' ? humanInput.requested_by_role : null,
|
|
1350
|
+
resume_status: typeof humanInput?.resume_status === 'string' ? humanInput.resume_status : null,
|
|
1351
|
+
intake_status: intakeSummary?.status,
|
|
1352
|
+
bootstrap_manifest_version: intakeSummary?.bootstrap_manifest_version ?? null,
|
|
1353
|
+
verified_manifest_version: intakeSummary?.verified_manifest_version ?? null,
|
|
1354
|
+
open_ambiguity_count: intakeSummary?.open_ambiguity_count ?? 0,
|
|
1355
|
+
last_verified_at: intakeSummary?.last_verified_at ?? null,
|
|
1356
|
+
promotion_basis: intakeSummary?.promotion_basis ?? null,
|
|
1357
|
+
merge_repair: mergeRepairSummary,
|
|
557
1358
|
activity_last_event_at:
|
|
558
1359
|
typeof frontMatter.activity_last_event_at === 'string'
|
|
559
1360
|
? frontMatter.activity_last_event_at
|
|
560
1361
|
: undefined,
|
|
1362
|
+
last_meaningful_change_at: lastMeaningfulChange(frontMatter),
|
|
561
1363
|
activity_detected_via:
|
|
562
1364
|
typeof frontMatter.activity_detected_via === 'string'
|
|
563
1365
|
? (frontMatter.activity_detected_via as FeatureSummary['activity_detected_via'])
|
|
@@ -566,6 +1368,7 @@ function normalizeFeatureSummary(
|
|
|
566
1368
|
frontMatter.pr && typeof frontMatter.pr === 'object'
|
|
567
1369
|
? (frontMatter.pr as FeatureSummary['pr'])
|
|
568
1370
|
: null,
|
|
1371
|
+
collision: resolveActiveCollision(featureId, index),
|
|
569
1372
|
};
|
|
570
1373
|
}
|
|
571
1374
|
|
|
@@ -646,6 +1449,9 @@ export async function readFeaturesIndex(repoRoot = AOP_ROOT): Promise<FeaturesIn
|
|
|
646
1449
|
if (Array.isArray(parsed.dep_blocked)) {
|
|
647
1450
|
normalized.dep_blocked = parsed.dep_blocked as FeaturesIndex['dep_blocked'];
|
|
648
1451
|
}
|
|
1452
|
+
if (Array.isArray(parsed.collision_records)) {
|
|
1453
|
+
normalized.collision_records = parsed.collision_records as FeaturesIndex['collision_records'];
|
|
1454
|
+
}
|
|
649
1455
|
const runtimeSessions = normalizeRuntimeSession(parsed.runtime_sessions);
|
|
650
1456
|
if (runtimeSessions) {
|
|
651
1457
|
normalized.runtime_sessions = runtimeSessions;
|
|
@@ -708,6 +1514,14 @@ export async function readFeaturePlan(
|
|
|
708
1514
|
}
|
|
709
1515
|
}
|
|
710
1516
|
|
|
1517
|
+
export async function readFeatureIntakeReview(
|
|
1518
|
+
featureId: string,
|
|
1519
|
+
repoRoot = AOP_ROOT,
|
|
1520
|
+
): Promise<FeatureIntakeReviewSummary | null> {
|
|
1521
|
+
const reviewPath = path.join(getFeatureRoot(featureId, repoRoot), 'intake.review.json');
|
|
1522
|
+
return normalizeIntakeReviewSummary(await readJsonFile<Record<string, unknown>>(reviewPath));
|
|
1523
|
+
}
|
|
1524
|
+
|
|
711
1525
|
export async function readFeatureCost(
|
|
712
1526
|
featureId: string,
|
|
713
1527
|
repoRoot = AOP_ROOT,
|
|
@@ -1091,7 +1905,7 @@ function parseStateLogBody(body: string): FeatureLogEntry[] {
|
|
|
1091
1905
|
|
|
1092
1906
|
const entries: FeatureLogEntry[] = [];
|
|
1093
1907
|
for (const line of lines) {
|
|
1094
|
-
const parsed = line.match(/^\[(.+?)
|
|
1908
|
+
const parsed = line.match(/^\[(.+?)]\s*(.+)$/);
|
|
1095
1909
|
if (parsed) {
|
|
1096
1910
|
entries.push({
|
|
1097
1911
|
timestamp: parsed[1],
|
|
@@ -1128,7 +1942,7 @@ function parseDecisionEntries(raw: string): AgentLogEntry[] {
|
|
|
1128
1942
|
if (trimmed.length === 0 || trimmed.startsWith('#')) {
|
|
1129
1943
|
continue;
|
|
1130
1944
|
}
|
|
1131
|
-
const match = trimmed.match(/^-?\s*([0-9TZ:.-]{10,})\s+\[([^\]]+)
|
|
1945
|
+
const match = trimmed.match(/^-?\s*([0-9TZ:.-]{10,})\s+\[([^\]]+)]\s+([\s\S]+)$/);
|
|
1132
1946
|
if (match) {
|
|
1133
1947
|
const timestampRaw = match[1];
|
|
1134
1948
|
const actor = match[2].trim();
|
|
@@ -1217,7 +2031,9 @@ export async function readFeatureLog(featureId: string, repoRoot = AOP_ROOT): Pr
|
|
|
1217
2031
|
}
|
|
1218
2032
|
|
|
1219
2033
|
function normalizeRawLogRole(value: unknown): RawLogFileMeta['role'] | null {
|
|
1220
|
-
return value === 'planner' || value === 'builder' || value === 'qa'
|
|
2034
|
+
return value === 'planner' || value === 'builder' || value === 'qa' || value === 'reconciler'
|
|
2035
|
+
? value
|
|
2036
|
+
: null;
|
|
1221
2037
|
}
|
|
1222
2038
|
|
|
1223
2039
|
export async function listRawLogFiles(
|
|
@@ -1282,6 +2098,65 @@ export async function readRawLogFile(
|
|
|
1282
2098
|
}
|
|
1283
2099
|
}
|
|
1284
2100
|
|
|
2101
|
+
function normalizeLiveAgentOutputStatus(value: unknown): LiveAgentOutputSnapshot['status'] {
|
|
2102
|
+
return value === 'running' || value === 'completed' || value === 'failed' ? value : 'idle';
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
function normalizeExecutionMode(value: unknown): LiveAgentOutputSnapshot['execution_mode'] | null {
|
|
2106
|
+
return value === 'interactive' || value === 'deterministic' ? value : null;
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
export async function readLiveAgentOutputSnapshots(
|
|
2110
|
+
featureId: string,
|
|
2111
|
+
repoRoot = AOP_ROOT,
|
|
2112
|
+
options: LiveAgentOutputReadOptions = {},
|
|
2113
|
+
): Promise<LiveAgentOutputSnapshot[]> {
|
|
2114
|
+
const liveDir = path.join(getFeatureRoot(featureId, repoRoot), 'logs', 'live');
|
|
2115
|
+
return await Promise.all(
|
|
2116
|
+
TRACKED_AGENT_ROLES.map(async (role) => {
|
|
2117
|
+
const metadataPath = path.join(liveDir, `${role}.json`);
|
|
2118
|
+
const contentPath = path.join(liveDir, `${role}.txt`);
|
|
2119
|
+
const [metadataRaw, content, contentStat] = await Promise.all([
|
|
2120
|
+
readFile(metadataPath, 'utf8').catch(() => null),
|
|
2121
|
+
readFile(contentPath, 'utf8').catch(() => ''),
|
|
2122
|
+
stat(contentPath).catch(() => null),
|
|
2123
|
+
]);
|
|
2124
|
+
|
|
2125
|
+
let metadata: Record<string, unknown> = {};
|
|
2126
|
+
if (metadataRaw) {
|
|
2127
|
+
try {
|
|
2128
|
+
const parsed = JSON.parse(metadataRaw) as unknown;
|
|
2129
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
2130
|
+
metadata = parsed as Record<string, unknown>;
|
|
2131
|
+
}
|
|
2132
|
+
} catch {
|
|
2133
|
+
metadata = {};
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
return {
|
|
2138
|
+
role,
|
|
2139
|
+
session_id: typeof metadata.session_id === 'string' ? metadata.session_id : null,
|
|
2140
|
+
provider: typeof metadata.provider === 'string' ? metadata.provider : null,
|
|
2141
|
+
model: typeof metadata.model === 'string' ? metadata.model : null,
|
|
2142
|
+
execution_mode: normalizeExecutionMode(metadata.execution_mode),
|
|
2143
|
+
status: normalizeLiveAgentOutputStatus(metadata.status),
|
|
2144
|
+
started_at: typeof metadata.started_at === 'string' ? metadata.started_at : null,
|
|
2145
|
+
ended_at: typeof metadata.ended_at === 'string' ? metadata.ended_at : null,
|
|
2146
|
+
updated_at: contentStat?.mtime.toISOString() ?? null,
|
|
2147
|
+
size_bytes: contentStat?.size ?? 0,
|
|
2148
|
+
content: tailTextByLines(content, options.tailLines),
|
|
2149
|
+
} satisfies LiveAgentOutputSnapshot;
|
|
2150
|
+
}),
|
|
2151
|
+
);
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
function summarizeLiveAgentOutputSnapshots(
|
|
2155
|
+
snapshots: LiveAgentOutputSnapshot[],
|
|
2156
|
+
): LiveAgentOutputStatusSnapshot[] {
|
|
2157
|
+
return snapshots.map(({ content: _content, ...snapshot }) => snapshot);
|
|
2158
|
+
}
|
|
2159
|
+
|
|
1285
2160
|
interface WorkerTimelineReadOptions {
|
|
1286
2161
|
role?: string | null;
|
|
1287
2162
|
valid?: 'all' | 'valid' | 'invalid';
|
|
@@ -1536,7 +2411,7 @@ export async function readInteractivePerformanceMetrics(
|
|
|
1536
2411
|
const averageFilesChanged = checkpointCount > 0 ? filesChangedTotal / checkpointCount : null;
|
|
1537
2412
|
|
|
1538
2413
|
return {
|
|
1539
|
-
updated_at:
|
|
2414
|
+
updated_at: parsed.updated_at,
|
|
1540
2415
|
checkpoint_count: checkpointCount,
|
|
1541
2416
|
valid_count: validCount,
|
|
1542
2417
|
invalid_count: invalidCount,
|
|
@@ -1595,28 +2470,109 @@ async function readFlakySuspects(repoRoot = AOP_ROOT): Promise<string[]> {
|
|
|
1595
2470
|
return [];
|
|
1596
2471
|
}
|
|
1597
2472
|
|
|
2473
|
+
const ORGANIZER_STATUS_FALLBACK: OrganizerStatusProjection = {
|
|
2474
|
+
available: false,
|
|
2475
|
+
generated_at: null,
|
|
2476
|
+
ready_set: [],
|
|
2477
|
+
deferred_set: [],
|
|
2478
|
+
blocked_set: [],
|
|
2479
|
+
};
|
|
2480
|
+
|
|
2481
|
+
export async function readOrganizerStatus(
|
|
2482
|
+
repoRoot = AOP_ROOT,
|
|
2483
|
+
instanceId?: string | null,
|
|
2484
|
+
): Promise<OrganizerStatusProjection> {
|
|
2485
|
+
if (!instanceId) {
|
|
2486
|
+
return ORGANIZER_STATUS_FALLBACK;
|
|
2487
|
+
}
|
|
2488
|
+
const artifactPath = path.join(
|
|
2489
|
+
repoRoot,
|
|
2490
|
+
'.aop',
|
|
2491
|
+
'runtime',
|
|
2492
|
+
instanceId,
|
|
2493
|
+
'organizer-ordering.json',
|
|
2494
|
+
);
|
|
2495
|
+
const parsed = await readJsonFile<Record<string, unknown>>(artifactPath);
|
|
2496
|
+
if (!parsed) {
|
|
2497
|
+
return ORGANIZER_STATUS_FALLBACK;
|
|
2498
|
+
}
|
|
2499
|
+
const generatedAt = typeof parsed.generated_at === 'string' ? parsed.generated_at : null;
|
|
2500
|
+
const readySet = Array.isArray(parsed.ready_set)
|
|
2501
|
+
? parsed.ready_set.filter((item): item is string => typeof item === 'string')
|
|
2502
|
+
: [];
|
|
2503
|
+
const deferredSet = Array.isArray(parsed.deferred_set)
|
|
2504
|
+
? parsed.deferred_set.flatMap((item) => {
|
|
2505
|
+
if (
|
|
2506
|
+
item !== null &&
|
|
2507
|
+
typeof item === 'object' &&
|
|
2508
|
+
typeof (item as Record<string, unknown>).feature_id === 'string' &&
|
|
2509
|
+
Array.isArray((item as Record<string, unknown>).blocked_by) &&
|
|
2510
|
+
typeof (item as Record<string, unknown>).reason === 'string'
|
|
2511
|
+
) {
|
|
2512
|
+
const entry = item as Record<string, unknown>;
|
|
2513
|
+
return [
|
|
2514
|
+
{
|
|
2515
|
+
feature_id: entry.feature_id as string,
|
|
2516
|
+
blocked_by: (entry.blocked_by as unknown[]).filter(
|
|
2517
|
+
(b): b is string => typeof b === 'string',
|
|
2518
|
+
),
|
|
2519
|
+
reason: entry.reason as string,
|
|
2520
|
+
},
|
|
2521
|
+
];
|
|
2522
|
+
}
|
|
2523
|
+
return [];
|
|
2524
|
+
})
|
|
2525
|
+
: [];
|
|
2526
|
+
const blockedSet = Array.isArray(parsed.blocked_set)
|
|
2527
|
+
? parsed.blocked_set.flatMap((item) => {
|
|
2528
|
+
if (
|
|
2529
|
+
item !== null &&
|
|
2530
|
+
typeof item === 'object' &&
|
|
2531
|
+
typeof (item as Record<string, unknown>).feature_id === 'string' &&
|
|
2532
|
+
typeof (item as Record<string, unknown>).reason === 'string'
|
|
2533
|
+
) {
|
|
2534
|
+
const entry = item as Record<string, unknown>;
|
|
2535
|
+
return [{ feature_id: entry.feature_id as string, reason: entry.reason as string }];
|
|
2536
|
+
}
|
|
2537
|
+
return [];
|
|
2538
|
+
})
|
|
2539
|
+
: [];
|
|
2540
|
+
return {
|
|
2541
|
+
available: true,
|
|
2542
|
+
generated_at: generatedAt,
|
|
2543
|
+
ready_set: readySet,
|
|
2544
|
+
deferred_set: deferredSet,
|
|
2545
|
+
blocked_set: blockedSet,
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
|
|
1598
2549
|
export async function readDashboardStatus(repoRoot = AOP_ROOT): Promise<DashboardStatusPayload> {
|
|
1599
2550
|
const index = await readFeaturesIndex(repoRoot);
|
|
2551
|
+
const budgetConfig = parseBudgetConfig(await readPolicyYaml(repoRoot));
|
|
1600
2552
|
const allFeatureIds = new Set<string>([
|
|
1601
2553
|
...(index.active ?? []),
|
|
1602
2554
|
...(index.blocked ?? []),
|
|
1603
2555
|
...(index.merged ?? []),
|
|
1604
2556
|
...(index.blocked_queue ?? []).map((entry) => entry.feature_id),
|
|
1605
2557
|
]);
|
|
2558
|
+
const dependencyBlockedIds = new Set(normalizeDepBlocked(index).map((entry) => entry.feature_id));
|
|
1606
2559
|
|
|
1607
2560
|
const features: FeatureSummary[] = [];
|
|
2561
|
+
let totalTokens = 0;
|
|
1608
2562
|
let totalCostUsd = 0;
|
|
2563
|
+
const nowMs = Date.now();
|
|
1609
2564
|
for (const featureId of [...allFeatureIds].sort((a, b) => a.localeCompare(b))) {
|
|
2565
|
+
let feature: FeatureSummary;
|
|
1610
2566
|
const statePath = path.join(getFeatureRoot(featureId, repoRoot), 'state.md');
|
|
1611
2567
|
try {
|
|
1612
2568
|
const raw = await readFile(statePath, 'utf-8');
|
|
1613
2569
|
const { frontMatter } = parseFrontMatter(raw);
|
|
1614
|
-
|
|
2570
|
+
feature = normalizeFeatureSummary(featureId, frontMatter, index, repoRoot);
|
|
1615
2571
|
} catch {
|
|
1616
2572
|
const blockedQueue = Array.isArray(index.blocked_queue)
|
|
1617
2573
|
? index.blocked_queue.map((entry) => entry.feature_id)
|
|
1618
2574
|
: [];
|
|
1619
|
-
|
|
2575
|
+
feature = {
|
|
1620
2576
|
id: featureId,
|
|
1621
2577
|
feature_id: featureId,
|
|
1622
2578
|
status: index.merged?.includes(featureId)
|
|
@@ -1634,30 +2590,1412 @@ export async function readDashboardStatus(repoRoot = AOP_ROOT): Promise<Dashboar
|
|
|
1634
2590
|
branch: null,
|
|
1635
2591
|
worktree_path: null,
|
|
1636
2592
|
pr: null,
|
|
1637
|
-
}
|
|
2593
|
+
};
|
|
1638
2594
|
}
|
|
1639
2595
|
|
|
1640
2596
|
const cost = await readFeatureCost(featureId, repoRoot);
|
|
2597
|
+
const enriched = enrichFeatureSummary(feature, {
|
|
2598
|
+
cost,
|
|
2599
|
+
budgetConfig,
|
|
2600
|
+
dependencyBlocked: dependencyBlockedIds.has(featureId),
|
|
2601
|
+
nowMs,
|
|
2602
|
+
});
|
|
2603
|
+
features.push(enriched);
|
|
1641
2604
|
if (cost) {
|
|
2605
|
+
totalTokens += cost.tokens_used;
|
|
1642
2606
|
totalCostUsd += cost.estimated_cost_usd;
|
|
1643
2607
|
}
|
|
1644
2608
|
}
|
|
1645
2609
|
|
|
1646
2610
|
const depBlocked = normalizeDepBlocked(index);
|
|
2611
|
+
const pendingQuestions = await readPendingQuestions(repoRoot);
|
|
1647
2612
|
return {
|
|
1648
2613
|
index,
|
|
1649
2614
|
features,
|
|
2615
|
+
pending_questions: {
|
|
2616
|
+
count: pendingQuestions.length,
|
|
2617
|
+
items: pendingQuestions.slice(0, 12),
|
|
2618
|
+
},
|
|
1650
2619
|
runtime: index.runtime_sessions,
|
|
1651
2620
|
lock_map: normalizeLockMap(index),
|
|
1652
2621
|
dep_blocked: depBlocked,
|
|
1653
2622
|
flaky_suspects: await readFlakySuspects(repoRoot),
|
|
1654
2623
|
metrics: {
|
|
1655
|
-
|
|
2624
|
+
total_tokens_today: totalTokens,
|
|
2625
|
+
total_cost_today_usd:
|
|
2626
|
+
budgetConfig.token_cost_conversion != null ? Number(totalCostUsd.toFixed(4)) : null,
|
|
1656
2627
|
merge_histogram_14d: buildMergeHistogram14d(features),
|
|
1657
2628
|
},
|
|
1658
2629
|
};
|
|
1659
2630
|
}
|
|
1660
2631
|
|
|
2632
|
+
export async function readFeatureTriage(
|
|
2633
|
+
featureId: string,
|
|
2634
|
+
repoRoot = AOP_ROOT,
|
|
2635
|
+
): Promise<FeatureTriageDetail | null> {
|
|
2636
|
+
const index = await readFeaturesIndex(repoRoot);
|
|
2637
|
+
const feature = await readFeatureState(featureId, repoRoot);
|
|
2638
|
+
if (!feature) {
|
|
2639
|
+
return null;
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
const budgetConfig = parseBudgetConfig(await readPolicyYaml(repoRoot));
|
|
2643
|
+
const [cost, reviewBrief, intakeReview] = await Promise.all([
|
|
2644
|
+
readFeatureCost(featureId, repoRoot),
|
|
2645
|
+
readFeatureReviewBrief(featureId, repoRoot),
|
|
2646
|
+
readFeatureIntakeReview(featureId, repoRoot),
|
|
2647
|
+
]);
|
|
2648
|
+
const dependencySummary =
|
|
2649
|
+
normalizeDepBlocked(index).find((entry) => entry.feature_id === featureId)
|
|
2650
|
+
?.depends_on_unresolved ?? [];
|
|
2651
|
+
const enriched = enrichFeatureSummary(feature, {
|
|
2652
|
+
cost,
|
|
2653
|
+
budgetConfig,
|
|
2654
|
+
dependencyBlocked: dependencySummary.length > 0,
|
|
2655
|
+
});
|
|
2656
|
+
const questions = await readFeatureQuestions(featureId, repoRoot, enriched);
|
|
2657
|
+
|
|
2658
|
+
return {
|
|
2659
|
+
feature: enriched,
|
|
2660
|
+
why_attention: enriched.attention_reasons ?? [],
|
|
2661
|
+
blocker_summary: summarizeBlocker(enriched, dependencySummary),
|
|
2662
|
+
open_questions: selectOperatorReadyOpenQuestions(enriched, questions),
|
|
2663
|
+
dependency_summary: dependencySummary,
|
|
2664
|
+
usage: {
|
|
2665
|
+
tokens_used: cost?.tokens_used ?? null,
|
|
2666
|
+
budget_limit_tokens: budgetConfig.per_feature_limit_tokens,
|
|
2667
|
+
derived_cost_usd:
|
|
2668
|
+
budgetConfig.token_cost_conversion != null
|
|
2669
|
+
? deriveCostFromTokens(cost?.tokens_used ?? null, budgetConfig.token_cost_conversion)
|
|
2670
|
+
: null,
|
|
2671
|
+
budget_state: enriched.budget_state,
|
|
2672
|
+
},
|
|
2673
|
+
intake:
|
|
2674
|
+
enriched.intake_status != null
|
|
2675
|
+
? {
|
|
2676
|
+
summary: {
|
|
2677
|
+
status: enriched.intake_status,
|
|
2678
|
+
bootstrap_manifest_version: enriched.bootstrap_manifest_version ?? null,
|
|
2679
|
+
verified_manifest_version: enriched.verified_manifest_version ?? null,
|
|
2680
|
+
open_ambiguity_count: enriched.open_ambiguity_count ?? 0,
|
|
2681
|
+
last_verified_at: enriched.last_verified_at ?? null,
|
|
2682
|
+
promotion_basis: enriched.promotion_basis ?? null,
|
|
2683
|
+
},
|
|
2684
|
+
review: intakeReview,
|
|
2685
|
+
}
|
|
2686
|
+
: null,
|
|
2687
|
+
review_snapshot:
|
|
2688
|
+
reviewBrief?.generated_at || enriched.pr
|
|
2689
|
+
? {
|
|
2690
|
+
pr_url: enriched.pr?.url ?? null,
|
|
2691
|
+
ci_status: enriched.pr?.ci_status ?? null,
|
|
2692
|
+
review_decision: enriched.pr?.review_decision ?? null,
|
|
2693
|
+
merge_ready: enriched.pr?.merge_ready ?? null,
|
|
2694
|
+
}
|
|
2695
|
+
: undefined,
|
|
2696
|
+
};
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2699
|
+
function parsePlanFiles(plan: Record<string, unknown> | null): string[] {
|
|
2700
|
+
if (!plan) {
|
|
2701
|
+
return [];
|
|
2702
|
+
}
|
|
2703
|
+
if (Array.isArray(plan.files)) {
|
|
2704
|
+
return uniqueSorted(plan.files.filter((item): item is string => typeof item === 'string'));
|
|
2705
|
+
}
|
|
2706
|
+
if (!plan.files || typeof plan.files !== 'object') {
|
|
2707
|
+
return [];
|
|
2708
|
+
}
|
|
2709
|
+
const files = plan.files as Record<string, unknown>;
|
|
2710
|
+
return uniqueSorted([
|
|
2711
|
+
...(Array.isArray(files.create)
|
|
2712
|
+
? files.create.filter((item): item is string => typeof item === 'string')
|
|
2713
|
+
: []),
|
|
2714
|
+
...(Array.isArray(files.modify)
|
|
2715
|
+
? files.modify.filter((item): item is string => typeof item === 'string')
|
|
2716
|
+
: []),
|
|
2717
|
+
...(Array.isArray(files.delete)
|
|
2718
|
+
? files.delete.filter((item): item is string => typeof item === 'string')
|
|
2719
|
+
: []),
|
|
2720
|
+
]);
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
export async function readFeatureFiles(
|
|
2724
|
+
featureId: string,
|
|
2725
|
+
repoRoot = AOP_ROOT,
|
|
2726
|
+
): Promise<FeatureFileIndex | null> {
|
|
2727
|
+
const feature = await readFeatureState(featureId, repoRoot);
|
|
2728
|
+
if (!feature) {
|
|
2729
|
+
return null;
|
|
2730
|
+
}
|
|
2731
|
+
const [plan, diff, checkpoints] = await Promise.all([
|
|
2732
|
+
readFeaturePlan(featureId, repoRoot),
|
|
2733
|
+
readFeatureDiff(featureId, repoRoot),
|
|
2734
|
+
readFeatureCheckpoints(featureId, repoRoot),
|
|
2735
|
+
]);
|
|
2736
|
+
const planFiles = parsePlanFiles(plan);
|
|
2737
|
+
const diffFiles = uniqueSorted(parseUnifiedDiff(diff).map((file) => file.filePath));
|
|
2738
|
+
const checkpointFiles = uniqueSorted(
|
|
2739
|
+
(checkpoints?.checkpoints ?? []).flatMap((checkpoint) => checkpoint.files_changed),
|
|
2740
|
+
);
|
|
2741
|
+
|
|
2742
|
+
return {
|
|
2743
|
+
feature_id: featureId,
|
|
2744
|
+
plan_files: planFiles,
|
|
2745
|
+
diff_files: diffFiles,
|
|
2746
|
+
checkpoint_files: checkpointFiles,
|
|
2747
|
+
all_files: uniqueSorted([...planFiles, ...diffFiles, ...checkpointFiles]),
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2751
|
+
async function buildFeatureCollisionRadar(
|
|
2752
|
+
featureId: string,
|
|
2753
|
+
repoRoot = AOP_ROOT,
|
|
2754
|
+
): Promise<CollisionRadarSnapshot | null> {
|
|
2755
|
+
const [currentFiles, index] = await Promise.all([
|
|
2756
|
+
readFeatureFiles(featureId, repoRoot),
|
|
2757
|
+
readFeaturesIndex(repoRoot),
|
|
2758
|
+
]);
|
|
2759
|
+
|
|
2760
|
+
if (!currentFiles) {
|
|
2761
|
+
return null;
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
const competingFeatures = index.active.filter((otherFeatureId) => otherFeatureId !== featureId);
|
|
2765
|
+
|
|
2766
|
+
const competingFileMaps = await Promise.all(
|
|
2767
|
+
competingFeatures.map(async (otherFeatureId) => {
|
|
2768
|
+
const files = await readFeatureFiles(otherFeatureId, repoRoot);
|
|
2769
|
+
return [otherFeatureId, files?.all_files ?? []] as const;
|
|
2770
|
+
}),
|
|
2771
|
+
);
|
|
2772
|
+
|
|
2773
|
+
return buildCollisionRadar(currentFiles.all_files, Object.fromEntries(competingFileMaps));
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
export async function readGateHistory(
|
|
2777
|
+
featureId: string,
|
|
2778
|
+
repoRoot = AOP_ROOT,
|
|
2779
|
+
): Promise<GateHistoryEntry[]> {
|
|
2780
|
+
const evidenceDir = path.join(getFeatureRoot(featureId, repoRoot), 'evidence');
|
|
2781
|
+
try {
|
|
2782
|
+
const files = (await readdir(evidenceDir))
|
|
2783
|
+
.filter(
|
|
2784
|
+
(name) =>
|
|
2785
|
+
/^gate-(fast|full|merge)-/.test(name) || /^latest-(fast|full|merge)\.json$/.test(name),
|
|
2786
|
+
)
|
|
2787
|
+
.sort((left, right) => left.localeCompare(right));
|
|
2788
|
+
const history: GateHistoryEntry[] = [];
|
|
2789
|
+
for (const file of files) {
|
|
2790
|
+
const parsed = await readJsonFile<Record<string, unknown>>(path.join(evidenceDir, file));
|
|
2791
|
+
if (!parsed) {
|
|
2792
|
+
continue;
|
|
2793
|
+
}
|
|
2794
|
+
const steps = Array.isArray(parsed.step_results) ? parsed.step_results : [];
|
|
2795
|
+
const stepTimestamps = steps
|
|
2796
|
+
.map((step) => asRecord(step))
|
|
2797
|
+
.flatMap((step) => [
|
|
2798
|
+
typeof step?.started_at === 'string' ? Date.parse(step.started_at) : Number.NaN,
|
|
2799
|
+
typeof step?.finished_at === 'string' ? Date.parse(step.finished_at) : Number.NaN,
|
|
2800
|
+
])
|
|
2801
|
+
.filter((value): value is number => Number.isFinite(value));
|
|
2802
|
+
const durationMs =
|
|
2803
|
+
stepTimestamps.length >= 2
|
|
2804
|
+
? Math.max(...stepTimestamps) - Math.min(...stepTimestamps)
|
|
2805
|
+
: null;
|
|
2806
|
+
history.push({
|
|
2807
|
+
id: `${featureId}:${file}`,
|
|
2808
|
+
file,
|
|
2809
|
+
mode: typeof parsed.mode === 'string' ? parsed.mode : 'unknown',
|
|
2810
|
+
profile: typeof parsed.profile === 'string' ? parsed.profile : null,
|
|
2811
|
+
overall: typeof parsed.overall === 'string' ? parsed.overall : 'unknown',
|
|
2812
|
+
finished_at: typeof parsed.finished_at === 'string' ? parsed.finished_at : null,
|
|
2813
|
+
step_count: steps.length,
|
|
2814
|
+
failing_step_count: steps.filter((step) => {
|
|
2815
|
+
const record = asRecord(step);
|
|
2816
|
+
return typeof record?.exit_code === 'number' && record.exit_code !== 0;
|
|
2817
|
+
}).length,
|
|
2818
|
+
duration_ms: durationMs,
|
|
2819
|
+
steps: steps.map((step) => {
|
|
2820
|
+
const record = asRecord(step);
|
|
2821
|
+
const outputTail = Array.isArray(record?.output_tail)
|
|
2822
|
+
? record.output_tail.filter((item): item is string => typeof item === 'string')
|
|
2823
|
+
: [];
|
|
2824
|
+
return {
|
|
2825
|
+
name: typeof record?.name === 'string' ? record.name : 'unknown',
|
|
2826
|
+
status:
|
|
2827
|
+
record?.status === 'pass' || record?.status === 'fail' ? record.status : 'unknown',
|
|
2828
|
+
output_tail: outputTail,
|
|
2829
|
+
};
|
|
2830
|
+
}),
|
|
2831
|
+
});
|
|
2832
|
+
}
|
|
2833
|
+
return history.sort((left, right) => {
|
|
2834
|
+
const leftMs = left.finished_at ? Date.parse(left.finished_at) : Number.NaN;
|
|
2835
|
+
const rightMs = right.finished_at ? Date.parse(right.finished_at) : Number.NaN;
|
|
2836
|
+
const safeLeft = Number.isNaN(leftMs) ? 0 : leftMs;
|
|
2837
|
+
const safeRight = Number.isNaN(rightMs) ? 0 : rightMs;
|
|
2838
|
+
return safeRight - safeLeft;
|
|
2839
|
+
});
|
|
2840
|
+
} catch {
|
|
2841
|
+
return [];
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
export async function readFeatureGenealogy(
|
|
2846
|
+
featureId: string,
|
|
2847
|
+
repoRoot = AOP_ROOT,
|
|
2848
|
+
): Promise<FeatureGenealogy | null> {
|
|
2849
|
+
const feature = await readFeatureState(featureId, repoRoot);
|
|
2850
|
+
if (!feature) {
|
|
2851
|
+
return null;
|
|
2852
|
+
}
|
|
2853
|
+
const [plan, checkpointState] = await Promise.all([
|
|
2854
|
+
readFeaturePlan(featureId, repoRoot),
|
|
2855
|
+
readFeatureCheckpoints(featureId, repoRoot),
|
|
2856
|
+
]);
|
|
2857
|
+
const statePath = path.join(getFeatureRoot(featureId, repoRoot), 'state.md');
|
|
2858
|
+
const runNodes: FeatureGenealogyNode[] = await (async () => {
|
|
2859
|
+
try {
|
|
2860
|
+
const raw = await readFile(statePath, 'utf8');
|
|
2861
|
+
const frontMatter = parseFrontMatter(raw).frontMatter;
|
|
2862
|
+
const runHistory = Array.isArray(frontMatter.run_history) ? frontMatter.run_history : [];
|
|
2863
|
+
return runHistory
|
|
2864
|
+
.map<FeatureGenealogyNode | null>((entry, index) => {
|
|
2865
|
+
const record = asRecord(entry);
|
|
2866
|
+
if (!record || typeof record.run_id !== 'string') {
|
|
2867
|
+
return null;
|
|
2868
|
+
}
|
|
2869
|
+
const endedAt = typeof record.ended_at === 'string' ? record.ended_at : null;
|
|
2870
|
+
const startedAt = typeof record.started_at === 'string' ? record.started_at : null;
|
|
2871
|
+
return {
|
|
2872
|
+
id: `${featureId}:run:${index}`,
|
|
2873
|
+
label: record.run_id,
|
|
2874
|
+
kind: 'run',
|
|
2875
|
+
timestamp: endedAt ?? startedAt,
|
|
2876
|
+
status: typeof record.status === 'string' ? record.status : null,
|
|
2877
|
+
} satisfies FeatureGenealogyNode;
|
|
2878
|
+
})
|
|
2879
|
+
.filter((entry): entry is FeatureGenealogyNode => entry != null);
|
|
2880
|
+
} catch {
|
|
2881
|
+
return [];
|
|
2882
|
+
}
|
|
2883
|
+
})();
|
|
2884
|
+
|
|
2885
|
+
const planVersion =
|
|
2886
|
+
typeof plan?.plan_version === 'number'
|
|
2887
|
+
? plan.plan_version
|
|
2888
|
+
: typeof plan?.plan_version === 'string'
|
|
2889
|
+
? Number.parseInt(plan.plan_version, 10)
|
|
2890
|
+
: (feature.plan_version ?? null);
|
|
2891
|
+
const revisionOf =
|
|
2892
|
+
typeof plan?.revision_of === 'number'
|
|
2893
|
+
? plan.revision_of
|
|
2894
|
+
: typeof plan?.revision_of === 'string'
|
|
2895
|
+
? Number.parseInt(plan.revision_of, 10)
|
|
2896
|
+
: (feature.revision_of ?? null);
|
|
2897
|
+
const planNodes: FeatureGenealogyNode[] = [];
|
|
2898
|
+
if (revisionOf != null && Number.isFinite(revisionOf)) {
|
|
2899
|
+
for (let version = 1; version <= revisionOf; version += 1) {
|
|
2900
|
+
planNodes.push({
|
|
2901
|
+
id: `${featureId}:plan:${version}`,
|
|
2902
|
+
label: `Plan v${version}`,
|
|
2903
|
+
kind: 'plan_revision',
|
|
2904
|
+
timestamp: null,
|
|
2905
|
+
status: version === revisionOf ? 'ancestor' : null,
|
|
2906
|
+
});
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
if (planVersion != null && Number.isFinite(planVersion)) {
|
|
2910
|
+
planNodes.push({
|
|
2911
|
+
id: `${featureId}:plan:${planVersion}`,
|
|
2912
|
+
label: `Plan v${planVersion}`,
|
|
2913
|
+
kind: 'plan_revision',
|
|
2914
|
+
timestamp: null,
|
|
2915
|
+
status: feature.status,
|
|
2916
|
+
note: feature.revision_reason ?? null,
|
|
2917
|
+
});
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
const checkpointNodes = (checkpointState?.checkpoints ?? []).slice(0, 10).map((checkpoint) => ({
|
|
2921
|
+
id: checkpoint.checkpoint_id,
|
|
2922
|
+
label: checkpoint.checkpoint_id,
|
|
2923
|
+
kind: 'checkpoint',
|
|
2924
|
+
timestamp: checkpoint.timestamp,
|
|
2925
|
+
status: checkpoint.validation_status,
|
|
2926
|
+
note: checkpoint.severity ?? null,
|
|
2927
|
+
})) satisfies FeatureGenealogyNode[];
|
|
2928
|
+
|
|
2929
|
+
return {
|
|
2930
|
+
feature_id: featureId,
|
|
2931
|
+
nodes: [
|
|
2932
|
+
{
|
|
2933
|
+
id: featureId,
|
|
2934
|
+
label: featureId,
|
|
2935
|
+
kind: 'feature',
|
|
2936
|
+
timestamp: feature.last_updated ?? null,
|
|
2937
|
+
status: feature.status,
|
|
2938
|
+
},
|
|
2939
|
+
...planNodes,
|
|
2940
|
+
...checkpointNodes,
|
|
2941
|
+
...runNodes,
|
|
2942
|
+
],
|
|
2943
|
+
};
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
function summarizeRolePerformance(
|
|
2947
|
+
entries: WorkerEventEntry[],
|
|
2948
|
+
role: string,
|
|
2949
|
+
): RolePerformanceSummary {
|
|
2950
|
+
const matching = entries.filter((entry) => entry.role === role);
|
|
2951
|
+
const elapsedValues = matching
|
|
2952
|
+
.map((entry) => entry.elapsed_ms)
|
|
2953
|
+
.filter((value): value is number => typeof value === 'number' && Number.isFinite(value));
|
|
2954
|
+
const requestValues = matching
|
|
2955
|
+
.map((entry) => entry.request_count)
|
|
2956
|
+
.filter((value): value is number => Number.isFinite(value));
|
|
2957
|
+
const providers = uniqueSorted(matching.map((entry) => entry.provider));
|
|
2958
|
+
const models = uniqueSorted(matching.map((entry) => entry.model));
|
|
2959
|
+
const lastEventAt = matching.length > 0 ? (matching[0]?.ts ?? null) : null;
|
|
2960
|
+
return {
|
|
2961
|
+
role,
|
|
2962
|
+
total_events: matching.length,
|
|
2963
|
+
valid_events: matching.filter((entry) => entry.valid).length,
|
|
2964
|
+
invalid_events: matching.filter((entry) => !entry.valid).length,
|
|
2965
|
+
avg_elapsed_ms: truncateNumber(averageNumber(elapsedValues), 1),
|
|
2966
|
+
avg_requests: truncateNumber(averageNumber(requestValues), 2),
|
|
2967
|
+
last_event_at: lastEventAt,
|
|
2968
|
+
providers,
|
|
2969
|
+
models,
|
|
2970
|
+
};
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2973
|
+
function sumRoleMetric(
|
|
2974
|
+
entries: WorkerEventEntry[],
|
|
2975
|
+
selector: (entry: WorkerEventEntry) => number | undefined,
|
|
2976
|
+
): number {
|
|
2977
|
+
return entries.reduce((sum, entry) => {
|
|
2978
|
+
const value = selector(entry);
|
|
2979
|
+
return typeof value === 'number' && Number.isFinite(value) ? sum + value : sum;
|
|
2980
|
+
}, 0);
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
function selectPrimaryProviderModel(entries: WorkerEventEntry[]): {
|
|
2984
|
+
provider: string | null;
|
|
2985
|
+
model: string | null;
|
|
2986
|
+
} {
|
|
2987
|
+
const aggregate = new Map<
|
|
2988
|
+
string,
|
|
2989
|
+
{ provider: string; model: string; count: number; latest_ts: number }
|
|
2990
|
+
>();
|
|
2991
|
+
|
|
2992
|
+
for (const entry of entries) {
|
|
2993
|
+
const key = `${entry.provider}:${entry.model}`;
|
|
2994
|
+
const existing = aggregate.get(key) ?? {
|
|
2995
|
+
provider: entry.provider,
|
|
2996
|
+
model: entry.model,
|
|
2997
|
+
count: 0,
|
|
2998
|
+
latest_ts: Number.NaN,
|
|
2999
|
+
};
|
|
3000
|
+
existing.count += 1;
|
|
3001
|
+
const timestamp = Date.parse(entry.ts);
|
|
3002
|
+
if (
|
|
3003
|
+
!Number.isNaN(timestamp) &&
|
|
3004
|
+
(Number.isNaN(existing.latest_ts) || timestamp > existing.latest_ts)
|
|
3005
|
+
) {
|
|
3006
|
+
existing.latest_ts = timestamp;
|
|
3007
|
+
}
|
|
3008
|
+
aggregate.set(key, existing);
|
|
3009
|
+
}
|
|
3010
|
+
|
|
3011
|
+
const selected = Array.from(aggregate.values()).sort((left, right) => {
|
|
3012
|
+
if (right.count !== left.count) {
|
|
3013
|
+
return right.count - left.count;
|
|
3014
|
+
}
|
|
3015
|
+
const leftTs = Number.isNaN(left.latest_ts) ? Number.NEGATIVE_INFINITY : left.latest_ts;
|
|
3016
|
+
const rightTs = Number.isNaN(right.latest_ts) ? Number.NEGATIVE_INFINITY : right.latest_ts;
|
|
3017
|
+
if (rightTs !== leftTs) {
|
|
3018
|
+
return rightTs - leftTs;
|
|
3019
|
+
}
|
|
3020
|
+
return `${left.provider}/${left.model}`.localeCompare(`${right.provider}/${right.model}`);
|
|
3021
|
+
})[0];
|
|
3022
|
+
|
|
3023
|
+
return selected
|
|
3024
|
+
? { provider: selected.provider, model: selected.model }
|
|
3025
|
+
: { provider: null, model: null };
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3028
|
+
function createPerformanceMetric(
|
|
3029
|
+
actual: number | null,
|
|
3030
|
+
baselineAverage: number | null,
|
|
3031
|
+
averageDigits = 1,
|
|
3032
|
+
): AgentRolePerformanceMetric {
|
|
3033
|
+
const safeAverage = baselineAverage != null && baselineAverage > 0 ? baselineAverage : null;
|
|
3034
|
+
return {
|
|
3035
|
+
actual,
|
|
3036
|
+
average: truncateNumber(baselineAverage, averageDigits),
|
|
3037
|
+
ratio: actual != null && safeAverage != null ? truncateNumber(actual / safeAverage, 2) : null,
|
|
3038
|
+
};
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
function summarizePerformanceCard(
|
|
3042
|
+
metrics: AgentRolePerformanceCardData['metrics'],
|
|
3043
|
+
sampleSize: number,
|
|
3044
|
+
): string {
|
|
3045
|
+
if (sampleSize === 0) {
|
|
3046
|
+
return 'Historical baseline unavailable for this provider and model.';
|
|
3047
|
+
}
|
|
3048
|
+
|
|
3049
|
+
const comparableRatios = [
|
|
3050
|
+
metrics.elapsed_ms.ratio,
|
|
3051
|
+
metrics.request_count.ratio,
|
|
3052
|
+
metrics.patch_count.ratio,
|
|
3053
|
+
metrics.plan_submission_count.ratio,
|
|
3054
|
+
].filter((value): value is number => typeof value === 'number' && Number.isFinite(value));
|
|
3055
|
+
|
|
3056
|
+
if (comparableRatios.length === 0) {
|
|
3057
|
+
return 'Historical baseline unavailable for this provider and model.';
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
if (
|
|
3061
|
+
typeof metrics.elapsed_ms.ratio === 'number' &&
|
|
3062
|
+
metrics.elapsed_ms.ratio > 1.5 &&
|
|
3063
|
+
typeof metrics.request_count.ratio === 'number' &&
|
|
3064
|
+
metrics.request_count.ratio > 1.5
|
|
3065
|
+
) {
|
|
3066
|
+
return 'Running slower and making more requests than average.';
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
if (comparableRatios.some((value) => value > 2.5)) {
|
|
3070
|
+
return 'Well above historical average.';
|
|
3071
|
+
}
|
|
3072
|
+
|
|
3073
|
+
if (comparableRatios.some((value) => value > 1.5)) {
|
|
3074
|
+
return 'Above historical average.';
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
if (comparableRatios.every((value) => value >= 0.75 && value <= 1.25)) {
|
|
3078
|
+
return 'On track.';
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
if (comparableRatios.some((value) => value < 0.75)) {
|
|
3082
|
+
return 'Ahead of historical average.';
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
return 'Mixed versus historical average.';
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
function computeHistoricalRoleMetricAverages(
|
|
3089
|
+
entries: WorkerEventEntry[],
|
|
3090
|
+
currentFeatureId: string,
|
|
3091
|
+
role: TrackedAgentRole,
|
|
3092
|
+
provider: string | null,
|
|
3093
|
+
model: string | null,
|
|
3094
|
+
): {
|
|
3095
|
+
sample_size: number;
|
|
3096
|
+
elapsed_ms: number | null;
|
|
3097
|
+
request_count: number | null;
|
|
3098
|
+
patch_count: number | null;
|
|
3099
|
+
plan_submission_count: number | null;
|
|
3100
|
+
} {
|
|
3101
|
+
if (!provider || !model) {
|
|
3102
|
+
return {
|
|
3103
|
+
sample_size: 0,
|
|
3104
|
+
elapsed_ms: null,
|
|
3105
|
+
request_count: null,
|
|
3106
|
+
patch_count: null,
|
|
3107
|
+
plan_submission_count: null,
|
|
3108
|
+
};
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
const perFeature = new Map<
|
|
3112
|
+
string,
|
|
3113
|
+
{
|
|
3114
|
+
elapsed_ms: number;
|
|
3115
|
+
request_count: number;
|
|
3116
|
+
patch_count: number;
|
|
3117
|
+
plan_submission_count: number;
|
|
3118
|
+
}
|
|
3119
|
+
>();
|
|
3120
|
+
|
|
3121
|
+
for (const entry of entries) {
|
|
3122
|
+
if (
|
|
3123
|
+
entry.role !== role ||
|
|
3124
|
+
entry.provider !== provider ||
|
|
3125
|
+
entry.model !== model ||
|
|
3126
|
+
!entry.feature_id ||
|
|
3127
|
+
entry.feature_id === currentFeatureId
|
|
3128
|
+
) {
|
|
3129
|
+
continue;
|
|
3130
|
+
}
|
|
3131
|
+
const existing = perFeature.get(entry.feature_id) ?? {
|
|
3132
|
+
elapsed_ms: 0,
|
|
3133
|
+
request_count: 0,
|
|
3134
|
+
patch_count: 0,
|
|
3135
|
+
plan_submission_count: 0,
|
|
3136
|
+
};
|
|
3137
|
+
existing.elapsed_ms +=
|
|
3138
|
+
typeof entry.elapsed_ms === 'number' && Number.isFinite(entry.elapsed_ms)
|
|
3139
|
+
? entry.elapsed_ms
|
|
3140
|
+
: 0;
|
|
3141
|
+
existing.request_count += Number.isFinite(entry.request_count) ? entry.request_count : 0;
|
|
3142
|
+
existing.patch_count += Number.isFinite(entry.patch_count) ? entry.patch_count : 0;
|
|
3143
|
+
existing.plan_submission_count += Number.isFinite(entry.plan_submission_count)
|
|
3144
|
+
? entry.plan_submission_count
|
|
3145
|
+
: 0;
|
|
3146
|
+
perFeature.set(entry.feature_id, existing);
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
const totals = Array.from(perFeature.values());
|
|
3150
|
+
return {
|
|
3151
|
+
sample_size: totals.length,
|
|
3152
|
+
elapsed_ms: averageNumber(totals.map((item) => item.elapsed_ms)),
|
|
3153
|
+
request_count: averageNumber(totals.map((item) => item.request_count)),
|
|
3154
|
+
patch_count: averageNumber(totals.map((item) => item.patch_count)),
|
|
3155
|
+
plan_submission_count: averageNumber(totals.map((item) => item.plan_submission_count)),
|
|
3156
|
+
};
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
async function buildFeatureRolePerformanceCards(
|
|
3160
|
+
feature: FeatureSummary,
|
|
3161
|
+
repoRoot = AOP_ROOT,
|
|
3162
|
+
): Promise<AgentRolePerformanceCardData[]> {
|
|
3163
|
+
const [featureEvents, allEvents] = await Promise.all([
|
|
3164
|
+
readWorkerTimelineEntries(feature.feature_id, repoRoot, { limit: 200 }),
|
|
3165
|
+
readAllWorkerEvents(repoRoot),
|
|
3166
|
+
]);
|
|
3167
|
+
|
|
3168
|
+
return TRACKED_AGENT_ROLES.flatMap((role) => {
|
|
3169
|
+
const roleStatusMap = feature.role_status as unknown as Record<string, string> | undefined;
|
|
3170
|
+
const roleStatus = roleStatusMap?.[role] ?? 'unknown';
|
|
3171
|
+
if (roleStatus === 'ready' || roleStatus === 'unknown') {
|
|
3172
|
+
return [];
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3175
|
+
const currentEntries = featureEvents.filter((entry) => entry.role === role);
|
|
3176
|
+
const currentProviderModel = selectPrimaryProviderModel(currentEntries);
|
|
3177
|
+
const historical = computeHistoricalRoleMetricAverages(
|
|
3178
|
+
allEvents,
|
|
3179
|
+
feature.feature_id,
|
|
3180
|
+
role,
|
|
3181
|
+
currentProviderModel.provider,
|
|
3182
|
+
currentProviderModel.model,
|
|
3183
|
+
);
|
|
3184
|
+
const metrics = {
|
|
3185
|
+
elapsed_ms: createPerformanceMetric(
|
|
3186
|
+
currentEntries.length > 0 ? sumRoleMetric(currentEntries, (entry) => entry.elapsed_ms) : 0,
|
|
3187
|
+
historical.elapsed_ms,
|
|
3188
|
+
1,
|
|
3189
|
+
),
|
|
3190
|
+
request_count: createPerformanceMetric(
|
|
3191
|
+
currentEntries.length > 0
|
|
3192
|
+
? sumRoleMetric(currentEntries, (entry) => entry.request_count)
|
|
3193
|
+
: 0,
|
|
3194
|
+
historical.request_count,
|
|
3195
|
+
1,
|
|
3196
|
+
),
|
|
3197
|
+
patch_count: createPerformanceMetric(
|
|
3198
|
+
currentEntries.length > 0 ? sumRoleMetric(currentEntries, (entry) => entry.patch_count) : 0,
|
|
3199
|
+
historical.patch_count,
|
|
3200
|
+
1,
|
|
3201
|
+
),
|
|
3202
|
+
plan_submission_count: createPerformanceMetric(
|
|
3203
|
+
currentEntries.length > 0
|
|
3204
|
+
? sumRoleMetric(currentEntries, (entry) => entry.plan_submission_count)
|
|
3205
|
+
: 0,
|
|
3206
|
+
historical.plan_submission_count,
|
|
3207
|
+
1,
|
|
3208
|
+
),
|
|
3209
|
+
};
|
|
3210
|
+
|
|
3211
|
+
return [
|
|
3212
|
+
{
|
|
3213
|
+
role,
|
|
3214
|
+
role_status: roleStatus as 'ready' | 'running' | 'blocked' | 'done' | 'unknown',
|
|
3215
|
+
provider: currentProviderModel.provider,
|
|
3216
|
+
model: currentProviderModel.model,
|
|
3217
|
+
total_events: currentEntries.length,
|
|
3218
|
+
sample_size: historical.sample_size,
|
|
3219
|
+
summary:
|
|
3220
|
+
currentEntries.length === 0
|
|
3221
|
+
? 'No worker-event telemetry has been recorded yet.'
|
|
3222
|
+
: summarizePerformanceCard(metrics, historical.sample_size),
|
|
3223
|
+
metrics,
|
|
3224
|
+
},
|
|
3225
|
+
];
|
|
3226
|
+
});
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
export async function readFeatureWorkerEventSummary(
|
|
3230
|
+
featureId: string,
|
|
3231
|
+
repoRoot = AOP_ROOT,
|
|
3232
|
+
options: WorkerTimelineReadOptions = {},
|
|
3233
|
+
): Promise<WorkerEventSummary> {
|
|
3234
|
+
const entries = await readWorkerTimelineEntries(featureId, repoRoot, options);
|
|
3235
|
+
const roles = uniqueSorted(entries.map((entry) => entry.role));
|
|
3236
|
+
return {
|
|
3237
|
+
entries,
|
|
3238
|
+
by_role: roles.map((role) => summarizeRolePerformance(entries, role)),
|
|
3239
|
+
};
|
|
3240
|
+
}
|
|
3241
|
+
|
|
3242
|
+
export async function readMergeQueueSnapshot(
|
|
3243
|
+
featureId?: string,
|
|
3244
|
+
repoRoot = AOP_ROOT,
|
|
3245
|
+
): Promise<MergeQueueSnapshot> {
|
|
3246
|
+
const payload = await readDashboardStatus(repoRoot);
|
|
3247
|
+
const dependencyMap = new Map(
|
|
3248
|
+
(payload.dep_blocked ?? []).map((entry) => [entry.feature_id, entry.depends_on_unresolved]),
|
|
3249
|
+
);
|
|
3250
|
+
const reverseDependencyMap = new Map<string, string[]>();
|
|
3251
|
+
for (const [blockedFeatureId, blockers] of dependencyMap.entries()) {
|
|
3252
|
+
for (const blocker of blockers) {
|
|
3253
|
+
const existing = reverseDependencyMap.get(blocker) ?? [];
|
|
3254
|
+
existing.push(blockedFeatureId);
|
|
3255
|
+
reverseDependencyMap.set(blocker, existing);
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
const queueFeatures = payload.features
|
|
3259
|
+
.filter((feature) => feature.phase === 'ready_to_merge')
|
|
3260
|
+
.sort((left, right) => {
|
|
3261
|
+
const leftMs = left.phase_entered_at ? Date.parse(left.phase_entered_at) : Number.NaN;
|
|
3262
|
+
const rightMs = right.phase_entered_at ? Date.parse(right.phase_entered_at) : Number.NaN;
|
|
3263
|
+
const safeLeft = Number.isNaN(leftMs) ? Number.MAX_SAFE_INTEGER : leftMs;
|
|
3264
|
+
const safeRight = Number.isNaN(rightMs) ? Number.MAX_SAFE_INTEGER : rightMs;
|
|
3265
|
+
if (safeLeft === safeRight) {
|
|
3266
|
+
return left.feature_id.localeCompare(right.feature_id);
|
|
3267
|
+
}
|
|
3268
|
+
return safeLeft - safeRight;
|
|
3269
|
+
});
|
|
3270
|
+
|
|
3271
|
+
const queue = queueFeatures.map((feature, index) => ({
|
|
3272
|
+
feature_id: feature.feature_id,
|
|
3273
|
+
position: index + 1,
|
|
3274
|
+
total: queueFeatures.length,
|
|
3275
|
+
status: feature.status,
|
|
3276
|
+
phase_entered_at: feature.phase_entered_at ?? null,
|
|
3277
|
+
merge_ready: feature.pr?.merge_ready === true,
|
|
3278
|
+
ci_status: feature.pr?.ci_status ?? null,
|
|
3279
|
+
pending_review_threads: feature.pr?.pending_review_threads ?? 0,
|
|
3280
|
+
has_conflicts: feature.pr?.has_conflicts === true,
|
|
3281
|
+
merge_score: feature.pr?.merge_score ?? null,
|
|
3282
|
+
blocked_by: dependencyMap.get(feature.feature_id) ?? [],
|
|
3283
|
+
blocks: reverseDependencyMap.get(feature.feature_id) ?? [],
|
|
3284
|
+
estimated_merge_at:
|
|
3285
|
+
index === 0 ? null : new Date(Date.now() + index * 15 * 60_000).toISOString(),
|
|
3286
|
+
lock_holder: false,
|
|
3287
|
+
})) satisfies MergeQueueEntry[];
|
|
3288
|
+
|
|
3289
|
+
return {
|
|
3290
|
+
queue,
|
|
3291
|
+
current_feature: featureId
|
|
3292
|
+
? (queue.find((entry) => entry.feature_id === featureId) ?? null)
|
|
3293
|
+
: null,
|
|
3294
|
+
};
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
export async function readProviderAnalyticsSnapshot(
|
|
3298
|
+
repoRoot = AOP_ROOT,
|
|
3299
|
+
): Promise<ProviderAnalytics[]> {
|
|
3300
|
+
const payload = await readDashboardStatus(repoRoot);
|
|
3301
|
+
const allEvents = await readAllWorkerEvents(repoRoot);
|
|
3302
|
+
const featureCost = new Map<string, number>();
|
|
3303
|
+
for (const feature of payload.features) {
|
|
3304
|
+
if (typeof feature.usage_tokens === 'number' && Number.isFinite(feature.usage_tokens)) {
|
|
3305
|
+
featureCost.set(feature.feature_id, feature.usage_tokens);
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3309
|
+
const aggregate = new Map<
|
|
3310
|
+
string,
|
|
3311
|
+
{
|
|
3312
|
+
provider: string;
|
|
3313
|
+
model: string;
|
|
3314
|
+
total_features: Set<string>;
|
|
3315
|
+
token_features: Set<string>;
|
|
3316
|
+
success_count: number;
|
|
3317
|
+
retry_count_total: number;
|
|
3318
|
+
duration_total: number;
|
|
3319
|
+
duration_count: number;
|
|
3320
|
+
token_total: number;
|
|
3321
|
+
}
|
|
3322
|
+
>();
|
|
3323
|
+
|
|
3324
|
+
for (const entry of allEvents) {
|
|
3325
|
+
const key = `${entry.provider}:${entry.model}`;
|
|
3326
|
+
const existing = aggregate.get(key) ?? {
|
|
3327
|
+
provider: entry.provider,
|
|
3328
|
+
model: entry.model,
|
|
3329
|
+
total_features: new Set<string>(),
|
|
3330
|
+
token_features: new Set<string>(),
|
|
3331
|
+
success_count: 0,
|
|
3332
|
+
retry_count_total: 0,
|
|
3333
|
+
duration_total: 0,
|
|
3334
|
+
duration_count: 0,
|
|
3335
|
+
token_total: 0,
|
|
3336
|
+
};
|
|
3337
|
+
if (entry.feature_id) {
|
|
3338
|
+
existing.total_features.add(entry.feature_id);
|
|
3339
|
+
if (!existing.token_features.has(entry.feature_id)) {
|
|
3340
|
+
existing.token_total += featureCost.get(entry.feature_id) ?? 0;
|
|
3341
|
+
existing.token_features.add(entry.feature_id);
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
if (entry.valid) {
|
|
3345
|
+
existing.success_count += 1;
|
|
3346
|
+
}
|
|
3347
|
+
existing.retry_count_total += Math.max(0, entry.request_count - 1);
|
|
3348
|
+
if (typeof entry.elapsed_ms === 'number' && Number.isFinite(entry.elapsed_ms)) {
|
|
3349
|
+
existing.duration_total += entry.elapsed_ms;
|
|
3350
|
+
existing.duration_count += 1;
|
|
3351
|
+
}
|
|
3352
|
+
aggregate.set(key, existing);
|
|
3353
|
+
}
|
|
3354
|
+
|
|
3355
|
+
return Array.from(aggregate.values())
|
|
3356
|
+
.map((entry) => {
|
|
3357
|
+
const totalFeatures = entry.total_features.size;
|
|
3358
|
+
const avgDuration =
|
|
3359
|
+
entry.duration_count > 0 ? entry.duration_total / entry.duration_count : 0;
|
|
3360
|
+
const avgRetry = totalFeatures > 0 ? entry.retry_count_total / totalFeatures : 0;
|
|
3361
|
+
return {
|
|
3362
|
+
provider: entry.provider,
|
|
3363
|
+
model: entry.model,
|
|
3364
|
+
total_features: totalFeatures,
|
|
3365
|
+
success_count: entry.success_count,
|
|
3366
|
+
success_rate: totalFeatures > 0 ? entry.success_count / totalFeatures : 0,
|
|
3367
|
+
avg_retry_count: truncateNumber(avgRetry, 2) ?? 0,
|
|
3368
|
+
avg_duration_ms: truncateNumber(avgDuration, 1) ?? 0,
|
|
3369
|
+
avg_tokens_used:
|
|
3370
|
+
totalFeatures > 0 ? (truncateNumber(entry.token_total / totalFeatures, 1) ?? 0) : 0,
|
|
3371
|
+
avg_cost_usd: 0,
|
|
3372
|
+
} satisfies ProviderAnalytics;
|
|
3373
|
+
})
|
|
3374
|
+
.sort(
|
|
3375
|
+
(left, right) =>
|
|
3376
|
+
right.total_features - left.total_features || left.provider.localeCompare(right.provider),
|
|
3377
|
+
);
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3380
|
+
function resolveUsageBaseline(
|
|
3381
|
+
entries: WorkerEventEntry[],
|
|
3382
|
+
providers: ProviderAnalytics[],
|
|
3383
|
+
): ProviderAnalytics | null {
|
|
3384
|
+
const currentProviderModel = selectPrimaryProviderModel(entries);
|
|
3385
|
+
if (!currentProviderModel.provider || !currentProviderModel.model) {
|
|
3386
|
+
return null;
|
|
3387
|
+
}
|
|
3388
|
+
return (
|
|
3389
|
+
providers.find(
|
|
3390
|
+
(provider) =>
|
|
3391
|
+
provider.provider === currentProviderModel.provider &&
|
|
3392
|
+
provider.model === currentProviderModel.model,
|
|
3393
|
+
) ?? null
|
|
3394
|
+
);
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3397
|
+
function normalizeRepoRelativePath(repoRoot: string, targetPath: string): string {
|
|
3398
|
+
return path.relative(repoRoot, targetPath).replaceAll('\\', '/');
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3401
|
+
async function pathExists(targetPath: string): Promise<boolean> {
|
|
3402
|
+
try {
|
|
3403
|
+
await stat(targetPath);
|
|
3404
|
+
return true;
|
|
3405
|
+
} catch {
|
|
3406
|
+
return false;
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3410
|
+
function specFilePath(featureId: string, repoRoot = AOP_ROOT): string {
|
|
3411
|
+
return path.join(getFeatureRoot(featureId, repoRoot), 'spec.md');
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3414
|
+
function bootstrapManifestPath(featureId: string, repoRoot = AOP_ROOT): string {
|
|
3415
|
+
return path.join(getFeatureRoot(featureId, repoRoot), 'spec.manifest.bootstrap.json');
|
|
3416
|
+
}
|
|
3417
|
+
|
|
3418
|
+
function verifiedManifestPath(featureId: string, repoRoot = AOP_ROOT): string {
|
|
3419
|
+
return path.join(getFeatureRoot(featureId, repoRoot), 'spec.manifest.verified.json');
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
function intakeReviewPath(featureId: string, repoRoot = AOP_ROOT): string {
|
|
3423
|
+
return path.join(getFeatureRoot(featureId, repoRoot), 'intake.review.json');
|
|
3424
|
+
}
|
|
3425
|
+
|
|
3426
|
+
function planMarkdownPath(featureId: string, repoRoot = AOP_ROOT): string {
|
|
3427
|
+
return path.join(getFeatureRoot(featureId, repoRoot), 'plan.md');
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
export type PlannerArtifactId = PlannerArtifactView['id'];
|
|
3431
|
+
|
|
3432
|
+
function plannerArtifactPath(
|
|
3433
|
+
featureId: string,
|
|
3434
|
+
artifactId: PlannerArtifactId,
|
|
3435
|
+
repoRoot = AOP_ROOT,
|
|
3436
|
+
): string {
|
|
3437
|
+
if (artifactId === 'source_spec') {
|
|
3438
|
+
return specFilePath(featureId, repoRoot);
|
|
3439
|
+
}
|
|
3440
|
+
if (artifactId === 'bootstrap_manifest') {
|
|
3441
|
+
return bootstrapManifestPath(featureId, repoRoot);
|
|
3442
|
+
}
|
|
3443
|
+
if (artifactId === 'verified_manifest') {
|
|
3444
|
+
return verifiedManifestPath(featureId, repoRoot);
|
|
3445
|
+
}
|
|
3446
|
+
if (artifactId === 'intake_review') {
|
|
3447
|
+
return intakeReviewPath(featureId, repoRoot);
|
|
3448
|
+
}
|
|
3449
|
+
if (artifactId === 'plan_markdown') {
|
|
3450
|
+
return planMarkdownPath(featureId, repoRoot);
|
|
3451
|
+
}
|
|
3452
|
+
return path.join(getFeatureRoot(featureId, repoRoot), 'plan.json');
|
|
3453
|
+
}
|
|
3454
|
+
|
|
3455
|
+
async function readTextArtifact(
|
|
3456
|
+
filePath: string,
|
|
3457
|
+
): Promise<{ raw: string; updatedAt: string | null } | null> {
|
|
3458
|
+
try {
|
|
3459
|
+
const [raw, fileStats] = await Promise.all([readFile(filePath, 'utf8'), stat(filePath)]);
|
|
3460
|
+
return {
|
|
3461
|
+
raw,
|
|
3462
|
+
updatedAt: fileStats.mtime.toISOString(),
|
|
3463
|
+
};
|
|
3464
|
+
} catch {
|
|
3465
|
+
return null;
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
|
|
3469
|
+
async function readJsonArtifact(
|
|
3470
|
+
filePath: string,
|
|
3471
|
+
): Promise<{ parsed: Record<string, unknown>; raw: string; updatedAt: string | null } | null> {
|
|
3472
|
+
const artifact = await readTextArtifact(filePath);
|
|
3473
|
+
if (!artifact) {
|
|
3474
|
+
return null;
|
|
3475
|
+
}
|
|
3476
|
+
try {
|
|
3477
|
+
return {
|
|
3478
|
+
parsed: JSON.parse(artifact.raw) as Record<string, unknown>,
|
|
3479
|
+
raw: artifact.raw,
|
|
3480
|
+
updatedAt: artifact.updatedAt,
|
|
3481
|
+
};
|
|
3482
|
+
} catch {
|
|
3483
|
+
return null;
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
function buildPlannerArtifactView(input: {
|
|
3488
|
+
id: PlannerArtifactId;
|
|
3489
|
+
label: PlannerArtifactView['label'];
|
|
3490
|
+
format: PlannerArtifactView['format'];
|
|
3491
|
+
repoRoot: string;
|
|
3492
|
+
featureId: string;
|
|
3493
|
+
path: string;
|
|
3494
|
+
version: number | null;
|
|
3495
|
+
updatedAt: string | null;
|
|
3496
|
+
rawContent: string | null;
|
|
3497
|
+
primary: boolean;
|
|
3498
|
+
authorityLabel: string | null;
|
|
3499
|
+
emptyState: string;
|
|
3500
|
+
}): PlannerArtifactView {
|
|
3501
|
+
return {
|
|
3502
|
+
id: input.id,
|
|
3503
|
+
label: input.label,
|
|
3504
|
+
format: input.format,
|
|
3505
|
+
path: normalizeRepoRelativePath(input.repoRoot, input.path),
|
|
3506
|
+
version: input.version,
|
|
3507
|
+
updated_at: input.updatedAt,
|
|
3508
|
+
raw_content: input.rawContent,
|
|
3509
|
+
primary: input.primary,
|
|
3510
|
+
authority_label: input.authorityLabel,
|
|
3511
|
+
empty_state: input.emptyState,
|
|
3512
|
+
};
|
|
3513
|
+
}
|
|
3514
|
+
|
|
3515
|
+
export async function readPlannerArtifactRaw(
|
|
3516
|
+
featureId: string,
|
|
3517
|
+
artifactId: PlannerArtifactId,
|
|
3518
|
+
repoRoot = AOP_ROOT,
|
|
3519
|
+
): Promise<{ content: string; path: string } | null> {
|
|
3520
|
+
const absolutePath = plannerArtifactPath(featureId, artifactId, repoRoot);
|
|
3521
|
+
const artifact = await readTextArtifact(absolutePath);
|
|
3522
|
+
if (!artifact) {
|
|
3523
|
+
return null;
|
|
3524
|
+
}
|
|
3525
|
+
return {
|
|
3526
|
+
content: artifact.raw,
|
|
3527
|
+
path: absolutePath,
|
|
3528
|
+
};
|
|
3529
|
+
}
|
|
3530
|
+
|
|
3531
|
+
function sortCheckpointsDescending(checkpoints: FeatureCheckpoint[]): FeatureCheckpoint[] {
|
|
3532
|
+
return [...checkpoints].sort((left, right) => {
|
|
3533
|
+
const leftMs = Date.parse(left.timestamp);
|
|
3534
|
+
const rightMs = Date.parse(right.timestamp);
|
|
3535
|
+
return (Number.isNaN(rightMs) ? 0 : rightMs) - (Number.isNaN(leftMs) ? 0 : leftMs);
|
|
3536
|
+
});
|
|
3537
|
+
}
|
|
3538
|
+
|
|
3539
|
+
function timestampInWindow(
|
|
3540
|
+
timestamp: string | null | undefined,
|
|
3541
|
+
startedAt: string,
|
|
3542
|
+
endedAt: string | null,
|
|
3543
|
+
): boolean {
|
|
3544
|
+
if (typeof timestamp !== 'string') {
|
|
3545
|
+
return false;
|
|
3546
|
+
}
|
|
3547
|
+
const valueMs = Date.parse(timestamp);
|
|
3548
|
+
const startedMs = Date.parse(startedAt);
|
|
3549
|
+
const endedMs = endedAt ? Date.parse(endedAt) : Number.POSITIVE_INFINITY;
|
|
3550
|
+
if (Number.isNaN(valueMs) || Number.isNaN(startedMs)) {
|
|
3551
|
+
return false;
|
|
3552
|
+
}
|
|
3553
|
+
return (
|
|
3554
|
+
valueMs >= startedMs && valueMs <= (Number.isNaN(endedMs) ? Number.POSITIVE_INFINITY : endedMs)
|
|
3555
|
+
);
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3558
|
+
function parseRunHistory(frontMatter: Record<string, unknown>): Array<{
|
|
3559
|
+
run_id: string;
|
|
3560
|
+
started_at: string;
|
|
3561
|
+
ended_at: string | null;
|
|
3562
|
+
status: FeatureRunHistoryEntry['status'];
|
|
3563
|
+
}> {
|
|
3564
|
+
const rawHistory = Array.isArray(frontMatter.run_history) ? frontMatter.run_history : [];
|
|
3565
|
+
return rawHistory
|
|
3566
|
+
.map((entry) => {
|
|
3567
|
+
const record = asRecord(entry);
|
|
3568
|
+
if (!record || typeof record.run_id !== 'string' || typeof record.started_at !== 'string') {
|
|
3569
|
+
return null;
|
|
3570
|
+
}
|
|
3571
|
+
const rawStatus = record.status;
|
|
3572
|
+
const status =
|
|
3573
|
+
rawStatus === 'active' ||
|
|
3574
|
+
rawStatus === 'completed' ||
|
|
3575
|
+
rawStatus === 'failed' ||
|
|
3576
|
+
rawStatus === 'timeout' ||
|
|
3577
|
+
rawStatus === 'interrupted'
|
|
3578
|
+
? rawStatus
|
|
3579
|
+
: 'interrupted';
|
|
3580
|
+
return {
|
|
3581
|
+
run_id: record.run_id,
|
|
3582
|
+
started_at: record.started_at,
|
|
3583
|
+
ended_at: typeof record.ended_at === 'string' ? record.ended_at : null,
|
|
3584
|
+
status,
|
|
3585
|
+
};
|
|
3586
|
+
})
|
|
3587
|
+
.filter(
|
|
3588
|
+
(
|
|
3589
|
+
entry,
|
|
3590
|
+
): entry is {
|
|
3591
|
+
run_id: string;
|
|
3592
|
+
started_at: string;
|
|
3593
|
+
ended_at: string | null;
|
|
3594
|
+
status: FeatureRunHistoryEntry['status'];
|
|
3595
|
+
} => entry != null,
|
|
3596
|
+
)
|
|
3597
|
+
.sort((left, right) => Date.parse(right.started_at) - Date.parse(left.started_at));
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3600
|
+
export async function readFeatureSpec(
|
|
3601
|
+
featureId: string,
|
|
3602
|
+
repoRoot = AOP_ROOT,
|
|
3603
|
+
options: { includeMarkdown?: boolean } = {},
|
|
3604
|
+
): Promise<FeatureSpecSummary | null> {
|
|
3605
|
+
const includeMarkdown = options.includeMarkdown ?? true;
|
|
3606
|
+
const fullPath = specFilePath(featureId, repoRoot);
|
|
3607
|
+
try {
|
|
3608
|
+
const [stats, markdown] = await Promise.all([
|
|
3609
|
+
stat(fullPath),
|
|
3610
|
+
includeMarkdown ? readFile(fullPath, 'utf8') : Promise.resolve(null),
|
|
3611
|
+
]);
|
|
3612
|
+
return summarizeFeatureSpec(
|
|
3613
|
+
normalizeRepoRelativePath(repoRoot, fullPath),
|
|
3614
|
+
markdown,
|
|
3615
|
+
stats.mtime.toISOString(),
|
|
3616
|
+
);
|
|
3617
|
+
} catch {
|
|
3618
|
+
return null;
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
|
|
3622
|
+
export async function readPlannerLifecycle(
|
|
3623
|
+
featureId: string,
|
|
3624
|
+
repoRoot = AOP_ROOT,
|
|
3625
|
+
): Promise<PlannerLifecycleSummary | null> {
|
|
3626
|
+
const [feature, bootstrapArtifact, verifiedArtifact, plan, questions, specArtifact] =
|
|
3627
|
+
await Promise.all([
|
|
3628
|
+
readFeatureState(featureId, repoRoot),
|
|
3629
|
+
readJsonArtifact(bootstrapManifestPath(featureId, repoRoot)),
|
|
3630
|
+
readJsonArtifact(verifiedManifestPath(featureId, repoRoot)),
|
|
3631
|
+
readFeaturePlan(featureId, repoRoot),
|
|
3632
|
+
readFeatureQuestions(featureId, repoRoot),
|
|
3633
|
+
readTextArtifact(specFilePath(featureId, repoRoot)),
|
|
3634
|
+
]);
|
|
3635
|
+
if (!feature) {
|
|
3636
|
+
return null;
|
|
3637
|
+
}
|
|
3638
|
+
return buildPlannerLifecycleSummary({
|
|
3639
|
+
feature,
|
|
3640
|
+
intakeSummary:
|
|
3641
|
+
feature.intake_status != null
|
|
3642
|
+
? {
|
|
3643
|
+
status: feature.intake_status,
|
|
3644
|
+
bootstrap_manifest_version: feature.bootstrap_manifest_version ?? null,
|
|
3645
|
+
verified_manifest_version: feature.verified_manifest_version ?? null,
|
|
3646
|
+
open_ambiguity_count: feature.open_ambiguity_count ?? 0,
|
|
3647
|
+
last_verified_at: feature.last_verified_at ?? null,
|
|
3648
|
+
promotion_basis: feature.promotion_basis ?? null,
|
|
3649
|
+
}
|
|
3650
|
+
: null,
|
|
3651
|
+
hasSpec: specArtifact != null,
|
|
3652
|
+
bootstrapManifest: normalizeBootstrapManifestRecord(bootstrapArtifact?.parsed ?? null),
|
|
3653
|
+
verifiedManifest: normalizeVerifiedManifestRecord(verifiedArtifact?.parsed ?? null),
|
|
3654
|
+
acceptedPlan: normalizeAcceptedPlanRecord(plan),
|
|
3655
|
+
questions,
|
|
3656
|
+
});
|
|
3657
|
+
}
|
|
3658
|
+
|
|
3659
|
+
export async function readFeatureIntakeWorkspace(
|
|
3660
|
+
featureId: string,
|
|
3661
|
+
repoRoot = AOP_ROOT,
|
|
3662
|
+
): Promise<FeatureIntakeWorkspace | null> {
|
|
3663
|
+
const [feature, bootstrapArtifact, verifiedArtifact, intakeReviewArtifact, spec, questions] =
|
|
3664
|
+
await Promise.all([
|
|
3665
|
+
readFeatureState(featureId, repoRoot),
|
|
3666
|
+
readJsonArtifact(bootstrapManifestPath(featureId, repoRoot)),
|
|
3667
|
+
readJsonArtifact(verifiedManifestPath(featureId, repoRoot)),
|
|
3668
|
+
readJsonArtifact(intakeReviewPath(featureId, repoRoot)),
|
|
3669
|
+
readFeatureSpec(featureId, repoRoot, { includeMarkdown: true }),
|
|
3670
|
+
readFeatureQuestions(featureId, repoRoot),
|
|
3671
|
+
]);
|
|
3672
|
+
if (!feature) {
|
|
3673
|
+
return null;
|
|
3674
|
+
}
|
|
3675
|
+
|
|
3676
|
+
const intakeSummary: FeatureIntakeSummary | null =
|
|
3677
|
+
feature.intake_status != null
|
|
3678
|
+
? {
|
|
3679
|
+
status: feature.intake_status,
|
|
3680
|
+
bootstrap_manifest_version: feature.bootstrap_manifest_version ?? null,
|
|
3681
|
+
verified_manifest_version: feature.verified_manifest_version ?? null,
|
|
3682
|
+
open_ambiguity_count: feature.open_ambiguity_count ?? 0,
|
|
3683
|
+
last_verified_at: feature.last_verified_at ?? null,
|
|
3684
|
+
promotion_basis: feature.promotion_basis ?? null,
|
|
3685
|
+
}
|
|
3686
|
+
: null;
|
|
3687
|
+
const intakeReview = normalizeIntakeReviewRecord(intakeReviewArtifact?.parsed ?? null);
|
|
3688
|
+
const intakeReviewSummary: FeatureIntakeReviewSummary | null = intakeReview
|
|
3689
|
+
? {
|
|
3690
|
+
status: intakeReview.status,
|
|
3691
|
+
questions_open: intakeReview.questions_open,
|
|
3692
|
+
questions_resolved: intakeReview.questions_resolved,
|
|
3693
|
+
ambiguity_count: intakeReview.ambiguities.length,
|
|
3694
|
+
}
|
|
3695
|
+
: null;
|
|
3696
|
+
const bootstrapManifest = normalizeBootstrapManifestRecord(bootstrapArtifact?.parsed ?? null);
|
|
3697
|
+
const verifiedManifest = normalizeVerifiedManifestRecord(verifiedArtifact?.parsed ?? null);
|
|
3698
|
+
const artifacts: PlannerArtifactView[] = [
|
|
3699
|
+
buildPlannerArtifactView({
|
|
3700
|
+
id: 'source_spec',
|
|
3701
|
+
label: 'Source Spec',
|
|
3702
|
+
format: 'markdown',
|
|
3703
|
+
repoRoot,
|
|
3704
|
+
featureId,
|
|
3705
|
+
path: specFilePath(featureId, repoRoot),
|
|
3706
|
+
version: null,
|
|
3707
|
+
updatedAt: spec?.last_updated ?? null,
|
|
3708
|
+
rawContent: spec?.markdown ?? null,
|
|
3709
|
+
primary: verifiedManifest == null,
|
|
3710
|
+
authorityLabel: verifiedManifest == null ? 'Primary input' : 'Reference input',
|
|
3711
|
+
emptyState: 'No source spec is available for this feature.',
|
|
3712
|
+
}),
|
|
3713
|
+
buildPlannerArtifactView({
|
|
3714
|
+
id: 'bootstrap_manifest',
|
|
3715
|
+
label: 'Bootstrap Manifest',
|
|
3716
|
+
format: 'json',
|
|
3717
|
+
repoRoot,
|
|
3718
|
+
featureId,
|
|
3719
|
+
path: bootstrapManifestPath(featureId, repoRoot),
|
|
3720
|
+
version: bootstrapManifest?.manifest_version ?? null,
|
|
3721
|
+
updatedAt: bootstrapArtifact?.updatedAt ?? null,
|
|
3722
|
+
rawContent:
|
|
3723
|
+
bootstrapArtifact?.parsed != null
|
|
3724
|
+
? JSON.stringify(bootstrapArtifact.parsed, null, 2)
|
|
3725
|
+
: null,
|
|
3726
|
+
primary: verifiedManifest == null,
|
|
3727
|
+
authorityLabel:
|
|
3728
|
+
verifiedManifest == null ? 'Current intake contract' : 'Original intake proposal',
|
|
3729
|
+
emptyState: 'Bootstrap manifest has not been generated yet.',
|
|
3730
|
+
}),
|
|
3731
|
+
buildPlannerArtifactView({
|
|
3732
|
+
id: 'verified_manifest',
|
|
3733
|
+
label: 'Verified Manifest',
|
|
3734
|
+
format: 'json',
|
|
3735
|
+
repoRoot,
|
|
3736
|
+
featureId,
|
|
3737
|
+
path: verifiedManifestPath(featureId, repoRoot),
|
|
3738
|
+
version: verifiedManifest?.manifest_version ?? null,
|
|
3739
|
+
updatedAt: verifiedArtifact?.updatedAt ?? null,
|
|
3740
|
+
rawContent:
|
|
3741
|
+
verifiedArtifact?.parsed != null ? JSON.stringify(verifiedArtifact.parsed, null, 2) : null,
|
|
3742
|
+
primary: verifiedManifest != null,
|
|
3743
|
+
authorityLabel: verifiedManifest != null ? 'Contract of record' : 'Pending verification',
|
|
3744
|
+
emptyState: 'Verified manifest has not been promoted yet.',
|
|
3745
|
+
}),
|
|
3746
|
+
buildPlannerArtifactView({
|
|
3747
|
+
id: 'intake_review',
|
|
3748
|
+
label: 'Intake Review',
|
|
3749
|
+
format: 'json',
|
|
3750
|
+
repoRoot,
|
|
3751
|
+
featureId,
|
|
3752
|
+
path: intakeReviewPath(featureId, repoRoot),
|
|
3753
|
+
version: intakeReview?.version ?? null,
|
|
3754
|
+
updatedAt: intakeReviewArtifact?.updatedAt ?? intakeReview?.last_updated_at ?? null,
|
|
3755
|
+
rawContent:
|
|
3756
|
+
intakeReviewArtifact?.parsed != null
|
|
3757
|
+
? JSON.stringify(intakeReviewArtifact.parsed, null, 2)
|
|
3758
|
+
: null,
|
|
3759
|
+
primary: false,
|
|
3760
|
+
authorityLabel: 'Review record',
|
|
3761
|
+
emptyState: 'No intake review artifact has been recorded yet.',
|
|
3762
|
+
}),
|
|
3763
|
+
];
|
|
3764
|
+
|
|
3765
|
+
return buildFeatureIntakeWorkspace({
|
|
3766
|
+
feature,
|
|
3767
|
+
intakeSummary,
|
|
3768
|
+
intakeReviewSummary,
|
|
3769
|
+
bootstrapManifest,
|
|
3770
|
+
verifiedManifest,
|
|
3771
|
+
intakeReview,
|
|
3772
|
+
questions,
|
|
3773
|
+
artifacts,
|
|
3774
|
+
});
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3777
|
+
export async function readFeaturePlanningWorkspace(
|
|
3778
|
+
featureId: string,
|
|
3779
|
+
repoRoot = AOP_ROOT,
|
|
3780
|
+
): Promise<FeaturePlanningWorkspace | null> {
|
|
3781
|
+
const [feature, verifiedArtifact, planArtifact, planMarkdownArtifact, questions] =
|
|
3782
|
+
await Promise.all([
|
|
3783
|
+
readFeatureState(featureId, repoRoot),
|
|
3784
|
+
readJsonArtifact(verifiedManifestPath(featureId, repoRoot)),
|
|
3785
|
+
readJsonArtifact(plannerArtifactPath(featureId, 'plan_json', repoRoot)),
|
|
3786
|
+
readTextArtifact(planMarkdownPath(featureId, repoRoot)),
|
|
3787
|
+
readFeatureQuestions(featureId, repoRoot),
|
|
3788
|
+
]);
|
|
3789
|
+
if (!feature) {
|
|
3790
|
+
return null;
|
|
3791
|
+
}
|
|
3792
|
+
const intakeSummary: FeatureIntakeSummary | null =
|
|
3793
|
+
feature.intake_status != null
|
|
3794
|
+
? {
|
|
3795
|
+
status: feature.intake_status,
|
|
3796
|
+
bootstrap_manifest_version: feature.bootstrap_manifest_version ?? null,
|
|
3797
|
+
verified_manifest_version: feature.verified_manifest_version ?? null,
|
|
3798
|
+
open_ambiguity_count: feature.open_ambiguity_count ?? 0,
|
|
3799
|
+
last_verified_at: feature.last_verified_at ?? null,
|
|
3800
|
+
promotion_basis: feature.promotion_basis ?? null,
|
|
3801
|
+
}
|
|
3802
|
+
: null;
|
|
3803
|
+
const verifiedManifest = normalizeVerifiedManifestRecord(verifiedArtifact?.parsed ?? null);
|
|
3804
|
+
const acceptedPlan = normalizeAcceptedPlanRecord(planArtifact?.parsed ?? null);
|
|
3805
|
+
const artifacts: PlannerArtifactView[] = [
|
|
3806
|
+
buildPlannerArtifactView({
|
|
3807
|
+
id: 'verified_manifest',
|
|
3808
|
+
label: 'Verified Manifest',
|
|
3809
|
+
format: 'json',
|
|
3810
|
+
repoRoot,
|
|
3811
|
+
featureId,
|
|
3812
|
+
path: verifiedManifestPath(featureId, repoRoot),
|
|
3813
|
+
version: verifiedManifest?.manifest_version ?? null,
|
|
3814
|
+
updatedAt: verifiedArtifact?.updatedAt ?? null,
|
|
3815
|
+
rawContent:
|
|
3816
|
+
verifiedArtifact?.parsed != null ? JSON.stringify(verifiedArtifact.parsed, null, 2) : null,
|
|
3817
|
+
primary: true,
|
|
3818
|
+
authorityLabel: 'Contract of record',
|
|
3819
|
+
emptyState: 'Verified manifest has not been promoted yet.',
|
|
3820
|
+
}),
|
|
3821
|
+
buildPlannerArtifactView({
|
|
3822
|
+
id: 'plan_markdown',
|
|
3823
|
+
label: 'Plan Markdown',
|
|
3824
|
+
format: 'markdown',
|
|
3825
|
+
repoRoot,
|
|
3826
|
+
featureId,
|
|
3827
|
+
path: planMarkdownPath(featureId, repoRoot),
|
|
3828
|
+
version: acceptedPlan?.plan_version ?? null,
|
|
3829
|
+
updatedAt: planMarkdownArtifact?.updatedAt ?? null,
|
|
3830
|
+
rawContent: planMarkdownArtifact?.raw ?? null,
|
|
3831
|
+
primary: acceptedPlan != null,
|
|
3832
|
+
authorityLabel: acceptedPlan != null ? 'Planner baseline' : 'Planning scratchpad',
|
|
3833
|
+
emptyState: 'No plan markdown has been recorded yet.',
|
|
3834
|
+
}),
|
|
3835
|
+
buildPlannerArtifactView({
|
|
3836
|
+
id: 'plan_json',
|
|
3837
|
+
label: 'Plan JSON',
|
|
3838
|
+
format: 'json',
|
|
3839
|
+
repoRoot,
|
|
3840
|
+
featureId,
|
|
3841
|
+
path: plannerArtifactPath(featureId, 'plan_json', repoRoot),
|
|
3842
|
+
version: acceptedPlan?.plan_version ?? null,
|
|
3843
|
+
updatedAt: planArtifact?.updatedAt ?? null,
|
|
3844
|
+
rawContent:
|
|
3845
|
+
planArtifact?.parsed != null ? JSON.stringify(planArtifact.parsed, null, 2) : null,
|
|
3846
|
+
primary: acceptedPlan != null,
|
|
3847
|
+
authorityLabel: acceptedPlan != null ? 'Accepted execution contract' : 'No accepted plan yet',
|
|
3848
|
+
emptyState: 'No accepted execution plan has been submitted yet.',
|
|
3849
|
+
}),
|
|
3850
|
+
];
|
|
3851
|
+
|
|
3852
|
+
return buildFeaturePlanningWorkspace({
|
|
3853
|
+
feature,
|
|
3854
|
+
intakeSummary,
|
|
3855
|
+
verifiedManifest,
|
|
3856
|
+
acceptedPlan,
|
|
3857
|
+
plannerQuestions: questions,
|
|
3858
|
+
artifacts,
|
|
3859
|
+
});
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3862
|
+
export async function readFeaturePlanProgress(
|
|
3863
|
+
featureId: string,
|
|
3864
|
+
repoRoot = AOP_ROOT,
|
|
3865
|
+
): Promise<FeatureDetail['plan_progress']> {
|
|
3866
|
+
const [feature, plan, checkpointState, gateEvidence, qaTestIndex] = await Promise.all([
|
|
3867
|
+
readFeatureState(featureId, repoRoot),
|
|
3868
|
+
readFeaturePlan(featureId, repoRoot),
|
|
3869
|
+
readFeatureCheckpoints(featureId, repoRoot),
|
|
3870
|
+
readLatestGateEvidence(featureId, repoRoot),
|
|
3871
|
+
readFeatureQaTestIndex(featureId, repoRoot),
|
|
3872
|
+
]);
|
|
3873
|
+
if (!feature) {
|
|
3874
|
+
return null;
|
|
3875
|
+
}
|
|
3876
|
+
return buildPlanProgressSummary({
|
|
3877
|
+
feature,
|
|
3878
|
+
plan,
|
|
3879
|
+
checkpoints: sortCheckpointsDescending(checkpointState?.checkpoints ?? []),
|
|
3880
|
+
gateEvidence,
|
|
3881
|
+
qaTestIndex,
|
|
3882
|
+
});
|
|
3883
|
+
}
|
|
3884
|
+
|
|
3885
|
+
export async function readFeatureCheckpointsPage(
|
|
3886
|
+
featureId: string,
|
|
3887
|
+
repoRoot = AOP_ROOT,
|
|
3888
|
+
options: { cursor?: string | null; limit?: number; includeIdle?: boolean } = {},
|
|
3889
|
+
): Promise<FeatureCheckpointsPage | null> {
|
|
3890
|
+
const checkpointState = await readFeatureCheckpoints(featureId, repoRoot);
|
|
3891
|
+
if (!checkpointState) {
|
|
3892
|
+
return null;
|
|
3893
|
+
}
|
|
3894
|
+
const checkpoints = sortCheckpointsDescending(checkpointState.checkpoints);
|
|
3895
|
+
const visibleCheckpoints =
|
|
3896
|
+
options.includeIdle === false
|
|
3897
|
+
? checkpoints.filter((checkpoint) => !isIdleCheckpoint(checkpoint))
|
|
3898
|
+
: checkpoints;
|
|
3899
|
+
const limit = Math.min(Math.max(options.limit ?? 20, 1), 100);
|
|
3900
|
+
const parsedCursor = Number.parseInt(options.cursor ?? '0', 10);
|
|
3901
|
+
const offset = Number.isNaN(parsedCursor) ? 0 : Math.max(0, parsedCursor);
|
|
3902
|
+
const items = visibleCheckpoints.slice(offset, offset + limit);
|
|
3903
|
+
return {
|
|
3904
|
+
items,
|
|
3905
|
+
total_count: visibleCheckpoints.length,
|
|
3906
|
+
suppressed_count: checkpoints.length - visibleCheckpoints.length,
|
|
3907
|
+
cursor: String(offset),
|
|
3908
|
+
next_cursor: offset + limit < visibleCheckpoints.length ? String(offset + limit) : null,
|
|
3909
|
+
prev_cursor: offset > 0 ? String(Math.max(0, offset - limit)) : null,
|
|
3910
|
+
limit,
|
|
3911
|
+
};
|
|
3912
|
+
}
|
|
3913
|
+
|
|
3914
|
+
export async function readFeatureRunHistory(
|
|
3915
|
+
featureId: string,
|
|
3916
|
+
repoRoot = AOP_ROOT,
|
|
3917
|
+
): Promise<FeatureRunHistoryEntry[]> {
|
|
3918
|
+
const [featureState, evidence, checkpointState, rawLogs] = await Promise.all([
|
|
3919
|
+
readFile(path.join(getFeatureRoot(featureId, repoRoot), 'state.md'), 'utf8').catch(() => null),
|
|
3920
|
+
listEvidenceArtifacts(featureId, repoRoot),
|
|
3921
|
+
readFeatureCheckpoints(featureId, repoRoot),
|
|
3922
|
+
listRawLogFiles(featureId, repoRoot, { limit: 200 }),
|
|
3923
|
+
]);
|
|
3924
|
+
if (!featureState) {
|
|
3925
|
+
return [];
|
|
3926
|
+
}
|
|
3927
|
+
const frontMatter = parseFrontMatter(featureState).frontMatter;
|
|
3928
|
+
const runHistory = parseRunHistory(frontMatter);
|
|
3929
|
+
return Promise.all(
|
|
3930
|
+
runHistory.map(async (run) => {
|
|
3931
|
+
const evidenceCount = evidence.filter((artifact) =>
|
|
3932
|
+
timestampInWindow(artifact.updated_at, run.started_at, run.ended_at),
|
|
3933
|
+
).length;
|
|
3934
|
+
const checkpointCount = (checkpointState?.checkpoints ?? []).filter((checkpoint) =>
|
|
3935
|
+
timestampInWindow(checkpoint.timestamp, run.started_at, run.ended_at),
|
|
3936
|
+
).length;
|
|
3937
|
+
const rawLogCount = rawLogs.filter((log) =>
|
|
3938
|
+
timestampInWindow(log.timestamp, run.started_at, run.ended_at),
|
|
3939
|
+
).length;
|
|
3940
|
+
const hasEvents = await pathExists(
|
|
3941
|
+
path.join(repoRoot, '.aop', 'runtime', 'worker-events', `${run.run_id}.jsonl`),
|
|
3942
|
+
);
|
|
3943
|
+
const hasOperations = await pathExists(
|
|
3944
|
+
path.join(repoRoot, '.aop', 'runtime', 'operation-ledger', `${run.run_id}.json`),
|
|
3945
|
+
);
|
|
3946
|
+
return {
|
|
3947
|
+
...run,
|
|
3948
|
+
evidence_count: evidenceCount,
|
|
3949
|
+
checkpoint_count: checkpointCount,
|
|
3950
|
+
raw_log_count: rawLogCount,
|
|
3951
|
+
has_events: hasEvents,
|
|
3952
|
+
has_operations: hasOperations,
|
|
3953
|
+
} satisfies FeatureRunHistoryEntry;
|
|
3954
|
+
}),
|
|
3955
|
+
);
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3958
|
+
export async function readFeatureRunHistoryDetail(
|
|
3959
|
+
featureId: string,
|
|
3960
|
+
runId: string,
|
|
3961
|
+
repoRoot = AOP_ROOT,
|
|
3962
|
+
): Promise<FeatureRunHistoryDetail | null> {
|
|
3963
|
+
const [runs, evidence, checkpointState, rawLogs, feature, plan, gateHistory] = await Promise.all([
|
|
3964
|
+
readFeatureRunHistory(featureId, repoRoot),
|
|
3965
|
+
listEvidenceArtifacts(featureId, repoRoot),
|
|
3966
|
+
readFeatureCheckpoints(featureId, repoRoot),
|
|
3967
|
+
listRawLogFiles(featureId, repoRoot, { limit: 200 }),
|
|
3968
|
+
readFeatureState(featureId, repoRoot),
|
|
3969
|
+
readFeaturePlan(featureId, repoRoot),
|
|
3970
|
+
readGateHistory(featureId, repoRoot),
|
|
3971
|
+
]);
|
|
3972
|
+
const run = runs.find((entry) => entry.run_id === runId);
|
|
3973
|
+
if (!run || !feature) {
|
|
3974
|
+
return null;
|
|
3975
|
+
}
|
|
3976
|
+
const revisionEvents = buildRevisionSummary({
|
|
3977
|
+
feature,
|
|
3978
|
+
plan,
|
|
3979
|
+
checkpoints: sortCheckpointsDescending(checkpointState?.checkpoints ?? []),
|
|
3980
|
+
gateHistory,
|
|
3981
|
+
}).filter((entry) => timestampInWindow(entry.timestamp, run.started_at, run.ended_at));
|
|
3982
|
+
return {
|
|
3983
|
+
run,
|
|
3984
|
+
evidence: evidence.filter((artifact) =>
|
|
3985
|
+
timestampInWindow(artifact.updated_at, run.started_at, run.ended_at),
|
|
3986
|
+
),
|
|
3987
|
+
checkpoints: sortCheckpointsDescending(
|
|
3988
|
+
(checkpointState?.checkpoints ?? []).filter((checkpoint) =>
|
|
3989
|
+
timestampInWindow(checkpoint.timestamp, run.started_at, run.ended_at),
|
|
3990
|
+
),
|
|
3991
|
+
),
|
|
3992
|
+
raw_logs: rawLogs.filter((log) =>
|
|
3993
|
+
timestampInWindow(log.timestamp, run.started_at, run.ended_at),
|
|
3994
|
+
),
|
|
3995
|
+
revision_events: revisionEvents,
|
|
3996
|
+
};
|
|
3997
|
+
}
|
|
3998
|
+
|
|
1661
3999
|
export async function readFeatureDetail(
|
|
1662
4000
|
featureId: string,
|
|
1663
4001
|
repoRoot = AOP_ROOT,
|
|
@@ -1668,18 +4006,128 @@ export async function readFeatureDetail(
|
|
|
1668
4006
|
}
|
|
1669
4007
|
|
|
1670
4008
|
const checkpointState = await readFeatureCheckpoints(featureId, repoRoot);
|
|
4009
|
+
const budgetConfig = parseBudgetConfig(await readPolicyYaml(repoRoot));
|
|
4010
|
+
const cost = await readFeatureCost(featureId, repoRoot);
|
|
4011
|
+
const enrichedFeature = enrichFeatureSummary(feature, {
|
|
4012
|
+
cost,
|
|
4013
|
+
budgetConfig,
|
|
4014
|
+
dependencyBlocked: false,
|
|
4015
|
+
});
|
|
4016
|
+
|
|
4017
|
+
const [
|
|
4018
|
+
plan,
|
|
4019
|
+
diff,
|
|
4020
|
+
evidence,
|
|
4021
|
+
gateEvidence,
|
|
4022
|
+
qaTestIndex,
|
|
4023
|
+
reviewBrief,
|
|
4024
|
+
logEntries,
|
|
4025
|
+
collisionRadar,
|
|
4026
|
+
rolePerformance,
|
|
4027
|
+
workerSummary,
|
|
4028
|
+
liveOutputSnapshots,
|
|
4029
|
+
genealogy,
|
|
4030
|
+
gateHistory,
|
|
4031
|
+
mergeQueue,
|
|
4032
|
+
providerAnalytics,
|
|
4033
|
+
spec,
|
|
4034
|
+
intakeReview,
|
|
4035
|
+
] = await Promise.all([
|
|
4036
|
+
readFeaturePlan(featureId, repoRoot),
|
|
4037
|
+
readFeatureDiff(featureId, repoRoot),
|
|
4038
|
+
listEvidenceArtifacts(featureId, repoRoot),
|
|
4039
|
+
readLatestGateEvidence(featureId, repoRoot),
|
|
4040
|
+
readFeatureQaTestIndex(featureId, repoRoot),
|
|
4041
|
+
readFeatureReviewBrief(featureId, repoRoot),
|
|
4042
|
+
readDecisionLogEntries(featureId, repoRoot),
|
|
4043
|
+
buildFeatureCollisionRadar(featureId, repoRoot),
|
|
4044
|
+
buildFeatureRolePerformanceCards(enrichedFeature, repoRoot),
|
|
4045
|
+
readFeatureWorkerEventSummary(featureId, repoRoot, { limit: 200 }),
|
|
4046
|
+
readLiveAgentOutputSnapshots(featureId, repoRoot),
|
|
4047
|
+
readFeatureGenealogy(featureId, repoRoot),
|
|
4048
|
+
readGateHistory(featureId, repoRoot),
|
|
4049
|
+
readMergeQueueSnapshot(featureId, repoRoot),
|
|
4050
|
+
readProviderAnalyticsSnapshot(repoRoot),
|
|
4051
|
+
readFeatureSpec(featureId, repoRoot, { includeMarkdown: false }),
|
|
4052
|
+
readFeatureIntakeReview(featureId, repoRoot),
|
|
4053
|
+
]);
|
|
4054
|
+
const questions = await readFeatureQuestions(featureId, repoRoot, enrichedFeature);
|
|
4055
|
+
|
|
4056
|
+
const usageBurn = buildUsageBurnProjection(
|
|
4057
|
+
cost,
|
|
4058
|
+
workerSummary.entries,
|
|
4059
|
+
resolveUsageBaseline(workerSummary.entries, providerAnalytics),
|
|
4060
|
+
budgetConfig.per_feature_limit_tokens,
|
|
4061
|
+
budgetConfig.token_cost_conversion,
|
|
4062
|
+
enrichedFeature.phase ?? 'unknown',
|
|
4063
|
+
);
|
|
4064
|
+
const checkpoints = sortCheckpointsDescending(checkpointState?.checkpoints ?? []);
|
|
4065
|
+
const planProgress = buildPlanProgressSummary({
|
|
4066
|
+
feature: enrichedFeature,
|
|
4067
|
+
plan,
|
|
4068
|
+
checkpoints,
|
|
4069
|
+
gateEvidence,
|
|
4070
|
+
qaTestIndex,
|
|
4071
|
+
});
|
|
4072
|
+
const verificationSummary = buildVerificationSummary({
|
|
4073
|
+
feature: enrichedFeature,
|
|
4074
|
+
gateEvidence,
|
|
4075
|
+
qaTestIndex,
|
|
4076
|
+
usageBurn,
|
|
4077
|
+
});
|
|
4078
|
+
const revisionSummary = buildRevisionSummary({
|
|
4079
|
+
feature: enrichedFeature,
|
|
4080
|
+
plan,
|
|
4081
|
+
checkpoints,
|
|
4082
|
+
gateHistory,
|
|
4083
|
+
});
|
|
4084
|
+
const actionableRisks = buildActionableRisks(plan, enrichedFeature);
|
|
1671
4085
|
|
|
1672
4086
|
return {
|
|
1673
|
-
feature,
|
|
1674
|
-
plan
|
|
1675
|
-
diff
|
|
1676
|
-
evidence
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
4087
|
+
feature: enrichedFeature,
|
|
4088
|
+
plan,
|
|
4089
|
+
diff,
|
|
4090
|
+
evidence,
|
|
4091
|
+
spec,
|
|
4092
|
+
plan_progress: planProgress,
|
|
4093
|
+
verification_summary: verificationSummary,
|
|
4094
|
+
revision_summary: revisionSummary,
|
|
4095
|
+
actionable_risks: actionableRisks,
|
|
4096
|
+
gate_evidence: gateEvidence,
|
|
4097
|
+
cost,
|
|
4098
|
+
budget_limit_tokens: budgetConfig.per_feature_limit_tokens,
|
|
4099
|
+
qa_test_index: qaTestIndex,
|
|
4100
|
+
review_brief: reviewBrief,
|
|
4101
|
+
log_entries: logEntries.slice(0, 50),
|
|
4102
|
+
execution_mode: checkpointState?.execution_mode ?? 'interactive',
|
|
4103
|
+
checkpoints,
|
|
4104
|
+
collision_radar: collisionRadar,
|
|
4105
|
+
role_performance: rolePerformance,
|
|
4106
|
+
usage_burn: usageBurn,
|
|
4107
|
+
genealogy,
|
|
4108
|
+
gate_history: gateHistory,
|
|
4109
|
+
merge_queue: mergeQueue,
|
|
4110
|
+
worker_summary: workerSummary,
|
|
4111
|
+
live_output_snapshots: summarizeLiveAgentOutputSnapshots(liveOutputSnapshots),
|
|
4112
|
+
provider_analytics: providerAnalytics,
|
|
4113
|
+
token_cost_conversion: budgetConfig.token_cost_conversion,
|
|
4114
|
+
questions: questions.filter(
|
|
4115
|
+
(question) =>
|
|
4116
|
+
question.status !== 'open' || isOperatorReadyQuestion(enrichedFeature, question),
|
|
4117
|
+
),
|
|
4118
|
+
intake:
|
|
4119
|
+
enrichedFeature.intake_status != null
|
|
4120
|
+
? {
|
|
4121
|
+
summary: {
|
|
4122
|
+
status: enrichedFeature.intake_status,
|
|
4123
|
+
bootstrap_manifest_version: enrichedFeature.bootstrap_manifest_version ?? null,
|
|
4124
|
+
verified_manifest_version: enrichedFeature.verified_manifest_version ?? null,
|
|
4125
|
+
open_ambiguity_count: enrichedFeature.open_ambiguity_count ?? 0,
|
|
4126
|
+
last_verified_at: enrichedFeature.last_verified_at ?? null,
|
|
4127
|
+
promotion_basis: enrichedFeature.promotion_basis ?? null,
|
|
4128
|
+
},
|
|
4129
|
+
review: intakeReview,
|
|
4130
|
+
}
|
|
4131
|
+
: null,
|
|
1684
4132
|
};
|
|
1685
4133
|
}
|