@mastra/factory 0.13.0-alpha.8 → 0.13.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 (232) hide show
  1. package/README.md +232 -10
  2. package/dist/auth.d.ts +2 -0
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/auth.js +29 -4
  5. package/dist/auth.js.map +1 -1
  6. package/dist/boards/define-board.d.ts +36 -2
  7. package/dist/boards/define-board.d.ts.map +1 -1
  8. package/dist/boards/define-board.js +58 -1
  9. package/dist/boards/define-board.js.map +1 -1
  10. package/dist/boards/index.d.ts +4 -2
  11. package/dist/boards/index.d.ts.map +1 -1
  12. package/dist/boards/index.js +2 -1
  13. package/dist/boards/index.js.map +1 -1
  14. package/dist/boards/relocate.d.ts +17 -0
  15. package/dist/boards/relocate.d.ts.map +1 -0
  16. package/dist/boards/relocate.js +53 -0
  17. package/dist/boards/relocate.js.map +1 -0
  18. package/dist/boards/review.d.ts +1 -1
  19. package/dist/boards/review.d.ts.map +1 -1
  20. package/dist/boards/review.js +17 -4
  21. package/dist/boards/review.js.map +1 -1
  22. package/dist/boards/semantics.d.ts +28 -0
  23. package/dist/boards/semantics.d.ts.map +1 -0
  24. package/dist/boards/semantics.js +41 -0
  25. package/dist/boards/semantics.js.map +1 -0
  26. package/dist/boards/work-tool-rules.d.ts +8 -0
  27. package/dist/boards/work-tool-rules.d.ts.map +1 -0
  28. package/dist/boards/work-tool-rules.js +20 -0
  29. package/dist/boards/work-tool-rules.js.map +1 -0
  30. package/dist/boards/work.d.ts +1 -1
  31. package/dist/boards/work.d.ts.map +1 -1
  32. package/dist/boards/work.js +13 -0
  33. package/dist/boards/work.js.map +1 -1
  34. package/dist/capabilities/intake.d.ts +2 -0
  35. package/dist/capabilities/intake.d.ts.map +1 -1
  36. package/dist/factory.d.ts +5 -6
  37. package/dist/factory.d.ts.map +1 -1
  38. package/dist/factory.js +41 -12
  39. package/dist/factory.js.map +1 -1
  40. package/dist/integrations/base.d.ts +7 -4
  41. package/dist/integrations/base.d.ts.map +1 -1
  42. package/dist/integrations/github/default-rules.d.ts +2 -2
  43. package/dist/integrations/github/default-rules.d.ts.map +1 -1
  44. package/dist/integrations/github/default-rules.js +2 -2
  45. package/dist/integrations/github/default-rules.js.map +1 -1
  46. package/dist/integrations/github/issue-reconciler.d.ts.map +1 -1
  47. package/dist/integrations/github/issue-reconciler.js +4 -3
  48. package/dist/integrations/github/issue-reconciler.js.map +1 -1
  49. package/dist/integrations/github/rules.d.ts +6 -2
  50. package/dist/integrations/github/rules.d.ts.map +1 -1
  51. package/dist/integrations/github/rules.js +96 -15
  52. package/dist/integrations/github/rules.js.map +1 -1
  53. package/dist/integrations/github/sandbox.d.ts +6 -3
  54. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  55. package/dist/integrations/github/sandbox.js +21 -2
  56. package/dist/integrations/github/sandbox.js.map +1 -1
  57. package/dist/integrations/issue-reconciler.d.ts +2 -0
  58. package/dist/integrations/issue-reconciler.d.ts.map +1 -1
  59. package/dist/integrations/issue-reconciler.js +1 -3
  60. package/dist/integrations/issue-reconciler.js.map +1 -1
  61. package/dist/integrations/linear/agent-tools.d.ts.map +1 -1
  62. package/dist/integrations/linear/agent-tools.js.map +1 -1
  63. package/dist/integrations/linear/default-rules.d.ts +2 -2
  64. package/dist/integrations/linear/default-rules.d.ts.map +1 -1
  65. package/dist/integrations/linear/default-rules.js +2 -2
  66. package/dist/integrations/linear/default-rules.js.map +1 -1
  67. package/dist/integrations/linear/integration.js +1 -0
  68. package/dist/integrations/linear/integration.js.map +1 -1
  69. package/dist/integrations/linear/issue-reconciler.d.ts.map +1 -1
  70. package/dist/integrations/linear/issue-reconciler.js +9 -4
  71. package/dist/integrations/linear/issue-reconciler.js.map +1 -1
  72. package/dist/integrations/linear/reconciliation-config.d.ts.map +1 -1
  73. package/dist/integrations/linear/reconciliation-config.js.map +1 -1
  74. package/dist/integrations/linear/routes.d.ts.map +1 -1
  75. package/dist/integrations/linear/routes.js +19 -26
  76. package/dist/integrations/linear/routes.js.map +1 -1
  77. package/dist/integrations/linear/rules.d.ts +7 -2
  78. package/dist/integrations/linear/rules.d.ts.map +1 -1
  79. package/dist/integrations/linear/rules.js +20 -8
  80. package/dist/integrations/linear/rules.js.map +1 -1
  81. package/dist/integrations/platform/api-client.d.ts.map +1 -1
  82. package/dist/integrations/platform/api-client.js +25 -4
  83. package/dist/integrations/platform/api-client.js.map +1 -1
  84. package/dist/integrations/platform/github/integration.d.ts +1 -0
  85. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  86. package/dist/integrations/platform/github/integration.js +5 -0
  87. package/dist/integrations/platform/github/integration.js.map +1 -1
  88. package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
  89. package/dist/integrations/platform/linear/integration.js +5 -2
  90. package/dist/integrations/platform/linear/integration.js.map +1 -1
  91. package/dist/integrations/slack/emoji-shortcodes.generated.d.ts +8 -0
  92. package/dist/integrations/slack/emoji-shortcodes.generated.d.ts.map +1 -0
  93. package/dist/integrations/slack/emoji-shortcodes.generated.js +1985 -0
  94. package/dist/integrations/slack/emoji-shortcodes.generated.js.map +1 -0
  95. package/dist/integrations/slack/emoji.d.ts +3 -0
  96. package/dist/integrations/slack/emoji.d.ts.map +1 -0
  97. package/dist/integrations/slack/emoji.js +12 -0
  98. package/dist/integrations/slack/emoji.js.map +1 -0
  99. package/dist/integrations/slack/integration.js +1 -1
  100. package/dist/integrations/slack/integration.js.map +1 -1
  101. package/dist/integrations/slack/slack.d.ts.map +1 -1
  102. package/dist/integrations/slack/slack.js +16 -2
  103. package/dist/integrations/slack/slack.js.map +1 -1
  104. package/dist/routes/attention-activity.d.ts +2 -3
  105. package/dist/routes/attention-activity.d.ts.map +1 -1
  106. package/dist/routes/attention-activity.js +12 -3
  107. package/dist/routes/attention-activity.js.map +1 -1
  108. package/dist/routes/attention-parked.d.ts +22 -0
  109. package/dist/routes/attention-parked.d.ts.map +1 -0
  110. package/dist/routes/attention-parked.js +136 -0
  111. package/dist/routes/attention-parked.js.map +1 -0
  112. package/dist/routes/attention-providers.js +8 -8
  113. package/dist/routes/attention-providers.js.map +1 -1
  114. package/dist/routes/attention.d.ts +2 -0
  115. package/dist/routes/attention.d.ts.map +1 -1
  116. package/dist/routes/attention.js +47 -118
  117. package/dist/routes/attention.js.map +1 -1
  118. package/dist/routes/contracts.d.ts +627 -0
  119. package/dist/routes/contracts.d.ts.map +1 -0
  120. package/dist/routes/contracts.js +468 -0
  121. package/dist/routes/contracts.js.map +1 -0
  122. package/dist/routes/intake.d.ts +18 -0
  123. package/dist/routes/intake.d.ts.map +1 -1
  124. package/dist/routes/intake.js +265 -15
  125. package/dist/routes/intake.js.map +1 -1
  126. package/dist/routes/projects.d.ts.map +1 -1
  127. package/dist/routes/projects.js +26 -76
  128. package/dist/routes/projects.js.map +1 -1
  129. package/dist/routes/supervisor.d.ts +2 -0
  130. package/dist/routes/supervisor.d.ts.map +1 -1
  131. package/dist/routes/supervisor.js +7 -6
  132. package/dist/routes/supervisor.js.map +1 -1
  133. package/dist/routes/surface.d.ts +7 -4
  134. package/dist/routes/surface.d.ts.map +1 -1
  135. package/dist/routes/surface.js +17 -11
  136. package/dist/routes/surface.js.map +1 -1
  137. package/dist/routes/telemetry.d.ts +13 -0
  138. package/dist/routes/telemetry.d.ts.map +1 -0
  139. package/dist/routes/telemetry.js +60 -0
  140. package/dist/routes/telemetry.js.map +1 -0
  141. package/dist/routes/work-items.d.ts +3 -3
  142. package/dist/routes/work-items.d.ts.map +1 -1
  143. package/dist/routes/work-items.js +134 -250
  144. package/dist/routes/work-items.js.map +1 -1
  145. package/dist/rules/dispatch-errors.d.ts +2 -2
  146. package/dist/rules/dispatch-errors.d.ts.map +1 -1
  147. package/dist/rules/dispatch-errors.js +10 -6
  148. package/dist/rules/dispatch-errors.js.map +1 -1
  149. package/dist/rules/dispatcher.d.ts +9 -2
  150. package/dist/rules/dispatcher.d.ts.map +1 -1
  151. package/dist/rules/dispatcher.js +76 -23
  152. package/dist/rules/dispatcher.js.map +1 -1
  153. package/dist/rules/index.d.ts +4 -5
  154. package/dist/rules/index.d.ts.map +1 -1
  155. package/dist/rules/index.js +3 -4
  156. package/dist/rules/processor.d.ts +3 -3
  157. package/dist/rules/processor.d.ts.map +1 -1
  158. package/dist/rules/processor.js +24 -31
  159. package/dist/rules/processor.js.map +1 -1
  160. package/dist/rules/resolve.d.ts +1 -3
  161. package/dist/rules/resolve.d.ts.map +1 -1
  162. package/dist/rules/resolve.js +1 -4
  163. package/dist/rules/resolve.js.map +1 -1
  164. package/dist/rules/start-coordinator.d.ts.map +1 -1
  165. package/dist/rules/start-coordinator.js +3 -1
  166. package/dist/rules/start-coordinator.js.map +1 -1
  167. package/dist/rules/tools.d.ts.map +1 -1
  168. package/dist/rules/tools.js +20 -10
  169. package/dist/rules/tools.js.map +1 -1
  170. package/dist/rules/transition-service.d.ts +7 -7
  171. package/dist/rules/transition-service.d.ts.map +1 -1
  172. package/dist/rules/transition-service.js +21 -25
  173. package/dist/rules/transition-service.js.map +1 -1
  174. package/dist/rules/types.d.ts +16 -11
  175. package/dist/rules/types.d.ts.map +1 -1
  176. package/dist/rules/types.js.map +1 -1
  177. package/dist/rules/validation.d.ts +12 -2
  178. package/dist/rules/validation.d.ts.map +1 -1
  179. package/dist/rules/validation.js +29 -20
  180. package/dist/rules/validation.js.map +1 -1
  181. package/dist/session/live-sessions.d.ts +33 -3
  182. package/dist/session/live-sessions.d.ts.map +1 -1
  183. package/dist/session/live-sessions.js +67 -5
  184. package/dist/session/live-sessions.js.map +1 -1
  185. package/dist/storage/domains/intake/base.d.ts +54 -1
  186. package/dist/storage/domains/intake/base.d.ts.map +1 -1
  187. package/dist/storage/domains/intake/base.js +136 -4
  188. package/dist/storage/domains/intake/base.js.map +1 -1
  189. package/dist/storage/domains/work-items/base.d.ts +22 -7
  190. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  191. package/dist/storage/domains/work-items/base.js +41 -26
  192. package/dist/storage/domains/work-items/base.js.map +1 -1
  193. package/dist/supervisor/health-worker.d.ts +2 -0
  194. package/dist/supervisor/health-worker.d.ts.map +1 -1
  195. package/dist/supervisor/health-worker.js +3 -1
  196. package/dist/supervisor/health-worker.js.map +1 -1
  197. package/dist/supervisor/health.d.ts +12 -14
  198. package/dist/supervisor/health.d.ts.map +1 -1
  199. package/dist/supervisor/health.js +24 -52
  200. package/dist/supervisor/health.js.map +1 -1
  201. package/dist/supervisor/instructions.d.ts +1 -1
  202. package/dist/supervisor/instructions.d.ts.map +1 -1
  203. package/dist/supervisor/instructions.js +10 -8
  204. package/dist/supervisor/instructions.js.map +1 -1
  205. package/dist/supervisor/read-tools.d.ts +2 -0
  206. package/dist/supervisor/read-tools.d.ts.map +1 -1
  207. package/dist/supervisor/read-tools.js +2 -2
  208. package/dist/supervisor/read-tools.js.map +1 -1
  209. package/dist/supervisor/write-tools.d.ts.map +1 -1
  210. package/dist/supervisor/write-tools.js +6 -4
  211. package/dist/supervisor/write-tools.js.map +1 -1
  212. package/dist/telemetry-types.d.ts +7 -0
  213. package/dist/telemetry-types.d.ts.map +1 -0
  214. package/dist/telemetry-types.js +24 -0
  215. package/dist/telemetry-types.js.map +1 -0
  216. package/dist/telemetry.d.ts +9 -0
  217. package/dist/telemetry.d.ts.map +1 -0
  218. package/dist/telemetry.js +47 -0
  219. package/dist/telemetry.js.map +1 -0
  220. package/dist/work-item-branch.d.ts +2 -0
  221. package/dist/work-item-branch.d.ts.map +1 -1
  222. package/dist/work-item-branch.js +6 -1
  223. package/dist/work-item-branch.js.map +1 -1
  224. package/dist/workspace.d.ts.map +1 -1
  225. package/dist/workspace.js +3 -1
  226. package/dist/workspace.js.map +1 -1
  227. package/factory-skills/configure-factory-rules/SKILL.md +30 -10
  228. package/package.json +11 -9
  229. package/dist/rules/defaults.d.ts +0 -9
  230. package/dist/rules/defaults.d.ts.map +0 -1
  231. package/dist/rules/defaults.js +0 -50
  232. package/dist/rules/defaults.js.map +0 -1
@@ -1,4 +1,5 @@
1
1
  import { MastraWorker } from '@mastra/core/worker';
2
+ import type { BoardRegistry } from '../boards/index.js';
2
3
  import type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';
3
4
  import type { WorkItemsStorage } from '../storage/domains/work-items/base.js';
4
5
  export declare const DEFAULT_SUPERVISOR_HEALTH_INTERVAL_MS: number;
@@ -8,6 +9,7 @@ export declare class FactorySupervisorHealthWorker extends MastraWorker {
8
9
  constructor(input: {
9
10
  projects: FactoryProjectsStorage;
10
11
  workItems: WorkItemsStorage;
12
+ boards: BoardRegistry;
11
13
  intervalMs?: number;
12
14
  });
13
15
  start(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"health-worker.d.ts","sourceRoot":"","sources":["../../src/supervisor/health-worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAG9E,eAAO,MAAM,qCAAqC,QAAa,CAAC;AAEhE,qBAAa,6BAA8B,SAAQ,YAAY;;IAC7D,QAAQ,CAAC,IAAI,+BAA+B;gBAShC,KAAK,EAAE;QAAE,QAAQ,EAAE,sBAAsB,CAAC;QAAC,SAAS,EAAE,gBAAgB,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;IAOnG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAO3B,IAAI,SAAS,IAAI,OAAO,CAEvB;CA2CF"}
1
+ {"version":3,"file":"health-worker.d.ts","sourceRoot":"","sources":["../../src/supervisor/health-worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAG9E,eAAO,MAAM,qCAAqC,QAAa,CAAC;AAEhE,qBAAa,6BAA8B,SAAQ,YAAY;;IAC7D,QAAQ,CAAC,IAAI,+BAA+B;gBAUhC,KAAK,EAAE;QACjB,QAAQ,EAAE,sBAAsB,CAAC;QACjC,SAAS,EAAE,gBAAgB,CAAC;QAC5B,MAAM,EAAE,aAAa,CAAC;QACtB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB;IAQK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAO3B,IAAI,SAAS,IAAI,OAAO,CAEvB;CA4CF"}
@@ -6,6 +6,7 @@ var FactorySupervisorHealthWorker = class extends MastraWorker {
6
6
  name = "factory-supervisor-health";
7
7
  #projects;
8
8
  #workItems;
9
+ #boards;
9
10
  #intervalMs;
10
11
  #running = false;
11
12
  #timer;
@@ -14,6 +15,7 @@ var FactorySupervisorHealthWorker = class extends MastraWorker {
14
15
  super();
15
16
  this.#projects = input.projects;
16
17
  this.#workItems = input.workItems;
18
+ this.#boards = input.boards;
17
19
  this.#intervalMs = input.intervalMs ?? 3e5;
18
20
  }
19
21
  async start() {
@@ -51,7 +53,7 @@ var FactorySupervisorHealthWorker = class extends MastraWorker {
51
53
  while (nextIndex < projects.length) {
52
54
  const project = projects[nextIndex++];
53
55
  if (!project) return;
54
- const report = await runFactoryHealthCheck(this.#workItems, {
56
+ const report = await runFactoryHealthCheck(this.#workItems, this.#boards, {
55
57
  orgId: project.orgId,
56
58
  factoryProjectId: project.id
57
59
  }, { now });
@@ -1 +1 @@
1
- {"version":3,"file":"health-worker.js","names":["#projects","#workItems","#intervalMs","#running","#schedule","#timer","#inFlight","#tick"],"sources":["../../src/supervisor/health-worker.ts"],"sourcesContent":["import { MastraWorker } from '@mastra/core/worker';\n\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { runFactoryHealthCheck } from './health.js';\n\nexport const DEFAULT_SUPERVISOR_HEALTH_INTERVAL_MS = 5 * 60_000;\n\nexport class FactorySupervisorHealthWorker extends MastraWorker {\n readonly name = 'factory-supervisor-health';\n\n readonly #projects: FactoryProjectsStorage;\n readonly #workItems: WorkItemsStorage;\n readonly #intervalMs: number;\n #running = false;\n #timer: ReturnType<typeof setTimeout> | undefined;\n #inFlight: Promise<void> | undefined;\n\n constructor(input: { projects: FactoryProjectsStorage; workItems: WorkItemsStorage; intervalMs?: number }) {\n super();\n this.#projects = input.projects;\n this.#workItems = input.workItems;\n this.#intervalMs = input.intervalMs ?? DEFAULT_SUPERVISOR_HEALTH_INTERVAL_MS;\n }\n\n async start(): Promise<void> {\n if (this.#running) return;\n if (!this.deps) throw new Error('FactorySupervisorHealthWorker: call init() before start()');\n this.#running = true;\n this.#schedule(0);\n }\n\n async stop(): Promise<void> {\n this.#running = false;\n if (this.#timer) clearTimeout(this.#timer);\n this.#timer = undefined;\n await this.#inFlight;\n }\n\n get isRunning(): boolean {\n return this.#running;\n }\n\n #schedule(delayMs: number): void {\n if (!this.#running) return;\n this.#timer = setTimeout(() => {\n this.#timer = undefined;\n this.#inFlight = this.#tick()\n .catch(error => this.deps?.logger.error('Factory supervisor health sweep failed', { error }))\n .finally(() => {\n this.#inFlight = undefined;\n this.#schedule(this.#intervalMs);\n });\n }, delayMs);\n this.#timer.unref?.();\n }\n\n async #tick(): Promise<void> {\n const now = new Date();\n const projects = await this.#projects.listAll();\n const concurrency = 4;\n let nextIndex = 0;\n const results = await Promise.allSettled(\n Array.from({ length: Math.min(concurrency, projects.length) }, async () => {\n while (nextIndex < projects.length) {\n const project = projects[nextIndex++];\n if (!project) return;\n const report = await runFactoryHealthCheck(\n this.#workItems,\n { orgId: project.orgId, factoryProjectId: project.id },\n { now },\n );\n await this.#workItems.syncSupervisorFindings({\n orgId: project.orgId,\n factoryProjectId: project.id,\n findings: report.findings,\n now,\n });\n }\n }),\n );\n const failure = results.find((result): result is PromiseRejectedResult => result.status === 'rejected');\n if (failure) throw failure.reason;\n }\n}\n"],"mappings":";;;AAMA,MAAa,wCAAwC,IAAI;AAEzD,IAAa,gCAAb,cAAmD,aAAa;CAC9D,OAAgB;CAEhB;CACA;CACA;CACA,WAAW;CACX;CACA;CAEA,YAAY,OAA+F;EACzG,MAAM;EACN,KAAKA,YAAY,MAAM;EACvB,KAAKC,aAAa,MAAM;EACxB,KAAKC,cAAc,MAAM,cAAA;CAC3B;CAEA,MAAM,QAAuB;EAC3B,IAAI,KAAKC,UAAU;EACnB,IAAI,CAAC,KAAK,MAAM,MAAM,IAAI,MAAM,2DAA2D;EAC3F,KAAKA,WAAW;EAChB,KAAKC,UAAU,CAAC;CAClB;CAEA,MAAM,OAAsB;EAC1B,KAAKD,WAAW;EAChB,IAAI,KAAKE,QAAQ,aAAa,KAAKA,MAAM;EACzC,KAAKA,SAAS,KAAA;EACd,MAAM,KAAKC;CACb;CAEA,IAAI,YAAqB;EACvB,OAAO,KAAKH;CACd;CAEA,UAAU,SAAuB;EAC/B,IAAI,CAAC,KAAKA,UAAU;EACpB,KAAKE,SAAS,iBAAiB;GAC7B,KAAKA,SAAS,KAAA;GACd,KAAKC,YAAY,KAAKC,MAAM,CAAC,CAC1B,OAAM,UAAS,KAAK,MAAM,OAAO,MAAM,0CAA0C,EAAE,MAAM,CAAC,CAAC,CAAC,CAC5F,cAAc;IACb,KAAKD,YAAY,KAAA;IACjB,KAAKF,UAAU,KAAKF,WAAW;GACjC,CAAC;EACL,GAAG,OAAO;EACV,KAAKG,OAAO,QAAQ;CACtB;CAEA,MAAME,QAAuB;EAC3B,MAAM,sBAAM,IAAI,KAAK;EACrB,MAAM,WAAW,MAAM,KAAKP,UAAU,QAAQ;EAC9C,MAAM,cAAc;EACpB,IAAI,YAAY;EAoBhB,MAAM,WAAU,MAnBM,QAAQ,WAC5B,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,aAAa,SAAS,MAAM,EAAE,GAAG,YAAY;GACzE,OAAO,YAAY,SAAS,QAAQ;IAClC,MAAM,UAAU,SAAS;IACzB,IAAI,CAAC,SAAS;IACd,MAAM,SAAS,MAAM,sBACnB,KAAKC,YACL;KAAE,OAAO,QAAQ;KAAO,kBAAkB,QAAQ;IAAG,GACrD,EAAE,IAAI,CACR;IACA,MAAM,KAAKA,WAAW,uBAAuB;KAC3C,OAAO,QAAQ;KACf,kBAAkB,QAAQ;KAC1B,UAAU,OAAO;KACjB;IACF,CAAC;GACH;EACF,CAAC,CACH,EAAA,CACwB,MAAM,WAA4C,OAAO,WAAW,UAAU;EACtG,IAAI,SAAS,MAAM,QAAQ;CAC7B;AACF"}
1
+ {"version":3,"file":"health-worker.js","names":["#projects","#workItems","#boards","#intervalMs","#running","#schedule","#timer","#inFlight","#tick"],"sources":["../../src/supervisor/health-worker.ts"],"sourcesContent":["import { MastraWorker } from '@mastra/core/worker';\n\nimport type { BoardRegistry } from '../boards/index.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { runFactoryHealthCheck } from './health.js';\n\nexport const DEFAULT_SUPERVISOR_HEALTH_INTERVAL_MS = 5 * 60_000;\n\nexport class FactorySupervisorHealthWorker extends MastraWorker {\n readonly name = 'factory-supervisor-health';\n\n readonly #projects: FactoryProjectsStorage;\n readonly #workItems: WorkItemsStorage;\n readonly #boards: BoardRegistry;\n readonly #intervalMs: number;\n #running = false;\n #timer: ReturnType<typeof setTimeout> | undefined;\n #inFlight: Promise<void> | undefined;\n\n constructor(input: {\n projects: FactoryProjectsStorage;\n workItems: WorkItemsStorage;\n boards: BoardRegistry;\n intervalMs?: number;\n }) {\n super();\n this.#projects = input.projects;\n this.#workItems = input.workItems;\n this.#boards = input.boards;\n this.#intervalMs = input.intervalMs ?? DEFAULT_SUPERVISOR_HEALTH_INTERVAL_MS;\n }\n\n async start(): Promise<void> {\n if (this.#running) return;\n if (!this.deps) throw new Error('FactorySupervisorHealthWorker: call init() before start()');\n this.#running = true;\n this.#schedule(0);\n }\n\n async stop(): Promise<void> {\n this.#running = false;\n if (this.#timer) clearTimeout(this.#timer);\n this.#timer = undefined;\n await this.#inFlight;\n }\n\n get isRunning(): boolean {\n return this.#running;\n }\n\n #schedule(delayMs: number): void {\n if (!this.#running) return;\n this.#timer = setTimeout(() => {\n this.#timer = undefined;\n this.#inFlight = this.#tick()\n .catch(error => this.deps?.logger.error('Factory supervisor health sweep failed', { error }))\n .finally(() => {\n this.#inFlight = undefined;\n this.#schedule(this.#intervalMs);\n });\n }, delayMs);\n this.#timer.unref?.();\n }\n\n async #tick(): Promise<void> {\n const now = new Date();\n const projects = await this.#projects.listAll();\n const concurrency = 4;\n let nextIndex = 0;\n const results = await Promise.allSettled(\n Array.from({ length: Math.min(concurrency, projects.length) }, async () => {\n while (nextIndex < projects.length) {\n const project = projects[nextIndex++];\n if (!project) return;\n const report = await runFactoryHealthCheck(\n this.#workItems,\n this.#boards,\n { orgId: project.orgId, factoryProjectId: project.id },\n { now },\n );\n await this.#workItems.syncSupervisorFindings({\n orgId: project.orgId,\n factoryProjectId: project.id,\n findings: report.findings,\n now,\n });\n }\n }),\n );\n const failure = results.find((result): result is PromiseRejectedResult => result.status === 'rejected');\n if (failure) throw failure.reason;\n }\n}\n"],"mappings":";;;AAOA,MAAa,wCAAwC,IAAI;AAEzD,IAAa,gCAAb,cAAmD,aAAa;CAC9D,OAAgB;CAEhB;CACA;CACA;CACA;CACA,WAAW;CACX;CACA;CAEA,YAAY,OAKT;EACD,MAAM;EACN,KAAKA,YAAY,MAAM;EACvB,KAAKC,aAAa,MAAM;EACxB,KAAKC,UAAU,MAAM;EACrB,KAAKC,cAAc,MAAM,cAAA;CAC3B;CAEA,MAAM,QAAuB;EAC3B,IAAI,KAAKC,UAAU;EACnB,IAAI,CAAC,KAAK,MAAM,MAAM,IAAI,MAAM,2DAA2D;EAC3F,KAAKA,WAAW;EAChB,KAAKC,UAAU,CAAC;CAClB;CAEA,MAAM,OAAsB;EAC1B,KAAKD,WAAW;EAChB,IAAI,KAAKE,QAAQ,aAAa,KAAKA,MAAM;EACzC,KAAKA,SAAS,KAAA;EACd,MAAM,KAAKC;CACb;CAEA,IAAI,YAAqB;EACvB,OAAO,KAAKH;CACd;CAEA,UAAU,SAAuB;EAC/B,IAAI,CAAC,KAAKA,UAAU;EACpB,KAAKE,SAAS,iBAAiB;GAC7B,KAAKA,SAAS,KAAA;GACd,KAAKC,YAAY,KAAKC,MAAM,CAAC,CAC1B,OAAM,UAAS,KAAK,MAAM,OAAO,MAAM,0CAA0C,EAAE,MAAM,CAAC,CAAC,CAAC,CAC5F,cAAc;IACb,KAAKD,YAAY,KAAA;IACjB,KAAKF,UAAU,KAAKF,WAAW;GACjC,CAAC;EACL,GAAG,OAAO;EACV,KAAKG,OAAO,QAAQ;CACtB;CAEA,MAAME,QAAuB;EAC3B,MAAM,sBAAM,IAAI,KAAK;EACrB,MAAM,WAAW,MAAM,KAAKR,UAAU,QAAQ;EAC9C,MAAM,cAAc;EACpB,IAAI,YAAY;EAqBhB,MAAM,WAAU,MApBM,QAAQ,WAC5B,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,aAAa,SAAS,MAAM,EAAE,GAAG,YAAY;GACzE,OAAO,YAAY,SAAS,QAAQ;IAClC,MAAM,UAAU,SAAS;IACzB,IAAI,CAAC,SAAS;IACd,MAAM,SAAS,MAAM,sBACnB,KAAKC,YACL,KAAKC,SACL;KAAE,OAAO,QAAQ;KAAO,kBAAkB,QAAQ;IAAG,GACrD,EAAE,IAAI,CACR;IACA,MAAM,KAAKD,WAAW,uBAAuB;KAC3C,OAAO,QAAQ;KACf,kBAAkB,QAAQ;KAC1B,UAAU,OAAO;KACjB;IACF,CAAC;GACH;EACF,CAAC,CACH,EAAA,CACwB,MAAM,WAA4C,OAAO,WAAW,UAAU;EACtG,IAAI,SAAS,MAAM,QAAQ;CAC7B;AACF"}
@@ -7,18 +7,10 @@
7
7
  * the row it came from and the standard repair a person (or the supervisor's
8
8
  * write tools) would apply.
9
9
  */
10
+ import type { BoardRegistry, PhaseSemantics } from '../boards/index.js';
10
11
  import type { FactoryDeferredDecisionRecord, FactoryPendingStartRecord, FactoryRunBindingRecord, WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';
11
- export type FactoryHealthFindingKind = 'decision-failed' | 'decision-stuck' | 'start-stalled' | 'seat-orphaned' | 'seat-missing' | 'proposal-waiting' | 'held-waiting' | 'label-drift';
12
+ export type FactoryHealthFindingKind = 'decision-stuck' | 'start-stalled' | 'seat-orphaned' | 'seat-missing' | 'held-waiting' | 'label-drift';
12
13
  export type FactoryHealthRepair = {
13
- action: 'retry-decision';
14
- decisionId: string;
15
- } | {
16
- action: 'dismiss-decision';
17
- decisionId: string;
18
- } | {
19
- action: 'resolve-proposal';
20
- decisionId: string;
21
- } | {
22
14
  action: 'revoke-binding';
23
15
  bindingId: string;
24
16
  } | {
@@ -42,8 +34,8 @@ export interface FactoryHealthFinding {
42
34
  title: string;
43
35
  /** One sentence of grounded evidence: ids, timestamps, error text. */
44
36
  evidence: string;
45
- /** How long the condition has held, in ms, when it is age-based. */
46
- ageMs: number | null;
37
+ /** ISO instant the condition began, when it is age-based. */
38
+ beganAt: string | null;
47
39
  suggestedRepair: FactoryHealthRepair | null;
48
40
  }
49
41
  export interface FactoryHealthReport {
@@ -58,7 +50,7 @@ export interface FactoryHealthThresholds {
58
50
  expiredLeaseMs: number;
59
51
  /** A pending start this old is not "booting", it is stalled. */
60
52
  stalledStartMs: number;
61
- /** Proposals and held cards older than this are worth nudging a person about. */
53
+ /** Held cards older than this are worth nudging a maintainer about. */
62
54
  waitingOnPersonMs: number;
63
55
  }
64
56
  export declare const DEFAULT_HEALTH_THRESHOLDS: FactoryHealthThresholds;
@@ -67,10 +59,16 @@ interface HealthInputs {
67
59
  decisions: FactoryDeferredDecisionRecord[];
68
60
  bindings: FactoryRunBindingRecord[];
69
61
  pendingStarts: FactoryPendingStartRecord[];
62
+ /**
63
+ * What the item's installed board says its current phase means. Undefined when the
64
+ * board is not installed, the phase is undeclared, or the row spans several stages;
65
+ * the check then neither revokes nor starts anything on a guess.
66
+ */
67
+ phaseSemantics: (item: WorkItemRow) => PhaseSemantics | undefined;
70
68
  }
71
69
  /** Pure: findings from rows. Exported so tests can feed fixtures directly. */
72
70
  export declare function computeFactoryHealth(inputs: HealthInputs, now: Date, thresholds?: FactoryHealthThresholds): FactoryHealthReport;
73
- export declare function runFactoryHealthCheck(workItems: WorkItemsStorage, scope: {
71
+ export declare function runFactoryHealthCheck(workItems: WorkItemsStorage, boards: BoardRegistry, scope: {
74
72
  orgId: string;
75
73
  factoryProjectId: string;
76
74
  }, options?: {
@@ -1 +1 @@
1
- {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/supervisor/health.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,OAAO,KAAK,EACV,6BAA6B,EAC7B,yBAAyB,EACzB,uBAAuB,EACvB,WAAW,EACX,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAE/C,MAAM,MAAM,wBAAwB,GAChC,iBAAiB,GACjB,gBAAgB,GAChB,eAAe,GACf,eAAe,GACf,cAAc,GACd,kBAAkB,GAClB,cAAc,GACd,aAAa,CAAC;AAElB,MAAM,MAAM,mBAAmB,GAC3B;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzD;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvD,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,wBAAwB,CAAC;IAC/B,2EAA2E;IAC3E,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,mEAAmE;IACnE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,eAAe,EAAE,mBAAmB,GAAG,IAAI,CAAC;CAC7C;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,oBAAoB,EAAE,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,uBAAuB;IACtC,gGAAgG;IAChG,eAAe,EAAE,MAAM,CAAC;IACxB,mFAAmF;IACnF,cAAc,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,cAAc,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,eAAO,MAAM,yBAAyB,EAAE,uBAKvC,CAAC;AAqDF,UAAU,YAAY;IACpB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,SAAS,EAAE,6BAA6B,EAAE,CAAC;IAC3C,QAAQ,EAAE,uBAAuB,EAAE,CAAC;IACpC,aAAa,EAAE,yBAAyB,EAAE,CAAC;CAC5C;AAED,8EAA8E;AAC9E,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,IAAI,EACT,UAAU,GAAE,uBAAmD,GAC9D,mBAAmB,CA2JrB;AAED,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,gBAAgB,EAC3B,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,EAClD,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,IAAI,CAAC;IAAC,UAAU,CAAC,EAAE,uBAAuB,CAAA;CAAO,GACjE,OAAO,CAAC,mBAAmB,CAAC,CAY9B"}
1
+ {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/supervisor/health.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAExE,OAAO,KAAK,EACV,6BAA6B,EAC7B,yBAAyB,EACzB,uBAAuB,EACvB,WAAW,EACX,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAE/C,MAAM,MAAM,wBAAwB,GAChC,gBAAgB,GAChB,eAAe,GACf,eAAe,GACf,cAAc,GACd,cAAc,GACd,aAAa,CAAC;AAElB,MAAM,MAAM,mBAAmB,GAC3B;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzD;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvD,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,wBAAwB,CAAC;IAC/B,2EAA2E;IAC3E,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,mEAAmE;IACnE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,eAAe,EAAE,mBAAmB,GAAG,IAAI,CAAC;CAC7C;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,oBAAoB,EAAE,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,uBAAuB;IACtC,gGAAgG;IAChG,eAAe,EAAE,MAAM,CAAC;IACxB,mFAAmF;IACnF,cAAc,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,cAAc,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,eAAO,MAAM,yBAAyB,EAAE,uBAKvC,CAAC;AAoDF,UAAU,YAAY;IACpB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,SAAS,EAAE,6BAA6B,EAAE,CAAC;IAC3C,QAAQ,EAAE,uBAAuB,EAAE,CAAC;IACpC,aAAa,EAAE,yBAAyB,EAAE,CAAC;IAC3C;;;;OAIG;IACH,cAAc,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,cAAc,GAAG,SAAS,CAAC;CACnE;AAED,8EAA8E;AAC9E,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,IAAI,EACT,UAAU,GAAE,uBAAmD,GAC9D,mBAAmB,CA0IrB;AAED,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,gBAAgB,EAC3B,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,EAClD,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,IAAI,CAAC;IAAC,UAAU,CAAC,EAAE,uBAAuB,CAAA;CAAO,GACjE,OAAO,CAAC,mBAAmB,CAAC,CAY9B"}
@@ -1,4 +1,6 @@
1
- import { FACTORY_ROLE_STAGES, NEEDS_APPROVAL_LABEL, factoryRuleStage, isTerminalFactoryRuleStage } from "../rules/types.js";
1
+ import { boardForWorkItem, workItemPhaseSemantics } from "../boards/semantics.js";
2
+ import { NEEDS_APPROVAL_LABEL, factoryRuleStage } from "../rules/types.js";
3
+ import "../boards/index.js";
2
4
  //#region src/supervisor/health.ts
3
5
  /**
4
6
  * Deterministic Factory health check.
@@ -16,12 +18,10 @@ const DEFAULT_HEALTH_THRESHOLDS = {
16
18
  waitingOnPersonMs: 1440 * 6e4
17
19
  };
18
20
  const FINDING_KINDS = [
19
- "decision-failed",
20
21
  "decision-stuck",
21
22
  "start-stalled",
22
23
  "seat-orphaned",
23
24
  "seat-missing",
24
- "proposal-waiting",
25
25
  "held-waiting",
26
26
  "label-drift"
27
27
  ];
@@ -31,8 +31,6 @@ const IN_FLIGHT_DECISION_STATUSES = /* @__PURE__ */ new Set([
31
31
  "leased",
32
32
  "proposed"
33
33
  ]);
34
- /** Working lane → the role whose seat carries a card through it. */
35
- const ROLE_FOR_LANE = new Map(Object.entries(FACTORY_ROLE_STAGES).map(([role, lane]) => [lane, role]));
36
34
  function itemNumber(item) {
37
35
  const number = item?.metadata?.number ?? item?.metadata?.githubIssueNumber ?? item?.metadata?.githubPullRequestNumber;
38
36
  return typeof number === "number" ? number : null;
@@ -61,6 +59,9 @@ function stageEnteredAt(item) {
61
59
  const at = open ? Date.parse(open.enteredAt) : NaN;
62
60
  return Number.isFinite(at) ? new Date(at) : null;
63
61
  }
62
+ function beganAtMs(finding) {
63
+ return finding.beganAt === null ? Number.MAX_SAFE_INTEGER : Date.parse(finding.beganAt);
64
+ }
64
65
  /** Pure: findings from rows. Exported so tests can feed fixtures directly. */
65
66
  function computeFactoryHealth(inputs, now, thresholds = DEFAULT_HEALTH_THRESHOLDS) {
66
67
  const itemsById = new Map(inputs.items.map((item) => [item.id, item]));
@@ -75,57 +76,26 @@ function computeFactoryHealth(inputs, now, thresholds = DEFAULT_HEALTH_THRESHOLD
75
76
  };
76
77
  for (const decision of inputs.decisions) {
77
78
  const base = subject(decision.workItemId);
78
- if (decision.status === "failed") {
79
- findings.push({
80
- kind: "decision-failed",
81
- id: `decision-failed:${decision.id}`,
82
- ...base,
83
- evidence: `Decision ${decision.id} (${describeDecision(decision)}) failed after ${decision.attempts} attempt(s) at ${decision.updatedAt.toISOString()}${decision.failureCode ? ` [${decision.failureCode}]` : ""}: ${truncate(decision.lastError ?? "no error recorded")}`,
84
- ageMs: now.getTime() - decision.updatedAt.getTime(),
85
- suggestedRepair: {
86
- action: "retry-decision",
87
- decisionId: decision.id
88
- }
89
- });
90
- continue;
91
- }
92
79
  if (decision.status === "retry" || decision.status === "pending") {
93
- const overdue = now.getTime() - decision.availableAt.getTime();
94
- if (overdue > thresholds.stuckDecisionMs) findings.push({
80
+ if (now.getTime() - decision.availableAt.getTime() > thresholds.stuckDecisionMs) findings.push({
95
81
  kind: "decision-stuck",
96
82
  id: `decision-stuck:${decision.id}`,
97
83
  ...base,
98
84
  evidence: `Decision ${decision.id} (${describeDecision(decision)}) has been ${decision.status} since ${decision.availableAt.toISOString()} with ${decision.attempts} attempt(s) and was never leased; is the dispatcher running?`,
99
- ageMs: overdue,
85
+ beganAt: decision.availableAt.toISOString(),
100
86
  suggestedRepair: null
101
87
  });
102
88
  continue;
103
89
  }
104
90
  if (decision.status === "leased" && decision.leaseExpiresAt) {
105
- const expired = now.getTime() - decision.leaseExpiresAt.getTime();
106
- if (expired > thresholds.expiredLeaseMs) findings.push({
91
+ if (now.getTime() - decision.leaseExpiresAt.getTime() > thresholds.expiredLeaseMs) findings.push({
107
92
  kind: "decision-stuck",
108
93
  id: `decision-stuck:${decision.id}`,
109
94
  ...base,
110
95
  evidence: `Decision ${decision.id} (${describeDecision(decision)}) is still leased by ${decision.leaseOwner ?? "unknown"} though the lease expired at ${decision.leaseExpiresAt.toISOString()}; the worker likely died mid-dispatch.`,
111
- ageMs: expired,
96
+ beganAt: decision.leaseExpiresAt.toISOString(),
112
97
  suggestedRepair: null
113
98
  });
114
- continue;
115
- }
116
- if (decision.status === "proposed") {
117
- const waiting = now.getTime() - decision.createdAt.getTime();
118
- if (waiting > thresholds.waitingOnPersonMs) findings.push({
119
- kind: "proposal-waiting",
120
- id: `proposal-waiting:${decision.id}`,
121
- ...base,
122
- evidence: `Proposed run ${decision.id} (${describeDecision(decision)}) has waited for a person since ${decision.createdAt.toISOString()}.`,
123
- ageMs: waiting,
124
- suggestedRepair: {
125
- action: "resolve-proposal",
126
- decisionId: decision.id
127
- }
128
- });
129
99
  }
130
100
  }
131
101
  const bindingsById = new Map(inputs.bindings.map((binding) => [binding.id, binding]));
@@ -139,7 +109,7 @@ function computeFactoryHealth(inputs, now, thresholds = DEFAULT_HEALTH_THRESHOLD
139
109
  id: `start-stalled:${start.id}`,
140
110
  ...base,
141
111
  evidence: `Pending start ${start.id}${binding ? ` for the ${binding.role} seat` : ""} is ${start.status} since ${start.createdAt.toISOString()} after ${start.attempts} attempt(s)${start.lastError ? `: ${truncate(start.lastError)}` : "."}`,
142
- ageMs: age,
112
+ beganAt: start.createdAt.toISOString(),
143
113
  suggestedRepair: binding ? {
144
114
  action: "revoke-binding",
145
115
  bindingId: binding.id
@@ -151,13 +121,13 @@ function computeFactoryHealth(inputs, now, thresholds = DEFAULT_HEALTH_THRESHOLD
151
121
  if (binding.status !== "active") continue;
152
122
  const item = itemsById.get(binding.workItemId);
153
123
  const stage = item ? factoryRuleStage(item.stages) : void 0;
154
- if (!item || !stage || isTerminalFactoryRuleStage([stage])) {
124
+ if (!item || inputs.phaseSemantics(item)?.kind === "terminal") {
155
125
  findings.push({
156
126
  kind: "seat-orphaned",
157
127
  id: `seat-orphaned:${binding.id}`,
158
128
  ...subject(item ? item.id : null),
159
129
  evidence: item ? `Binding ${binding.id} (${binding.role}) is still active though the card is in ${stage ?? item.stages.join("+")}.` : `Binding ${binding.id} (${binding.role}) is active for work item ${binding.workItemId}, which no longer exists.`,
160
- ageMs: now.getTime() - binding.createdAt.getTime(),
130
+ beganAt: item ? stageEnteredAt(item)?.toISOString() ?? null : null,
161
131
  suggestedRepair: {
162
132
  action: "revoke-binding",
163
133
  bindingId: binding.id
@@ -172,30 +142,31 @@ function computeFactoryHealth(inputs, now, thresholds = DEFAULT_HEALTH_THRESHOLD
172
142
  const inFlightByItem = new Set(inputs.decisions.filter((d) => d.workItemId && IN_FLIGHT_DECISION_STATUSES.has(d.status)).map((d) => d.workItemId));
173
143
  for (const item of inputs.items) {
174
144
  const stage = factoryRuleStage(item.stages);
175
- if (!stage || stage === "intake" || isTerminalFactoryRuleStage([stage])) continue;
145
+ const semantics = stage ? inputs.phaseSemantics(item) : void 0;
146
+ if (!stage || semantics?.kind !== "working") continue;
176
147
  const base = subject(item.id);
177
148
  const enteredAt = stageEnteredAt(item);
178
149
  const inStageMs = enteredAt ? now.getTime() - enteredAt.getTime() : null;
179
- if (stage === "triage" && item.triageType !== null && item.triageType !== "bug" && !item.acceptedAt) {
150
+ if (boardForWorkItem(item) === "work" && stage === "triage" && item.triageType !== null && item.triageType !== "bug" && !item.acceptedAt) {
180
151
  if (inStageMs !== null && inStageMs > thresholds.waitingOnPersonMs) findings.push({
181
152
  kind: "held-waiting",
182
153
  id: `held-waiting:${item.id}`,
183
154
  ...base,
184
155
  evidence: `Triaged as "${item.triageType}" and waiting for a maintainer's decision since ${enteredAt.toISOString()}.`,
185
- ageMs: inStageMs,
156
+ beganAt: enteredAt.toISOString(),
186
157
  suggestedRepair: {
187
158
  action: "accept-work-item",
188
159
  workItemId: item.id
189
160
  }
190
161
  });
191
162
  } else if (!activeSeatsByItem.has(item.id) && !inFlightByItem.has(item.id)) {
192
- const role = ROLE_FOR_LANE.get(stage);
163
+ const role = semantics.role;
193
164
  findings.push({
194
165
  kind: "seat-missing",
195
166
  id: `seat-missing:${item.id}`,
196
167
  ...base,
197
168
  evidence: `In ${stage} since ${enteredAt?.toISOString() ?? "unknown"} with no active seat and no decision in flight; nothing will move it.`,
198
- ageMs: inStageMs,
169
+ beganAt: enteredAt?.toISOString() ?? null,
199
170
  suggestedRepair: role ? {
200
171
  action: "start-run",
201
172
  workItemId: item.id,
@@ -208,14 +179,14 @@ function computeFactoryHealth(inputs, now, thresholds = DEFAULT_HEALTH_THRESHOLD
208
179
  id: `label-drift:${item.id}`,
209
180
  ...base,
210
181
  evidence: `Accepted at ${item.acceptedAt.toISOString()} but the last observed labels still include "${NEEDS_APPROVAL_LABEL}".`,
211
- ageMs: now.getTime() - item.acceptedAt.getTime(),
182
+ beganAt: item.acceptedAt.toISOString(),
212
183
  suggestedRepair: {
213
184
  action: "reconcile-labels",
214
185
  workItemId: item.id
215
186
  }
216
187
  });
217
188
  }
218
- findings.sort((a, b) => (b.ageMs ?? 0) - (a.ageMs ?? 0));
189
+ findings.sort((a, b) => beganAtMs(a) - beganAtMs(b) || a.id.localeCompare(b.id));
219
190
  const counts = Object.fromEntries(FINDING_KINDS.map((kind) => [kind, 0]));
220
191
  for (const finding of findings) counts[finding.kind] += 1;
221
192
  return {
@@ -224,7 +195,7 @@ function computeFactoryHealth(inputs, now, thresholds = DEFAULT_HEALTH_THRESHOLD
224
195
  counts
225
196
  };
226
197
  }
227
- async function runFactoryHealthCheck(workItems, scope, options = {}) {
198
+ async function runFactoryHealthCheck(workItems, boards, scope, options = {}) {
228
199
  const [items, decisions, bindings, pendingStarts] = await Promise.all([
229
200
  workItems.list(scope),
230
201
  workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId),
@@ -235,7 +206,8 @@ async function runFactoryHealthCheck(workItems, scope, options = {}) {
235
206
  items,
236
207
  decisions,
237
208
  bindings,
238
- pendingStarts
209
+ pendingStarts,
210
+ phaseSemantics: (item) => workItemPhaseSemantics(boards, item)
239
211
  }, options.now ?? /* @__PURE__ */ new Date(), options.thresholds);
240
212
  }
241
213
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"health.js","names":[],"sources":["../../src/supervisor/health.ts"],"sourcesContent":["/**\n * Deterministic Factory health check.\n *\n * Every finding is computed from storage rows alone — no model, no controller —\n * so the supervisor agent explains findings rather than inventing them, and the\n * same list can be rendered by the UI or polled by a timer. Each finding names\n * the row it came from and the standard repair a person (or the supervisor's\n * write tools) would apply.\n */\n\nimport {\n FACTORY_ROLE_STAGES,\n factoryRuleStage,\n isTerminalFactoryRuleStage,\n NEEDS_APPROVAL_LABEL,\n} from '../rules/types.js';\nimport type {\n FactoryDeferredDecisionRecord,\n FactoryPendingStartRecord,\n FactoryRunBindingRecord,\n WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\n\nexport type FactoryHealthFindingKind =\n | 'decision-failed'\n | 'decision-stuck'\n | 'start-stalled'\n | 'seat-orphaned'\n | 'seat-missing'\n | 'proposal-waiting'\n | 'held-waiting'\n | 'label-drift';\n\nexport type FactoryHealthRepair =\n | { action: 'retry-decision'; decisionId: string }\n | { action: 'dismiss-decision'; decisionId: string }\n | { action: 'resolve-proposal'; decisionId: string }\n | { action: 'revoke-binding'; bindingId: string }\n | { action: 'accept-work-item'; workItemId: string }\n | { action: 'start-run'; workItemId: string; role: string }\n | { action: 'reconcile-labels'; workItemId: string };\n\nexport interface FactoryHealthFinding {\n kind: FactoryHealthFindingKind;\n /** Stable per (kind, subject) so the UI and attention inbox can dedupe. */\n id: string;\n workItemId: string | null;\n /** Card number as shown on the board, when the row carries one. */\n workItemNumber: number | null;\n title: string;\n /** One sentence of grounded evidence: ids, timestamps, error text. */\n evidence: string;\n /** How long the condition has held, in ms, when it is age-based. */\n ageMs: number | null;\n suggestedRepair: FactoryHealthRepair | null;\n}\n\nexport interface FactoryHealthReport {\n checkedAt: string;\n findings: FactoryHealthFinding[];\n counts: Record<FactoryHealthFindingKind, number>;\n}\n\nexport interface FactoryHealthThresholds {\n /** A retry/pending decision whose `availableAt` is this far in the past never got picked up. */\n stuckDecisionMs: number;\n /** A lease this far past its expiry belongs to a worker that died mid-dispatch. */\n expiredLeaseMs: number;\n /** A pending start this old is not \"booting\", it is stalled. */\n stalledStartMs: number;\n /** Proposals and held cards older than this are worth nudging a person about. */\n waitingOnPersonMs: number;\n}\n\nexport const DEFAULT_HEALTH_THRESHOLDS: FactoryHealthThresholds = {\n stuckDecisionMs: 10 * 60_000,\n expiredLeaseMs: 5 * 60_000,\n stalledStartMs: 10 * 60_000,\n waitingOnPersonMs: 24 * 60 * 60_000,\n};\n\nconst FINDING_KINDS: FactoryHealthFindingKind[] = [\n 'decision-failed',\n 'decision-stuck',\n 'start-stalled',\n 'seat-orphaned',\n 'seat-missing',\n 'proposal-waiting',\n 'held-waiting',\n 'label-drift',\n];\n\nconst IN_FLIGHT_DECISION_STATUSES = new Set(['pending', 'retry', 'leased', 'proposed']);\n\n/** Working lane → the role whose seat carries a card through it. */\nconst ROLE_FOR_LANE = new Map<string, string>(Object.entries(FACTORY_ROLE_STAGES).map(([role, lane]) => [lane, role]));\n\nfunction itemNumber(item: WorkItemRow | undefined): number | null {\n const number = item?.metadata?.number ?? item?.metadata?.githubIssueNumber ?? item?.metadata?.githubPullRequestNumber;\n return typeof number === 'number' ? number : null;\n}\n\nfunction itemLabels(item: WorkItemRow): string[] {\n const labels = item.metadata?.labels;\n return Array.isArray(labels) ? labels.filter((label): label is string => typeof label === 'string') : [];\n}\n\nfunction decisionType(decision: FactoryDeferredDecisionRecord): string {\n const type = decision.decision.type;\n return typeof type === 'string' ? type : 'decision';\n}\n\nfunction decisionRole(decision: FactoryDeferredDecisionRecord): string | null {\n const role = decision.decision.role;\n return typeof role === 'string' ? role : null;\n}\n\nfunction describeDecision(decision: FactoryDeferredDecisionRecord): string {\n const role = decisionRole(decision);\n return role ? `${decisionType(decision)} (${role})` : decisionType(decision);\n}\n\nfunction truncate(text: string, max = 200): string {\n return text.length > max ? `${text.slice(0, max - 1)}…` : text;\n}\n\nfunction stageEnteredAt(item: WorkItemRow): Date | null {\n const open = [...item.stageHistory].reverse().find(entry => !entry.exitedAt);\n const at = open ? Date.parse(open.enteredAt) : Number.NaN;\n return Number.isFinite(at) ? new Date(at) : null;\n}\n\ninterface HealthInputs {\n items: WorkItemRow[];\n decisions: FactoryDeferredDecisionRecord[];\n bindings: FactoryRunBindingRecord[];\n pendingStarts: FactoryPendingStartRecord[];\n}\n\n/** Pure: findings from rows. Exported so tests can feed fixtures directly. */\nexport function computeFactoryHealth(\n inputs: HealthInputs,\n now: Date,\n thresholds: FactoryHealthThresholds = DEFAULT_HEALTH_THRESHOLDS,\n): FactoryHealthReport {\n const itemsById = new Map(inputs.items.map(item => [item.id, item]));\n const findings: FactoryHealthFinding[] = [];\n const subject = (workItemId: string | null) => {\n const item = workItemId ? itemsById.get(workItemId) : undefined;\n return { workItemId, workItemNumber: itemNumber(item), title: item?.title ?? '' };\n };\n\n for (const decision of inputs.decisions) {\n const base = subject(decision.workItemId);\n if (decision.status === 'failed') {\n findings.push({\n kind: 'decision-failed',\n id: `decision-failed:${decision.id}`,\n ...base,\n evidence: `Decision ${decision.id} (${describeDecision(decision)}) failed after ${decision.attempts} attempt(s) at ${decision.updatedAt.toISOString()}${decision.failureCode ? ` [${decision.failureCode}]` : ''}: ${truncate(decision.lastError ?? 'no error recorded')}`,\n ageMs: now.getTime() - decision.updatedAt.getTime(),\n suggestedRepair: { action: 'retry-decision', decisionId: decision.id },\n });\n continue;\n }\n if (decision.status === 'retry' || decision.status === 'pending') {\n const overdue = now.getTime() - decision.availableAt.getTime();\n if (overdue > thresholds.stuckDecisionMs) {\n findings.push({\n kind: 'decision-stuck',\n id: `decision-stuck:${decision.id}`,\n ...base,\n evidence: `Decision ${decision.id} (${describeDecision(decision)}) has been ${decision.status} since ${decision.availableAt.toISOString()} with ${decision.attempts} attempt(s) and was never leased; is the dispatcher running?`,\n ageMs: overdue,\n suggestedRepair: null,\n });\n }\n continue;\n }\n if (decision.status === 'leased' && decision.leaseExpiresAt) {\n const expired = now.getTime() - decision.leaseExpiresAt.getTime();\n if (expired > thresholds.expiredLeaseMs) {\n findings.push({\n kind: 'decision-stuck',\n id: `decision-stuck:${decision.id}`,\n ...base,\n evidence: `Decision ${decision.id} (${describeDecision(decision)}) is still leased by ${decision.leaseOwner ?? 'unknown'} though the lease expired at ${decision.leaseExpiresAt.toISOString()}; the worker likely died mid-dispatch.`,\n ageMs: expired,\n suggestedRepair: null,\n });\n }\n continue;\n }\n if (decision.status === 'proposed') {\n const waiting = now.getTime() - decision.createdAt.getTime();\n if (waiting > thresholds.waitingOnPersonMs) {\n findings.push({\n kind: 'proposal-waiting',\n id: `proposal-waiting:${decision.id}`,\n ...base,\n evidence: `Proposed run ${decision.id} (${describeDecision(decision)}) has waited for a person since ${decision.createdAt.toISOString()}.`,\n ageMs: waiting,\n suggestedRepair: { action: 'resolve-proposal', decisionId: decision.id },\n });\n }\n }\n }\n\n const bindingsById = new Map(inputs.bindings.map(binding => [binding.id, binding]));\n for (const start of inputs.pendingStarts) {\n if (start.status === 'sent') continue;\n const binding = bindingsById.get(start.bindingId);\n const base = subject(binding?.workItemId ?? null);\n const age = now.getTime() - start.createdAt.getTime();\n if (start.status === 'failed' || age > thresholds.stalledStartMs) {\n findings.push({\n kind: 'start-stalled',\n id: `start-stalled:${start.id}`,\n ...base,\n evidence: `Pending start ${start.id}${binding ? ` for the ${binding.role} seat` : ''} is ${start.status} since ${start.createdAt.toISOString()} after ${start.attempts} attempt(s)${start.lastError ? `: ${truncate(start.lastError)}` : '.'}`,\n ageMs: age,\n suggestedRepair: binding ? { action: 'revoke-binding', bindingId: binding.id } : null,\n });\n }\n }\n\n const activeSeatsByItem = new Map<string, FactoryRunBindingRecord[]>();\n for (const binding of inputs.bindings) {\n if (binding.status !== 'active') continue;\n const item = itemsById.get(binding.workItemId);\n const stage = item ? factoryRuleStage(item.stages) : undefined;\n if (!item || !stage || isTerminalFactoryRuleStage([stage])) {\n findings.push({\n kind: 'seat-orphaned',\n id: `seat-orphaned:${binding.id}`,\n ...subject(item ? item.id : null),\n evidence: item\n ? `Binding ${binding.id} (${binding.role}) is still active though the card is in ${stage ?? item.stages.join('+')}.`\n : `Binding ${binding.id} (${binding.role}) is active for work item ${binding.workItemId}, which no longer exists.`,\n ageMs: now.getTime() - binding.createdAt.getTime(),\n suggestedRepair: { action: 'revoke-binding', bindingId: binding.id },\n });\n continue;\n }\n const seats = activeSeatsByItem.get(item.id) ?? [];\n seats.push(binding);\n activeSeatsByItem.set(item.id, seats);\n }\n\n const inFlightByItem = new Set(\n inputs.decisions.filter(d => d.workItemId && IN_FLIGHT_DECISION_STATUSES.has(d.status)).map(d => d.workItemId!),\n );\n for (const item of inputs.items) {\n const stage = factoryRuleStage(item.stages);\n if (!stage || stage === 'intake' || isTerminalFactoryRuleStage([stage])) continue;\n const base = subject(item.id);\n const enteredAt = stageEnteredAt(item);\n const inStageMs = enteredAt ? now.getTime() - enteredAt.getTime() : null;\n\n const held = stage === 'triage' && item.triageType !== null && item.triageType !== 'bug' && !item.acceptedAt;\n if (held) {\n if (inStageMs !== null && inStageMs > thresholds.waitingOnPersonMs) {\n findings.push({\n kind: 'held-waiting',\n id: `held-waiting:${item.id}`,\n ...base,\n evidence: `Triaged as \"${item.triageType}\" and waiting for a maintainer's decision since ${enteredAt!.toISOString()}.`,\n ageMs: inStageMs,\n suggestedRepair: { action: 'accept-work-item', workItemId: item.id },\n });\n }\n } else if (!activeSeatsByItem.has(item.id) && !inFlightByItem.has(item.id)) {\n const role = ROLE_FOR_LANE.get(stage);\n findings.push({\n kind: 'seat-missing',\n id: `seat-missing:${item.id}`,\n ...base,\n evidence: `In ${stage} since ${enteredAt?.toISOString() ?? 'unknown'} with no active seat and no decision in flight; nothing will move it.`,\n ageMs: inStageMs,\n suggestedRepair: role ? { action: 'start-run', workItemId: item.id, role } : null,\n });\n }\n\n if (item.acceptedAt && itemLabels(item).includes(NEEDS_APPROVAL_LABEL)) {\n findings.push({\n kind: 'label-drift',\n id: `label-drift:${item.id}`,\n ...base,\n evidence: `Accepted at ${item.acceptedAt.toISOString()} but the last observed labels still include \"${NEEDS_APPROVAL_LABEL}\".`,\n ageMs: now.getTime() - item.acceptedAt.getTime(),\n suggestedRepair: { action: 'reconcile-labels', workItemId: item.id },\n });\n }\n }\n\n findings.sort((a, b) => (b.ageMs ?? 0) - (a.ageMs ?? 0));\n const counts = Object.fromEntries(FINDING_KINDS.map(kind => [kind, 0])) as Record<FactoryHealthFindingKind, number>;\n for (const finding of findings) counts[finding.kind] += 1;\n return { checkedAt: now.toISOString(), findings, counts };\n}\n\nexport async function runFactoryHealthCheck(\n workItems: WorkItemsStorage,\n scope: { orgId: string; factoryProjectId: string },\n options: { now?: Date; thresholds?: FactoryHealthThresholds } = {},\n): Promise<FactoryHealthReport> {\n const [items, decisions, bindings, pendingStarts] = await Promise.all([\n workItems.list(scope),\n workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId),\n workItems.listRunBindings(scope.orgId, scope.factoryProjectId),\n workItems.listPendingStarts(scope.orgId, scope.factoryProjectId),\n ]);\n return computeFactoryHealth(\n { items, decisions, bindings, pendingStarts },\n options.now ?? new Date(),\n options.thresholds,\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"health.js","names":[],"sources":["../../src/supervisor/health.ts"],"sourcesContent":["/**\n * Deterministic Factory health check.\n *\n * Every finding is computed from storage rows alone — no model, no controller —\n * so the supervisor agent explains findings rather than inventing them, and the\n * same list can be rendered by the UI or polled by a timer. Each finding names\n * the row it came from and the standard repair a person (or the supervisor's\n * write tools) would apply.\n */\n\nimport { boardForWorkItem, workItemPhaseSemantics } from '../boards/index.js';\nimport type { BoardRegistry, PhaseSemantics } from '../boards/index.js';\nimport { factoryRuleStage, NEEDS_APPROVAL_LABEL } from '../rules/types.js';\nimport type {\n FactoryDeferredDecisionRecord,\n FactoryPendingStartRecord,\n FactoryRunBindingRecord,\n WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\n\nexport type FactoryHealthFindingKind =\n | 'decision-stuck'\n | 'start-stalled'\n | 'seat-orphaned'\n | 'seat-missing'\n | 'held-waiting'\n | 'label-drift';\n\nexport type FactoryHealthRepair =\n | { action: 'revoke-binding'; bindingId: string }\n | { action: 'accept-work-item'; workItemId: string }\n | { action: 'start-run'; workItemId: string; role: string }\n | { action: 'reconcile-labels'; workItemId: string };\n\nexport interface FactoryHealthFinding {\n kind: FactoryHealthFindingKind;\n /** Stable per (kind, subject) so the UI and attention inbox can dedupe. */\n id: string;\n workItemId: string | null;\n /** Card number as shown on the board, when the row carries one. */\n workItemNumber: number | null;\n title: string;\n /** One sentence of grounded evidence: ids, timestamps, error text. */\n evidence: string;\n /** ISO instant the condition began, when it is age-based. */\n beganAt: string | null;\n suggestedRepair: FactoryHealthRepair | null;\n}\n\nexport interface FactoryHealthReport {\n checkedAt: string;\n findings: FactoryHealthFinding[];\n counts: Record<FactoryHealthFindingKind, number>;\n}\n\nexport interface FactoryHealthThresholds {\n /** A retry/pending decision whose `availableAt` is this far in the past never got picked up. */\n stuckDecisionMs: number;\n /** A lease this far past its expiry belongs to a worker that died mid-dispatch. */\n expiredLeaseMs: number;\n /** A pending start this old is not \"booting\", it is stalled. */\n stalledStartMs: number;\n /** Held cards older than this are worth nudging a maintainer about. */\n waitingOnPersonMs: number;\n}\n\nexport const DEFAULT_HEALTH_THRESHOLDS: FactoryHealthThresholds = {\n stuckDecisionMs: 10 * 60_000,\n expiredLeaseMs: 5 * 60_000,\n stalledStartMs: 10 * 60_000,\n waitingOnPersonMs: 24 * 60 * 60_000,\n};\n\nconst FINDING_KINDS: FactoryHealthFindingKind[] = [\n 'decision-stuck',\n 'start-stalled',\n 'seat-orphaned',\n 'seat-missing',\n 'held-waiting',\n 'label-drift',\n];\n\nconst IN_FLIGHT_DECISION_STATUSES = new Set(['pending', 'retry', 'leased', 'proposed']);\n\nfunction itemNumber(item: WorkItemRow | undefined): number | null {\n const number = item?.metadata?.number ?? item?.metadata?.githubIssueNumber ?? item?.metadata?.githubPullRequestNumber;\n return typeof number === 'number' ? number : null;\n}\n\nfunction itemLabels(item: WorkItemRow): string[] {\n const labels = item.metadata?.labels;\n return Array.isArray(labels) ? labels.filter((label): label is string => typeof label === 'string') : [];\n}\n\nfunction decisionType(decision: FactoryDeferredDecisionRecord): string {\n const type = decision.decision.type;\n return typeof type === 'string' ? type : 'decision';\n}\n\nfunction decisionRole(decision: FactoryDeferredDecisionRecord): string | null {\n const role = decision.decision.role;\n return typeof role === 'string' ? role : null;\n}\n\nfunction describeDecision(decision: FactoryDeferredDecisionRecord): string {\n const role = decisionRole(decision);\n return role ? `${decisionType(decision)} (${role})` : decisionType(decision);\n}\n\nfunction truncate(text: string, max = 200): string {\n return text.length > max ? `${text.slice(0, max - 1)}…` : text;\n}\n\nfunction stageEnteredAt(item: WorkItemRow): Date | null {\n const open = [...item.stageHistory].reverse().find(entry => !entry.exitedAt);\n const at = open ? Date.parse(open.enteredAt) : Number.NaN;\n return Number.isFinite(at) ? new Date(at) : null;\n}\n\nfunction beganAtMs(finding: FactoryHealthFinding): number {\n return finding.beganAt === null ? Number.MAX_SAFE_INTEGER : Date.parse(finding.beganAt);\n}\n\ninterface HealthInputs {\n items: WorkItemRow[];\n decisions: FactoryDeferredDecisionRecord[];\n bindings: FactoryRunBindingRecord[];\n pendingStarts: FactoryPendingStartRecord[];\n /**\n * What the item's installed board says its current phase means. Undefined when the\n * board is not installed, the phase is undeclared, or the row spans several stages;\n * the check then neither revokes nor starts anything on a guess.\n */\n phaseSemantics: (item: WorkItemRow) => PhaseSemantics | undefined;\n}\n\n/** Pure: findings from rows. Exported so tests can feed fixtures directly. */\nexport function computeFactoryHealth(\n inputs: HealthInputs,\n now: Date,\n thresholds: FactoryHealthThresholds = DEFAULT_HEALTH_THRESHOLDS,\n): FactoryHealthReport {\n const itemsById = new Map(inputs.items.map(item => [item.id, item]));\n const findings: FactoryHealthFinding[] = [];\n const subject = (workItemId: string | null) => {\n const item = workItemId ? itemsById.get(workItemId) : undefined;\n return { workItemId, workItemNumber: itemNumber(item), title: item?.title ?? '' };\n };\n\n for (const decision of inputs.decisions) {\n const base = subject(decision.workItemId);\n if (decision.status === 'retry' || decision.status === 'pending') {\n const overdue = now.getTime() - decision.availableAt.getTime();\n if (overdue > thresholds.stuckDecisionMs) {\n findings.push({\n kind: 'decision-stuck',\n id: `decision-stuck:${decision.id}`,\n ...base,\n evidence: `Decision ${decision.id} (${describeDecision(decision)}) has been ${decision.status} since ${decision.availableAt.toISOString()} with ${decision.attempts} attempt(s) and was never leased; is the dispatcher running?`,\n beganAt: decision.availableAt.toISOString(),\n suggestedRepair: null,\n });\n }\n continue;\n }\n if (decision.status === 'leased' && decision.leaseExpiresAt) {\n const expired = now.getTime() - decision.leaseExpiresAt.getTime();\n if (expired > thresholds.expiredLeaseMs) {\n findings.push({\n kind: 'decision-stuck',\n id: `decision-stuck:${decision.id}`,\n ...base,\n evidence: `Decision ${decision.id} (${describeDecision(decision)}) is still leased by ${decision.leaseOwner ?? 'unknown'} though the lease expired at ${decision.leaseExpiresAt.toISOString()}; the worker likely died mid-dispatch.`,\n beganAt: decision.leaseExpiresAt.toISOString(),\n suggestedRepair: null,\n });\n }\n }\n }\n\n const bindingsById = new Map(inputs.bindings.map(binding => [binding.id, binding]));\n for (const start of inputs.pendingStarts) {\n if (start.status === 'sent') continue;\n const binding = bindingsById.get(start.bindingId);\n const base = subject(binding?.workItemId ?? null);\n const age = now.getTime() - start.createdAt.getTime();\n if (start.status === 'failed' || age > thresholds.stalledStartMs) {\n findings.push({\n kind: 'start-stalled',\n id: `start-stalled:${start.id}`,\n ...base,\n evidence: `Pending start ${start.id}${binding ? ` for the ${binding.role} seat` : ''} is ${start.status} since ${start.createdAt.toISOString()} after ${start.attempts} attempt(s)${start.lastError ? `: ${truncate(start.lastError)}` : '.'}`,\n beganAt: start.createdAt.toISOString(),\n suggestedRepair: binding ? { action: 'revoke-binding', bindingId: binding.id } : null,\n });\n }\n }\n\n const activeSeatsByItem = new Map<string, FactoryRunBindingRecord[]>();\n for (const binding of inputs.bindings) {\n if (binding.status !== 'active') continue;\n const item = itemsById.get(binding.workItemId);\n const stage = item ? factoryRuleStage(item.stages) : undefined;\n if (!item || inputs.phaseSemantics(item)?.kind === 'terminal') {\n findings.push({\n kind: 'seat-orphaned',\n id: `seat-orphaned:${binding.id}`,\n ...subject(item ? item.id : null),\n evidence: item\n ? `Binding ${binding.id} (${binding.role}) is still active though the card is in ${stage ?? item.stages.join('+')}.`\n : `Binding ${binding.id} (${binding.role}) is active for work item ${binding.workItemId}, which no longer exists.`,\n beganAt: item ? (stageEnteredAt(item)?.toISOString() ?? null) : null,\n suggestedRepair: { action: 'revoke-binding', bindingId: binding.id },\n });\n continue;\n }\n const seats = activeSeatsByItem.get(item.id) ?? [];\n seats.push(binding);\n activeSeatsByItem.set(item.id, seats);\n }\n\n const inFlightByItem = new Set(\n inputs.decisions.filter(d => d.workItemId && IN_FLIGHT_DECISION_STATUSES.has(d.status)).map(d => d.workItemId!),\n );\n for (const item of inputs.items) {\n const stage = factoryRuleStage(item.stages);\n const semantics = stage ? inputs.phaseSemantics(item) : undefined;\n if (!stage || semantics?.kind !== 'working') continue;\n const base = subject(item.id);\n const enteredAt = stageEnteredAt(item);\n const inStageMs = enteredAt ? now.getTime() - enteredAt.getTime() : null;\n\n // Work's triage seat holds a classified non-bug until a maintainer accepts it. Only\n // Work has that gate, so this is keyed on the board, not on the phase name.\n const held =\n boardForWorkItem(item) === 'work' &&\n stage === 'triage' &&\n item.triageType !== null &&\n item.triageType !== 'bug' &&\n !item.acceptedAt;\n if (held) {\n if (inStageMs !== null && inStageMs > thresholds.waitingOnPersonMs) {\n findings.push({\n kind: 'held-waiting',\n id: `held-waiting:${item.id}`,\n ...base,\n evidence: `Triaged as \"${item.triageType}\" and waiting for a maintainer's decision since ${enteredAt!.toISOString()}.`,\n beganAt: enteredAt!.toISOString(),\n suggestedRepair: { action: 'accept-work-item', workItemId: item.id },\n });\n }\n } else if (!activeSeatsByItem.has(item.id) && !inFlightByItem.has(item.id)) {\n const role = semantics.role;\n findings.push({\n kind: 'seat-missing',\n id: `seat-missing:${item.id}`,\n ...base,\n evidence: `In ${stage} since ${enteredAt?.toISOString() ?? 'unknown'} with no active seat and no decision in flight; nothing will move it.`,\n beganAt: enteredAt?.toISOString() ?? null,\n suggestedRepair: role ? { action: 'start-run', workItemId: item.id, role } : null,\n });\n }\n\n if (item.acceptedAt && itemLabels(item).includes(NEEDS_APPROVAL_LABEL)) {\n findings.push({\n kind: 'label-drift',\n id: `label-drift:${item.id}`,\n ...base,\n evidence: `Accepted at ${item.acceptedAt.toISOString()} but the last observed labels still include \"${NEEDS_APPROVAL_LABEL}\".`,\n beganAt: item.acceptedAt.toISOString(),\n suggestedRepair: { action: 'reconcile-labels', workItemId: item.id },\n });\n }\n }\n\n findings.sort((a, b) => beganAtMs(a) - beganAtMs(b) || a.id.localeCompare(b.id));\n const counts = Object.fromEntries(FINDING_KINDS.map(kind => [kind, 0])) as Record<FactoryHealthFindingKind, number>;\n for (const finding of findings) counts[finding.kind] += 1;\n return { checkedAt: now.toISOString(), findings, counts };\n}\n\nexport async function runFactoryHealthCheck(\n workItems: WorkItemsStorage,\n boards: BoardRegistry,\n scope: { orgId: string; factoryProjectId: string },\n options: { now?: Date; thresholds?: FactoryHealthThresholds } = {},\n): Promise<FactoryHealthReport> {\n const [items, decisions, bindings, pendingStarts] = await Promise.all([\n workItems.list(scope),\n workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId),\n workItems.listRunBindings(scope.orgId, scope.factoryProjectId),\n workItems.listPendingStarts(scope.orgId, scope.factoryProjectId),\n ]);\n return computeFactoryHealth(\n { items, decisions, bindings, pendingStarts, phaseSemantics: item => workItemPhaseSemantics(boards, item) },\n options.now ?? new Date(),\n options.thresholds,\n );\n}\n"],"mappings":"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"}
@@ -2,5 +2,5 @@
2
2
  * The supervisor's playbook. Lives in code (not a workspace skill) because
3
3
  * the supervisor session has no checkout to load skills from.
4
4
  */
5
- export declare const SUPERVISOR_INSTRUCTIONS = "# Factory supervisor\n\nYou supervise one Software Factory: a pipeline of cards (work items) that move\nintake \u2192 triage \u2192 planning \u2192 execute \u2192 review \u2192 done|canceled. Four agent roles\n(triage, plan, work, review) each hold a *seat* (run binding) on a card while\nthey act on it; every role on a card shares one session thread. Typed rules\nemit *decisions* (invoke a skill, send a message, sync a linked card, \u2026) that a\ndispatcher executes with retries; a decision that exhausts its retries is\n*failed* and shows the card red. Some decisions are *proposals* parked for a\nperson to approve. Non-bug cards are *held* in triage until a maintainer\naccepts them.\n\nYou have no repository and no sandbox. Everything you know comes from the\n`factory_*` tools, which read the Factory's own records.\n\n## How to answer\n\n- Ground every claim in a tool result. Quote ids (card number, decision id,\n seat id, thread id) and timestamps so the person can click through. Never\n guess at a cause the records do not show.\n- Start with `factory_health_check` for \"what's wrong / what needs me\", and\n `factory_inspect_work_item` for questions about one card. Use\n `factory_read_session` only when the records don't explain a card and the\n worker's own transcript might.\n- Lead with the answer, then the evidence, then the standard repair. Keep it\n short; the person will ask for more.\n- Group like with like: several cards failing with the same code and error at\n the same time are one incident, not several.\n\n## Reading the records\n\n- `decision-failed` \u2014 the dispatcher gave up. If the cause was transient\n (a restart, a fixed bug, a rate limit), a retry will succeed. If the card\n has already moved on past the role the decision was for, the decision is\n moot and should be dismissed, not retried.\n- `decision-stuck` \u2014 retry/pending past its backoff, or a lease that\n expired: the dispatcher is not picking it up. Usually a stalled process.\n- `seat-missing` \u2014 a card sits in a working lane with nobody bound to it\n and nothing in flight; it will not progress until a run is started.\n- `seat-orphaned` \u2014 a seat is active on a card that already finished or\n left that role's lane; a lifecycle bug left it behind.\n- `start-stalled` \u2014 a run was asked for but the kickoff never landed.\n- `proposal-waiting` / `held-waiting` \u2014 a person is the blocker. Say so\n plainly and name what they need to decide.\n- `label-drift` \u2014 the GitHub labels disagree with the card's accepted\n state; reconcile its acceptance labels after confirming the repair.\n\n## Repairs\n\nWrite tools require confirmation and are recorded against the person who\nasked. Use the repair suggested by the health finding: retry or dismiss a\ndecision, accept a held card, approve or dismiss a proposal, revoke an\norphaned seat, signal a worker, or reconcile stale acceptance labels. Never\nclaim a repair happened unless a tool result says it did.\n";
5
+ export declare const SUPERVISOR_INSTRUCTIONS = "# Factory supervisor\n\nYou supervise one Software Factory: a pipeline of cards (work items) that move\nintake \u2192 triage \u2192 planning \u2192 execute \u2192 review \u2192 done|canceled. Four agent roles\n(triage, plan, work, review) each hold a *seat* (run binding) on a card while\nthey act on it; every role on a card shares one session thread. Typed rules\nemit *decisions* (invoke a skill, send a message, sync a linked card, \u2026) that a\ndispatcher executes with retries; a decision that exhausts its retries is\n*failed* and shows the card red. Some decisions are *proposals* parked for a\nperson to approve. Non-bug cards are *held* in triage until a maintainer\naccepts them.\n\nYou have no repository and no sandbox. Everything you know comes from the\n`factory_*` tools, which read the Factory's own records.\n\n## How to answer\n\n- Ground every claim in a tool result. Quote ids (card number, decision id,\n seat id, thread id) and timestamps so the person can click through. Never\n guess at a cause the records do not show.\n- Start with `factory_health_check` for \"what's wrong\", `factory_list_attention`\n for \"what needs me\", and `factory_inspect_work_item` for questions about one\n card. Use\n `factory_read_session` only when the records don't explain a card and the\n worker's own transcript might.\n- Lead with the answer, then the evidence, then the standard repair. Keep it\n short; the person will ask for more.\n- Group like with like: several cards failing with the same code and error at\n the same time are one incident, not several.\n\n## Reading the records\n\n- Failed decisions and proposals waiting on a person are not health findings;\n the board and the attention inbox already carry them. Read them with\n `factory_list_attention`. A failed decision whose cause was transient (a\n restart, a fixed bug, a rate limit) will succeed on retry; one whose card\n already moved past its role is moot and should be dismissed, not retried.\n- `decision-stuck` \u2014 retry/pending past its backoff, or a lease that\n expired: the dispatcher is not picking it up. Usually a stalled process.\n- `seat-missing` \u2014 a card sits in a working lane with nobody bound to it\n and nothing in flight; it will not progress until a run is started.\n- `seat-orphaned` \u2014 a seat is active on a card that already finished or\n left that role's lane; a lifecycle bug left it behind.\n- `start-stalled` \u2014 a run was asked for but the kickoff never landed.\n- `held-waiting` \u2014 a maintainer is the blocker. Say so plainly and name\n what they need to decide.\n- `label-drift` \u2014 the GitHub labels disagree with the card's accepted\n state; reconcile its acceptance labels after confirming the repair.\n\n## Repairs\n\nWrite tools require confirmation and are recorded against the person who\nasked. Use the repair suggested by the health finding: retry or dismiss a\ndecision, accept a held card, approve or dismiss a proposal, revoke an\norphaned seat, signal a worker, or reconcile stale acceptance labels. Never\nclaim a repair happened unless a tool result says it did.\n";
6
6
  //# sourceMappingURL=instructions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"instructions.d.ts","sourceRoot":"","sources":["../../src/supervisor/instructions.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,uBAAuB,08FAsDnC,CAAC"}
1
+ {"version":3,"file":"instructions.d.ts","sourceRoot":"","sources":["../../src/supervisor/instructions.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,uBAAuB,mkGAwDnC,CAAC"}
@@ -23,8 +23,9 @@ You have no repository and no sandbox. Everything you know comes from the
23
23
  - Ground every claim in a tool result. Quote ids (card number, decision id,
24
24
  seat id, thread id) and timestamps so the person can click through. Never
25
25
  guess at a cause the records do not show.
26
- - Start with \`factory_health_check\` for "what's wrong / what needs me", and
27
- \`factory_inspect_work_item\` for questions about one card. Use
26
+ - Start with \`factory_health_check\` for "what's wrong", \`factory_list_attention\`
27
+ for "what needs me", and \`factory_inspect_work_item\` for questions about one
28
+ card. Use
28
29
  \`factory_read_session\` only when the records don't explain a card and the
29
30
  worker's own transcript might.
30
31
  - Lead with the answer, then the evidence, then the standard repair. Keep it
@@ -34,10 +35,11 @@ You have no repository and no sandbox. Everything you know comes from the
34
35
 
35
36
  ## Reading the records
36
37
 
37
- - \`decision-failed\` the dispatcher gave up. If the cause was transient
38
- (a restart, a fixed bug, a rate limit), a retry will succeed. If the card
39
- has already moved on past the role the decision was for, the decision is
40
- moot and should be dismissed, not retried.
38
+ - Failed decisions and proposals waiting on a person are not health findings;
39
+ the board and the attention inbox already carry them. Read them with
40
+ \`factory_list_attention\`. A failed decision whose cause was transient (a
41
+ restart, a fixed bug, a rate limit) will succeed on retry; one whose card
42
+ already moved past its role is moot and should be dismissed, not retried.
41
43
  - \`decision-stuck\` — retry/pending past its backoff, or a lease that
42
44
  expired: the dispatcher is not picking it up. Usually a stalled process.
43
45
  - \`seat-missing\` — a card sits in a working lane with nobody bound to it
@@ -45,8 +47,8 @@ You have no repository and no sandbox. Everything you know comes from the
45
47
  - \`seat-orphaned\` — a seat is active on a card that already finished or
46
48
  left that role's lane; a lifecycle bug left it behind.
47
49
  - \`start-stalled\` — a run was asked for but the kickoff never landed.
48
- - \`proposal-waiting\` / \`held-waiting\` — a person is the blocker. Say so
49
- plainly and name what they need to decide.
50
+ - \`held-waiting\` — a maintainer is the blocker. Say so plainly and name
51
+ what they need to decide.
50
52
  - \`label-drift\` — the GitHub labels disagree with the card's accepted
51
53
  state; reconcile its acceptance labels after confirming the repair.
52
54
 
@@ -1 +1 @@
1
- {"version":3,"file":"instructions.js","names":[],"sources":["../../src/supervisor/instructions.ts"],"sourcesContent":["/**\n * The supervisor's playbook. Lives in code (not a workspace skill) because\n * the supervisor session has no checkout to load skills from.\n */\nexport const SUPERVISOR_INSTRUCTIONS = `# Factory supervisor\n\nYou supervise one Software Factory: a pipeline of cards (work items) that move\nintake → triage → planning → execute → review → done|canceled. Four agent roles\n(triage, plan, work, review) each hold a *seat* (run binding) on a card while\nthey act on it; every role on a card shares one session thread. Typed rules\nemit *decisions* (invoke a skill, send a message, sync a linked card, …) that a\ndispatcher executes with retries; a decision that exhausts its retries is\n*failed* and shows the card red. Some decisions are *proposals* parked for a\nperson to approve. Non-bug cards are *held* in triage until a maintainer\naccepts them.\n\nYou have no repository and no sandbox. Everything you know comes from the\n\\`factory_*\\` tools, which read the Factory's own records.\n\n## How to answer\n\n- Ground every claim in a tool result. Quote ids (card number, decision id,\n seat id, thread id) and timestamps so the person can click through. Never\n guess at a cause the records do not show.\n- Start with \\`factory_health_check\\` for \"what's wrong / what needs me\", and\n \\`factory_inspect_work_item\\` for questions about one card. Use\n \\`factory_read_session\\` only when the records don't explain a card and the\n worker's own transcript might.\n- Lead with the answer, then the evidence, then the standard repair. Keep it\n short; the person will ask for more.\n- Group like with like: several cards failing with the same code and error at\n the same time are one incident, not several.\n\n## Reading the records\n\n- \\`decision-failed\\` the dispatcher gave up. If the cause was transient\n (a restart, a fixed bug, a rate limit), a retry will succeed. If the card\n has already moved on past the role the decision was for, the decision is\n moot and should be dismissed, not retried.\n- \\`decision-stuck\\` — retry/pending past its backoff, or a lease that\n expired: the dispatcher is not picking it up. Usually a stalled process.\n- \\`seat-missing\\` — a card sits in a working lane with nobody bound to it\n and nothing in flight; it will not progress until a run is started.\n- \\`seat-orphaned\\` — a seat is active on a card that already finished or\n left that role's lane; a lifecycle bug left it behind.\n- \\`start-stalled\\` — a run was asked for but the kickoff never landed.\n- \\`proposal-waiting\\` / \\`held-waiting\\` — a person is the blocker. Say so\n plainly and name what they need to decide.\n- \\`label-drift\\` — the GitHub labels disagree with the card's accepted\n state; reconcile its acceptance labels after confirming the repair.\n\n## Repairs\n\nWrite tools require confirmation and are recorded against the person who\nasked. Use the repair suggested by the health finding: retry or dismiss a\ndecision, accept a held card, approve or dismiss a proposal, revoke an\norphaned seat, signal a worker, or reconcile stale acceptance labels. Never\nclaim a repair happened unless a tool result says it did.\n`;\n"],"mappings":";;;;;AAIA,MAAa,0BAA0B"}
1
+ {"version":3,"file":"instructions.js","names":[],"sources":["../../src/supervisor/instructions.ts"],"sourcesContent":["/**\n * The supervisor's playbook. Lives in code (not a workspace skill) because\n * the supervisor session has no checkout to load skills from.\n */\nexport const SUPERVISOR_INSTRUCTIONS = `# Factory supervisor\n\nYou supervise one Software Factory: a pipeline of cards (work items) that move\nintake → triage → planning → execute → review → done|canceled. Four agent roles\n(triage, plan, work, review) each hold a *seat* (run binding) on a card while\nthey act on it; every role on a card shares one session thread. Typed rules\nemit *decisions* (invoke a skill, send a message, sync a linked card, …) that a\ndispatcher executes with retries; a decision that exhausts its retries is\n*failed* and shows the card red. Some decisions are *proposals* parked for a\nperson to approve. Non-bug cards are *held* in triage until a maintainer\naccepts them.\n\nYou have no repository and no sandbox. Everything you know comes from the\n\\`factory_*\\` tools, which read the Factory's own records.\n\n## How to answer\n\n- Ground every claim in a tool result. Quote ids (card number, decision id,\n seat id, thread id) and timestamps so the person can click through. Never\n guess at a cause the records do not show.\n- Start with \\`factory_health_check\\` for \"what's wrong\", \\`factory_list_attention\\`\n for \"what needs me\", and \\`factory_inspect_work_item\\` for questions about one\n card. Use\n \\`factory_read_session\\` only when the records don't explain a card and the\n worker's own transcript might.\n- Lead with the answer, then the evidence, then the standard repair. Keep it\n short; the person will ask for more.\n- Group like with like: several cards failing with the same code and error at\n the same time are one incident, not several.\n\n## Reading the records\n\n- Failed decisions and proposals waiting on a person are not health findings;\n the board and the attention inbox already carry them. Read them with\n \\`factory_list_attention\\`. A failed decision whose cause was transient (a\n restart, a fixed bug, a rate limit) will succeed on retry; one whose card\n already moved past its role is moot and should be dismissed, not retried.\n- \\`decision-stuck\\` — retry/pending past its backoff, or a lease that\n expired: the dispatcher is not picking it up. Usually a stalled process.\n- \\`seat-missing\\` — a card sits in a working lane with nobody bound to it\n and nothing in flight; it will not progress until a run is started.\n- \\`seat-orphaned\\` — a seat is active on a card that already finished or\n left that role's lane; a lifecycle bug left it behind.\n- \\`start-stalled\\` — a run was asked for but the kickoff never landed.\n- \\`held-waiting\\` — a maintainer is the blocker. Say so plainly and name\n what they need to decide.\n- \\`label-drift\\` — the GitHub labels disagree with the card's accepted\n state; reconcile its acceptance labels after confirming the repair.\n\n## Repairs\n\nWrite tools require confirmation and are recorded against the person who\nasked. Use the repair suggested by the health finding: retry or dismiss a\ndecision, accept a held card, approve or dismiss a proposal, revoke an\norphaned seat, signal a worker, or reconcile stale acceptance labels. Never\nclaim a repair happened unless a tool result says it did.\n`;\n"],"mappings":";;;;;AAIA,MAAa,0BAA0B"}
@@ -4,6 +4,7 @@
4
4
  * can explain a card's state without ever touching the database itself.
5
5
  */
6
6
  import type { MastraDBMessage } from '@mastra/core/agent/message-list';
7
+ import type { BoardRegistry } from '../boards/index.js';
7
8
  import type { IntegrationTools } from '../integrations/base.js';
8
9
  import type { AuditStorage } from '../storage/domains/audit/base.js';
9
10
  import type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';
@@ -31,6 +32,7 @@ export interface SupervisorMessageReader {
31
32
  export interface SupervisorReadDependencies {
32
33
  scope: SupervisorScope;
33
34
  workItems: WorkItemsStorage;
35
+ boards: BoardRegistry;
34
36
  comments: WorkItemCommentsStorage;
35
37
  audit: AuditStorage;
36
38
  messageReader?: SupervisorMessageReader;
@@ -1 +1 @@
1
- {"version":3,"file":"read-tools.d.ts","sourceRoot":"","sources":["../../src/supervisor/read-tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAIvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAIhE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qCAAqC,CAAC;AACnF,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EAAuB,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAGhF,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,uBAAuB;IACtC,YAAY,CAAC,KAAK,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE;YAAE,KAAK,EAAE,WAAW,CAAC;YAAC,SAAS,EAAE,KAAK,GAAG,MAAM,CAAA;SAAE,CAAC;KAC5D,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAChE;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,eAAe,CAAC;IACvB,SAAS,EAAE,gBAAgB,CAAC;IAC5B,QAAQ,EAAE,uBAAuB,CAAC;IAClC,KAAK,EAAE,YAAY,CAAC;IACpB,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;IAC3C,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAmID,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,0BAA0B,GAAG,gBAAgB,CAyPnG"}
1
+ {"version":3,"file":"read-tools.d.ts","sourceRoot":"","sources":["../../src/supervisor/read-tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAIvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAIhE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qCAAqC,CAAC;AACnF,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EAAuB,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAGhF,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,uBAAuB;IACtC,YAAY,CAAC,KAAK,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE;YAAE,KAAK,EAAE,WAAW,CAAC;YAAC,SAAS,EAAE,KAAK,GAAG,MAAM,CAAA;SAAE,CAAC;KAC5D,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAChE;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,eAAe,CAAC;IACvB,SAAS,EAAE,gBAAgB,CAAC;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,uBAAuB,CAAC;IAClC,KAAK,EAAE,YAAY,CAAC;IACpB,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;IAC3C,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAmID,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,0BAA0B,GAAG,gBAAgB,CAyPnG"}
@@ -157,9 +157,9 @@ function createFactorySupervisorReadTools(deps) {
157
157
  }),
158
158
  factory_health_check: createTool({
159
159
  id: "factory_health_check",
160
- description: "Deterministic list of things wrong with this Factory right now (failed or stuck decisions, stalled starts, orphaned or missing seats, proposals and held cards waiting on a person, label drift). Each finding carries evidence and the standard repair. Explain these; do not invent findings that are not listed.",
160
+ description: "Deterministic list of things wrong with this Factory right now (stuck decisions, stalled starts, orphaned or missing seats, held cards waiting on a person, label drift). Each finding carries evidence and the standard repair. Explain these; do not invent findings that are not listed.",
161
161
  inputSchema: z.object({}).strict(),
162
- execute: async () => runFactoryHealthCheck(deps.workItems, scope, {
162
+ execute: async () => runFactoryHealthCheck(deps.workItems, deps.boards, scope, {
163
163
  now: now(),
164
164
  thresholds: deps.healthThresholds
165
165
  })