@renseiai/agentfactory 0.8.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 +125 -0
- package/dist/src/config/index.d.ts +3 -0
- package/dist/src/config/index.d.ts.map +1 -0
- package/dist/src/config/index.js +1 -0
- package/dist/src/config/repository-config.d.ts +44 -0
- package/dist/src/config/repository-config.d.ts.map +1 -0
- package/dist/src/config/repository-config.js +88 -0
- package/dist/src/config/repository-config.test.d.ts +2 -0
- package/dist/src/config/repository-config.test.d.ts.map +1 -0
- package/dist/src/config/repository-config.test.js +249 -0
- package/dist/src/deployment/deployment-checker.d.ts +110 -0
- package/dist/src/deployment/deployment-checker.d.ts.map +1 -0
- package/dist/src/deployment/deployment-checker.js +242 -0
- package/dist/src/deployment/index.d.ts +3 -0
- package/dist/src/deployment/index.d.ts.map +1 -0
- package/dist/src/deployment/index.js +2 -0
- package/dist/src/frontend/index.d.ts +2 -0
- package/dist/src/frontend/index.d.ts.map +1 -0
- package/dist/src/frontend/index.js +1 -0
- package/dist/src/frontend/types.d.ts +106 -0
- package/dist/src/frontend/types.d.ts.map +1 -0
- package/dist/src/frontend/types.js +11 -0
- package/dist/src/governor/decision-engine.d.ts +52 -0
- package/dist/src/governor/decision-engine.d.ts.map +1 -0
- package/dist/src/governor/decision-engine.js +220 -0
- package/dist/src/governor/decision-engine.test.d.ts +2 -0
- package/dist/src/governor/decision-engine.test.d.ts.map +1 -0
- package/dist/src/governor/decision-engine.test.js +629 -0
- package/dist/src/governor/event-bus.d.ts +43 -0
- package/dist/src/governor/event-bus.d.ts.map +1 -0
- package/dist/src/governor/event-bus.js +8 -0
- package/dist/src/governor/event-deduplicator.d.ts +43 -0
- package/dist/src/governor/event-deduplicator.d.ts.map +1 -0
- package/dist/src/governor/event-deduplicator.js +53 -0
- package/dist/src/governor/event-driven-governor.d.ts +131 -0
- package/dist/src/governor/event-driven-governor.d.ts.map +1 -0
- package/dist/src/governor/event-driven-governor.js +379 -0
- package/dist/src/governor/event-driven-governor.test.d.ts +2 -0
- package/dist/src/governor/event-driven-governor.test.d.ts.map +1 -0
- package/dist/src/governor/event-driven-governor.test.js +673 -0
- package/dist/src/governor/event-types.d.ts +78 -0
- package/dist/src/governor/event-types.d.ts.map +1 -0
- package/dist/src/governor/event-types.js +32 -0
- package/dist/src/governor/governor-types.d.ts +82 -0
- package/dist/src/governor/governor-types.d.ts.map +1 -0
- package/dist/src/governor/governor-types.js +21 -0
- package/dist/src/governor/governor.d.ts +100 -0
- package/dist/src/governor/governor.d.ts.map +1 -0
- package/dist/src/governor/governor.js +262 -0
- package/dist/src/governor/governor.test.d.ts +2 -0
- package/dist/src/governor/governor.test.d.ts.map +1 -0
- package/dist/src/governor/governor.test.js +514 -0
- package/dist/src/governor/human-touchpoints.d.ts +131 -0
- package/dist/src/governor/human-touchpoints.d.ts.map +1 -0
- package/dist/src/governor/human-touchpoints.js +251 -0
- package/dist/src/governor/human-touchpoints.test.d.ts +2 -0
- package/dist/src/governor/human-touchpoints.test.d.ts.map +1 -0
- package/dist/src/governor/human-touchpoints.test.js +366 -0
- package/dist/src/governor/in-memory-event-bus.d.ts +29 -0
- package/dist/src/governor/in-memory-event-bus.d.ts.map +1 -0
- package/dist/src/governor/in-memory-event-bus.js +79 -0
- package/dist/src/governor/index.d.ts +14 -0
- package/dist/src/governor/index.d.ts.map +1 -0
- package/dist/src/governor/index.js +13 -0
- package/dist/src/governor/override-parser.d.ts +60 -0
- package/dist/src/governor/override-parser.d.ts.map +1 -0
- package/dist/src/governor/override-parser.js +98 -0
- package/dist/src/governor/override-parser.test.d.ts +2 -0
- package/dist/src/governor/override-parser.test.d.ts.map +1 -0
- package/dist/src/governor/override-parser.test.js +312 -0
- package/dist/src/governor/platform-adapter.d.ts +69 -0
- package/dist/src/governor/platform-adapter.d.ts.map +1 -0
- package/dist/src/governor/platform-adapter.js +11 -0
- package/dist/src/governor/processing-state.d.ts +66 -0
- package/dist/src/governor/processing-state.d.ts.map +1 -0
- package/dist/src/governor/processing-state.js +43 -0
- package/dist/src/governor/processing-state.test.d.ts +2 -0
- package/dist/src/governor/processing-state.test.d.ts.map +1 -0
- package/dist/src/governor/processing-state.test.js +96 -0
- package/dist/src/governor/top-of-funnel.d.ts +118 -0
- package/dist/src/governor/top-of-funnel.d.ts.map +1 -0
- package/dist/src/governor/top-of-funnel.js +168 -0
- package/dist/src/governor/top-of-funnel.test.d.ts +2 -0
- package/dist/src/governor/top-of-funnel.test.d.ts.map +1 -0
- package/dist/src/governor/top-of-funnel.test.js +331 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +10 -0
- package/dist/src/linear-cli.d.ts +38 -0
- package/dist/src/linear-cli.d.ts.map +1 -0
- package/dist/src/linear-cli.js +674 -0
- package/dist/src/logger.d.ts +117 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +430 -0
- package/dist/src/manifest/generate.d.ts +20 -0
- package/dist/src/manifest/generate.d.ts.map +1 -0
- package/dist/src/manifest/generate.js +65 -0
- package/dist/src/manifest/index.d.ts +4 -0
- package/dist/src/manifest/index.d.ts.map +1 -0
- package/dist/src/manifest/index.js +2 -0
- package/dist/src/manifest/route-manifest.d.ts +34 -0
- package/dist/src/manifest/route-manifest.d.ts.map +1 -0
- package/dist/src/manifest/route-manifest.js +148 -0
- package/dist/src/orchestrator/activity-emitter.d.ts +119 -0
- package/dist/src/orchestrator/activity-emitter.d.ts.map +1 -0
- package/dist/src/orchestrator/activity-emitter.js +306 -0
- package/dist/src/orchestrator/api-activity-emitter.d.ts +167 -0
- package/dist/src/orchestrator/api-activity-emitter.d.ts.map +1 -0
- package/dist/src/orchestrator/api-activity-emitter.js +417 -0
- package/dist/src/orchestrator/heartbeat-writer.d.ts +57 -0
- package/dist/src/orchestrator/heartbeat-writer.d.ts.map +1 -0
- package/dist/src/orchestrator/heartbeat-writer.js +137 -0
- package/dist/src/orchestrator/index.d.ts +20 -0
- package/dist/src/orchestrator/index.d.ts.map +1 -0
- package/dist/src/orchestrator/index.js +22 -0
- package/dist/src/orchestrator/log-analyzer.d.ts +160 -0
- package/dist/src/orchestrator/log-analyzer.d.ts.map +1 -0
- package/dist/src/orchestrator/log-analyzer.js +572 -0
- package/dist/src/orchestrator/log-config.d.ts +39 -0
- package/dist/src/orchestrator/log-config.d.ts.map +1 -0
- package/dist/src/orchestrator/log-config.js +45 -0
- package/dist/src/orchestrator/orchestrator.d.ts +316 -0
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -0
- package/dist/src/orchestrator/orchestrator.js +3290 -0
- package/dist/src/orchestrator/parse-work-result.d.ts +16 -0
- package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -0
- package/dist/src/orchestrator/parse-work-result.js +135 -0
- package/dist/src/orchestrator/parse-work-result.test.d.ts +2 -0
- package/dist/src/orchestrator/parse-work-result.test.d.ts.map +1 -0
- package/dist/src/orchestrator/parse-work-result.test.js +234 -0
- package/dist/src/orchestrator/progress-logger.d.ts +72 -0
- package/dist/src/orchestrator/progress-logger.d.ts.map +1 -0
- package/dist/src/orchestrator/progress-logger.js +135 -0
- package/dist/src/orchestrator/session-logger.d.ts +159 -0
- package/dist/src/orchestrator/session-logger.d.ts.map +1 -0
- package/dist/src/orchestrator/session-logger.js +275 -0
- package/dist/src/orchestrator/state-recovery.d.ts +96 -0
- package/dist/src/orchestrator/state-recovery.d.ts.map +1 -0
- package/dist/src/orchestrator/state-recovery.js +302 -0
- package/dist/src/orchestrator/state-types.d.ts +165 -0
- package/dist/src/orchestrator/state-types.d.ts.map +1 -0
- package/dist/src/orchestrator/state-types.js +7 -0
- package/dist/src/orchestrator/stream-parser.d.ts +151 -0
- package/dist/src/orchestrator/stream-parser.d.ts.map +1 -0
- package/dist/src/orchestrator/stream-parser.js +137 -0
- package/dist/src/orchestrator/types.d.ts +232 -0
- package/dist/src/orchestrator/types.d.ts.map +1 -0
- package/dist/src/orchestrator/types.js +4 -0
- package/dist/src/orchestrator/validate-git-remote.test.d.ts +2 -0
- package/dist/src/orchestrator/validate-git-remote.test.d.ts.map +1 -0
- package/dist/src/orchestrator/validate-git-remote.test.js +61 -0
- package/dist/src/providers/a2a-auth.d.ts +81 -0
- package/dist/src/providers/a2a-auth.d.ts.map +1 -0
- package/dist/src/providers/a2a-auth.js +188 -0
- package/dist/src/providers/a2a-auth.test.d.ts +2 -0
- package/dist/src/providers/a2a-auth.test.d.ts.map +1 -0
- package/dist/src/providers/a2a-auth.test.js +232 -0
- package/dist/src/providers/a2a-provider.d.ts +254 -0
- package/dist/src/providers/a2a-provider.d.ts.map +1 -0
- package/dist/src/providers/a2a-provider.integration.test.d.ts +9 -0
- package/dist/src/providers/a2a-provider.integration.test.d.ts.map +1 -0
- package/dist/src/providers/a2a-provider.integration.test.js +665 -0
- package/dist/src/providers/a2a-provider.js +811 -0
- package/dist/src/providers/a2a-provider.test.d.ts +2 -0
- package/dist/src/providers/a2a-provider.test.d.ts.map +1 -0
- package/dist/src/providers/a2a-provider.test.js +681 -0
- package/dist/src/providers/amp-provider.d.ts +20 -0
- package/dist/src/providers/amp-provider.d.ts.map +1 -0
- package/dist/src/providers/amp-provider.js +24 -0
- package/dist/src/providers/claude-provider.d.ts +18 -0
- package/dist/src/providers/claude-provider.d.ts.map +1 -0
- package/dist/src/providers/claude-provider.js +437 -0
- package/dist/src/providers/codex-provider.d.ts +133 -0
- package/dist/src/providers/codex-provider.d.ts.map +1 -0
- package/dist/src/providers/codex-provider.js +381 -0
- package/dist/src/providers/codex-provider.test.d.ts +2 -0
- package/dist/src/providers/codex-provider.test.d.ts.map +1 -0
- package/dist/src/providers/codex-provider.test.js +387 -0
- package/dist/src/providers/index.d.ts +44 -0
- package/dist/src/providers/index.d.ts.map +1 -0
- package/dist/src/providers/index.js +85 -0
- package/dist/src/providers/spring-ai-provider.d.ts +90 -0
- package/dist/src/providers/spring-ai-provider.d.ts.map +1 -0
- package/dist/src/providers/spring-ai-provider.integration.test.d.ts +13 -0
- package/dist/src/providers/spring-ai-provider.integration.test.d.ts.map +1 -0
- package/dist/src/providers/spring-ai-provider.integration.test.js +351 -0
- package/dist/src/providers/spring-ai-provider.js +317 -0
- package/dist/src/providers/spring-ai-provider.test.d.ts +2 -0
- package/dist/src/providers/spring-ai-provider.test.d.ts.map +1 -0
- package/dist/src/providers/spring-ai-provider.test.js +200 -0
- package/dist/src/providers/types.d.ts +165 -0
- package/dist/src/providers/types.d.ts.map +1 -0
- package/dist/src/providers/types.js +13 -0
- package/dist/src/templates/adapters.d.ts +51 -0
- package/dist/src/templates/adapters.d.ts.map +1 -0
- package/dist/src/templates/adapters.js +104 -0
- package/dist/src/templates/adapters.test.d.ts +2 -0
- package/dist/src/templates/adapters.test.d.ts.map +1 -0
- package/dist/src/templates/adapters.test.js +165 -0
- package/dist/src/templates/agent-definition.d.ts +85 -0
- package/dist/src/templates/agent-definition.d.ts.map +1 -0
- package/dist/src/templates/agent-definition.js +97 -0
- package/dist/src/templates/agent-definition.test.d.ts +2 -0
- package/dist/src/templates/agent-definition.test.d.ts.map +1 -0
- package/dist/src/templates/agent-definition.test.js +209 -0
- package/dist/src/templates/index.d.ts +14 -0
- package/dist/src/templates/index.d.ts.map +1 -0
- package/dist/src/templates/index.js +11 -0
- package/dist/src/templates/loader.d.ts +41 -0
- package/dist/src/templates/loader.d.ts.map +1 -0
- package/dist/src/templates/loader.js +114 -0
- package/dist/src/templates/registry.d.ts +80 -0
- package/dist/src/templates/registry.d.ts.map +1 -0
- package/dist/src/templates/registry.js +177 -0
- package/dist/src/templates/registry.test.d.ts +2 -0
- package/dist/src/templates/registry.test.d.ts.map +1 -0
- package/dist/src/templates/registry.test.js +198 -0
- package/dist/src/templates/renderer.d.ts +29 -0
- package/dist/src/templates/renderer.d.ts.map +1 -0
- package/dist/src/templates/renderer.js +35 -0
- package/dist/src/templates/strategy-templates.test.d.ts +2 -0
- package/dist/src/templates/strategy-templates.test.d.ts.map +1 -0
- package/dist/src/templates/strategy-templates.test.js +619 -0
- package/dist/src/templates/types.d.ts +233 -0
- package/dist/src/templates/types.d.ts.map +1 -0
- package/dist/src/templates/types.js +127 -0
- package/dist/src/templates/types.test.d.ts +2 -0
- package/dist/src/templates/types.test.d.ts.map +1 -0
- package/dist/src/templates/types.test.js +232 -0
- package/dist/src/tools/index.d.ts +6 -0
- package/dist/src/tools/index.d.ts.map +1 -0
- package/dist/src/tools/index.js +3 -0
- package/dist/src/tools/linear-runner.d.ts +34 -0
- package/dist/src/tools/linear-runner.d.ts.map +1 -0
- package/dist/src/tools/linear-runner.js +700 -0
- package/dist/src/tools/plugins/linear.d.ts +9 -0
- package/dist/src/tools/plugins/linear.d.ts.map +1 -0
- package/dist/src/tools/plugins/linear.js +138 -0
- package/dist/src/tools/registry.d.ts +9 -0
- package/dist/src/tools/registry.d.ts.map +1 -0
- package/dist/src/tools/registry.js +18 -0
- package/dist/src/tools/types.d.ts +18 -0
- package/dist/src/tools/types.d.ts.map +1 -0
- package/dist/src/tools/types.js +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deployment Status Checker
|
|
3
|
+
*
|
|
4
|
+
* Queries GitHub commit status API for Vercel deployment state.
|
|
5
|
+
* Used to verify deployments before QA/acceptance work can proceed.
|
|
6
|
+
*/
|
|
7
|
+
import { exec } from 'child_process';
|
|
8
|
+
import { promisify } from 'util';
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
const DEFAULT_OPTIONS = {
|
|
11
|
+
owner: 'renseiai',
|
|
12
|
+
repo: 'agentfactory',
|
|
13
|
+
timeout: 30000,
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Parse Vercel app name from GitHub status context
|
|
17
|
+
* Contexts look like: "Vercel – renseiai-social" or "Vercel"
|
|
18
|
+
*/
|
|
19
|
+
function parseAppName(context) {
|
|
20
|
+
// Extract app name after "Vercel – " or "Vercel - "
|
|
21
|
+
const match = context.match(/Vercel\s*[–-]\s*(.+)/);
|
|
22
|
+
if (match) {
|
|
23
|
+
return match[1].trim();
|
|
24
|
+
}
|
|
25
|
+
// Fallback to just "Vercel" if no app name
|
|
26
|
+
return context;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Check if a status description indicates a successful skip (monorepo optimization)
|
|
30
|
+
* Vercel skips deployments for unchanged apps, which is treated as success
|
|
31
|
+
*/
|
|
32
|
+
function isSuccessfulSkip(description) {
|
|
33
|
+
return description.toLowerCase().includes('skipped') &&
|
|
34
|
+
description.toLowerCase().includes('not affected');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Map GitHub status state to our normalized state
|
|
38
|
+
*/
|
|
39
|
+
function normalizeState(state, description) {
|
|
40
|
+
// Handle successful skips as success
|
|
41
|
+
if (isSuccessfulSkip(description)) {
|
|
42
|
+
return 'success';
|
|
43
|
+
}
|
|
44
|
+
switch (state.toLowerCase()) {
|
|
45
|
+
case 'success':
|
|
46
|
+
return 'success';
|
|
47
|
+
case 'pending':
|
|
48
|
+
return 'pending';
|
|
49
|
+
case 'failure':
|
|
50
|
+
return 'failure';
|
|
51
|
+
case 'error':
|
|
52
|
+
return 'error';
|
|
53
|
+
default:
|
|
54
|
+
return 'pending';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get the PR number for the current branch using gh CLI
|
|
59
|
+
*/
|
|
60
|
+
export async function getPRNumber(timeout = 30000) {
|
|
61
|
+
try {
|
|
62
|
+
const { stdout } = await execAsync('gh pr list --head "$(git branch --show-current)" --json number -q ".[0].number"', { timeout });
|
|
63
|
+
const prNumber = parseInt(stdout.trim(), 10);
|
|
64
|
+
return isNaN(prNumber) ? null : prNumber;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get the head commit SHA for a PR
|
|
72
|
+
*/
|
|
73
|
+
export async function getPRHeadSha(prNumber, options = {}) {
|
|
74
|
+
const { owner, repo, timeout } = { ...DEFAULT_OPTIONS, ...options };
|
|
75
|
+
try {
|
|
76
|
+
const { stdout } = await execAsync(`gh api repos/${owner}/${repo}/pulls/${prNumber} --jq '.head.sha'`, { timeout });
|
|
77
|
+
return stdout.trim() || null;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check deployment status for a specific commit SHA
|
|
85
|
+
*/
|
|
86
|
+
export async function checkDeploymentStatus(commitSha, options = {}) {
|
|
87
|
+
const { owner, repo, timeout } = { ...DEFAULT_OPTIONS, ...options };
|
|
88
|
+
const { stdout } = await execAsync(`gh api repos/${owner}/${repo}/commits/${commitSha}/status`, { timeout });
|
|
89
|
+
const response = JSON.parse(stdout);
|
|
90
|
+
// Extract Vercel-related statuses
|
|
91
|
+
const allStatuses = response.statuses || [];
|
|
92
|
+
// Filter to only Vercel statuses
|
|
93
|
+
const vercelStatuses = allStatuses.filter((s) => s.context.toLowerCase().includes('vercel'));
|
|
94
|
+
const deploymentStatuses = vercelStatuses.map((s) => ({
|
|
95
|
+
app: parseAppName(s.context),
|
|
96
|
+
state: normalizeState(s.state, s.description || ''),
|
|
97
|
+
description: s.description || '',
|
|
98
|
+
targetUrl: s.target_url,
|
|
99
|
+
context: s.context,
|
|
100
|
+
}));
|
|
101
|
+
// Calculate aggregate states
|
|
102
|
+
const allSucceeded = deploymentStatuses.length > 0 &&
|
|
103
|
+
deploymentStatuses.every((s) => s.state === 'success');
|
|
104
|
+
const anyFailed = deploymentStatuses.some((s) => s.state === 'failure' || s.state === 'error');
|
|
105
|
+
const anyPending = deploymentStatuses.some((s) => s.state === 'pending');
|
|
106
|
+
return {
|
|
107
|
+
allSucceeded,
|
|
108
|
+
anyFailed,
|
|
109
|
+
anyPending,
|
|
110
|
+
statuses: deploymentStatuses,
|
|
111
|
+
commitSha,
|
|
112
|
+
overallState: response.state || 'unknown',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Check deployment status for a PR number
|
|
117
|
+
* Convenience function that gets the commit SHA and checks status
|
|
118
|
+
*/
|
|
119
|
+
export async function checkPRDeploymentStatus(prNumber, options = {}) {
|
|
120
|
+
const commitSha = await getPRHeadSha(prNumber, options);
|
|
121
|
+
if (!commitSha) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
return checkDeploymentStatus(commitSha, options);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Format deployment check result for display
|
|
128
|
+
*/
|
|
129
|
+
export function formatDeploymentStatus(result) {
|
|
130
|
+
const lines = [];
|
|
131
|
+
lines.push(`## Deployment Status Check`);
|
|
132
|
+
lines.push(``);
|
|
133
|
+
lines.push(`**Commit:** \`${result.commitSha.slice(0, 7)}\``);
|
|
134
|
+
lines.push(`**Overall State:** ${result.overallState}`);
|
|
135
|
+
lines.push(``);
|
|
136
|
+
if (result.statuses.length === 0) {
|
|
137
|
+
lines.push(`No Vercel deployments found for this commit.`);
|
|
138
|
+
return lines.join('\n');
|
|
139
|
+
}
|
|
140
|
+
lines.push(`| App | State | Description |`);
|
|
141
|
+
lines.push(`|-----|-------|-------------|`);
|
|
142
|
+
for (const status of result.statuses) {
|
|
143
|
+
const stateEmoji = {
|
|
144
|
+
success: '✅',
|
|
145
|
+
pending: '⏳',
|
|
146
|
+
failure: '❌',
|
|
147
|
+
error: '❌',
|
|
148
|
+
}[status.state];
|
|
149
|
+
const url = status.targetUrl
|
|
150
|
+
? `[${status.app}](${status.targetUrl})`
|
|
151
|
+
: status.app;
|
|
152
|
+
lines.push(`| ${url} | ${stateEmoji} ${status.state} | ${status.description} |`);
|
|
153
|
+
}
|
|
154
|
+
lines.push(``);
|
|
155
|
+
if (result.anyFailed) {
|
|
156
|
+
lines.push(`### ❌ Deployment Failed`);
|
|
157
|
+
lines.push(``);
|
|
158
|
+
lines.push(`One or more Vercel deployments failed. QA/acceptance cannot proceed until deployments succeed.`);
|
|
159
|
+
}
|
|
160
|
+
else if (result.anyPending) {
|
|
161
|
+
lines.push(`### ⏳ Deployment Pending`);
|
|
162
|
+
lines.push(``);
|
|
163
|
+
lines.push(`One or more Vercel deployments are still in progress.`);
|
|
164
|
+
}
|
|
165
|
+
else if (result.allSucceeded) {
|
|
166
|
+
lines.push(`### ✅ All Deployments Succeeded`);
|
|
167
|
+
lines.push(``);
|
|
168
|
+
lines.push(`All Vercel deployments have completed successfully.`);
|
|
169
|
+
}
|
|
170
|
+
return lines.join('\n');
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Format failed deployments for a comment
|
|
174
|
+
*/
|
|
175
|
+
export function formatFailedDeployments(result) {
|
|
176
|
+
const failed = result.statuses.filter((s) => s.state === 'failure' || s.state === 'error');
|
|
177
|
+
if (failed.length === 0) {
|
|
178
|
+
return 'No failed deployments.';
|
|
179
|
+
}
|
|
180
|
+
const lines = ['**Failed Deployments:**', ''];
|
|
181
|
+
for (const status of failed) {
|
|
182
|
+
const url = status.targetUrl
|
|
183
|
+
? ` - [View logs](${status.targetUrl})`
|
|
184
|
+
: '';
|
|
185
|
+
lines.push(`- **${status.app}**: ${status.description}${url}`);
|
|
186
|
+
}
|
|
187
|
+
return lines.join('\n');
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Find open PRs associated with a Linear issue identifier
|
|
191
|
+
* Searches for PRs with the issue identifier in the branch name or title
|
|
192
|
+
*
|
|
193
|
+
* @param issueIdentifier - The Linear issue identifier (e.g., "SUP-123")
|
|
194
|
+
* @param options - Options for the search
|
|
195
|
+
* @returns Array of matching PRs
|
|
196
|
+
*/
|
|
197
|
+
export async function findPRsForIssue(issueIdentifier, options = {}) {
|
|
198
|
+
const { owner, repo, timeout } = { ...DEFAULT_OPTIONS, ...options };
|
|
199
|
+
try {
|
|
200
|
+
// Search for PRs with the issue identifier in branch name or title
|
|
201
|
+
const { stdout } = await execAsync(`gh pr list --repo ${owner}/${repo} --state open --json number,headRefName,headRefOid,title,url --search "${issueIdentifier}"`, { timeout });
|
|
202
|
+
const prs = JSON.parse(stdout || '[]');
|
|
203
|
+
// Filter to PRs that actually match the issue identifier
|
|
204
|
+
const lowerIdentifier = issueIdentifier.toLowerCase();
|
|
205
|
+
const matchingPRs = prs.filter((pr) => {
|
|
206
|
+
const branchMatch = pr.headRefName.toLowerCase().includes(lowerIdentifier);
|
|
207
|
+
const titleMatch = pr.title.toLowerCase().includes(lowerIdentifier);
|
|
208
|
+
return branchMatch || titleMatch;
|
|
209
|
+
});
|
|
210
|
+
return matchingPRs.map((pr) => ({
|
|
211
|
+
number: pr.number,
|
|
212
|
+
headSha: pr.headRefOid,
|
|
213
|
+
branch: pr.headRefName,
|
|
214
|
+
title: pr.title,
|
|
215
|
+
url: pr.url,
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Check deployment status for an issue by finding associated PRs
|
|
224
|
+
* Returns the first PR's deployment status, or null if no PRs found
|
|
225
|
+
*
|
|
226
|
+
* @param issueIdentifier - The Linear issue identifier (e.g., "SUP-123")
|
|
227
|
+
* @param options - Options for the search and check
|
|
228
|
+
* @returns Deployment check result with PR info, or null if no PR found
|
|
229
|
+
*/
|
|
230
|
+
export async function checkIssueDeploymentStatus(issueIdentifier, options = {}) {
|
|
231
|
+
const prs = await findPRsForIssue(issueIdentifier, options);
|
|
232
|
+
if (prs.length === 0) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
// Use the first matching PR (most recent PRs are returned first by gh)
|
|
236
|
+
const pr = prs[0];
|
|
237
|
+
const result = await checkDeploymentStatus(pr.headSha, options);
|
|
238
|
+
return {
|
|
239
|
+
...result,
|
|
240
|
+
pr,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { checkDeploymentStatus, checkPRDeploymentStatus, checkIssueDeploymentStatus, findPRsForIssue, getPRNumber, getPRHeadSha, formatDeploymentStatus, formatFailedDeployments, } from './deployment-checker.js';
|
|
2
|
+
export type { DeploymentStatus, DeploymentCheckResult, DeploymentCheckOptions, IssuePRInfo, } from './deployment-checker.js';
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/deployment/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,0BAA0B,EAC1B,eAAe,EACf,WAAW,EACX,YAAY,EACZ,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,yBAAyB,CAAA;AAEhC,YAAY,EACV,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,EACtB,WAAW,GACZ,MAAM,yBAAyB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/frontend/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './types.js';
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontend-agnostic types for work scheduling systems.
|
|
3
|
+
*
|
|
4
|
+
* These types define the contract between the orchestrator/governor
|
|
5
|
+
* and any work scheduling frontend (Linear, Asana, etc.).
|
|
6
|
+
* Each frontend provides an adapter implementing WorkSchedulingFrontend.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Abstract workflow statuses that map to frontend-specific names.
|
|
10
|
+
* Each frontend adapter maps these to its native equivalents.
|
|
11
|
+
*/
|
|
12
|
+
export type AbstractStatus = 'icebox' | 'backlog' | 'started' | 'finished' | 'delivered' | 'accepted' | 'rejected' | 'canceled';
|
|
13
|
+
/**
|
|
14
|
+
* Terminal statuses where no agent work is needed.
|
|
15
|
+
*/
|
|
16
|
+
export declare const TERMINAL_ABSTRACT_STATUSES: AbstractStatus[];
|
|
17
|
+
/**
|
|
18
|
+
* Minimal issue representation shared across frontends.
|
|
19
|
+
*/
|
|
20
|
+
export interface AbstractIssue {
|
|
21
|
+
id: string;
|
|
22
|
+
identifier: string;
|
|
23
|
+
title: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
url: string;
|
|
26
|
+
status: AbstractStatus;
|
|
27
|
+
priority: number;
|
|
28
|
+
labels: string[];
|
|
29
|
+
parentId?: string;
|
|
30
|
+
project?: string;
|
|
31
|
+
createdAt: Date;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Minimal comment representation.
|
|
35
|
+
*/
|
|
36
|
+
export interface AbstractComment {
|
|
37
|
+
id: string;
|
|
38
|
+
body: string;
|
|
39
|
+
userId?: string;
|
|
40
|
+
userName?: string;
|
|
41
|
+
createdAt: Date;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* External URL for agent sessions.
|
|
45
|
+
*/
|
|
46
|
+
export interface ExternalUrl {
|
|
47
|
+
label: string;
|
|
48
|
+
url: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Input for creating an issue.
|
|
52
|
+
*/
|
|
53
|
+
export interface CreateIssueInput {
|
|
54
|
+
title: string;
|
|
55
|
+
teamId?: string;
|
|
56
|
+
description?: string;
|
|
57
|
+
projectId?: string;
|
|
58
|
+
status?: AbstractStatus;
|
|
59
|
+
labels?: string[];
|
|
60
|
+
parentId?: string;
|
|
61
|
+
priority?: number;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Input for creating a blocker issue.
|
|
65
|
+
*/
|
|
66
|
+
export interface CreateBlockerInput {
|
|
67
|
+
title: string;
|
|
68
|
+
description?: string;
|
|
69
|
+
teamId?: string;
|
|
70
|
+
projectId?: string;
|
|
71
|
+
assignee?: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Input for updating an agent session.
|
|
75
|
+
*/
|
|
76
|
+
export interface UpdateSessionInput {
|
|
77
|
+
externalUrls?: ExternalUrl[];
|
|
78
|
+
plan?: Array<{
|
|
79
|
+
content: string;
|
|
80
|
+
status: 'pending' | 'inProgress' | 'completed' | 'canceled';
|
|
81
|
+
}>;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The frontend-agnostic interface for work scheduling systems.
|
|
85
|
+
* Each frontend (Linear, Asana, etc.) provides an adapter implementing this interface.
|
|
86
|
+
*/
|
|
87
|
+
export interface WorkSchedulingFrontend {
|
|
88
|
+
readonly name: string;
|
|
89
|
+
resolveStatus(abstract: AbstractStatus): string;
|
|
90
|
+
abstractStatus(nativeStatus: string): AbstractStatus;
|
|
91
|
+
getIssue(id: string): Promise<AbstractIssue>;
|
|
92
|
+
listIssuesByStatus(project: string, status: AbstractStatus): Promise<AbstractIssue[]>;
|
|
93
|
+
getUnblockedIssues(project: string, status: AbstractStatus): Promise<AbstractIssue[]>;
|
|
94
|
+
getIssueComments(id: string): Promise<AbstractComment[]>;
|
|
95
|
+
isParentIssue(id: string): Promise<boolean>;
|
|
96
|
+
isChildIssue(id: string): Promise<boolean>;
|
|
97
|
+
getSubIssues(id: string): Promise<AbstractIssue[]>;
|
|
98
|
+
transitionIssue(id: string, status: AbstractStatus): Promise<void>;
|
|
99
|
+
createComment(id: string, body: string): Promise<void>;
|
|
100
|
+
createIssue(data: CreateIssueInput): Promise<AbstractIssue>;
|
|
101
|
+
createBlockerIssue(sourceId: string, data: CreateBlockerInput): Promise<AbstractIssue>;
|
|
102
|
+
createAgentSession(issueId: string, externalUrls?: ExternalUrl[]): Promise<string>;
|
|
103
|
+
updateAgentSession(sessionId: string, data: UpdateSessionInput): Promise<void>;
|
|
104
|
+
createActivity(sessionId: string, type: string, content: string): Promise<void>;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/frontend/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontend-agnostic types for work scheduling systems.
|
|
3
|
+
*
|
|
4
|
+
* These types define the contract between the orchestrator/governor
|
|
5
|
+
* and any work scheduling frontend (Linear, Asana, etc.).
|
|
6
|
+
* Each frontend provides an adapter implementing WorkSchedulingFrontend.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Terminal statuses where no agent work is needed.
|
|
10
|
+
*/
|
|
11
|
+
export const TERMINAL_ABSTRACT_STATUSES = ['accepted', 'canceled'];
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decision Engine
|
|
3
|
+
*
|
|
4
|
+
* Pure function that determines what action the Governor should take for a
|
|
5
|
+
* given issue. No side effects, no I/O — just decision logic.
|
|
6
|
+
*
|
|
7
|
+
* The decision tree evaluates issues based on their current status, override
|
|
8
|
+
* state, active sessions, cooldowns, and configuration flags.
|
|
9
|
+
*/
|
|
10
|
+
import type { GovernorAction, GovernorConfig, GovernorIssue } from './governor-types.js';
|
|
11
|
+
/**
|
|
12
|
+
* All external state the Governor gathers before asking the decision engine
|
|
13
|
+
* what to do. Callers are responsible for populating this context; the
|
|
14
|
+
* decision engine itself never performs I/O.
|
|
15
|
+
*/
|
|
16
|
+
export interface DecisionContext {
|
|
17
|
+
issue: GovernorIssue;
|
|
18
|
+
config: GovernorConfig;
|
|
19
|
+
hasActiveSession: boolean;
|
|
20
|
+
isHeld: boolean;
|
|
21
|
+
isWithinCooldown: boolean;
|
|
22
|
+
isParentIssue: boolean;
|
|
23
|
+
workflowStrategy?: string;
|
|
24
|
+
researchCompleted: boolean;
|
|
25
|
+
backlogCreationCompleted: boolean;
|
|
26
|
+
/** Number of completed agent sessions for this issue (for circuit breaker) */
|
|
27
|
+
completedSessionCount: number;
|
|
28
|
+
}
|
|
29
|
+
/** Max agent sessions before the circuit breaker trips and the issue is held */
|
|
30
|
+
export declare const MAX_SESSION_ATTEMPTS = 3;
|
|
31
|
+
export interface DecisionResult {
|
|
32
|
+
action: GovernorAction;
|
|
33
|
+
reason: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Determine what action the Governor should take for a single issue.
|
|
37
|
+
*
|
|
38
|
+
* Decision rules (evaluated in order):
|
|
39
|
+
*
|
|
40
|
+
* 1. Skip if active session exists
|
|
41
|
+
* 2. Skip if within cooldown
|
|
42
|
+
* 3. Skip if HOLD override is active
|
|
43
|
+
* 4. Terminal status (Accepted, Canceled, Duplicate) -> none
|
|
44
|
+
* 5. Icebox -> delegate to top-of-funnel (research / backlog-creation)
|
|
45
|
+
* 6. Backlog -> trigger-development (if enabled)
|
|
46
|
+
* 7. Finished -> trigger-qa (if enabled; check escalation)
|
|
47
|
+
* 8. Delivered -> trigger-acceptance (if enabled)
|
|
48
|
+
* 9. Rejected -> trigger-refinement (check strategy for escalation)
|
|
49
|
+
* 10. Unknown status -> none
|
|
50
|
+
*/
|
|
51
|
+
export declare function decideAction(ctx: DecisionContext): DecisionResult;
|
|
52
|
+
//# sourceMappingURL=decision-engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decision-engine.d.ts","sourceRoot":"","sources":["../../../src/governor/decision-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAWxF;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,CAAA;IACpB,MAAM,EAAE,cAAc,CAAA;IACtB,gBAAgB,EAAE,OAAO,CAAA;IACzB,MAAM,EAAE,OAAO,CAAA;IACf,gBAAgB,EAAE,OAAO,CAAA;IACzB,aAAa,EAAE,OAAO,CAAA;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,wBAAwB,EAAE,OAAO,CAAA;IACjC,8EAA8E;IAC9E,qBAAqB,EAAE,MAAM,CAAA;CAC9B;AAED,gFAAgF;AAChF,eAAO,MAAM,oBAAoB,IAAI,CAAA;AAMrC,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,cAAc,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;CACf;AAaD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,cAAc,CAsEjE"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decision Engine
|
|
3
|
+
*
|
|
4
|
+
* Pure function that determines what action the Governor should take for a
|
|
5
|
+
* given issue. No side effects, no I/O — just decision logic.
|
|
6
|
+
*
|
|
7
|
+
* The decision tree evaluates issues based on their current status, override
|
|
8
|
+
* state, active sessions, cooldowns, and configuration flags.
|
|
9
|
+
*/
|
|
10
|
+
import { determineTopOfFunnelAction, DEFAULT_TOP_OF_FUNNEL_CONFIG, } from './top-of-funnel.js';
|
|
11
|
+
/** Max agent sessions before the circuit breaker trips and the issue is held */
|
|
12
|
+
export const MAX_SESSION_ATTEMPTS = 3;
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Terminal statuses
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
/** Statuses where no further agent work is required */
|
|
17
|
+
const TERMINAL_STATUSES = new Set(['Accepted', 'Canceled', 'Duplicate']);
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Decision Function
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
/**
|
|
22
|
+
* Determine what action the Governor should take for a single issue.
|
|
23
|
+
*
|
|
24
|
+
* Decision rules (evaluated in order):
|
|
25
|
+
*
|
|
26
|
+
* 1. Skip if active session exists
|
|
27
|
+
* 2. Skip if within cooldown
|
|
28
|
+
* 3. Skip if HOLD override is active
|
|
29
|
+
* 4. Terminal status (Accepted, Canceled, Duplicate) -> none
|
|
30
|
+
* 5. Icebox -> delegate to top-of-funnel (research / backlog-creation)
|
|
31
|
+
* 6. Backlog -> trigger-development (if enabled)
|
|
32
|
+
* 7. Finished -> trigger-qa (if enabled; check escalation)
|
|
33
|
+
* 8. Delivered -> trigger-acceptance (if enabled)
|
|
34
|
+
* 9. Rejected -> trigger-refinement (check strategy for escalation)
|
|
35
|
+
* 10. Unknown status -> none
|
|
36
|
+
*/
|
|
37
|
+
export function decideAction(ctx) {
|
|
38
|
+
const { issue, config } = ctx;
|
|
39
|
+
// --- Universal skip conditions ---
|
|
40
|
+
if (ctx.hasActiveSession) {
|
|
41
|
+
return { action: 'none', reason: `Issue ${issue.identifier} already has an active agent session` };
|
|
42
|
+
}
|
|
43
|
+
if (ctx.isWithinCooldown) {
|
|
44
|
+
return { action: 'none', reason: `Issue ${issue.identifier} is within cooldown period` };
|
|
45
|
+
}
|
|
46
|
+
if (ctx.isHeld) {
|
|
47
|
+
return { action: 'none', reason: `Issue ${issue.identifier} is held (HOLD override active)` };
|
|
48
|
+
}
|
|
49
|
+
// --- Circuit breaker ---
|
|
50
|
+
// Prevent issues from cycling through agents indefinitely.
|
|
51
|
+
// If an issue has had too many sessions without reaching a terminal status,
|
|
52
|
+
// stop dispatching and require manual intervention.
|
|
53
|
+
if (ctx.completedSessionCount >= MAX_SESSION_ATTEMPTS) {
|
|
54
|
+
return {
|
|
55
|
+
action: 'none',
|
|
56
|
+
reason: `Issue ${issue.identifier} has had ${ctx.completedSessionCount} agent sessions without progressing — circuit breaker tripped (max ${MAX_SESSION_ATTEMPTS})`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// --- Terminal statuses ---
|
|
60
|
+
if (TERMINAL_STATUSES.has(issue.status)) {
|
|
61
|
+
return { action: 'none', reason: `Issue ${issue.identifier} is in terminal status: ${issue.status}` };
|
|
62
|
+
}
|
|
63
|
+
// --- Sub-issue guard ---
|
|
64
|
+
// Sub-issues are managed exclusively by the coordinator (or qa-coordinator /
|
|
65
|
+
// acceptance-coordinator) via the parent issue. The governor must never
|
|
66
|
+
// dispatch workflows on sub-issues directly, regardless of their status,
|
|
67
|
+
// to prevent duplicate work.
|
|
68
|
+
if (issue.parentId !== undefined) {
|
|
69
|
+
return {
|
|
70
|
+
action: 'none',
|
|
71
|
+
reason: `Sub-issue ${issue.identifier} skipped — coordinator manages sub-issues via parent`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// --- Status-specific decisions ---
|
|
75
|
+
switch (issue.status) {
|
|
76
|
+
case 'Icebox':
|
|
77
|
+
return decideIcebox(ctx);
|
|
78
|
+
case 'Backlog':
|
|
79
|
+
return decideBacklog(ctx);
|
|
80
|
+
case 'Started':
|
|
81
|
+
return { action: 'none', reason: `Issue ${issue.identifier} is in Started status (agent already working)` };
|
|
82
|
+
case 'Finished':
|
|
83
|
+
return decideFinished(ctx);
|
|
84
|
+
case 'Delivered':
|
|
85
|
+
return decideDelivered(ctx);
|
|
86
|
+
case 'Rejected':
|
|
87
|
+
return decideRejected(ctx);
|
|
88
|
+
default:
|
|
89
|
+
return { action: 'none', reason: `Issue ${issue.identifier} has unrecognized status: ${issue.status}` };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Per-status decision helpers
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
/**
|
|
96
|
+
* Handle Icebox issues by delegating to the top-of-funnel logic.
|
|
97
|
+
*/
|
|
98
|
+
function decideIcebox(ctx) {
|
|
99
|
+
const { issue, config } = ctx;
|
|
100
|
+
// Build top-of-funnel config by merging defaults with any overrides
|
|
101
|
+
const tofConfig = {
|
|
102
|
+
...DEFAULT_TOP_OF_FUNNEL_CONFIG,
|
|
103
|
+
enableAutoResearch: config.enableAutoResearch,
|
|
104
|
+
enableAutoBacklogCreation: config.enableAutoBacklogCreation,
|
|
105
|
+
...(config.topOfFunnel ?? {}),
|
|
106
|
+
};
|
|
107
|
+
const tofAction = determineTopOfFunnelAction({
|
|
108
|
+
id: issue.id,
|
|
109
|
+
identifier: issue.identifier,
|
|
110
|
+
title: issue.title,
|
|
111
|
+
description: issue.description,
|
|
112
|
+
status: issue.status,
|
|
113
|
+
labels: issue.labels,
|
|
114
|
+
createdAt: issue.createdAt,
|
|
115
|
+
parentId: issue.parentId,
|
|
116
|
+
}, tofConfig, {
|
|
117
|
+
hasActiveSession: ctx.hasActiveSession,
|
|
118
|
+
isHeld: ctx.isHeld,
|
|
119
|
+
researchCompleted: ctx.researchCompleted,
|
|
120
|
+
backlogCreationCompleted: ctx.backlogCreationCompleted,
|
|
121
|
+
isParentIssue: ctx.isParentIssue,
|
|
122
|
+
});
|
|
123
|
+
switch (tofAction.type) {
|
|
124
|
+
case 'trigger-research':
|
|
125
|
+
return { action: 'trigger-research', reason: tofAction.reason };
|
|
126
|
+
case 'trigger-backlog-creation':
|
|
127
|
+
return { action: 'trigger-backlog-creation', reason: tofAction.reason };
|
|
128
|
+
case 'none':
|
|
129
|
+
return { action: 'none', reason: tofAction.reason };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Handle Backlog issues — trigger development if enabled.
|
|
134
|
+
* Parent issues use the coordination template.
|
|
135
|
+
* Sub-issues are skipped — only top-level/parent issues are dispatched directly.
|
|
136
|
+
* The coordinator handles sub-issue lifecycle once the parent is being worked.
|
|
137
|
+
*/
|
|
138
|
+
function decideBacklog(ctx) {
|
|
139
|
+
const { issue, config } = ctx;
|
|
140
|
+
if (!config.enableAutoDevelopment) {
|
|
141
|
+
return { action: 'none', reason: `Auto-development is disabled for ${issue.identifier}` };
|
|
142
|
+
}
|
|
143
|
+
// Parent issues use the coordination template for sub-issue orchestration.
|
|
144
|
+
if (ctx.isParentIssue) {
|
|
145
|
+
return {
|
|
146
|
+
action: 'trigger-development',
|
|
147
|
+
reason: `Parent issue ${issue.identifier} is in Backlog — triggering coordination development`,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
action: 'trigger-development',
|
|
152
|
+
reason: `Issue ${issue.identifier} is in Backlog — triggering development`,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Handle Finished issues — trigger QA if enabled.
|
|
157
|
+
* Check workflow strategy for escalation scenarios.
|
|
158
|
+
*/
|
|
159
|
+
function decideFinished(ctx) {
|
|
160
|
+
const { issue, config } = ctx;
|
|
161
|
+
if (!config.enableAutoQA) {
|
|
162
|
+
return { action: 'none', reason: `Auto-QA is disabled for ${issue.identifier}` };
|
|
163
|
+
}
|
|
164
|
+
// If strategy is escalate-human, the issue needs human attention
|
|
165
|
+
if (ctx.workflowStrategy === 'escalate-human') {
|
|
166
|
+
return {
|
|
167
|
+
action: 'escalate-human',
|
|
168
|
+
reason: `Issue ${issue.identifier} is in Finished with escalate-human strategy — needs human review`,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
// If strategy is decompose, trigger decomposition instead of QA
|
|
172
|
+
if (ctx.workflowStrategy === 'decompose') {
|
|
173
|
+
return {
|
|
174
|
+
action: 'decompose',
|
|
175
|
+
reason: `Issue ${issue.identifier} is in Finished with decompose strategy — triggering decomposition`,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
action: 'trigger-qa',
|
|
180
|
+
reason: `Issue ${issue.identifier} is in Finished — triggering QA`,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Handle Delivered issues — trigger acceptance if enabled.
|
|
185
|
+
*/
|
|
186
|
+
function decideDelivered(ctx) {
|
|
187
|
+
const { issue, config } = ctx;
|
|
188
|
+
if (!config.enableAutoAcceptance) {
|
|
189
|
+
return { action: 'none', reason: `Auto-acceptance is disabled for ${issue.identifier}` };
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
action: 'trigger-acceptance',
|
|
193
|
+
reason: `Issue ${issue.identifier} is in Delivered — triggering acceptance`,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Handle Rejected issues — trigger refinement.
|
|
198
|
+
* Check strategy for escalation thresholds.
|
|
199
|
+
*/
|
|
200
|
+
function decideRejected(ctx) {
|
|
201
|
+
const { issue } = ctx;
|
|
202
|
+
// If strategy is escalate-human, the issue needs human attention
|
|
203
|
+
if (ctx.workflowStrategy === 'escalate-human') {
|
|
204
|
+
return {
|
|
205
|
+
action: 'escalate-human',
|
|
206
|
+
reason: `Issue ${issue.identifier} is Rejected with escalate-human strategy — needs human intervention`,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
// If strategy is decompose, trigger decomposition instead of refinement
|
|
210
|
+
if (ctx.workflowStrategy === 'decompose') {
|
|
211
|
+
return {
|
|
212
|
+
action: 'decompose',
|
|
213
|
+
reason: `Issue ${issue.identifier} is Rejected with decompose strategy — triggering decomposition`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
action: 'trigger-refinement',
|
|
218
|
+
reason: `Issue ${issue.identifier} is Rejected — triggering refinement`,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decision-engine.test.d.ts","sourceRoot":"","sources":["../../../src/governor/decision-engine.test.ts"],"names":[],"mappings":""}
|