@mastra/factory 0.2.2-alpha.0 → 0.2.2-alpha.2

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 (174) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/auth.d.ts +2 -2
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/auth.js +401 -314
  5. package/dist/auth.js.map +1 -1
  6. package/dist/capabilities/connection.js +1 -1
  7. package/dist/capabilities/intake.js +1 -1
  8. package/dist/capabilities/version-control.js +1 -1
  9. package/dist/factory.d.ts.map +1 -1
  10. package/dist/factory.js +441 -39455
  11. package/dist/factory.js.map +1 -1
  12. package/dist/index.js +2 -39466
  13. package/dist/integrations/base.js +1 -1
  14. package/dist/integrations/github/config.js +36 -22
  15. package/dist/integrations/github/config.js.map +1 -1
  16. package/dist/integrations/github/factory-session.js +33 -37
  17. package/dist/integrations/github/factory-session.js.map +1 -1
  18. package/dist/integrations/github/integration.js +1037 -3624
  19. package/dist/integrations/github/integration.js.map +1 -1
  20. package/dist/integrations/github/issue-triage.js +71 -71
  21. package/dist/integrations/github/issue-triage.js.map +1 -1
  22. package/dist/integrations/github/pat.js +47 -36
  23. package/dist/integrations/github/pat.js.map +1 -1
  24. package/dist/integrations/github/provenance.js +72 -101
  25. package/dist/integrations/github/provenance.js.map +1 -1
  26. package/dist/integrations/github/routes.d.ts.map +1 -1
  27. package/dist/integrations/github/routes.js +1272 -1833
  28. package/dist/integrations/github/routes.js.map +1 -1
  29. package/dist/integrations/github/rules.js +203 -471
  30. package/dist/integrations/github/rules.js.map +1 -1
  31. package/dist/integrations/github/sandbox.d.ts +2 -0
  32. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  33. package/dist/integrations/github/sandbox.js +401 -326
  34. package/dist/integrations/github/sandbox.js.map +1 -1
  35. package/dist/integrations/github/session-subscriptions.js +165 -223
  36. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  37. package/dist/integrations/github/subscriptions.js +43 -51
  38. package/dist/integrations/github/subscriptions.js.map +1 -1
  39. package/dist/integrations/github/token-refresh.js +15 -18
  40. package/dist/integrations/github/token-refresh.js.map +1 -1
  41. package/dist/integrations/github/webhook.js +359 -343
  42. package/dist/integrations/github/webhook.js.map +1 -1
  43. package/dist/integrations/linear/agent-tools.js +77 -100
  44. package/dist/integrations/linear/agent-tools.js.map +1 -1
  45. package/dist/integrations/linear/integration.js +611 -1309
  46. package/dist/integrations/linear/integration.js.map +1 -1
  47. package/dist/integrations/linear/routes.js +227 -230
  48. package/dist/integrations/linear/routes.js.map +1 -1
  49. package/dist/integrations/linear/rules.js +126 -346
  50. package/dist/integrations/linear/rules.js.map +1 -1
  51. package/dist/integrations/linear/storage.js +1 -1
  52. package/dist/integrations/platform/api-client.js +131 -135
  53. package/dist/integrations/platform/api-client.js.map +1 -1
  54. package/dist/integrations/platform/github/event-worker.js +275 -573
  55. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  56. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  57. package/dist/integrations/platform/github/integration.js +845 -3942
  58. package/dist/integrations/platform/github/integration.js.map +1 -1
  59. package/dist/integrations/platform/linear/integration.js +457 -1309
  60. package/dist/integrations/platform/linear/integration.js.map +1 -1
  61. package/dist/integrations/workos/integration.js +80 -85
  62. package/dist/integrations/workos/integration.js.map +1 -1
  63. package/dist/routes/config.d.ts.map +1 -1
  64. package/dist/routes/config.js +933 -915
  65. package/dist/routes/config.js.map +1 -1
  66. package/dist/routes/custom-provider-source.js +110 -93
  67. package/dist/routes/custom-provider-source.js.map +1 -1
  68. package/dist/routes/fs.js +401 -349
  69. package/dist/routes/fs.js.map +1 -1
  70. package/dist/routes/intake.js +165 -165
  71. package/dist/routes/intake.js.map +1 -1
  72. package/dist/routes/oauth.js +447 -25166
  73. package/dist/routes/oauth.js.map +1 -1
  74. package/dist/routes/projects.js +437 -376
  75. package/dist/routes/projects.js.map +1 -1
  76. package/dist/routes/provider-credentials.js +76 -58
  77. package/dist/routes/provider-credentials.js.map +1 -1
  78. package/dist/routes/route.js +12 -8
  79. package/dist/routes/route.js.map +1 -1
  80. package/dist/routes/skills.js +160 -173
  81. package/dist/routes/skills.js.map +1 -1
  82. package/dist/routes/surface.js +250 -28618
  83. package/dist/routes/surface.js.map +1 -1
  84. package/dist/routes/tenant-credentials.js +141 -166
  85. package/dist/routes/tenant-credentials.js.map +1 -1
  86. package/dist/routes/work-items.js +654 -862
  87. package/dist/routes/work-items.js.map +1 -1
  88. package/dist/rules/binding-context.js +23 -35
  89. package/dist/rules/binding-context.js.map +1 -1
  90. package/dist/rules/defaults.js +200 -309
  91. package/dist/rules/defaults.js.map +1 -1
  92. package/dist/rules/dispatcher.d.ts.map +1 -1
  93. package/dist/rules/dispatcher.js +438 -677
  94. package/dist/rules/dispatcher.js.map +1 -1
  95. package/dist/rules/index.js +5 -599
  96. package/dist/rules/processor.js +344 -596
  97. package/dist/rules/processor.js.map +1 -1
  98. package/dist/rules/resolve.js +21 -18
  99. package/dist/rules/resolve.js.map +1 -1
  100. package/dist/rules/start-coordinator.js +168 -157
  101. package/dist/rules/start-coordinator.js.map +1 -1
  102. package/dist/rules/tools.js +50 -85
  103. package/dist/rules/tools.js.map +1 -1
  104. package/dist/rules/transition-service.js +183 -454
  105. package/dist/rules/transition-service.js.map +1 -1
  106. package/dist/rules/types.js +33 -29
  107. package/dist/rules/types.js.map +1 -1
  108. package/dist/rules/validation.js +266 -281
  109. package/dist/rules/validation.js.map +1 -1
  110. package/dist/sandbox/fleet.js +272 -250
  111. package/dist/sandbox/fleet.js.map +1 -1
  112. package/dist/sandbox/reattach.js +13 -6
  113. package/dist/sandbox/reattach.js.map +1 -1
  114. package/dist/server-error.js +12 -16
  115. package/dist/server-error.js.map +1 -1
  116. package/dist/skills/service.js +30 -35
  117. package/dist/skills/service.js.map +1 -1
  118. package/dist/spa-static.d.ts.map +1 -1
  119. package/dist/spa-static.js +91 -64
  120. package/dist/spa-static.js.map +1 -1
  121. package/dist/state-signing.js +73 -44
  122. package/dist/state-signing.js.map +1 -1
  123. package/dist/storage/domains/audit/agent-audit.js +60 -59
  124. package/dist/storage/domains/audit/agent-audit.js.map +1 -1
  125. package/dist/storage/domains/audit/base.js +169 -112
  126. package/dist/storage/domains/audit/base.js.map +1 -1
  127. package/dist/storage/domains/audit/domain.js +155 -153
  128. package/dist/storage/domains/audit/domain.js.map +1 -1
  129. package/dist/storage/domains/credentials/base.js +259 -243
  130. package/dist/storage/domains/credentials/base.js.map +1 -1
  131. package/dist/storage/domains/custom-providers/base.js +138 -143
  132. package/dist/storage/domains/custom-providers/base.js.map +1 -1
  133. package/dist/storage/domains/intake/base.js +75 -57
  134. package/dist/storage/domains/intake/base.js.map +1 -1
  135. package/dist/storage/domains/integrations/base.js +310 -245
  136. package/dist/storage/domains/integrations/base.js.map +1 -1
  137. package/dist/storage/domains/memory-settings/base.js +117 -102
  138. package/dist/storage/domains/memory-settings/base.js.map +1 -1
  139. package/dist/storage/domains/model-packs/base.js +89 -88
  140. package/dist/storage/domains/model-packs/base.js.map +1 -1
  141. package/dist/storage/domains/projects/base.js +97 -92
  142. package/dist/storage/domains/projects/base.js.map +1 -1
  143. package/dist/storage/domains/queue-health/base.js +111 -76
  144. package/dist/storage/domains/queue-health/base.js.map +1 -1
  145. package/dist/storage/domains/source-control/base.d.ts +9 -0
  146. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  147. package/dist/storage/domains/source-control/base.js +777 -702
  148. package/dist/storage/domains/source-control/base.js.map +1 -1
  149. package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
  150. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  151. package/dist/storage/domains/source-control/inmemory.js +322 -344
  152. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  153. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  154. package/dist/storage/domains/work-items/base.js +1266 -1132
  155. package/dist/storage/domains/work-items/base.js.map +1 -1
  156. package/dist/storage/domains/work-items/metrics.js +202 -184
  157. package/dist/storage/domains/work-items/metrics.js.map +1 -1
  158. package/dist/timing.d.ts +15 -0
  159. package/dist/timing.d.ts.map +1 -0
  160. package/dist/timing.js +34 -0
  161. package/dist/timing.js.map +1 -0
  162. package/dist/workspace.d.ts +2 -3
  163. package/dist/workspace.d.ts.map +1 -1
  164. package/dist/workspace.js +236 -438
  165. package/dist/workspace.js.map +1 -1
  166. package/factory-skills/factory-review/SKILL.md +9 -2
  167. package/package.json +9 -9
  168. package/dist/capabilities/connection.js.map +0 -1
  169. package/dist/capabilities/intake.js.map +0 -1
  170. package/dist/capabilities/version-control.js.map +0 -1
  171. package/dist/index.js.map +0 -1
  172. package/dist/integrations/base.js.map +0 -1
  173. package/dist/integrations/linear/storage.js.map +0 -1
  174. package/dist/rules/index.js.map +0 -1
@@ -1,203 +1,221 @@
1
- // src/storage/domains/work-items/base.ts
2
- import { createHash } from "crypto";
3
- import { FactoryStorageDomain, UniqueViolationError } from "@mastra/core/storage";
4
- var AUTOMATION_ACTORS = /* @__PURE__ */ new Set([
5
- "factory",
6
- "system",
7
- "automation",
8
- "factory-rule-dispatcher",
9
- "factory-tool-result-rule"
10
- ]);
11
- function isAutomationActor(by) {
12
- if (by === void 0) return false;
13
- return AUTOMATION_ACTORS.has(by) || by.startsWith("agent:") || by.startsWith("github:");
14
- }
15
-
16
- // src/storage/domains/work-items/metrics.ts
17
- var DEFAULT_METRICS_WINDOW = 30;
18
- var MAX_METRICS_WINDOW = 366;
19
- var DAY_MS = 864e5;
20
- var DONE_STAGE = "done";
21
- var CANCELED_STAGE = "canceled";
22
- var TERMINAL_STAGES = /* @__PURE__ */ new Set([DONE_STAGE, CANCELED_STAGE]);
23
- var AGING_WIP_LIMIT = 10;
24
- var DATE_ONLY_RE = /^\d{4}-\d{2}-\d{2}$/;
25
- var ZONED_DATETIME_RE = /(?:[Zz]|[+-]\d{2}:?\d{2})$/;
1
+ import { isAutomationActor } from "./base.js";
2
+ //#region src/storage/domains/work-items/metrics.ts
3
+ /**
4
+ * Aggregation math for the Factory Metrics page.
5
+ *
6
+ * Pure functions over `work_items` rows — flow metrics (throughput, cycle
7
+ * time, stage durations, WIP, aging WIP) plus demand mix, all derived from the
8
+ * server-appended `stageHistory` log. Keeping this DB-free makes the math unit
9
+ * testable and lets the route stay a thin shell.
10
+ */
11
+ /** Default window span (days) when the request omits or malforms the range. */
12
+ const DEFAULT_METRICS_WINDOW = 30;
13
+ /** Hard cap on the range span (days) bounds the gap-filled throughput array. */
14
+ const MAX_METRICS_WINDOW = 366;
15
+ const DAY_MS = 864e5;
16
+ /** Terminal stage — items here count as completed, not in-flight. */
17
+ const DONE_STAGE = "done";
18
+ /** Terminal stage for tracked non-completions — never a completion. */
19
+ const CANCELED_STAGE = "canceled";
20
+ /**
21
+ * Terminal stages — items holding only these are not in-flight. `done` is a
22
+ * completion (feeds throughput/cycle time); `canceled` is a tracked
23
+ * non-completion outcome and feeds neither.
24
+ */
25
+ const TERMINAL_STAGES = /* @__PURE__ */ new Set([DONE_STAGE, CANCELED_STAGE]);
26
+ const AGING_WIP_LIMIT = 10;
27
+ const DATE_ONLY_RE = /^\d{4}-\d{2}-\d{2}$/;
28
+ /** Datetime carrying an explicit `Z` or `±HH:MM` offset. */
29
+ const ZONED_DATETIME_RE = /(?:[Zz]|[+-]\d{2}:?\d{2})$/;
26
30
  function parseRangeParam(value, boundary) {
27
- if (typeof value !== "string" || value.length === 0) return void 0;
28
- const dateOnly = DATE_ONLY_RE.test(value);
29
- if (!dateOnly && !ZONED_DATETIME_RE.test(value)) return void 0;
30
- const time = Date.parse(value);
31
- if (Number.isNaN(time)) return void 0;
32
- return boundary === "to" && dateOnly ? time + DAY_MS : time;
31
+ if (typeof value !== "string" || value.length === 0) return void 0;
32
+ const dateOnly = DATE_ONLY_RE.test(value);
33
+ if (!dateOnly && !ZONED_DATETIME_RE.test(value)) return void 0;
34
+ const time = Date.parse(value);
35
+ if (Number.isNaN(time)) return void 0;
36
+ return boundary === "to" && dateOnly ? time + DAY_MS : time;
33
37
  }
34
38
  function utcDayStart(time) {
35
- return Date.parse(`${utcDay(time)}T00:00:00Z`);
39
+ return Date.parse(`${utcDay(time)}T00:00:00Z`);
36
40
  }
41
+ /**
42
+ * Resolve untrusted `from`/`to` into a bounded half-open UTC window. A date-only
43
+ * `to` covers the whole day; an open/future end resolves to the end of the
44
+ * current UTC day (not `now`) so an event at this instant stays inside the
45
+ * window instead of on its excluded edge.
46
+ */
37
47
  function parseMetricsRange(fromParam, toParam, now) {
38
- const nowMs = now.getTime();
39
- const endOfToday = utcDayStart(nowMs) + DAY_MS;
40
- const requestedEnd = parseRangeParam(toParam, "to") ?? endOfToday;
41
- const windowEnd = Math.min(requestedEnd, endOfToday);
42
- const lastIncludedDay = utcDayStart(windowEnd - 1);
43
- const defaultStart = lastIncludedDay - (DEFAULT_METRICS_WINDOW - 1) * DAY_MS;
44
- const parsedFrom = parseRangeParam(fromParam, "from");
45
- let windowStart = parsedFrom !== void 0 && parsedFrom < windowEnd ? parsedFrom : defaultStart;
46
- const earliestStart = lastIncludedDay - (MAX_METRICS_WINDOW - 1) * DAY_MS;
47
- if (windowStart < earliestStart) windowStart = earliestStart;
48
- return { windowStart, windowEnd };
48
+ const endOfToday = utcDayStart(now.getTime()) + DAY_MS;
49
+ const requestedEnd = parseRangeParam(toParam, "to") ?? endOfToday;
50
+ const windowEnd = Math.min(requestedEnd, endOfToday);
51
+ const lastIncludedDay = utcDayStart(windowEnd - 1);
52
+ const defaultStart = lastIncludedDay - 29 * DAY_MS;
53
+ const parsedFrom = parseRangeParam(fromParam, "from");
54
+ let windowStart = parsedFrom !== void 0 && parsedFrom < windowEnd ? parsedFrom : defaultStart;
55
+ const earliestStart = lastIncludedDay - 365 * DAY_MS;
56
+ if (windowStart < earliestStart) windowStart = earliestStart;
57
+ return {
58
+ windowStart,
59
+ windowEnd
60
+ };
49
61
  }
50
62
  function parseTime(iso) {
51
- const time = Date.parse(iso);
52
- return Number.isNaN(time) ? 0 : time;
63
+ const time = Date.parse(iso);
64
+ return Number.isNaN(time) ? 0 : time;
53
65
  }
66
+ /** Nearest-rank percentile over an unsorted sample list. */
54
67
  function percentile(samples, fraction) {
55
- if (samples.length === 0) return null;
56
- const sorted = [...samples].sort((a, b) => a - b);
57
- const rank = Math.max(1, Math.ceil(fraction * sorted.length));
58
- return sorted[rank - 1];
68
+ if (samples.length === 0) return null;
69
+ const sorted = [...samples].sort((a, b) => a - b);
70
+ return sorted[Math.max(1, Math.ceil(fraction * sorted.length)) - 1];
59
71
  }
72
+ /** UTC `YYYY-MM-DD` for a timestamp. */
60
73
  function utcDay(time) {
61
- return new Date(time).toISOString().slice(0, 10);
74
+ return new Date(time).toISOString().slice(0, 10);
62
75
  }
76
+ /**
77
+ * The item's completion time: the `enteredAt` of its still-open `done` entry.
78
+ * `undefined` when the item isn't currently done (including when it was pulled
79
+ * back out of done — that visit has `exitedAt` and doesn't count).
80
+ */
63
81
  function completedAt(item) {
64
- if (!item.stages.includes(DONE_STAGE)) return void 0;
65
- for (let i = item.stageHistory.length - 1; i >= 0; i--) {
66
- const entry = item.stageHistory[i];
67
- if (entry.stage === DONE_STAGE && entry.exitedAt === void 0) return parseTime(entry.enteredAt);
68
- }
69
- return void 0;
82
+ if (!item.stages.includes(DONE_STAGE)) return void 0;
83
+ for (let i = item.stageHistory.length - 1; i >= 0; i--) {
84
+ const entry = item.stageHistory[i];
85
+ if (entry.stage === DONE_STAGE && entry.exitedAt === void 0) return parseTime(entry.enteredAt);
86
+ }
70
87
  }
88
+ /** Open (no `exitedAt`) history entry for a currently-held non-terminal stage. */
71
89
  function openEntries(item) {
72
- return item.stageHistory.filter(
73
- (entry) => entry.exitedAt === void 0 && !TERMINAL_STAGES.has(entry.stage) && item.stages.includes(entry.stage)
74
- );
90
+ return item.stageHistory.filter((entry) => entry.exitedAt === void 0 && !TERMINAL_STAGES.has(entry.stage) && item.stages.includes(entry.stage));
75
91
  }
76
92
  function computeFactoryMetrics(items, opts) {
77
- const { windowStart, windowEnd } = opts;
78
- let earliest = Infinity;
79
- for (const item of items) earliest = Math.min(earliest, item.createdAt.getTime());
80
- const earliestItemAt = Number.isFinite(earliest) ? new Date(earliest).toISOString() : null;
81
- const throughputByDay = /* @__PURE__ */ new Map();
82
- const firstDay = utcDayStart(windowStart);
83
- for (let day = firstDay; day < windowEnd; day += DAY_MS) {
84
- throughputByDay.set(utcDay(day), 0);
85
- }
86
- const cycleSamples = [];
87
- for (const item of items) {
88
- const doneAt = completedAt(item);
89
- if (doneAt === void 0 || doneAt < windowStart || doneAt >= windowEnd) continue;
90
- const day = utcDay(doneAt);
91
- throughputByDay.set(day, (throughputByDay.get(day) ?? 0) + 1);
92
- cycleSamples.push(Math.max(0, doneAt - item.createdAt.getTime()));
93
- }
94
- const durationsByStage = /* @__PURE__ */ new Map();
95
- for (const item of items) {
96
- for (const entry of item.stageHistory) {
97
- if (entry.exitedAt === void 0 || TERMINAL_STAGES.has(entry.stage)) continue;
98
- const exited = parseTime(entry.exitedAt);
99
- if (exited < windowStart || exited >= windowEnd) continue;
100
- const duration = Math.max(0, exited - parseTime(entry.enteredAt));
101
- const samples = durationsByStage.get(entry.stage) ?? [];
102
- samples.push(duration);
103
- durationsByStage.set(entry.stage, samples);
104
- }
105
- }
106
- const wipByStage = /* @__PURE__ */ new Map();
107
- let wipTotal = 0;
108
- const aging = [];
109
- for (const item of items) {
110
- for (const stage of item.stages) {
111
- wipByStage.set(stage, (wipByStage.get(stage) ?? 0) + 1);
112
- }
113
- const inFlightStages = item.stages.filter((stage) => !TERMINAL_STAGES.has(stage));
114
- if (inFlightStages.length === 0) continue;
115
- wipTotal += 1;
116
- const open = openEntries(item);
117
- const oldest = open.reduce(
118
- (best, entry) => !best || parseTime(entry.enteredAt) < parseTime(best.enteredAt) ? entry : best,
119
- void 0
120
- );
121
- aging.push({
122
- id: item.id,
123
- title: item.title,
124
- stage: oldest?.stage ?? inFlightStages[0],
125
- enteredAt: oldest?.enteredAt ?? item.createdAt.toISOString(),
126
- url: item.externalSource?.url ?? null
127
- });
128
- }
129
- aging.sort((a, b) => parseTime(a.enteredAt) - parseTime(b.enteredAt));
130
- const sourceCounts = /* @__PURE__ */ new Map();
131
- let transitionsTotal = 0;
132
- let transitionsHuman = 0;
133
- for (const item of items) {
134
- if (item.createdAt.getTime() >= windowStart && item.createdAt.getTime() < windowEnd) {
135
- const source = item.externalSource ? `${item.externalSource.integrationId}:${item.externalSource.type}` : "manual";
136
- sourceCounts.set(source, (sourceCounts.get(source) ?? 0) + 1);
137
- }
138
- for (const entry of item.stageHistory) {
139
- const entered = parseTime(entry.enteredAt);
140
- if (entered < windowStart || entered >= windowEnd) continue;
141
- transitionsTotal += 1;
142
- if (!isAutomationActor(entry.by)) transitionsHuman += 1;
143
- }
144
- }
145
- const automationByStage = /* @__PURE__ */ new Map();
146
- for (const item of items) {
147
- const itemDone = completedAt(item) !== void 0;
148
- const itemCanceled = item.stages.includes(CANCELED_STAGE);
149
- for (let i = 0; i < item.stageHistory.length; i++) {
150
- const entry = item.stageHistory[i];
151
- if (entry.exitedAt === void 0 || TERMINAL_STAGES.has(entry.stage)) continue;
152
- const exited = parseTime(entry.exitedAt);
153
- if (exited < windowStart || exited >= windowEnd) continue;
154
- let row = automationByStage.get(entry.stage);
155
- if (!row) {
156
- row = {
157
- stage: entry.stage,
158
- exits: 0,
159
- automated: 0,
160
- outcomes: { done: 0, canceled: 0, reworked: 0, inFlight: 0 }
161
- };
162
- automationByStage.set(entry.stage, row);
163
- }
164
- row.exits += 1;
165
- const firstVisitIndex = item.stageHistory.findIndex((e) => e.stage === entry.stage);
166
- if (firstVisitIndex !== i || !isAutomationActor(entry.by) || !isAutomationActor(entry.exitedBy)) continue;
167
- row.automated += 1;
168
- const reworked = item.stageHistory.some((e, j) => j > i && e.stage === entry.stage);
169
- if (reworked) row.outcomes.reworked += 1;
170
- else if (itemDone) row.outcomes.done += 1;
171
- else if (itemCanceled) row.outcomes.canceled += 1;
172
- else row.outcomes.inFlight += 1;
173
- }
174
- }
175
- return {
176
- windowDays: throughputByDay.size,
177
- earliestItemAt,
178
- throughput: [...throughputByDay.entries()].map(([date, count]) => ({ date, count })).sort((a, b) => a.date.localeCompare(b.date)),
179
- cycleTime: {
180
- medianMs: percentile(cycleSamples, 0.5),
181
- p90Ms: percentile(cycleSamples, 0.9),
182
- samples: cycleSamples.length
183
- },
184
- stageDurations: [...durationsByStage.entries()].map(([stage, samples]) => ({
185
- stage,
186
- medianMs: percentile(samples, 0.5),
187
- samples: samples.length
188
- })),
189
- wip: [...wipByStage.entries()].map(([stage, count]) => ({ stage, count })),
190
- wipTotal,
191
- agingWip: aging.slice(0, AGING_WIP_LIMIT),
192
- sourceMix: [...sourceCounts.entries()].map(([source, count]) => ({ source, count })).sort((a, b) => b.count - a.count),
193
- transitions: { human: transitionsHuman, total: transitionsTotal },
194
- stageAutomation: [...automationByStage.values()]
195
- };
93
+ const { windowStart, windowEnd } = opts;
94
+ let earliest = Infinity;
95
+ for (const item of items) earliest = Math.min(earliest, item.createdAt.getTime());
96
+ const earliestItemAt = Number.isFinite(earliest) ? new Date(earliest).toISOString() : null;
97
+ const throughputByDay = /* @__PURE__ */ new Map();
98
+ const firstDay = utcDayStart(windowStart);
99
+ for (let day = firstDay; day < windowEnd; day += DAY_MS) throughputByDay.set(utcDay(day), 0);
100
+ const cycleSamples = [];
101
+ for (const item of items) {
102
+ const doneAt = completedAt(item);
103
+ if (doneAt === void 0 || doneAt < windowStart || doneAt >= windowEnd) continue;
104
+ const day = utcDay(doneAt);
105
+ throughputByDay.set(day, (throughputByDay.get(day) ?? 0) + 1);
106
+ cycleSamples.push(Math.max(0, doneAt - item.createdAt.getTime()));
107
+ }
108
+ const durationsByStage = /* @__PURE__ */ new Map();
109
+ for (const item of items) for (const entry of item.stageHistory) {
110
+ if (entry.exitedAt === void 0 || TERMINAL_STAGES.has(entry.stage)) continue;
111
+ const exited = parseTime(entry.exitedAt);
112
+ if (exited < windowStart || exited >= windowEnd) continue;
113
+ const duration = Math.max(0, exited - parseTime(entry.enteredAt));
114
+ const samples = durationsByStage.get(entry.stage) ?? [];
115
+ samples.push(duration);
116
+ durationsByStage.set(entry.stage, samples);
117
+ }
118
+ const wipByStage = /* @__PURE__ */ new Map();
119
+ let wipTotal = 0;
120
+ const aging = [];
121
+ for (const item of items) {
122
+ for (const stage of item.stages) wipByStage.set(stage, (wipByStage.get(stage) ?? 0) + 1);
123
+ const inFlightStages = item.stages.filter((stage) => !TERMINAL_STAGES.has(stage));
124
+ if (inFlightStages.length === 0) continue;
125
+ wipTotal += 1;
126
+ const oldest = openEntries(item).reduce((best, entry) => !best || parseTime(entry.enteredAt) < parseTime(best.enteredAt) ? entry : best, void 0);
127
+ aging.push({
128
+ id: item.id,
129
+ title: item.title,
130
+ stage: oldest?.stage ?? inFlightStages[0],
131
+ enteredAt: oldest?.enteredAt ?? item.createdAt.toISOString(),
132
+ url: item.externalSource?.url ?? null
133
+ });
134
+ }
135
+ aging.sort((a, b) => parseTime(a.enteredAt) - parseTime(b.enteredAt));
136
+ const sourceCounts = /* @__PURE__ */ new Map();
137
+ let transitionsTotal = 0;
138
+ let transitionsHuman = 0;
139
+ for (const item of items) {
140
+ if (item.createdAt.getTime() >= windowStart && item.createdAt.getTime() < windowEnd) {
141
+ const source = item.externalSource ? `${item.externalSource.integrationId}:${item.externalSource.type}` : "manual";
142
+ sourceCounts.set(source, (sourceCounts.get(source) ?? 0) + 1);
143
+ }
144
+ for (const entry of item.stageHistory) {
145
+ const entered = parseTime(entry.enteredAt);
146
+ if (entered < windowStart || entered >= windowEnd) continue;
147
+ transitionsTotal += 1;
148
+ if (!isAutomationActor(entry.by)) transitionsHuman += 1;
149
+ }
150
+ }
151
+ const automationByStage = /* @__PURE__ */ new Map();
152
+ for (const item of items) {
153
+ const itemDone = completedAt(item) !== void 0;
154
+ const itemCanceled = item.stages.includes(CANCELED_STAGE);
155
+ for (let i = 0; i < item.stageHistory.length; i++) {
156
+ const entry = item.stageHistory[i];
157
+ if (entry.exitedAt === void 0 || TERMINAL_STAGES.has(entry.stage)) continue;
158
+ const exited = parseTime(entry.exitedAt);
159
+ if (exited < windowStart || exited >= windowEnd) continue;
160
+ let row = automationByStage.get(entry.stage);
161
+ if (!row) {
162
+ row = {
163
+ stage: entry.stage,
164
+ exits: 0,
165
+ automated: 0,
166
+ outcomes: {
167
+ done: 0,
168
+ canceled: 0,
169
+ reworked: 0,
170
+ inFlight: 0
171
+ }
172
+ };
173
+ automationByStage.set(entry.stage, row);
174
+ }
175
+ row.exits += 1;
176
+ if (item.stageHistory.findIndex((e) => e.stage === entry.stage) !== i || !isAutomationActor(entry.by) || !isAutomationActor(entry.exitedBy)) continue;
177
+ row.automated += 1;
178
+ if (item.stageHistory.some((e, j) => j > i && e.stage === entry.stage)) row.outcomes.reworked += 1;
179
+ else if (itemDone) row.outcomes.done += 1;
180
+ else if (itemCanceled) row.outcomes.canceled += 1;
181
+ else row.outcomes.inFlight += 1;
182
+ }
183
+ }
184
+ return {
185
+ windowDays: throughputByDay.size,
186
+ earliestItemAt,
187
+ throughput: [...throughputByDay.entries()].map(([date, count]) => ({
188
+ date,
189
+ count
190
+ })).sort((a, b) => a.date.localeCompare(b.date)),
191
+ cycleTime: {
192
+ medianMs: percentile(cycleSamples, .5),
193
+ p90Ms: percentile(cycleSamples, .9),
194
+ samples: cycleSamples.length
195
+ },
196
+ stageDurations: [...durationsByStage.entries()].map(([stage, samples]) => ({
197
+ stage,
198
+ medianMs: percentile(samples, .5),
199
+ samples: samples.length
200
+ })),
201
+ wip: [...wipByStage.entries()].map(([stage, count]) => ({
202
+ stage,
203
+ count
204
+ })),
205
+ wipTotal,
206
+ agingWip: aging.slice(0, AGING_WIP_LIMIT),
207
+ sourceMix: [...sourceCounts.entries()].map(([source, count]) => ({
208
+ source,
209
+ count
210
+ })).sort((a, b) => b.count - a.count),
211
+ transitions: {
212
+ human: transitionsHuman,
213
+ total: transitionsTotal
214
+ },
215
+ stageAutomation: [...automationByStage.values()]
216
+ };
196
217
  }
197
- export {
198
- DEFAULT_METRICS_WINDOW,
199
- MAX_METRICS_WINDOW,
200
- computeFactoryMetrics,
201
- parseMetricsRange
202
- };
218
+ //#endregion
219
+ export { DEFAULT_METRICS_WINDOW, MAX_METRICS_WINDOW, computeFactoryMetrics, parseMetricsRange };
220
+
203
221
  //# sourceMappingURL=metrics.js.map