@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,11 @@
1
+ /**
2
+ * PlatformAdapter Interface
3
+ *
4
+ * Extends the WorkSchedulingFrontend concept with methods needed by the
5
+ * EventDrivenGovernor to process webhook events and poll project state.
6
+ *
7
+ * Platform adapters (e.g., LinearPlatformAdapter) implement this interface
8
+ * structurally, translating between platform-native concepts and the
9
+ * Governor's abstract event/issue types.
10
+ */
11
+ export {};
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Processing State Tracker
3
+ *
4
+ * Tracks which top-of-funnel processing phases have been completed for each
5
+ * issue to prevent re-processing. The core package defines the types and the
6
+ * storage adapter interface; concrete implementations live in packages/server
7
+ * (e.g., RedisProcessingStateStorage).
8
+ */
9
+ /**
10
+ * Processing phases tracked by the top-of-funnel governor.
11
+ */
12
+ export type ProcessingPhase = 'research' | 'backlog-creation';
13
+ /**
14
+ * Record of a completed processing phase for an issue.
15
+ */
16
+ export interface ProcessingRecord {
17
+ issueId: string;
18
+ phase: ProcessingPhase;
19
+ completedAt: number;
20
+ sessionId?: string;
21
+ }
22
+ /**
23
+ * Storage adapter for persisting processing state.
24
+ *
25
+ * Implementations must be idempotent — calling `markPhaseCompleted` twice
26
+ * for the same issue+phase is a no-op (overwrites with the latest data).
27
+ *
28
+ * See `RedisProcessingStateStorage` in `packages/server` for the Redis-backed
29
+ * implementation.
30
+ */
31
+ export interface ProcessingStateStorage {
32
+ /**
33
+ * Check whether a given phase has already been completed for an issue.
34
+ */
35
+ isPhaseCompleted(issueId: string, phase: ProcessingPhase): Promise<boolean>;
36
+ /**
37
+ * Mark a phase as completed for an issue.
38
+ * If the phase was already marked, this overwrites the record.
39
+ */
40
+ markPhaseCompleted(issueId: string, phase: ProcessingPhase, sessionId?: string): Promise<void>;
41
+ /**
42
+ * Clear a phase completion record for an issue.
43
+ * Useful when an issue is re-opened or moved back to Icebox.
44
+ */
45
+ clearPhase(issueId: string, phase: ProcessingPhase): Promise<void>;
46
+ /**
47
+ * Retrieve the processing record for a phase, if it exists.
48
+ */
49
+ getPhaseRecord(issueId: string, phase: ProcessingPhase): Promise<ProcessingRecord | null>;
50
+ }
51
+ /**
52
+ * Simple in-memory implementation of `ProcessingStateStorage`.
53
+ * Suitable for tests and single-process local development; not shared across
54
+ * processes.
55
+ */
56
+ export declare class InMemoryProcessingStateStorage implements ProcessingStateStorage {
57
+ private store;
58
+ private key;
59
+ isPhaseCompleted(issueId: string, phase: ProcessingPhase): Promise<boolean>;
60
+ markPhaseCompleted(issueId: string, phase: ProcessingPhase, sessionId?: string): Promise<void>;
61
+ clearPhase(issueId: string, phase: ProcessingPhase): Promise<void>;
62
+ getPhaseRecord(issueId: string, phase: ProcessingPhase): Promise<ProcessingRecord | null>;
63
+ /** Clear all records (useful in tests). */
64
+ clear(): void;
65
+ }
66
+ //# sourceMappingURL=processing-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"processing-state.d.ts","sourceRoot":"","sources":["../../../src/governor/processing-state.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,kBAAkB,CAAA;AAE7D;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,eAAe,CAAA;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAMD;;;;;;;;GAQG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAE3E;;;OAGG;IACH,kBAAkB,CAChB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,eAAe,EACtB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAAA;IAEhB;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAElE;;OAEG;IACH,cAAc,CACZ,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;CACpC;AAMD;;;;GAIG;AACH,qBAAa,8BAA+B,YAAW,sBAAsB;IAC3E,OAAO,CAAC,KAAK,CAAsC;IAEnD,OAAO,CAAC,GAAG;IAIL,gBAAgB,CACpB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,OAAO,CAAC;IAIb,kBAAkB,CACtB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,eAAe,EACtB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IASV,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlE,cAAc,CAClB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAInC,2CAA2C;IAC3C,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Processing State Tracker
3
+ *
4
+ * Tracks which top-of-funnel processing phases have been completed for each
5
+ * issue to prevent re-processing. The core package defines the types and the
6
+ * storage adapter interface; concrete implementations live in packages/server
7
+ * (e.g., RedisProcessingStateStorage).
8
+ */
9
+ // ---------------------------------------------------------------------------
10
+ // In-memory implementation (for testing and local development)
11
+ // ---------------------------------------------------------------------------
12
+ /**
13
+ * Simple in-memory implementation of `ProcessingStateStorage`.
14
+ * Suitable for tests and single-process local development; not shared across
15
+ * processes.
16
+ */
17
+ export class InMemoryProcessingStateStorage {
18
+ store = new Map();
19
+ key(issueId, phase) {
20
+ return `${issueId}:${phase}`;
21
+ }
22
+ async isPhaseCompleted(issueId, phase) {
23
+ return this.store.has(this.key(issueId, phase));
24
+ }
25
+ async markPhaseCompleted(issueId, phase, sessionId) {
26
+ this.store.set(this.key(issueId, phase), {
27
+ issueId,
28
+ phase,
29
+ completedAt: Date.now(),
30
+ sessionId,
31
+ });
32
+ }
33
+ async clearPhase(issueId, phase) {
34
+ this.store.delete(this.key(issueId, phase));
35
+ }
36
+ async getPhaseRecord(issueId, phase) {
37
+ return this.store.get(this.key(issueId, phase)) ?? null;
38
+ }
39
+ /** Clear all records (useful in tests). */
40
+ clear() {
41
+ this.store.clear();
42
+ }
43
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=processing-state.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"processing-state.test.d.ts","sourceRoot":"","sources":["../../../src/governor/processing-state.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,96 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { InMemoryProcessingStateStorage, } from './processing-state.js';
3
+ // ---------------------------------------------------------------------------
4
+ // ProcessingStateStorage interface contract tests
5
+ // ---------------------------------------------------------------------------
6
+ // These tests exercise the interface through the InMemoryProcessingStateStorage
7
+ // implementation but validate the contract that any implementation must honour.
8
+ describe('ProcessingStateStorage (InMemoryProcessingStateStorage)', () => {
9
+ let storage;
10
+ beforeEach(() => {
11
+ storage = new InMemoryProcessingStateStorage();
12
+ });
13
+ // -- isPhaseCompleted --
14
+ it('returns false for a phase that has not been completed', async () => {
15
+ const completed = await storage.isPhaseCompleted('issue-1', 'research');
16
+ expect(completed).toBe(false);
17
+ });
18
+ it('returns true after marking a phase as completed', async () => {
19
+ await storage.markPhaseCompleted('issue-1', 'research');
20
+ const completed = await storage.isPhaseCompleted('issue-1', 'research');
21
+ expect(completed).toBe(true);
22
+ });
23
+ // -- markPhaseCompleted --
24
+ it('records a phase completion with optional sessionId', async () => {
25
+ await storage.markPhaseCompleted('issue-1', 'research', 'session-abc');
26
+ const record = await storage.getPhaseRecord('issue-1', 'research');
27
+ expect(record).not.toBeNull();
28
+ expect(record.issueId).toBe('issue-1');
29
+ expect(record.phase).toBe('research');
30
+ expect(record.sessionId).toBe('session-abc');
31
+ expect(record.completedAt).toBeGreaterThan(0);
32
+ });
33
+ it('is idempotent — re-marking overwrites the record', async () => {
34
+ await storage.markPhaseCompleted('issue-1', 'research', 'session-1');
35
+ const first = await storage.getPhaseRecord('issue-1', 'research');
36
+ await storage.markPhaseCompleted('issue-1', 'research', 'session-2');
37
+ const second = await storage.getPhaseRecord('issue-1', 'research');
38
+ expect(second.sessionId).toBe('session-2');
39
+ expect(second.completedAt).toBeGreaterThanOrEqual(first.completedAt);
40
+ });
41
+ // -- clearPhase --
42
+ it('clears a completed phase', async () => {
43
+ await storage.markPhaseCompleted('issue-1', 'research');
44
+ expect(await storage.isPhaseCompleted('issue-1', 'research')).toBe(true);
45
+ await storage.clearPhase('issue-1', 'research');
46
+ expect(await storage.isPhaseCompleted('issue-1', 'research')).toBe(false);
47
+ });
48
+ it('clearing an already-absent phase is a no-op', async () => {
49
+ // Should not throw
50
+ await storage.clearPhase('issue-1', 'research');
51
+ expect(await storage.isPhaseCompleted('issue-1', 'research')).toBe(false);
52
+ });
53
+ // -- getPhaseRecord --
54
+ it('returns null for an absent record', async () => {
55
+ const record = await storage.getPhaseRecord('issue-1', 'backlog-creation');
56
+ expect(record).toBeNull();
57
+ });
58
+ it('returns the stored record', async () => {
59
+ await storage.markPhaseCompleted('issue-1', 'backlog-creation', 'sess-x');
60
+ const record = await storage.getPhaseRecord('issue-1', 'backlog-creation');
61
+ expect(record).toEqual(expect.objectContaining({
62
+ issueId: 'issue-1',
63
+ phase: 'backlog-creation',
64
+ sessionId: 'sess-x',
65
+ }));
66
+ });
67
+ // -- Phase isolation --
68
+ it('different phases for the same issue are independent', async () => {
69
+ await storage.markPhaseCompleted('issue-1', 'research');
70
+ expect(await storage.isPhaseCompleted('issue-1', 'research')).toBe(true);
71
+ expect(await storage.isPhaseCompleted('issue-1', 'backlog-creation')).toBe(false);
72
+ });
73
+ it('same phase for different issues are independent', async () => {
74
+ await storage.markPhaseCompleted('issue-1', 'research');
75
+ expect(await storage.isPhaseCompleted('issue-1', 'research')).toBe(true);
76
+ expect(await storage.isPhaseCompleted('issue-2', 'research')).toBe(false);
77
+ });
78
+ // -- All phases --
79
+ it('supports both defined processing phases', async () => {
80
+ const phases = ['research', 'backlog-creation'];
81
+ for (const phase of phases) {
82
+ await storage.markPhaseCompleted('issue-x', phase, `sess-${phase}`);
83
+ expect(await storage.isPhaseCompleted('issue-x', phase)).toBe(true);
84
+ const record = await storage.getPhaseRecord('issue-x', phase);
85
+ expect(record.phase).toBe(phase);
86
+ }
87
+ });
88
+ // -- InMemory-specific: clear all --
89
+ it('clear() removes all records', async () => {
90
+ await storage.markPhaseCompleted('issue-1', 'research');
91
+ await storage.markPhaseCompleted('issue-2', 'backlog-creation');
92
+ storage.clear();
93
+ expect(await storage.isPhaseCompleted('issue-1', 'research')).toBe(false);
94
+ expect(await storage.isPhaseCompleted('issue-2', 'backlog-creation')).toBe(false);
95
+ });
96
+ });
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Top-of-Funnel Triggers
3
+ *
4
+ * Heuristics and trigger logic for auto-research and auto-backlog-creation.
5
+ * Issues in "Icebox" are evaluated to determine whether they need research
6
+ * (fleshing out requirements, acceptance criteria, technical approach) or
7
+ * are already well-researched and ready for backlog creation (decomposition
8
+ * into sub-issues, estimation, etc.).
9
+ *
10
+ * All functions are pure — no side effects, no network calls.
11
+ */
12
+ /**
13
+ * Configuration for the top-of-funnel governor heuristics.
14
+ */
15
+ export interface TopOfFunnelConfig {
16
+ /** Minimum time in Icebox before triggering research (ms). Default: 1 hour */
17
+ iceboxResearchDelayMs: number;
18
+ /** Minimum description length to consider "well-researched". Default: 200 */
19
+ minResearchedDescriptionLength: number;
20
+ /** Headers that indicate a well-researched issue */
21
+ researchedHeaders: string[];
22
+ /** Labels that explicitly request research */
23
+ researchRequestLabels: string[];
24
+ /** Whether auto-research is enabled. Default: true */
25
+ enableAutoResearch: boolean;
26
+ /** Whether auto-backlog-creation is enabled. Default: true */
27
+ enableAutoBacklogCreation: boolean;
28
+ }
29
+ /**
30
+ * Sensible defaults for the top-of-funnel configuration.
31
+ */
32
+ export declare const DEFAULT_TOP_OF_FUNNEL_CONFIG: TopOfFunnelConfig;
33
+ /**
34
+ * Minimal issue information needed by the top-of-funnel evaluator.
35
+ */
36
+ export interface IssueInfo {
37
+ id: string;
38
+ identifier: string;
39
+ title: string;
40
+ description?: string;
41
+ status: string;
42
+ labels: string[];
43
+ createdAt: number;
44
+ parentId?: string;
45
+ }
46
+ /**
47
+ * Discriminated union describing the action the governor should take for an
48
+ * Icebox issue.
49
+ */
50
+ export type TopOfFunnelAction = {
51
+ type: 'trigger-research';
52
+ issueId: string;
53
+ reason: string;
54
+ } | {
55
+ type: 'trigger-backlog-creation';
56
+ issueId: string;
57
+ reason: string;
58
+ } | {
59
+ type: 'none';
60
+ issueId: string;
61
+ reason: string;
62
+ };
63
+ /**
64
+ * External state the caller supplies so the evaluator can avoid re-processing.
65
+ */
66
+ export interface TopOfFunnelContext {
67
+ /** Whether there is already an active agent session for this issue */
68
+ hasActiveSession: boolean;
69
+ /** Whether the issue is held (e.g., awaiting human input) */
70
+ isHeld: boolean;
71
+ /** Whether the research phase was already completed */
72
+ researchCompleted: boolean;
73
+ /** Whether the backlog-creation phase was already completed */
74
+ backlogCreationCompleted: boolean;
75
+ /** Whether the issue is a parent/coordinator issue */
76
+ isParentIssue: boolean;
77
+ }
78
+ /**
79
+ * Check whether an issue description appears well-researched.
80
+ *
81
+ * Heuristic:
82
+ * - The description must meet a minimum character length **and**
83
+ * - Contain at least one of the recognised structured headers.
84
+ */
85
+ export declare function isWellResearched(description: string | undefined, config?: TopOfFunnelConfig): boolean;
86
+ /**
87
+ * Check whether an issue needs research.
88
+ *
89
+ * Criteria (all must hold):
90
+ * 1. Issue is in "Icebox" status
91
+ * 2. Description is **not** well-researched (short / missing headers) **or**
92
+ * the issue carries a research-request label
93
+ * 3. The issue has been in Icebox for longer than the configured delay
94
+ * 4. The issue is not a parent/coordinator issue
95
+ *
96
+ * Note: active-session and hold checks are done in `determineTopOfFunnelAction`
97
+ * so that callers who just want the heuristic can use this function standalone.
98
+ */
99
+ export declare function needsResearch(issue: IssueInfo, config?: TopOfFunnelConfig): boolean;
100
+ /**
101
+ * Check whether an issue is ready for backlog creation.
102
+ *
103
+ * Criteria (all must hold):
104
+ * 1. Issue is in "Icebox" status
105
+ * 2. Description **is** well-researched (meets length + header heuristic)
106
+ * 3. The issue is not a parent/coordinator issue
107
+ *
108
+ * The `researchCompleted` flag is checked in `determineTopOfFunnelAction`.
109
+ */
110
+ export declare function isReadyForBacklogCreation(issue: IssueInfo, config?: TopOfFunnelConfig): boolean;
111
+ /**
112
+ * Determine the top-of-funnel action for an Icebox issue.
113
+ *
114
+ * Uses workflow / processing state to avoid re-processing issues that have
115
+ * already been through research or backlog creation.
116
+ */
117
+ export declare function determineTopOfFunnelAction(issue: IssueInfo, config: TopOfFunnelConfig, context: TopOfFunnelContext): TopOfFunnelAction;
118
+ //# sourceMappingURL=top-of-funnel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"top-of-funnel.d.ts","sourceRoot":"","sources":["../../../src/governor/top-of-funnel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,8EAA8E;IAC9E,qBAAqB,EAAE,MAAM,CAAA;IAC7B,6EAA6E;IAC7E,8BAA8B,EAAE,MAAM,CAAA;IACtC,oDAAoD;IACpD,iBAAiB,EAAE,MAAM,EAAE,CAAA;IAC3B,8CAA8C;IAC9C,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAC/B,sDAAsD;IACtD,kBAAkB,EAAE,OAAO,CAAA;IAC3B,8DAA8D;IAC9D,yBAAyB,EAAE,OAAO,CAAA;CACnC;AAED;;GAEG;AACH,eAAO,MAAM,4BAA4B,EAAE,iBAa1C,CAAA;AAMD;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7D;IAAE,IAAI,EAAE,0BAA0B,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACrE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAMrD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,gBAAgB,EAAE,OAAO,CAAA;IACzB,6DAA6D;IAC7D,MAAM,EAAE,OAAO,CAAA;IACf,uDAAuD;IACvD,iBAAiB,EAAE,OAAO,CAAA;IAC1B,+DAA+D;IAC/D,wBAAwB,EAAE,OAAO,CAAA;IACjC,sDAAsD;IACtD,aAAa,EAAE,OAAO,CAAA;CACvB;AAMD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,MAAM,GAAE,iBAAgD,GACvD,OAAO,CAWT;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,SAAS,EAChB,MAAM,GAAE,iBAAgD,GACvD,OAAO,CAiBT;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,SAAS,EAChB,MAAM,GAAE,iBAAgD,GACvD,OAAO,CAOT;AAMD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,kBAAkB,GAC1B,iBAAiB,CA0FnB"}
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Top-of-Funnel Triggers
3
+ *
4
+ * Heuristics and trigger logic for auto-research and auto-backlog-creation.
5
+ * Issues in "Icebox" are evaluated to determine whether they need research
6
+ * (fleshing out requirements, acceptance criteria, technical approach) or
7
+ * are already well-researched and ready for backlog creation (decomposition
8
+ * into sub-issues, estimation, etc.).
9
+ *
10
+ * All functions are pure — no side effects, no network calls.
11
+ */
12
+ /**
13
+ * Sensible defaults for the top-of-funnel configuration.
14
+ */
15
+ export const DEFAULT_TOP_OF_FUNNEL_CONFIG = {
16
+ iceboxResearchDelayMs: 60 * 60 * 1000, // 1 hour
17
+ minResearchedDescriptionLength: 200,
18
+ researchedHeaders: [
19
+ '## Acceptance Criteria',
20
+ '## Technical Approach',
21
+ '## Summary',
22
+ '## Design',
23
+ '## Requirements',
24
+ ],
25
+ researchRequestLabels: ['Needs Research'],
26
+ enableAutoResearch: false,
27
+ enableAutoBacklogCreation: false,
28
+ };
29
+ // ---------------------------------------------------------------------------
30
+ // Pure evaluation helpers
31
+ // ---------------------------------------------------------------------------
32
+ /**
33
+ * Check whether an issue description appears well-researched.
34
+ *
35
+ * Heuristic:
36
+ * - The description must meet a minimum character length **and**
37
+ * - Contain at least one of the recognised structured headers.
38
+ */
39
+ export function isWellResearched(description, config = DEFAULT_TOP_OF_FUNNEL_CONFIG) {
40
+ if (!description)
41
+ return false;
42
+ const meetsLengthThreshold = description.length >= config.minResearchedDescriptionLength;
43
+ const hasStructuredHeader = config.researchedHeaders.some((header) => description.includes(header));
44
+ return meetsLengthThreshold && hasStructuredHeader;
45
+ }
46
+ /**
47
+ * Check whether an issue needs research.
48
+ *
49
+ * Criteria (all must hold):
50
+ * 1. Issue is in "Icebox" status
51
+ * 2. Description is **not** well-researched (short / missing headers) **or**
52
+ * the issue carries a research-request label
53
+ * 3. The issue has been in Icebox for longer than the configured delay
54
+ * 4. The issue is not a parent/coordinator issue
55
+ *
56
+ * Note: active-session and hold checks are done in `determineTopOfFunnelAction`
57
+ * so that callers who just want the heuristic can use this function standalone.
58
+ */
59
+ export function needsResearch(issue, config = DEFAULT_TOP_OF_FUNNEL_CONFIG) {
60
+ if (issue.status !== 'Icebox')
61
+ return false;
62
+ // Parent issues use coordination, not individual research
63
+ if (issue.parentId !== undefined)
64
+ return false;
65
+ const ageMs = Date.now() - issue.createdAt;
66
+ if (ageMs < config.iceboxResearchDelayMs)
67
+ return false;
68
+ // Explicit research-request label always triggers research
69
+ const hasResearchLabel = issue.labels.some((label) => config.researchRequestLabels.includes(label));
70
+ if (hasResearchLabel)
71
+ return true;
72
+ // Otherwise, only if the description is not well-researched
73
+ return !isWellResearched(issue.description, config);
74
+ }
75
+ /**
76
+ * Check whether an issue is ready for backlog creation.
77
+ *
78
+ * Criteria (all must hold):
79
+ * 1. Issue is in "Icebox" status
80
+ * 2. Description **is** well-researched (meets length + header heuristic)
81
+ * 3. The issue is not a parent/coordinator issue
82
+ *
83
+ * The `researchCompleted` flag is checked in `determineTopOfFunnelAction`.
84
+ */
85
+ export function isReadyForBacklogCreation(issue, config = DEFAULT_TOP_OF_FUNNEL_CONFIG) {
86
+ if (issue.status !== 'Icebox')
87
+ return false;
88
+ // Parent issues use coordination, not individual backlog creation
89
+ if (issue.parentId !== undefined)
90
+ return false;
91
+ return isWellResearched(issue.description, config);
92
+ }
93
+ // ---------------------------------------------------------------------------
94
+ // Main decision function
95
+ // ---------------------------------------------------------------------------
96
+ /**
97
+ * Determine the top-of-funnel action for an Icebox issue.
98
+ *
99
+ * Uses workflow / processing state to avoid re-processing issues that have
100
+ * already been through research or backlog creation.
101
+ */
102
+ export function determineTopOfFunnelAction(issue, config, context) {
103
+ const none = (reason) => ({
104
+ type: 'none',
105
+ issueId: issue.id,
106
+ reason,
107
+ });
108
+ // --- Guard rails ---
109
+ if (issue.status !== 'Icebox') {
110
+ return none('Issue is not in Icebox status');
111
+ }
112
+ if (context.hasActiveSession) {
113
+ return none('Issue already has an active agent session');
114
+ }
115
+ if (context.isHeld) {
116
+ return none('Issue is held (awaiting human input)');
117
+ }
118
+ if (context.isParentIssue) {
119
+ return none('Parent/coordinator issues are handled via coordination workflow');
120
+ }
121
+ // --- Research evaluation ---
122
+ if (config.enableAutoResearch && !context.researchCompleted) {
123
+ const hasResearchLabel = issue.labels.some((label) => config.researchRequestLabels.includes(label));
124
+ const descriptionNeedsResearch = !isWellResearched(issue.description, config);
125
+ if (hasResearchLabel || descriptionNeedsResearch) {
126
+ const ageMs = Date.now() - issue.createdAt;
127
+ if (ageMs >= config.iceboxResearchDelayMs) {
128
+ const reason = hasResearchLabel
129
+ ? `Issue has research-request label: ${issue.labels.filter((l) => config.researchRequestLabels.includes(l)).join(', ')}`
130
+ : 'Issue description lacks sufficient detail or structured headers';
131
+ return {
132
+ type: 'trigger-research',
133
+ issueId: issue.id,
134
+ reason,
135
+ };
136
+ }
137
+ return none(`Issue needs research but has not been in Icebox long enough (${Math.round(ageMs / 1000)}s < ${Math.round(config.iceboxResearchDelayMs / 1000)}s)`);
138
+ }
139
+ }
140
+ if (!config.enableAutoResearch && !context.researchCompleted) {
141
+ // Auto-research is disabled; if the description is sparse we still skip
142
+ // backlog creation since research hasn't been done.
143
+ if (!isWellResearched(issue.description, config)) {
144
+ return none('Auto-research is disabled and issue description is not well-researched');
145
+ }
146
+ }
147
+ // --- Backlog-creation evaluation ---
148
+ if (config.enableAutoBacklogCreation && !context.backlogCreationCompleted) {
149
+ if (isWellResearched(issue.description, config)) {
150
+ return {
151
+ type: 'trigger-backlog-creation',
152
+ issueId: issue.id,
153
+ reason: 'Issue is well-researched and ready for backlog creation',
154
+ };
155
+ }
156
+ }
157
+ // --- Fallthrough ---
158
+ if (context.researchCompleted && context.backlogCreationCompleted) {
159
+ return none('Both research and backlog-creation phases already completed');
160
+ }
161
+ if (context.backlogCreationCompleted) {
162
+ return none('Backlog-creation phase already completed');
163
+ }
164
+ if (!config.enableAutoBacklogCreation) {
165
+ return none('Auto-backlog-creation is disabled');
166
+ }
167
+ return none('No top-of-funnel action needed');
168
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=top-of-funnel.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"top-of-funnel.test.d.ts","sourceRoot":"","sources":["../../../src/governor/top-of-funnel.test.ts"],"names":[],"mappings":""}