@vercel/factory 0.0.8 → 0.0.10

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.
package/dist/index.d.mts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { a as StoredEntry, i as StoreDriver, n as RecordExistsError, o as StoredRecord, r as RecordNotFoundError, s as VersionConflictError, t as CollectionName } from "./driver-CrZsy7sm.mjs";
2
+ import { a as factoryConfigSchema, i as factoryConfigRepositorySchema, n as FactoryConfigInput, r as defineFactory, t as FactoryConfig } from "./factory-config-Bu4mklm9.mjs";
2
3
  import { a as signalSchema, c as ReplyTo, d as replyToSchema, f as schemaVersionSchema, i as signalOutcomeSchema, l as SCHEMA_VERSION, m as vocabularySchema, n as SignalOutcome, o as signalSourceSchema, p as usdSchema, r as SignalSource, s as standardSignalSources, t as Signal, u as isoDateTimeSchema } from "./signal-CHVEmzvI.mjs";
3
- import { $ as ReceiptPhase, A as TaskPatch, B as taskDevboxSchema, C as ChangeTransitionOptions, Ct as changeIdSchema, D as CreateTaskInput, Dt as repositoryIdsSchema, E as CreateStoresOptions, Et as repositoryIdSchema, F as TaskKind, G as taskStateSchema, H as taskOriginSchema, I as TaskState, J as repositorySchema, K as Repository, L as harnessSchema, M as createStores, N as Task, O as DecideSignalOptions, Ot as signalIdSchema, P as TaskApproval, Q as ReceiptKind, R as standardTaskKinds, S as ChangePatch, St as TaskId, T as CreateSignalInput, Tt as receiptIdSchema, U as taskQuestionSchema, V as taskKindSchema, W as taskSchema, X as Cost, Y as repositorySlugSchema, Z as Receipt, _ as findingVerificationMethodSchema, _t as ChangeId, a as approvalRuleSchema, at as receiptStateSchema, b as ApprovalDecision, bt as RepositoryId, c as Finding, ct as standardReceiptStates, d as FindingVerification, dt as ChangeState, et as ReceiptState, f as findingCategorySchema, ft as Level, g as findingVerificationLevelSchema, gt as levelSchema, h as findingSchema, ht as changeStateSchema, i as approvalPolicySchema, it as receiptSchema, j as TaskTransitionOptions, k as FactoryStores, kt as taskIdSchema, l as FindingCategory, lt as Change, m as findingPrioritySchema, mt as changeSchema, n as ApprovalRule, nt as receiptKindSchema, o as defaultApprovalPolicy, ot as standardReceiptKinds, p as findingFingerprint, pt as changeEvidenceSchema, q as RepositorySlug, r as ApprovalSubject, rt as receiptPhaseSchema, s as requiresApproval, st as standardReceiptPhases, t as ApprovalPolicy, tt as costSchema, u as FindingPriority, ut as ChangeEvidence, v as findingVerificationSchema, vt as FactoryId, w as CreateChangeInput, wt as factoryIdSchema, x as ApprovalError, xt as SignalId, y as AppendReceiptInput, yt as ReceiptId, z as taskApprovalSchema } from "./approval-qkxww0wJ.mjs";
4
+ import { $ as Cost, A as FactoryStores, At as repositoryIdsSchema, B as standardTaskKinds, C as ChangePatch, Ct as RepositoryId, D as CreateStoresOptions, Dt as factoryIdSchema, E as CreateSignalInput, Et as changeIdSchema, F as TaskApproval, G as taskOriginSchema, H as taskDevboxSchema, I as TaskKind, J as taskStateSchema, K as taskQuestionSchema, L as TaskState, M as TaskTransitionOptions, Mt as taskIdSchema, N as createStores, O as CreateTaskInput, Ot as receiptIdSchema, P as Task, Q as repositorySlugSchema, R as coordinationRepositoryIdsSchema, S as ChangeAssociationError, St as ReceiptId, T as CreateChangeInput, Tt as TaskId, U as taskExecutionRepositoryIdsSchema, V as taskApprovalSchema, W as taskKindSchema, X as RepositorySlug, Y as Repository, Z as repositorySchema, _ as findingVerificationMethodSchema, _t as changeSchema, a as approvalRuleSchema, at as receiptKindSchema, b as ApprovalDecision, bt as ChangeId, c as Finding, ct as receiptStateSchema, d as FindingVerification, dt as standardReceiptStates, et as Receipt, f as findingCategorySchema, ft as Change, g as findingVerificationLevelSchema, gt as changeEvidenceSchema, h as findingSchema, ht as Level, i as approvalPolicySchema, it as costSchema, j as TaskPatch, jt as signalIdSchema, k as DecideSignalOptions, kt as repositoryIdSchema, l as FindingCategory, lt as standardReceiptKinds, m as findingPrioritySchema, mt as ChangeState, n as ApprovalRule, nt as ReceiptPhase, o as defaultApprovalPolicy, ot as receiptPhaseSchema, p as findingFingerprint, pt as ChangeEvidence, q as taskSchema, r as ApprovalSubject, rt as ReceiptState, s as requiresApproval, st as receiptSchema, t as ApprovalPolicy, tt as ReceiptKind, u as FindingPriority, ut as standardReceiptPhases, v as findingVerificationSchema, vt as changeStateSchema, w as ChangeTransitionOptions, wt as SignalId, x as ApprovalError, xt as FactoryId, y as AppendReceiptInput, yt as levelSchema, z as harnessSchema } from "./approval-CDiBco2V.mjs";
4
5
  import { z } from "zod";
5
- import { EveNextConfig, EveNextConfigFunction, EveNextConfigInput } from "eve/next";
6
6
  //#region src/briefs.d.ts
7
7
  /** The finding behind a task, when its signal carries one. */
8
8
  declare function findingForTask(stores: FactoryStores, task: Task): Promise<Finding | null>;
@@ -14,50 +14,6 @@ declare function fixBrief(task: Task, repository: Repository, finding: Finding |
14
14
  /** The scanner's starting message for a scan task. */
15
15
  declare function scanBrief(task: Task, repository: Repository): string;
16
16
  //#endregion
17
- //#region src/schema/factory-config.d.ts
18
- declare const factoryConfigRepositorySchema: z.ZodObject<{
19
- slug: z.ZodString;
20
- dependsOn: z.ZodDefault<z.ZodArray<z.ZodString>>;
21
- }, z.core.$strict>;
22
- declare const factoryConfigSchema: z.ZodObject<{
23
- connectors: z.ZodDefault<z.ZodObject<{
24
- github: z.ZodArray<z.ZodObject<{
25
- owner: z.ZodString;
26
- connector: z.ZodString;
27
- }, z.core.$strict>>;
28
- slack: z.ZodDefault<z.ZodArray<z.ZodObject<{
29
- connector: z.ZodString;
30
- }, z.core.$strict>>>;
31
- }, z.core.$strict>>;
32
- repositories: z.ZodArray<z.ZodObject<{
33
- slug: z.ZodString;
34
- dependsOn: z.ZodDefault<z.ZodArray<z.ZodString>>;
35
- }, z.core.$strict>>;
36
- operators: z.ZodArray<z.ZodString>;
37
- approval: z.ZodDefault<z.ZodArray<z.ZodObject<{
38
- source: z.ZodOptional<z.ZodString>;
39
- kind: z.ZodOptional<z.ZodString>;
40
- priority: z.ZodOptional<z.ZodArray<z.ZodEnum<{
41
- P0: "P0";
42
- P1: "P1";
43
- P2: "P2";
44
- P3: "P3";
45
- }>>>;
46
- require: z.ZodBoolean;
47
- }, z.core.$strict>>>;
48
- }, z.core.$strict>;
49
- type FactoryConfig = z.infer<typeof factoryConfigSchema>;
50
- type FactoryConfigInput = z.input<typeof factoryConfigSchema>;
51
- declare function defineFactory(config: FactoryConfigInput): FactoryConfig;
52
- //#endregion
53
- //#region src/build-factory.d.ts
54
- interface FactoryArgs<TConfig extends EveNextConfig = EveNextConfig> {
55
- factoryConfig: FactoryConfigInput;
56
- nextConfig: EveNextConfigInput<TConfig>;
57
- root?: string;
58
- }
59
- declare function buildFactory<TConfig extends EveNextConfig>({ factoryConfig, nextConfig, root }: FactoryArgs<TConfig>): EveNextConfigFunction<TConfig>;
60
- //#endregion
61
17
  //#region src/changes.d.ts
62
18
  interface AdoptPullRequestInput {
63
19
  repositoryId: RepositoryId;
@@ -94,6 +50,75 @@ declare const DEFAULT_MAX_LAUNCH_ATTEMPTS = 45;
94
50
  */
95
51
  declare function dispatchTask(stores: FactoryStores, taskId: TaskId, options: DispatchOptions): Promise<Task>;
96
52
  //#endregion
53
+ //#region src/metrics.d.ts
54
+ /**
55
+ * The derived review bucket from the default risk x confidence table. Derived
56
+ * at read time, never stored; "blocked" is a bucket, not a change state.
57
+ */
58
+ type ReviewBucket = "stamp" | "review" | "blocked";
59
+ declare function reviewBucket(risk: Level, confidence: Level): ReviewBucket;
60
+ interface LedgerRows {
61
+ changes: readonly Change[];
62
+ signals: readonly Signal[];
63
+ receipts: readonly Receipt[];
64
+ tasks: readonly Task[];
65
+ }
66
+ interface MetricsOptions {
67
+ /** Evaluation time; defaults to the current time. */
68
+ now?: Date;
69
+ /** Days a landed change must hold without a rollback. Default 7. */
70
+ holdDays?: number;
71
+ }
72
+ interface LedgerMetrics {
73
+ /**
74
+ * Changes a human merged that reached deployed (rolled_out for high risk)
75
+ * and did not roll back within the hold window. Includes provisional ones.
76
+ */
77
+ landed: number;
78
+ /** Landed changes still inside the hold window; the number may drop. */
79
+ provisional: number;
80
+ /** landed / (landed + rolled back + closed after ready). Null when nothing decided. */
81
+ stickRate: number | null;
82
+ /**
83
+ * Not computable from ledger rows: needs a record of human commits pushed
84
+ * to the pull request after ready, which nothing writes today.
85
+ */
86
+ stampedClean: null;
87
+ /** Median ms from the ready receipt to the merged receipt, per review bucket. */
88
+ humanTimeMs: {
89
+ stamp: number | null;
90
+ review: number | null;
91
+ };
92
+ /** Sum of settled task cost over landed changes / landed. Null until budgets settle. */
93
+ costPerLandedUsd: number | null;
94
+ /** Signals triage turned away plus changes closed in verification. */
95
+ filtered: number;
96
+ }
97
+ /**
98
+ * The six product metrics from ledger rows alone. One rule governs them: a
99
+ * change counts only after a human merged it and it stayed. Activity that
100
+ * never landed is not a result. Compute over rows already filtered to the
101
+ * period and repositories you are reporting on.
102
+ */
103
+ declare function computeLedgerMetrics(rows: LedgerRows, options?: MetricsOptions): LedgerMetrics;
104
+ type HealthColor = "green" | "yellow" | "red";
105
+ interface HealthOptions {
106
+ now?: Date;
107
+ /** Open-change cap from factory configuration; omitted disables that check. */
108
+ maxOpenChanges?: number;
109
+ }
110
+ interface RepositoryHealth {
111
+ color: HealthColor;
112
+ /** Every rule that fired, so the color is explainable from rows. */
113
+ reasons: string[];
114
+ }
115
+ /**
116
+ * Green, yellow, or red from rows, never from a model. Budget and onboarding
117
+ * signals are not evaluated yet: budgets do not settle and onboarding
118
+ * investigations are not recorded, so those spec rules have no rows to read.
119
+ */
120
+ declare function computeRepositoryHealth(rows: LedgerRows, options?: HealthOptions): RepositoryHealth;
121
+ //#endregion
97
122
  //#region src/schema/budget.d.ts
98
123
  declare const budgetMonthSchema: z.ZodString;
99
124
  declare const budgetSchema: z.ZodObject<{
@@ -183,5 +208,5 @@ interface SweepResult {
183
208
  */
184
209
  declare function sweepQueued(stores: FactoryStores, options: SweepOptions): Promise<SweepResult>;
185
210
  //#endregion
186
- export { AdoptPullRequestInput, AppendReceiptInput, ApprovalDecision, ApprovalError, ApprovalPolicy, ApprovalRule, ApprovalSubject, Budget, Change, ChangeEvidence, ChangeId, ChangePatch, ChangeState, ChangeTransitionOptions, CollectionName, Cost, CreateChangeInput, CreateSignalInput, CreateStoresOptions, CreateTaskInput, DEFAULT_MAX_LAUNCH_ATTEMPTS, DecideSignalOptions, DispatchOptions, DispatchRefusedError, FactoryArgs, FactoryConfig, FactoryConfigInput, FactoryId, FactoryStores, Finding, FindingCategory, FindingPriority, FindingVerification, InvalidTransitionError, Level, Receipt, ReceiptId, ReceiptKind, ReceiptPhase, ReceiptState, RecordExistsError, RecordNotFoundError, ReplyTo, Repository, RepositoryId, RepositorySlug, SCHEMA_VERSION, Signal, SignalCreator, SignalId, SignalOutcome, SignalSource, StoreDriver, StoredEntry, StoredRecord, SweepOptions, SweepResult, Task, TaskApproval, TaskId, TaskKind, TaskLauncher, TaskPatch, TaskState, TaskTransitionOptions, Transition, TransitionActor, VersionConflictError, adoptPullRequest, approvalPolicySchema, approvalRuleSchema, assertTransition, budgetMonthSchema, budgetSchema, buildFactory, changeEvidenceSchema, changeIdSchema, changeInitialState, changeSchema, changeStateSchema, changeTransitions, costSchema, createInMemoryDriver, createSignalCreator, createStores, defaultApprovalPolicy, defineFactory, dispatchTask, factoryConfigRepositorySchema, factoryConfigSchema, factoryIdSchema, findingCategorySchema, findingFingerprint, findingForTask, findingPrioritySchema, findingSchema, findingVerificationLevelSchema, findingVerificationMethodSchema, findingVerificationSchema, fixBrief, harnessSchema, isTransitionAllowed, isoDateTimeSchema, legalNextStates, levelSchema, receiptIdSchema, receiptKindSchema, receiptPhaseSchema, receiptSchema, receiptStateSchema, replyToSchema, repositoryIdSchema, repositoryIdsSchema, repositorySchema, repositorySlugSchema, requiresApproval, scanBrief, schemaVersionSchema, signalIdSchema, signalInitialOutcome, signalOutcomeSchema, signalOutcomeTransitions, signalSchema, signalSourceSchema, standardReceiptKinds, standardReceiptPhases, standardReceiptStates, standardSignalSources, standardTaskKinds, sweepQueued, taskApprovalSchema, taskDevboxSchema, taskIdSchema, taskInitialState, taskKindSchema, taskOriginSchema, taskQuestionSchema, taskSchema, taskStateSchema, taskTransitions, terminalStates, usdSchema, vocabularySchema };
211
+ export { AdoptPullRequestInput, AppendReceiptInput, ApprovalDecision, ApprovalError, ApprovalPolicy, ApprovalRule, ApprovalSubject, Budget, Change, ChangeAssociationError, ChangeEvidence, ChangeId, ChangePatch, ChangeState, ChangeTransitionOptions, CollectionName, Cost, CreateChangeInput, CreateSignalInput, CreateStoresOptions, CreateTaskInput, DEFAULT_MAX_LAUNCH_ATTEMPTS, DecideSignalOptions, DispatchOptions, DispatchRefusedError, FactoryConfig, FactoryConfigInput, FactoryId, FactoryStores, Finding, FindingCategory, FindingPriority, FindingVerification, HealthColor, HealthOptions, InvalidTransitionError, LedgerMetrics, LedgerRows, Level, MetricsOptions, Receipt, ReceiptId, ReceiptKind, ReceiptPhase, ReceiptState, RecordExistsError, RecordNotFoundError, ReplyTo, Repository, RepositoryHealth, RepositoryId, RepositorySlug, ReviewBucket, SCHEMA_VERSION, Signal, SignalCreator, SignalId, SignalOutcome, SignalSource, StoreDriver, StoredEntry, StoredRecord, SweepOptions, SweepResult, Task, TaskApproval, TaskId, TaskKind, TaskLauncher, TaskPatch, TaskState, TaskTransitionOptions, Transition, TransitionActor, VersionConflictError, adoptPullRequest, approvalPolicySchema, approvalRuleSchema, assertTransition, budgetMonthSchema, budgetSchema, changeEvidenceSchema, changeIdSchema, changeInitialState, changeSchema, changeStateSchema, changeTransitions, computeLedgerMetrics, computeRepositoryHealth, coordinationRepositoryIdsSchema, costSchema, createInMemoryDriver, createSignalCreator, createStores, defaultApprovalPolicy, defineFactory, dispatchTask, factoryConfigRepositorySchema, factoryConfigSchema, factoryIdSchema, findingCategorySchema, findingFingerprint, findingForTask, findingPrioritySchema, findingSchema, findingVerificationLevelSchema, findingVerificationMethodSchema, findingVerificationSchema, fixBrief, harnessSchema, isTransitionAllowed, isoDateTimeSchema, legalNextStates, levelSchema, receiptIdSchema, receiptKindSchema, receiptPhaseSchema, receiptSchema, receiptStateSchema, replyToSchema, repositoryIdSchema, repositoryIdsSchema, repositorySchema, repositorySlugSchema, requiresApproval, reviewBucket, scanBrief, schemaVersionSchema, signalIdSchema, signalInitialOutcome, signalOutcomeSchema, signalOutcomeTransitions, signalSchema, signalSourceSchema, standardReceiptKinds, standardReceiptPhases, standardReceiptStates, standardSignalSources, standardTaskKinds, sweepQueued, taskApprovalSchema, taskDevboxSchema, taskExecutionRepositoryIdsSchema, taskIdSchema, taskInitialState, taskKindSchema, taskOriginSchema, taskQuestionSchema, taskSchema, taskStateSchema, taskTransitions, terminalStates, usdSchema, vocabularySchema };
187
212
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  import { a as repositoryIdsSchema, c as SCHEMA_VERSION, d as schemaVersionSchema, f as usdSchema, i as repositoryIdSchema, l as isoDateTimeSchema, n as factoryIdSchema, o as signalIdSchema, p as vocabularySchema, r as receiptIdSchema, s as taskIdSchema, t as changeIdSchema, u as replyToSchema } from "./id-DwCihQr4.mjs";
2
- import { C as findingPrioritySchema, D as findingVerificationSchema, E as findingVerificationMethodSchema, S as findingFingerprint, T as findingVerificationLevelSchema, _ as approvalPolicySchema, a as taskKindSchema, b as requiresApproval, c as taskSchema, d as changeSchema, f as changeStateSchema, g as dispatchTask, h as DispatchRefusedError, i as taskDevboxSchema, l as taskStateSchema, m as DEFAULT_MAX_LAUNCH_ATTEMPTS, n as standardTaskKinds, o as taskOriginSchema, p as levelSchema, r as taskApprovalSchema, s as taskQuestionSchema, t as harnessSchema, u as changeEvidenceSchema, v as approvalRuleSchema, w as findingSchema, x as findingCategorySchema, y as defaultApprovalPolicy } from "./task-BxTU3MJb.mjs";
2
+ import { a as findingCategorySchema, c as findingSchema, d as findingVerificationSchema, i as requiresApproval, l as findingVerificationLevelSchema, n as approvalRuleSchema, o as findingFingerprint, r as defaultApprovalPolicy, s as findingPrioritySchema, t as approvalPolicySchema, u as findingVerificationMethodSchema } from "./approval-DdvEKLiE.mjs";
3
+ import { _ as taskSchema, a as DEFAULT_MAX_LAUNCH_ATTEMPTS, c as coordinationRepositoryIdsSchema, d as taskApprovalSchema, f as taskDevboxSchema, g as taskQuestionSchema, h as taskOriginSchema, i as levelSchema, l as harnessSchema, m as taskKindSchema, n as changeSchema, o as DispatchRefusedError, p as taskExecutionRepositoryIdsSchema, r as changeStateSchema, s as dispatchTask, t as changeEvidenceSchema, u as standardTaskKinds, v as taskStateSchema } from "./change-BPYhWKuL.mjs";
3
4
  import { n as RecordNotFoundError, r as VersionConflictError, t as RecordExistsError } from "./driver-Bbd25rNM.mjs";
5
+ import { a as repositorySlugSchema, i as repositorySchema, n as factoryConfigRepositorySchema, r as factoryConfigSchema, t as defineFactory } from "./factory-config-ifd3JxiU.mjs";
4
6
  import { z } from "zod";
5
7
  import { createHash, randomUUID } from "node:crypto";
6
- import { readdirSync } from "node:fs";
7
- import path from "node:path";
8
8
  //#region src/briefs.ts
9
9
  /** The finding behind a task, when its signal carries one. */
10
10
  async function findingForTask(stores, task) {
@@ -38,59 +38,6 @@ function scanBrief(task, repository) {
38
38
  ].join("\n");
39
39
  }
40
40
  //#endregion
41
- //#region src/schema/repository.ts
42
- const repositorySlugSchema = z.string().regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?\/[a-z0-9._-]+$/).describe("owner/name, lowercase, e.g. acme/web.");
43
- const repositorySchema = z.strictObject({
44
- schemaVersion: schemaVersionSchema,
45
- id: repositoryIdSchema,
46
- slug: repositorySlugSchema,
47
- defaultBranch: z.string().min(1).describe("Branch changes land on, usually main."),
48
- enabled: z.boolean().describe("Whether the factory may work on this repository."),
49
- dependsOn: z.array(repositorySlugSchema).describe("Slugs of repositories this one consumes, for multi-repo work ordering."),
50
- devbox: z.strictObject({
51
- projectId: z.string().min(1),
52
- teamId: z.string().min(1),
53
- snapshot: z.string().min(1).optional().describe("Prebuilt snapshot to boot from.")
54
- }).optional().describe("Devbox execution config. Absent when this repository never uses devboxes."),
55
- connectedAt: isoDateTimeSchema
56
- });
57
- //#endregion
58
- //#region src/schema/factory-config.ts
59
- const factoryConfigRepositorySchema = z.strictObject({
60
- slug: repositorySlugSchema.describe("Repository this factory may work on."),
61
- dependsOn: z.array(repositorySlugSchema).default([]).describe("Repositories that must land changes before this repository.")
62
- });
63
- const githubConnectorSchema = z.strictObject({
64
- owner: z.string().regex(/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/).describe("GitHub user or organization that owns this connector installation."),
65
- connector: z.string().regex(/^github\/[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/).describe("Vercel Connect UID installed for this GitHub owner.")
66
- });
67
- const slackConnectorSchema = z.strictObject({ connector: z.string().regex(/^slack\/[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/).describe("Vercel Connect UID installed for this Slack app.") });
68
- const factoryConfigSchema = z.strictObject({
69
- connectors: z.strictObject({
70
- github: z.array(githubConnectorSchema).refine((connectors) => new Set(connectors.map(({ owner }) => owner)).size === connectors.length, "GitHub connector owners must be unique."),
71
- slack: z.array(slackConnectorSchema).max(1, "A factory may configure one Slack connector.").default([])
72
- }).default({
73
- github: [],
74
- slack: []
75
- }).describe("Provider connections available to this factory."),
76
- repositories: z.array(factoryConfigRepositorySchema).min(1).describe("Repositories this factory is allowed to inspect and change."),
77
- operators: z.array(z.string().min(1)).min(1).describe("Provider-qualified identities allowed to operate this factory."),
78
- approval: approvalPolicySchema.default(defaultApprovalPolicy).describe("Which tasks wait for a human before they run. First match wins.")
79
- });
80
- function defineFactory(config) {
81
- return factoryConfigSchema.parse(config);
82
- }
83
- //#endregion
84
- //#region src/build-factory.ts
85
- function buildFactory({ factoryConfig, nextConfig, root = process.cwd() }) {
86
- defineFactory(factoryConfig);
87
- const agentDefinitions = Object.fromEntries(readdirSync(path.join(root, "agents"), { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => [entry.name, path.join(root, "agents", entry.name)]));
88
- return async (phase, context) => {
89
- const { withEve } = await import("eve/next");
90
- return withEve(nextConfig, { agents: agentDefinitions })(phase, context);
91
- };
92
- }
93
- //#endregion
94
41
  //#region src/changes.ts
95
42
  async function adoptPullRequest(stores, input) {
96
43
  const existing = (await stores.changes.list()).find((change) => change.repositoryId === input.repositoryId && change.prNumber === input.prNumber && (change.state === "verifying" || change.state === "ready"));
@@ -109,6 +56,168 @@ async function adoptPullRequest(stores, input) {
109
56
  });
110
57
  }
111
58
  //#endregion
59
+ //#region src/metrics.ts
60
+ const bucketTable = {
61
+ low: {
62
+ low: "review",
63
+ medium: "stamp",
64
+ high: "stamp"
65
+ },
66
+ medium: {
67
+ low: "review",
68
+ medium: "review",
69
+ high: "stamp"
70
+ },
71
+ high: {
72
+ low: "blocked",
73
+ medium: "review",
74
+ high: "review"
75
+ }
76
+ };
77
+ function reviewBucket(risk, confidence) {
78
+ return bucketTable[risk][confidence];
79
+ }
80
+ function collectFacts(rows) {
81
+ const byChange = /* @__PURE__ */ new Map();
82
+ for (const receipt of rows.receipts) {
83
+ if (receipt.kind !== "change" || receipt.changeId === void 0) continue;
84
+ const list = byChange.get(receipt.changeId) ?? [];
85
+ list.push(receipt);
86
+ byChange.set(receipt.changeId, list);
87
+ }
88
+ const at = (receipts, state) => {
89
+ const found = receipts?.find((receipt) => receipt.state === state);
90
+ return found === void 0 ? null : Date.parse(found.recordedAt);
91
+ };
92
+ return rows.changes.map((change) => {
93
+ const receipts = byChange.get(change.id);
94
+ const requiredState = change.risk === "high" ? "rolled_out" : "deployed";
95
+ return {
96
+ change,
97
+ readyAt: at(receipts, "ready"),
98
+ mergedAt: at(receipts, "merged"),
99
+ reachedAt: at(receipts, requiredState),
100
+ rolledBackAt: at(receipts, "rolled_back")
101
+ };
102
+ });
103
+ }
104
+ /** True for a change a human saw as ready and then closed unmerged. */
105
+ function closedAfterReady(facts) {
106
+ return facts.change.state === "closed" && (facts.readyAt !== null || facts.change.decidedBy !== void 0);
107
+ }
108
+ function median(values) {
109
+ if (values.length === 0) return null;
110
+ const sorted = [...values].sort((a, b) => a - b);
111
+ const mid = Math.floor(sorted.length / 2);
112
+ return sorted.length % 2 === 1 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
113
+ }
114
+ /**
115
+ * The six product metrics from ledger rows alone. One rule governs them: a
116
+ * change counts only after a human merged it and it stayed. Activity that
117
+ * never landed is not a result. Compute over rows already filtered to the
118
+ * period and repositories you are reporting on.
119
+ */
120
+ function computeLedgerMetrics(rows, options = {}) {
121
+ const now = (options.now ?? /* @__PURE__ */ new Date()).getTime();
122
+ const holdMs = (options.holdDays ?? 7) * 24 * 60 * 60 * 1e3;
123
+ const facts = collectFacts(rows);
124
+ let landed = 0;
125
+ let provisional = 0;
126
+ let rolledBack = 0;
127
+ let closedAfterReadyCount = 0;
128
+ let closedInVerification = 0;
129
+ const landedChangeIds = /* @__PURE__ */ new Set();
130
+ for (const fact of facts) {
131
+ const { change } = fact;
132
+ if (fact.rolledBackAt !== null) {
133
+ rolledBack += 1;
134
+ continue;
135
+ }
136
+ if (change.state === "closed") {
137
+ if (closedAfterReady(fact)) closedAfterReadyCount += 1;
138
+ else closedInVerification += 1;
139
+ continue;
140
+ }
141
+ if (change.decidedBy !== void 0 && fact.reachedAt !== null) {
142
+ landed += 1;
143
+ landedChangeIds.add(change.id);
144
+ if (now - fact.reachedAt < holdMs) provisional += 1;
145
+ }
146
+ }
147
+ const stickDenominator = landed + rolledBack + closedAfterReadyCount;
148
+ const humanTimes = {
149
+ stamp: [],
150
+ review: []
151
+ };
152
+ for (const fact of facts) {
153
+ if (fact.readyAt === null || fact.mergedAt === null) continue;
154
+ const bucket = reviewBucket(fact.change.risk, fact.change.confidence);
155
+ if (bucket === "blocked") continue;
156
+ humanTimes[bucket].push(fact.mergedAt - fact.readyAt);
157
+ }
158
+ let settledOverLanded = 0;
159
+ let sawSettledCost = false;
160
+ for (const task of rows.tasks) {
161
+ if (task.settledUsd === void 0) continue;
162
+ if (task.kind === "code_change" && task.changeId !== void 0 && landedChangeIds.has(task.changeId)) {
163
+ settledOverLanded += task.settledUsd;
164
+ sawSettledCost = true;
165
+ }
166
+ }
167
+ const rejectedSignals = rows.signals.filter((signal) => signal.outcome !== "pending" && signal.outcome !== "accepted").length;
168
+ return {
169
+ landed,
170
+ provisional,
171
+ stickRate: stickDenominator === 0 ? null : landed / stickDenominator,
172
+ stampedClean: null,
173
+ humanTimeMs: {
174
+ stamp: median(humanTimes.stamp),
175
+ review: median(humanTimes.review)
176
+ },
177
+ costPerLandedUsd: sawSettledCost && landed > 0 ? settledOverLanded / landed : null,
178
+ filtered: rejectedSignals + closedInVerification
179
+ };
180
+ }
181
+ const openChangeStates = /* @__PURE__ */ new Set(["verifying", "ready"]);
182
+ /**
183
+ * Green, yellow, or red from rows, never from a model. Budget and onboarding
184
+ * signals are not evaluated yet: budgets do not settle and onboarding
185
+ * investigations are not recorded, so those spec rules have no rows to read.
186
+ */
187
+ function computeRepositoryHealth(rows, options = {}) {
188
+ const now = (options.now ?? /* @__PURE__ */ new Date()).getTime();
189
+ const dayMs = 864e5;
190
+ const red = [];
191
+ const yellow = [];
192
+ for (const receipt of rows.receipts) {
193
+ if (now - Date.parse(receipt.recordedAt) >= dayMs) continue;
194
+ if (receipt.state === "rolled_back") red.push(`rollback in the last 24h (${receipt.receiptId})`);
195
+ if (receipt.state === "cancelled" && receipt.reason?.toLowerCase().includes("kill")) red.push(`kill in the last 24h (${receipt.receiptId})`);
196
+ }
197
+ const openQuestions = rows.tasks.filter((task) => task.kind === "question" && task.state === "needs_human").length;
198
+ if (openQuestions > 0) yellow.push(`${openQuestions} open question(s)`);
199
+ const needsHuman = rows.tasks.filter((task) => task.kind !== "question" && task.state === "needs_human").length;
200
+ if (needsHuman > 0) yellow.push(`${needsHuman} task(s) waiting on a human`);
201
+ const blocked = rows.changes.filter((change) => openChangeStates.has(change.state) && reviewBucket(change.risk, change.confidence) === "blocked").length;
202
+ if (blocked > 0) yellow.push(`${blocked} change(s) in the blocked bucket`);
203
+ if (options.maxOpenChanges !== void 0) {
204
+ const open = rows.changes.filter((change) => openChangeStates.has(change.state)).length;
205
+ if (open >= options.maxOpenChanges) yellow.push(`open changes at the cap (${open}/${options.maxOpenChanges})`);
206
+ }
207
+ if (red.length > 0) return {
208
+ color: "red",
209
+ reasons: red
210
+ };
211
+ if (yellow.length > 0) return {
212
+ color: "yellow",
213
+ reasons: yellow
214
+ };
215
+ return {
216
+ color: "green",
217
+ reasons: []
218
+ };
219
+ }
220
+ //#endregion
112
221
  //#region src/schema/budget.ts
113
222
  const budgetMonthSchema = z.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/);
114
223
  const budgetSchema = z.strictObject({
@@ -139,6 +248,7 @@ const standardReceiptStates = [
139
248
  "needs_human",
140
249
  "cancelled",
141
250
  "settled",
251
+ "ready",
142
252
  "merged",
143
253
  "deployed",
144
254
  "rolled_out",
@@ -491,6 +601,18 @@ var ApprovalError = class extends Error {
491
601
  this.reason = reason;
492
602
  }
493
603
  };
604
+ var ChangeAssociationError = class extends Error {
605
+ taskId;
606
+ repositoryId;
607
+ reason;
608
+ constructor(taskId, repositoryId, reason) {
609
+ super(`change for task "${taskId}" refused: ${reason}`);
610
+ this.name = "ChangeAssociationError";
611
+ this.taskId = taskId;
612
+ this.repositoryId = repositoryId;
613
+ this.reason = reason;
614
+ }
615
+ };
494
616
  const taskReceiptStates = {
495
617
  running: "dispatched",
496
618
  succeeded: "succeeded",
@@ -499,6 +621,7 @@ const taskReceiptStates = {
499
621
  cancelled: "cancelled"
500
622
  };
501
623
  const changeReceiptStates = {
624
+ ready: "ready",
502
625
  merged: "merged",
503
626
  deployed: "deployed",
504
627
  rolled_out: "rolled_out",
@@ -741,6 +864,13 @@ function createStores(options) {
741
864
  createdAt: timestamp,
742
865
  updatedAt: timestamp
743
866
  });
867
+ await mustRead("repositories", change.repositoryId, repositorySchema);
868
+ if (change.taskId !== void 0) {
869
+ const task = (await mustRead("tasks", change.taskId, taskSchema)).value;
870
+ if (task.kind !== "code_change") throw new ChangeAssociationError(change.taskId, change.repositoryId, "changes can only be produced by code_change tasks");
871
+ const taskRepositoryId = task.repositoryIds[0];
872
+ if (taskRepositoryId !== change.repositoryId) throw new ChangeAssociationError(change.taskId, change.repositoryId, `repository "${change.repositoryId}" does not match task repository "${taskRepositoryId}"`);
873
+ }
744
874
  await driver.insert("changes", change.id, change);
745
875
  return change;
746
876
  },
@@ -858,6 +988,21 @@ async function sweepQueued(stores, options) {
858
988
  };
859
989
  const queued = (await stores.tasks.list()).filter((task) => task.state === "queued" && task.kind !== "question").sort((a, b) => a.createdAt.localeCompare(b.createdAt));
860
990
  for (const task of queued) {
991
+ if (task.kind === "coordination") {
992
+ result.skipped.push({
993
+ taskId: task.id,
994
+ reason: "coordination tasks are reconciled, not dispatched"
995
+ });
996
+ continue;
997
+ }
998
+ const executionRepositoryIds = taskExecutionRepositoryIdsSchema.safeParse(task.repositoryIds);
999
+ if (!executionRepositoryIds.success) {
1000
+ result.skipped.push({
1001
+ taskId: task.id,
1002
+ reason: "task execution requires exactly one repository"
1003
+ });
1004
+ continue;
1005
+ }
861
1006
  if (task.approval === "required") {
862
1007
  result.skipped.push({
863
1008
  taskId: task.id,
@@ -873,8 +1018,7 @@ async function sweepQueued(stores, options) {
873
1018
  });
874
1019
  continue;
875
1020
  }
876
- const repositoryId = task.repositoryIds?.[0];
877
- const repository = repositoryId === void 0 ? null : await stores.repositories.get(repositoryId);
1021
+ const repository = await stores.repositories.get(executionRepositoryIds.data[0]);
878
1022
  if (repository === null) {
879
1023
  result.skipped.push({
880
1024
  taskId: task.id,
@@ -902,6 +1046,6 @@ async function sweepQueued(stores, options) {
902
1046
  return result;
903
1047
  }
904
1048
  //#endregion
905
- export { ApprovalError, DEFAULT_MAX_LAUNCH_ATTEMPTS, DispatchRefusedError, InvalidTransitionError, RecordExistsError, RecordNotFoundError, SCHEMA_VERSION, VersionConflictError, adoptPullRequest, approvalPolicySchema, approvalRuleSchema, assertTransition, budgetMonthSchema, budgetSchema, buildFactory, changeEvidenceSchema, changeIdSchema, changeInitialState, changeSchema, changeStateSchema, changeTransitions, costSchema, createInMemoryDriver, createSignalCreator, createStores, defaultApprovalPolicy, defineFactory, dispatchTask, factoryConfigRepositorySchema, factoryConfigSchema, factoryIdSchema, findingCategorySchema, findingFingerprint, findingForTask, findingPrioritySchema, findingSchema, findingVerificationLevelSchema, findingVerificationMethodSchema, findingVerificationSchema, fixBrief, harnessSchema, isTransitionAllowed, isoDateTimeSchema, legalNextStates, levelSchema, receiptIdSchema, receiptKindSchema, receiptPhaseSchema, receiptSchema, receiptStateSchema, replyToSchema, repositoryIdSchema, repositoryIdsSchema, repositorySchema, repositorySlugSchema, requiresApproval, scanBrief, schemaVersionSchema, signalIdSchema, signalInitialOutcome, signalOutcomeSchema, signalOutcomeTransitions, signalSchema, signalSourceSchema, standardReceiptKinds, standardReceiptPhases, standardReceiptStates, standardSignalSources, standardTaskKinds, sweepQueued, taskApprovalSchema, taskDevboxSchema, taskIdSchema, taskInitialState, taskKindSchema, taskOriginSchema, taskQuestionSchema, taskSchema, taskStateSchema, taskTransitions, terminalStates, usdSchema, vocabularySchema };
1049
+ export { ApprovalError, ChangeAssociationError, DEFAULT_MAX_LAUNCH_ATTEMPTS, DispatchRefusedError, InvalidTransitionError, RecordExistsError, RecordNotFoundError, SCHEMA_VERSION, VersionConflictError, adoptPullRequest, approvalPolicySchema, approvalRuleSchema, assertTransition, budgetMonthSchema, budgetSchema, changeEvidenceSchema, changeIdSchema, changeInitialState, changeSchema, changeStateSchema, changeTransitions, computeLedgerMetrics, computeRepositoryHealth, coordinationRepositoryIdsSchema, costSchema, createInMemoryDriver, createSignalCreator, createStores, defaultApprovalPolicy, defineFactory, dispatchTask, factoryConfigRepositorySchema, factoryConfigSchema, factoryIdSchema, findingCategorySchema, findingFingerprint, findingForTask, findingPrioritySchema, findingSchema, findingVerificationLevelSchema, findingVerificationMethodSchema, findingVerificationSchema, fixBrief, harnessSchema, isTransitionAllowed, isoDateTimeSchema, legalNextStates, levelSchema, receiptIdSchema, receiptKindSchema, receiptPhaseSchema, receiptSchema, receiptStateSchema, replyToSchema, repositoryIdSchema, repositoryIdsSchema, repositorySchema, repositorySlugSchema, requiresApproval, reviewBucket, scanBrief, schemaVersionSchema, signalIdSchema, signalInitialOutcome, signalOutcomeSchema, signalOutcomeTransitions, signalSchema, signalSourceSchema, standardReceiptKinds, standardReceiptPhases, standardReceiptStates, standardSignalSources, standardTaskKinds, sweepQueued, taskApprovalSchema, taskDevboxSchema, taskExecutionRepositoryIdsSchema, taskIdSchema, taskInitialState, taskKindSchema, taskOriginSchema, taskQuestionSchema, taskSchema, taskStateSchema, taskTransitions, terminalStates, usdSchema, vocabularySchema };
906
1050
 
907
1051
  //# sourceMappingURL=index.mjs.map