@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.
Files changed (246) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +125 -0
  3. package/dist/src/config/index.d.ts +3 -0
  4. package/dist/src/config/index.d.ts.map +1 -0
  5. package/dist/src/config/index.js +1 -0
  6. package/dist/src/config/repository-config.d.ts +44 -0
  7. package/dist/src/config/repository-config.d.ts.map +1 -0
  8. package/dist/src/config/repository-config.js +88 -0
  9. package/dist/src/config/repository-config.test.d.ts +2 -0
  10. package/dist/src/config/repository-config.test.d.ts.map +1 -0
  11. package/dist/src/config/repository-config.test.js +249 -0
  12. package/dist/src/deployment/deployment-checker.d.ts +110 -0
  13. package/dist/src/deployment/deployment-checker.d.ts.map +1 -0
  14. package/dist/src/deployment/deployment-checker.js +242 -0
  15. package/dist/src/deployment/index.d.ts +3 -0
  16. package/dist/src/deployment/index.d.ts.map +1 -0
  17. package/dist/src/deployment/index.js +2 -0
  18. package/dist/src/frontend/index.d.ts +2 -0
  19. package/dist/src/frontend/index.d.ts.map +1 -0
  20. package/dist/src/frontend/index.js +1 -0
  21. package/dist/src/frontend/types.d.ts +106 -0
  22. package/dist/src/frontend/types.d.ts.map +1 -0
  23. package/dist/src/frontend/types.js +11 -0
  24. package/dist/src/governor/decision-engine.d.ts +52 -0
  25. package/dist/src/governor/decision-engine.d.ts.map +1 -0
  26. package/dist/src/governor/decision-engine.js +220 -0
  27. package/dist/src/governor/decision-engine.test.d.ts +2 -0
  28. package/dist/src/governor/decision-engine.test.d.ts.map +1 -0
  29. package/dist/src/governor/decision-engine.test.js +629 -0
  30. package/dist/src/governor/event-bus.d.ts +43 -0
  31. package/dist/src/governor/event-bus.d.ts.map +1 -0
  32. package/dist/src/governor/event-bus.js +8 -0
  33. package/dist/src/governor/event-deduplicator.d.ts +43 -0
  34. package/dist/src/governor/event-deduplicator.d.ts.map +1 -0
  35. package/dist/src/governor/event-deduplicator.js +53 -0
  36. package/dist/src/governor/event-driven-governor.d.ts +131 -0
  37. package/dist/src/governor/event-driven-governor.d.ts.map +1 -0
  38. package/dist/src/governor/event-driven-governor.js +379 -0
  39. package/dist/src/governor/event-driven-governor.test.d.ts +2 -0
  40. package/dist/src/governor/event-driven-governor.test.d.ts.map +1 -0
  41. package/dist/src/governor/event-driven-governor.test.js +673 -0
  42. package/dist/src/governor/event-types.d.ts +78 -0
  43. package/dist/src/governor/event-types.d.ts.map +1 -0
  44. package/dist/src/governor/event-types.js +32 -0
  45. package/dist/src/governor/governor-types.d.ts +82 -0
  46. package/dist/src/governor/governor-types.d.ts.map +1 -0
  47. package/dist/src/governor/governor-types.js +21 -0
  48. package/dist/src/governor/governor.d.ts +100 -0
  49. package/dist/src/governor/governor.d.ts.map +1 -0
  50. package/dist/src/governor/governor.js +262 -0
  51. package/dist/src/governor/governor.test.d.ts +2 -0
  52. package/dist/src/governor/governor.test.d.ts.map +1 -0
  53. package/dist/src/governor/governor.test.js +514 -0
  54. package/dist/src/governor/human-touchpoints.d.ts +131 -0
  55. package/dist/src/governor/human-touchpoints.d.ts.map +1 -0
  56. package/dist/src/governor/human-touchpoints.js +251 -0
  57. package/dist/src/governor/human-touchpoints.test.d.ts +2 -0
  58. package/dist/src/governor/human-touchpoints.test.d.ts.map +1 -0
  59. package/dist/src/governor/human-touchpoints.test.js +366 -0
  60. package/dist/src/governor/in-memory-event-bus.d.ts +29 -0
  61. package/dist/src/governor/in-memory-event-bus.d.ts.map +1 -0
  62. package/dist/src/governor/in-memory-event-bus.js +79 -0
  63. package/dist/src/governor/index.d.ts +14 -0
  64. package/dist/src/governor/index.d.ts.map +1 -0
  65. package/dist/src/governor/index.js +13 -0
  66. package/dist/src/governor/override-parser.d.ts +60 -0
  67. package/dist/src/governor/override-parser.d.ts.map +1 -0
  68. package/dist/src/governor/override-parser.js +98 -0
  69. package/dist/src/governor/override-parser.test.d.ts +2 -0
  70. package/dist/src/governor/override-parser.test.d.ts.map +1 -0
  71. package/dist/src/governor/override-parser.test.js +312 -0
  72. package/dist/src/governor/platform-adapter.d.ts +69 -0
  73. package/dist/src/governor/platform-adapter.d.ts.map +1 -0
  74. package/dist/src/governor/platform-adapter.js +11 -0
  75. package/dist/src/governor/processing-state.d.ts +66 -0
  76. package/dist/src/governor/processing-state.d.ts.map +1 -0
  77. package/dist/src/governor/processing-state.js +43 -0
  78. package/dist/src/governor/processing-state.test.d.ts +2 -0
  79. package/dist/src/governor/processing-state.test.d.ts.map +1 -0
  80. package/dist/src/governor/processing-state.test.js +96 -0
  81. package/dist/src/governor/top-of-funnel.d.ts +118 -0
  82. package/dist/src/governor/top-of-funnel.d.ts.map +1 -0
  83. package/dist/src/governor/top-of-funnel.js +168 -0
  84. package/dist/src/governor/top-of-funnel.test.d.ts +2 -0
  85. package/dist/src/governor/top-of-funnel.test.d.ts.map +1 -0
  86. package/dist/src/governor/top-of-funnel.test.js +331 -0
  87. package/dist/src/index.d.ts +11 -0
  88. package/dist/src/index.d.ts.map +1 -0
  89. package/dist/src/index.js +10 -0
  90. package/dist/src/linear-cli.d.ts +38 -0
  91. package/dist/src/linear-cli.d.ts.map +1 -0
  92. package/dist/src/linear-cli.js +674 -0
  93. package/dist/src/logger.d.ts +117 -0
  94. package/dist/src/logger.d.ts.map +1 -0
  95. package/dist/src/logger.js +430 -0
  96. package/dist/src/manifest/generate.d.ts +20 -0
  97. package/dist/src/manifest/generate.d.ts.map +1 -0
  98. package/dist/src/manifest/generate.js +65 -0
  99. package/dist/src/manifest/index.d.ts +4 -0
  100. package/dist/src/manifest/index.d.ts.map +1 -0
  101. package/dist/src/manifest/index.js +2 -0
  102. package/dist/src/manifest/route-manifest.d.ts +34 -0
  103. package/dist/src/manifest/route-manifest.d.ts.map +1 -0
  104. package/dist/src/manifest/route-manifest.js +148 -0
  105. package/dist/src/orchestrator/activity-emitter.d.ts +119 -0
  106. package/dist/src/orchestrator/activity-emitter.d.ts.map +1 -0
  107. package/dist/src/orchestrator/activity-emitter.js +306 -0
  108. package/dist/src/orchestrator/api-activity-emitter.d.ts +167 -0
  109. package/dist/src/orchestrator/api-activity-emitter.d.ts.map +1 -0
  110. package/dist/src/orchestrator/api-activity-emitter.js +417 -0
  111. package/dist/src/orchestrator/heartbeat-writer.d.ts +57 -0
  112. package/dist/src/orchestrator/heartbeat-writer.d.ts.map +1 -0
  113. package/dist/src/orchestrator/heartbeat-writer.js +137 -0
  114. package/dist/src/orchestrator/index.d.ts +20 -0
  115. package/dist/src/orchestrator/index.d.ts.map +1 -0
  116. package/dist/src/orchestrator/index.js +22 -0
  117. package/dist/src/orchestrator/log-analyzer.d.ts +160 -0
  118. package/dist/src/orchestrator/log-analyzer.d.ts.map +1 -0
  119. package/dist/src/orchestrator/log-analyzer.js +572 -0
  120. package/dist/src/orchestrator/log-config.d.ts +39 -0
  121. package/dist/src/orchestrator/log-config.d.ts.map +1 -0
  122. package/dist/src/orchestrator/log-config.js +45 -0
  123. package/dist/src/orchestrator/orchestrator.d.ts +316 -0
  124. package/dist/src/orchestrator/orchestrator.d.ts.map +1 -0
  125. package/dist/src/orchestrator/orchestrator.js +3290 -0
  126. package/dist/src/orchestrator/parse-work-result.d.ts +16 -0
  127. package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -0
  128. package/dist/src/orchestrator/parse-work-result.js +135 -0
  129. package/dist/src/orchestrator/parse-work-result.test.d.ts +2 -0
  130. package/dist/src/orchestrator/parse-work-result.test.d.ts.map +1 -0
  131. package/dist/src/orchestrator/parse-work-result.test.js +234 -0
  132. package/dist/src/orchestrator/progress-logger.d.ts +72 -0
  133. package/dist/src/orchestrator/progress-logger.d.ts.map +1 -0
  134. package/dist/src/orchestrator/progress-logger.js +135 -0
  135. package/dist/src/orchestrator/session-logger.d.ts +159 -0
  136. package/dist/src/orchestrator/session-logger.d.ts.map +1 -0
  137. package/dist/src/orchestrator/session-logger.js +275 -0
  138. package/dist/src/orchestrator/state-recovery.d.ts +96 -0
  139. package/dist/src/orchestrator/state-recovery.d.ts.map +1 -0
  140. package/dist/src/orchestrator/state-recovery.js +302 -0
  141. package/dist/src/orchestrator/state-types.d.ts +165 -0
  142. package/dist/src/orchestrator/state-types.d.ts.map +1 -0
  143. package/dist/src/orchestrator/state-types.js +7 -0
  144. package/dist/src/orchestrator/stream-parser.d.ts +151 -0
  145. package/dist/src/orchestrator/stream-parser.d.ts.map +1 -0
  146. package/dist/src/orchestrator/stream-parser.js +137 -0
  147. package/dist/src/orchestrator/types.d.ts +232 -0
  148. package/dist/src/orchestrator/types.d.ts.map +1 -0
  149. package/dist/src/orchestrator/types.js +4 -0
  150. package/dist/src/orchestrator/validate-git-remote.test.d.ts +2 -0
  151. package/dist/src/orchestrator/validate-git-remote.test.d.ts.map +1 -0
  152. package/dist/src/orchestrator/validate-git-remote.test.js +61 -0
  153. package/dist/src/providers/a2a-auth.d.ts +81 -0
  154. package/dist/src/providers/a2a-auth.d.ts.map +1 -0
  155. package/dist/src/providers/a2a-auth.js +188 -0
  156. package/dist/src/providers/a2a-auth.test.d.ts +2 -0
  157. package/dist/src/providers/a2a-auth.test.d.ts.map +1 -0
  158. package/dist/src/providers/a2a-auth.test.js +232 -0
  159. package/dist/src/providers/a2a-provider.d.ts +254 -0
  160. package/dist/src/providers/a2a-provider.d.ts.map +1 -0
  161. package/dist/src/providers/a2a-provider.integration.test.d.ts +9 -0
  162. package/dist/src/providers/a2a-provider.integration.test.d.ts.map +1 -0
  163. package/dist/src/providers/a2a-provider.integration.test.js +665 -0
  164. package/dist/src/providers/a2a-provider.js +811 -0
  165. package/dist/src/providers/a2a-provider.test.d.ts +2 -0
  166. package/dist/src/providers/a2a-provider.test.d.ts.map +1 -0
  167. package/dist/src/providers/a2a-provider.test.js +681 -0
  168. package/dist/src/providers/amp-provider.d.ts +20 -0
  169. package/dist/src/providers/amp-provider.d.ts.map +1 -0
  170. package/dist/src/providers/amp-provider.js +24 -0
  171. package/dist/src/providers/claude-provider.d.ts +18 -0
  172. package/dist/src/providers/claude-provider.d.ts.map +1 -0
  173. package/dist/src/providers/claude-provider.js +437 -0
  174. package/dist/src/providers/codex-provider.d.ts +133 -0
  175. package/dist/src/providers/codex-provider.d.ts.map +1 -0
  176. package/dist/src/providers/codex-provider.js +381 -0
  177. package/dist/src/providers/codex-provider.test.d.ts +2 -0
  178. package/dist/src/providers/codex-provider.test.d.ts.map +1 -0
  179. package/dist/src/providers/codex-provider.test.js +387 -0
  180. package/dist/src/providers/index.d.ts +44 -0
  181. package/dist/src/providers/index.d.ts.map +1 -0
  182. package/dist/src/providers/index.js +85 -0
  183. package/dist/src/providers/spring-ai-provider.d.ts +90 -0
  184. package/dist/src/providers/spring-ai-provider.d.ts.map +1 -0
  185. package/dist/src/providers/spring-ai-provider.integration.test.d.ts +13 -0
  186. package/dist/src/providers/spring-ai-provider.integration.test.d.ts.map +1 -0
  187. package/dist/src/providers/spring-ai-provider.integration.test.js +351 -0
  188. package/dist/src/providers/spring-ai-provider.js +317 -0
  189. package/dist/src/providers/spring-ai-provider.test.d.ts +2 -0
  190. package/dist/src/providers/spring-ai-provider.test.d.ts.map +1 -0
  191. package/dist/src/providers/spring-ai-provider.test.js +200 -0
  192. package/dist/src/providers/types.d.ts +165 -0
  193. package/dist/src/providers/types.d.ts.map +1 -0
  194. package/dist/src/providers/types.js +13 -0
  195. package/dist/src/templates/adapters.d.ts +51 -0
  196. package/dist/src/templates/adapters.d.ts.map +1 -0
  197. package/dist/src/templates/adapters.js +104 -0
  198. package/dist/src/templates/adapters.test.d.ts +2 -0
  199. package/dist/src/templates/adapters.test.d.ts.map +1 -0
  200. package/dist/src/templates/adapters.test.js +165 -0
  201. package/dist/src/templates/agent-definition.d.ts +85 -0
  202. package/dist/src/templates/agent-definition.d.ts.map +1 -0
  203. package/dist/src/templates/agent-definition.js +97 -0
  204. package/dist/src/templates/agent-definition.test.d.ts +2 -0
  205. package/dist/src/templates/agent-definition.test.d.ts.map +1 -0
  206. package/dist/src/templates/agent-definition.test.js +209 -0
  207. package/dist/src/templates/index.d.ts +14 -0
  208. package/dist/src/templates/index.d.ts.map +1 -0
  209. package/dist/src/templates/index.js +11 -0
  210. package/dist/src/templates/loader.d.ts +41 -0
  211. package/dist/src/templates/loader.d.ts.map +1 -0
  212. package/dist/src/templates/loader.js +114 -0
  213. package/dist/src/templates/registry.d.ts +80 -0
  214. package/dist/src/templates/registry.d.ts.map +1 -0
  215. package/dist/src/templates/registry.js +177 -0
  216. package/dist/src/templates/registry.test.d.ts +2 -0
  217. package/dist/src/templates/registry.test.d.ts.map +1 -0
  218. package/dist/src/templates/registry.test.js +198 -0
  219. package/dist/src/templates/renderer.d.ts +29 -0
  220. package/dist/src/templates/renderer.d.ts.map +1 -0
  221. package/dist/src/templates/renderer.js +35 -0
  222. package/dist/src/templates/strategy-templates.test.d.ts +2 -0
  223. package/dist/src/templates/strategy-templates.test.d.ts.map +1 -0
  224. package/dist/src/templates/strategy-templates.test.js +619 -0
  225. package/dist/src/templates/types.d.ts +233 -0
  226. package/dist/src/templates/types.d.ts.map +1 -0
  227. package/dist/src/templates/types.js +127 -0
  228. package/dist/src/templates/types.test.d.ts +2 -0
  229. package/dist/src/templates/types.test.d.ts.map +1 -0
  230. package/dist/src/templates/types.test.js +232 -0
  231. package/dist/src/tools/index.d.ts +6 -0
  232. package/dist/src/tools/index.d.ts.map +1 -0
  233. package/dist/src/tools/index.js +3 -0
  234. package/dist/src/tools/linear-runner.d.ts +34 -0
  235. package/dist/src/tools/linear-runner.d.ts.map +1 -0
  236. package/dist/src/tools/linear-runner.js +700 -0
  237. package/dist/src/tools/plugins/linear.d.ts +9 -0
  238. package/dist/src/tools/plugins/linear.d.ts.map +1 -0
  239. package/dist/src/tools/plugins/linear.js +138 -0
  240. package/dist/src/tools/registry.d.ts +9 -0
  241. package/dist/src/tools/registry.d.ts.map +1 -0
  242. package/dist/src/tools/registry.js +18 -0
  243. package/dist/src/tools/types.d.ts +18 -0
  244. package/dist/src/tools/types.d.ts.map +1 -0
  245. package/dist/src/tools/types.js +1 -0
  246. 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,2 @@
1
+ // Deployment status checker
2
+ export { checkDeploymentStatus, checkPRDeploymentStatus, checkIssueDeploymentStatus, findPRsForIssue, getPRNumber, getPRHeadSha, formatDeploymentStatus, formatFailedDeployments, } from './deployment-checker.js';
@@ -0,0 +1,2 @@
1
+ export * from './types.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -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":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB,QAAQ,GACR,SAAS,GACT,SAAS,GACT,UAAU,GACV,WAAW,GACX,UAAU,GACV,UAAU,GACV,UAAU,CAAA;AAEd;;GAEG;AACH,eAAO,MAAM,0BAA0B,EAAE,cAAc,EAA6B,CAAA;AAEpF;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,cAAc,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,IAAI,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,IAAI,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;CACZ;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,YAAY,CAAC,EAAE,WAAW,EAAE,CAAA;IAC5B,IAAI,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,SAAS,GAAG,YAAY,GAAG,WAAW,GAAG,UAAU,CAAA;KAAE,CAAC,CAAA;CAC/F;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAGrB,aAAa,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,CAAA;IAC/C,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,cAAc,CAAA;IAGpD,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;IAC5C,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAA;IACrF,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAA;IACrF,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAAA;IACxD,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC3C,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC1C,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAA;IAGlD,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClE,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtD,WAAW,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;IAC3D,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;IAGtF,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAClF,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9E,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAChF"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=decision-engine.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-engine.test.d.ts","sourceRoot":"","sources":["../../../src/governor/decision-engine.test.ts"],"names":[],"mappings":""}