@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,331 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { needsResearch, isReadyForBacklogCreation, isWellResearched, determineTopOfFunnelAction, DEFAULT_TOP_OF_FUNNEL_CONFIG, } from './top-of-funnel.js';
3
+ // ---------------------------------------------------------------------------
4
+ // Helpers
5
+ // ---------------------------------------------------------------------------
6
+ /** Create a minimal IssueInfo with sensible defaults. */
7
+ function makeIssue(overrides = {}) {
8
+ return {
9
+ id: 'issue-1',
10
+ identifier: 'SUP-100',
11
+ title: 'Test Issue',
12
+ description: undefined,
13
+ status: 'Icebox',
14
+ labels: [],
15
+ createdAt: Date.now() - 2 * 60 * 60 * 1000, // 2 hours ago (past delay)
16
+ ...overrides,
17
+ };
18
+ }
19
+ /** Build a well-researched description (long + structured header). */
20
+ function wellResearchedDescription() {
21
+ return ('## Acceptance Criteria\n' +
22
+ '- The system should handle X\n' +
23
+ '- The system should handle Y\n' +
24
+ '\n' +
25
+ '## Technical Approach\n' +
26
+ 'We will implement this by modifying the governor module to add top-of-funnel ' +
27
+ 'triggers that automatically research issues in the Icebox. This approach ' +
28
+ 'ensures issues are enriched before entering the active backlog.');
29
+ }
30
+ /** Build a sparse description (too short / no headers). */
31
+ function sparseDescription() {
32
+ return 'Fix the thing.';
33
+ }
34
+ /** Default context where nothing blocks the action. */
35
+ function defaultContext(overrides = {}) {
36
+ return {
37
+ hasActiveSession: false,
38
+ isHeld: false,
39
+ researchCompleted: false,
40
+ backlogCreationCompleted: false,
41
+ isParentIssue: false,
42
+ ...overrides,
43
+ };
44
+ }
45
+ // ---------------------------------------------------------------------------
46
+ // isWellResearched
47
+ // ---------------------------------------------------------------------------
48
+ describe('isWellResearched', () => {
49
+ it('returns false for undefined description', () => {
50
+ expect(isWellResearched(undefined)).toBe(false);
51
+ });
52
+ it('returns false for empty description', () => {
53
+ expect(isWellResearched('')).toBe(false);
54
+ });
55
+ it('returns false for short description even with a header', () => {
56
+ expect(isWellResearched('## Acceptance Criteria\nDone.')).toBe(false);
57
+ });
58
+ it('returns false for long description without structured headers', () => {
59
+ const longNoHeaders = 'A'.repeat(300);
60
+ expect(isWellResearched(longNoHeaders)).toBe(false);
61
+ });
62
+ it('returns true for description meeting both length and header criteria', () => {
63
+ expect(isWellResearched(wellResearchedDescription())).toBe(true);
64
+ });
65
+ it('recognises all configured headers', () => {
66
+ for (const header of DEFAULT_TOP_OF_FUNNEL_CONFIG.researchedHeaders) {
67
+ const desc = `${header}\n${'x'.repeat(250)}`;
68
+ expect(isWellResearched(desc)).toBe(true);
69
+ }
70
+ });
71
+ it('respects custom config with different length threshold', () => {
72
+ const config = {
73
+ ...DEFAULT_TOP_OF_FUNNEL_CONFIG,
74
+ minResearchedDescriptionLength: 10,
75
+ };
76
+ // Short but has a header and meets reduced threshold
77
+ expect(isWellResearched('## Summary\nShort text', config)).toBe(true);
78
+ });
79
+ it('respects custom config with different headers', () => {
80
+ const config = {
81
+ ...DEFAULT_TOP_OF_FUNNEL_CONFIG,
82
+ researchedHeaders: ['## Custom Header'],
83
+ };
84
+ const desc = `## Custom Header\n${'x'.repeat(250)}`;
85
+ expect(isWellResearched(desc, config)).toBe(true);
86
+ // Default headers should no longer match
87
+ expect(isWellResearched(wellResearchedDescription(), config)).toBe(false);
88
+ });
89
+ });
90
+ // ---------------------------------------------------------------------------
91
+ // needsResearch
92
+ // ---------------------------------------------------------------------------
93
+ describe('needsResearch', () => {
94
+ beforeEach(() => {
95
+ vi.useFakeTimers();
96
+ vi.setSystemTime(new Date('2025-06-01T12:00:00Z'));
97
+ });
98
+ afterEach(() => {
99
+ vi.useRealTimers();
100
+ });
101
+ it('returns false if issue is not in Icebox', () => {
102
+ const issue = makeIssue({ status: 'Backlog' });
103
+ expect(needsResearch(issue)).toBe(false);
104
+ });
105
+ it('returns false if issue has not been in Icebox long enough', () => {
106
+ // Created 30 minutes ago — default delay is 1 hour
107
+ const issue = makeIssue({ createdAt: Date.now() - 30 * 60 * 1000 });
108
+ expect(needsResearch(issue)).toBe(false);
109
+ });
110
+ it('returns true for old Icebox issue with sparse description', () => {
111
+ const issue = makeIssue({ description: sparseDescription() });
112
+ expect(needsResearch(issue)).toBe(true);
113
+ });
114
+ it('returns true for old Icebox issue with no description', () => {
115
+ const issue = makeIssue({ description: undefined });
116
+ expect(needsResearch(issue)).toBe(true);
117
+ });
118
+ it('returns false for old Icebox issue with well-researched description', () => {
119
+ const issue = makeIssue({ description: wellResearchedDescription() });
120
+ expect(needsResearch(issue)).toBe(false);
121
+ });
122
+ it('returns true when issue has "Needs Research" label even with well-researched description', () => {
123
+ const issue = makeIssue({
124
+ description: wellResearchedDescription(),
125
+ labels: ['Needs Research'],
126
+ });
127
+ expect(needsResearch(issue)).toBe(true);
128
+ });
129
+ it('returns false for parent issues (they use coordination)', () => {
130
+ const issue = makeIssue({ parentId: 'parent-1', description: sparseDescription() });
131
+ expect(needsResearch(issue)).toBe(false);
132
+ });
133
+ it('respects custom delay threshold', () => {
134
+ const config = {
135
+ ...DEFAULT_TOP_OF_FUNNEL_CONFIG,
136
+ iceboxResearchDelayMs: 10 * 60 * 1000, // 10 minutes
137
+ };
138
+ // Created 15 minutes ago — should pass reduced threshold
139
+ const issue = makeIssue({
140
+ createdAt: Date.now() - 15 * 60 * 1000,
141
+ description: sparseDescription(),
142
+ });
143
+ expect(needsResearch(issue, config)).toBe(true);
144
+ });
145
+ it('respects custom research request labels', () => {
146
+ const config = {
147
+ ...DEFAULT_TOP_OF_FUNNEL_CONFIG,
148
+ researchRequestLabels: ['Research Me'],
149
+ };
150
+ const issue = makeIssue({
151
+ description: wellResearchedDescription(),
152
+ labels: ['Research Me'],
153
+ });
154
+ expect(needsResearch(issue, config)).toBe(true);
155
+ // Default label should NOT trigger with custom config
156
+ const issueDefault = makeIssue({
157
+ description: wellResearchedDescription(),
158
+ labels: ['Needs Research'],
159
+ });
160
+ expect(needsResearch(issueDefault, config)).toBe(false);
161
+ });
162
+ });
163
+ // ---------------------------------------------------------------------------
164
+ // isReadyForBacklogCreation
165
+ // ---------------------------------------------------------------------------
166
+ describe('isReadyForBacklogCreation', () => {
167
+ it('returns false if issue is not in Icebox', () => {
168
+ const issue = makeIssue({
169
+ status: 'Backlog',
170
+ description: wellResearchedDescription(),
171
+ });
172
+ expect(isReadyForBacklogCreation(issue)).toBe(false);
173
+ });
174
+ it('returns false for sparse description', () => {
175
+ const issue = makeIssue({ description: sparseDescription() });
176
+ expect(isReadyForBacklogCreation(issue)).toBe(false);
177
+ });
178
+ it('returns true for well-researched Icebox issue', () => {
179
+ const issue = makeIssue({ description: wellResearchedDescription() });
180
+ expect(isReadyForBacklogCreation(issue)).toBe(true);
181
+ });
182
+ it('returns false for parent issues', () => {
183
+ const issue = makeIssue({
184
+ parentId: 'parent-1',
185
+ description: wellResearchedDescription(),
186
+ });
187
+ expect(isReadyForBacklogCreation(issue)).toBe(false);
188
+ });
189
+ });
190
+ // ---------------------------------------------------------------------------
191
+ // determineTopOfFunnelAction
192
+ // ---------------------------------------------------------------------------
193
+ describe('determineTopOfFunnelAction', () => {
194
+ // Enable both auto-triggers for testing (defaults are off)
195
+ const config = {
196
+ ...DEFAULT_TOP_OF_FUNNEL_CONFIG,
197
+ enableAutoResearch: true,
198
+ enableAutoBacklogCreation: true,
199
+ };
200
+ beforeEach(() => {
201
+ vi.useFakeTimers();
202
+ vi.setSystemTime(new Date('2025-06-01T12:00:00Z'));
203
+ });
204
+ afterEach(() => {
205
+ vi.useRealTimers();
206
+ });
207
+ // -- Guard rails --
208
+ it('returns none when issue is not in Icebox', () => {
209
+ const issue = makeIssue({ status: 'Backlog' });
210
+ const action = determineTopOfFunnelAction(issue, config, defaultContext());
211
+ expect(action.type).toBe('none');
212
+ expect(action.reason).toContain('not in Icebox');
213
+ });
214
+ it('returns none when issue has an active session', () => {
215
+ const issue = makeIssue();
216
+ const action = determineTopOfFunnelAction(issue, config, defaultContext({ hasActiveSession: true }));
217
+ expect(action.type).toBe('none');
218
+ expect(action.reason).toContain('active agent session');
219
+ });
220
+ it('returns none when issue is held', () => {
221
+ const issue = makeIssue();
222
+ const action = determineTopOfFunnelAction(issue, config, defaultContext({ isHeld: true }));
223
+ expect(action.type).toBe('none');
224
+ expect(action.reason).toContain('held');
225
+ });
226
+ it('returns none for parent/coordinator issues', () => {
227
+ const issue = makeIssue();
228
+ const action = determineTopOfFunnelAction(issue, config, defaultContext({ isParentIssue: true }));
229
+ expect(action.type).toBe('none');
230
+ expect(action.reason).toContain('coordination');
231
+ });
232
+ // -- Research triggers --
233
+ it('triggers research for sparse description after delay', () => {
234
+ const issue = makeIssue({ description: sparseDescription() });
235
+ const action = determineTopOfFunnelAction(issue, config, defaultContext());
236
+ expect(action.type).toBe('trigger-research');
237
+ expect(action.reason).toContain('lacks sufficient detail');
238
+ });
239
+ it('triggers research when research label is present', () => {
240
+ const issue = makeIssue({
241
+ description: wellResearchedDescription(),
242
+ labels: ['Needs Research'],
243
+ });
244
+ const action = determineTopOfFunnelAction(issue, config, defaultContext());
245
+ expect(action.type).toBe('trigger-research');
246
+ expect(action.reason).toContain('research-request label');
247
+ });
248
+ it('returns none when research is needed but delay not met', () => {
249
+ const issue = makeIssue({
250
+ description: sparseDescription(),
251
+ createdAt: Date.now() - 30 * 60 * 1000, // 30 min
252
+ });
253
+ const action = determineTopOfFunnelAction(issue, config, defaultContext());
254
+ expect(action.type).toBe('none');
255
+ expect(action.reason).toContain('not been in Icebox long enough');
256
+ });
257
+ it('skips research when research already completed', () => {
258
+ const issue = makeIssue({ description: sparseDescription() });
259
+ const action = determineTopOfFunnelAction(issue, config, defaultContext({ researchCompleted: true }));
260
+ // Research completed but description still sparse — should not trigger
261
+ // backlog creation either because description is not well-researched
262
+ expect(action.type).toBe('none');
263
+ });
264
+ // -- Backlog-creation triggers --
265
+ it('triggers backlog creation for well-researched issue', () => {
266
+ const issue = makeIssue({ description: wellResearchedDescription() });
267
+ const action = determineTopOfFunnelAction(issue, config, defaultContext());
268
+ expect(action.type).toBe('trigger-backlog-creation');
269
+ expect(action.reason).toContain('ready for backlog creation');
270
+ });
271
+ it('triggers backlog creation when research completed and description is now rich', () => {
272
+ const issue = makeIssue({ description: wellResearchedDescription() });
273
+ const action = determineTopOfFunnelAction(issue, config, defaultContext({ researchCompleted: true }));
274
+ expect(action.type).toBe('trigger-backlog-creation');
275
+ });
276
+ it('skips backlog creation when already completed', () => {
277
+ const issue = makeIssue({ description: wellResearchedDescription() });
278
+ const action = determineTopOfFunnelAction(issue, config, defaultContext({ backlogCreationCompleted: true }));
279
+ expect(action.type).toBe('none');
280
+ expect(action.reason).toContain('already completed');
281
+ });
282
+ it('returns none when both phases completed', () => {
283
+ const issue = makeIssue({ description: wellResearchedDescription() });
284
+ const action = determineTopOfFunnelAction(issue, config, defaultContext({ researchCompleted: true, backlogCreationCompleted: true }));
285
+ expect(action.type).toBe('none');
286
+ expect(action.reason).toContain('already completed');
287
+ });
288
+ // -- Disabled flags --
289
+ it('skips research when enableAutoResearch is false', () => {
290
+ const disabledConfig = {
291
+ ...config,
292
+ enableAutoResearch: false,
293
+ };
294
+ const issue = makeIssue({ description: sparseDescription() });
295
+ const action = determineTopOfFunnelAction(issue, disabledConfig, defaultContext());
296
+ expect(action.type).toBe('none');
297
+ expect(action.reason).toContain('not well-researched');
298
+ });
299
+ it('skips backlog creation when enableAutoBacklogCreation is false', () => {
300
+ const disabledConfig = {
301
+ ...config,
302
+ enableAutoBacklogCreation: false,
303
+ };
304
+ const issue = makeIssue({ description: wellResearchedDescription() });
305
+ const action = determineTopOfFunnelAction(issue, disabledConfig, defaultContext());
306
+ expect(action.type).toBe('none');
307
+ expect(action.reason).toContain('disabled');
308
+ });
309
+ it('still triggers research even when backlog creation is disabled', () => {
310
+ const disabledConfig = {
311
+ ...config,
312
+ enableAutoBacklogCreation: false,
313
+ };
314
+ const issue = makeIssue({ description: sparseDescription() });
315
+ const action = determineTopOfFunnelAction(issue, disabledConfig, defaultContext());
316
+ expect(action.type).toBe('trigger-research');
317
+ });
318
+ // -- Config override combos --
319
+ it('respects a custom iceboxResearchDelayMs', () => {
320
+ const customConfig = {
321
+ ...config,
322
+ iceboxResearchDelayMs: 5 * 60 * 1000, // 5 minutes
323
+ };
324
+ const issue = makeIssue({
325
+ description: sparseDescription(),
326
+ createdAt: Date.now() - 10 * 60 * 1000, // 10 min
327
+ });
328
+ const action = determineTopOfFunnelAction(issue, customConfig, defaultContext());
329
+ expect(action.type).toBe('trigger-research');
330
+ });
331
+ });
@@ -0,0 +1,11 @@
1
+ export * from './orchestrator/index.js';
2
+ export * from './providers/index.js';
3
+ export * from './logger.js';
4
+ export * from './deployment/index.js';
5
+ export * from './templates/index.js';
6
+ export * from './governor/index.js';
7
+ export * from './frontend/index.js';
8
+ export * from './config/index.js';
9
+ export * from './manifest/index.js';
10
+ export * from './tools/index.js';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAA;AACvC,cAAc,sBAAsB,CAAA;AACpC,cAAc,aAAa,CAAA;AAC3B,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAA;AACjC,cAAc,qBAAqB,CAAA;AACnC,cAAc,kBAAkB,CAAA"}
@@ -0,0 +1,10 @@
1
+ export * from './orchestrator/index.js';
2
+ export * from './providers/index.js';
3
+ export * from './logger.js';
4
+ export * from './deployment/index.js';
5
+ export * from './templates/index.js';
6
+ export * from './governor/index.js';
7
+ export * from './frontend/index.js';
8
+ export * from './config/index.js';
9
+ export * from './manifest/index.js';
10
+ export * from './tools/index.js';
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @deprecated Use `af-linear` from `@renseiai/agentfactory-cli` instead.
4
+ * This file is kept for backwards compatibility. The canonical implementation
5
+ * is in packages/cli/src/lib/linear-runner.ts.
6
+ *
7
+ * Linear CLI - Command-line interface for Linear Agent SDK
8
+ *
9
+ * Usage:
10
+ * pnpm af-linear <command> [options]
11
+ *
12
+ * Commands:
13
+ * get-issue <id> Get issue details
14
+ * create-issue Create a new issue
15
+ * update-issue <id> Update an existing issue
16
+ * list-comments <issueId> List comments on an issue
17
+ * create-comment <issueId> Create a comment on an issue
18
+ * list-backlog-issues List backlog issues for a project
19
+ * list-unblocked-backlog List unblocked backlog issues
20
+ * check-blocked <id> Check if an issue is blocked
21
+ * add-relation <id> <id> Create relation between issues
22
+ * list-relations <id> List relations for an issue
23
+ * remove-relation <id> Remove a relation by ID
24
+ * list-sub-issues <id> List sub-issues of a parent issue
25
+ * list-sub-issue-statuses <id> List sub-issue statuses (lightweight)
26
+ * update-sub-issue <id> Update sub-issue status with comment
27
+ * check-deployment <PR> Check Vercel deployment status for a PR
28
+ *
29
+ * Array Values:
30
+ * --labels accepts comma-separated: --labels "Bug,Feature"
31
+ * For values with commas, use JSON: --labels '["Bug", "UI, UX"]'
32
+ * Text fields (--description, --title, --body) preserve commas.
33
+ *
34
+ * Environment:
35
+ * LINEAR_API_KEY Required API key for authentication
36
+ */
37
+ import 'dotenv/config';
38
+ //# sourceMappingURL=linear-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-cli.d.ts","sourceRoot":"","sources":["../../src/linear-cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAGH,OAAO,eAAe,CAAA"}