mcoda 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -2
- package/README.md +9 -300
- package/dist/bin/McodaEntrypoint.d.ts +5 -0
- package/dist/bin/McodaEntrypoint.d.ts.map +1 -0
- package/dist/bin/McodaEntrypoint.js +175 -0
- package/dist/commands/agents/AgentsCommands.d.ts +4 -0
- package/dist/commands/agents/AgentsCommands.d.ts.map +1 -0
- package/dist/commands/agents/AgentsCommands.js +376 -0
- package/dist/commands/agents/GatewayAgentCommand.d.ts +4 -0
- package/dist/commands/agents/GatewayAgentCommand.d.ts.map +1 -0
- package/dist/commands/agents/GatewayAgentCommand.js +583 -0
- package/dist/commands/agents/TestAgentCommand.d.ts +4 -0
- package/dist/commands/agents/TestAgentCommand.d.ts.map +1 -0
- package/dist/commands/agents/TestAgentCommand.js +57 -0
- package/dist/commands/backlog/BacklogCommands.d.ts +17 -0
- package/dist/commands/backlog/BacklogCommands.d.ts.map +1 -0
- package/dist/commands/backlog/BacklogCommands.js +260 -0
- package/dist/commands/backlog/OrderTasksCommand.d.ts +16 -0
- package/dist/commands/backlog/OrderTasksCommand.d.ts.map +1 -0
- package/dist/commands/backlog/OrderTasksCommand.js +211 -0
- package/dist/commands/backlog/TaskShowCommands.d.ts +16 -0
- package/dist/commands/backlog/TaskShowCommands.d.ts.map +1 -0
- package/dist/commands/backlog/TaskShowCommands.js +275 -0
- package/dist/commands/docs/DocsCommands.d.ts +37 -0
- package/dist/commands/docs/DocsCommands.d.ts.map +1 -0
- package/dist/commands/docs/DocsCommands.js +381 -0
- package/dist/commands/estimate/EstimateCommands.d.ts +24 -0
- package/dist/commands/estimate/EstimateCommands.d.ts.map +1 -0
- package/dist/commands/estimate/EstimateCommands.js +259 -0
- package/dist/commands/jobs/JobsCommands.d.ts +24 -0
- package/dist/commands/jobs/JobsCommands.d.ts.map +1 -0
- package/dist/commands/jobs/JobsCommands.js +535 -0
- package/dist/commands/openapi/OpenapiCommands.d.ts +14 -0
- package/dist/commands/openapi/OpenapiCommands.d.ts.map +1 -0
- package/dist/commands/openapi/OpenapiCommands.js +157 -0
- package/dist/commands/planning/CreateTasksCommand.d.ts +17 -0
- package/dist/commands/planning/CreateTasksCommand.d.ts.map +1 -0
- package/dist/commands/planning/CreateTasksCommand.js +134 -0
- package/dist/commands/planning/MigrateTasksCommand.d.ts +15 -0
- package/dist/commands/planning/MigrateTasksCommand.d.ts.map +1 -0
- package/dist/commands/planning/MigrateTasksCommand.js +95 -0
- package/dist/commands/planning/PlanningCommands.d.ts +3 -0
- package/dist/commands/planning/PlanningCommands.d.ts.map +1 -0
- package/dist/commands/planning/PlanningCommands.js +2 -0
- package/dist/commands/planning/QaTasksCommand.d.ts +30 -0
- package/dist/commands/planning/QaTasksCommand.d.ts.map +1 -0
- package/dist/commands/planning/QaTasksCommand.js +293 -0
- package/dist/commands/planning/RefineTasksCommand.d.ts +30 -0
- package/dist/commands/planning/RefineTasksCommand.d.ts.map +1 -0
- package/dist/commands/planning/RefineTasksCommand.js +365 -0
- package/dist/commands/review/CodeReviewCommand.d.ts +21 -0
- package/dist/commands/review/CodeReviewCommand.d.ts.map +1 -0
- package/dist/commands/review/CodeReviewCommand.js +236 -0
- package/dist/commands/routing/RoutingCommands.d.ts +7 -0
- package/dist/commands/routing/RoutingCommands.d.ts.map +1 -0
- package/dist/commands/routing/RoutingCommands.js +484 -0
- package/dist/commands/telemetry/TelemetryCommands.d.ts +26 -0
- package/dist/commands/telemetry/TelemetryCommands.d.ts.map +1 -0
- package/dist/commands/telemetry/TelemetryCommands.js +313 -0
- package/dist/commands/update/UpdateCommands.d.ts +4 -0
- package/dist/commands/update/UpdateCommands.d.ts.map +1 -0
- package/dist/commands/update/UpdateCommands.js +280 -0
- package/dist/commands/work/WorkOnTasksCommand.d.ts +21 -0
- package/dist/commands/work/WorkOnTasksCommand.d.ts.map +1 -0
- package/dist/commands/work/WorkOnTasksCommand.js +238 -0
- package/dist/commands/workspace/SetWorkspaceCommand.d.ts +9 -0
- package/dist/commands/workspace/SetWorkspaceCommand.d.ts.map +1 -0
- package/dist/commands/workspace/SetWorkspaceCommand.js +128 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/package.json +31 -16
- package/.editorconfig +0 -9
- package/.eslintrc.cjs +0 -12
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -29
- package/.github/ISSUE_TEMPLATE/config.yml +0 -5
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
- package/.github/workflows/ci.yml +0 -37
- package/.github/workflows/nightly.yml +0 -38
- package/.github/workflows/release-dry-run.yml +0 -40
- package/.github/workflows/release-please.yml +0 -22
- package/.github/workflows/release.yml +0 -139
- package/.prettierrc +0 -5
- package/.release-please-manifest.json +0 -8
- package/CLA.md +0 -42
- package/CONTRIBUTING.md +0 -38
- package/docs/oss_publishing_plan.md +0 -41
- package/docs/pdr/.gitkeep +0 -0
- package/docs/quality_gates.md +0 -32
- package/docs/rfp/.gitkeep +0 -0
- package/docs/sds/sds.md +0 -11963
- package/docs/usage.md +0 -72
- package/openapi/gen-openapi.ts +0 -1
- package/openapi/generated/clients/.gitkeep +0 -0
- package/openapi/generated/types/.gitkeep +0 -0
- package/openapi/generated/types/index.ts +0 -118
- package/openapi/mcoda.yaml +0 -2063
- package/pack-mcoda.sh +0 -88
- package/packages/agents/CHANGELOG.md +0 -7
- package/packages/agents/LICENSE +0 -21
- package/packages/agents/README.md +0 -9
- package/packages/agents/package.json +0 -41
- package/packages/agents/src/AgentService/.gitkeep +0 -0
- package/packages/agents/src/AgentService/AgentService.d.ts +0 -21
- package/packages/agents/src/AgentService/AgentService.d.ts.map +0 -1
- package/packages/agents/src/AgentService/AgentService.js +0 -141
- package/packages/agents/src/AgentService/AgentService.ts +0 -308
- package/packages/agents/src/__tests__/AgentService.test.ts +0 -284
- package/packages/agents/src/adapters/AdapterTypes.d.ts +0 -29
- package/packages/agents/src/adapters/AdapterTypes.d.ts.map +0 -1
- package/packages/agents/src/adapters/AdapterTypes.js +0 -1
- package/packages/agents/src/adapters/AdapterTypes.ts +0 -32
- package/packages/agents/src/adapters/codex/.gitkeep +0 -0
- package/packages/agents/src/adapters/codex/CodexAdapter.d.ts +0 -11
- package/packages/agents/src/adapters/codex/CodexAdapter.d.ts.map +0 -1
- package/packages/agents/src/adapters/codex/CodexAdapter.js +0 -43
- package/packages/agents/src/adapters/codex/CodexAdapter.ts +0 -63
- package/packages/agents/src/adapters/codex/CodexCliRunner.ts +0 -154
- package/packages/agents/src/adapters/gemini/.gitkeep +0 -0
- package/packages/agents/src/adapters/gemini/GeminiAdapter.d.ts +0 -11
- package/packages/agents/src/adapters/gemini/GeminiAdapter.d.ts.map +0 -1
- package/packages/agents/src/adapters/gemini/GeminiAdapter.js +0 -42
- package/packages/agents/src/adapters/gemini/GeminiAdapter.ts +0 -58
- package/packages/agents/src/adapters/gemini/GeminiCliRunner.ts +0 -75
- package/packages/agents/src/adapters/local/.gitkeep +0 -0
- package/packages/agents/src/adapters/local/LocalAdapter.d.ts +0 -11
- package/packages/agents/src/adapters/local/LocalAdapter.d.ts.map +0 -1
- package/packages/agents/src/adapters/local/LocalAdapter.js +0 -38
- package/packages/agents/src/adapters/local/LocalAdapter.ts +0 -43
- package/packages/agents/src/adapters/ollama/OllamaCliAdapter.ts +0 -58
- package/packages/agents/src/adapters/ollama/OllamaCliRunner.ts +0 -70
- package/packages/agents/src/adapters/ollama/OllamaRemoteAdapter.ts +0 -205
- package/packages/agents/src/adapters/openai/.gitkeep +0 -0
- package/packages/agents/src/adapters/openai/OpenAiAdapter.d.ts +0 -11
- package/packages/agents/src/adapters/openai/OpenAiAdapter.d.ts.map +0 -1
- package/packages/agents/src/adapters/openai/OpenAiAdapter.js +0 -51
- package/packages/agents/src/adapters/openai/OpenAiAdapter.ts +0 -56
- package/packages/agents/src/adapters/openai/OpenAiCliAdapter.ts +0 -62
- package/packages/agents/src/adapters/qa/.gitkeep +0 -0
- package/packages/agents/src/adapters/qa/QaAdapter.d.ts +0 -11
- package/packages/agents/src/adapters/qa/QaAdapter.d.ts.map +0 -1
- package/packages/agents/src/adapters/qa/QaAdapter.js +0 -37
- package/packages/agents/src/adapters/qa/QaAdapter.ts +0 -42
- package/packages/agents/src/adapters/zhipu/ZhipuApiAdapter.ts +0 -273
- package/packages/agents/src/index.d.ts +0 -8
- package/packages/agents/src/index.d.ts.map +0 -1
- package/packages/agents/src/index.js +0 -7
- package/packages/agents/src/index.ts +0 -11
- package/packages/agents/tsconfig.json +0 -14
- package/packages/cli/CHANGELOG.md +0 -7
- package/packages/cli/LICENSE +0 -21
- package/packages/cli/README.md +0 -23
- package/packages/cli/package.json +0 -61
- package/packages/cli/src/__tests__/AgentsCommands.test.ts +0 -137
- package/packages/cli/src/__tests__/BacklogCommands.test.ts +0 -40
- package/packages/cli/src/__tests__/CodeReviewCommand.test.ts +0 -594
- package/packages/cli/src/__tests__/CreateTasksCommand.test.ts +0 -40
- package/packages/cli/src/__tests__/DocsCommands.test.ts +0 -41
- package/packages/cli/src/__tests__/EstimateCommands.test.ts +0 -54
- package/packages/cli/src/__tests__/JobsCommands.behavior.test.ts +0 -311
- package/packages/cli/src/__tests__/JobsCommands.test.ts +0 -49
- package/packages/cli/src/__tests__/MigrateTasksCommand.test.ts +0 -36
- package/packages/cli/src/__tests__/OpenapiCommands.test.ts +0 -34
- package/packages/cli/src/__tests__/OrderTasksCommand.test.ts +0 -150
- package/packages/cli/src/__tests__/PlanningCommands.test.ts +0 -9
- package/packages/cli/src/__tests__/QaTasksCommand.test.ts +0 -58
- package/packages/cli/src/__tests__/RefineTasksCommand.test.ts +0 -63
- package/packages/cli/src/__tests__/RoutingCommands.test.ts +0 -302
- package/packages/cli/src/__tests__/SetWorkspaceCommand.test.ts +0 -18
- package/packages/cli/src/__tests__/TaskShowCommands.test.ts +0 -130
- package/packages/cli/src/__tests__/TelemetryCommands.test.ts +0 -35
- package/packages/cli/src/__tests__/TestAgentCommand.test.ts +0 -41
- package/packages/cli/src/__tests__/UpdateCommands.test.ts +0 -292
- package/packages/cli/src/__tests__/WorkOnTasksCommand.test.ts +0 -42
- package/packages/cli/src/bin/.gitkeep +0 -0
- package/packages/cli/src/bin/McodaEntrypoint.ts +0 -180
- package/packages/cli/src/commands/agents/.gitkeep +0 -0
- package/packages/cli/src/commands/agents/AgentsCommands.ts +0 -374
- package/packages/cli/src/commands/agents/GatewayAgentCommand.ts +0 -621
- package/packages/cli/src/commands/agents/TestAgentCommand.ts +0 -63
- package/packages/cli/src/commands/backlog/.gitkeep +0 -0
- package/packages/cli/src/commands/backlog/BacklogCommands.ts +0 -286
- package/packages/cli/src/commands/backlog/OrderTasksCommand.ts +0 -237
- package/packages/cli/src/commands/backlog/TaskShowCommands.ts +0 -289
- package/packages/cli/src/commands/docs/.gitkeep +0 -0
- package/packages/cli/src/commands/docs/DocsCommands.ts +0 -413
- package/packages/cli/src/commands/estimate/EstimateCommands.ts +0 -290
- package/packages/cli/src/commands/jobs/.gitkeep +0 -0
- package/packages/cli/src/commands/jobs/JobsCommands.ts +0 -595
- package/packages/cli/src/commands/openapi/OpenapiCommands.ts +0 -167
- package/packages/cli/src/commands/planning/.gitkeep +0 -0
- package/packages/cli/src/commands/planning/CreateTasksCommand.ts +0 -149
- package/packages/cli/src/commands/planning/MigrateTasksCommand.ts +0 -105
- package/packages/cli/src/commands/planning/PlanningCommands.ts +0 -1
- package/packages/cli/src/commands/planning/QaTasksCommand.ts +0 -320
- package/packages/cli/src/commands/planning/RefineTasksCommand.ts +0 -408
- package/packages/cli/src/commands/review/CodeReviewCommand.ts +0 -262
- package/packages/cli/src/commands/routing/.gitkeep +0 -0
- package/packages/cli/src/commands/routing/RoutingCommands.ts +0 -554
- package/packages/cli/src/commands/telemetry/.gitkeep +0 -0
- package/packages/cli/src/commands/telemetry/TelemetryCommands.ts +0 -348
- package/packages/cli/src/commands/update/.gitkeep +0 -0
- package/packages/cli/src/commands/update/UpdateCommands.ts +0 -301
- package/packages/cli/src/commands/work/WorkOnTasksCommand.ts +0 -264
- package/packages/cli/src/commands/workspace/SetWorkspaceCommand.ts +0 -132
- package/packages/cli/test/packaging_guardrails.test.js +0 -75
- package/packages/cli/tsconfig.json +0 -20
- package/packages/core/CHANGELOG.md +0 -7
- package/packages/core/LICENSE +0 -21
- package/packages/core/README.md +0 -9
- package/packages/core/package.json +0 -45
- package/packages/core/src/__tests__/SmokeClasses.test.ts +0 -32
- package/packages/core/src/api/AgentsApi.ts +0 -219
- package/packages/core/src/api/QaTasksApi.ts +0 -38
- package/packages/core/src/api/TasksApi.ts +0 -35
- package/packages/core/src/api/__tests__/AgentsApi.test.ts +0 -203
- package/packages/core/src/api/__tests__/QaTasksApi.test.ts +0 -51
- package/packages/core/src/api/__tests__/TasksApi.test.ts +0 -56
- package/packages/core/src/config/.gitkeep +0 -0
- package/packages/core/src/config/ConfigService.ts +0 -1
- package/packages/core/src/domain/dependencies/.gitkeep +0 -0
- package/packages/core/src/domain/dependencies/Dependency.ts +0 -1
- package/packages/core/src/domain/epics/.gitkeep +0 -0
- package/packages/core/src/domain/epics/Epic.ts +0 -1
- package/packages/core/src/domain/projects/.gitkeep +0 -0
- package/packages/core/src/domain/projects/Project.ts +0 -1
- package/packages/core/src/domain/tasks/.gitkeep +0 -0
- package/packages/core/src/domain/tasks/Task.ts +0 -1
- package/packages/core/src/domain/userStories/.gitkeep +0 -0
- package/packages/core/src/domain/userStories/UserStory.ts +0 -1
- package/packages/core/src/index.ts +0 -27
- package/packages/core/src/prompts/.gitkeep +0 -0
- package/packages/core/src/prompts/PdrPrompts.ts +0 -23
- package/packages/core/src/prompts/PromptLoader.ts +0 -1
- package/packages/core/src/prompts/SdsPrompts.ts +0 -47
- package/packages/core/src/services/agents/.gitkeep +0 -0
- package/packages/core/src/services/agents/AgentManagementService.ts +0 -1
- package/packages/core/src/services/agents/GatewayAgentService.ts +0 -956
- package/packages/core/src/services/agents/RoutingService.ts +0 -461
- package/packages/core/src/services/agents/__tests__/GatewayAgentService.test.ts +0 -72
- package/packages/core/src/services/agents/__tests__/RoutingService.test.ts +0 -267
- package/packages/core/src/services/agents/generated/RoutingApiClient.ts +0 -89
- package/packages/core/src/services/backlog/.gitkeep +0 -0
- package/packages/core/src/services/backlog/BacklogService.ts +0 -580
- package/packages/core/src/services/backlog/TaskOrderingService.ts +0 -868
- package/packages/core/src/services/backlog/__tests__/BacklogService.test.ts +0 -219
- package/packages/core/src/services/backlog/__tests__/TaskOrderingService.test.ts +0 -268
- package/packages/core/src/services/docs/.gitkeep +0 -0
- package/packages/core/src/services/docs/DocsService.ts +0 -1913
- package/packages/core/src/services/docs/__tests__/DocsService.test.ts +0 -350
- package/packages/core/src/services/estimate/EstimateService.ts +0 -111
- package/packages/core/src/services/estimate/VelocityService.ts +0 -272
- package/packages/core/src/services/estimate/__tests__/VelocityAndEstimate.test.ts +0 -209
- package/packages/core/src/services/estimate/types.ts +0 -41
- package/packages/core/src/services/execution/.gitkeep +0 -0
- package/packages/core/src/services/execution/ExecutionService.ts +0 -1
- package/packages/core/src/services/execution/QaFollowupService.ts +0 -289
- package/packages/core/src/services/execution/QaProfileService.ts +0 -160
- package/packages/core/src/services/execution/QaTasksService.ts +0 -1303
- package/packages/core/src/services/execution/TaskSelectionService.ts +0 -362
- package/packages/core/src/services/execution/TaskStateService.ts +0 -64
- package/packages/core/src/services/execution/WorkOnTasksService.ts +0 -2023
- package/packages/core/src/services/execution/__tests__/QaFollowupService.test.ts +0 -58
- package/packages/core/src/services/execution/__tests__/QaProfileService.test.ts +0 -49
- package/packages/core/src/services/execution/__tests__/QaTasksService.test.ts +0 -157
- package/packages/core/src/services/execution/__tests__/TaskSelectionService.test.ts +0 -179
- package/packages/core/src/services/execution/__tests__/TaskStateService.test.ts +0 -51
- package/packages/core/src/services/execution/__tests__/WorkOnTasksService.test.ts +0 -285
- package/packages/core/src/services/jobs/.gitkeep +0 -0
- package/packages/core/src/services/jobs/JobInsightsService.ts +0 -355
- package/packages/core/src/services/jobs/JobResumeService.ts +0 -119
- package/packages/core/src/services/jobs/JobService.ts +0 -648
- package/packages/core/src/services/jobs/JobsApiClient.ts +0 -113
- package/packages/core/src/services/jobs/__tests__/JobInsightsService.test.ts +0 -17
- package/packages/core/src/services/jobs/__tests__/JobResumeService.test.ts +0 -45
- package/packages/core/src/services/jobs/__tests__/JobService.test.ts +0 -44
- package/packages/core/src/services/openapi/OpenApiService.ts +0 -558
- package/packages/core/src/services/openapi/__tests__/OpenApiService.test.ts +0 -57
- package/packages/core/src/services/planning/.gitkeep +0 -0
- package/packages/core/src/services/planning/CreateTasksService.ts +0 -1280
- package/packages/core/src/services/planning/KeyHelpers.ts +0 -80
- package/packages/core/src/services/planning/PlanningService.ts +0 -1
- package/packages/core/src/services/planning/RefineTasksService.ts +0 -1552
- package/packages/core/src/services/planning/__tests__/CreateTasksService.test.ts +0 -288
- package/packages/core/src/services/planning/__tests__/KeyHelpers.test.ts +0 -16
- package/packages/core/src/services/planning/__tests__/RefineTasksService.test.ts +0 -172
- package/packages/core/src/services/review/CodeReviewService.ts +0 -1386
- package/packages/core/src/services/review/__tests__/CodeReviewService.test.ts +0 -89
- package/packages/core/src/services/system/SystemUpdateService.ts +0 -177
- package/packages/core/src/services/system/__tests__/SystemUpdateService.test.ts +0 -40
- package/packages/core/src/services/tasks/TaskApiResolver.ts +0 -37
- package/packages/core/src/services/tasks/TaskDetailService.ts +0 -494
- package/packages/core/src/services/tasks/__tests__/TaskApiResolver.test.ts +0 -41
- package/packages/core/src/services/tasks/__tests__/TaskDetailService.test.ts +0 -178
- package/packages/core/src/services/telemetry/.gitkeep +0 -0
- package/packages/core/src/services/telemetry/TelemetryService.ts +0 -515
- package/packages/core/src/services/telemetry/__tests__/TelemetryService.test.ts +0 -160
- package/packages/core/src/workspace/.gitkeep +0 -0
- package/packages/core/src/workspace/WorkspaceManager.ts +0 -234
- package/packages/core/tsconfig.json +0 -20
- package/packages/db/CHANGELOG.md +0 -7
- package/packages/db/LICENSE +0 -21
- package/packages/db/README.md +0 -9
- package/packages/db/package.json +0 -42
- package/packages/db/src/__tests__/GlobalRepository.test.ts +0 -109
- package/packages/db/src/__tests__/SchemaAlignment.test.ts +0 -80
- package/packages/db/src/__tests__/WorkspaceRepository.test.ts +0 -19
- package/packages/db/src/index.d.ts +0 -6
- package/packages/db/src/index.d.ts.map +0 -1
- package/packages/db/src/index.js +0 -5
- package/packages/db/src/index.ts +0 -6
- package/packages/db/src/migrations/global/.gitkeep +0 -0
- package/packages/db/src/migrations/global/GlobalMigrations.d.ts +0 -9
- package/packages/db/src/migrations/global/GlobalMigrations.d.ts.map +0 -1
- package/packages/db/src/migrations/global/GlobalMigrations.js +0 -68
- package/packages/db/src/migrations/global/GlobalMigrations.ts +0 -336
- package/packages/db/src/migrations/workspace/.gitkeep +0 -0
- package/packages/db/src/migrations/workspace/WorkspaceMigrations.d.ts +0 -9
- package/packages/db/src/migrations/workspace/WorkspaceMigrations.d.ts.map +0 -1
- package/packages/db/src/migrations/workspace/WorkspaceMigrations.js +0 -251
- package/packages/db/src/migrations/workspace/WorkspaceMigrations.ts +0 -248
- package/packages/db/src/repositories/global/.gitkeep +0 -0
- package/packages/db/src/repositories/global/GlobalRepository.d.ts +0 -30
- package/packages/db/src/repositories/global/GlobalRepository.d.ts.map +0 -1
- package/packages/db/src/repositories/global/GlobalRepository.js +0 -209
- package/packages/db/src/repositories/global/GlobalRepository.ts +0 -492
- package/packages/db/src/repositories/workspace/.gitkeep +0 -0
- package/packages/db/src/repositories/workspace/WorkspaceRepository.d.ts +0 -282
- package/packages/db/src/repositories/workspace/WorkspaceRepository.d.ts.map +0 -1
- package/packages/db/src/repositories/workspace/WorkspaceRepository.js +0 -773
- package/packages/db/src/repositories/workspace/WorkspaceRepository.ts +0 -1511
- package/packages/db/src/sqlite/connection.d.ts +0 -11
- package/packages/db/src/sqlite/connection.d.ts.map +0 -1
- package/packages/db/src/sqlite/connection.js +0 -31
- package/packages/db/src/sqlite/connection.ts +0 -35
- package/packages/db/src/sqlite/pragmas.d.ts +0 -5
- package/packages/db/src/sqlite/pragmas.d.ts.map +0 -1
- package/packages/db/src/sqlite/pragmas.js +0 -6
- package/packages/db/src/sqlite/pragmas.ts +0 -10
- package/packages/db/tsconfig.json +0 -13
- package/packages/generators/package.json +0 -21
- package/packages/generators/src/__tests__/Generators.test.ts +0 -19
- package/packages/generators/src/index.ts +0 -1
- package/packages/generators/src/openapi/generateTypes.ts +0 -1
- package/packages/generators/src/openapi/validateSchema.ts +0 -1
- package/packages/generators/src/scaffolding/docs/.gitkeep +0 -0
- package/packages/generators/src/scaffolding/docs/DocsScaffolder.ts +0 -1
- package/packages/generators/src/scaffolding/global/.gitkeep +0 -0
- package/packages/generators/src/scaffolding/global/GlobalScaffolder.ts +0 -1
- package/packages/generators/src/scaffolding/workspace/.gitkeep +0 -0
- package/packages/generators/src/scaffolding/workspace/WorkspaceScaffolder.ts +0 -1
- package/packages/generators/tsconfig.json +0 -10
- package/packages/integrations/CHANGELOG.md +0 -7
- package/packages/integrations/LICENSE +0 -21
- package/packages/integrations/README.md +0 -9
- package/packages/integrations/package.json +0 -47
- package/packages/integrations/src/docdex/.gitkeep +0 -0
- package/packages/integrations/src/docdex/DocdexClient.d.ts +0 -50
- package/packages/integrations/src/docdex/DocdexClient.d.ts.map +0 -1
- package/packages/integrations/src/docdex/DocdexClient.js +0 -216
- package/packages/integrations/src/docdex/DocdexClient.ts +0 -261
- package/packages/integrations/src/docdex/__tests__/DocdexClient.test.ts +0 -29
- package/packages/integrations/src/index.d.ts +0 -2
- package/packages/integrations/src/index.d.ts.map +0 -1
- package/packages/integrations/src/index.js +0 -4
- package/packages/integrations/src/index.ts +0 -5
- package/packages/integrations/src/issues/.gitkeep +0 -0
- package/packages/integrations/src/issues/IssuesClient.ts +0 -1
- package/packages/integrations/src/issues/__tests__/IssuesClient.test.ts +0 -10
- package/packages/integrations/src/qa/.gitkeep +0 -0
- package/packages/integrations/src/qa/ChromiumQaAdapter.ts +0 -89
- package/packages/integrations/src/qa/CliQaAdapter.ts +0 -95
- package/packages/integrations/src/qa/MaestroQaAdapter.ts +0 -91
- package/packages/integrations/src/qa/QaAdapter.ts +0 -7
- package/packages/integrations/src/qa/QaClient.ts +0 -1
- package/packages/integrations/src/qa/QaTypes.ts +0 -26
- package/packages/integrations/src/qa/__tests__/ChromiumQaAdapter.test.ts +0 -30
- package/packages/integrations/src/qa/__tests__/CliQaAdapter.test.ts +0 -33
- package/packages/integrations/src/qa/__tests__/MaestroQaAdapter.test.ts +0 -30
- package/packages/integrations/src/qa/index.ts +0 -5
- package/packages/integrations/src/system/SystemClient.ts +0 -50
- package/packages/integrations/src/system/__tests__/SystemClient.test.ts +0 -40
- package/packages/integrations/src/telemetry/TelemetryClient.ts +0 -139
- package/packages/integrations/src/telemetry/__tests__/TelemetryClient.test.ts +0 -41
- package/packages/integrations/src/vcs/.gitkeep +0 -0
- package/packages/integrations/src/vcs/VcsClient.ts +0 -211
- package/packages/integrations/src/vcs/__tests__/VcsClient.test.ts +0 -26
- package/packages/integrations/tsconfig.json +0 -14
- package/packages/shared/CHANGELOG.md +0 -7
- package/packages/shared/LICENSE +0 -21
- package/packages/shared/README.md +0 -9
- package/packages/shared/package.json +0 -40
- package/packages/shared/src/__tests__/CommandMetadata.test.ts +0 -15
- package/packages/shared/src/__tests__/ServiceShells.test.ts +0 -16
- package/packages/shared/src/crypto/.gitkeep +0 -0
- package/packages/shared/src/crypto/CryptoHelper.d.ts +0 -15
- package/packages/shared/src/crypto/CryptoHelper.d.ts.map +0 -1
- package/packages/shared/src/crypto/CryptoHelper.js +0 -54
- package/packages/shared/src/crypto/CryptoHelper.ts +0 -57
- package/packages/shared/src/errors/.gitkeep +0 -0
- package/packages/shared/src/errors/ErrorFactory.ts +0 -1
- package/packages/shared/src/index.d.ts +0 -6
- package/packages/shared/src/index.d.ts.map +0 -1
- package/packages/shared/src/index.js +0 -4
- package/packages/shared/src/index.ts +0 -35
- package/packages/shared/src/logging/.gitkeep +0 -0
- package/packages/shared/src/logging/Logger.ts +0 -1
- package/packages/shared/src/metadata/CommandMetadata.ts +0 -165
- package/packages/shared/src/openapi/.gitkeep +0 -0
- package/packages/shared/src/openapi/OpenApiTypes.d.ts +0 -216
- package/packages/shared/src/openapi/OpenApiTypes.d.ts.map +0 -1
- package/packages/shared/src/openapi/OpenApiTypes.js +0 -1
- package/packages/shared/src/openapi/OpenApiTypes.ts +0 -312
- package/packages/shared/src/paths/.gitkeep +0 -0
- package/packages/shared/src/paths/PathHelper.d.ts +0 -12
- package/packages/shared/src/paths/PathHelper.d.ts.map +0 -1
- package/packages/shared/src/paths/PathHelper.js +0 -24
- package/packages/shared/src/paths/PathHelper.ts +0 -29
- package/packages/shared/src/qa/QaProfile.ts +0 -14
- package/packages/shared/src/utils/.gitkeep +0 -0
- package/packages/shared/src/utils/UtilityService.ts +0 -1
- package/packages/shared/tsconfig.json +0 -10
- package/packages/testing/package.json +0 -26
- package/packages/testing/src/__tests__/TestingFakes.test.ts +0 -15
- package/packages/testing/src/cli/e2e/.gitkeep +0 -0
- package/packages/testing/src/cli/e2e/E2eSuite.ts +0 -1
- package/packages/testing/src/fakes/agents/.gitkeep +0 -0
- package/packages/testing/src/fakes/agents/FakeAgents.ts +0 -1
- package/packages/testing/src/fakes/docdex/.gitkeep +0 -0
- package/packages/testing/src/fakes/docdex/FakeDocdexClient.ts +0 -1
- package/packages/testing/src/fakes/qa/.gitkeep +0 -0
- package/packages/testing/src/fakes/qa/FakeQaClient.ts +0 -1
- package/packages/testing/src/fakes/vcs/.gitkeep +0 -0
- package/packages/testing/src/fakes/vcs/FakeVcsClient.ts +0 -1
- package/packages/testing/src/fixtures/db/.gitkeep +0 -0
- package/packages/testing/src/fixtures/db/DbFixtures.ts +0 -1
- package/packages/testing/src/fixtures/workspaces/.gitkeep +0 -0
- package/packages/testing/src/fixtures/workspaces/WorkspaceFixtures.ts +0 -1
- package/packages/testing/src/index.ts +0 -1
- package/packages/testing/tsconfig.json +0 -10
- package/pnpm-workspace.yaml +0 -2
- package/prompts/README.md +0 -5
- package/prompts/code-reviewer.md +0 -23
- package/prompts/code-writer.md +0 -35
- package/prompts/gateway-agent.md +0 -27
- package/prompts/qa-agent.md +0 -21
- package/release-please-config.json +0 -39
- package/scripts/build-all.ts +0 -1
- package/scripts/dev.ts +0 -1
- package/scripts/install-local-cli.sh +0 -28
- package/scripts/pack-npm-tarballs.js +0 -63
- package/scripts/release.ts +0 -1
- package/scripts/run-node-tests.js +0 -37
- package/tests/all.js +0 -127
- package/tests/api/openapi_spec.test.js +0 -21
- package/tests/artifacts.md +0 -31
- package/tests/component/cli_version.test.js +0 -38
- package/tests/integration/workspace_resolver.test.js +0 -44
- package/tests/unit/crypto_helper.test.js +0 -36
- package/tests/unit/path_helper.test.js +0 -20
- package/tsconfig.base.json +0 -32
- /package/{packages/cli/src/index.ts → dist/index.js} +0 -0
|
@@ -1,1386 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
import { AgentService } from "@mcoda/agents";
|
|
4
|
-
import { DocdexClient, VcsClient } from "@mcoda/integrations";
|
|
5
|
-
import { GlobalRepository, WorkspaceRepository, type EpicRow, type StoryRow, type TaskInsert, type TaskRow } from "@mcoda/db";
|
|
6
|
-
import { PathHelper } from "@mcoda/shared";
|
|
7
|
-
import { WorkspaceResolution } from "../../workspace/WorkspaceManager.js";
|
|
8
|
-
import { JobService } from "../jobs/JobService.js";
|
|
9
|
-
import { TaskSelectionFilters, TaskSelectionService } from "../execution/TaskSelectionService.js";
|
|
10
|
-
import { TaskStateService } from "../execution/TaskStateService.js";
|
|
11
|
-
import { BacklogService } from "../backlog/BacklogService.js";
|
|
12
|
-
import yaml from "yaml";
|
|
13
|
-
import { createTaskKeyGenerator } from "../planning/KeyHelpers.js";
|
|
14
|
-
import { RoutingService } from "../agents/RoutingService.js";
|
|
15
|
-
|
|
16
|
-
const DEFAULT_BASE_BRANCH = "mcoda-dev";
|
|
17
|
-
const REVIEW_DIR = (workspaceRoot: string, jobId: string) => path.join(workspaceRoot, ".mcoda", "jobs", jobId, "review");
|
|
18
|
-
const STATE_PATH = (workspaceRoot: string, jobId: string) => path.join(REVIEW_DIR(workspaceRoot, jobId), "state.json");
|
|
19
|
-
const DEFAULT_CODE_REVIEW_PROMPT = [
|
|
20
|
-
"You are the code-review agent. Before reviewing, query docdex with the task key and feature keywords (MCP `docdex_search` limit 4–8 or CLI `docdexd query --repo <repo> --query \"<term>\" --limit 6 --snippets=false`). If results look stale, reindex (`docdex_index` or `docdexd index --repo <repo>`) then re-run. Fetch snippets via `docdex_open` or `/snippet/:doc_id?text_only=true` only for specific hits.",
|
|
21
|
-
"Use docdex snippets to verify contracts (data shapes, offline scope, accessibility/perf guardrails, acceptance criteria). Call out mismatches, missing tests, and undocumented changes.",
|
|
22
|
-
].join("\n");
|
|
23
|
-
const DEFAULT_JOB_PROMPT = "You are an mcoda agent that follows workspace runbooks and responds with actionable, concise output.";
|
|
24
|
-
const DEFAULT_CHARACTER_PROMPT =
|
|
25
|
-
"Write clearly, avoid hallucinations, cite assumptions, and prioritize risk mitigation for the user.";
|
|
26
|
-
|
|
27
|
-
export interface CodeReviewRequest extends TaskSelectionFilters {
|
|
28
|
-
workspace: WorkspaceResolution;
|
|
29
|
-
baseRef?: string;
|
|
30
|
-
dryRun?: boolean;
|
|
31
|
-
agentName?: string;
|
|
32
|
-
agentStream?: boolean;
|
|
33
|
-
resumeJobId?: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface ReviewFinding {
|
|
37
|
-
type?: string;
|
|
38
|
-
severity?: string;
|
|
39
|
-
file?: string;
|
|
40
|
-
line?: number;
|
|
41
|
-
message: string;
|
|
42
|
-
suggestedFix?: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface ReviewAgentResult {
|
|
46
|
-
decision: "approve" | "changes_requested" | "block" | "info_only";
|
|
47
|
-
summary?: string;
|
|
48
|
-
findings: ReviewFinding[];
|
|
49
|
-
testRecommendations?: string[];
|
|
50
|
-
raw?: string;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface TaskReviewResult {
|
|
54
|
-
taskId: string;
|
|
55
|
-
taskKey: string;
|
|
56
|
-
statusBefore: string;
|
|
57
|
-
statusAfter?: string;
|
|
58
|
-
decision?: ReviewAgentResult["decision"];
|
|
59
|
-
findings: ReviewFinding[];
|
|
60
|
-
error?: string;
|
|
61
|
-
followupTasks?: { taskId: string; taskKey: string; epicId: string; userStoryId: string; generic?: boolean }[];
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export interface CodeReviewResult {
|
|
65
|
-
jobId: string;
|
|
66
|
-
commandRunId: string;
|
|
67
|
-
tasks: TaskReviewResult[];
|
|
68
|
-
warnings: string[];
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
interface ReviewJobState {
|
|
72
|
-
baseRef: string;
|
|
73
|
-
statusFilter: string[];
|
|
74
|
-
selectedTaskIds: string[];
|
|
75
|
-
contextBuilt: string[];
|
|
76
|
-
reviewed: { taskId: string; decision?: string; error?: string }[];
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const estimateTokens = (text: string): number => Math.max(1, Math.ceil((text ?? "").length / 4));
|
|
80
|
-
|
|
81
|
-
const parseJsonOutput = (raw: string): ReviewAgentResult | undefined => {
|
|
82
|
-
const trimmed = raw.trim();
|
|
83
|
-
const fenced = trimmed.replace(/^```(?:json)?/i, "").replace(/```$/, "").trim();
|
|
84
|
-
const candidates = [trimmed, fenced];
|
|
85
|
-
for (const candidate of candidates) {
|
|
86
|
-
const start = candidate.indexOf("{");
|
|
87
|
-
const end = candidate.lastIndexOf("}");
|
|
88
|
-
if (start === -1 || end === -1 || end <= start) continue;
|
|
89
|
-
const slice = candidate.slice(start, end + 1);
|
|
90
|
-
try {
|
|
91
|
-
const parsed = JSON.parse(slice) as ReviewAgentResult;
|
|
92
|
-
return { ...parsed, raw: raw };
|
|
93
|
-
} catch {
|
|
94
|
-
/* ignore */
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return undefined;
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const summarizeComments = (comments: { category?: string; body: string; file?: string; line?: number }[]): string => {
|
|
101
|
-
if (!comments.length) return "No prior comments.";
|
|
102
|
-
return comments
|
|
103
|
-
.map((c) => {
|
|
104
|
-
const loc = c.file ? `${c.file}${c.line ? `:${c.line}` : ""}` : "";
|
|
105
|
-
return `- [${c.category ?? "general"}] ${loc ? `${loc} ` : ""}${c.body}`;
|
|
106
|
-
})
|
|
107
|
-
.join("\n");
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const JSON_CONTRACT = `{
|
|
111
|
-
"decision": "approve | changes_requested | block | info_only",
|
|
112
|
-
"summary": "short textual summary",
|
|
113
|
-
"findings": [
|
|
114
|
-
{
|
|
115
|
-
"type": "bug | style | test | docs | contract | security | other",
|
|
116
|
-
"severity": "info | low | medium | high | critical",
|
|
117
|
-
"file": "relative/path/to/file.ext",
|
|
118
|
-
"line": 123,
|
|
119
|
-
"message": "Clear reviewer message",
|
|
120
|
-
"suggestedFix": "Optional suggested change"
|
|
121
|
-
}
|
|
122
|
-
],
|
|
123
|
-
"testRecommendations": ["Optional test or QA recommendations per task"]
|
|
124
|
-
}`;
|
|
125
|
-
|
|
126
|
-
const normalizeSingleLine = (value: string | undefined, fallback: string): string => {
|
|
127
|
-
const trimmed = (value ?? "").replace(/\s+/g, " ").trim();
|
|
128
|
-
return trimmed || fallback;
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const buildStandardReviewComment = (params: {
|
|
132
|
-
decision?: string;
|
|
133
|
-
statusBefore: string;
|
|
134
|
-
statusAfter?: string;
|
|
135
|
-
findingsCount: number;
|
|
136
|
-
summary?: string;
|
|
137
|
-
followupTaskKeys?: string[];
|
|
138
|
-
error?: string;
|
|
139
|
-
}): string => {
|
|
140
|
-
const decision = params.decision ?? (params.error ? "error" : "info_only");
|
|
141
|
-
const statusAfter = params.statusAfter ?? params.statusBefore;
|
|
142
|
-
const summary = normalizeSingleLine(params.summary, params.error ? "Review failed." : "No summary provided.");
|
|
143
|
-
const error = normalizeSingleLine(params.error, "none");
|
|
144
|
-
const followups = params.followupTaskKeys && params.followupTaskKeys.length ? params.followupTaskKeys.join(", ") : "none";
|
|
145
|
-
return [
|
|
146
|
-
"[code-review]",
|
|
147
|
-
`decision: ${decision}`,
|
|
148
|
-
`status_before: ${params.statusBefore}`,
|
|
149
|
-
`status_after: ${statusAfter}`,
|
|
150
|
-
`findings: ${params.findingsCount}`,
|
|
151
|
-
`summary: ${summary}`,
|
|
152
|
-
`followups: ${followups}`,
|
|
153
|
-
`error: ${error}`,
|
|
154
|
-
].join("\n");
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
export class CodeReviewService {
|
|
158
|
-
private selectionService: TaskSelectionService;
|
|
159
|
-
private stateService: TaskStateService;
|
|
160
|
-
private vcs: VcsClient;
|
|
161
|
-
private taskLogSeq = new Map<string, number>();
|
|
162
|
-
private routingService: RoutingService;
|
|
163
|
-
|
|
164
|
-
constructor(
|
|
165
|
-
private workspace: WorkspaceResolution,
|
|
166
|
-
private deps: {
|
|
167
|
-
agentService: AgentService;
|
|
168
|
-
docdex: DocdexClient;
|
|
169
|
-
jobService: JobService;
|
|
170
|
-
workspaceRepo: WorkspaceRepository;
|
|
171
|
-
selectionService?: TaskSelectionService;
|
|
172
|
-
stateService?: TaskStateService;
|
|
173
|
-
repo: GlobalRepository;
|
|
174
|
-
vcsClient?: VcsClient;
|
|
175
|
-
routingService: RoutingService;
|
|
176
|
-
},
|
|
177
|
-
) {
|
|
178
|
-
this.selectionService = deps.selectionService ?? new TaskSelectionService(workspace, deps.workspaceRepo);
|
|
179
|
-
this.stateService = deps.stateService ?? new TaskStateService(deps.workspaceRepo);
|
|
180
|
-
this.vcs = deps.vcsClient ?? new VcsClient();
|
|
181
|
-
this.routingService = deps.routingService;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
static async create(workspace: WorkspaceResolution): Promise<CodeReviewService> {
|
|
185
|
-
const repo = await GlobalRepository.create();
|
|
186
|
-
const agentService = new AgentService(repo);
|
|
187
|
-
const routingService = await RoutingService.create();
|
|
188
|
-
const docdex = new DocdexClient({
|
|
189
|
-
workspaceRoot: workspace.workspaceRoot,
|
|
190
|
-
baseUrl: workspace.config?.docdexUrl ?? process.env.MCODA_DOCDEX_URL,
|
|
191
|
-
});
|
|
192
|
-
const workspaceRepo = await WorkspaceRepository.create(workspace.workspaceRoot);
|
|
193
|
-
const jobService = new JobService(workspace, workspaceRepo);
|
|
194
|
-
const selectionService = new TaskSelectionService(workspace, workspaceRepo);
|
|
195
|
-
const stateService = new TaskStateService(workspaceRepo);
|
|
196
|
-
const vcsClient = new VcsClient();
|
|
197
|
-
return new CodeReviewService(workspace, {
|
|
198
|
-
agentService,
|
|
199
|
-
docdex,
|
|
200
|
-
jobService,
|
|
201
|
-
workspaceRepo,
|
|
202
|
-
selectionService,
|
|
203
|
-
stateService,
|
|
204
|
-
repo,
|
|
205
|
-
vcsClient,
|
|
206
|
-
routingService,
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
async close(): Promise<void> {
|
|
211
|
-
const maybeClose = async (target: unknown) => {
|
|
212
|
-
try {
|
|
213
|
-
if ((target as any)?.close) await (target as any).close();
|
|
214
|
-
} catch {
|
|
215
|
-
/* ignore */
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
await maybeClose(this.deps.selectionService);
|
|
219
|
-
await maybeClose(this.deps.stateService);
|
|
220
|
-
await maybeClose(this.deps.agentService);
|
|
221
|
-
await maybeClose(this.deps.jobService);
|
|
222
|
-
await maybeClose(this.deps.repo);
|
|
223
|
-
await maybeClose(this.deps.workspaceRepo);
|
|
224
|
-
await maybeClose(this.deps.routingService);
|
|
225
|
-
await maybeClose(this.deps.docdex);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
private async readPromptFiles(paths: string[]): Promise<string[]> {
|
|
229
|
-
const contents: string[] = [];
|
|
230
|
-
const seen = new Set<string>();
|
|
231
|
-
for (const promptPath of paths) {
|
|
232
|
-
try {
|
|
233
|
-
const content = await fs.readFile(promptPath, "utf8");
|
|
234
|
-
const trimmed = content.trim();
|
|
235
|
-
if (trimmed && !seen.has(trimmed)) {
|
|
236
|
-
contents.push(trimmed);
|
|
237
|
-
seen.add(trimmed);
|
|
238
|
-
}
|
|
239
|
-
} catch {
|
|
240
|
-
/* optional prompt */
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
return contents;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
private async ensureMcoda(): Promise<void> {
|
|
247
|
-
await PathHelper.ensureDir(this.workspace.mcodaDir);
|
|
248
|
-
const gitignorePath = path.join(this.workspace.workspaceRoot, ".gitignore");
|
|
249
|
-
const entry = ".mcoda/\n";
|
|
250
|
-
try {
|
|
251
|
-
const content = await fs.readFile(gitignorePath, "utf8");
|
|
252
|
-
if (!content.includes(".mcoda/")) {
|
|
253
|
-
await fs.writeFile(gitignorePath, `${content.trimEnd()}\n${entry}`, "utf8");
|
|
254
|
-
}
|
|
255
|
-
} catch {
|
|
256
|
-
await fs.writeFile(gitignorePath, entry, "utf8");
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
private async loadPrompts(agentId: string): Promise<{ jobPrompt?: string; characterPrompt?: string; commandPrompt?: string }> {
|
|
261
|
-
const mcodaPromptPath = path.join(this.workspace.workspaceRoot, ".mcoda", "prompts", "code-reviewer.md");
|
|
262
|
-
const workspacePromptPath = path.join(this.workspace.workspaceRoot, "prompts", "code-reviewer.md");
|
|
263
|
-
try {
|
|
264
|
-
await fs.mkdir(path.dirname(mcodaPromptPath), { recursive: true });
|
|
265
|
-
await fs.access(mcodaPromptPath);
|
|
266
|
-
console.info(`[code-review] using existing code-reviewer prompt at ${mcodaPromptPath}`);
|
|
267
|
-
} catch {
|
|
268
|
-
try {
|
|
269
|
-
await fs.access(workspacePromptPath);
|
|
270
|
-
await fs.copyFile(workspacePromptPath, mcodaPromptPath);
|
|
271
|
-
console.info(`[code-review] copied code-reviewer prompt to ${mcodaPromptPath}`);
|
|
272
|
-
} catch {
|
|
273
|
-
console.info(`[code-review] no code-reviewer prompt found at ${workspacePromptPath}; writing default prompt to ${mcodaPromptPath}`);
|
|
274
|
-
await fs.writeFile(mcodaPromptPath, DEFAULT_CODE_REVIEW_PROMPT, 'utf8');
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
const filePrompts = await this.readPromptFiles([mcodaPromptPath, workspacePromptPath]);
|
|
278
|
-
const agentPrompts =
|
|
279
|
-
"getPrompts" in this.deps.agentService ? await (this.deps.agentService as any).getPrompts(agentId) : undefined;
|
|
280
|
-
const mergedCommandPrompt = (() => {
|
|
281
|
-
const parts = [...filePrompts];
|
|
282
|
-
if (agentPrompts?.commandPrompts?.["code-review"]) {
|
|
283
|
-
parts.push(agentPrompts.commandPrompts["code-review"]);
|
|
284
|
-
}
|
|
285
|
-
if (!parts.length) parts.push(DEFAULT_CODE_REVIEW_PROMPT);
|
|
286
|
-
return parts.filter(Boolean).join("\n\n");
|
|
287
|
-
})();
|
|
288
|
-
return {
|
|
289
|
-
jobPrompt: agentPrompts?.jobPrompt ?? DEFAULT_JOB_PROMPT,
|
|
290
|
-
characterPrompt: agentPrompts?.characterPrompt ?? DEFAULT_CHARACTER_PROMPT,
|
|
291
|
-
commandPrompt: mergedCommandPrompt || undefined,
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
private async loadRunbookAndChecklists(): Promise<string[]> {
|
|
296
|
-
const extras: string[] = [];
|
|
297
|
-
const runbookPath = path.join(this.workspace.workspaceRoot, ".mcoda", "prompts", "commands", "code-review.md");
|
|
298
|
-
try {
|
|
299
|
-
const content = await fs.readFile(runbookPath, "utf8");
|
|
300
|
-
extras.push(content);
|
|
301
|
-
} catch {
|
|
302
|
-
/* optional */
|
|
303
|
-
}
|
|
304
|
-
const checklistDir = path.join(this.workspace.workspaceRoot, ".mcoda", "checklists");
|
|
305
|
-
try {
|
|
306
|
-
const entries = await fs.readdir(checklistDir);
|
|
307
|
-
for (const entry of entries) {
|
|
308
|
-
if (!entry.endsWith(".md")) continue;
|
|
309
|
-
const content = await fs.readFile(path.join(checklistDir, entry), "utf8");
|
|
310
|
-
extras.push(content);
|
|
311
|
-
}
|
|
312
|
-
} catch {
|
|
313
|
-
/* optional */
|
|
314
|
-
}
|
|
315
|
-
return extras;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
private async resolveAgent(agentName?: string) {
|
|
319
|
-
const resolved = await this.routingService.resolveAgentForCommand({
|
|
320
|
-
workspace: this.workspace,
|
|
321
|
-
commandName: "code-review",
|
|
322
|
-
overrideAgentSlug: agentName,
|
|
323
|
-
});
|
|
324
|
-
return resolved.agent;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
private async selectTasksViaApi(filters: {
|
|
328
|
-
projectKey?: string;
|
|
329
|
-
epicKey?: string;
|
|
330
|
-
storyKey?: string;
|
|
331
|
-
taskKeys?: string[];
|
|
332
|
-
statusFilter: string[];
|
|
333
|
-
limit?: number;
|
|
334
|
-
}): Promise<(TaskRow & { epicKey: string; storyKey: string; epicTitle?: string; epicDescription?: string; storyTitle?: string; storyDescription?: string; acceptanceCriteria?: string[] })[]> {
|
|
335
|
-
// Prefer the backlog/task OpenAPI surface (via BacklogService) to mirror API filtering semantics.
|
|
336
|
-
const backlog = await BacklogService.create(this.workspace);
|
|
337
|
-
try {
|
|
338
|
-
const result = await backlog.getBacklog({
|
|
339
|
-
projectKey: filters.projectKey,
|
|
340
|
-
epicKey: filters.epicKey,
|
|
341
|
-
storyKey: filters.storyKey,
|
|
342
|
-
statuses: filters.statusFilter,
|
|
343
|
-
verbose: true,
|
|
344
|
-
});
|
|
345
|
-
let tasks = result.summary.tasks;
|
|
346
|
-
if (filters.taskKeys?.length) {
|
|
347
|
-
const allowed = new Set(filters.taskKeys);
|
|
348
|
-
tasks = tasks.filter((t) => allowed.has(t.task_key));
|
|
349
|
-
}
|
|
350
|
-
if (filters.limit && filters.limit > 0) {
|
|
351
|
-
tasks = tasks.slice(0, filters.limit);
|
|
352
|
-
}
|
|
353
|
-
const ids = tasks.map((t) => t.task_id);
|
|
354
|
-
const detailed = await this.deps.workspaceRepo.getTasksWithRelations(ids);
|
|
355
|
-
// Preserve ordering from backlog
|
|
356
|
-
const order = new Map(ids.map((id, idx) => [id, idx]));
|
|
357
|
-
return detailed.sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0));
|
|
358
|
-
} finally {
|
|
359
|
-
await backlog.close();
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
private async persistState(jobId: string, state: ReviewJobState): Promise<void> {
|
|
364
|
-
const dir = REVIEW_DIR(this.workspace.workspaceRoot, jobId);
|
|
365
|
-
await fs.mkdir(dir, { recursive: true });
|
|
366
|
-
await fs.writeFile(
|
|
367
|
-
STATE_PATH(this.workspace.workspaceRoot, jobId),
|
|
368
|
-
JSON.stringify({ schema_version: 1, job_id: jobId, updated_at: new Date().toISOString(), ...state }, null, 2),
|
|
369
|
-
"utf8",
|
|
370
|
-
);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
private async loadState(jobId: string): Promise<ReviewJobState | undefined> {
|
|
374
|
-
try {
|
|
375
|
-
const raw = await fs.readFile(STATE_PATH(this.workspace.workspaceRoot, jobId), "utf8");
|
|
376
|
-
return JSON.parse(raw) as ReviewJobState;
|
|
377
|
-
} catch {
|
|
378
|
-
return undefined;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
private async writeCheckpoint(jobId: string, stage: string, details?: Record<string, unknown>): Promise<void> {
|
|
383
|
-
await this.deps.jobService.writeCheckpoint(jobId, { stage, timestamp: new Date().toISOString(), details });
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
private componentHintsFromPaths(paths: string[]): string[] {
|
|
387
|
-
const hints = new Set<string>();
|
|
388
|
-
for (const p of paths) {
|
|
389
|
-
const segments = p.split("/").filter(Boolean);
|
|
390
|
-
if (segments.length) {
|
|
391
|
-
hints.add(segments[0]);
|
|
392
|
-
if (segments.length > 1) hints.add(`${segments[0]}/${segments[1]}`);
|
|
393
|
-
}
|
|
394
|
-
const file = p.split("/").pop();
|
|
395
|
-
if (file) {
|
|
396
|
-
const base = file.split(".")[0];
|
|
397
|
-
hints.add(base);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
return Array.from(hints).slice(0, 8);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
private async gatherDocContext(taskTitle: string, paths: string[], acceptance?: string[]): Promise<{ snippets: string[]; warnings: string[] }> {
|
|
404
|
-
const snippets: string[] = [];
|
|
405
|
-
const warnings: string[] = [];
|
|
406
|
-
const queries = [...new Set([...(paths.length ? this.componentHintsFromPaths(paths) : []), taskTitle, ...(acceptance ?? [])])].slice(0, 8);
|
|
407
|
-
for (const query of queries) {
|
|
408
|
-
try {
|
|
409
|
-
const docs = await this.deps.docdex.search({
|
|
410
|
-
query,
|
|
411
|
-
profile: "workspace-code",
|
|
412
|
-
});
|
|
413
|
-
snippets.push(
|
|
414
|
-
...docs.slice(0, 2).map((doc) => {
|
|
415
|
-
const content = (doc.segments?.[0]?.content ?? doc.content ?? "").slice(0, 400);
|
|
416
|
-
const ref = doc.path ?? doc.id ?? doc.title ?? query;
|
|
417
|
-
return `- [${doc.docType ?? "doc"}] ${ref}: ${content}`;
|
|
418
|
-
}),
|
|
419
|
-
);
|
|
420
|
-
} catch (error) {
|
|
421
|
-
warnings.push(`docdex search failed for ${query}: ${(error as Error).message}`);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
return { snippets: Array.from(new Set(snippets)), warnings };
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
private buildReviewPrompt(params: {
|
|
428
|
-
systemPrompts: string[];
|
|
429
|
-
task: TaskRow & { epicKey?: string; storyKey?: string; epicTitle?: string; epicDescription?: string; storyTitle?: string; storyDescription?: string; acceptanceCriteria?: string[] };
|
|
430
|
-
diff: string;
|
|
431
|
-
docContext: string[];
|
|
432
|
-
openapiSnippet?: string;
|
|
433
|
-
checklists?: string[];
|
|
434
|
-
historySummary: string;
|
|
435
|
-
baseRef: string;
|
|
436
|
-
branch?: string;
|
|
437
|
-
}): string {
|
|
438
|
-
const parts: string[] = [];
|
|
439
|
-
if (params.systemPrompts.length) {
|
|
440
|
-
parts.push(params.systemPrompts.join("\n\n"));
|
|
441
|
-
}
|
|
442
|
-
const acceptance = params.task.acceptanceCriteria && params.task.acceptanceCriteria.length ? params.task.acceptanceCriteria.join(" | ") : "none provided";
|
|
443
|
-
parts.push(
|
|
444
|
-
[
|
|
445
|
-
`Task ${params.task.key}: ${params.task.title}`,
|
|
446
|
-
`Epic: ${params.task.epicKey ?? ""} ${params.task.epicTitle ?? ""}`.trim(),
|
|
447
|
-
`Epic description: ${params.task.epicDescription ? params.task.epicDescription : "none"}`,
|
|
448
|
-
`Story: ${params.task.storyKey ?? ""} ${params.task.storyTitle ?? ""}`.trim(),
|
|
449
|
-
`Story description: ${params.task.storyDescription ? params.task.storyDescription : "none"}`,
|
|
450
|
-
`Status: ${params.task.status}, Branch: ${params.branch ?? params.task.vcsBranch ?? "n/a"} (base ${params.baseRef})`,
|
|
451
|
-
`Task description: ${params.task.description ? params.task.description : "none"}`,
|
|
452
|
-
`History:\n${params.historySummary}`,
|
|
453
|
-
`Acceptance criteria: ${acceptance}`,
|
|
454
|
-
params.docContext.length ? `Doc context (docdex excerpts):\n${params.docContext.join("\n")}` : "Doc context: none",
|
|
455
|
-
params.openapiSnippet
|
|
456
|
-
? `OpenAPI (authoritative contract; do not invent endpoints outside this):\n${params.openapiSnippet}`
|
|
457
|
-
: "OpenAPI: not provided; avoid inventing endpoints.",
|
|
458
|
-
params.checklists && params.checklists.length ? `Review checklists/runbook:\n${params.checklists.join("\n\n")}` : "Checklists: none",
|
|
459
|
-
"Diff:\n" + (params.diff || "(no diff)"),
|
|
460
|
-
"Respond with STRICT JSON only, matching:\n" + JSON_CONTRACT,
|
|
461
|
-
"Rules: honor OpenAPI contracts; cite doc context where relevant; do not add prose outside JSON.",
|
|
462
|
-
].join("\n"),
|
|
463
|
-
);
|
|
464
|
-
return parts.join("\n\n");
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
private async buildHistorySummary(taskId: string): Promise<string> {
|
|
468
|
-
const comments = await this.deps.workspaceRepo.listTaskComments(taskId, {
|
|
469
|
-
sourceCommands: ["work-on-tasks", "code-review", "qa-tasks"],
|
|
470
|
-
limit: 10,
|
|
471
|
-
});
|
|
472
|
-
const lastReview = await this.deps.workspaceRepo.getLatestTaskReview(taskId);
|
|
473
|
-
const parts: string[] = [];
|
|
474
|
-
if (lastReview) {
|
|
475
|
-
parts.push(`Last review decision: ${lastReview.decision}${lastReview.summary ? ` — ${lastReview.summary}` : ""}`);
|
|
476
|
-
}
|
|
477
|
-
if (comments.length) {
|
|
478
|
-
parts.push("Recent comments:");
|
|
479
|
-
parts.push(
|
|
480
|
-
summarizeComments(
|
|
481
|
-
comments.map((c) => ({
|
|
482
|
-
category: c.category ?? undefined,
|
|
483
|
-
body: c.body,
|
|
484
|
-
file: c.file ?? undefined,
|
|
485
|
-
line: c.line ?? undefined,
|
|
486
|
-
})),
|
|
487
|
-
),
|
|
488
|
-
);
|
|
489
|
-
const unresolved = comments.filter((c) => !c.resolvedAt);
|
|
490
|
-
if (unresolved.length) {
|
|
491
|
-
parts.push(`Unresolved items: ${unresolved.length}`);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
if (!parts.length) return "No prior review or QA history.";
|
|
495
|
-
return parts.join("\n");
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
private extractPathsFromDiff(diff: string): string[] {
|
|
499
|
-
const regex = /^(?:\+\+\+ b\/|\-\-\- a\/)([^\s]+)$/gm;
|
|
500
|
-
const paths = new Set<string>();
|
|
501
|
-
let match: RegExpExecArray | null;
|
|
502
|
-
while ((match = regex.exec(diff)) !== null) {
|
|
503
|
-
const raw = match[1]?.trim();
|
|
504
|
-
if (raw && raw !== "/dev/null") paths.add(raw.replace(/^a\//, "").replace(/^b\//, ""));
|
|
505
|
-
}
|
|
506
|
-
return Array.from(paths);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
private async buildOpenApiSlice(changedPaths: string[], acceptance?: string[]): Promise<string | undefined> {
|
|
510
|
-
const openapiPath = path.join(this.workspace.workspaceRoot, "openapi", "mcoda.yaml");
|
|
511
|
-
try {
|
|
512
|
-
const content = await fs.readFile(openapiPath, "utf8");
|
|
513
|
-
const parsed = yaml.parse(content) as any;
|
|
514
|
-
const pathHints = this.componentHintsFromPaths(changedPaths);
|
|
515
|
-
const criteriaHints = (acceptance ?? []).map((c) => c.toLowerCase()).slice(0, 5);
|
|
516
|
-
const matches: Record<string, any> = {};
|
|
517
|
-
if (parsed?.paths) {
|
|
518
|
-
for (const [apiPath, ops] of Object.entries(parsed.paths as Record<string, any>)) {
|
|
519
|
-
const lowerPath = apiPath.toLowerCase();
|
|
520
|
-
const hit =
|
|
521
|
-
pathHints.some((h) => lowerPath.includes(h.toLowerCase())) ||
|
|
522
|
-
criteriaHints.some((h) => lowerPath.includes(h)) ||
|
|
523
|
-
(!pathHints.length && !criteriaHints.length);
|
|
524
|
-
if (hit) {
|
|
525
|
-
matches[apiPath] = ops;
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
const schemaMatches: Record<string, any> = {};
|
|
530
|
-
if (parsed?.components?.schemas) {
|
|
531
|
-
for (const [name, schema] of Object.entries(parsed.components.schemas as Record<string, any>)) {
|
|
532
|
-
const lower = name.toLowerCase();
|
|
533
|
-
if (pathHints.some((h) => lower.includes(h.toLowerCase()))) {
|
|
534
|
-
schemaMatches[name] = schema;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
if (!Object.keys(matches).length && !Object.keys(schemaMatches).length) {
|
|
539
|
-
return content.slice(0, 4000);
|
|
540
|
-
}
|
|
541
|
-
const slice = {
|
|
542
|
-
openapi: parsed.openapi ?? "3.0.0",
|
|
543
|
-
info: parsed.info,
|
|
544
|
-
paths: matches,
|
|
545
|
-
components: Object.keys(schemaMatches).length ? { schemas: schemaMatches } : undefined,
|
|
546
|
-
};
|
|
547
|
-
const rendered = yaml.stringify(slice);
|
|
548
|
-
return rendered.slice(0, 8000);
|
|
549
|
-
} catch {
|
|
550
|
-
return undefined;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
private async buildDiff(
|
|
555
|
-
task: TaskRow,
|
|
556
|
-
baseRef: string,
|
|
557
|
-
fileScope: string[],
|
|
558
|
-
): Promise<{ diff: string; source: "commit" | "branch"; commitSha?: string; warning?: string }> {
|
|
559
|
-
const branch = task.vcsBranch;
|
|
560
|
-
await this.vcs.ensureRepo(this.workspace.workspaceRoot);
|
|
561
|
-
const paths = fileScope.length ? fileScope : undefined;
|
|
562
|
-
const commitSha = task.vcsLastCommitSha;
|
|
563
|
-
if (commitSha) {
|
|
564
|
-
try {
|
|
565
|
-
const diff = await this.vcs.diff(this.workspace.workspaceRoot, `${commitSha}^`, commitSha, paths);
|
|
566
|
-
return { diff, source: "commit", commitSha };
|
|
567
|
-
} catch (error) {
|
|
568
|
-
if (!branch) {
|
|
569
|
-
throw new Error(`Task branch missing and commit diff failed: ${(error as Error).message}`);
|
|
570
|
-
}
|
|
571
|
-
const fallback = await this.vcs.diff(this.workspace.workspaceRoot, baseRef, branch, paths);
|
|
572
|
-
return {
|
|
573
|
-
diff: fallback,
|
|
574
|
-
source: "branch",
|
|
575
|
-
commitSha,
|
|
576
|
-
warning: `Failed to diff commit ${commitSha}; fell back to branch ${branch}.`,
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
if (!branch) throw new Error("Task branch missing");
|
|
581
|
-
const diff = await this.vcs.diff(this.workspace.workspaceRoot, baseRef, branch, paths);
|
|
582
|
-
return { diff, source: "branch" };
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
private async writeReviewSummaryComment(params: {
|
|
586
|
-
task: TaskRow;
|
|
587
|
-
taskRunId: string;
|
|
588
|
-
jobId: string;
|
|
589
|
-
agentId: string;
|
|
590
|
-
statusBefore: string;
|
|
591
|
-
statusAfter?: string;
|
|
592
|
-
decision?: string;
|
|
593
|
-
summary?: string;
|
|
594
|
-
findingsCount: number;
|
|
595
|
-
followupTaskKeys?: string[];
|
|
596
|
-
error?: string;
|
|
597
|
-
}): Promise<void> {
|
|
598
|
-
const body = buildStandardReviewComment({
|
|
599
|
-
decision: params.decision,
|
|
600
|
-
statusBefore: params.statusBefore,
|
|
601
|
-
statusAfter: params.statusAfter,
|
|
602
|
-
findingsCount: params.findingsCount,
|
|
603
|
-
summary: params.summary,
|
|
604
|
-
followupTaskKeys: params.followupTaskKeys,
|
|
605
|
-
error: params.error,
|
|
606
|
-
});
|
|
607
|
-
await this.deps.workspaceRepo.createTaskComment({
|
|
608
|
-
taskId: params.task.id,
|
|
609
|
-
taskRunId: params.taskRunId,
|
|
610
|
-
jobId: params.jobId,
|
|
611
|
-
sourceCommand: "code-review",
|
|
612
|
-
authorType: "agent",
|
|
613
|
-
authorAgentId: params.agentId,
|
|
614
|
-
category: "review_summary",
|
|
615
|
-
body,
|
|
616
|
-
createdAt: new Date().toISOString(),
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
private async persistContext(jobId: string, taskId: string, context: Record<string, unknown>): Promise<void> {
|
|
621
|
-
const dir = path.join(REVIEW_DIR(this.workspace.workspaceRoot, jobId), "context");
|
|
622
|
-
await fs.mkdir(dir, { recursive: true });
|
|
623
|
-
await fs.writeFile(
|
|
624
|
-
path.join(dir, `${taskId}.json`),
|
|
625
|
-
JSON.stringify({ schema_version: 1, task_id: taskId, created_at: new Date().toISOString(), ...context }, null, 2),
|
|
626
|
-
"utf8",
|
|
627
|
-
);
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
private async persistDiff(jobId: string, taskId: string, diff: string): Promise<void> {
|
|
631
|
-
const dir = path.join(REVIEW_DIR(this.workspace.workspaceRoot, jobId), "diffs");
|
|
632
|
-
await fs.mkdir(dir, { recursive: true });
|
|
633
|
-
await fs.writeFile(path.join(dir, `${taskId}.diff`), diff, "utf8");
|
|
634
|
-
// structured review diff snapshot
|
|
635
|
-
const files: { path: string; hunks: string[] }[] = [];
|
|
636
|
-
let current: { path: string; hunks: string[] } | null = null;
|
|
637
|
-
for (const line of diff.split(/\r?\n/)) {
|
|
638
|
-
if (line.startsWith("diff --git")) {
|
|
639
|
-
if (current) {
|
|
640
|
-
files.push(current);
|
|
641
|
-
current = null;
|
|
642
|
-
}
|
|
643
|
-
continue;
|
|
644
|
-
}
|
|
645
|
-
const fileHeader = line.match(/^(\+\+\+|---)\s+[ab]\/(.+)$/);
|
|
646
|
-
if (fileHeader) {
|
|
647
|
-
if (current) files.push(current);
|
|
648
|
-
current = { path: fileHeader[2], hunks: [] };
|
|
649
|
-
continue;
|
|
650
|
-
}
|
|
651
|
-
if (line.startsWith("@@")) {
|
|
652
|
-
if (current) current.hunks.push(line);
|
|
653
|
-
continue;
|
|
654
|
-
}
|
|
655
|
-
if (current) current.hunks.push(line);
|
|
656
|
-
}
|
|
657
|
-
if (current) files.push(current);
|
|
658
|
-
await fs.writeFile(path.join(dir, `${taskId}.json`), JSON.stringify({ schema_version: 1, task_id: taskId, files }, null, 2), "utf8");
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
private severityToPriority(severity?: string): number | null {
|
|
662
|
-
if (!severity) return null;
|
|
663
|
-
const normalized = severity.toLowerCase();
|
|
664
|
-
const order: Record<string, number> = { critical: 1, high: 2, medium: 3, low: 4, info: 5 };
|
|
665
|
-
return order[normalized] ?? null;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
private shouldCreateFollowupTask(decision: ReviewAgentResult["decision"] | undefined, finding: ReviewFinding): boolean {
|
|
669
|
-
// SDS rule: create follow-ups for blocking/changes_requested decisions or critical/high issues,
|
|
670
|
-
// and for contract/security/bug types at medium+ severity. Do not create for approve+low/info.
|
|
671
|
-
const sev = (finding.severity ?? "").toLowerCase();
|
|
672
|
-
const type = (finding.type ?? "").toLowerCase();
|
|
673
|
-
const decisionRequestsChange = decision === "changes_requested" || decision === "block";
|
|
674
|
-
if (decisionRequestsChange && sev !== "info") return true;
|
|
675
|
-
if (["critical", "high"].includes(sev)) return true;
|
|
676
|
-
if (["bug", "security", "contract"].includes(type) && !["info", "low"].includes(sev)) return true;
|
|
677
|
-
return false;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
private buildFollowupTitle(task: TaskRow & { storyKey?: string }, finding: ReviewFinding, generatedKey: string): string {
|
|
681
|
-
const base = (finding.message ?? "Review follow-up").split("\n")[0]?.trim() ?? "Review follow-up";
|
|
682
|
-
const prefix = finding.file ? `${finding.file}: ` : "";
|
|
683
|
-
const raw = `${prefix}${base}`;
|
|
684
|
-
const truncated = raw.length > 140 ? `${raw.slice(0, 137)}...` : raw;
|
|
685
|
-
return truncated || `Follow-up ${generatedKey} for ${task.key}`;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
private buildFollowupDescription(task: TaskRow & { storyKey?: string; epicKey?: string }, finding: ReviewFinding, decision?: ReviewAgentResult["decision"]): string {
|
|
689
|
-
const lines = [
|
|
690
|
-
`Auto-created from code review of ${task.key}. Decision: ${decision ?? "n/a"}.`,
|
|
691
|
-
finding.message ? `Finding: ${finding.message}` : undefined,
|
|
692
|
-
finding.file ? `Location: ${finding.file}${finding.line ? `:${finding.line}` : ""}` : undefined,
|
|
693
|
-
finding.severity ? `Severity: ${finding.severity}` : undefined,
|
|
694
|
-
finding.type ? `Category: ${finding.type}` : undefined,
|
|
695
|
-
finding.suggestedFix ? `Suggested fix: ${finding.suggestedFix}` : undefined,
|
|
696
|
-
`Story: ${task.storyKey ?? task.userStoryId}, Epic: ${task.epicKey ?? task.epicId}`,
|
|
697
|
-
].filter(Boolean);
|
|
698
|
-
return lines.join("\n");
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
private async ensureGenericContainers(projectId: string): Promise<{ epic: EpicRow; story: StoryRow }> {
|
|
702
|
-
const epicCandidates = ["epic-bugs", "epic-issues"];
|
|
703
|
-
let epic: EpicRow | undefined;
|
|
704
|
-
for (const key of epicCandidates) {
|
|
705
|
-
epic = await this.deps.workspaceRepo.getEpicByKey(projectId, key);
|
|
706
|
-
if (epic) break;
|
|
707
|
-
}
|
|
708
|
-
if (!epic) {
|
|
709
|
-
const [createdEpic] = await this.deps.workspaceRepo.insertEpics(
|
|
710
|
-
[
|
|
711
|
-
{
|
|
712
|
-
projectId,
|
|
713
|
-
key: epicCandidates[0],
|
|
714
|
-
title: "Bug Backlog",
|
|
715
|
-
description: "Generic epic for code review follow-up issues",
|
|
716
|
-
metadata: { source: "code-review", autoGenerated: true },
|
|
717
|
-
},
|
|
718
|
-
],
|
|
719
|
-
true,
|
|
720
|
-
);
|
|
721
|
-
epic = createdEpic;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
const storyCandidates = ["us-bugs", "us-issues"];
|
|
725
|
-
let story: StoryRow | undefined;
|
|
726
|
-
for (const key of storyCandidates) {
|
|
727
|
-
story = await this.deps.workspaceRepo.getStoryByKey(epic.id, key);
|
|
728
|
-
if (story) break;
|
|
729
|
-
}
|
|
730
|
-
if (!story) {
|
|
731
|
-
const [createdStory] = await this.deps.workspaceRepo.insertStories(
|
|
732
|
-
[
|
|
733
|
-
{
|
|
734
|
-
projectId,
|
|
735
|
-
epicId: epic.id,
|
|
736
|
-
key: storyCandidates[0],
|
|
737
|
-
title: "Review issues",
|
|
738
|
-
description: "Auto-created story for code review findings",
|
|
739
|
-
acceptanceCriteria: "Track, fix, and verify issues found during reviews.",
|
|
740
|
-
metadata: { source: "code-review", autoGenerated: true },
|
|
741
|
-
},
|
|
742
|
-
],
|
|
743
|
-
true,
|
|
744
|
-
);
|
|
745
|
-
story = createdStory;
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
return { epic, story };
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
private async createFollowupTasksForFindings(params: {
|
|
752
|
-
task: TaskRow & { storyKey?: string; epicKey?: string };
|
|
753
|
-
findings: ReviewFinding[];
|
|
754
|
-
decision?: ReviewAgentResult["decision"];
|
|
755
|
-
jobId: string;
|
|
756
|
-
commandRunId: string;
|
|
757
|
-
taskRunId: string;
|
|
758
|
-
}): Promise<TaskRow[]> {
|
|
759
|
-
const actionable = params.findings.filter((f) => this.shouldCreateFollowupTask(params.decision, f));
|
|
760
|
-
if (!actionable.length) return [];
|
|
761
|
-
|
|
762
|
-
const useGeneric = actionable.some((f) => !f.file && !f.line);
|
|
763
|
-
const genericContainers = useGeneric ? await this.ensureGenericContainers(params.task.projectId) : undefined;
|
|
764
|
-
|
|
765
|
-
const inserts: TaskInsert[] = [];
|
|
766
|
-
const generators = new Map<
|
|
767
|
-
string,
|
|
768
|
-
{
|
|
769
|
-
gen: () => string;
|
|
770
|
-
keys: Set<string>;
|
|
771
|
-
storyKey: string;
|
|
772
|
-
}
|
|
773
|
-
>();
|
|
774
|
-
const ensureKey = async (storyId: string, storyKey: string): Promise<string> => {
|
|
775
|
-
let entry = generators.get(storyId);
|
|
776
|
-
if (!entry) {
|
|
777
|
-
const existing = new Set(await this.deps.workspaceRepo.listTaskKeys(storyId));
|
|
778
|
-
entry = { gen: createTaskKeyGenerator(storyKey, existing), keys: existing, storyKey };
|
|
779
|
-
generators.set(storyId, entry);
|
|
780
|
-
}
|
|
781
|
-
const key = entry.gen();
|
|
782
|
-
entry.keys.add(key);
|
|
783
|
-
return key;
|
|
784
|
-
};
|
|
785
|
-
|
|
786
|
-
for (const finding of actionable) {
|
|
787
|
-
const genericTarget = !finding.file && !finding.line && genericContainers;
|
|
788
|
-
const storyId = genericTarget ? genericContainers.story.id : params.task.userStoryId;
|
|
789
|
-
const storyKey = genericTarget ? genericContainers!.story.key : params.task.storyKey ?? genericContainers?.story.key ?? "US-AUTO";
|
|
790
|
-
const epicId = genericTarget ? genericContainers!.epic.id : params.task.epicId;
|
|
791
|
-
const taskKey = await ensureKey(storyId, storyKey);
|
|
792
|
-
inserts.push({
|
|
793
|
-
projectId: params.task.projectId,
|
|
794
|
-
epicId,
|
|
795
|
-
userStoryId: storyId,
|
|
796
|
-
key: taskKey,
|
|
797
|
-
title: this.buildFollowupTitle(params.task, finding, taskKey),
|
|
798
|
-
description: this.buildFollowupDescription(params.task, finding, params.decision),
|
|
799
|
-
type: finding.type ?? (params.decision === "changes_requested" || params.decision === "block" ? "bug" : "issue"),
|
|
800
|
-
status: "not_started",
|
|
801
|
-
storyPoints: null,
|
|
802
|
-
priority: this.severityToPriority(finding.severity),
|
|
803
|
-
metadata: {
|
|
804
|
-
source: "code-review",
|
|
805
|
-
source_task_id: params.task.id,
|
|
806
|
-
source_task_key: params.task.key,
|
|
807
|
-
source_job_id: params.jobId,
|
|
808
|
-
source_command_run_id: params.commandRunId,
|
|
809
|
-
source_task_run_id: params.taskRunId,
|
|
810
|
-
severity: finding.severity,
|
|
811
|
-
type: finding.type,
|
|
812
|
-
file: finding.file,
|
|
813
|
-
line: finding.line,
|
|
814
|
-
suggestedFix: finding.suggestedFix,
|
|
815
|
-
generic: genericTarget ? true : false,
|
|
816
|
-
decision: params.decision,
|
|
817
|
-
},
|
|
818
|
-
});
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
const created = await this.deps.workspaceRepo.insertTasks(inserts, true);
|
|
822
|
-
for (let i = 0; i < created.length; i += 1) {
|
|
823
|
-
const createdTask = created[i];
|
|
824
|
-
const sourceFinding = actionable[i];
|
|
825
|
-
await this.deps.workspaceRepo.insertTaskLog({
|
|
826
|
-
taskRunId: params.taskRunId,
|
|
827
|
-
sequence: this.sequenceForTask(params.taskRunId),
|
|
828
|
-
timestamp: new Date().toISOString(),
|
|
829
|
-
source: "followup_task",
|
|
830
|
-
message: `Created follow-up task ${createdTask.key}`,
|
|
831
|
-
details: { targetTaskId: createdTask.id, sourceFinding },
|
|
832
|
-
});
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
return created;
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
async reviewTasks(request: CodeReviewRequest): Promise<CodeReviewResult> {
|
|
839
|
-
await this.ensureMcoda();
|
|
840
|
-
const agentStream = request.agentStream !== false;
|
|
841
|
-
const baseRef = request.baseRef ?? this.workspace.config?.branch ?? DEFAULT_BASE_BRANCH;
|
|
842
|
-
const statusFilter = request.statusFilter && request.statusFilter.length ? request.statusFilter : ["ready_to_review"];
|
|
843
|
-
let state: ReviewJobState | undefined;
|
|
844
|
-
|
|
845
|
-
const commandRun = await this.deps.jobService.startCommandRun("code-review", request.projectKey, {
|
|
846
|
-
taskIds: request.taskKeys,
|
|
847
|
-
gitBaseBranch: baseRef,
|
|
848
|
-
jobId: request.resumeJobId,
|
|
849
|
-
});
|
|
850
|
-
|
|
851
|
-
let jobId = request.resumeJobId;
|
|
852
|
-
let selectedTaskIds: string[] = [];
|
|
853
|
-
let warnings: string[] = [];
|
|
854
|
-
let selectedTasks: Array<TaskRow & { epicKey: string; storyKey: string; epicTitle?: string; epicDescription?: string; storyTitle?: string; storyDescription?: string; acceptanceCriteria?: string[] }> =
|
|
855
|
-
[];
|
|
856
|
-
|
|
857
|
-
if (request.resumeJobId) {
|
|
858
|
-
const job = await this.deps.jobService.getJob(request.resumeJobId);
|
|
859
|
-
if (!job) throw new Error(`Job not found: ${request.resumeJobId}`);
|
|
860
|
-
if ((job.commandName ?? job.type) !== "code-review" && job.type !== "review") {
|
|
861
|
-
throw new Error(`Job ${request.resumeJobId} is not a code-review job`);
|
|
862
|
-
}
|
|
863
|
-
state = await this.loadState(request.resumeJobId);
|
|
864
|
-
selectedTaskIds = state?.selectedTaskIds ?? (Array.isArray((job.payload as any)?.selection) ? ((job.payload as any).selection as string[]) : []);
|
|
865
|
-
if (!selectedTaskIds.length) {
|
|
866
|
-
throw new Error("Resume requested but no task selection found in job payload");
|
|
867
|
-
}
|
|
868
|
-
await this.deps.jobService.updateJobStatus(job.id, "running", {
|
|
869
|
-
totalItems: job.totalItems ?? selectedTaskIds.length,
|
|
870
|
-
processedItems: state?.reviewed.length ?? 0,
|
|
871
|
-
});
|
|
872
|
-
selectedTasks = await this.deps.workspaceRepo.getTasksWithRelations(selectedTaskIds);
|
|
873
|
-
} else {
|
|
874
|
-
try {
|
|
875
|
-
selectedTasks = await this.selectTasksViaApi({
|
|
876
|
-
projectKey: request.projectKey,
|
|
877
|
-
epicKey: request.epicKey,
|
|
878
|
-
storyKey: request.storyKey,
|
|
879
|
-
taskKeys: request.taskKeys,
|
|
880
|
-
statusFilter,
|
|
881
|
-
limit: request.limit,
|
|
882
|
-
});
|
|
883
|
-
} catch {
|
|
884
|
-
const selection = await this.selectionService.selectTasks({
|
|
885
|
-
projectKey: request.projectKey,
|
|
886
|
-
epicKey: request.epicKey,
|
|
887
|
-
storyKey: request.storyKey,
|
|
888
|
-
taskKeys: request.taskKeys,
|
|
889
|
-
statusFilter,
|
|
890
|
-
limit: request.limit,
|
|
891
|
-
});
|
|
892
|
-
warnings = [...selection.warnings];
|
|
893
|
-
selectedTasks = selection.ordered.map((t) => t.task);
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
selectedTaskIds = selectedTasks.map((t) => t.id);
|
|
897
|
-
const job = await this.deps.jobService.startJob("review", commandRun.id, request.projectKey, {
|
|
898
|
-
commandName: "code-review",
|
|
899
|
-
payload: {
|
|
900
|
-
projectKey: request.projectKey,
|
|
901
|
-
epicKey: request.epicKey,
|
|
902
|
-
storyKey: request.storyKey,
|
|
903
|
-
tasks: request.taskKeys,
|
|
904
|
-
statusFilter,
|
|
905
|
-
baseRef,
|
|
906
|
-
selection: selectedTaskIds,
|
|
907
|
-
dryRun: request.dryRun ?? false,
|
|
908
|
-
agent: request.agentName,
|
|
909
|
-
agentStream,
|
|
910
|
-
},
|
|
911
|
-
totalItems: selectedTaskIds.length,
|
|
912
|
-
processedItems: 0,
|
|
913
|
-
});
|
|
914
|
-
jobId = job.id;
|
|
915
|
-
state = {
|
|
916
|
-
baseRef,
|
|
917
|
-
statusFilter,
|
|
918
|
-
selectedTaskIds,
|
|
919
|
-
contextBuilt: [],
|
|
920
|
-
reviewed: [],
|
|
921
|
-
};
|
|
922
|
-
await this.persistState(jobId, state);
|
|
923
|
-
await this.writeCheckpoint(jobId, "tasks_selected", { tasks: selectedTaskIds, baseRef, statusFilter });
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
if (!jobId) {
|
|
927
|
-
throw new Error("Failed to resolve job id for code-review");
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
if (!state) {
|
|
931
|
-
state = {
|
|
932
|
-
baseRef,
|
|
933
|
-
statusFilter,
|
|
934
|
-
selectedTaskIds,
|
|
935
|
-
contextBuilt: [],
|
|
936
|
-
reviewed: [],
|
|
937
|
-
};
|
|
938
|
-
await this.persistState(jobId, state);
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
if (selectedTaskIds.length === 0) {
|
|
942
|
-
await this.deps.jobService.updateJobStatus(jobId, "completed", { totalItems: 0, processedItems: 0 });
|
|
943
|
-
await this.deps.jobService.finishCommandRun(commandRun.id, "succeeded");
|
|
944
|
-
return { jobId, commandRunId: commandRun.id, tasks: [], warnings };
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
const tasks =
|
|
948
|
-
selectedTasks.length && selectedTaskIds.length === selectedTasks.length
|
|
949
|
-
? selectedTasks
|
|
950
|
-
: await this.deps.workspaceRepo.getTasksWithRelations(selectedTaskIds);
|
|
951
|
-
const agent = await this.resolveAgent(request.agentName);
|
|
952
|
-
const prompts = await this.loadPrompts(agent.id);
|
|
953
|
-
const extras = await this.loadRunbookAndChecklists();
|
|
954
|
-
const systemPrompts = [prompts.jobPrompt, prompts.characterPrompt, prompts.commandPrompt, ...extras].filter(Boolean) as string[];
|
|
955
|
-
|
|
956
|
-
const results: TaskReviewResult[] = [];
|
|
957
|
-
|
|
958
|
-
for (const task of tasks) {
|
|
959
|
-
const statusBefore = task.status;
|
|
960
|
-
const taskRun = await this.deps.workspaceRepo.createTaskRun({
|
|
961
|
-
taskId: task.id,
|
|
962
|
-
command: "code-review",
|
|
963
|
-
jobId,
|
|
964
|
-
commandRunId: commandRun.id,
|
|
965
|
-
agentId: agent.id,
|
|
966
|
-
status: "running",
|
|
967
|
-
startedAt: new Date().toISOString(),
|
|
968
|
-
storyPointsAtRun: task.storyPoints ?? null,
|
|
969
|
-
gitBranch: task.vcsBranch ?? null,
|
|
970
|
-
gitBaseBranch: task.vcsBaseBranch ?? null,
|
|
971
|
-
gitCommitSha: task.vcsLastCommitSha ?? null,
|
|
972
|
-
});
|
|
973
|
-
|
|
974
|
-
const findings: ReviewFinding[] = [];
|
|
975
|
-
let decision: ReviewAgentResult["decision"] | undefined;
|
|
976
|
-
let statusAfter: string | undefined;
|
|
977
|
-
const followupCreated: { taskId: string; taskKey: string; epicId: string; userStoryId: string; generic?: boolean }[] = [];
|
|
978
|
-
|
|
979
|
-
// Debug visibility: show prompts/task details for this run
|
|
980
|
-
const systemPrompt = systemPrompts.join("\n\n");
|
|
981
|
-
|
|
982
|
-
try {
|
|
983
|
-
const metadata = (task.metadata as any) ?? {};
|
|
984
|
-
const allowedFiles: string[] = Array.isArray(metadata.files) ? metadata.files : [];
|
|
985
|
-
const diffResult = await this.buildDiff(task, state?.baseRef ?? baseRef, allowedFiles);
|
|
986
|
-
const diff = diffResult.diff;
|
|
987
|
-
if (diffResult.warning) warnings.push(diffResult.warning);
|
|
988
|
-
await this.persistDiff(jobId, task.id, diff);
|
|
989
|
-
await this.deps.workspaceRepo.insertTaskLog({
|
|
990
|
-
taskRunId: taskRun.id,
|
|
991
|
-
sequence: this.sequenceForTask(taskRun.id),
|
|
992
|
-
timestamp: new Date().toISOString(),
|
|
993
|
-
source: "context_git_diff",
|
|
994
|
-
message: "Git diff computed",
|
|
995
|
-
details: {
|
|
996
|
-
baseRef: state?.baseRef ?? baseRef,
|
|
997
|
-
branch: task.vcsBranch,
|
|
998
|
-
commitSha: diffResult.commitSha,
|
|
999
|
-
diffSource: diffResult.source,
|
|
1000
|
-
allowedFiles,
|
|
1001
|
-
},
|
|
1002
|
-
});
|
|
1003
|
-
|
|
1004
|
-
const historySummary = await this.buildHistorySummary(task.id);
|
|
1005
|
-
await this.deps.workspaceRepo.insertTaskLog({
|
|
1006
|
-
taskRunId: taskRun.id,
|
|
1007
|
-
sequence: this.sequenceForTask(taskRun.id),
|
|
1008
|
-
timestamp: new Date().toISOString(),
|
|
1009
|
-
source: "context_history",
|
|
1010
|
-
message: "Loaded task history",
|
|
1011
|
-
});
|
|
1012
|
-
|
|
1013
|
-
const changedPaths = this.extractPathsFromDiff(diff);
|
|
1014
|
-
const docLinks = await this.gatherDocContext(task.title, changedPaths.length ? changedPaths : allowedFiles, task.acceptanceCriteria);
|
|
1015
|
-
if (docLinks.warnings.length) warnings.push(...docLinks.warnings);
|
|
1016
|
-
await this.deps.workspaceRepo.insertTaskLog({
|
|
1017
|
-
taskRunId: taskRun.id,
|
|
1018
|
-
sequence: this.sequenceForTask(taskRun.id),
|
|
1019
|
-
timestamp: new Date().toISOString(),
|
|
1020
|
-
source: "context_docdex",
|
|
1021
|
-
message: "Docdex context gathered",
|
|
1022
|
-
details: { snippets: docLinks.snippets },
|
|
1023
|
-
});
|
|
1024
|
-
|
|
1025
|
-
const openapiSnippet = await this.buildOpenApiSlice(changedPaths, task.acceptanceCriteria);
|
|
1026
|
-
if (!openapiSnippet) {
|
|
1027
|
-
warnings.push("OpenAPI spec not found; proceeding without snippet");
|
|
1028
|
-
}
|
|
1029
|
-
await this.deps.workspaceRepo.insertTaskLog({
|
|
1030
|
-
taskRunId: taskRun.id,
|
|
1031
|
-
sequence: this.sequenceForTask(taskRun.id),
|
|
1032
|
-
timestamp: new Date().toISOString(),
|
|
1033
|
-
source: "context_openapi",
|
|
1034
|
-
message: "OpenAPI snippet loaded",
|
|
1035
|
-
});
|
|
1036
|
-
|
|
1037
|
-
const prompt = this.buildReviewPrompt({
|
|
1038
|
-
systemPrompts,
|
|
1039
|
-
task,
|
|
1040
|
-
diff,
|
|
1041
|
-
docContext: docLinks.snippets,
|
|
1042
|
-
openapiSnippet,
|
|
1043
|
-
historySummary,
|
|
1044
|
-
baseRef: state?.baseRef ?? baseRef,
|
|
1045
|
-
branch: task.vcsBranch ?? undefined,
|
|
1046
|
-
});
|
|
1047
|
-
|
|
1048
|
-
const separator = "============================================================";
|
|
1049
|
-
const deps =
|
|
1050
|
-
Array.isArray((task as any).dependencyKeys) && (task as any).dependencyKeys.length
|
|
1051
|
-
? (task as any).dependencyKeys
|
|
1052
|
-
: Array.isArray((task.metadata as any)?.depends_on)
|
|
1053
|
-
? ((task.metadata as any).depends_on as string[])
|
|
1054
|
-
: [];
|
|
1055
|
-
console.info(separator);
|
|
1056
|
-
console.info("[code-review] START OF TASK");
|
|
1057
|
-
console.info(`[code-review] Task key: ${task.key}`);
|
|
1058
|
-
console.info(`[code-review] Title: ${task.title ?? "(none)"}`);
|
|
1059
|
-
console.info(`[code-review] Description: ${task.description ?? "(none)"}`);
|
|
1060
|
-
console.info(
|
|
1061
|
-
`[code-review] Story points: ${typeof task.storyPoints === "number" ? task.storyPoints : "(none)"}`,
|
|
1062
|
-
);
|
|
1063
|
-
console.info(`[code-review] Dependencies: ${deps.length ? deps.join(", ") : "(none available)"}`);
|
|
1064
|
-
if (Array.isArray(task.acceptanceCriteria) && task.acceptanceCriteria.length) {
|
|
1065
|
-
console.info(`[code-review] Acceptance criteria:\n- ${task.acceptanceCriteria.join("\n- ")}`);
|
|
1066
|
-
}
|
|
1067
|
-
console.info(`[code-review] System prompt used:\n${systemPrompt || "(none)"}`);
|
|
1068
|
-
console.info(`[code-review] Task prompt used:\n${prompt}`);
|
|
1069
|
-
console.info(separator);
|
|
1070
|
-
|
|
1071
|
-
await this.persistContext(jobId, task.id, {
|
|
1072
|
-
historySummary,
|
|
1073
|
-
docdex: docLinks.snippets,
|
|
1074
|
-
openapiSnippet,
|
|
1075
|
-
changedPaths,
|
|
1076
|
-
});
|
|
1077
|
-
state?.contextBuilt.push(task.id);
|
|
1078
|
-
await this.persistState(jobId, state!);
|
|
1079
|
-
await this.writeCheckpoint(jobId, "context_built", { contextBuilt: state?.contextBuilt ?? [], schema_version: 1 });
|
|
1080
|
-
|
|
1081
|
-
const recordUsage = async (
|
|
1082
|
-
phase: string,
|
|
1083
|
-
promptText: string,
|
|
1084
|
-
outputText: string,
|
|
1085
|
-
durationSeconds: number,
|
|
1086
|
-
tokenMeta?: { tokensPrompt?: number; tokensCompletion?: number; tokensTotal?: number; model?: string | null },
|
|
1087
|
-
) => {
|
|
1088
|
-
const tokensPrompt = tokenMeta?.tokensPrompt ?? estimateTokens(promptText);
|
|
1089
|
-
const tokensCompletion = tokenMeta?.tokensCompletion ?? estimateTokens(outputText);
|
|
1090
|
-
const tokensTotal = tokenMeta?.tokensTotal ?? tokensPrompt + tokensCompletion;
|
|
1091
|
-
await this.deps.jobService.recordTokenUsage({
|
|
1092
|
-
workspaceId: this.workspace.workspaceId,
|
|
1093
|
-
agentId: agent.id,
|
|
1094
|
-
modelName: tokenMeta?.model ?? (agent as any).defaultModel ?? undefined,
|
|
1095
|
-
jobId,
|
|
1096
|
-
commandRunId: commandRun.id,
|
|
1097
|
-
taskRunId: taskRun.id,
|
|
1098
|
-
taskId: task.id,
|
|
1099
|
-
projectId: task.projectId,
|
|
1100
|
-
tokensPrompt,
|
|
1101
|
-
tokensCompletion,
|
|
1102
|
-
tokensTotal,
|
|
1103
|
-
durationSeconds,
|
|
1104
|
-
timestamp: new Date().toISOString(),
|
|
1105
|
-
metadata: { commandName: "code-review", phase, action: phase },
|
|
1106
|
-
});
|
|
1107
|
-
};
|
|
1108
|
-
|
|
1109
|
-
let agentOutput = "";
|
|
1110
|
-
let durationSeconds = 0;
|
|
1111
|
-
const started = Date.now();
|
|
1112
|
-
let lastStreamMeta: any;
|
|
1113
|
-
if (agentStream && this.deps.agentService.invokeStream) {
|
|
1114
|
-
const stream = await this.deps.agentService.invokeStream(agent.id, { input: prompt, metadata: { taskKey: task.key } });
|
|
1115
|
-
for await (const chunk of stream) {
|
|
1116
|
-
agentOutput += chunk.output ?? "";
|
|
1117
|
-
lastStreamMeta = chunk.metadata ?? lastStreamMeta;
|
|
1118
|
-
await this.deps.workspaceRepo.insertTaskLog({
|
|
1119
|
-
taskRunId: taskRun.id,
|
|
1120
|
-
sequence: this.sequenceForTask(taskRun.id),
|
|
1121
|
-
timestamp: new Date().toISOString(),
|
|
1122
|
-
source: "agent",
|
|
1123
|
-
message: chunk.output ?? "",
|
|
1124
|
-
});
|
|
1125
|
-
}
|
|
1126
|
-
durationSeconds = Math.round(((Date.now() - started) / 1000) * 1000) / 1000;
|
|
1127
|
-
} else {
|
|
1128
|
-
const response = await this.deps.agentService.invoke(agent.id, { input: prompt, metadata: { taskKey: task.key } });
|
|
1129
|
-
agentOutput = response.output ?? "";
|
|
1130
|
-
durationSeconds = Math.round(((Date.now() - started) / 1000) * 1000) / 1000;
|
|
1131
|
-
await this.deps.workspaceRepo.insertTaskLog({
|
|
1132
|
-
taskRunId: taskRun.id,
|
|
1133
|
-
sequence: this.sequenceForTask(taskRun.id),
|
|
1134
|
-
timestamp: new Date().toISOString(),
|
|
1135
|
-
source: "agent",
|
|
1136
|
-
message: agentOutput,
|
|
1137
|
-
});
|
|
1138
|
-
lastStreamMeta = response.metadata;
|
|
1139
|
-
}
|
|
1140
|
-
const tokenMetaMain = lastStreamMeta
|
|
1141
|
-
? {
|
|
1142
|
-
tokensPrompt: typeof lastStreamMeta.tokensPrompt === "number" ? lastStreamMeta.tokensPrompt : (lastStreamMeta.tokens_prompt as number | undefined),
|
|
1143
|
-
tokensCompletion:
|
|
1144
|
-
typeof lastStreamMeta.tokensCompletion === "number"
|
|
1145
|
-
? lastStreamMeta.tokensCompletion
|
|
1146
|
-
: (lastStreamMeta.tokens_completion as number | undefined),
|
|
1147
|
-
tokensTotal: typeof lastStreamMeta.tokensTotal === "number" ? lastStreamMeta.tokensTotal : (lastStreamMeta.tokens_total as number | undefined),
|
|
1148
|
-
model: (lastStreamMeta.model ?? lastStreamMeta.model_name ?? null) as string | null,
|
|
1149
|
-
}
|
|
1150
|
-
: undefined;
|
|
1151
|
-
await recordUsage("review_main", prompt, agentOutput, durationSeconds, tokenMetaMain);
|
|
1152
|
-
|
|
1153
|
-
let parsed = parseJsonOutput(agentOutput);
|
|
1154
|
-
if (!parsed) {
|
|
1155
|
-
await this.deps.workspaceRepo.insertTaskLog({
|
|
1156
|
-
taskRunId: taskRun.id,
|
|
1157
|
-
sequence: this.sequenceForTask(taskRun.id),
|
|
1158
|
-
timestamp: new Date().toISOString(),
|
|
1159
|
-
source: "agent",
|
|
1160
|
-
message: "Invalid JSON from agent; retrying once with stricter instructions.",
|
|
1161
|
-
});
|
|
1162
|
-
const retryPrompt = `${prompt}\n\nRespond ONLY with valid JSON matching the schema above. Do not include prose or fences.`;
|
|
1163
|
-
const retryStarted = Date.now();
|
|
1164
|
-
const retryResp = await this.deps.agentService.invoke(agent.id, { input: retryPrompt, metadata: { taskKey: task.key, retry: true } });
|
|
1165
|
-
const retryOutput = retryResp.output ?? "";
|
|
1166
|
-
const retryDuration = Math.round(((Date.now() - retryStarted) / 1000) * 1000) / 1000;
|
|
1167
|
-
await this.deps.workspaceRepo.insertTaskLog({
|
|
1168
|
-
taskRunId: taskRun.id,
|
|
1169
|
-
sequence: this.sequenceForTask(taskRun.id),
|
|
1170
|
-
timestamp: new Date().toISOString(),
|
|
1171
|
-
source: "agent_retry",
|
|
1172
|
-
message: retryOutput,
|
|
1173
|
-
});
|
|
1174
|
-
const retryTokenMeta = retryResp.metadata
|
|
1175
|
-
? {
|
|
1176
|
-
tokensPrompt:
|
|
1177
|
-
typeof retryResp.metadata.tokensPrompt === "number"
|
|
1178
|
-
? retryResp.metadata.tokensPrompt
|
|
1179
|
-
: (retryResp.metadata.tokens_prompt as number | undefined),
|
|
1180
|
-
tokensCompletion:
|
|
1181
|
-
typeof retryResp.metadata.tokensCompletion === "number"
|
|
1182
|
-
? retryResp.metadata.tokensCompletion
|
|
1183
|
-
: (retryResp.metadata.tokens_completion as number | undefined),
|
|
1184
|
-
tokensTotal:
|
|
1185
|
-
typeof retryResp.metadata.tokensTotal === "number"
|
|
1186
|
-
? retryResp.metadata.tokensTotal
|
|
1187
|
-
: (retryResp.metadata.tokens_total as number | undefined),
|
|
1188
|
-
model: (retryResp.metadata.model ?? retryResp.metadata.model_name ?? null) as string | null,
|
|
1189
|
-
}
|
|
1190
|
-
: undefined;
|
|
1191
|
-
await recordUsage("review_retry", retryPrompt, retryOutput, retryDuration, retryTokenMeta);
|
|
1192
|
-
parsed = parseJsonOutput(retryOutput);
|
|
1193
|
-
agentOutput = retryOutput;
|
|
1194
|
-
}
|
|
1195
|
-
if (!parsed) {
|
|
1196
|
-
throw new Error("Agent output did not contain valid JSON review result after retry");
|
|
1197
|
-
}
|
|
1198
|
-
parsed.raw = agentOutput;
|
|
1199
|
-
decision = parsed.decision;
|
|
1200
|
-
findings.push(...(parsed.findings ?? []));
|
|
1201
|
-
|
|
1202
|
-
const followups = await this.createFollowupTasksForFindings({
|
|
1203
|
-
task,
|
|
1204
|
-
findings: parsed.findings ?? [],
|
|
1205
|
-
decision: parsed.decision,
|
|
1206
|
-
jobId,
|
|
1207
|
-
commandRunId: commandRun.id,
|
|
1208
|
-
taskRunId: taskRun.id,
|
|
1209
|
-
});
|
|
1210
|
-
if (followups.length) {
|
|
1211
|
-
followupCreated.push(
|
|
1212
|
-
...followups.map((t) => ({
|
|
1213
|
-
taskId: t.id,
|
|
1214
|
-
taskKey: t.key,
|
|
1215
|
-
epicId: t.epicId,
|
|
1216
|
-
userStoryId: t.userStoryId,
|
|
1217
|
-
generic: (t as any)?.metadata?.generic ? true : undefined,
|
|
1218
|
-
})),
|
|
1219
|
-
);
|
|
1220
|
-
warnings.push(`Created follow-up tasks for ${task.key}: ${followups.map((t) => t.key).join(", ")}`);
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
let taskStatusUpdate = statusBefore;
|
|
1224
|
-
if (!request.dryRun) {
|
|
1225
|
-
if (parsed.decision === "approve") {
|
|
1226
|
-
await this.stateService.markReadyToQa(task);
|
|
1227
|
-
taskStatusUpdate = "ready_to_qa";
|
|
1228
|
-
} else if (parsed.decision === "changes_requested") {
|
|
1229
|
-
await this.stateService.returnToInProgress(task);
|
|
1230
|
-
taskStatusUpdate = "in_progress";
|
|
1231
|
-
} else if (parsed.decision === "block") {
|
|
1232
|
-
await this.stateService.markBlocked(task, "review_blocked");
|
|
1233
|
-
taskStatusUpdate = "blocked";
|
|
1234
|
-
}
|
|
1235
|
-
} else {
|
|
1236
|
-
await this.deps.workspaceRepo.insertTaskLog({
|
|
1237
|
-
taskRunId: taskRun.id,
|
|
1238
|
-
sequence: this.sequenceForTask(taskRun.id),
|
|
1239
|
-
timestamp: new Date().toISOString(),
|
|
1240
|
-
source: "state",
|
|
1241
|
-
message: "Dry-run enabled; skipping status transition.",
|
|
1242
|
-
details: { requestedDecision: parsed.decision },
|
|
1243
|
-
});
|
|
1244
|
-
}
|
|
1245
|
-
statusAfter = taskStatusUpdate;
|
|
1246
|
-
|
|
1247
|
-
for (const finding of parsed.findings ?? []) {
|
|
1248
|
-
await this.deps.workspaceRepo.createTaskComment({
|
|
1249
|
-
taskId: task.id,
|
|
1250
|
-
taskRunId: taskRun.id,
|
|
1251
|
-
jobId,
|
|
1252
|
-
sourceCommand: "code-review",
|
|
1253
|
-
authorType: "agent",
|
|
1254
|
-
authorAgentId: agent.id,
|
|
1255
|
-
category: finding.type ?? "other",
|
|
1256
|
-
file: finding.file,
|
|
1257
|
-
line: finding.line,
|
|
1258
|
-
pathHint: finding.file,
|
|
1259
|
-
body: finding.message + (finding.suggestedFix ? `\n\nSuggested fix: ${finding.suggestedFix}` : ""),
|
|
1260
|
-
metadata: {
|
|
1261
|
-
severity: finding.severity,
|
|
1262
|
-
suggestedFix: finding.suggestedFix,
|
|
1263
|
-
},
|
|
1264
|
-
createdAt: new Date().toISOString(),
|
|
1265
|
-
});
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
await this.writeReviewSummaryComment({
|
|
1269
|
-
task,
|
|
1270
|
-
taskRunId: taskRun.id,
|
|
1271
|
-
jobId,
|
|
1272
|
-
agentId: agent.id,
|
|
1273
|
-
statusBefore,
|
|
1274
|
-
statusAfter: statusAfter ?? statusBefore,
|
|
1275
|
-
decision: parsed.decision,
|
|
1276
|
-
summary: parsed.summary,
|
|
1277
|
-
findingsCount: parsed.findings?.length ?? 0,
|
|
1278
|
-
followupTaskKeys: followupCreated.map((t) => t.taskKey),
|
|
1279
|
-
});
|
|
1280
|
-
|
|
1281
|
-
await this.deps.workspaceRepo.createTaskReview({
|
|
1282
|
-
taskId: task.id,
|
|
1283
|
-
jobId,
|
|
1284
|
-
agentId: agent.id,
|
|
1285
|
-
modelName: (agent as any).defaultModel ?? undefined,
|
|
1286
|
-
decision: parsed.decision,
|
|
1287
|
-
summary: parsed.summary ?? undefined,
|
|
1288
|
-
findingsJson: parsed.findings ?? [],
|
|
1289
|
-
testRecommendationsJson: parsed.testRecommendations ?? [],
|
|
1290
|
-
createdAt: new Date().toISOString(),
|
|
1291
|
-
});
|
|
1292
|
-
await this.stateService.recordReviewMetadata(task, {
|
|
1293
|
-
decision: parsed.decision,
|
|
1294
|
-
agentId: agent.id,
|
|
1295
|
-
modelName: (agent as any).defaultModel ?? null,
|
|
1296
|
-
jobId,
|
|
1297
|
-
});
|
|
1298
|
-
|
|
1299
|
-
await this.deps.workspaceRepo.updateTaskRun(taskRun.id, {
|
|
1300
|
-
status: "succeeded",
|
|
1301
|
-
finishedAt: new Date().toISOString(),
|
|
1302
|
-
runContext: { decision: parsed.decision },
|
|
1303
|
-
});
|
|
1304
|
-
|
|
1305
|
-
state?.reviewed.push({ taskId: task.id, decision: parsed.decision });
|
|
1306
|
-
await this.persistState(jobId, state!);
|
|
1307
|
-
await this.writeCheckpoint(jobId, "review_applied", { reviewed: state?.reviewed ?? [], schema_version: 1 });
|
|
1308
|
-
} catch (error) {
|
|
1309
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1310
|
-
results.push({ taskId: task.id, taskKey: task.key, statusBefore, findings, error: message, followupTasks: followupCreated });
|
|
1311
|
-
await this.deps.workspaceRepo.insertTaskLog({
|
|
1312
|
-
taskRunId: taskRun.id,
|
|
1313
|
-
sequence: this.sequenceForTask(taskRun.id),
|
|
1314
|
-
timestamp: new Date().toISOString(),
|
|
1315
|
-
source: "review_error",
|
|
1316
|
-
message,
|
|
1317
|
-
});
|
|
1318
|
-
try {
|
|
1319
|
-
await this.writeReviewSummaryComment({
|
|
1320
|
-
task,
|
|
1321
|
-
taskRunId: taskRun.id,
|
|
1322
|
-
jobId,
|
|
1323
|
-
agentId: agent.id,
|
|
1324
|
-
statusBefore,
|
|
1325
|
-
statusAfter: statusBefore,
|
|
1326
|
-
findingsCount: findings.length,
|
|
1327
|
-
error: message,
|
|
1328
|
-
});
|
|
1329
|
-
} catch {
|
|
1330
|
-
await this.deps.workspaceRepo.insertTaskLog({
|
|
1331
|
-
taskRunId: taskRun.id,
|
|
1332
|
-
sequence: this.sequenceForTask(taskRun.id),
|
|
1333
|
-
timestamp: new Date().toISOString(),
|
|
1334
|
-
source: "review_error",
|
|
1335
|
-
message: "Failed to write review summary comment.",
|
|
1336
|
-
});
|
|
1337
|
-
}
|
|
1338
|
-
await this.deps.workspaceRepo.updateTaskRun(taskRun.id, {
|
|
1339
|
-
status: "failed",
|
|
1340
|
-
finishedAt: new Date().toISOString(),
|
|
1341
|
-
});
|
|
1342
|
-
state?.reviewed.push({ taskId: task.id, error: message });
|
|
1343
|
-
await this.persistState(jobId, state!);
|
|
1344
|
-
await this.writeCheckpoint(jobId, "review_applied", { reviewed: state?.reviewed ?? [], schema_version: 1 });
|
|
1345
|
-
await this.deps.jobService.updateJobStatus(jobId, "running", {
|
|
1346
|
-
processedItems: state?.reviewed.length ?? 0,
|
|
1347
|
-
});
|
|
1348
|
-
continue;
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
results.push({
|
|
1352
|
-
taskId: task.id,
|
|
1353
|
-
taskKey: task.key,
|
|
1354
|
-
statusBefore,
|
|
1355
|
-
statusAfter,
|
|
1356
|
-
decision,
|
|
1357
|
-
findings,
|
|
1358
|
-
followupTasks: followupCreated,
|
|
1359
|
-
});
|
|
1360
|
-
|
|
1361
|
-
await this.deps.jobService.updateJobStatus(jobId, "running", {
|
|
1362
|
-
processedItems: state?.reviewed.length ?? 0,
|
|
1363
|
-
});
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
await this.deps.jobService.updateJobStatus(jobId, "completed", {
|
|
1367
|
-
processedItems: state?.reviewed.length ?? selectedTaskIds.length,
|
|
1368
|
-
totalItems: selectedTaskIds.length,
|
|
1369
|
-
});
|
|
1370
|
-
await this.deps.jobService.finishCommandRun(commandRun.id, "succeeded");
|
|
1371
|
-
|
|
1372
|
-
return {
|
|
1373
|
-
jobId,
|
|
1374
|
-
commandRunId: commandRun.id,
|
|
1375
|
-
tasks: results,
|
|
1376
|
-
warnings,
|
|
1377
|
-
};
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
private sequenceForTask(taskRunId: string): number {
|
|
1381
|
-
const current = this.taskLogSeq.get(taskRunId) ?? 0;
|
|
1382
|
-
const next = current + 1;
|
|
1383
|
-
this.taskLogSeq.set(taskRunId, next);
|
|
1384
|
-
return next;
|
|
1385
|
-
}
|
|
1386
|
-
}
|