@urateam/core 0.1.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/dist/__tests__/assembler.test.d.ts +2 -0
- package/dist/__tests__/assembler.test.d.ts.map +1 -0
- package/dist/__tests__/assembler.test.js +63 -0
- package/dist/__tests__/assembler.test.js.map +1 -0
- package/dist/__tests__/auth-check.test.d.ts +2 -0
- package/dist/__tests__/auth-check.test.d.ts.map +1 -0
- package/dist/__tests__/auth-check.test.js +88 -0
- package/dist/__tests__/auth-check.test.js.map +1 -0
- package/dist/__tests__/auto-merge.test.d.ts +15 -0
- package/dist/__tests__/auto-merge.test.d.ts.map +1 -0
- package/dist/__tests__/auto-merge.test.js +428 -0
- package/dist/__tests__/auto-merge.test.js.map +1 -0
- package/dist/__tests__/bec89-unified-schema.test.d.ts +2 -0
- package/dist/__tests__/bec89-unified-schema.test.d.ts.map +1 -0
- package/dist/__tests__/bec89-unified-schema.test.js +235 -0
- package/dist/__tests__/bec89-unified-schema.test.js.map +1 -0
- package/dist/__tests__/conflict-detector.test.d.ts +2 -0
- package/dist/__tests__/conflict-detector.test.d.ts.map +1 -0
- package/dist/__tests__/conflict-detector.test.js +206 -0
- package/dist/__tests__/conflict-detector.test.js.map +1 -0
- package/dist/__tests__/coordination.test.d.ts +2 -0
- package/dist/__tests__/coordination.test.d.ts.map +1 -0
- package/dist/__tests__/coordination.test.js +257 -0
- package/dist/__tests__/coordination.test.js.map +1 -0
- package/dist/__tests__/db-postgres.test.d.ts +14 -0
- package/dist/__tests__/db-postgres.test.d.ts.map +1 -0
- package/dist/__tests__/db-postgres.test.js +289 -0
- package/dist/__tests__/db-postgres.test.js.map +1 -0
- package/dist/__tests__/db.test.d.ts +2 -0
- package/dist/__tests__/db.test.d.ts.map +1 -0
- package/dist/__tests__/db.test.js +182 -0
- package/dist/__tests__/db.test.js.map +1 -0
- package/dist/__tests__/deep-review.test.d.ts +2 -0
- package/dist/__tests__/deep-review.test.d.ts.map +1 -0
- package/dist/__tests__/deep-review.test.js +322 -0
- package/dist/__tests__/deep-review.test.js.map +1 -0
- package/dist/__tests__/devcontainer.test.d.ts +2 -0
- package/dist/__tests__/devcontainer.test.d.ts.map +1 -0
- package/dist/__tests__/devcontainer.test.js +89 -0
- package/dist/__tests__/devcontainer.test.js.map +1 -0
- package/dist/__tests__/distributed-lock.test.d.ts +18 -0
- package/dist/__tests__/distributed-lock.test.d.ts.map +1 -0
- package/dist/__tests__/distributed-lock.test.js +237 -0
- package/dist/__tests__/distributed-lock.test.js.map +1 -0
- package/dist/__tests__/e2e-pipeline.test.d.ts +25 -0
- package/dist/__tests__/e2e-pipeline.test.d.ts.map +1 -0
- package/dist/__tests__/e2e-pipeline.test.js +517 -0
- package/dist/__tests__/e2e-pipeline.test.js.map +1 -0
- package/dist/__tests__/error-classifier.test.d.ts +2 -0
- package/dist/__tests__/error-classifier.test.d.ts.map +1 -0
- package/dist/__tests__/error-classifier.test.js +33 -0
- package/dist/__tests__/error-classifier.test.js.map +1 -0
- package/dist/__tests__/executor-integration.test.d.ts +11 -0
- package/dist/__tests__/executor-integration.test.d.ts.map +1 -0
- package/dist/__tests__/executor-integration.test.js +246 -0
- package/dist/__tests__/executor-integration.test.js.map +1 -0
- package/dist/__tests__/executor-issue-id.test.d.ts +13 -0
- package/dist/__tests__/executor-issue-id.test.d.ts.map +1 -0
- package/dist/__tests__/executor-issue-id.test.js +211 -0
- package/dist/__tests__/executor-issue-id.test.js.map +1 -0
- package/dist/__tests__/executor.test.d.ts +2 -0
- package/dist/__tests__/executor.test.d.ts.map +1 -0
- package/dist/__tests__/executor.test.js +164 -0
- package/dist/__tests__/executor.test.js.map +1 -0
- package/dist/__tests__/extract-handoff.test.d.ts +2 -0
- package/dist/__tests__/extract-handoff.test.d.ts.map +1 -0
- package/dist/__tests__/extract-handoff.test.js +131 -0
- package/dist/__tests__/extract-handoff.test.js.map +1 -0
- package/dist/__tests__/fail-on-auto-commit.test.d.ts +2 -0
- package/dist/__tests__/fail-on-auto-commit.test.d.ts.map +1 -0
- package/dist/__tests__/fail-on-auto-commit.test.js +156 -0
- package/dist/__tests__/fail-on-auto-commit.test.js.map +1 -0
- package/dist/__tests__/fixtures/webhook-comment.json +5 -0
- package/dist/__tests__/fixtures/webhook-state-change.json +15 -0
- package/dist/__tests__/force-push-agent-branches.test.d.ts +12 -0
- package/dist/__tests__/force-push-agent-branches.test.d.ts.map +1 -0
- package/dist/__tests__/force-push-agent-branches.test.js +348 -0
- package/dist/__tests__/force-push-agent-branches.test.js.map +1 -0
- package/dist/__tests__/github-webhook.test.d.ts +2 -0
- package/dist/__tests__/github-webhook.test.d.ts.map +1 -0
- package/dist/__tests__/github-webhook.test.js +370 -0
- package/dist/__tests__/github-webhook.test.js.map +1 -0
- package/dist/__tests__/gitlab.test.d.ts +28 -0
- package/dist/__tests__/gitlab.test.d.ts.map +1 -0
- package/dist/__tests__/gitlab.test.js +241 -0
- package/dist/__tests__/gitlab.test.js.map +1 -0
- package/dist/__tests__/integration/auto-commit.test.d.ts +2 -0
- package/dist/__tests__/integration/auto-commit.test.d.ts.map +1 -0
- package/dist/__tests__/integration/auto-commit.test.js +207 -0
- package/dist/__tests__/integration/auto-commit.test.js.map +1 -0
- package/dist/__tests__/integration/bec99-cross-worktree-guard.test.d.ts +10 -0
- package/dist/__tests__/integration/bec99-cross-worktree-guard.test.d.ts.map +1 -0
- package/dist/__tests__/integration/bec99-cross-worktree-guard.test.js +183 -0
- package/dist/__tests__/integration/bec99-cross-worktree-guard.test.js.map +1 -0
- package/dist/__tests__/integration/reproduce-bec99.test.d.ts +32 -0
- package/dist/__tests__/integration/reproduce-bec99.test.d.ts.map +1 -0
- package/dist/__tests__/integration/reproduce-bec99.test.js +243 -0
- package/dist/__tests__/integration/reproduce-bec99.test.js.map +1 -0
- package/dist/__tests__/integration/vitest-changed.test.d.ts +10 -0
- package/dist/__tests__/integration/vitest-changed.test.d.ts.map +1 -0
- package/dist/__tests__/integration/vitest-changed.test.js +128 -0
- package/dist/__tests__/integration/vitest-changed.test.js.map +1 -0
- package/dist/__tests__/license.test.d.ts +2 -0
- package/dist/__tests__/license.test.d.ts.map +1 -0
- package/dist/__tests__/license.test.js +53 -0
- package/dist/__tests__/license.test.js.map +1 -0
- package/dist/__tests__/mcp-resolver.test.d.ts +2 -0
- package/dist/__tests__/mcp-resolver.test.d.ts.map +1 -0
- package/dist/__tests__/mcp-resolver.test.js +65 -0
- package/dist/__tests__/mcp-resolver.test.js.map +1 -0
- package/dist/__tests__/migrator.test.d.ts +2 -0
- package/dist/__tests__/migrator.test.d.ts.map +1 -0
- package/dist/__tests__/migrator.test.js +300 -0
- package/dist/__tests__/migrator.test.js.map +1 -0
- package/dist/__tests__/notifier-discord.test.d.ts +2 -0
- package/dist/__tests__/notifier-discord.test.d.ts.map +1 -0
- package/dist/__tests__/notifier-discord.test.js +166 -0
- package/dist/__tests__/notifier-discord.test.js.map +1 -0
- package/dist/__tests__/notifier-slack.test.d.ts +2 -0
- package/dist/__tests__/notifier-slack.test.d.ts.map +1 -0
- package/dist/__tests__/notifier-slack.test.js +157 -0
- package/dist/__tests__/notifier-slack.test.js.map +1 -0
- package/dist/__tests__/notifier.test.d.ts +2 -0
- package/dist/__tests__/notifier.test.d.ts.map +1 -0
- package/dist/__tests__/notifier.test.js +207 -0
- package/dist/__tests__/notifier.test.js.map +1 -0
- package/dist/__tests__/pipeline-config.test.d.ts +2 -0
- package/dist/__tests__/pipeline-config.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline-config.test.js +143 -0
- package/dist/__tests__/pipeline-config.test.js.map +1 -0
- package/dist/__tests__/pipeline-runner.test.d.ts +2 -0
- package/dist/__tests__/pipeline-runner.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline-runner.test.js +359 -0
- package/dist/__tests__/pipeline-runner.test.js.map +1 -0
- package/dist/__tests__/pm-approvals-n1.repro.test.d.ts +9 -0
- package/dist/__tests__/pm-approvals-n1.repro.test.d.ts.map +1 -0
- package/dist/__tests__/pm-approvals-n1.repro.test.js +175 -0
- package/dist/__tests__/pm-approvals-n1.repro.test.js.map +1 -0
- package/dist/__tests__/pm-approvals.test.d.ts +2 -0
- package/dist/__tests__/pm-approvals.test.d.ts.map +1 -0
- package/dist/__tests__/pm-approvals.test.js +162 -0
- package/dist/__tests__/pm-approvals.test.js.map +1 -0
- package/dist/__tests__/pm-budget.test.d.ts +2 -0
- package/dist/__tests__/pm-budget.test.d.ts.map +1 -0
- package/dist/__tests__/pm-budget.test.js +65 -0
- package/dist/__tests__/pm-budget.test.js.map +1 -0
- package/dist/__tests__/pm-conflict.test.d.ts +2 -0
- package/dist/__tests__/pm-conflict.test.d.ts.map +1 -0
- package/dist/__tests__/pm-conflict.test.js +87 -0
- package/dist/__tests__/pm-conflict.test.js.map +1 -0
- package/dist/__tests__/pm-promote.test.d.ts +2 -0
- package/dist/__tests__/pm-promote.test.d.ts.map +1 -0
- package/dist/__tests__/pm-promote.test.js +82 -0
- package/dist/__tests__/pm-promote.test.js.map +1 -0
- package/dist/__tests__/pm-recover.test.d.ts +2 -0
- package/dist/__tests__/pm-recover.test.d.ts.map +1 -0
- package/dist/__tests__/pm-recover.test.js +100 -0
- package/dist/__tests__/pm-recover.test.js.map +1 -0
- package/dist/__tests__/pm-scheduler.test.d.ts +2 -0
- package/dist/__tests__/pm-scheduler.test.d.ts.map +1 -0
- package/dist/__tests__/pm-scheduler.test.js +112 -0
- package/dist/__tests__/pm-scheduler.test.js.map +1 -0
- package/dist/__tests__/pm-slack-interface.test.d.ts +2 -0
- package/dist/__tests__/pm-slack-interface.test.d.ts.map +1 -0
- package/dist/__tests__/pm-slack-interface.test.js +372 -0
- package/dist/__tests__/pm-slack-interface.test.js.map +1 -0
- package/dist/__tests__/pm-slack.test.d.ts +2 -0
- package/dist/__tests__/pm-slack.test.d.ts.map +1 -0
- package/dist/__tests__/pm-slack.test.js +83 -0
- package/dist/__tests__/pm-slack.test.js.map +1 -0
- package/dist/__tests__/pm-triage.test.d.ts +2 -0
- package/dist/__tests__/pm-triage.test.d.ts.map +1 -0
- package/dist/__tests__/pm-triage.test.js +198 -0
- package/dist/__tests__/pm-triage.test.js.map +1 -0
- package/dist/__tests__/pm-types.test.d.ts +2 -0
- package/dist/__tests__/pm-types.test.d.ts.map +1 -0
- package/dist/__tests__/pm-types.test.js +76 -0
- package/dist/__tests__/pm-types.test.js.map +1 -0
- package/dist/__tests__/pr-automerge.test.d.ts +18 -0
- package/dist/__tests__/pr-automerge.test.d.ts.map +1 -0
- package/dist/__tests__/pr-automerge.test.js +645 -0
- package/dist/__tests__/pr-automerge.test.js.map +1 -0
- package/dist/__tests__/pr-description.test.d.ts +2 -0
- package/dist/__tests__/pr-description.test.d.ts.map +1 -0
- package/dist/__tests__/pr-description.test.js +728 -0
- package/dist/__tests__/pr-description.test.js.map +1 -0
- package/dist/__tests__/prompt-injection.test.d.ts +2 -0
- package/dist/__tests__/prompt-injection.test.d.ts.map +1 -0
- package/dist/__tests__/prompt-injection.test.js +446 -0
- package/dist/__tests__/prompt-injection.test.js.map +1 -0
- package/dist/__tests__/ralph-gate.test.d.ts +19 -0
- package/dist/__tests__/ralph-gate.test.d.ts.map +1 -0
- package/dist/__tests__/ralph-gate.test.js +593 -0
- package/dist/__tests__/ralph-gate.test.js.map +1 -0
- package/dist/__tests__/ralph-review-fix-regression.test.d.ts +18 -0
- package/dist/__tests__/ralph-review-fix-regression.test.d.ts.map +1 -0
- package/dist/__tests__/ralph-review-fix-regression.test.js +306 -0
- package/dist/__tests__/ralph-review-fix-regression.test.js.map +1 -0
- package/dist/__tests__/ralph.test.d.ts +2 -0
- package/dist/__tests__/ralph.test.d.ts.map +1 -0
- package/dist/__tests__/ralph.test.js +96 -0
- package/dist/__tests__/ralph.test.js.map +1 -0
- package/dist/__tests__/recover-stuck.test.d.ts +8 -0
- package/dist/__tests__/recover-stuck.test.d.ts.map +1 -0
- package/dist/__tests__/recover-stuck.test.js +399 -0
- package/dist/__tests__/recover-stuck.test.js.map +1 -0
- package/dist/__tests__/repo.test.d.ts +2 -0
- package/dist/__tests__/repo.test.d.ts.map +1 -0
- package/dist/__tests__/repo.test.js +295 -0
- package/dist/__tests__/repo.test.js.map +1 -0
- package/dist/__tests__/repro-bec58-n-plus-one.test.d.ts +2 -0
- package/dist/__tests__/repro-bec58-n-plus-one.test.d.ts.map +1 -0
- package/dist/__tests__/repro-bec58-n-plus-one.test.js +187 -0
- package/dist/__tests__/repro-bec58-n-plus-one.test.js.map +1 -0
- package/dist/__tests__/reproduce-bec113-pagination-warning.test.d.ts +16 -0
- package/dist/__tests__/reproduce-bec113-pagination-warning.test.d.ts.map +1 -0
- package/dist/__tests__/reproduce-bec113-pagination-warning.test.js +226 -0
- package/dist/__tests__/reproduce-bec113-pagination-warning.test.js.map +1 -0
- package/dist/__tests__/reproduce-bec43-updatedat.test.d.ts +2 -0
- package/dist/__tests__/reproduce-bec43-updatedat.test.d.ts.map +1 -0
- package/dist/__tests__/reproduce-bec43-updatedat.test.js +76 -0
- package/dist/__tests__/reproduce-bec43-updatedat.test.js.map +1 -0
- package/dist/__tests__/reproduce-bec48-distributed-race.test.d.ts +18 -0
- package/dist/__tests__/reproduce-bec48-distributed-race.test.d.ts.map +1 -0
- package/dist/__tests__/reproduce-bec48-distributed-race.test.js +178 -0
- package/dist/__tests__/reproduce-bec48-distributed-race.test.js.map +1 -0
- package/dist/__tests__/reproduce-bec62.test.d.ts +2 -0
- package/dist/__tests__/reproduce-bec62.test.d.ts.map +1 -0
- package/dist/__tests__/reproduce-bec62.test.js +86 -0
- package/dist/__tests__/reproduce-bec62.test.js.map +1 -0
- package/dist/__tests__/reproduce-bec91-stuck-in-progress.test.d.ts +13 -0
- package/dist/__tests__/reproduce-bec91-stuck-in-progress.test.d.ts.map +1 -0
- package/dist/__tests__/reproduce-bec91-stuck-in-progress.test.js +220 -0
- package/dist/__tests__/reproduce-bec91-stuck-in-progress.test.js.map +1 -0
- package/dist/__tests__/review-feedback.test.d.ts +2 -0
- package/dist/__tests__/review-feedback.test.d.ts.map +1 -0
- package/dist/__tests__/review-feedback.test.js +383 -0
- package/dist/__tests__/review-feedback.test.js.map +1 -0
- package/dist/__tests__/sanitizer.test.d.ts +2 -0
- package/dist/__tests__/sanitizer.test.d.ts.map +1 -0
- package/dist/__tests__/sanitizer.test.js +162 -0
- package/dist/__tests__/sanitizer.test.js.map +1 -0
- package/dist/__tests__/security.test.d.ts +2 -0
- package/dist/__tests__/security.test.d.ts.map +1 -0
- package/dist/__tests__/security.test.js +52 -0
- package/dist/__tests__/security.test.js.map +1 -0
- package/dist/__tests__/server.test.d.ts +2 -0
- package/dist/__tests__/server.test.d.ts.map +1 -0
- package/dist/__tests__/server.test.js +61 -0
- package/dist/__tests__/server.test.js.map +1 -0
- package/dist/__tests__/slack-alerts.test.d.ts +2 -0
- package/dist/__tests__/slack-alerts.test.d.ts.map +1 -0
- package/dist/__tests__/slack-alerts.test.js +214 -0
- package/dist/__tests__/slack-alerts.test.js.map +1 -0
- package/dist/__tests__/stage-models.test.d.ts +14 -0
- package/dist/__tests__/stage-models.test.d.ts.map +1 -0
- package/dist/__tests__/stage-models.test.js +244 -0
- package/dist/__tests__/stage-models.test.js.map +1 -0
- package/dist/__tests__/start-todo.test.d.ts +2 -0
- package/dist/__tests__/start-todo.test.d.ts.map +1 -0
- package/dist/__tests__/start-todo.test.js +175 -0
- package/dist/__tests__/start-todo.test.js.map +1 -0
- package/dist/__tests__/tech-stack.test.d.ts +2 -0
- package/dist/__tests__/tech-stack.test.d.ts.map +1 -0
- package/dist/__tests__/tech-stack.test.js +75 -0
- package/dist/__tests__/tech-stack.test.js.map +1 -0
- package/dist/__tests__/templates.test.d.ts +2 -0
- package/dist/__tests__/templates.test.d.ts.map +1 -0
- package/dist/__tests__/templates.test.js +161 -0
- package/dist/__tests__/templates.test.js.map +1 -0
- package/dist/__tests__/test-quality.test.d.ts +2 -0
- package/dist/__tests__/test-quality.test.d.ts.map +1 -0
- package/dist/__tests__/test-quality.test.js +329 -0
- package/dist/__tests__/test-quality.test.js.map +1 -0
- package/dist/__tests__/token-budget.test.d.ts +2 -0
- package/dist/__tests__/token-budget.test.d.ts.map +1 -0
- package/dist/__tests__/token-budget.test.js +198 -0
- package/dist/__tests__/token-budget.test.js.map +1 -0
- package/dist/__tests__/types.test.d.ts +2 -0
- package/dist/__tests__/types.test.d.ts.map +1 -0
- package/dist/__tests__/types.test.js +156 -0
- package/dist/__tests__/types.test.js.map +1 -0
- package/dist/__tests__/validate.test.d.ts +2 -0
- package/dist/__tests__/validate.test.d.ts.map +1 -0
- package/dist/__tests__/validate.test.js +128 -0
- package/dist/__tests__/validate.test.js.map +1 -0
- package/dist/__tests__/webhook-handler.test.d.ts +2 -0
- package/dist/__tests__/webhook-handler.test.d.ts.map +1 -0
- package/dist/__tests__/webhook-handler.test.js +286 -0
- package/dist/__tests__/webhook-handler.test.js.map +1 -0
- package/dist/__tests__/webhook.test.d.ts +2 -0
- package/dist/__tests__/webhook.test.d.ts.map +1 -0
- package/dist/__tests__/webhook.test.js +58 -0
- package/dist/__tests__/webhook.test.js.map +1 -0
- package/dist/db/client.d.ts +56 -0
- package/dist/db/client.d.ts.map +1 -0
- package/dist/db/client.js +201 -0
- package/dist/db/client.js.map +1 -0
- package/dist/db/index.d.ts +4 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +4 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrations/postgres/001_initial_schema.sql +78 -0
- package/dist/db/migrations/postgres/002_pg_timestamps.sql +78 -0
- package/dist/db/migrations/postgres/003_retry_count.sql +10 -0
- package/dist/db/migrations/postgres/004_review_feedback.sql +20 -0
- package/dist/db/migrations/postgres/005_auto_merge.sql +15 -0
- package/dist/db/migrations/sqlite/001_initial_schema.sql +78 -0
- package/dist/db/migrations/sqlite/002_retry_count.sql +5 -0
- package/dist/db/migrations/sqlite/003_review_feedback.sql +7 -0
- package/dist/db/migrations/sqlite/004_auto_merge.sql +6 -0
- package/dist/db/migrator.d.ts +51 -0
- package/dist/db/migrator.d.ts.map +1 -0
- package/dist/db/migrator.js +188 -0
- package/dist/db/migrator.js.map +1 -0
- package/dist/db/schema.d.ts +1114 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +129 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/entrypoint.d.ts +2 -0
- package/dist/entrypoint.d.ts.map +1 -0
- package/dist/entrypoint.js +113 -0
- package/dist/entrypoint.js.map +1 -0
- package/dist/executor/agent-config.d.ts +10 -0
- package/dist/executor/agent-config.d.ts.map +1 -0
- package/dist/executor/agent-config.js +81 -0
- package/dist/executor/agent-config.js.map +1 -0
- package/dist/executor/agent-stream.d.ts +65 -0
- package/dist/executor/agent-stream.d.ts.map +1 -0
- package/dist/executor/agent-stream.js +101 -0
- package/dist/executor/agent-stream.js.map +1 -0
- package/dist/executor/auth-check.d.ts +10 -0
- package/dist/executor/auth-check.d.ts.map +1 -0
- package/dist/executor/auth-check.js +52 -0
- package/dist/executor/auth-check.js.map +1 -0
- package/dist/executor/deep-review.d.ts +61 -0
- package/dist/executor/deep-review.d.ts.map +1 -0
- package/dist/executor/deep-review.js +308 -0
- package/dist/executor/deep-review.js.map +1 -0
- package/dist/executor/executor.d.ts +27 -0
- package/dist/executor/executor.d.ts.map +1 -0
- package/dist/executor/executor.js +168 -0
- package/dist/executor/executor.js.map +1 -0
- package/dist/executor/extract-handoff.d.ts +14 -0
- package/dist/executor/extract-handoff.d.ts.map +1 -0
- package/dist/executor/extract-handoff.js +80 -0
- package/dist/executor/extract-handoff.js.map +1 -0
- package/dist/executor/handoff.d.ts +24 -0
- package/dist/executor/handoff.d.ts.map +1 -0
- package/dist/executor/handoff.js +63 -0
- package/dist/executor/handoff.js.map +1 -0
- package/dist/executor/index.d.ts +8 -0
- package/dist/executor/index.d.ts.map +1 -0
- package/dist/executor/index.js +8 -0
- package/dist/executor/index.js.map +1 -0
- package/dist/executor/mcp-resolver.d.ts +29 -0
- package/dist/executor/mcp-resolver.d.ts.map +1 -0
- package/dist/executor/mcp-resolver.js +80 -0
- package/dist/executor/mcp-resolver.js.map +1 -0
- package/dist/executor/permissions.d.ts +11 -0
- package/dist/executor/permissions.d.ts.map +1 -0
- package/dist/executor/permissions.js +32 -0
- package/dist/executor/permissions.js.map +1 -0
- package/dist/executor/profiles.d.ts +5 -0
- package/dist/executor/profiles.d.ts.map +1 -0
- package/dist/executor/profiles.js +35 -0
- package/dist/executor/profiles.js.map +1 -0
- package/dist/executor/prompt/assembler.d.ts +10 -0
- package/dist/executor/prompt/assembler.d.ts.map +1 -0
- package/dist/executor/prompt/assembler.js +28 -0
- package/dist/executor/prompt/assembler.js.map +1 -0
- package/dist/executor/prompt/index.d.ts +5 -0
- package/dist/executor/prompt/index.d.ts.map +1 -0
- package/dist/executor/prompt/index.js +5 -0
- package/dist/executor/prompt/index.js.map +1 -0
- package/dist/executor/prompt/sanitizer.d.ts +25 -0
- package/dist/executor/prompt/sanitizer.d.ts.map +1 -0
- package/dist/executor/prompt/sanitizer.js +81 -0
- package/dist/executor/prompt/sanitizer.js.map +1 -0
- package/dist/executor/prompt/schema-mapper.d.ts +7 -0
- package/dist/executor/prompt/schema-mapper.d.ts.map +1 -0
- package/dist/executor/prompt/schema-mapper.js +59 -0
- package/dist/executor/prompt/schema-mapper.js.map +1 -0
- package/dist/executor/prompt/templates.d.ts +31 -0
- package/dist/executor/prompt/templates.d.ts.map +1 -0
- package/dist/executor/prompt/templates.js +283 -0
- package/dist/executor/prompt/templates.js.map +1 -0
- package/dist/executor/ralph.d.ts +19 -0
- package/dist/executor/ralph.d.ts.map +1 -0
- package/dist/executor/ralph.js +112 -0
- package/dist/executor/ralph.js.map +1 -0
- package/dist/executor/test-quality.d.ts +117 -0
- package/dist/executor/test-quality.d.ts.map +1 -0
- package/dist/executor/test-quality.js +261 -0
- package/dist/executor/test-quality.js.map +1 -0
- package/dist/executor/validate.d.ts +15 -0
- package/dist/executor/validate.d.ts.map +1 -0
- package/dist/executor/validate.js +124 -0
- package/dist/executor/validate.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/license.d.ts +18 -0
- package/dist/license.d.ts.map +1 -0
- package/dist/license.js +44 -0
- package/dist/license.js.map +1 -0
- package/dist/logger.d.ts +43 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +91 -0
- package/dist/logger.js.map +1 -0
- package/dist/notifier/composite.d.ts +13 -0
- package/dist/notifier/composite.d.ts.map +1 -0
- package/dist/notifier/composite.js +28 -0
- package/dist/notifier/composite.js.map +1 -0
- package/dist/notifier/discord.d.ts +14 -0
- package/dist/notifier/discord.d.ts.map +1 -0
- package/dist/notifier/discord.js +105 -0
- package/dist/notifier/discord.js.map +1 -0
- package/dist/notifier/index.d.ts +6 -0
- package/dist/notifier/index.d.ts.map +1 -0
- package/dist/notifier/index.js +6 -0
- package/dist/notifier/index.js.map +1 -0
- package/dist/notifier/linear.d.ts +28 -0
- package/dist/notifier/linear.d.ts.map +1 -0
- package/dist/notifier/linear.js +138 -0
- package/dist/notifier/linear.js.map +1 -0
- package/dist/notifier/slack-alerts.d.ts +62 -0
- package/dist/notifier/slack-alerts.d.ts.map +1 -0
- package/dist/notifier/slack-alerts.js +184 -0
- package/dist/notifier/slack-alerts.js.map +1 -0
- package/dist/notifier/slack.d.ts +14 -0
- package/dist/notifier/slack.d.ts.map +1 -0
- package/dist/notifier/slack.js +146 -0
- package/dist/notifier/slack.js.map +1 -0
- package/dist/pipeline/automerge.d.ts +44 -0
- package/dist/pipeline/automerge.d.ts.map +1 -0
- package/dist/pipeline/automerge.js +135 -0
- package/dist/pipeline/automerge.js.map +1 -0
- package/dist/pipeline/config.d.ts +5 -0
- package/dist/pipeline/config.d.ts.map +1 -0
- package/dist/pipeline/config.js +68 -0
- package/dist/pipeline/config.js.map +1 -0
- package/dist/pipeline/distributed-lock.d.ts +50 -0
- package/dist/pipeline/distributed-lock.d.ts.map +1 -0
- package/dist/pipeline/distributed-lock.js +114 -0
- package/dist/pipeline/distributed-lock.js.map +1 -0
- package/dist/pipeline/error-classifier.d.ts +9 -0
- package/dist/pipeline/error-classifier.d.ts.map +1 -0
- package/dist/pipeline/error-classifier.js +25 -0
- package/dist/pipeline/error-classifier.js.map +1 -0
- package/dist/pipeline/index.d.ts +9 -0
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/pipeline/index.js +9 -0
- package/dist/pipeline/index.js.map +1 -0
- package/dist/pipeline/pr-description.d.ts +35 -0
- package/dist/pipeline/pr-description.d.ts.map +1 -0
- package/dist/pipeline/pr-description.js +52 -0
- package/dist/pipeline/pr-description.js.map +1 -0
- package/dist/pipeline/queue.d.ts +7 -0
- package/dist/pipeline/queue.d.ts.map +1 -0
- package/dist/pipeline/queue.js +39 -0
- package/dist/pipeline/queue.js.map +1 -0
- package/dist/pipeline/router.d.ts +6 -0
- package/dist/pipeline/router.d.ts.map +1 -0
- package/dist/pipeline/router.js +19 -0
- package/dist/pipeline/router.js.map +1 -0
- package/dist/pipeline/runner.d.ts +142 -0
- package/dist/pipeline/runner.d.ts.map +1 -0
- package/dist/pipeline/runner.js +1848 -0
- package/dist/pipeline/runner.js.map +1 -0
- package/dist/pm/actions/approval-helpers.d.ts +11 -0
- package/dist/pm/actions/approval-helpers.d.ts.map +1 -0
- package/dist/pm/actions/approval-helpers.js +34 -0
- package/dist/pm/actions/approval-helpers.js.map +1 -0
- package/dist/pm/actions/cancel.d.ts +11 -0
- package/dist/pm/actions/cancel.d.ts.map +1 -0
- package/dist/pm/actions/cancel.js +68 -0
- package/dist/pm/actions/cancel.js.map +1 -0
- package/dist/pm/actions/deprioritize.d.ts +12 -0
- package/dist/pm/actions/deprioritize.d.ts.map +1 -0
- package/dist/pm/actions/deprioritize.js +55 -0
- package/dist/pm/actions/deprioritize.js.map +1 -0
- package/dist/pm/actions/promote.d.ts +11 -0
- package/dist/pm/actions/promote.d.ts.map +1 -0
- package/dist/pm/actions/promote.js +78 -0
- package/dist/pm/actions/promote.js.map +1 -0
- package/dist/pm/actions/recover-stuck.d.ts +42 -0
- package/dist/pm/actions/recover-stuck.d.ts.map +1 -0
- package/dist/pm/actions/recover-stuck.js +143 -0
- package/dist/pm/actions/recover-stuck.js.map +1 -0
- package/dist/pm/actions/recover.d.ts +18 -0
- package/dist/pm/actions/recover.d.ts.map +1 -0
- package/dist/pm/actions/recover.js +56 -0
- package/dist/pm/actions/recover.js.map +1 -0
- package/dist/pm/actions/resolve-approvals.d.ts +17 -0
- package/dist/pm/actions/resolve-approvals.d.ts.map +1 -0
- package/dist/pm/actions/resolve-approvals.js +92 -0
- package/dist/pm/actions/resolve-approvals.js.map +1 -0
- package/dist/pm/actions/start-todo.d.ts +28 -0
- package/dist/pm/actions/start-todo.d.ts.map +1 -0
- package/dist/pm/actions/start-todo.js +117 -0
- package/dist/pm/actions/start-todo.js.map +1 -0
- package/dist/pm/actions/triage.d.ts +13 -0
- package/dist/pm/actions/triage.d.ts.map +1 -0
- package/dist/pm/actions/triage.js +109 -0
- package/dist/pm/actions/triage.js.map +1 -0
- package/dist/pm/budget.d.ts +9 -0
- package/dist/pm/budget.d.ts.map +1 -0
- package/dist/pm/budget.js +62 -0
- package/dist/pm/budget.js.map +1 -0
- package/dist/pm/call-claude.d.ts +3 -0
- package/dist/pm/call-claude.d.ts.map +1 -0
- package/dist/pm/call-claude.js +37 -0
- package/dist/pm/call-claude.js.map +1 -0
- package/dist/pm/conflict-detector.d.ts +42 -0
- package/dist/pm/conflict-detector.d.ts.map +1 -0
- package/dist/pm/conflict-detector.js +116 -0
- package/dist/pm/conflict-detector.js.map +1 -0
- package/dist/pm/conflict.d.ts +20 -0
- package/dist/pm/conflict.d.ts.map +1 -0
- package/dist/pm/conflict.js +63 -0
- package/dist/pm/conflict.js.map +1 -0
- package/dist/pm/coordination.d.ts +50 -0
- package/dist/pm/coordination.d.ts.map +1 -0
- package/dist/pm/coordination.js +163 -0
- package/dist/pm/coordination.js.map +1 -0
- package/dist/pm/linear-helpers.d.ts +2 -0
- package/dist/pm/linear-helpers.d.ts.map +1 -0
- package/dist/pm/linear-helpers.js +16 -0
- package/dist/pm/linear-helpers.js.map +1 -0
- package/dist/pm/scheduler.d.ts +47 -0
- package/dist/pm/scheduler.d.ts.map +1 -0
- package/dist/pm/scheduler.js +346 -0
- package/dist/pm/scheduler.js.map +1 -0
- package/dist/pm/slack-helpers.d.ts +2 -0
- package/dist/pm/slack-helpers.d.ts.map +1 -0
- package/dist/pm/slack-helpers.js +24 -0
- package/dist/pm/slack-helpers.js.map +1 -0
- package/dist/pm/slack-interface.d.ts +133 -0
- package/dist/pm/slack-interface.d.ts.map +1 -0
- package/dist/pm/slack-interface.js +641 -0
- package/dist/pm/slack-interface.js.map +1 -0
- package/dist/pm/slack.d.ts +18 -0
- package/dist/pm/slack.d.ts.map +1 -0
- package/dist/pm/slack.js +144 -0
- package/dist/pm/slack.js.map +1 -0
- package/dist/pm/types.d.ts +99 -0
- package/dist/pm/types.d.ts.map +1 -0
- package/dist/pm/types.js +17 -0
- package/dist/pm/types.js.map +1 -0
- package/dist/repo/config.d.ts +35 -0
- package/dist/repo/config.d.ts.map +1 -0
- package/dist/repo/config.js +72 -0
- package/dist/repo/config.js.map +1 -0
- package/dist/repo/devcontainer.d.ts +33 -0
- package/dist/repo/devcontainer.d.ts.map +1 -0
- package/dist/repo/devcontainer.js +90 -0
- package/dist/repo/devcontainer.js.map +1 -0
- package/dist/repo/git.d.ts +185 -0
- package/dist/repo/git.d.ts.map +1 -0
- package/dist/repo/git.js +586 -0
- package/dist/repo/git.js.map +1 -0
- package/dist/repo/github.d.ts +56 -0
- package/dist/repo/github.d.ts.map +1 -0
- package/dist/repo/github.js +164 -0
- package/dist/repo/github.js.map +1 -0
- package/dist/repo/gitlab.d.ts +47 -0
- package/dist/repo/gitlab.d.ts.map +1 -0
- package/dist/repo/gitlab.js +91 -0
- package/dist/repo/gitlab.js.map +1 -0
- package/dist/repo/index.d.ts +7 -0
- package/dist/repo/index.d.ts.map +1 -0
- package/dist/repo/index.js +5 -0
- package/dist/repo/index.js.map +1 -0
- package/dist/repo/tech-stack.d.ts +13 -0
- package/dist/repo/tech-stack.d.ts.map +1 -0
- package/dist/repo/tech-stack.js +112 -0
- package/dist/repo/tech-stack.js.map +1 -0
- package/dist/security/index.d.ts +3 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +3 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/review-checklist.d.ts +9 -0
- package/dist/security/review-checklist.d.ts.map +1 -0
- package/dist/security/review-checklist.js +46 -0
- package/dist/security/review-checklist.js.map +1 -0
- package/dist/security/sandbox.d.ts +7 -0
- package/dist/security/sandbox.d.ts.map +1 -0
- package/dist/security/sandbox.js +31 -0
- package/dist/security/sandbox.js.map +1 -0
- package/dist/server.d.ts +48 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +90 -0
- package/dist/server.js.map +1 -0
- package/dist/types.d.ts +1230 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +225 -0
- package/dist/types.js.map +1 -0
- package/dist/webhook/github-handler.d.ts +39 -0
- package/dist/webhook/github-handler.d.ts.map +1 -0
- package/dist/webhook/github-handler.js +439 -0
- package/dist/webhook/github-handler.js.map +1 -0
- package/dist/webhook/handler.d.ts +16 -0
- package/dist/webhook/handler.d.ts.map +1 -0
- package/dist/webhook/handler.js +171 -0
- package/dist/webhook/handler.js.map +1 -0
- package/dist/webhook/index.d.ts +5 -0
- package/dist/webhook/index.d.ts.map +1 -0
- package/dist/webhook/index.js +5 -0
- package/dist/webhook/index.js.map +1 -0
- package/dist/webhook/parser.d.ts +18 -0
- package/dist/webhook/parser.d.ts.map +1 -0
- package/dist/webhook/parser.js +30 -0
- package/dist/webhook/parser.js.map +1 -0
- package/dist/webhook/signature.d.ts +2 -0
- package/dist/webhook/signature.d.ts.map +1 -0
- package/dist/webhook/signature.js +14 -0
- package/dist/webhook/signature.js.map +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PM Agent Slack Interface — bidirectional human-agent communication.
|
|
3
|
+
*
|
|
4
|
+
* Inbound:
|
|
5
|
+
* POST /slack/commands — slash commands (/pm prioritize, /pm create, …)
|
|
6
|
+
* POST /slack/events — Events API (natural language messages + URL verification)
|
|
7
|
+
*
|
|
8
|
+
* Outbound helpers (call from PM scheduler):
|
|
9
|
+
* notifyAssigned, notifySkipped, askForClarification, postDailySummary
|
|
10
|
+
*/
|
|
11
|
+
import { Hono } from "hono";
|
|
12
|
+
import { createLogger } from "../logger.js";
|
|
13
|
+
import { sanitize } from "../executor/prompt/sanitizer.js";
|
|
14
|
+
import { parseJsonObject } from "../executor/agent-stream.js";
|
|
15
|
+
import { makeCallClaude, makeCallClaudeSonnet } from "./call-claude.js";
|
|
16
|
+
import { postSlackMessage } from "./slack-helpers.js";
|
|
17
|
+
const log = createLogger({ component: "PmAgent:slack-interface" });
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Module-level constants
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
/** Valid PM command type names — kept in sync with the PmCommand union type. */
|
|
22
|
+
const VALID_PM_COMMAND_TYPES = [
|
|
23
|
+
"prioritize",
|
|
24
|
+
"create",
|
|
25
|
+
"bulk_create",
|
|
26
|
+
"status",
|
|
27
|
+
"pause",
|
|
28
|
+
"resume",
|
|
29
|
+
"assign",
|
|
30
|
+
"unknown",
|
|
31
|
+
];
|
|
32
|
+
/** Linear workflow state name for the Triage column. */
|
|
33
|
+
const LINEAR_STATE_TRIAGE = "Triage";
|
|
34
|
+
/** Linear workflow state name for the Todo column. */
|
|
35
|
+
const LINEAR_STATE_TODO = "Todo";
|
|
36
|
+
/** Linear label name that triggers the auto-implement pipeline. */
|
|
37
|
+
const LINEAR_LABEL_AUTO_IMPLEMENT = "auto-implement";
|
|
38
|
+
/** Valid numeric priority values accepted by Linear (1=Urgent … 4=Low). */
|
|
39
|
+
const VALID_PRIORITIES = [1, 2, 3, 4];
|
|
40
|
+
/** Default priority used when a generated issue omits or has an invalid value. */
|
|
41
|
+
const DEFAULT_PRIORITY = 3;
|
|
42
|
+
/** Lazy singleton for the `crypto` built-in — avoids repeated dynamic import on every Slack request. */
|
|
43
|
+
let _cryptoModule = null;
|
|
44
|
+
async function getCrypto() {
|
|
45
|
+
if (!_cryptoModule) {
|
|
46
|
+
try {
|
|
47
|
+
_cryptoModule = await import("crypto");
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
log.error("crypto module unavailable — cannot verify Slack signatures");
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return _cryptoModule;
|
|
55
|
+
}
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Shared pause state — single-process only; use Redis for multi-process
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
let paused = false;
|
|
60
|
+
export function isPmPaused() {
|
|
61
|
+
return paused;
|
|
62
|
+
}
|
|
63
|
+
export function setPmPaused(value) {
|
|
64
|
+
paused = value;
|
|
65
|
+
}
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Slack request verification
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
/**
|
|
70
|
+
* Verifies the Slack signing secret against the X-Slack-Signature header.
|
|
71
|
+
* Returns `true` if valid, `false` otherwise.
|
|
72
|
+
*
|
|
73
|
+
* See https://api.slack.com/authentication/verifying-requests-from-slack
|
|
74
|
+
*/
|
|
75
|
+
export async function verifySlackSignature(rawBody, timestamp, signature, signingSecret) {
|
|
76
|
+
// Reject requests older than 5 minutes
|
|
77
|
+
const requestAge = Math.abs(Date.now() / 1000 - Number(timestamp));
|
|
78
|
+
if (requestAge > 300)
|
|
79
|
+
return false;
|
|
80
|
+
const baseString = `v0:${timestamp}:${rawBody}`;
|
|
81
|
+
const crypto = await getCrypto();
|
|
82
|
+
if (!crypto)
|
|
83
|
+
return false;
|
|
84
|
+
const hmac = crypto
|
|
85
|
+
.createHmac("sha256", signingSecret)
|
|
86
|
+
.update(baseString, "utf8")
|
|
87
|
+
.digest("hex");
|
|
88
|
+
const expected = `v0=${hmac}`;
|
|
89
|
+
// Constant-time comparison
|
|
90
|
+
if (expected.length !== signature.length)
|
|
91
|
+
return false;
|
|
92
|
+
let result = 0;
|
|
93
|
+
for (let i = 0; i < expected.length; i++) {
|
|
94
|
+
result |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
|
|
95
|
+
}
|
|
96
|
+
return result === 0;
|
|
97
|
+
}
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Command parser
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
/**
|
|
102
|
+
* Parses the text following "/pm" into a structured `PmCommand`.
|
|
103
|
+
*
|
|
104
|
+
* Examples:
|
|
105
|
+
* "prioritize BEC-25" → { type: "prioritize", issueId: "BEC-25" }
|
|
106
|
+
* 'create "title" "desc"' → { type: "create", title: "title", description: "desc" }
|
|
107
|
+
* "status" → { type: "status" }
|
|
108
|
+
* "pause" → { type: "pause" }
|
|
109
|
+
* "resume" → { type: "resume" }
|
|
110
|
+
* "assign BEC-13" → { type: "assign", issueId: "BEC-13" }
|
|
111
|
+
*/
|
|
112
|
+
const ISSUE_ID_RE = /^[A-Z]+-\d+$/;
|
|
113
|
+
/**
|
|
114
|
+
* Extracts and validates an issue ID from a command like "prioritize BEC-25".
|
|
115
|
+
* Returns the uppercase issue ID string, or `null` if the format is invalid.
|
|
116
|
+
*/
|
|
117
|
+
function parseIssueIdCommand(trimmed, keyword) {
|
|
118
|
+
const re = new RegExp(`^${keyword}\\s+`, "i");
|
|
119
|
+
const raw = trimmed.replace(re, "").trim().toUpperCase();
|
|
120
|
+
return ISSUE_ID_RE.test(raw) ? raw : null;
|
|
121
|
+
}
|
|
122
|
+
export function parsePmCommand(text) {
|
|
123
|
+
const trimmed = text.trim();
|
|
124
|
+
if (/^prioritize\s+/i.test(trimmed)) {
|
|
125
|
+
const issueId = parseIssueIdCommand(trimmed, "prioritize");
|
|
126
|
+
if (!issueId)
|
|
127
|
+
return { type: "unknown", original: text };
|
|
128
|
+
return { type: "prioritize", issueId };
|
|
129
|
+
}
|
|
130
|
+
if (/^assign\s+/i.test(trimmed)) {
|
|
131
|
+
const issueId = parseIssueIdCommand(trimmed, "assign");
|
|
132
|
+
if (!issueId)
|
|
133
|
+
return { type: "unknown", original: text };
|
|
134
|
+
return { type: "assign", issueId };
|
|
135
|
+
}
|
|
136
|
+
if (/^create\s+/i.test(trimmed)) {
|
|
137
|
+
const rest = trimmed.replace(/^create\s+/i, "").trim();
|
|
138
|
+
const matches = rest.match(/^"([^"]+)"\s+"([^"]+)"$/);
|
|
139
|
+
if (matches) {
|
|
140
|
+
return { type: "create", title: matches[1], description: matches[2] };
|
|
141
|
+
}
|
|
142
|
+
// Single-quoted titles, no description
|
|
143
|
+
const singleMatch = rest.match(/^"([^"]+)"$/);
|
|
144
|
+
if (singleMatch) {
|
|
145
|
+
return { type: "create", title: singleMatch[1], description: "" };
|
|
146
|
+
}
|
|
147
|
+
return { type: "unknown", original: text };
|
|
148
|
+
}
|
|
149
|
+
if (/^status$/i.test(trimmed))
|
|
150
|
+
return { type: "status" };
|
|
151
|
+
if (/^pause$/i.test(trimmed))
|
|
152
|
+
return { type: "pause" };
|
|
153
|
+
if (/^resume$/i.test(trimmed))
|
|
154
|
+
return { type: "resume" };
|
|
155
|
+
return { type: "unknown", original: text };
|
|
156
|
+
}
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// Natural language intent parsing via Claude
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
/**
|
|
161
|
+
* Interprets a free-text Slack message as a `PmCommand` using Claude.
|
|
162
|
+
* Returns `{ type: "unknown", original }` if no actionable intent is found.
|
|
163
|
+
*/
|
|
164
|
+
export async function interpretNaturalLanguage(message, callClaude) {
|
|
165
|
+
const safe = sanitize(message);
|
|
166
|
+
const prompt = `You are a PM Agent assistant. The following Slack message was sent to you:\n\n"${safe}"\n\nClassify it as exactly ONE of these JSON responses (no other text):\n{"type":"prioritize","issueId":"<id>"}\n{"type":"create","title":"<t>","description":"<d>"}\n{"type":"bulk_create","request":"<original request>"}\n{"type":"status"}\n{"type":"pause"}\n{"type":"resume"}\n{"type":"assign","issueId":"<id>"}\n{"type":"unknown","original":"${safe}"}\n\nRules:\n- Issue IDs look like BEC-25, ENG-42, etc.\n- "more urgent" / "higher priority" → prioritize\n- "add", "open a ticket", "create" → create (single issue with clear title)\n- "create issues for", "find gaps and create", "generate issues", "analyze and create multiple", "create tickets for all" → bulk_create (multiple issues from analysis)\n- "what's running", "show queue" → status\n- "stop", "pause" → pause\n- "start again", "unpause", "resume" → resume\n- "move to todo", "assign" → assign\n- Use bulk_create when the request implies analysis or generating multiple issues, not a single specific issue\n\nRespond ONLY with the JSON object.`;
|
|
167
|
+
try {
|
|
168
|
+
const raw = await callClaude(prompt);
|
|
169
|
+
const parsed = parseJsonObject(raw);
|
|
170
|
+
if (!parsed)
|
|
171
|
+
return { type: "unknown", original: message };
|
|
172
|
+
// Validate type field against the canonical list of command types
|
|
173
|
+
if (!VALID_PM_COMMAND_TYPES.includes(parsed.type)) {
|
|
174
|
+
return { type: "unknown", original: message };
|
|
175
|
+
}
|
|
176
|
+
return parsed;
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
log.warn({ err, message }, "failed to parse NL intent");
|
|
180
|
+
return { type: "unknown", original: message };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Executes a parsed `PmCommand` against Linear and returns a human-readable
|
|
185
|
+
* response string suitable for posting back to Slack.
|
|
186
|
+
*/
|
|
187
|
+
export async function executePmCommand(cmd, deps) {
|
|
188
|
+
let _linear = null;
|
|
189
|
+
async function getLinear() {
|
|
190
|
+
if (!_linear && deps.linearApiKey) {
|
|
191
|
+
const { LinearClient } = await import("@linear/sdk");
|
|
192
|
+
_linear = new LinearClient({ apiKey: deps.linearApiKey });
|
|
193
|
+
}
|
|
194
|
+
return _linear;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Searches Linear for an issue by its identifier (e.g. "BEC-25") and returns
|
|
198
|
+
* the first match, or `null` when not found. Shared by prioritize + assign.
|
|
199
|
+
*/
|
|
200
|
+
async function findIssueByIdentifier(linear, issueId) {
|
|
201
|
+
const results = await linear.searchIssues(issueId);
|
|
202
|
+
return results.nodes?.[0] ?? null;
|
|
203
|
+
}
|
|
204
|
+
switch (cmd.type) {
|
|
205
|
+
case "status": {
|
|
206
|
+
const state = paused ? "⏸ *Paused*" : "▶️ *Running*";
|
|
207
|
+
return `PM Agent is ${state}.\nUse \`/pm pause\` or \`/pm resume\` to control autonomous assignment.`;
|
|
208
|
+
}
|
|
209
|
+
case "pause": {
|
|
210
|
+
setPmPaused(true);
|
|
211
|
+
log.info("PM Agent paused via Slack");
|
|
212
|
+
return "⏸ PM Agent autonomous assignment has been *paused*. Use `/pm resume` to restart.";
|
|
213
|
+
}
|
|
214
|
+
case "resume": {
|
|
215
|
+
setPmPaused(false);
|
|
216
|
+
log.info("PM Agent resumed via Slack");
|
|
217
|
+
return "▶️ PM Agent autonomous assignment has been *resumed*.";
|
|
218
|
+
}
|
|
219
|
+
case "prioritize": {
|
|
220
|
+
if (!deps.linearApiKey) {
|
|
221
|
+
return `⚠️ No Linear API key configured — cannot prioritize *${cmd.issueId}*.`;
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const linear = await getLinear();
|
|
225
|
+
if (!linear)
|
|
226
|
+
return `⚠️ No Linear API key configured — cannot prioritize *${cmd.issueId}*.`;
|
|
227
|
+
const issue = await findIssueByIdentifier(linear, cmd.issueId);
|
|
228
|
+
if (!issue)
|
|
229
|
+
return `⚠️ Issue *${cmd.issueId}* not found in Linear.`;
|
|
230
|
+
// updateIssue and createComment are independent — run in parallel
|
|
231
|
+
await Promise.all([
|
|
232
|
+
linear.updateIssue(issue.id, { priority: 1 }),
|
|
233
|
+
linear.createComment({
|
|
234
|
+
issueId: issue.id,
|
|
235
|
+
body: "🤖 **PM Agent** — Bumped to top of queue via Slack command.",
|
|
236
|
+
}),
|
|
237
|
+
]);
|
|
238
|
+
log.info({ issueId: cmd.issueId }, "prioritized via Slack");
|
|
239
|
+
return `✅ *${cmd.issueId}* has been bumped to top priority (Urgent).`;
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
log.error({ err, issueId: cmd.issueId }, "prioritize failed");
|
|
243
|
+
return `❌ Failed to prioritize *${cmd.issueId}*: ${err.message}`;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
case "assign": {
|
|
247
|
+
if (!deps.linearApiKey) {
|
|
248
|
+
return `⚠️ No Linear API key configured — cannot assign *${cmd.issueId}*.`;
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
const linear = await getLinear();
|
|
252
|
+
if (!linear)
|
|
253
|
+
return `⚠️ No Linear API key configured — cannot assign *${cmd.issueId}*.`;
|
|
254
|
+
const issue = await findIssueByIdentifier(linear, cmd.issueId);
|
|
255
|
+
if (!issue)
|
|
256
|
+
return `⚠️ Issue *${cmd.issueId}* not found in Linear.`;
|
|
257
|
+
const team = await issue.team;
|
|
258
|
+
const allStates = await linear.workflowStates({
|
|
259
|
+
filter: { team: { id: { eq: team?.id } } },
|
|
260
|
+
first: 50,
|
|
261
|
+
});
|
|
262
|
+
const todoState = allStates.nodes?.find((s) => s.name === LINEAR_STATE_TODO);
|
|
263
|
+
if (!todoState)
|
|
264
|
+
return `⚠️ No "${LINEAR_STATE_TODO}" state found for *${cmd.issueId}*'s team.`;
|
|
265
|
+
await linear.updateIssue(issue.id, { stateId: todoState.id });
|
|
266
|
+
await linear.createComment({
|
|
267
|
+
issueId: issue.id,
|
|
268
|
+
body: "🤖 **PM Agent** — Manually assigned to Todo via Slack command.",
|
|
269
|
+
});
|
|
270
|
+
log.info({ issueId: cmd.issueId }, "manually assigned to Todo via Slack");
|
|
271
|
+
return `✅ *${cmd.issueId}* has been moved to Todo.`;
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
log.error({ err, issueId: cmd.issueId }, "assign failed");
|
|
275
|
+
return `❌ Failed to assign *${cmd.issueId}*: ${err.message}`;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
case "create": {
|
|
279
|
+
if (!deps.linearApiKey) {
|
|
280
|
+
return `⚠️ No Linear API key configured — cannot create issue.`;
|
|
281
|
+
}
|
|
282
|
+
if (!deps.teamIds || deps.teamIds.length === 0) {
|
|
283
|
+
return `⚠️ No team IDs configured — cannot create issue.`;
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
const linear = await getLinear();
|
|
287
|
+
if (!linear)
|
|
288
|
+
return `⚠️ No Linear API key configured — cannot create issue.`;
|
|
289
|
+
const created = await linear.createIssue({
|
|
290
|
+
teamId: deps.teamIds[0],
|
|
291
|
+
title: cmd.title,
|
|
292
|
+
description: cmd.description || undefined,
|
|
293
|
+
});
|
|
294
|
+
const issue = await created.issue;
|
|
295
|
+
const url = issue?.url ?? "";
|
|
296
|
+
log.info({ title: cmd.title, issueId: issue?.identifier }, "issue created via Slack");
|
|
297
|
+
return `✅ Created <${url}|${issue?.identifier ?? "new issue"}>: *${cmd.title}*`;
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
log.error({ err, title: cmd.title }, "create issue failed");
|
|
301
|
+
return `❌ Failed to create issue: ${err.message}`;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
case "bulk_create": {
|
|
305
|
+
if (!deps.linearApiKey) {
|
|
306
|
+
return `⚠️ No Linear API key configured — cannot create issues.`;
|
|
307
|
+
}
|
|
308
|
+
if (!deps.teamIds || deps.teamIds.length === 0) {
|
|
309
|
+
return `⚠️ No team IDs configured — cannot create issues.`;
|
|
310
|
+
}
|
|
311
|
+
if (!deps.callClaudeSonnet) {
|
|
312
|
+
return `⚠️ Bulk create requires a Sonnet model caller — not configured.`;
|
|
313
|
+
}
|
|
314
|
+
try {
|
|
315
|
+
const specs = await analyzeBulkCreateRequest(cmd.request, deps.callClaudeSonnet);
|
|
316
|
+
if (specs.length === 0) {
|
|
317
|
+
return `🤔 Could not generate any issues from your request. Try being more specific.`;
|
|
318
|
+
}
|
|
319
|
+
const linear = await getLinear();
|
|
320
|
+
if (!linear)
|
|
321
|
+
return `⚠️ No Linear API key configured — cannot create issues.`;
|
|
322
|
+
// Resolve the Triage state and auto-implement label IDs
|
|
323
|
+
const teamId = deps.teamIds[0];
|
|
324
|
+
const [allStatesRes, allLabelsRes] = await Promise.all([
|
|
325
|
+
linear.workflowStates({ filter: { team: { id: { eq: teamId } } }, first: 50 }),
|
|
326
|
+
linear.issueLabels({ first: 100 }),
|
|
327
|
+
]);
|
|
328
|
+
const triageState = allStatesRes.nodes?.find((s) => s.name === LINEAR_STATE_TRIAGE);
|
|
329
|
+
const labelMap = new Map();
|
|
330
|
+
for (const label of allLabelsRes.nodes ?? []) {
|
|
331
|
+
labelMap.set(label.name.toLowerCase(), label.id);
|
|
332
|
+
}
|
|
333
|
+
const autoImplementLabelId = labelMap.get(LINEAR_LABEL_AUTO_IMPLEMENT);
|
|
334
|
+
// Build all payloads first, then create all issues in parallel
|
|
335
|
+
const payloads = specs.map((spec) => {
|
|
336
|
+
const descWithCriteria = spec.acceptanceCriteria.length > 0
|
|
337
|
+
? `${spec.description}\n\n**Acceptance Criteria:**\n${spec.acceptanceCriteria.map((c) => `- [ ] ${c}`).join("\n")}`
|
|
338
|
+
: spec.description;
|
|
339
|
+
const payload = {
|
|
340
|
+
teamId,
|
|
341
|
+
title: spec.title,
|
|
342
|
+
description: descWithCriteria || undefined,
|
|
343
|
+
priority: spec.priority,
|
|
344
|
+
};
|
|
345
|
+
if (triageState)
|
|
346
|
+
payload.stateId = triageState.id;
|
|
347
|
+
if (autoImplementLabelId)
|
|
348
|
+
payload.labelIds = [autoImplementLabelId];
|
|
349
|
+
return payload;
|
|
350
|
+
});
|
|
351
|
+
const results = await Promise.all(payloads.map((p) => linear.createIssue(p)));
|
|
352
|
+
const issueObjects = await Promise.all(results.map((r) => r.issue));
|
|
353
|
+
const created = [];
|
|
354
|
+
for (let i = 0; i < issueObjects.length; i++) {
|
|
355
|
+
const issue = issueObjects[i];
|
|
356
|
+
if (issue) {
|
|
357
|
+
const title = specs[i].title;
|
|
358
|
+
created.push({ identifier: issue.identifier ?? "", url: issue.url ?? "", title });
|
|
359
|
+
log.info({ issueId: issue.identifier, title }, "bulk issue created via Slack");
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (created.length === 0) {
|
|
363
|
+
return `❌ Failed to create any issues.`;
|
|
364
|
+
}
|
|
365
|
+
const lines = [`✅ Created ${created.length} issue${created.length === 1 ? "" : "s"}:`];
|
|
366
|
+
for (const issue of created) {
|
|
367
|
+
const link = issue.url ? `<${issue.url}|${issue.identifier}>` : `*${issue.identifier}*`;
|
|
368
|
+
lines.push(`• ${link}: *${issue.title}*`);
|
|
369
|
+
}
|
|
370
|
+
return lines.join("\n");
|
|
371
|
+
}
|
|
372
|
+
catch (err) {
|
|
373
|
+
log.error({ err, request: cmd.request }, "bulk create failed");
|
|
374
|
+
return `❌ Failed to create issues: ${err.message}`;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
case "unknown":
|
|
378
|
+
return `🤔 I didn't understand that. Try:\n• \`/pm status\`\n• \`/pm prioritize BEC-25\`\n• \`/pm create "title" "description"\`\n• \`/pm assign BEC-13\`\n• \`/pm pause\` / \`/pm resume\``;
|
|
379
|
+
default:
|
|
380
|
+
return `Unknown command.`;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// ---------------------------------------------------------------------------
|
|
384
|
+
// Outbound notifications (PM Agent → Slack)
|
|
385
|
+
// ---------------------------------------------------------------------------
|
|
386
|
+
export class SlackInterfaceNotifier {
|
|
387
|
+
botToken;
|
|
388
|
+
channelId;
|
|
389
|
+
constructor(config) {
|
|
390
|
+
this.botToken = config.botToken;
|
|
391
|
+
this.channelId = config.channelId;
|
|
392
|
+
}
|
|
393
|
+
/** Called when PM Agent promotes an issue to Todo. */
|
|
394
|
+
async notifyAssigned(n) {
|
|
395
|
+
const urlPart = n.issueUrl ? `<${n.issueUrl}|${n.issueId}>` : `*${n.issueId}*`;
|
|
396
|
+
const text = `🤖 *PM Agent assigned* ${urlPart}: ${n.issueTitle}\n` +
|
|
397
|
+
`*Reasoning:* ${n.reasoning}`;
|
|
398
|
+
await this.postMessage({ channel: this.channelId, blocks: [{ type: "section", text: { type: "mrkdwn", text } }] });
|
|
399
|
+
}
|
|
400
|
+
/** Called when PM Agent skips/deprioritizes an issue. */
|
|
401
|
+
async notifySkipped(n) {
|
|
402
|
+
const text = `⏭ *PM Agent skipped* *${n.issueId}*: ${n.issueTitle}\n` +
|
|
403
|
+
`*Reason:* ${n.reasoning}`;
|
|
404
|
+
await this.postMessage({ channel: this.channelId, blocks: [{ type: "section", text: { type: "mrkdwn", text } }] });
|
|
405
|
+
}
|
|
406
|
+
/** Ask a human for input when priority is ambiguous. */
|
|
407
|
+
async askForClarification(question) {
|
|
408
|
+
const text = `❓ *PM Agent needs your input:*\n${question}`;
|
|
409
|
+
await this.postMessage({ channel: this.channelId, blocks: [{ type: "section", text: { type: "mrkdwn", text } }] });
|
|
410
|
+
}
|
|
411
|
+
/** Post a daily summary of assigned, completed, and blocked issues. */
|
|
412
|
+
async postDailySummary(entries, date) {
|
|
413
|
+
const label = date ?? new Date().toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric" });
|
|
414
|
+
const lines = [`📊 *PM Agent Daily Summary — ${label}*`, ""];
|
|
415
|
+
const assigned = entries.filter((e) => e.status === "assigned");
|
|
416
|
+
const completed = entries.filter((e) => e.status === "completed");
|
|
417
|
+
const blocked = entries.filter((e) => e.status === "blocked");
|
|
418
|
+
if (assigned.length > 0) {
|
|
419
|
+
lines.push(`*Assigned (${assigned.length}):*`);
|
|
420
|
+
for (const e of assigned)
|
|
421
|
+
lines.push(` • *${e.issueId}* — ${e.issueTitle}`);
|
|
422
|
+
}
|
|
423
|
+
if (completed.length > 0) {
|
|
424
|
+
lines.push(`*Completed (${completed.length}):*`);
|
|
425
|
+
for (const e of completed)
|
|
426
|
+
lines.push(` • *${e.issueId}* — ${e.issueTitle}`);
|
|
427
|
+
}
|
|
428
|
+
if (blocked.length > 0) {
|
|
429
|
+
lines.push(`*Blocked (${blocked.length}):*`);
|
|
430
|
+
for (const e of blocked)
|
|
431
|
+
lines.push(` • *${e.issueId}* — ${e.issueTitle}`);
|
|
432
|
+
}
|
|
433
|
+
if (entries.length === 0) {
|
|
434
|
+
lines.push("No activity today.");
|
|
435
|
+
}
|
|
436
|
+
await this.postMessage({
|
|
437
|
+
channel: this.channelId,
|
|
438
|
+
blocks: [{ type: "section", text: { type: "mrkdwn", text: lines.join("\n") } }],
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
async postMessage(payload) {
|
|
442
|
+
await postSlackMessage(this.botToken, payload);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// ---------------------------------------------------------------------------
|
|
446
|
+
// Hono router factory
|
|
447
|
+
// ---------------------------------------------------------------------------
|
|
448
|
+
/**
|
|
449
|
+
* Creates a Hono router that handles Slack slash commands and Events API
|
|
450
|
+
* messages for the PM Agent.
|
|
451
|
+
*
|
|
452
|
+
* Mount it in your app:
|
|
453
|
+
* const { router } = createSlackInterface(config);
|
|
454
|
+
* app.route("/", router);
|
|
455
|
+
*
|
|
456
|
+
* The router registers:
|
|
457
|
+
* POST /slack/commands — Slack slash command handler
|
|
458
|
+
* POST /slack/events — Slack Events API handler
|
|
459
|
+
*/
|
|
460
|
+
export function createSlackInterface(config) {
|
|
461
|
+
const router = new Hono();
|
|
462
|
+
const notifier = new SlackInterfaceNotifier(config);
|
|
463
|
+
const callClaude = config.callClaude ?? makeCallClaude();
|
|
464
|
+
const callClaudeSonnet = config.callClaudeSonnet ?? makeCallClaudeSonnet();
|
|
465
|
+
const executorDeps = {
|
|
466
|
+
linearApiKey: config.linearApiKey,
|
|
467
|
+
teamIds: config.teamIds,
|
|
468
|
+
callClaudeSonnet,
|
|
469
|
+
};
|
|
470
|
+
// Helper: verify Slack signature and return 401 on failure
|
|
471
|
+
async function checkSignature(c) {
|
|
472
|
+
const rawBody = await c.req.text();
|
|
473
|
+
const timestamp = c.req.header("X-Slack-Request-Timestamp") ?? "";
|
|
474
|
+
const signature = c.req.header("X-Slack-Signature") ?? "";
|
|
475
|
+
const valid = await verifySlackSignature(rawBody, timestamp, signature, config.signingSecret);
|
|
476
|
+
if (!valid)
|
|
477
|
+
return null;
|
|
478
|
+
return rawBody;
|
|
479
|
+
}
|
|
480
|
+
// ------------------------------------------------------------------
|
|
481
|
+
// POST /slack/commands
|
|
482
|
+
// ------------------------------------------------------------------
|
|
483
|
+
router.post("/slack/commands", async (c) => {
|
|
484
|
+
const rawBody = await checkSignature(c);
|
|
485
|
+
if (rawBody === null) {
|
|
486
|
+
return c.json({ error: "Invalid signature" }, 401);
|
|
487
|
+
}
|
|
488
|
+
// Parse URL-encoded form body
|
|
489
|
+
const params = new URLSearchParams(rawBody);
|
|
490
|
+
const commandText = (params.get("text") ?? "").trim();
|
|
491
|
+
const responseUrl = params.get("response_url") ?? "";
|
|
492
|
+
log.info({ commandText }, "received Slack slash command");
|
|
493
|
+
let cmd = parsePmCommand(commandText);
|
|
494
|
+
// Fall back to NL interpretation if command is unknown
|
|
495
|
+
if (cmd.type === "unknown" && commandText.length > 0) {
|
|
496
|
+
cmd = await interpretNaturalLanguage(commandText, callClaude);
|
|
497
|
+
}
|
|
498
|
+
const replyText = await executePmCommand(cmd, executorDeps);
|
|
499
|
+
// If a response_url is provided, post back asynchronously
|
|
500
|
+
if (responseUrl) {
|
|
501
|
+
postToResponseUrl(responseUrl, replyText).catch((err) => log.error({ err }, "failed to post to Slack response_url"));
|
|
502
|
+
}
|
|
503
|
+
// Immediate acknowledgement (required within 3s)
|
|
504
|
+
return c.json({ response_type: "ephemeral", text: replyText });
|
|
505
|
+
});
|
|
506
|
+
// ------------------------------------------------------------------
|
|
507
|
+
// POST /slack/events
|
|
508
|
+
// ------------------------------------------------------------------
|
|
509
|
+
router.post("/slack/events", async (c) => {
|
|
510
|
+
// Slack sends url_verification during setup — must respond with challenge.
|
|
511
|
+
// We peek at the body first; if it's a challenge, respond immediately.
|
|
512
|
+
// For all other events, verify the signature.
|
|
513
|
+
const rawBody = await c.req.text();
|
|
514
|
+
let body;
|
|
515
|
+
try {
|
|
516
|
+
body = JSON.parse(rawBody);
|
|
517
|
+
}
|
|
518
|
+
catch {
|
|
519
|
+
return c.json({ error: "Invalid JSON" }, 400);
|
|
520
|
+
}
|
|
521
|
+
// URL verification challenge — respond before signature check
|
|
522
|
+
// (Slack sends this once during Event Subscriptions setup)
|
|
523
|
+
if (body.type === "url_verification") {
|
|
524
|
+
return c.json({ challenge: body.challenge });
|
|
525
|
+
}
|
|
526
|
+
// Verify signature for all real events
|
|
527
|
+
const timestamp = c.req.header("X-Slack-Request-Timestamp") ?? "";
|
|
528
|
+
const signature = c.req.header("X-Slack-Signature") ?? "";
|
|
529
|
+
const valid = await verifySlackSignature(rawBody, timestamp, signature, config.signingSecret);
|
|
530
|
+
if (!valid) {
|
|
531
|
+
return c.json({ error: "Invalid signature" }, 401);
|
|
532
|
+
}
|
|
533
|
+
// Handle event callbacks
|
|
534
|
+
if (body.type === "event_callback") {
|
|
535
|
+
const event = body.event ?? {};
|
|
536
|
+
// Only handle direct messages or mentions in the PM channel
|
|
537
|
+
if (event.type === "app_mention" || event.type === "message") {
|
|
538
|
+
// Skip bot messages to avoid loops
|
|
539
|
+
if (event.bot_id || event.subtype === "bot_message") {
|
|
540
|
+
return c.json({ ok: true });
|
|
541
|
+
}
|
|
542
|
+
const messageText = (event.text ?? "").replace(/<@[A-Z0-9]+>/g, "").trim();
|
|
543
|
+
if (!messageText)
|
|
544
|
+
return c.json({ ok: true });
|
|
545
|
+
log.info({ messageText }, "received Slack message event");
|
|
546
|
+
// Process asynchronously — acknowledge immediately
|
|
547
|
+
processMessageAsync(messageText, event.channel ?? config.channelId, config.botToken, callClaude, executorDeps)
|
|
548
|
+
.catch((err) => log.error({ err }, "async message processing failed"));
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return c.json({ ok: true });
|
|
552
|
+
});
|
|
553
|
+
return { router, notifier };
|
|
554
|
+
}
|
|
555
|
+
// ---------------------------------------------------------------------------
|
|
556
|
+
// Internal helpers
|
|
557
|
+
// ---------------------------------------------------------------------------
|
|
558
|
+
/**
|
|
559
|
+
* Uses a capable Claude model (Sonnet) to analyze a bulk create request and
|
|
560
|
+
* produce a structured list of issue specifications.
|
|
561
|
+
*/
|
|
562
|
+
export async function analyzeBulkCreateRequest(request, callClaudeSonnet) {
|
|
563
|
+
const safe = sanitize(request);
|
|
564
|
+
const prompt = `You are a software PM Agent. A user asked: "${safe}"\n\n` +
|
|
565
|
+
`Analyze this request and generate a list of concrete, actionable software issues to create.\n` +
|
|
566
|
+
`Each issue must have a clear title, description, priority (1=urgent, 2=high, 3=medium, 4=low), and acceptance criteria.\n\n` +
|
|
567
|
+
`Respond ONLY with a JSON array (no other text), e.g.:\n` +
|
|
568
|
+
`[\n` +
|
|
569
|
+
` {\n` +
|
|
570
|
+
` "title": "Issue title",\n` +
|
|
571
|
+
` "description": "Clear description of what needs to be done",\n` +
|
|
572
|
+
` "priority": 2,\n` +
|
|
573
|
+
` "acceptanceCriteria": ["Criterion 1", "Criterion 2"]\n` +
|
|
574
|
+
` }\n` +
|
|
575
|
+
`]\n\n` +
|
|
576
|
+
`Rules:\n` +
|
|
577
|
+
`- Generate between 1 and 10 issues\n` +
|
|
578
|
+
`- Each issue must be specific and actionable\n` +
|
|
579
|
+
`- Priority must be 1, 2, 3, or 4\n` +
|
|
580
|
+
`- acceptanceCriteria must be a non-empty array of strings\n` +
|
|
581
|
+
`- Respond ONLY with the JSON array, no markdown fences or explanation`;
|
|
582
|
+
try {
|
|
583
|
+
const raw = await callClaudeSonnet(prompt);
|
|
584
|
+
// Parse the array — look for a JSON array in the response
|
|
585
|
+
const arrayMatch = raw.match(/\[[\s\S]*\]/);
|
|
586
|
+
if (!arrayMatch) {
|
|
587
|
+
log.warn({ responsePreview: raw.slice(0, 200) }, "bulk create: no JSON array in Claude response");
|
|
588
|
+
return [];
|
|
589
|
+
}
|
|
590
|
+
let parsed;
|
|
591
|
+
try {
|
|
592
|
+
parsed = JSON.parse(arrayMatch[0]);
|
|
593
|
+
}
|
|
594
|
+
catch {
|
|
595
|
+
log.warn({ responsePreview: raw.slice(0, 200) }, "bulk create: failed to parse JSON array");
|
|
596
|
+
return [];
|
|
597
|
+
}
|
|
598
|
+
if (!Array.isArray(parsed))
|
|
599
|
+
return [];
|
|
600
|
+
const specs = [];
|
|
601
|
+
for (const item of parsed) {
|
|
602
|
+
if (typeof item !== "object" || item === null)
|
|
603
|
+
continue;
|
|
604
|
+
const title = typeof item.title === "string" ? item.title.trim() : "";
|
|
605
|
+
const description = typeof item.description === "string" ? item.description.trim() : "";
|
|
606
|
+
const priority = typeof item.priority === "number" && VALID_PRIORITIES.includes(item.priority)
|
|
607
|
+
? item.priority
|
|
608
|
+
: DEFAULT_PRIORITY;
|
|
609
|
+
const acceptanceCriteria = Array.isArray(item.acceptanceCriteria)
|
|
610
|
+
? item.acceptanceCriteria.filter((c) => typeof c === "string" && c.trim().length > 0)
|
|
611
|
+
: [];
|
|
612
|
+
if (!title)
|
|
613
|
+
continue;
|
|
614
|
+
// Cap title/description length to prevent excessively large issues
|
|
615
|
+
specs.push({
|
|
616
|
+
title: title.slice(0, 200),
|
|
617
|
+
description: description.slice(0, 5000),
|
|
618
|
+
priority,
|
|
619
|
+
acceptanceCriteria: acceptanceCriteria.slice(0, 10),
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
return specs.slice(0, 10);
|
|
623
|
+
}
|
|
624
|
+
catch (err) {
|
|
625
|
+
log.warn({ err }, "bulk create: failed to analyze request");
|
|
626
|
+
return [];
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
async function postToResponseUrl(responseUrl, text) {
|
|
630
|
+
await fetch(responseUrl, {
|
|
631
|
+
method: "POST",
|
|
632
|
+
headers: { "Content-Type": "application/json" },
|
|
633
|
+
body: JSON.stringify({ response_type: "ephemeral", text }),
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
async function processMessageAsync(text, channel, botToken, callClaude, deps) {
|
|
637
|
+
const cmd = await interpretNaturalLanguage(text, callClaude);
|
|
638
|
+
const replyText = await executePmCommand(cmd, deps);
|
|
639
|
+
await postSlackMessage(botToken, { channel, text: replyText });
|
|
640
|
+
}
|
|
641
|
+
//# sourceMappingURL=slack-interface.js.map
|