@intrect/openswarm 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +544 -0
- package/config.example.yaml +107 -0
- package/dist/adapters/base.d.ts +8 -0
- package/dist/adapters/base.d.ts.map +1 -0
- package/dist/adapters/base.js +104 -0
- package/dist/adapters/base.js.map +1 -0
- package/dist/adapters/claude.d.ts +13 -0
- package/dist/adapters/claude.d.ts.map +1 -0
- package/dist/adapters/claude.js +318 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/adapters/codex.d.ts +14 -0
- package/dist/adapters/codex.d.ts.map +1 -0
- package/dist/adapters/codex.js +366 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/cryptoQuantAdapter.d.ts +85 -0
- package/dist/adapters/cryptoQuantAdapter.d.ts.map +1 -0
- package/dist/adapters/cryptoQuantAdapter.js +238 -0
- package/dist/adapters/cryptoQuantAdapter.js.map +1 -0
- package/dist/adapters/index.d.ts +17 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +47 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/processRegistry.d.ts +38 -0
- package/dist/adapters/processRegistry.d.ts.map +1 -0
- package/dist/adapters/processRegistry.js +147 -0
- package/dist/adapters/processRegistry.js.map +1 -0
- package/dist/adapters/streamBuffer.d.ts +59 -0
- package/dist/adapters/streamBuffer.d.ts.map +1 -0
- package/dist/adapters/streamBuffer.js +123 -0
- package/dist/adapters/streamBuffer.js.map +1 -0
- package/dist/adapters/types.d.ts +65 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +6 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/agents/agentBus.d.ts +160 -0
- package/dist/agents/agentBus.d.ts.map +1 -0
- package/dist/agents/agentBus.js +350 -0
- package/dist/agents/agentBus.js.map +1 -0
- package/dist/agents/agentPair.d.ts +210 -0
- package/dist/agents/agentPair.d.ts.map +1 -0
- package/dist/agents/agentPair.js +420 -0
- package/dist/agents/agentPair.js.map +1 -0
- package/dist/agents/auditor.d.ts +27 -0
- package/dist/agents/auditor.d.ts.map +1 -0
- package/dist/agents/auditor.js +237 -0
- package/dist/agents/auditor.js.map +1 -0
- package/dist/agents/cliStreamParser.d.ts +18 -0
- package/dist/agents/cliStreamParser.d.ts.map +1 -0
- package/dist/agents/cliStreamParser.js +156 -0
- package/dist/agents/cliStreamParser.js.map +1 -0
- package/dist/agents/documenter.d.ts +31 -0
- package/dist/agents/documenter.d.ts.map +1 -0
- package/dist/agents/documenter.js +285 -0
- package/dist/agents/documenter.js.map +1 -0
- package/dist/agents/index.d.ts +10 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +10 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/pairMetrics.d.ts +63 -0
- package/dist/agents/pairMetrics.d.ts.map +1 -0
- package/dist/agents/pairMetrics.js +231 -0
- package/dist/agents/pairMetrics.js.map +1 -0
- package/dist/agents/pairPipeline.d.ts +155 -0
- package/dist/agents/pairPipeline.d.ts.map +1 -0
- package/dist/agents/pairPipeline.js +793 -0
- package/dist/agents/pairPipeline.js.map +1 -0
- package/dist/agents/pairWebhook.d.ts +59 -0
- package/dist/agents/pairWebhook.d.ts.map +1 -0
- package/dist/agents/pairWebhook.js +242 -0
- package/dist/agents/pairWebhook.js.map +1 -0
- package/dist/agents/pipelineFormat.d.ts +11 -0
- package/dist/agents/pipelineFormat.d.ts.map +1 -0
- package/dist/agents/pipelineFormat.js +164 -0
- package/dist/agents/pipelineFormat.js.map +1 -0
- package/dist/agents/pipelineGuards.d.ts +23 -0
- package/dist/agents/pipelineGuards.d.ts.map +1 -0
- package/dist/agents/pipelineGuards.js +175 -0
- package/dist/agents/pipelineGuards.js.map +1 -0
- package/dist/agents/reviewer.d.ts +37 -0
- package/dist/agents/reviewer.d.ts.map +1 -0
- package/dist/agents/reviewer.js +213 -0
- package/dist/agents/reviewer.js.map +1 -0
- package/dist/agents/skillDocumenter.d.ts +23 -0
- package/dist/agents/skillDocumenter.d.ts.map +1 -0
- package/dist/agents/skillDocumenter.js +218 -0
- package/dist/agents/skillDocumenter.js.map +1 -0
- package/dist/agents/tester.d.ts +37 -0
- package/dist/agents/tester.d.ts.map +1 -0
- package/dist/agents/tester.js +308 -0
- package/dist/agents/tester.js.map +1 -0
- package/dist/agents/worker.d.ts +30 -0
- package/dist/agents/worker.d.ts.map +1 -0
- package/dist/agents/worker.js +128 -0
- package/dist/agents/worker.js.map +1 -0
- package/dist/automation/autonomousRunner.d.ts +123 -0
- package/dist/automation/autonomousRunner.d.ts.map +1 -0
- package/dist/automation/autonomousRunner.js +1082 -0
- package/dist/automation/autonomousRunner.js.map +1 -0
- package/dist/automation/ciWorker.d.ts +51 -0
- package/dist/automation/ciWorker.d.ts.map +1 -0
- package/dist/automation/ciWorker.js +282 -0
- package/dist/automation/ciWorker.js.map +1 -0
- package/dist/automation/conflictResolver.d.ts +29 -0
- package/dist/automation/conflictResolver.d.ts.map +1 -0
- package/dist/automation/conflictResolver.js +261 -0
- package/dist/automation/conflictResolver.js.map +1 -0
- package/dist/automation/dailyReporter.d.ts +26 -0
- package/dist/automation/dailyReporter.d.ts.map +1 -0
- package/dist/automation/dailyReporter.js +132 -0
- package/dist/automation/dailyReporter.js.map +1 -0
- package/dist/automation/index.d.ts +7 -0
- package/dist/automation/index.d.ts.map +1 -0
- package/dist/automation/index.js +7 -0
- package/dist/automation/index.js.map +1 -0
- package/dist/automation/longRunningMonitor.d.ts +26 -0
- package/dist/automation/longRunningMonitor.d.ts.map +1 -0
- package/dist/automation/longRunningMonitor.js +231 -0
- package/dist/automation/longRunningMonitor.js.map +1 -0
- package/dist/automation/prOwnership.d.ts +18 -0
- package/dist/automation/prOwnership.d.ts.map +1 -0
- package/dist/automation/prOwnership.js +61 -0
- package/dist/automation/prOwnership.js.map +1 -0
- package/dist/automation/prProcessor.d.ts +62 -0
- package/dist/automation/prProcessor.d.ts.map +1 -0
- package/dist/automation/prProcessor.js +700 -0
- package/dist/automation/prProcessor.js.map +1 -0
- package/dist/automation/runnerExecution.d.ts +49 -0
- package/dist/automation/runnerExecution.d.ts.map +1 -0
- package/dist/automation/runnerExecution.js +663 -0
- package/dist/automation/runnerExecution.js.map +1 -0
- package/dist/automation/runnerState.d.ts +170 -0
- package/dist/automation/runnerState.d.ts.map +1 -0
- package/dist/automation/runnerState.js +495 -0
- package/dist/automation/runnerState.js.map +1 -0
- package/dist/automation/runnerTypes.d.ts +46 -0
- package/dist/automation/runnerTypes.d.ts.map +1 -0
- package/dist/automation/runnerTypes.js +5 -0
- package/dist/automation/runnerTypes.js.map +1 -0
- package/dist/automation/scheduler.d.ts +75 -0
- package/dist/automation/scheduler.d.ts.map +1 -0
- package/dist/automation/scheduler.js +394 -0
- package/dist/automation/scheduler.js.map +1 -0
- package/dist/cli/promptHandler.d.ts +13 -0
- package/dist/cli/promptHandler.d.ts.map +1 -0
- package/dist/cli/promptHandler.js +189 -0
- package/dist/cli/promptHandler.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +138 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/config.d.ts +308 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +529 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/eventHub.d.ts +194 -0
- package/dist/core/eventHub.d.ts.map +1 -0
- package/dist/core/eventHub.js +136 -0
- package/dist/core/eventHub.js.map +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +6 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/service.d.ts +27 -0
- package/dist/core/service.d.ts.map +1 -0
- package/dist/core/service.js +438 -0
- package/dist/core/service.js.map +1 -0
- package/dist/core/traceCollector.d.ts +105 -0
- package/dist/core/traceCollector.d.ts.map +1 -0
- package/dist/core/traceCollector.js +141 -0
- package/dist/core/traceCollector.js.map +1 -0
- package/dist/core/types.d.ts +413 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +5 -0
- package/dist/core/types.js.map +1 -0
- package/dist/discord/discordCore.d.ts +104 -0
- package/dist/discord/discordCore.d.ts.map +1 -0
- package/dist/discord/discordCore.js +698 -0
- package/dist/discord/discordCore.js.map +1 -0
- package/dist/discord/discordHandlers.d.ts +86 -0
- package/dist/discord/discordHandlers.d.ts.map +1 -0
- package/dist/discord/discordHandlers.js +849 -0
- package/dist/discord/discordHandlers.js.map +1 -0
- package/dist/discord/discordPair.d.ts +6 -0
- package/dist/discord/discordPair.d.ts.map +1 -0
- package/dist/discord/discordPair.js +567 -0
- package/dist/discord/discordPair.js.map +1 -0
- package/dist/discord/index.d.ts +4 -0
- package/dist/discord/index.d.ts.map +1 -0
- package/dist/discord/index.js +11 -0
- package/dist/discord/index.js.map +1 -0
- package/dist/github/github.d.ts +236 -0
- package/dist/github/github.d.ts.map +1 -0
- package/dist/github/github.js +535 -0
- package/dist/github/github.js.map +1 -0
- package/dist/github/index.d.ts +2 -0
- package/dist/github/index.d.ts.map +1 -0
- package/dist/github/index.js +2 -0
- package/dist/github/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/knowledge/analyzer.d.ts +36 -0
- package/dist/knowledge/analyzer.d.ts.map +1 -0
- package/dist/knowledge/analyzer.js +170 -0
- package/dist/knowledge/analyzer.js.map +1 -0
- package/dist/knowledge/gitInfo.d.ts +10 -0
- package/dist/knowledge/gitInfo.d.ts.map +1 -0
- package/dist/knowledge/gitInfo.js +134 -0
- package/dist/knowledge/gitInfo.js.map +1 -0
- package/dist/knowledge/graph.d.ts +45 -0
- package/dist/knowledge/graph.d.ts.map +1 -0
- package/dist/knowledge/graph.js +262 -0
- package/dist/knowledge/graph.js.map +1 -0
- package/dist/knowledge/graphqlExporter.d.ts +64 -0
- package/dist/knowledge/graphqlExporter.d.ts.map +1 -0
- package/dist/knowledge/graphqlExporter.js +333 -0
- package/dist/knowledge/graphqlExporter.js.map +1 -0
- package/dist/knowledge/index.d.ts +58 -0
- package/dist/knowledge/index.d.ts.map +1 -0
- package/dist/knowledge/index.js +212 -0
- package/dist/knowledge/index.js.map +1 -0
- package/dist/knowledge/repository.d.ts +64 -0
- package/dist/knowledge/repository.d.ts.map +1 -0
- package/dist/knowledge/repository.js +250 -0
- package/dist/knowledge/repository.js.map +1 -0
- package/dist/knowledge/riskOnAnalyzer.d.ts +79 -0
- package/dist/knowledge/riskOnAnalyzer.d.ts.map +1 -0
- package/dist/knowledge/riskOnAnalyzer.js +243 -0
- package/dist/knowledge/riskOnAnalyzer.js.map +1 -0
- package/dist/knowledge/scanner.d.ts +14 -0
- package/dist/knowledge/scanner.d.ts.map +1 -0
- package/dist/knowledge/scanner.js +350 -0
- package/dist/knowledge/scanner.js.map +1 -0
- package/dist/knowledge/store.d.ts +23 -0
- package/dist/knowledge/store.d.ts.map +1 -0
- package/dist/knowledge/store.js +86 -0
- package/dist/knowledge/store.js.map +1 -0
- package/dist/knowledge/types.d.ts +288 -0
- package/dist/knowledge/types.d.ts.map +1 -0
- package/dist/knowledge/types.js +111 -0
- package/dist/knowledge/types.js.map +1 -0
- package/dist/linear/index.d.ts +3 -0
- package/dist/linear/index.d.ts.map +1 -0
- package/dist/linear/index.js +3 -0
- package/dist/linear/index.js.map +1 -0
- package/dist/linear/linear.d.ts +160 -0
- package/dist/linear/linear.d.ts.map +1 -0
- package/dist/linear/linear.js +983 -0
- package/dist/linear/linear.js.map +1 -0
- package/dist/linear/projectUpdater.d.ts +23 -0
- package/dist/linear/projectUpdater.d.ts.map +1 -0
- package/dist/linear/projectUpdater.js +347 -0
- package/dist/linear/projectUpdater.js.map +1 -0
- package/dist/locale/en.d.ts +3 -0
- package/dist/locale/en.d.ts.map +1 -0
- package/dist/locale/en.js +436 -0
- package/dist/locale/en.js.map +1 -0
- package/dist/locale/index.d.ts +28 -0
- package/dist/locale/index.d.ts.map +1 -0
- package/dist/locale/index.js +89 -0
- package/dist/locale/index.js.map +1 -0
- package/dist/locale/ko.d.ts +3 -0
- package/dist/locale/ko.d.ts.map +1 -0
- package/dist/locale/ko.js +436 -0
- package/dist/locale/ko.js.map +1 -0
- package/dist/locale/prompts/en.d.ts +3 -0
- package/dist/locale/prompts/en.d.ts.map +1 -0
- package/dist/locale/prompts/en.js +278 -0
- package/dist/locale/prompts/en.js.map +1 -0
- package/dist/locale/prompts/ko.d.ts +3 -0
- package/dist/locale/prompts/ko.d.ts.map +1 -0
- package/dist/locale/prompts/ko.js +279 -0
- package/dist/locale/prompts/ko.js.map +1 -0
- package/dist/locale/types.d.ts +407 -0
- package/dist/locale/types.d.ts.map +1 -0
- package/dist/locale/types.js +5 -0
- package/dist/locale/types.js.map +1 -0
- package/dist/memory/codex.d.ts +93 -0
- package/dist/memory/codex.d.ts.map +1 -0
- package/dist/memory/codex.js +366 -0
- package/dist/memory/codex.js.map +1 -0
- package/dist/memory/compaction.d.ts +21 -0
- package/dist/memory/compaction.d.ts.map +1 -0
- package/dist/memory/compaction.js +205 -0
- package/dist/memory/compaction.js.map +1 -0
- package/dist/memory/index.d.ts +13 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +13 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/memoryCore.d.ts +178 -0
- package/dist/memory/memoryCore.d.ts.map +1 -0
- package/dist/memory/memoryCore.js +623 -0
- package/dist/memory/memoryCore.js.map +1 -0
- package/dist/memory/memoryOps.d.ts +90 -0
- package/dist/memory/memoryOps.d.ts.map +1 -0
- package/dist/memory/memoryOps.js +606 -0
- package/dist/memory/memoryOps.js.map +1 -0
- package/dist/orchestration/conflictDetector.d.ts +15 -0
- package/dist/orchestration/conflictDetector.d.ts.map +1 -0
- package/dist/orchestration/conflictDetector.js +130 -0
- package/dist/orchestration/conflictDetector.js.map +1 -0
- package/dist/orchestration/decisionEngine.d.ts +177 -0
- package/dist/orchestration/decisionEngine.d.ts.map +1 -0
- package/dist/orchestration/decisionEngine.js +496 -0
- package/dist/orchestration/decisionEngine.js.map +1 -0
- package/dist/orchestration/index.d.ts +5 -0
- package/dist/orchestration/index.d.ts.map +1 -0
- package/dist/orchestration/index.js +5 -0
- package/dist/orchestration/index.js.map +1 -0
- package/dist/orchestration/taskParser.d.ts +67 -0
- package/dist/orchestration/taskParser.d.ts.map +1 -0
- package/dist/orchestration/taskParser.js +542 -0
- package/dist/orchestration/taskParser.js.map +1 -0
- package/dist/orchestration/taskScheduler.d.ts +141 -0
- package/dist/orchestration/taskScheduler.d.ts.map +1 -0
- package/dist/orchestration/taskScheduler.js +317 -0
- package/dist/orchestration/taskScheduler.js.map +1 -0
- package/dist/orchestration/workflow.d.ts +145 -0
- package/dist/orchestration/workflow.d.ts.map +1 -0
- package/dist/orchestration/workflow.js +301 -0
- package/dist/orchestration/workflow.js.map +1 -0
- package/dist/runners/cliRunner.d.ts +11 -0
- package/dist/runners/cliRunner.d.ts.map +1 -0
- package/dist/runners/cliRunner.js +194 -0
- package/dist/runners/cliRunner.js.map +1 -0
- package/dist/support/apiCache.d.ts +85 -0
- package/dist/support/apiCache.d.ts.map +1 -0
- package/dist/support/apiCache.js +163 -0
- package/dist/support/apiCache.js.map +1 -0
- package/dist/support/chat.d.ts +3 -0
- package/dist/support/chat.d.ts.map +1 -0
- package/dist/support/chat.js +304 -0
- package/dist/support/chat.js.map +1 -0
- package/dist/support/chatBackend.d.ts +25 -0
- package/dist/support/chatBackend.d.ts.map +1 -0
- package/dist/support/chatBackend.js +235 -0
- package/dist/support/chatBackend.js.map +1 -0
- package/dist/support/chatMemory.d.ts +71 -0
- package/dist/support/chatMemory.d.ts.map +1 -0
- package/dist/support/chatMemory.js +119 -0
- package/dist/support/chatMemory.js.map +1 -0
- package/dist/support/chatTui.d.ts +3 -0
- package/dist/support/chatTui.d.ts.map +1 -0
- package/dist/support/chatTui.js +998 -0
- package/dist/support/chatTui.js.map +1 -0
- package/dist/support/costTracker.d.ts +29 -0
- package/dist/support/costTracker.d.ts.map +1 -0
- package/dist/support/costTracker.js +113 -0
- package/dist/support/costTracker.js.map +1 -0
- package/dist/support/dashboardHtml.d.ts +3 -0
- package/dist/support/dashboardHtml.d.ts.map +1 -0
- package/dist/support/dashboardHtml.js +2070 -0
- package/dist/support/dashboardHtml.js.map +1 -0
- package/dist/support/delete-beliefs.d.ts +2 -0
- package/dist/support/delete-beliefs.d.ts.map +1 -0
- package/dist/support/delete-beliefs.js +34 -0
- package/dist/support/delete-beliefs.js.map +1 -0
- package/dist/support/dev.d.ts +55 -0
- package/dist/support/dev.d.ts.map +1 -0
- package/dist/support/dev.js +298 -0
- package/dist/support/dev.js.map +1 -0
- package/dist/support/editParser.d.ts +37 -0
- package/dist/support/editParser.d.ts.map +1 -0
- package/dist/support/editParser.js +365 -0
- package/dist/support/editParser.js.map +1 -0
- package/dist/support/gitStatus.d.ts +21 -0
- package/dist/support/gitStatus.d.ts.map +1 -0
- package/dist/support/gitStatus.js +108 -0
- package/dist/support/gitStatus.js.map +1 -0
- package/dist/support/gitTracker.d.ts +30 -0
- package/dist/support/gitTracker.d.ts.map +1 -0
- package/dist/support/gitTracker.js +143 -0
- package/dist/support/gitTracker.js.map +1 -0
- package/dist/support/index.d.ts +13 -0
- package/dist/support/index.d.ts.map +1 -0
- package/dist/support/index.js +13 -0
- package/dist/support/index.js.map +1 -0
- package/dist/support/planner.d.ts +58 -0
- package/dist/support/planner.d.ts.map +1 -0
- package/dist/support/planner.js +395 -0
- package/dist/support/planner.js.map +1 -0
- package/dist/support/projectMapper.d.ts +46 -0
- package/dist/support/projectMapper.d.ts.map +1 -0
- package/dist/support/projectMapper.js +259 -0
- package/dist/support/projectMapper.js.map +1 -0
- package/dist/support/quotaTracker.d.ts +29 -0
- package/dist/support/quotaTracker.d.ts.map +1 -0
- package/dist/support/quotaTracker.js +89 -0
- package/dist/support/quotaTracker.js.map +1 -0
- package/dist/support/rateLimiter.d.ts +101 -0
- package/dist/support/rateLimiter.d.ts.map +1 -0
- package/dist/support/rateLimiter.js +219 -0
- package/dist/support/rateLimiter.js.map +1 -0
- package/dist/support/rollback.d.ts +61 -0
- package/dist/support/rollback.d.ts.map +1 -0
- package/dist/support/rollback.js +328 -0
- package/dist/support/rollback.js.map +1 -0
- package/dist/support/stuckDetector.d.ts +68 -0
- package/dist/support/stuckDetector.d.ts.map +1 -0
- package/dist/support/stuckDetector.js +174 -0
- package/dist/support/stuckDetector.js.map +1 -0
- package/dist/support/timeWindow.d.ts +60 -0
- package/dist/support/timeWindow.d.ts.map +1 -0
- package/dist/support/timeWindow.js +236 -0
- package/dist/support/timeWindow.js.map +1 -0
- package/dist/support/web.d.ts +11 -0
- package/dist/support/web.d.ts.map +1 -0
- package/dist/support/web.js +938 -0
- package/dist/support/web.js.map +1 -0
- package/dist/support/workflowLinear.d.ts +99 -0
- package/dist/support/workflowLinear.d.ts.map +1 -0
- package/dist/support/workflowLinear.js +257 -0
- package/dist/support/workflowLinear.js.map +1 -0
- package/dist/support/worktreeManager.d.ts +20 -0
- package/dist/support/worktreeManager.d.ts.map +1 -0
- package/dist/support/worktreeManager.js +144 -0
- package/dist/support/worktreeManager.js.map +1 -0
- package/dist/taskState/store.d.ts +101 -0
- package/dist/taskState/store.d.ts.map +1 -0
- package/dist/taskState/store.js +346 -0
- package/dist/taskState/store.js.map +1 -0
- package/package.json +70 -0
- package/templates/AGENTS.md +432 -0
- package/templates/BOOT.md +25 -0
- package/templates/BOOTSTRAP.md +50 -0
- package/templates/CHANGELOG_AUDIT.md +74 -0
- package/templates/HEARTBEAT.md +86 -0
- package/templates/IDENTITY.md +27 -0
- package/templates/ISSUE_ANALYSIS.md +31 -0
- package/templates/PR_LAND.md +75 -0
- package/templates/PR_REVIEW.md +97 -0
- package/templates/SOUL.dev.md +52 -0
- package/templates/SOUL.md +81 -0
- package/templates/TOOLS.md +52 -0
- package/templates/USER.md +22 -0
|
@@ -0,0 +1,983 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// OpenSwarm - Linear Integration
|
|
3
|
+
// ============================================
|
|
4
|
+
import { LinearClient } from '@linear/sdk';
|
|
5
|
+
import { getDateLocale } from '../locale/index.js';
|
|
6
|
+
import { setLinearClient } from './projectUpdater.js';
|
|
7
|
+
import { withRateLimit } from '../support/rateLimiter.js';
|
|
8
|
+
/**
|
|
9
|
+
* Extract project info from an issue
|
|
10
|
+
*/
|
|
11
|
+
async function getProjectInfo(issue) {
|
|
12
|
+
try {
|
|
13
|
+
const project = await issue.project;
|
|
14
|
+
if (!project)
|
|
15
|
+
return undefined;
|
|
16
|
+
return {
|
|
17
|
+
id: project.id,
|
|
18
|
+
name: project.name,
|
|
19
|
+
icon: project.icon ?? undefined,
|
|
20
|
+
color: project.color ?? undefined,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
let client = null;
|
|
28
|
+
let teamId = '';
|
|
29
|
+
let teamIds = [];
|
|
30
|
+
/** Build a Linear team filter that works for both single and multiple team IDs */
|
|
31
|
+
function teamFilter() {
|
|
32
|
+
if (teamIds.length === 1) {
|
|
33
|
+
return { id: { eq: teamIds[0] } };
|
|
34
|
+
}
|
|
35
|
+
return { id: { in: teamIds } };
|
|
36
|
+
}
|
|
37
|
+
// Daily issue creation limit
|
|
38
|
+
const DAILY_ISSUE_LIMIT = 10;
|
|
39
|
+
let dailyIssueCount = 0;
|
|
40
|
+
let lastResetDate = '';
|
|
41
|
+
const inProgressCache = new Map();
|
|
42
|
+
const backlogCache = new Map();
|
|
43
|
+
const myIssuesCache = new Map();
|
|
44
|
+
const CACHE_TTL_MS = 300000; // 5 minute cache (was 1min, reduced API calls)
|
|
45
|
+
function isCacheValid(cache) {
|
|
46
|
+
if (!cache)
|
|
47
|
+
return false;
|
|
48
|
+
return Date.now() - cache.timestamp < CACHE_TTL_MS;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Clear all caches (call when issues are mutated)
|
|
52
|
+
*/
|
|
53
|
+
export function clearLinearCache() {
|
|
54
|
+
inProgressCache.clear();
|
|
55
|
+
backlogCache.clear();
|
|
56
|
+
myIssuesCache.clear();
|
|
57
|
+
console.log('[Linear] Cache cleared');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Reset daily counter on date change
|
|
61
|
+
*/
|
|
62
|
+
function resetDailyCounterIfNeeded() {
|
|
63
|
+
const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
|
|
64
|
+
if (today !== lastResetDate) {
|
|
65
|
+
dailyIssueCount = 0;
|
|
66
|
+
lastResetDate = today;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Remaining issue creation quota for today
|
|
71
|
+
*/
|
|
72
|
+
export function getRemainingDailyIssues() {
|
|
73
|
+
resetDailyCounterIfNeeded();
|
|
74
|
+
return Math.max(0, DAILY_ISSUE_LIMIT - dailyIssueCount);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Number of issues created today
|
|
78
|
+
*/
|
|
79
|
+
export function getDailyIssueCount() {
|
|
80
|
+
resetDailyCounterIfNeeded();
|
|
81
|
+
return dailyIssueCount;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Initialize the Linear client
|
|
85
|
+
* Rate limiting is applied at the function level in this file
|
|
86
|
+
*/
|
|
87
|
+
export function initLinear(apiKey, team) {
|
|
88
|
+
client = new LinearClient({ apiKey });
|
|
89
|
+
teamId = team;
|
|
90
|
+
teamIds = team.split(',').map(id => id.trim()).filter(Boolean);
|
|
91
|
+
setLinearClient(client);
|
|
92
|
+
console.log('[Linear] Client initialized');
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Return the Linear client instance
|
|
96
|
+
*/
|
|
97
|
+
export function getClient() {
|
|
98
|
+
if (!client) {
|
|
99
|
+
throw new Error('Linear client not initialized. Call initLinear() first.');
|
|
100
|
+
}
|
|
101
|
+
return client;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get in-progress issues for an agent (with caching)
|
|
105
|
+
*/
|
|
106
|
+
export async function getInProgressIssues(agentLabel) {
|
|
107
|
+
// Check cache first
|
|
108
|
+
const cached = inProgressCache.get(agentLabel);
|
|
109
|
+
if (cached && isCacheValid(cached)) {
|
|
110
|
+
console.log(`[Linear] Using cached in-progress issues for ${agentLabel}`);
|
|
111
|
+
return cached.data;
|
|
112
|
+
}
|
|
113
|
+
console.log(`[Linear] Fetching in-progress issues for ${agentLabel}`);
|
|
114
|
+
const linear = getClient();
|
|
115
|
+
const issues = await withRateLimit('linear', async () => linear.issues({
|
|
116
|
+
filter: {
|
|
117
|
+
team: teamFilter(),
|
|
118
|
+
state: { name: { in: ['In Progress', 'Started'] } },
|
|
119
|
+
labels: { name: { eq: agentLabel } },
|
|
120
|
+
},
|
|
121
|
+
}));
|
|
122
|
+
const result = [];
|
|
123
|
+
// Batch fetch all related data to minimize API calls
|
|
124
|
+
for (const issue of issues.nodes) {
|
|
125
|
+
// Use Promise.all to parallelize, but still results in N queries per issue
|
|
126
|
+
// Linear SDK doesn't support includes/eager loading, so this is unavoidable
|
|
127
|
+
const [comments, labels, state, project] = await Promise.all([
|
|
128
|
+
issue.comments(),
|
|
129
|
+
issue.labels(),
|
|
130
|
+
issue.state,
|
|
131
|
+
getProjectInfo(issue),
|
|
132
|
+
]);
|
|
133
|
+
result.push({
|
|
134
|
+
id: issue.id,
|
|
135
|
+
identifier: issue.identifier,
|
|
136
|
+
title: issue.title,
|
|
137
|
+
description: issue.description ?? undefined,
|
|
138
|
+
state: state?.name ?? 'Unknown',
|
|
139
|
+
priority: issue.priority,
|
|
140
|
+
labels: labels.nodes.map((l) => l.name),
|
|
141
|
+
comments: comments.nodes.map((c) => ({
|
|
142
|
+
id: c.id,
|
|
143
|
+
body: c.body,
|
|
144
|
+
createdAt: c.createdAt.toISOString(),
|
|
145
|
+
user: undefined, // TODO: resolve user name
|
|
146
|
+
})),
|
|
147
|
+
project,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
// Cache the result
|
|
151
|
+
inProgressCache.set(agentLabel, {
|
|
152
|
+
data: result,
|
|
153
|
+
timestamp: Date.now(),
|
|
154
|
+
agentLabel,
|
|
155
|
+
});
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get the next issue from the backlog (with caching)
|
|
160
|
+
*/
|
|
161
|
+
export async function getNextBacklogIssue(agentLabel) {
|
|
162
|
+
// Check cache first
|
|
163
|
+
const cached = backlogCache.get(agentLabel);
|
|
164
|
+
if (cached && isCacheValid(cached) && cached.data.length > 0) {
|
|
165
|
+
console.log(`[Linear] Using cached backlog issue for ${agentLabel}`);
|
|
166
|
+
return cached.data[0];
|
|
167
|
+
}
|
|
168
|
+
console.log(`[Linear] Fetching backlog issues for ${agentLabel}`);
|
|
169
|
+
const linear = getClient();
|
|
170
|
+
const issues = await withRateLimit('linear', async () => linear.issues({
|
|
171
|
+
filter: {
|
|
172
|
+
team: teamFilter(),
|
|
173
|
+
state: { name: { in: ['Backlog', 'Todo'] } },
|
|
174
|
+
labels: { name: { eq: agentLabel } },
|
|
175
|
+
},
|
|
176
|
+
first: 10, // Fetch multiple and sort by priority
|
|
177
|
+
}));
|
|
178
|
+
// Sort by priority (lower = higher priority: 1=Urgent, 4=Low, 0=None)
|
|
179
|
+
const sorted = [...issues.nodes].sort((a, b) => {
|
|
180
|
+
// Push priority 0 (None) to the end
|
|
181
|
+
const pa = a.priority === 0 ? 999 : a.priority;
|
|
182
|
+
const pb = b.priority === 0 ? 999 : b.priority;
|
|
183
|
+
return pa - pb;
|
|
184
|
+
});
|
|
185
|
+
const issue = sorted[0];
|
|
186
|
+
if (!issue)
|
|
187
|
+
return null;
|
|
188
|
+
const [comments, labels, state, project] = await Promise.all([
|
|
189
|
+
issue.comments(),
|
|
190
|
+
issue.labels(),
|
|
191
|
+
issue.state,
|
|
192
|
+
getProjectInfo(issue),
|
|
193
|
+
]);
|
|
194
|
+
const result = {
|
|
195
|
+
id: issue.id,
|
|
196
|
+
identifier: issue.identifier,
|
|
197
|
+
title: issue.title,
|
|
198
|
+
description: issue.description ?? undefined,
|
|
199
|
+
state: state?.name ?? 'Unknown',
|
|
200
|
+
priority: issue.priority,
|
|
201
|
+
labels: labels.nodes.map((l) => l.name),
|
|
202
|
+
comments: comments.nodes.map((c) => ({
|
|
203
|
+
id: c.id,
|
|
204
|
+
body: c.body,
|
|
205
|
+
createdAt: c.createdAt.toISOString(),
|
|
206
|
+
user: undefined,
|
|
207
|
+
})),
|
|
208
|
+
project,
|
|
209
|
+
};
|
|
210
|
+
// Cache the result
|
|
211
|
+
backlogCache.set(agentLabel, {
|
|
212
|
+
data: [result],
|
|
213
|
+
timestamp: Date.now(),
|
|
214
|
+
agentLabel,
|
|
215
|
+
});
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Get assigned active issues (with caching)
|
|
220
|
+
* (Todo, In Progress, Review states - excludes Backlog)
|
|
221
|
+
*/
|
|
222
|
+
export async function getMyIssues(agentLabelOrOptions) {
|
|
223
|
+
const opts = typeof agentLabelOrOptions === 'string'
|
|
224
|
+
? { agentLabel: agentLabelOrOptions }
|
|
225
|
+
: agentLabelOrOptions ?? {};
|
|
226
|
+
const { agentLabel, slim = false, timeoutMs = 30000 } = opts;
|
|
227
|
+
// Generate cache key
|
|
228
|
+
const cacheKey = `${agentLabel || 'all'}:${slim}`;
|
|
229
|
+
// Check cache first
|
|
230
|
+
const cached = myIssuesCache.get(cacheKey);
|
|
231
|
+
if (cached && isCacheValid(cached)) {
|
|
232
|
+
console.log(`[Linear] Using cached issues for ${cacheKey}`);
|
|
233
|
+
return cached.data;
|
|
234
|
+
}
|
|
235
|
+
console.log(`[Linear] Fetching issues for ${cacheKey}`);
|
|
236
|
+
const linear = getClient();
|
|
237
|
+
const baseFilter = {
|
|
238
|
+
team: teamFilter(),
|
|
239
|
+
};
|
|
240
|
+
// Add label filter if agentLabel is provided
|
|
241
|
+
if (agentLabel) {
|
|
242
|
+
baseFilter.labels = { name: { eq: agentLabel } };
|
|
243
|
+
}
|
|
244
|
+
// Wrap with timeout
|
|
245
|
+
const fetchIssues = async () => {
|
|
246
|
+
// Slim mode: query each state separately to avoid lazy resolver calls for issue.state
|
|
247
|
+
// Full mode: combined query then resolve per-issue
|
|
248
|
+
const result = [];
|
|
249
|
+
if (slim) {
|
|
250
|
+
// Separate queries per state → tag each issue without resolver calls
|
|
251
|
+
// Build a project ID→info cache to avoid per-issue project resolver calls
|
|
252
|
+
const todoFilter = { ...baseFilter, state: { name: { in: ['Todo'] } } };
|
|
253
|
+
const inProgressFilter = { ...baseFilter, state: { name: { in: ['In Progress', 'In Review'] } } };
|
|
254
|
+
const backlogFilter = { ...baseFilter, state: { name: { in: ['Backlog'] } } };
|
|
255
|
+
const [todoIssues, inProgressIssues, backlogIssues] = await Promise.all([
|
|
256
|
+
withRateLimit('linear', async () => linear.issues({ filter: todoFilter, first: 50 })),
|
|
257
|
+
withRateLimit('linear', async () => linear.issues({ filter: inProgressFilter, first: 50 })),
|
|
258
|
+
withRateLimit('linear', async () => linear.issues({ filter: backlogFilter, first: 50 })),
|
|
259
|
+
]);
|
|
260
|
+
const withState = [
|
|
261
|
+
...todoIssues.nodes.map(i => ({ issue: i, state: 'Todo' })),
|
|
262
|
+
...inProgressIssues.nodes.map(i => ({ issue: i, state: 'In Progress' })),
|
|
263
|
+
...backlogIssues.nodes.map(i => ({ issue: i, state: 'Backlog' })),
|
|
264
|
+
];
|
|
265
|
+
// Resolve all project info in parallel (one API call per issue, but batched)
|
|
266
|
+
// issue.project returns id+name together, cache by project id to avoid duplicates
|
|
267
|
+
const projectCache = new Map();
|
|
268
|
+
const projectResolutions = await Promise.all(withState.map(({ issue }) => withRateLimit('linear', () => Promise.resolve(issue.project)).catch(() => null)));
|
|
269
|
+
for (const p of projectResolutions) {
|
|
270
|
+
if (p && !projectCache.has(p.id)) {
|
|
271
|
+
projectCache.set(p.id, { id: p.id, name: p.name, icon: p.icon ?? undefined, color: p.color ?? undefined });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
for (let i = 0; i < withState.length; i++) {
|
|
275
|
+
const { issue, state } = withState[i];
|
|
276
|
+
const p = projectResolutions[i];
|
|
277
|
+
const project = p ? projectCache.get(p.id) : undefined;
|
|
278
|
+
result.push({
|
|
279
|
+
id: issue.id,
|
|
280
|
+
identifier: issue.identifier,
|
|
281
|
+
title: issue.title,
|
|
282
|
+
description: issue.description ?? undefined,
|
|
283
|
+
state,
|
|
284
|
+
priority: issue.priority,
|
|
285
|
+
labels: [],
|
|
286
|
+
comments: [],
|
|
287
|
+
project,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
// Full mode: fetch executable + backlog, then resolve per-issue
|
|
293
|
+
const executableFilter = { ...baseFilter, state: { name: { in: ['Todo', 'In Progress', 'In Review'] } } };
|
|
294
|
+
const backlogFilter = { ...baseFilter, state: { name: { in: ['Backlog'] } } };
|
|
295
|
+
const [executableIssues, backlogIssues] = await Promise.all([
|
|
296
|
+
withRateLimit('linear', async () => linear.issues({ filter: executableFilter, first: 50 })),
|
|
297
|
+
withRateLimit('linear', async () => linear.issues({ filter: backlogFilter, first: 50 })),
|
|
298
|
+
]);
|
|
299
|
+
{
|
|
300
|
+
// Full mode: load all details (comments, labels, project)
|
|
301
|
+
const allNodes = [...executableIssues.nodes, ...backlogIssues.nodes];
|
|
302
|
+
for (const issue of allNodes) {
|
|
303
|
+
const [comments, labels, project] = await Promise.all([
|
|
304
|
+
issue.comments(),
|
|
305
|
+
issue.labels(),
|
|
306
|
+
getProjectInfo(issue),
|
|
307
|
+
]);
|
|
308
|
+
result.push({
|
|
309
|
+
id: issue.id,
|
|
310
|
+
identifier: issue.identifier,
|
|
311
|
+
title: issue.title,
|
|
312
|
+
description: issue.description ?? undefined,
|
|
313
|
+
state: (await issue.state)?.name ?? 'Unknown',
|
|
314
|
+
priority: issue.priority,
|
|
315
|
+
labels: labels.nodes.map((l) => l.name),
|
|
316
|
+
comments: comments.nodes.map((c) => ({
|
|
317
|
+
id: c.id,
|
|
318
|
+
body: c.body,
|
|
319
|
+
createdAt: c.createdAt.toISOString(),
|
|
320
|
+
user: undefined,
|
|
321
|
+
})),
|
|
322
|
+
project,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// Sort by priority
|
|
327
|
+
return result.sort((a, b) => {
|
|
328
|
+
const pa = a.priority === 0 ? 999 : a.priority;
|
|
329
|
+
const pb = b.priority === 0 ? 999 : b.priority;
|
|
330
|
+
return pa - pb;
|
|
331
|
+
});
|
|
332
|
+
};
|
|
333
|
+
// Apply timeout
|
|
334
|
+
const result = await Promise.race([
|
|
335
|
+
fetchIssues(),
|
|
336
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`getMyIssues timed out after ${timeoutMs}ms`)), timeoutMs)),
|
|
337
|
+
]);
|
|
338
|
+
// Cache the result
|
|
339
|
+
myIssuesCache.set(cacheKey, {
|
|
340
|
+
data: result,
|
|
341
|
+
timestamp: Date.now(),
|
|
342
|
+
agentLabel: agentLabel || 'all',
|
|
343
|
+
});
|
|
344
|
+
return result;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Get a specific issue by ID or identifier
|
|
348
|
+
*/
|
|
349
|
+
export async function getIssue(issueIdOrIdentifier) {
|
|
350
|
+
const linear = getClient();
|
|
351
|
+
try {
|
|
352
|
+
// Check if it's an identifier format (e.g., LIN-123)
|
|
353
|
+
const isIdentifier = /^[A-Z]+-\d+$/.test(issueIdOrIdentifier);
|
|
354
|
+
let issue;
|
|
355
|
+
if (isIdentifier) {
|
|
356
|
+
// Search by identifier - use number field
|
|
357
|
+
const numPart = issueIdOrIdentifier.split('-')[1];
|
|
358
|
+
const issueNumber = parseInt(numPart, 10);
|
|
359
|
+
const issues = await linear.issues({
|
|
360
|
+
filter: {
|
|
361
|
+
team: teamFilter(),
|
|
362
|
+
number: { eq: issueNumber },
|
|
363
|
+
},
|
|
364
|
+
first: 1,
|
|
365
|
+
});
|
|
366
|
+
issue = issues.nodes[0];
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
// Look up directly by ID
|
|
370
|
+
issue = await linear.issue(issueIdOrIdentifier);
|
|
371
|
+
}
|
|
372
|
+
if (!issue)
|
|
373
|
+
return null;
|
|
374
|
+
const [comments, labels, project] = await Promise.all([
|
|
375
|
+
issue.comments(),
|
|
376
|
+
issue.labels(),
|
|
377
|
+
getProjectInfo(issue),
|
|
378
|
+
]);
|
|
379
|
+
return {
|
|
380
|
+
id: issue.id,
|
|
381
|
+
identifier: issue.identifier,
|
|
382
|
+
title: issue.title,
|
|
383
|
+
description: issue.description ?? undefined,
|
|
384
|
+
state: (await issue.state)?.name ?? 'Unknown',
|
|
385
|
+
priority: issue.priority,
|
|
386
|
+
labels: labels.nodes.map((l) => l.name),
|
|
387
|
+
comments: comments.nodes.map((c) => ({
|
|
388
|
+
id: c.id,
|
|
389
|
+
body: c.body,
|
|
390
|
+
createdAt: c.createdAt.toISOString(),
|
|
391
|
+
user: undefined,
|
|
392
|
+
})),
|
|
393
|
+
project,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
console.error(`[Linear] getIssue error for ${issueIdOrIdentifier}:`, error);
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Update issue state
|
|
403
|
+
*/
|
|
404
|
+
export async function updateIssueState(issueId, stateName, retries = 2) {
|
|
405
|
+
const linear = getClient();
|
|
406
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
407
|
+
try {
|
|
408
|
+
// Fetch the issue to get its actual team ID (avoids cross-team state mismatch)
|
|
409
|
+
const issue = await linear.issue(issueId);
|
|
410
|
+
const issueTeam = await issue.team;
|
|
411
|
+
const resolvedTeamId = issueTeam?.id ?? teamIds[0] ?? teamId;
|
|
412
|
+
// Get team workflow states
|
|
413
|
+
const team = await linear.team(resolvedTeamId);
|
|
414
|
+
const states = await team.states();
|
|
415
|
+
const targetState = states.nodes.find((s) => s.name.toLowerCase().includes(stateName.toLowerCase()));
|
|
416
|
+
if (!targetState) {
|
|
417
|
+
console.error(`[Linear] State "${stateName}" not found in team workflow`);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
await linear.updateIssue(issueId, {
|
|
421
|
+
stateId: targetState.id,
|
|
422
|
+
});
|
|
423
|
+
// Clear cache after mutation
|
|
424
|
+
clearLinearCache();
|
|
425
|
+
console.log(`[Linear] Issue ${issueId} state changed to ${stateName}`);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
console.error(`[Linear] Failed to update issue state (attempt ${attempt + 1}/${retries + 1}):`, error);
|
|
430
|
+
if (attempt < retries) {
|
|
431
|
+
await new Promise(r => setTimeout(r, 1000 * (attempt + 1)));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
console.error(`[Linear] All ${retries + 1} attempts to update issue ${issueId} to "${stateName}" failed`);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Add a comment to an issue
|
|
439
|
+
*/
|
|
440
|
+
export async function addComment(issueId, body) {
|
|
441
|
+
const linear = getClient();
|
|
442
|
+
await linear.createComment({
|
|
443
|
+
issueId,
|
|
444
|
+
body,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
/** Log a HALT event (low confidence) as a comment on a Linear issue */
|
|
448
|
+
export async function logHalt(issueId, sessionId, confidence, iteration, reason) {
|
|
449
|
+
await addComment(issueId, `⚠️ **[Automation] HALT - Low Confidence**\n\nSession: \`${sessionId}\` | Confidence: ${confidence}% | Attempt: #${iteration}\nReason: ${reason}\n\n**Action Required:** Review task requirements / provide context / break into sub-tasks\n\n---\n_Auto-generated_`);
|
|
450
|
+
}
|
|
451
|
+
/** Log work start comment for an agent */
|
|
452
|
+
export async function logWorkStart(issueId, sessionName) {
|
|
453
|
+
await addComment(issueId, `🤖 **[${sessionName}] Work Started**\n\nTime: ${new Date().toISOString()}\n\n---\n_Auto-generated_`);
|
|
454
|
+
await updateIssueState(issueId, 'In Progress');
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Log progress comment for an agent
|
|
458
|
+
*/
|
|
459
|
+
export async function logProgress(issueId, sessionName, progress) {
|
|
460
|
+
const timestamp = new Date().toISOString();
|
|
461
|
+
const body = `🤖 **[${sessionName}] Progress Update**
|
|
462
|
+
|
|
463
|
+
${progress}
|
|
464
|
+
|
|
465
|
+
Time: ${timestamp}`;
|
|
466
|
+
await addComment(issueId, body);
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Log work completion comment for an agent
|
|
470
|
+
*/
|
|
471
|
+
export async function logWorkComplete(issueId, sessionName, summary) {
|
|
472
|
+
const timestamp = new Date().toISOString();
|
|
473
|
+
const body = `🤖 **[${sessionName}] ✅ Work Complete**
|
|
474
|
+
|
|
475
|
+
${summary ?? ''}
|
|
476
|
+
|
|
477
|
+
Time: ${timestamp}`;
|
|
478
|
+
await addComment(issueId, body);
|
|
479
|
+
await updateIssueState(issueId, 'Done');
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Log blocked comment for an agent
|
|
483
|
+
*/
|
|
484
|
+
export async function logBlocked(issueId, sessionName, reason) {
|
|
485
|
+
const timestamp = new Date().toISOString();
|
|
486
|
+
const body = `🤖 **[${sessionName}] ⚠️ Blocked**
|
|
487
|
+
|
|
488
|
+
Reason: ${reason}
|
|
489
|
+
|
|
490
|
+
User intervention required
|
|
491
|
+
|
|
492
|
+
Time: ${timestamp}`;
|
|
493
|
+
await addComment(issueId, body);
|
|
494
|
+
// Use 'Todo' instead of 'Blocked' (Blocked state may not exist in team workflow)
|
|
495
|
+
await updateIssueState(issueId, 'Todo');
|
|
496
|
+
}
|
|
497
|
+
// Pair Mode Linear Integration
|
|
498
|
+
/**
|
|
499
|
+
* Log pair session start comment
|
|
500
|
+
*/
|
|
501
|
+
export async function logPairStart(issueId, sessionId, projectPath) {
|
|
502
|
+
const timestamp = new Date().toLocaleString(getDateLocale());
|
|
503
|
+
const body = `👥 **[Pair Session] Work Started**
|
|
504
|
+
|
|
505
|
+
🆔 Session: \`${sessionId}\`
|
|
506
|
+
📁 Project: \`${projectPath}\`
|
|
507
|
+
⏰ Time: ${timestamp}
|
|
508
|
+
|
|
509
|
+
Starting work in Worker/Reviewer pair mode.
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
_Auto-generated_`;
|
|
513
|
+
await addComment(issueId, body);
|
|
514
|
+
await updateIssueState(issueId, 'In Progress');
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Log pair session review start comment
|
|
518
|
+
*/
|
|
519
|
+
export async function logPairReview(issueId, sessionId, attempt) {
|
|
520
|
+
const timestamp = new Date().toLocaleString(getDateLocale());
|
|
521
|
+
const body = `🔍 **[Pair Session] Reviewing**
|
|
522
|
+
|
|
523
|
+
🆔 Session: \`${sessionId}\`
|
|
524
|
+
🔢 Attempt: #${attempt}
|
|
525
|
+
⏰ Time: ${timestamp}
|
|
526
|
+
|
|
527
|
+
Reviewer is reviewing Worker's output.`;
|
|
528
|
+
await addComment(issueId, body);
|
|
529
|
+
await updateIssueState(issueId, 'In Review');
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Log pair session revision request comment
|
|
533
|
+
*/
|
|
534
|
+
export async function logPairRevision(issueId, sessionId, feedback, issues) {
|
|
535
|
+
const timestamp = new Date().toLocaleString(getDateLocale());
|
|
536
|
+
const issueList = issues.length > 0
|
|
537
|
+
? issues.map((i, idx) => `${idx + 1}. ${i}`).join('\n')
|
|
538
|
+
: '(none)';
|
|
539
|
+
const body = `🔄 **[Pair Session] Revision Requested**
|
|
540
|
+
|
|
541
|
+
🆔 Session: \`${sessionId}\`
|
|
542
|
+
⏰ Time: ${timestamp}
|
|
543
|
+
|
|
544
|
+
**Feedback:** ${feedback}
|
|
545
|
+
|
|
546
|
+
**Issues:**
|
|
547
|
+
${issueList}
|
|
548
|
+
|
|
549
|
+
Worker will proceed with revisions.`;
|
|
550
|
+
await addComment(issueId, body);
|
|
551
|
+
await updateIssueState(issueId, 'In Progress');
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Log pair session completion comment
|
|
555
|
+
*/
|
|
556
|
+
export async function logPairComplete(issueId, sessionId, stats) {
|
|
557
|
+
const timestamp = new Date().toLocaleString(getDateLocale());
|
|
558
|
+
const durationStr = stats.duration < 60
|
|
559
|
+
? `${stats.duration}s`
|
|
560
|
+
: `${Math.floor(stats.duration / 60)}m ${stats.duration % 60}s`;
|
|
561
|
+
const filesStr = stats.filesChanged.length > 0
|
|
562
|
+
? stats.filesChanged.slice(0, 10).map(f => `- \`${f}\``).join('\n')
|
|
563
|
+
: '(none)';
|
|
564
|
+
// Build sections
|
|
565
|
+
const sections = [];
|
|
566
|
+
// Worker section
|
|
567
|
+
if (stats.workerSummary) {
|
|
568
|
+
sections.push(`## 🔨 Worker Report
|
|
569
|
+
|
|
570
|
+
**What was done:**
|
|
571
|
+
${stats.workerSummary}`);
|
|
572
|
+
if (stats.workerCommands && stats.workerCommands.length > 0) {
|
|
573
|
+
const cmdStr = stats.workerCommands.slice(0, 5).map(c => `- \`${c}\``).join('\n');
|
|
574
|
+
sections.push(`**Commands executed:**
|
|
575
|
+
${cmdStr}`);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
// Reviewer section
|
|
579
|
+
if (stats.reviewerFeedback) {
|
|
580
|
+
sections.push(`## ✅ Reviewer Report
|
|
581
|
+
|
|
582
|
+
**Decision:** ${stats.reviewerDecision || 'APPROVE'}
|
|
583
|
+
|
|
584
|
+
**Feedback:**
|
|
585
|
+
${stats.reviewerFeedback}`);
|
|
586
|
+
}
|
|
587
|
+
// Tester section
|
|
588
|
+
if (stats.testResults) {
|
|
589
|
+
const { passed, failed, coverage, failedTests } = stats.testResults;
|
|
590
|
+
const totalTests = passed + failed;
|
|
591
|
+
const passRate = totalTests > 0 ? ((passed / totalTests) * 100).toFixed(1) : '0';
|
|
592
|
+
let testSection = `## 🧪 Test Results
|
|
593
|
+
|
|
594
|
+
- ✅ Passed: ${passed}/${totalTests} (${passRate}%)`;
|
|
595
|
+
if (coverage !== undefined) {
|
|
596
|
+
testSection += `\n- 📊 Coverage: ${coverage.toFixed(1)}%`;
|
|
597
|
+
}
|
|
598
|
+
if (failed > 0 && failedTests && failedTests.length > 0) {
|
|
599
|
+
const failedStr = failedTests.slice(0, 3).map(t => `- ❌ ${t}`).join('\n');
|
|
600
|
+
testSection += `\n\n**Failed tests:**\n${failedStr}`;
|
|
601
|
+
if (failedTests.length > 3) {
|
|
602
|
+
testSection += `\n- ... and ${failedTests.length - 3} more`;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
sections.push(testSection);
|
|
606
|
+
}
|
|
607
|
+
// Remaining work section
|
|
608
|
+
if (stats.remainingWork) {
|
|
609
|
+
sections.push(`## 📋 Remaining Work
|
|
610
|
+
|
|
611
|
+
${stats.remainingWork}`);
|
|
612
|
+
}
|
|
613
|
+
const body = `✅ **[Automation] Task Complete**
|
|
614
|
+
|
|
615
|
+
🆔 Session: \`${sessionId}\`
|
|
616
|
+
⏰ Completed: ${timestamp}
|
|
617
|
+
|
|
618
|
+
**📊 Summary:**
|
|
619
|
+
- 🔄 Iterations: ${stats.attempts}
|
|
620
|
+
- ⏱️ Duration: ${durationStr}
|
|
621
|
+
- 📁 Files changed: ${stats.filesChanged.length}
|
|
622
|
+
|
|
623
|
+
**Changed files:**
|
|
624
|
+
${filesStr}
|
|
625
|
+
|
|
626
|
+
---
|
|
627
|
+
|
|
628
|
+
${sections.join('\n\n---\n\n')}
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
_Automated by Worker/Reviewer/Tester pipeline_`;
|
|
632
|
+
await addComment(issueId, body);
|
|
633
|
+
await updateIssueState(issueId, 'Done');
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Log pair session failure/rejection comment
|
|
637
|
+
*/
|
|
638
|
+
export async function logPairFailed(issueId, sessionId, reason, details) {
|
|
639
|
+
const timestamp = new Date().toLocaleString(getDateLocale());
|
|
640
|
+
const reasonText = {
|
|
641
|
+
rejected: '❌ Reviewer rejected the work',
|
|
642
|
+
max_attempts: '⚠️ Maximum retry attempts exceeded',
|
|
643
|
+
error: '💥 An error occurred',
|
|
644
|
+
}[reason];
|
|
645
|
+
const body = `❌ **[Pair Session] Work Failed**
|
|
646
|
+
|
|
647
|
+
🆔 Session: \`${sessionId}\`
|
|
648
|
+
⏰ Time: ${timestamp}
|
|
649
|
+
|
|
650
|
+
**Reason:** ${reasonText}
|
|
651
|
+
|
|
652
|
+
**Details:**
|
|
653
|
+
${details}
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
_Manual intervention required_`;
|
|
657
|
+
await addComment(issueId, body);
|
|
658
|
+
// Don't change state on failure; let the user decide
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Create a new issue (with daily limit enforcement)
|
|
662
|
+
*/
|
|
663
|
+
export async function createIssue(title, description, labels = [], options) {
|
|
664
|
+
resetDailyCounterIfNeeded();
|
|
665
|
+
// Check daily limit (unless bypassLimit is set)
|
|
666
|
+
if (!options?.bypassLimit && dailyIssueCount >= DAILY_ISSUE_LIMIT) {
|
|
667
|
+
return {
|
|
668
|
+
error: `Daily issue creation limit (${DAILY_ISSUE_LIMIT}) reached. Please try again tomorrow.`,
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
const linear = getClient();
|
|
672
|
+
// Look up label IDs
|
|
673
|
+
const team = await linear.team(teamIds[0] ?? teamId);
|
|
674
|
+
const teamLabels = await team.labels();
|
|
675
|
+
const labelIds = labels
|
|
676
|
+
.map((name) => teamLabels.nodes.find((l) => l.name === name)?.id)
|
|
677
|
+
.filter((id) => !!id);
|
|
678
|
+
const issuePayload = await linear.createIssue({
|
|
679
|
+
teamId,
|
|
680
|
+
title,
|
|
681
|
+
description,
|
|
682
|
+
labelIds,
|
|
683
|
+
});
|
|
684
|
+
const issue = await issuePayload.issue;
|
|
685
|
+
if (!issue) {
|
|
686
|
+
throw new Error('Failed to create issue');
|
|
687
|
+
}
|
|
688
|
+
// Increment counter
|
|
689
|
+
dailyIssueCount++;
|
|
690
|
+
// Clear cache after mutation
|
|
691
|
+
clearLinearCache();
|
|
692
|
+
return {
|
|
693
|
+
id: issue.id,
|
|
694
|
+
identifier: issue.identifier,
|
|
695
|
+
title: issue.title,
|
|
696
|
+
description: issue.description ?? undefined,
|
|
697
|
+
state: 'Backlog',
|
|
698
|
+
priority: issue.priority,
|
|
699
|
+
labels,
|
|
700
|
+
comments: [],
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Create a sub-issue (for Planner decomposition)
|
|
705
|
+
* - Creates as a child of the parent issue via parentId
|
|
706
|
+
* - Exempt from daily limit (auto-decomposition is required work)
|
|
707
|
+
*/
|
|
708
|
+
export async function createSubIssue(parentId, title, description, options) {
|
|
709
|
+
const linear = getClient();
|
|
710
|
+
try {
|
|
711
|
+
// Get parent issue info
|
|
712
|
+
const parentIssue = await linear.issue(parentId);
|
|
713
|
+
if (!parentIssue) {
|
|
714
|
+
return { error: `Parent issue not found: ${parentId}` };
|
|
715
|
+
}
|
|
716
|
+
// Look up label IDs
|
|
717
|
+
const team = await linear.team(teamIds[0] ?? teamId);
|
|
718
|
+
const teamLabels = await team.labels();
|
|
719
|
+
const labelIds = (options?.labels || [])
|
|
720
|
+
.map((name) => teamLabels.nodes.find((l) => l.name === name)?.id)
|
|
721
|
+
.filter((id) => !!id);
|
|
722
|
+
// Add auto-decomposed label
|
|
723
|
+
const autoLabel = teamLabels.nodes.find((l) => l.name === 'auto-decomposed');
|
|
724
|
+
if (autoLabel) {
|
|
725
|
+
labelIds.push(autoLabel.id);
|
|
726
|
+
}
|
|
727
|
+
// Create the sub-issue (use parent issue's team ID, not global)
|
|
728
|
+
const parentTeam = await parentIssue.team;
|
|
729
|
+
const subIssueTeamId = parentTeam?.id ?? (teamIds[0] ?? teamId);
|
|
730
|
+
const issuePayload = await linear.createIssue({
|
|
731
|
+
teamId: subIssueTeamId,
|
|
732
|
+
parentId, // Link to parent issue
|
|
733
|
+
title,
|
|
734
|
+
description,
|
|
735
|
+
labelIds,
|
|
736
|
+
priority: options?.priority ?? 3,
|
|
737
|
+
projectId: options?.projectId,
|
|
738
|
+
});
|
|
739
|
+
const issue = await issuePayload.issue;
|
|
740
|
+
if (!issue) {
|
|
741
|
+
throw new Error('Failed to create sub-issue');
|
|
742
|
+
}
|
|
743
|
+
console.log(`[Linear] Created sub-issue: ${issue.identifier} under ${parentIssue.identifier}`);
|
|
744
|
+
return {
|
|
745
|
+
id: issue.id,
|
|
746
|
+
identifier: issue.identifier,
|
|
747
|
+
title: issue.title,
|
|
748
|
+
description: issue.description ?? undefined,
|
|
749
|
+
state: 'Backlog',
|
|
750
|
+
priority: issue.priority,
|
|
751
|
+
labels: options?.labels || [],
|
|
752
|
+
comments: [],
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
catch (error) {
|
|
756
|
+
console.error('[Linear] createSubIssue error:', error);
|
|
757
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Mark a parent issue as 'decomposed'
|
|
762
|
+
*/
|
|
763
|
+
export async function markAsDecomposed(issueId, subIssueCount, totalMinutes) {
|
|
764
|
+
const timestamp = new Date().toLocaleString(getDateLocale());
|
|
765
|
+
const body = `📋 **[Planner] Task Decomposition Complete**
|
|
766
|
+
|
|
767
|
+
⏰ Time: ${timestamp}
|
|
768
|
+
|
|
769
|
+
**Decomposition result:**
|
|
770
|
+
- Sub-issues created: ${subIssueCount}
|
|
771
|
+
- Total estimated time: ${totalMinutes}min
|
|
772
|
+
|
|
773
|
+
This issue has been decomposed into sub-issues.
|
|
774
|
+
The parent issue remains active while child issues execute.
|
|
775
|
+
Once all sub-issues are completed, OpenSwarm will close this issue automatically.
|
|
776
|
+
|
|
777
|
+
---
|
|
778
|
+
_Auto-decomposed by Planner agent_`;
|
|
779
|
+
await addComment(issueId, body);
|
|
780
|
+
// Keep parent issue active until all child issues complete.
|
|
781
|
+
try {
|
|
782
|
+
await updateIssueState(issueId, 'In Progress');
|
|
783
|
+
}
|
|
784
|
+
catch (err) {
|
|
785
|
+
console.warn('[Linear] Failed to mark decomposed parent as In Progress:', err);
|
|
786
|
+
}
|
|
787
|
+
// Add label (if decomposed label exists)
|
|
788
|
+
try {
|
|
789
|
+
const linear = getClient();
|
|
790
|
+
const team = await linear.team(teamIds[0] ?? teamId);
|
|
791
|
+
const teamLabels = await team.labels();
|
|
792
|
+
const decomposedLabel = teamLabels.nodes.find((l) => l.name === 'decomposed');
|
|
793
|
+
if (decomposedLabel) {
|
|
794
|
+
const issue = await linear.issue(issueId);
|
|
795
|
+
const currentLabels = await issue.labels();
|
|
796
|
+
const currentLabelIds = currentLabels.nodes.map(l => l.id);
|
|
797
|
+
await linear.updateIssue(issueId, {
|
|
798
|
+
labelIds: [...currentLabelIds, decomposedLabel.id],
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
catch (err) {
|
|
803
|
+
console.warn('[Linear] Failed to add decomposed label:', err);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Agent proposes work by creating a backlog issue
|
|
808
|
+
* - Enforces daily limit of 10
|
|
809
|
+
* - Automatically adds 'agent-proposal' label
|
|
810
|
+
* - Created with low priority (4)
|
|
811
|
+
*/
|
|
812
|
+
export async function proposeWork(sessionName, title, rationale, suggestedApproach) {
|
|
813
|
+
resetDailyCounterIfNeeded();
|
|
814
|
+
// Check daily limit
|
|
815
|
+
if (dailyIssueCount >= DAILY_ISSUE_LIMIT) {
|
|
816
|
+
console.log(`[${sessionName}] Daily issue creation limit reached (${dailyIssueCount}/${DAILY_ISSUE_LIMIT})`);
|
|
817
|
+
return {
|
|
818
|
+
error: `Daily issue creation limit (${DAILY_ISSUE_LIMIT}) reached. Please defer the proposal to tomorrow.`,
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
const linear = getClient();
|
|
822
|
+
// Look up Backlog state ID
|
|
823
|
+
const team = await linear.team(teamIds[0] ?? teamId);
|
|
824
|
+
const states = await team.states();
|
|
825
|
+
const backlogState = states.nodes.find((s) => s.name.toLowerCase() === 'backlog');
|
|
826
|
+
// Look up label IDs (agent-proposal + sessionName)
|
|
827
|
+
const teamLabels = await team.labels();
|
|
828
|
+
const proposalLabel = teamLabels.nodes.find((l) => l.name === 'agent-proposal');
|
|
829
|
+
const sessionLabel = teamLabels.nodes.find((l) => l.name === sessionName);
|
|
830
|
+
const labelIds = [];
|
|
831
|
+
if (proposalLabel)
|
|
832
|
+
labelIds.push(proposalLabel.id);
|
|
833
|
+
if (sessionLabel)
|
|
834
|
+
labelIds.push(sessionLabel.id);
|
|
835
|
+
// Compose description
|
|
836
|
+
const description = `## 🤖 Agent Proposal
|
|
837
|
+
|
|
838
|
+
**Proposed by:** ${sessionName}
|
|
839
|
+
**Created at:** ${new Date().toISOString()}
|
|
840
|
+
|
|
841
|
+
---
|
|
842
|
+
|
|
843
|
+
### Rationale
|
|
844
|
+
${rationale}
|
|
845
|
+
|
|
846
|
+
${suggestedApproach ? `### Suggested Approach\n${suggestedApproach}` : ''}
|
|
847
|
+
|
|
848
|
+
---
|
|
849
|
+
_This issue was auto-created by an agent. Please review and adjust priority or delete as needed._`;
|
|
850
|
+
const issuePayload = await linear.createIssue({
|
|
851
|
+
teamId,
|
|
852
|
+
title: `[Proposal] ${title}`,
|
|
853
|
+
description,
|
|
854
|
+
labelIds,
|
|
855
|
+
stateId: backlogState?.id,
|
|
856
|
+
priority: 4, // Low priority
|
|
857
|
+
});
|
|
858
|
+
const issue = await issuePayload.issue;
|
|
859
|
+
if (!issue) {
|
|
860
|
+
throw new Error('Failed to create proposal issue');
|
|
861
|
+
}
|
|
862
|
+
// Increment counter
|
|
863
|
+
dailyIssueCount++;
|
|
864
|
+
console.log(`[${sessionName}] Proposal created: ${issue.identifier} (today ${dailyIssueCount}/${DAILY_ISSUE_LIMIT})`);
|
|
865
|
+
return {
|
|
866
|
+
id: issue.id,
|
|
867
|
+
identifier: issue.identifier,
|
|
868
|
+
title: issue.title,
|
|
869
|
+
description: issue.description ?? undefined,
|
|
870
|
+
state: 'Backlog',
|
|
871
|
+
priority: 4,
|
|
872
|
+
labels: ['agent-proposal', sessionName].filter(Boolean),
|
|
873
|
+
comments: [],
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Get stuck/failed issues and PRs (issues stuck in In Progress for >7 days, or with retry/failed labels)
|
|
878
|
+
*/
|
|
879
|
+
export async function getStuckIssues() {
|
|
880
|
+
const linear = getClient();
|
|
881
|
+
const now = Date.now();
|
|
882
|
+
const STUCK_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
883
|
+
// Fetch In Progress issues
|
|
884
|
+
const inProgressIssues = await withRateLimit('linear', async () => linear.issues({
|
|
885
|
+
filter: {
|
|
886
|
+
team: teamFilter(),
|
|
887
|
+
state: { name: { eq: 'In Progress' } },
|
|
888
|
+
},
|
|
889
|
+
first: 100,
|
|
890
|
+
}));
|
|
891
|
+
// Fetch issues with retry/failed/blocked labels
|
|
892
|
+
const problematicIssues = await withRateLimit('linear', async () => linear.issues({
|
|
893
|
+
filter: {
|
|
894
|
+
team: teamFilter(),
|
|
895
|
+
state: { name: { nin: ['Done', 'Canceled'] } },
|
|
896
|
+
labels: { name: { in: ['retry', 'failed', 'blocked', 'needs-help'] } },
|
|
897
|
+
},
|
|
898
|
+
first: 100,
|
|
899
|
+
}));
|
|
900
|
+
const stuckIssues = [];
|
|
901
|
+
const failedIssues = [];
|
|
902
|
+
// Process In Progress issues (check if stuck)
|
|
903
|
+
for (const issue of inProgressIssues.nodes) {
|
|
904
|
+
const updatedAt = new Date(issue.updatedAt).getTime();
|
|
905
|
+
const stuckMs = now - updatedAt;
|
|
906
|
+
if (stuckMs > STUCK_THRESHOLD_MS) {
|
|
907
|
+
const [state, labels, comments, project] = await Promise.all([
|
|
908
|
+
issue.state,
|
|
909
|
+
issue.labels(),
|
|
910
|
+
issue.comments(),
|
|
911
|
+
getProjectInfo(issue),
|
|
912
|
+
]);
|
|
913
|
+
const stuckDays = Math.floor(stuckMs / (24 * 60 * 60 * 1000));
|
|
914
|
+
stuckIssues.push({
|
|
915
|
+
id: issue.id,
|
|
916
|
+
identifier: issue.identifier,
|
|
917
|
+
title: issue.title,
|
|
918
|
+
description: issue.description ?? undefined,
|
|
919
|
+
state: state?.name ?? 'Unknown',
|
|
920
|
+
priority: issue.priority,
|
|
921
|
+
labels: labels.nodes.map((l) => l.name),
|
|
922
|
+
comments: comments.nodes.map((c) => ({
|
|
923
|
+
id: c.id,
|
|
924
|
+
body: c.body,
|
|
925
|
+
createdAt: c.createdAt.toISOString(),
|
|
926
|
+
user: undefined,
|
|
927
|
+
})),
|
|
928
|
+
project,
|
|
929
|
+
stuckDays,
|
|
930
|
+
reason: `No updates for ${stuckDays} days`,
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
// Process problematic issues (retry, failed, blocked)
|
|
935
|
+
for (const issue of problematicIssues.nodes) {
|
|
936
|
+
const [state, labels, comments, project] = await Promise.all([
|
|
937
|
+
issue.state,
|
|
938
|
+
issue.labels(),
|
|
939
|
+
issue.comments(),
|
|
940
|
+
getProjectInfo(issue),
|
|
941
|
+
]);
|
|
942
|
+
const labelNames = labels.nodes.map((l) => l.name);
|
|
943
|
+
let reason = 'Unknown issue';
|
|
944
|
+
if (labelNames.includes('failed')) {
|
|
945
|
+
reason = 'Marked as failed';
|
|
946
|
+
}
|
|
947
|
+
else if (labelNames.includes('retry')) {
|
|
948
|
+
reason = 'Requires retry';
|
|
949
|
+
}
|
|
950
|
+
else if (labelNames.includes('blocked')) {
|
|
951
|
+
reason = 'Blocked by dependencies';
|
|
952
|
+
}
|
|
953
|
+
else if (labelNames.includes('needs-help')) {
|
|
954
|
+
reason = 'Needs manual intervention';
|
|
955
|
+
}
|
|
956
|
+
failedIssues.push({
|
|
957
|
+
id: issue.id,
|
|
958
|
+
identifier: issue.identifier,
|
|
959
|
+
title: issue.title,
|
|
960
|
+
description: issue.description ?? undefined,
|
|
961
|
+
state: state?.name ?? 'Unknown',
|
|
962
|
+
priority: issue.priority,
|
|
963
|
+
labels: labelNames,
|
|
964
|
+
comments: comments.nodes.map((c) => ({
|
|
965
|
+
id: c.id,
|
|
966
|
+
body: c.body,
|
|
967
|
+
createdAt: c.createdAt.toISOString(),
|
|
968
|
+
user: undefined,
|
|
969
|
+
})),
|
|
970
|
+
project,
|
|
971
|
+
reason,
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
return {
|
|
975
|
+
stuckIssues: stuckIssues.sort((a, b) => b.stuckDays - a.stuckDays),
|
|
976
|
+
failedIssues: failedIssues.sort((a, b) => {
|
|
977
|
+
const pa = a.priority === 0 ? 999 : a.priority;
|
|
978
|
+
const pb = b.priority === 0 ? 999 : b.priority;
|
|
979
|
+
return pa - pb;
|
|
980
|
+
}),
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
//# sourceMappingURL=linear.js.map
|