@amistio/cli 0.1.23 → 0.1.25

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.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { createHash as createHash7, randomUUID } from "node:crypto";
5
- import { writeFile as writeFile9 } from "node:fs/promises";
6
- import os7 from "node:os";
7
- import path15 from "node:path";
4
+ import { createHash as createHash8, randomUUID } from "node:crypto";
5
+ import { writeFile as writeFile10 } from "node:fs/promises";
6
+ import os8 from "node:os";
7
+ import path16 from "node:path";
8
8
  import { Command } from "commander";
9
9
 
10
10
  // ../shared/src/schemas.ts
@@ -24,6 +24,10 @@ var itemTypeSchema = z.enum([
24
24
  "securityScan",
25
25
  "securityFinding",
26
26
  "securityPostureSnapshot",
27
+ "testProfile",
28
+ "testQualityScan",
29
+ "testQualityFinding",
30
+ "implementationTestGate",
27
31
  "appEvaluationScan",
28
32
  "appEvaluationFinding",
29
33
  "implementationVerification",
@@ -79,9 +83,89 @@ var workStatusSchema = z.enum([
79
83
  ]);
80
84
  var sourceSchema = z.enum(["web", "repo", "generated", "runner"]);
81
85
  var executionModeSchema = z.enum(["localRunner", "cloudSandbox"]);
82
- var workKindSchema = z.enum(["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan", "appEvaluationScan", "projectContextRefresh", "implementationVerification"]);
86
+ var workKindSchema = z.enum(["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan", "appEvaluationScan", "projectContextRefresh", "implementationVerification", "testQualityScan", "implementationTestGate", "runnerCommand"]);
87
+ var autopilotModeSchema = z.enum(["disabled", "enabled", "paused"]);
88
+ var autopilotClassificationOutcomeSchema = z.enum(["safeAutopilotEligible", "requiresReview", "blocked", "unsafe"]);
89
+ var autopilotCandidateTypeSchema = z.enum([
90
+ "generatedDraftApproval",
91
+ "projectBrainSync",
92
+ "issueDiagnosis",
93
+ "issueFixHandoff",
94
+ "securityPostureScan",
95
+ "securityRemediationHandoff",
96
+ "appEvaluationScan",
97
+ "appEvaluationPlanHandoff",
98
+ "appEvaluationCleanup",
99
+ "projectContextRefresh",
100
+ "impactPreview",
101
+ "implementationQueue",
102
+ "implementationVerification",
103
+ "testQualityScan",
104
+ "implementationTestGate",
105
+ "requeueWork",
106
+ "runnerCommand"
107
+ ]);
108
+ var autopilotReasonCodeSchema = z.enum([
109
+ "allowedLowRisk",
110
+ "disabled",
111
+ "paused",
112
+ "runnerRequired",
113
+ "runnerIncompatible",
114
+ "repositoryRequired",
115
+ "staleRevision",
116
+ "duplicateInFlight",
117
+ "budgetExceeded",
118
+ "protectedSurface",
119
+ "destructiveAction",
120
+ "rawSourceUpload",
121
+ "secretRisk",
122
+ "primaryCheckoutMutation",
123
+ "policyBlocked",
124
+ "reviewRequired",
125
+ "cooldownActive",
126
+ "expired",
127
+ "failureBudgetExceeded",
128
+ "verificationFailed",
129
+ "redactionHit",
130
+ "candidateTypeNotAllowed",
131
+ "riskTierExceeded",
132
+ "unsafePath",
133
+ "schemaInvalid",
134
+ "notAdditive",
135
+ "scopeMismatch",
136
+ "sizeLimitExceeded",
137
+ "worktreeIsolationRequired",
138
+ "verificationRequired",
139
+ "organizationPolicyRequired",
140
+ "unsafe"
141
+ ]);
142
+ var autopilotGuardCheckSchema = z.object({
143
+ name: z.string().trim().min(1).max(120),
144
+ status: z.enum(["passed", "failed", "warning"]),
145
+ summary: z.string().trim().min(1).max(600)
146
+ });
83
147
  var implementationHandoffStatusSchema = z.enum(["notStarted", "noChanges", "prReady", "blocked", "failed"]);
84
148
  var implementationHandoffCleanupStatusSchema = z.enum(["notApplicable", "pending", "completed", "failed"]);
149
+ var safeRepoPathSchema = z.string().trim().min(1).max(300).refine((value) => {
150
+ if (value.startsWith("/") || /^[A-Za-z]:[\\/]/.test(value)) {
151
+ return false;
152
+ }
153
+ return !value.split(/[\\/]+/).includes("..");
154
+ }, "Path must be repository-relative without traversal");
155
+ var implementationHandoffArtifactStatusSchema = z.enum(["included", "skipped", "blocked"]);
156
+ var implementationHandoffArtifactSchema = z.object({
157
+ documentId: z.string().trim().min(1).max(200),
158
+ title: z.string().trim().min(1).max(200).optional(),
159
+ repoPath: safeRepoPathSchema,
160
+ contentFormat: documentContentFormatSchema,
161
+ status: implementationHandoffArtifactStatusSchema,
162
+ message: z.string().trim().min(1).max(600).optional()
163
+ }).strict();
164
+ var implementationHandoffArtifactsSchema = z.object({
165
+ included: z.array(implementationHandoffArtifactSchema).max(50).default([]),
166
+ skipped: z.array(implementationHandoffArtifactSchema).max(50).default([]),
167
+ blocked: z.array(implementationHandoffArtifactSchema).max(50).default([])
168
+ }).strict();
85
169
  var implementationVerificationOutcomeSchema = z.enum(["verifiedImplemented", "partiallyImplemented", "notImplemented", "inconclusive", "verificationBlocked"]);
86
170
  var implementationVerificationStatusSchema = z.enum(["queued", "running", "completed", "failed", "blocked", "stale"]);
87
171
  var implementationProofStatusSchema = z.enum(["unverified", "verificationQueued", "verificationRunning", "verifiedImplemented", "partiallyImplemented", "notImplemented", "inconclusive", "verificationBlocked", "humanOverride"]);
@@ -96,12 +180,97 @@ var implementationHandoffSchema = z.object({
96
180
  prUrl: z.string().trim().url().max(500).optional(),
97
181
  cleanupStatus: implementationHandoffCleanupStatusSchema.optional(),
98
182
  cleanupMessage: z.string().trim().min(1).max(600).optional(),
183
+ artifacts: implementationHandoffArtifactsSchema.optional(),
99
184
  message: z.string().trim().min(1).max(600).optional(),
100
185
  error: z.string().trim().min(1).max(1200).optional()
101
186
  }).strict();
102
187
  var generatedDraftStatusSchema = z.enum(["queued", "generating", "reviewing", "approved", "rejected", "changesRequested", "failed"]);
103
188
  var assistantQuestionModeSchema = z.enum(["brainOnly", "sourceAware"]);
104
189
  var impactRiskLevelSchema = z.enum(["low", "medium", "high", "critical"]);
190
+ var autopilotBudgetWindowSchema = z.object({
191
+ name: z.string().trim().min(1).max(80),
192
+ limit: z.number().int().nonnegative(),
193
+ used: z.number().int().nonnegative(),
194
+ remaining: z.number().int().nonnegative().optional(),
195
+ windowStartedAt: isoDateTimeSchema,
196
+ windowEndsAt: isoDateTimeSchema
197
+ }).strict();
198
+ var autopilotPolicySnapshotSchema = z.object({
199
+ policyVersion: z.string().trim().min(1).max(80),
200
+ mode: autopilotModeSchema,
201
+ enabledByUserId: z.string().min(1).optional(),
202
+ repositoryLinkId: z.string().min(1).optional(),
203
+ runnerId: z.string().min(1).optional(),
204
+ allowedWorkKinds: z.array(workKindSchema).max(20).default([]),
205
+ allowedCandidateTypes: z.array(autopilotCandidateTypeSchema).max(30).default([]),
206
+ maxRiskLevel: impactRiskLevelSchema.default("low"),
207
+ dailyWorkLimit: z.number().int().positive().max(100).optional(),
208
+ dailyPrLimit: z.number().int().nonnegative().max(100).optional(),
209
+ concurrentWorkLimit: z.number().int().positive().max(20).optional(),
210
+ runnerMinuteBudget: z.number().int().nonnegative().max(1440).optional(),
211
+ failureBudget: z.number().int().nonnegative().max(50).optional(),
212
+ expiresAt: isoDateTimeSchema.optional(),
213
+ reviewAfter: isoDateTimeSchema.optional(),
214
+ cooldownUntil: isoDateTimeSchema.optional(),
215
+ pausedReason: z.string().trim().min(1).max(600).optional(),
216
+ budgetWindows: z.array(autopilotBudgetWindowSchema).max(10).default([])
217
+ }).strict();
218
+ var autopilotCandidateLinksSchema = z.object({
219
+ workItemId: z.string().min(1).optional(),
220
+ documentId: z.string().min(1).optional(),
221
+ generatedDraftId: z.string().min(1).optional(),
222
+ issueId: z.string().min(1).optional(),
223
+ securityScanId: z.string().min(1).optional(),
224
+ securityFindingId: z.string().min(1).optional(),
225
+ appEvaluationScanId: z.string().min(1).optional(),
226
+ appEvaluationFindingId: z.string().min(1).optional(),
227
+ implementationVerificationId: z.string().min(1).optional(),
228
+ testQualityScanId: z.string().min(1).optional(),
229
+ testQualityFindingId: z.string().min(1).optional(),
230
+ implementationTestGateId: z.string().min(1).optional(),
231
+ projectContextRefreshId: z.string().min(1).optional(),
232
+ runnerCommandId: z.string().min(1).optional(),
233
+ repositoryLinkId: z.string().min(1).optional(),
234
+ runnerId: z.string().min(1).optional(),
235
+ branchName: z.string().trim().min(1).max(200).optional(),
236
+ worktreeKey: z.string().trim().min(1).max(300).optional(),
237
+ pullRequestUrl: z.string().trim().url().max(500).optional()
238
+ }).strict();
239
+ var autopilotCandidateActionSchema = z.object({
240
+ candidateId: z.string().trim().min(1).max(160),
241
+ candidateType: autopilotCandidateTypeSchema,
242
+ subjectId: z.string().trim().min(1).max(200),
243
+ workKind: workKindSchema.optional(),
244
+ riskLevel: impactRiskLevelSchema.default("low"),
245
+ summary: z.string().trim().min(1).max(600),
246
+ safeRepoPaths: z.array(safeRepoPathSchema).max(50).default([]),
247
+ evidenceSummaries: z.array(z.string().trim().min(1).max(300)).max(20).default([]),
248
+ links: autopilotCandidateLinksSchema.default({}),
249
+ policySnapshot: autopilotPolicySnapshotSchema.optional(),
250
+ outcome: autopilotClassificationOutcomeSchema.optional(),
251
+ reasonCodes: z.array(autopilotReasonCodeSchema).max(30).optional(),
252
+ guardChecks: z.array(autopilotGuardCheckSchema).max(30).optional()
253
+ }).strict();
254
+ var autopilotAuthorizationSchema = z.object({
255
+ authorizationId: z.string().trim().min(1).max(160),
256
+ policyVersion: z.string().trim().min(1).max(80),
257
+ mode: autopilotModeSchema,
258
+ outcome: autopilotClassificationOutcomeSchema,
259
+ candidateId: z.string().trim().min(1).max(160).optional(),
260
+ candidateType: autopilotCandidateTypeSchema.optional(),
261
+ subjectId: z.string().trim().min(1).max(200).optional(),
262
+ riskLevel: impactRiskLevelSchema,
263
+ reasonCodes: z.array(autopilotReasonCodeSchema).min(1),
264
+ guardChecks: z.array(autopilotGuardCheckSchema).default([]),
265
+ policySnapshot: autopilotPolicySnapshotSchema.optional(),
266
+ budgetWindows: z.array(autopilotBudgetWindowSchema).max(10).default([]),
267
+ linkedRecords: autopilotCandidateLinksSchema.optional(),
268
+ evidenceSummaries: z.array(z.string().trim().min(1).max(300)).max(20).default([]),
269
+ authorizedAt: isoDateTimeSchema.optional(),
270
+ authorizedBy: z.literal("autopilot").optional(),
271
+ reviewedByUserId: z.string().min(1).optional(),
272
+ summary: z.string().trim().min(1).max(600)
273
+ }).strict();
105
274
  var impactReportStatusSchema = z.enum(["queued", "running", "completed", "failed", "stale"]);
106
275
  var issueCategorySchema = z.enum(["bug", "regression", "brokenWorkflow", "security", "operational", "other"]);
107
276
  var issueSeveritySchema = z.enum(["low", "medium", "high", "critical"]);
@@ -114,6 +283,22 @@ var securityFindingSeveritySchema = z.enum(["info", "low", "medium", "high", "cr
114
283
  var securityFindingConfidenceSchema = z.enum(["low", "medium", "high"]);
115
284
  var securityFindingStatusSchema = z.enum(["open", "planReady", "approved", "changesRequested", "dismissed", "acceptedRisk", "remediationQueued", "resolved", "failed"]);
116
285
  var securityApprovalStateSchema = z.enum(["proposed", "approved", "changesRequested", "dismissed", "acceptedRisk", "implemented", "blocked"]);
286
+ var testProfileStatusSchema = z.enum(["detected", "reviewReady", "approved", "disabled", "stale"]);
287
+ var testPackageManagerSchema = z.enum(["pnpm", "npm", "yarn", "bun", "dotnet", "maven", "gradle", "mixed", "unknown"]);
288
+ var testCommandKindSchema = z.enum(["verify", "test", "coverage", "lint", "typecheck", "build", "focused"]);
289
+ var testCommandSourceSchema = z.enum(["detected", "approved", "manual"]);
290
+ var testCoverageMetricStatusSchema = z.enum(["met", "belowThreshold", "missing", "blocked", "unknown"]);
291
+ var testCommandStatusSchema = z.enum(["passed", "failed", "blocked", "skipped", "missing"]);
292
+ var testRedactionStatusSchema = z.enum(["clean", "redacted", "blocked"]);
293
+ var testQualityScanStatusSchema = z.enum(["queued", "running", "completed", "failed", "stale", "skipped"]);
294
+ var testQualityFindingCategorySchema = z.enum(["missingTests", "missingCoverage", "missingCommand", "lowCoverage", "failingTests", "failingQuality", "failingQualityCheck", "staleScan", "blockedEnvironment", "weakTests", "flakyTests", "unverifiedImplementation", "testGap", "other"]);
295
+ var testQualityFindingSeveritySchema = z.enum(["info", "low", "medium", "high", "critical"]);
296
+ var testQualityFindingConfidenceSchema = z.enum(["low", "medium", "high"]);
297
+ var testQualityFindingStatusSchema = z.enum(["open", "planReady", "approved", "changesRequested", "dismissed", "acceptedRisk", "implementationQueued", "resolved", "failed"]);
298
+ var testQualityApprovalStateSchema = z.enum(["proposed", "approved", "changesRequested", "dismissed", "acceptedRisk", "implemented", "blocked"]);
299
+ var implementationTestGateStatusSchema = z.enum(["queued", "running", "passed", "failed", "blocked", "overridden", "stale"]);
300
+ var implementationTestGateOutcomeSchema = z.enum(["passed", "failed", "blocked", "missingTests", "belowThreshold", "overridden"]);
301
+ var implementationTestGateSourceSchema = z.enum(["manual", "runner", "completion", "prHandoff", "push", "system"]);
117
302
  var appEvaluationScanStatusSchema = z.enum(["queued", "running", "completed", "failed", "stale", "skipped"]);
118
303
  var appEvaluationFindingCategorySchema = z.enum(["verification", "productDrift", "planCleanup", "promptRot", "missingMemory", "releaseReadiness", "ux", "accessibility", "performance", "reliability", "securityPosture", "other"]);
119
304
  var appEvaluationFindingSeveritySchema = z.enum(["info", "low", "medium", "high", "critical"]);
@@ -122,6 +307,10 @@ var appEvaluationFindingStatusSchema = z.enum(["open", "planReady", "approved",
122
307
  var appEvaluationApprovalStateSchema = z.enum(["proposed", "approved", "changesRequested", "dismissed", "acceptedRisk", "implemented", "blocked"]);
123
308
  var appEvaluationPlanLifecycleActionSchema = z.enum(["createPlan", "updatePlan", "markCompleted", "markImplemented", "markSuperseded", "markBlocked", "archive", "keepActive", "none"]);
124
309
  var activityEventTypeSchema = z.enum([
310
+ "autopilotAuthorized",
311
+ "autopilotReviewRequired",
312
+ "autopilotBlocked",
313
+ "autopilotPaused",
125
314
  "commandSubmitted",
126
315
  "generationQueued",
127
316
  "generationCompleted",
@@ -161,6 +350,22 @@ var activityEventTypeSchema = z.enum([
161
350
  "appEvaluationFindingReviewed",
162
351
  "appEvaluationPlanCreated",
163
352
  "appEvaluationImplementationQueued",
353
+ "testProfileDetected",
354
+ "testProfileUpdated",
355
+ "testQualityScanQueued",
356
+ "testQualityScanStarted",
357
+ "testQualityScanCompleted",
358
+ "testQualityScanFailed",
359
+ "testQualityFindingOpened",
360
+ "testQualityFindingReviewed",
361
+ "testQualityPlanCreated",
362
+ "implementationTestGateQueued",
363
+ "implementationTestGateStarted",
364
+ "implementationTestGatePassed",
365
+ "implementationTestGateFailed",
366
+ "implementationTestGateBlocked",
367
+ "implementationTestGateOverride",
368
+ "ciWorkflowRetired",
164
369
  "workRequeueRequested",
165
370
  "workRequeueQueued",
166
371
  "workRequeueBlocked",
@@ -288,6 +493,7 @@ var runnerResourceUsageSchema = z.object({
288
493
  systemLoadAverage5m: z.number().nonnegative().optional(),
289
494
  systemLoadAverage15m: z.number().nonnegative().optional()
290
495
  });
496
+ var runnerClaimLaneIdSchema = z.string().trim().min(1).max(80);
291
497
  var workIsolationModeSchema = z.enum(["none", "primaryCheckout", "branch", "gitWorktree"]);
292
498
  var repositoryLinkSourceSchema = z.enum(["web", "cli"]);
293
499
  var repositoryCloneStatusSchema = z.enum(["notCloned", "cloned", "validated", "failed"]);
@@ -370,6 +576,25 @@ var repositoryLinkItemSchema = baseItemSchema.extend({
370
576
  cloneStatus: repositoryCloneStatusSchema.optional(),
371
577
  autoSyncEnabled: z.boolean().optional(),
372
578
  appEvaluationEnabled: z.boolean().optional(),
579
+ testQualityEnabled: z.boolean().optional(),
580
+ autopilotMode: autopilotModeSchema.optional(),
581
+ autopilotPolicyVersion: z.string().trim().min(1).max(80).optional(),
582
+ autopilotSafeWorkKinds: z.array(workKindSchema).optional(),
583
+ autopilotAllowedCandidateTypes: z.array(autopilotCandidateTypeSchema).optional(),
584
+ autopilotMaxRiskLevel: impactRiskLevelSchema.optional(),
585
+ autopilotRunnerId: z.string().min(1).optional(),
586
+ autopilotPausedReason: z.string().trim().min(1).max(600).optional(),
587
+ autopilotDailyWorkLimit: z.number().int().positive().max(100).optional(),
588
+ autopilotDailyPrLimit: z.number().int().nonnegative().max(100).optional(),
589
+ autopilotConcurrentWorkLimit: z.number().int().positive().max(20).optional(),
590
+ autopilotRunnerMinuteBudget: z.number().int().nonnegative().max(1440).optional(),
591
+ autopilotFailureBudget: z.number().int().nonnegative().max(50).optional(),
592
+ autopilotExpiresAt: isoDateTimeSchema.optional(),
593
+ autopilotReviewAfter: isoDateTimeSchema.optional(),
594
+ autopilotCooldownUntil: isoDateTimeSchema.optional(),
595
+ autopilotBudgetWindows: z.array(autopilotBudgetWindowSchema).max(10).optional(),
596
+ autopilotUpdatedByUserId: z.string().min(1).optional(),
597
+ autopilotUpdatedAt: isoDateTimeSchema.optional(),
373
598
  lastValidatedAt: isoDateTimeSchema.optional(),
374
599
  status: z.enum(["active", "revoked"]).default("active")
375
600
  });
@@ -441,6 +666,9 @@ var workItemSchema = baseItemSchema.extend({
441
666
  appEvaluationScanId: z.string().min(1).optional(),
442
667
  appEvaluationFindingId: z.string().min(1).optional(),
443
668
  implementationVerificationId: z.string().min(1).optional(),
669
+ testQualityScanId: z.string().min(1).optional(),
670
+ testQualityFindingId: z.string().min(1).optional(),
671
+ implementationTestGateId: z.string().min(1).optional(),
444
672
  projectContextRefreshId: z.string().min(1).optional(),
445
673
  contextMissId: z.string().min(1).optional(),
446
674
  sourceWorkItemId: z.string().min(1).optional(),
@@ -468,6 +696,7 @@ var workItemSchema = baseItemSchema.extend({
468
696
  claimedByRunnerId: z.string().optional(),
469
697
  pairedByUserId: z.string().min(1).optional(),
470
698
  machineId: z.string().min(1).optional(),
699
+ claimLaneId: runnerClaimLaneIdSchema.optional(),
471
700
  claimLeaseId: z.string().min(1).optional(),
472
701
  claimAttempt: z.number().int().nonnegative().optional(),
473
702
  leaseExpiresAt: isoDateTimeSchema.optional(),
@@ -488,6 +717,12 @@ var workItemSchema = baseItemSchema.extend({
488
717
  sessionDecision: sessionDecisionSchema.optional(),
489
718
  sessionDecisionReason: z.string().min(1).optional(),
490
719
  implementationHandoff: implementationHandoffSchema.optional(),
720
+ autopilotAuthorization: autopilotAuthorizationSchema.optional(),
721
+ autopilotAuthorizationId: z.string().min(1).optional(),
722
+ autopilotCandidateId: z.string().min(1).optional(),
723
+ autopilotCandidateType: autopilotCandidateTypeSchema.optional(),
724
+ autopilotClassificationOutcome: autopilotClassificationOutcomeSchema.optional(),
725
+ autopilotPolicyVersion: z.string().trim().min(1).max(80).optional(),
491
726
  lastStatusMessage: z.string().optional(),
492
727
  lastStatusAt: isoDateTimeSchema
493
728
  });
@@ -507,6 +742,8 @@ var runnerHeartbeatItemSchema = baseItemSchema.extend({
507
742
  supportedWorkKinds: z.array(workKindSchema).optional(),
508
743
  supportsBranchIsolation: z.boolean().optional(),
509
744
  supportsGitWorktreeIsolation: z.boolean().optional(),
745
+ maxConcurrentWork: z.number().int().min(1).max(4).optional(),
746
+ activeClaimLaneIds: z.array(runnerClaimLaneIdSchema).max(4).optional(),
510
747
  currentWorkItemId: z.string().min(1).optional(),
511
748
  currentImplementationScopeId: z.string().min(1).optional(),
512
749
  currentWorktreeKey: z.string().min(1).optional(),
@@ -565,6 +802,7 @@ var runnerExecutionLogItemSchema = baseItemSchema.extend({
565
802
  executionWorktreeKey: z.string().min(1).optional(),
566
803
  isolationMode: workIsolationModeSchema.optional(),
567
804
  repositoryLockId: z.string().min(1).optional(),
805
+ claimLaneId: runnerClaimLaneIdSchema.optional(),
568
806
  claimLeaseId: z.string().min(1).optional(),
569
807
  status: z.enum(["claimed", "running", "completed", "failed", "blocked", "idle"]),
570
808
  executionMode: executionModeSchema.optional(),
@@ -796,12 +1034,7 @@ var projectContextFreshnessSchema = z.enum(["fresh", "stale", "partial", "missin
796
1034
  var projectContextMapStatusSchema = z.enum(["draft", "proposed", "approved", "stale", "archived"]);
797
1035
  var projectContextRefreshStatusSchema = z.enum(["queued", "running", "completed", "failed", "skipped"]);
798
1036
  var contextMissStatusSchema = z.enum(["open", "resolved", "dismissed"]);
799
- var projectContextRepoPathSchema = z.string().trim().min(1).max(300).refine((value) => {
800
- if (value.startsWith("/") || /^[A-Za-z]:[\\/]/.test(value)) {
801
- return false;
802
- }
803
- return !value.split(/[\\/]+/).includes("..");
804
- }, "Path must be repository-relative without traversal");
1037
+ var projectContextRepoPathSchema = safeRepoPathSchema;
805
1038
  var projectContextCitationSchema = assistantCitationSchema.extend({
806
1039
  repoPath: projectContextRepoPathSchema.optional()
807
1040
  });
@@ -1269,6 +1502,179 @@ var securityPostureSnapshotItemSchema = baseItemSchema.extend({
1269
1502
  nextScheduledAt: isoDateTimeSchema.optional(),
1270
1503
  summary: z.string().trim().min(1).max(2e3).optional()
1271
1504
  });
1505
+ var testCoverageThresholdsSchema = z.object({
1506
+ lines: z.number().min(0).max(100).optional(),
1507
+ statements: z.number().min(0).max(100).optional(),
1508
+ branches: z.number().min(0).max(100).optional(),
1509
+ functions: z.number().min(0).max(100).optional()
1510
+ }).strict();
1511
+ var testProfileCommandSchema = z.object({
1512
+ commandId: z.string().trim().min(1).max(160),
1513
+ kind: testCommandKindSchema,
1514
+ label: z.string().trim().min(1).max(200),
1515
+ source: testCommandSourceSchema.default("detected"),
1516
+ packageName: z.string().trim().min(1).max(200).optional(),
1517
+ scriptName: z.string().trim().min(1).max(120).optional(),
1518
+ tool: z.string().trim().min(1).max(120).optional(),
1519
+ workingDirectory: projectContextRepoPathSchema.optional(),
1520
+ required: z.boolean().default(false),
1521
+ estimatedDurationSeconds: z.number().int().positive().max(7200).optional()
1522
+ }).strict();
1523
+ var testProfileItemSchema = baseItemSchema.extend({
1524
+ type: z.literal("testProfile"),
1525
+ projectId: z.string().min(1),
1526
+ testProfileId: z.string().min(1),
1527
+ repositoryLinkId: z.string().min(1),
1528
+ status: testProfileStatusSchema,
1529
+ enabled: z.boolean().default(true),
1530
+ detectedPackageManager: testPackageManagerSchema.default("unknown"),
1531
+ packageManagers: z.array(testPackageManagerSchema).default([]),
1532
+ commands: z.array(testProfileCommandSchema).max(40).default([]),
1533
+ coverageThresholds: testCoverageThresholdsSchema.default({}),
1534
+ defaultWholeAppCommandId: z.string().trim().min(1).max(160).optional(),
1535
+ focusedCommandIds: z.array(z.string().trim().min(1).max(160)).max(20).default([]),
1536
+ lastDetectedAt: isoDateTimeSchema.optional(),
1537
+ lastReviewedRevision: z.string().trim().min(1).max(160).optional(),
1538
+ reviewedByUserId: z.string().min(1).optional(),
1539
+ reviewedAt: isoDateTimeSchema.optional()
1540
+ });
1541
+ var testCommandSummarySchema = z.object({
1542
+ commandId: z.string().trim().min(1).max(160).optional(),
1543
+ kind: testCommandKindSchema,
1544
+ label: z.string().trim().min(1).max(200),
1545
+ status: testCommandStatusSchema,
1546
+ durationMs: z.number().int().nonnegative().optional(),
1547
+ exitCode: z.number().int().min(0).max(255).optional(),
1548
+ summary: z.string().trim().min(1).max(1200),
1549
+ outputExcerpt: z.string().trim().min(1).max(1200).optional(),
1550
+ safePaths: z.array(projectContextRepoPathSchema).max(30).default([])
1551
+ }).strict();
1552
+ var testCoverageSummarySchema = z.object({
1553
+ status: testCoverageMetricStatusSchema,
1554
+ lines: z.number().min(0).max(100).optional(),
1555
+ statements: z.number().min(0).max(100).optional(),
1556
+ branches: z.number().min(0).max(100).optional(),
1557
+ functions: z.number().min(0).max(100).optional(),
1558
+ thresholds: testCoverageThresholdsSchema.default({}),
1559
+ reportPath: projectContextRepoPathSchema.optional(),
1560
+ summary: z.string().trim().min(1).max(1200).optional()
1561
+ }).strict();
1562
+ var testRedactionStateSchema = z.object({
1563
+ status: testRedactionStatusSchema.default("clean"),
1564
+ redactedFields: z.array(z.string().trim().min(1).max(120)).max(40).default([]),
1565
+ summary: z.string().trim().min(1).max(600).optional()
1566
+ }).strict();
1567
+ var testQualityFindingResultSchema = z.object({
1568
+ title: z.string().trim().min(1).max(200),
1569
+ category: testQualityFindingCategorySchema,
1570
+ severity: testQualityFindingSeveritySchema,
1571
+ confidence: testQualityFindingConfidenceSchema.default("medium"),
1572
+ summary: z.string().trim().min(1).max(2e3),
1573
+ affectedSurfaces: z.array(z.string().trim().min(1).max(300)).default([]),
1574
+ evidence: z.array(z.string().trim().min(1).max(600)).default([]),
1575
+ safePaths: z.array(projectContextRepoPathSchema).max(30).default([]),
1576
+ suggestedAction: z.string().trim().min(1).max(4e3),
1577
+ verificationPlan: z.array(z.string().trim().min(1).max(300)).min(1),
1578
+ proposedPlanTitle: z.string().trim().min(1).max(200).optional(),
1579
+ proposedPlanRepoPath: projectContextRepoPathSchema.optional(),
1580
+ proposedPlanContent: z.string().trim().min(1).max(3e4).optional(),
1581
+ dedupeKey: z.string().trim().min(1).max(240).optional(),
1582
+ status: testQualityFindingStatusSchema.default("open")
1583
+ }).strict();
1584
+ var testQualityScanResultSchema = z.object({
1585
+ summary: z.string().trim().min(1).max(2e3),
1586
+ profile: testProfileItemSchema.omit({ id: true, type: true, schemaVersion: true, accountId: true, projectId: true, createdAt: true, updatedAt: true }).optional(),
1587
+ commandSummaries: z.array(testCommandSummarySchema).max(40).default([]),
1588
+ coverage: testCoverageSummarySchema.optional(),
1589
+ findings: z.array(testQualityFindingResultSchema).max(50).default([]),
1590
+ blockedReasons: z.array(z.string().trim().min(1).max(600)).max(20).default([]),
1591
+ redactionState: testRedactionStateSchema.default({ status: "clean", redactedFields: [] }),
1592
+ verificationPlan: z.array(z.string().trim().min(1).max(300)).min(1),
1593
+ warnings: z.array(z.string().trim().min(1).max(600)).default([])
1594
+ }).strict();
1595
+ var testQualityScanItemSchema = baseItemSchema.extend({
1596
+ type: z.literal("testQualityScan"),
1597
+ projectId: z.string().min(1),
1598
+ testQualityScanId: z.string().min(1),
1599
+ scheduledDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
1600
+ source: z.enum(["runner", "manual"]),
1601
+ status: testQualityScanStatusSchema,
1602
+ repositoryLinkId: z.string().min(1).optional(),
1603
+ runnerId: z.string().min(1).optional(),
1604
+ workItemId: z.string().min(1).optional(),
1605
+ testProfileId: z.string().min(1).optional(),
1606
+ findingIds: z.array(z.string().min(1)).default([]),
1607
+ summary: z.string().trim().min(1).max(2e3).optional(),
1608
+ result: testQualityScanResultSchema.optional(),
1609
+ startedAt: isoDateTimeSchema.optional(),
1610
+ completedAt: isoDateTimeSchema.optional(),
1611
+ failedAt: isoDateTimeSchema.optional(),
1612
+ error: z.string().max(1e3).optional()
1613
+ });
1614
+ var testQualityFindingItemSchema = baseItemSchema.extend({
1615
+ type: z.literal("testQualityFinding"),
1616
+ projectId: z.string().min(1),
1617
+ testQualityFindingId: z.string().min(1),
1618
+ testQualityScanId: z.string().min(1).optional(),
1619
+ implementationTestGateId: z.string().min(1).optional(),
1620
+ repositoryLinkId: z.string().min(1).optional(),
1621
+ title: z.string().trim().min(1).max(200),
1622
+ category: testQualityFindingCategorySchema,
1623
+ severity: testQualityFindingSeveritySchema,
1624
+ confidence: testQualityFindingConfidenceSchema.default("medium"),
1625
+ status: testQualityFindingStatusSchema,
1626
+ approvalState: testQualityApprovalStateSchema.default("proposed"),
1627
+ summary: z.string().trim().min(1).max(2e3),
1628
+ affectedSurfaces: z.array(z.string().trim().min(1).max(300)).default([]),
1629
+ evidence: z.array(z.string().trim().min(1).max(600)).default([]),
1630
+ safePaths: z.array(projectContextRepoPathSchema).max(30).default([]),
1631
+ suggestedAction: z.string().trim().min(1).max(4e3),
1632
+ verificationPlan: z.array(z.string().trim().min(1).max(300)).min(1),
1633
+ dedupeKey: z.string().trim().min(1).max(240).optional(),
1634
+ proposedPlanDocumentId: z.string().min(1).optional(),
1635
+ implementationWorkItemId: z.string().min(1).optional(),
1636
+ reviewNotes: z.string().trim().min(1).optional(),
1637
+ reviewedByUserId: z.string().min(1).optional(),
1638
+ reviewedAt: isoDateTimeSchema.optional(),
1639
+ resolvedAt: isoDateTimeSchema.optional(),
1640
+ lastReviewAction: z.enum(["approve", "requestChanges", "dismiss", "acceptRisk", "reopen"]).optional()
1641
+ });
1642
+ var implementationTestGateResultSchema = z.object({
1643
+ outcome: implementationTestGateOutcomeSchema,
1644
+ summary: z.string().trim().min(1).max(2e3),
1645
+ commandSummaries: z.array(testCommandSummarySchema).max(40).default([]),
1646
+ coverage: testCoverageSummarySchema.optional(),
1647
+ findings: z.array(testQualityFindingResultSchema).max(50).default([]),
1648
+ testGapJustification: z.string().trim().min(1).max(1200).optional(),
1649
+ blockedReasons: z.array(z.string().trim().min(1).max(600)).max(20).default([]),
1650
+ redactionState: testRedactionStateSchema.default({ status: "clean", redactedFields: [] }),
1651
+ verificationPlan: z.array(z.string().trim().min(1).max(300)).default([]),
1652
+ warnings: z.array(z.string().trim().min(1).max(600)).default([])
1653
+ }).strict();
1654
+ var implementationTestGateItemSchema = baseItemSchema.extend({
1655
+ type: z.literal("implementationTestGate"),
1656
+ projectId: z.string().min(1),
1657
+ implementationTestGateId: z.string().min(1),
1658
+ status: implementationTestGateStatusSchema,
1659
+ source: implementationTestGateSourceSchema.default("system"),
1660
+ sourceWorkItemId: z.string().min(1),
1661
+ gateWorkItemId: z.string().min(1).optional(),
1662
+ repositoryLinkId: z.string().min(1).optional(),
1663
+ runnerId: z.string().min(1).optional(),
1664
+ planDocumentId: z.string().min(1).optional(),
1665
+ planDocumentRevision: z.number().int().nonnegative().optional(),
1666
+ testProfileId: z.string().min(1).optional(),
1667
+ outcome: implementationTestGateOutcomeSchema.optional(),
1668
+ result: implementationTestGateResultSchema.optional(),
1669
+ summary: z.string().trim().min(1).max(2e3).optional(),
1670
+ startedAt: isoDateTimeSchema.optional(),
1671
+ completedAt: isoDateTimeSchema.optional(),
1672
+ failedAt: isoDateTimeSchema.optional(),
1673
+ overriddenByUserId: z.string().min(1).optional(),
1674
+ overrideReason: z.string().trim().min(1).max(1e3).optional(),
1675
+ overriddenAt: isoDateTimeSchema.optional(),
1676
+ error: z.string().max(1e3).optional()
1677
+ });
1272
1678
  var appEvaluationFindingResultSchema = z.object({
1273
1679
  title: z.string().trim().min(1).max(200),
1274
1680
  category: appEvaluationFindingCategorySchema,
@@ -1358,13 +1764,19 @@ var activityEventItemSchema = baseItemSchema.extend({
1358
1764
  relatedAppEvaluationScanId: z.string().min(1).optional(),
1359
1765
  relatedAppEvaluationFindingId: z.string().min(1).optional(),
1360
1766
  relatedImplementationVerificationId: z.string().min(1).optional(),
1767
+ relatedTestQualityScanId: z.string().min(1).optional(),
1768
+ relatedTestQualityFindingId: z.string().min(1).optional(),
1769
+ relatedImplementationTestGateId: z.string().min(1).optional(),
1361
1770
  relatedProjectContextRefreshId: z.string().min(1).optional(),
1362
1771
  relatedProjectSystemDiagramId: z.string().min(1).optional(),
1363
1772
  relatedContextMissId: z.string().min(1).optional(),
1773
+ relatedAutopilotAuthorizationId: z.string().min(1).optional(),
1774
+ relatedAutopilotCandidateId: z.string().min(1).optional(),
1364
1775
  generatedDraftId: z.string().min(1).optional(),
1365
1776
  runnerId: z.string().min(1).optional(),
1366
1777
  repositoryLinkId: z.string().min(1).optional(),
1367
1778
  runnerLogId: z.string().min(1).optional(),
1779
+ claimLaneId: runnerClaimLaneIdSchema.optional(),
1368
1780
  commandId: z.string().min(1).optional()
1369
1781
  });
1370
1782
  var activityHandoffExportSchema = z.object({
@@ -1421,6 +1833,10 @@ var projectItemUnionSchema = z.discriminatedUnion("type", [
1421
1833
  securityScanItemSchema,
1422
1834
  securityFindingItemSchema,
1423
1835
  securityPostureSnapshotItemSchema,
1836
+ testProfileItemSchema,
1837
+ testQualityScanItemSchema,
1838
+ testQualityFindingItemSchema,
1839
+ implementationTestGateItemSchema,
1424
1840
  appEvaluationScanItemSchema,
1425
1841
  appEvaluationFindingItemSchema,
1426
1842
  implementationVerificationItemSchema,
@@ -2178,6 +2594,24 @@ async function readGitTopLevel(rootDir) {
2178
2594
 
2179
2595
  // src/api-client.ts
2180
2596
  import { z as z3 } from "zod";
2597
+ var AmistioApiError = class extends Error {
2598
+ constructor(status, statusText, detail) {
2599
+ super(`Amistio API request failed: ${status} ${statusText}${detail ? ` - ${detail}` : ""}`);
2600
+ this.status = status;
2601
+ this.statusText = statusText;
2602
+ this.detail = detail;
2603
+ this.name = "AmistioApiError";
2604
+ }
2605
+ status;
2606
+ statusText;
2607
+ detail;
2608
+ };
2609
+ function isRetryableApiError(error) {
2610
+ if (error instanceof AmistioApiError) {
2611
+ return error.status === 408 || error.status === 429 || error.status >= 500;
2612
+ }
2613
+ return error instanceof TypeError;
2614
+ }
2181
2615
  var ApiClient = class {
2182
2616
  constructor(options) {
2183
2617
  this.options = options;
@@ -2508,6 +2942,26 @@ var ApiClient = class {
2508
2942
  }
2509
2943
  );
2510
2944
  }
2945
+ async submitTestQualityScanResult(projectId, workItemId, result) {
2946
+ return this.request(
2947
+ `/projects/${projectId}/work-items/${workItemId}/test-quality-result`,
2948
+ z3.object({ scan: testQualityScanItemSchema, findings: z3.array(testQualityFindingItemSchema), workItem: workItemSchema }),
2949
+ {
2950
+ method: "POST",
2951
+ body: JSON.stringify(result)
2952
+ }
2953
+ );
2954
+ }
2955
+ async submitImplementationTestGateResult(projectId, workItemId, result) {
2956
+ return this.request(
2957
+ `/projects/${projectId}/work-items/${workItemId}/implementation-test-gate-result`,
2958
+ z3.object({ gate: implementationTestGateItemSchema, findings: z3.array(testQualityFindingItemSchema), workItem: workItemSchema, sourceWorkItem: workItemSchema.optional() }),
2959
+ {
2960
+ method: "POST",
2961
+ body: JSON.stringify(result)
2962
+ }
2963
+ );
2964
+ }
2511
2965
  async request(urlPath, schema, init) {
2512
2966
  const response = await fetch(resolveApiUrl(this.options.apiUrl, urlPath), {
2513
2967
  ...init,
@@ -2519,7 +2973,7 @@ var ApiClient = class {
2519
2973
  });
2520
2974
  if (!response.ok) {
2521
2975
  const detail = await response.text().catch(() => "");
2522
- throw new Error(`Amistio API request failed: ${response.status} ${response.statusText}${detail ? ` - ${detail}` : ""}`);
2976
+ throw new AmistioApiError(response.status, response.statusText, detail);
2523
2977
  }
2524
2978
  return schema.parse(await response.json());
2525
2979
  }
@@ -2554,8 +3008,8 @@ var toolSessionMutationSchema = z3.object({
2554
3008
  });
2555
3009
  function resolveApiUrl(apiUrl, urlPath) {
2556
3010
  const base = apiUrl.endsWith("/") ? apiUrl.slice(0, -1) : apiUrl;
2557
- const path16 = urlPath.startsWith("/") ? urlPath : `/${urlPath}`;
2558
- return new URL(`${base}${path16}`);
3011
+ const path17 = urlPath.startsWith("/") ? urlPath : `/${urlPath}`;
3012
+ return new URL(`${base}${path17}`);
2559
3013
  }
2560
3014
 
2561
3015
  // src/orchestrator.ts
@@ -2619,11 +3073,347 @@ async function writePromptFile(filePath, prompt) {
2619
3073
  return filePath;
2620
3074
  }
2621
3075
 
2622
- // src/local-tool-runner.ts
2623
- import { spawn } from "node:child_process";
2624
- import { mkdtemp, readFile as readFile3, rm, writeFile as writeFile4 } from "node:fs/promises";
3076
+ // src/result-finalization-outbox.ts
3077
+ import { createHash as createHash2 } from "node:crypto";
3078
+ import { mkdir as mkdir5, readdir as readdir2, readFile as readFile3, rename, rm, writeFile as writeFile4 } from "node:fs/promises";
2625
3079
  import os2 from "node:os";
2626
3080
  import path5 from "node:path";
3081
+ import { z as z4 } from "zod";
3082
+ var brainGenerationResultMutationSchema = z4.discriminatedUnion("status", [
3083
+ z4.object({
3084
+ status: z4.literal("completed"),
3085
+ runnerId: z4.string().min(1),
3086
+ idempotencyKey: z4.string().min(1),
3087
+ artifacts: z4.array(generatedBrainArtifactSchema).min(1),
3088
+ tool: z4.string().min(1).optional(),
3089
+ durationMs: z4.number().int().nonnegative().optional(),
3090
+ sessionPolicy: sessionPolicySchema.optional(),
3091
+ sessionGroupKey: z4.string().min(1).optional(),
3092
+ toolSessionId: z4.string().min(1).optional(),
3093
+ sessionDecision: sessionDecisionSchema.optional(),
3094
+ sessionDecisionReason: z4.string().min(1).optional(),
3095
+ message: z4.string().optional()
3096
+ }),
3097
+ z4.object({
3098
+ status: z4.literal("failed"),
3099
+ runnerId: z4.string().min(1),
3100
+ idempotencyKey: z4.string().min(1),
3101
+ tool: z4.string().min(1).optional(),
3102
+ durationMs: z4.number().int().nonnegative().optional(),
3103
+ sessionPolicy: sessionPolicySchema.optional(),
3104
+ sessionGroupKey: z4.string().min(1).optional(),
3105
+ toolSessionId: z4.string().min(1).optional(),
3106
+ sessionDecision: sessionDecisionSchema.optional(),
3107
+ sessionDecisionReason: z4.string().min(1).optional(),
3108
+ message: z4.string().optional(),
3109
+ error: z4.string().optional()
3110
+ })
3111
+ ]);
3112
+ var brainGenerationFinalizationEntrySchema = z4.object({
3113
+ schemaVersion: z4.literal(1),
3114
+ kind: z4.literal("brainGenerationResult"),
3115
+ status: z4.enum(["pending", "terminal"]),
3116
+ accountId: z4.string().min(1),
3117
+ projectId: z4.string().min(1),
3118
+ repositoryLinkId: z4.string().min(1),
3119
+ runnerId: z4.string().min(1),
3120
+ workItemId: z4.string().min(1),
3121
+ workKind: z4.enum(["brainGeneration", "planRevision"]),
3122
+ attempt: z4.number().int().nonnegative(),
3123
+ idempotencyKey: z4.string().min(1),
3124
+ result: brainGenerationResultMutationSchema,
3125
+ retryCount: z4.number().int().nonnegative(),
3126
+ createdAt: z4.string().min(1),
3127
+ updatedAt: z4.string().min(1),
3128
+ lastError: z4.string().optional()
3129
+ });
3130
+ var resultFinalizationTelemetrySchema = z4.object({
3131
+ runnerId: z4.string().min(1),
3132
+ idempotencyKey: z4.string().min(1),
3133
+ tool: z4.string().min(1).optional(),
3134
+ durationMs: z4.number().int().nonnegative().optional(),
3135
+ sessionPolicy: sessionPolicySchema.optional(),
3136
+ sessionGroupKey: z4.string().min(1).optional(),
3137
+ toolSessionId: z4.string().min(1).optional(),
3138
+ sessionDecision: sessionDecisionSchema.optional(),
3139
+ sessionDecisionReason: z4.string().min(1).optional(),
3140
+ message: z4.string().optional()
3141
+ });
3142
+ var resultFinalizationFailureSchema = resultFinalizationTelemetrySchema.extend({
3143
+ status: z4.literal("failed"),
3144
+ error: z4.string().optional()
3145
+ });
3146
+ var assistantResultMutationSchema = z4.discriminatedUnion("status", [
3147
+ resultFinalizationTelemetrySchema.extend({
3148
+ status: z4.literal("completed"),
3149
+ answer: assistantAnswerResultSchema.shape.answer,
3150
+ sourceBoundary: assistantAnswerResultSchema.shape.sourceBoundary.optional(),
3151
+ citations: assistantAnswerResultSchema.shape.citations.optional()
3152
+ }),
3153
+ resultFinalizationFailureSchema
3154
+ ]);
3155
+ var impactPreviewResultMutationSchema = z4.discriminatedUnion("status", [
3156
+ resultFinalizationTelemetrySchema.extend({
3157
+ status: z4.literal("completed"),
3158
+ report: impactPreviewResultSchema
3159
+ }),
3160
+ resultFinalizationFailureSchema
3161
+ ]);
3162
+ var issueDiagnosisResultMutationSchema = z4.discriminatedUnion("status", [
3163
+ resultFinalizationTelemetrySchema.extend({
3164
+ status: z4.literal("completed"),
3165
+ diagnosis: issueDiagnosisResultSchema
3166
+ }),
3167
+ resultFinalizationFailureSchema
3168
+ ]);
3169
+ var securityPostureScanResultMutationSchema = z4.discriminatedUnion("status", [
3170
+ resultFinalizationTelemetrySchema.extend({
3171
+ status: z4.literal("completed"),
3172
+ result: securityPostureScanResultSchema
3173
+ }),
3174
+ resultFinalizationFailureSchema
3175
+ ]);
3176
+ var appEvaluationScanResultMutationSchema = z4.discriminatedUnion("status", [
3177
+ resultFinalizationTelemetrySchema.extend({
3178
+ status: z4.literal("completed"),
3179
+ result: appEvaluationScanResultSchema
3180
+ }),
3181
+ resultFinalizationFailureSchema
3182
+ ]);
3183
+ var projectContextRefreshResultMutationSchema = z4.discriminatedUnion("status", [
3184
+ resultFinalizationTelemetrySchema.extend({
3185
+ status: z4.literal("completed"),
3186
+ result: projectContextRefreshResultSchema
3187
+ }),
3188
+ resultFinalizationFailureSchema
3189
+ ]);
3190
+ var implementationVerificationResultMutationSchema = z4.discriminatedUnion("status", [
3191
+ resultFinalizationTelemetrySchema.extend({
3192
+ status: z4.literal("completed"),
3193
+ result: implementationVerificationResultSchema
3194
+ }),
3195
+ resultFinalizationFailureSchema
3196
+ ]);
3197
+ var testQualityScanResultMutationSchema = z4.discriminatedUnion("status", [
3198
+ resultFinalizationTelemetrySchema.extend({
3199
+ status: z4.literal("completed"),
3200
+ result: testQualityScanResultSchema
3201
+ }),
3202
+ resultFinalizationFailureSchema
3203
+ ]);
3204
+ var implementationTestGateResultMutationSchema = z4.discriminatedUnion("status", [
3205
+ resultFinalizationTelemetrySchema.extend({
3206
+ status: z4.literal("completed"),
3207
+ result: implementationTestGateResultSchema
3208
+ }),
3209
+ resultFinalizationFailureSchema
3210
+ ]);
3211
+ var durableResultMutationSchema = z4.discriminatedUnion("resultKind", [
3212
+ z4.object({ resultKind: z4.literal("assistantResult"), result: assistantResultMutationSchema }),
3213
+ z4.object({ resultKind: z4.literal("impactPreviewResult"), result: impactPreviewResultMutationSchema }),
3214
+ z4.object({ resultKind: z4.literal("issueDiagnosisResult"), result: issueDiagnosisResultMutationSchema }),
3215
+ z4.object({ resultKind: z4.literal("securityPostureScanResult"), result: securityPostureScanResultMutationSchema }),
3216
+ z4.object({ resultKind: z4.literal("appEvaluationScanResult"), result: appEvaluationScanResultMutationSchema }),
3217
+ z4.object({ resultKind: z4.literal("projectContextRefreshResult"), result: projectContextRefreshResultMutationSchema }),
3218
+ z4.object({ resultKind: z4.literal("implementationVerificationResult"), result: implementationVerificationResultMutationSchema }),
3219
+ z4.object({ resultKind: z4.literal("testQualityScanResult"), result: testQualityScanResultMutationSchema }),
3220
+ z4.object({ resultKind: z4.literal("implementationTestGateResult"), result: implementationTestGateResultMutationSchema })
3221
+ ]);
3222
+ var durableResultFinalizationEntrySchema = z4.object({
3223
+ schemaVersion: z4.literal(1),
3224
+ kind: z4.literal("runnerResult"),
3225
+ status: z4.enum(["pending", "terminal"]),
3226
+ accountId: z4.string().min(1),
3227
+ projectId: z4.string().min(1),
3228
+ repositoryLinkId: z4.string().min(1),
3229
+ runnerId: z4.string().min(1),
3230
+ workItemId: z4.string().min(1),
3231
+ workKind: z4.enum(["assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan", "appEvaluationScan", "projectContextRefresh", "implementationVerification", "testQualityScan", "implementationTestGate"]),
3232
+ attempt: z4.number().int().nonnegative(),
3233
+ idempotencyKey: z4.string().min(1),
3234
+ retryCount: z4.number().int().nonnegative(),
3235
+ createdAt: z4.string().min(1),
3236
+ updatedAt: z4.string().min(1),
3237
+ lastError: z4.string().optional()
3238
+ }).and(durableResultMutationSchema);
3239
+ function defaultResultFinalizationOutboxDir() {
3240
+ return path5.join(os2.homedir(), ".config", "amistio", "result-finalizations");
3241
+ }
3242
+ function createBrainGenerationFinalizationEntry(input, now = (/* @__PURE__ */ new Date()).toISOString()) {
3243
+ return brainGenerationFinalizationEntrySchema.parse({
3244
+ schemaVersion: 1,
3245
+ kind: "brainGenerationResult",
3246
+ status: "pending",
3247
+ accountId: input.accountId,
3248
+ projectId: input.projectId,
3249
+ repositoryLinkId: input.repositoryLinkId,
3250
+ runnerId: input.runnerId,
3251
+ workItemId: input.workItemId,
3252
+ workKind: input.workKind,
3253
+ attempt: input.attempt,
3254
+ idempotencyKey: input.idempotencyKey,
3255
+ result: input.result,
3256
+ retryCount: 0,
3257
+ createdAt: now,
3258
+ updatedAt: now
3259
+ });
3260
+ }
3261
+ function createDurableResultFinalizationEntry(input, now = (/* @__PURE__ */ new Date()).toISOString()) {
3262
+ return durableResultFinalizationEntrySchema.parse({
3263
+ schemaVersion: 1,
3264
+ kind: "runnerResult",
3265
+ status: "pending",
3266
+ accountId: input.accountId,
3267
+ projectId: input.projectId,
3268
+ repositoryLinkId: input.repositoryLinkId,
3269
+ runnerId: input.runnerId,
3270
+ workItemId: input.workItemId,
3271
+ workKind: input.workKind,
3272
+ attempt: input.attempt,
3273
+ idempotencyKey: input.idempotencyKey,
3274
+ resultKind: input.resultKind,
3275
+ result: input.result,
3276
+ retryCount: 0,
3277
+ createdAt: now,
3278
+ updatedAt: now
3279
+ });
3280
+ }
3281
+ async function upsertBrainGenerationFinalizationEntry(entry, outboxDir = defaultResultFinalizationOutboxDir()) {
3282
+ await mkdir5(outboxDir, { recursive: true });
3283
+ const filePath = brainGenerationFinalizationEntryPath(entry, outboxDir);
3284
+ const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
3285
+ await writeFile4(tempPath, `${JSON.stringify(entry, null, 2)}
3286
+ `, "utf8");
3287
+ await rename(tempPath, filePath);
3288
+ }
3289
+ async function listPendingBrainGenerationFinalizations(scope, outboxDir = defaultResultFinalizationOutboxDir()) {
3290
+ const entries = await readdir2(outboxDir, { withFileTypes: true }).catch((error) => {
3291
+ if (error.code === "ENOENT") return [];
3292
+ throw error;
3293
+ });
3294
+ const pending = [];
3295
+ for (const entry of entries) {
3296
+ if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
3297
+ const filePath = path5.join(outboxDir, entry.name);
3298
+ let raw;
3299
+ try {
3300
+ raw = JSON.parse(await readFile3(filePath, "utf8"));
3301
+ } catch {
3302
+ continue;
3303
+ }
3304
+ const parsed = brainGenerationFinalizationEntrySchema.safeParse(raw);
3305
+ if (!parsed.success) continue;
3306
+ const value = parsed.data;
3307
+ if (value.status !== "pending") continue;
3308
+ if (value.accountId !== scope.accountId || value.projectId !== scope.projectId || value.repositoryLinkId !== scope.repositoryLinkId || value.runnerId !== scope.runnerId) continue;
3309
+ pending.push(value);
3310
+ }
3311
+ return pending.sort((first, second) => Date.parse(first.createdAt) - Date.parse(second.createdAt));
3312
+ }
3313
+ async function upsertDurableResultFinalizationEntry(entry, outboxDir = defaultResultFinalizationOutboxDir()) {
3314
+ await mkdir5(outboxDir, { recursive: true });
3315
+ const filePath = durableResultFinalizationEntryPath(entry, outboxDir);
3316
+ const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
3317
+ await writeFile4(tempPath, `${JSON.stringify(entry, null, 2)}
3318
+ `, "utf8");
3319
+ await rename(tempPath, filePath);
3320
+ }
3321
+ async function listPendingDurableResultFinalizations(scope, outboxDir = defaultResultFinalizationOutboxDir()) {
3322
+ const entries = await readdir2(outboxDir, { withFileTypes: true }).catch((error) => {
3323
+ if (error.code === "ENOENT") return [];
3324
+ throw error;
3325
+ });
3326
+ const pending = [];
3327
+ for (const entry of entries) {
3328
+ if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
3329
+ const filePath = path5.join(outboxDir, entry.name);
3330
+ let raw;
3331
+ try {
3332
+ raw = JSON.parse(await readFile3(filePath, "utf8"));
3333
+ } catch {
3334
+ continue;
3335
+ }
3336
+ const parsed = durableResultFinalizationEntrySchema.safeParse(raw);
3337
+ if (!parsed.success) continue;
3338
+ const value = parsed.data;
3339
+ if (value.status !== "pending") continue;
3340
+ if (value.accountId !== scope.accountId || value.projectId !== scope.projectId || value.repositoryLinkId !== scope.repositoryLinkId || value.runnerId !== scope.runnerId) continue;
3341
+ pending.push(value);
3342
+ }
3343
+ return pending.sort((first, second) => Date.parse(first.createdAt) - Date.parse(second.createdAt));
3344
+ }
3345
+ async function markBrainGenerationFinalizationRetry(entry, error, outboxDir = defaultResultFinalizationOutboxDir()) {
3346
+ const updated = brainGenerationFinalizationEntrySchema.parse({
3347
+ ...entry,
3348
+ status: "pending",
3349
+ retryCount: entry.retryCount + 1,
3350
+ lastError: truncateLocalError(error),
3351
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3352
+ });
3353
+ await upsertBrainGenerationFinalizationEntry(updated, outboxDir);
3354
+ return updated;
3355
+ }
3356
+ async function markBrainGenerationFinalizationTerminal(entry, error, outboxDir = defaultResultFinalizationOutboxDir()) {
3357
+ const updated = brainGenerationFinalizationEntrySchema.parse({
3358
+ ...entry,
3359
+ status: "terminal",
3360
+ lastError: truncateLocalError(error),
3361
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3362
+ });
3363
+ await upsertBrainGenerationFinalizationEntry(updated, outboxDir);
3364
+ return updated;
3365
+ }
3366
+ async function deleteBrainGenerationFinalizationEntry(entry, outboxDir = defaultResultFinalizationOutboxDir()) {
3367
+ await rm(brainGenerationFinalizationEntryPath(entry, outboxDir), { force: true });
3368
+ }
3369
+ async function markDurableResultFinalizationRetry(entry, error, outboxDir = defaultResultFinalizationOutboxDir()) {
3370
+ const updated = durableResultFinalizationEntrySchema.parse({
3371
+ ...entry,
3372
+ status: "pending",
3373
+ retryCount: entry.retryCount + 1,
3374
+ lastError: truncateLocalError(error),
3375
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3376
+ });
3377
+ await upsertDurableResultFinalizationEntry(updated, outboxDir);
3378
+ return updated;
3379
+ }
3380
+ async function markDurableResultFinalizationTerminal(entry, error, outboxDir = defaultResultFinalizationOutboxDir()) {
3381
+ const updated = durableResultFinalizationEntrySchema.parse({
3382
+ ...entry,
3383
+ status: "terminal",
3384
+ lastError: truncateLocalError(error),
3385
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3386
+ });
3387
+ await upsertDurableResultFinalizationEntry(updated, outboxDir);
3388
+ return updated;
3389
+ }
3390
+ async function deleteDurableResultFinalizationEntry(entry, outboxDir = defaultResultFinalizationOutboxDir()) {
3391
+ await rm(durableResultFinalizationEntryPath(entry, outboxDir), { force: true });
3392
+ }
3393
+ function brainGenerationFinalizationEntryPath(entry, outboxDir) {
3394
+ return path5.join(outboxDir, `${brainGenerationFinalizationEntryKey(entry)}.json`);
3395
+ }
3396
+ function brainGenerationFinalizationEntryKey(entry) {
3397
+ const hash = createHash2("sha256").update([entry.accountId, entry.projectId, entry.repositoryLinkId, entry.runnerId, entry.workItemId, String(entry.attempt), entry.idempotencyKey].join("\0")).digest("hex").slice(0, 32);
3398
+ return `brain-generation-${hash}`;
3399
+ }
3400
+ function durableResultFinalizationEntryPath(entry, outboxDir) {
3401
+ return path5.join(outboxDir, `${durableResultFinalizationEntryKey(entry)}.json`);
3402
+ }
3403
+ function durableResultFinalizationEntryKey(entry) {
3404
+ const hash = createHash2("sha256").update([entry.accountId, entry.projectId, entry.repositoryLinkId, entry.runnerId, entry.workItemId, String(entry.attempt), entry.idempotencyKey, entry.resultKind].join("\0")).digest("hex").slice(0, 32);
3405
+ return `runner-result-${hash}`;
3406
+ }
3407
+ function truncateLocalError(error) {
3408
+ const trimmed = error.trim();
3409
+ return trimmed.length > 600 ? `${trimmed.slice(0, 600)}...` : trimmed;
3410
+ }
3411
+
3412
+ // src/local-tool-runner.ts
3413
+ import { spawn } from "node:child_process";
3414
+ import { mkdtemp, readFile as readFile4, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
3415
+ import os3 from "node:os";
3416
+ import path6 from "node:path";
2627
3417
  var localToolNames = runnerToolNames;
2628
3418
  var allReasoningEfforts = ["auto", "low", "medium", "high", "xhigh"];
2629
3419
  var highReasoningEfforts = ["auto", "high", "xhigh"];
@@ -2830,9 +3620,9 @@ async function detectLocalTools() {
2830
3620
  );
2831
3621
  }
2832
3622
  async function runLocalTool(options) {
2833
- const promptTempDir = await mkdtemp(path5.join(os2.tmpdir(), "amistio-prompt-"));
2834
- const promptFilePath = path5.join(promptTempDir, "prompt.md");
2835
- await writeFile4(promptFilePath, options.prompt, "utf8");
3623
+ const promptTempDir = await mkdtemp(path6.join(os3.tmpdir(), "amistio-prompt-"));
3624
+ const promptFilePath = path6.join(promptTempDir, "prompt.md");
3625
+ await writeFile5(promptFilePath, options.prompt, "utf8");
2836
3626
  const modelConfig = normalizeModelOptions(options);
2837
3627
  try {
2838
3628
  const runnerOptions = {
@@ -2864,11 +3654,11 @@ async function runLocalTool(options) {
2864
3654
  ...result
2865
3655
  };
2866
3656
  } finally {
2867
- await rm(promptTempDir, { recursive: true, force: true });
3657
+ await rm2(promptTempDir, { recursive: true, force: true });
2868
3658
  }
2869
3659
  }
2870
3660
  async function createToolRunPreview(options) {
2871
- const promptFilePath = path5.join(os2.tmpdir(), "amistio-generated-prompt.md");
3661
+ const promptFilePath = path6.join(os3.tmpdir(), "amistio-generated-prompt.md");
2872
3662
  const modelConfig = normalizeModelOptions(options);
2873
3663
  const runnerOptions = {
2874
3664
  rootDir: options.rootDir,
@@ -3061,13 +3851,13 @@ async function detectProviderCatalog(adapter) {
3061
3851
  }
3062
3852
  async function loadOpencodeProviderCatalog() {
3063
3853
  const configPaths = [
3064
- path5.join(os2.homedir(), ".config", "opencode", "opencode.json"),
3065
- path5.join(os2.homedir(), ".config", "opencode", "config.json"),
3066
- path5.join(process.cwd(), "opencode.json")
3854
+ path6.join(os3.homedir(), ".config", "opencode", "opencode.json"),
3855
+ path6.join(os3.homedir(), ".config", "opencode", "config.json"),
3856
+ path6.join(process.cwd(), "opencode.json")
3067
3857
  ];
3068
3858
  for (const configPath of configPaths) {
3069
3859
  try {
3070
- const parsed = JSON.parse(await readFile3(configPath, "utf8"));
3860
+ const parsed = JSON.parse(await readFile4(configPath, "utf8"));
3071
3861
  const providerValue = isRecord(parsed) ? parsed.provider : void 0;
3072
3862
  const catalog = sanitizeProviderCatalog(providerValue);
3073
3863
  if (catalog) return catalog;
@@ -3439,16 +4229,16 @@ function shellQuote(value) {
3439
4229
 
3440
4230
  // src/runner-daemon.ts
3441
4231
  import { spawn as spawn2 } from "node:child_process";
3442
- import { createHash as createHash2 } from "node:crypto";
4232
+ import { createHash as createHash3 } from "node:crypto";
3443
4233
  import { openSync } from "node:fs";
3444
- import { mkdir as mkdir5, readdir as readdir2, readFile as readFile4, writeFile as writeFile5 } from "node:fs/promises";
3445
- import os3 from "node:os";
3446
- import path6 from "node:path";
4234
+ import { mkdir as mkdir6, readdir as readdir3, readFile as readFile5, writeFile as writeFile6 } from "node:fs/promises";
4235
+ import os4 from "node:os";
4236
+ import path7 from "node:path";
3447
4237
  function currentRunnerMode() {
3448
4238
  return process.env.AMISTIO_RUNNER_MODE === "background" ? "background" : "foreground";
3449
4239
  }
3450
4240
  function defaultRunnerMetadataDir() {
3451
- return path6.join(os3.homedir(), ".config", "amistio", "runners");
4241
+ return path7.join(os4.homedir(), ".config", "amistio", "runners");
3452
4242
  }
3453
4243
  function updatedCliRunnerLaunchOptions() {
3454
4244
  return { executablePath: "amistio", directExecutable: true };
@@ -3465,8 +4255,8 @@ async function startRunnerDaemon(input) {
3465
4255
  if (existing?.status === "running" && isProcessRunning(existing.pid)) {
3466
4256
  throw new Error(`Background runner ${existing.runnerId} is already running with PID ${existing.pid}.`);
3467
4257
  }
3468
- await mkdir5(metadataDir, { recursive: true });
3469
- const logPath = path6.join(metadataDir, `${runnerDaemonKey(input)}.log`);
4258
+ await mkdir6(metadataDir, { recursive: true });
4259
+ const logPath = path7.join(metadataDir, `${runnerDaemonKey(input)}.log`);
3470
4260
  const logFd = openSync(logPath, "a");
3471
4261
  const launch = resolveRunnerDaemonLaunch({
3472
4262
  args: input.args,
@@ -3493,13 +4283,13 @@ async function startRunnerDaemon(input) {
3493
4283
  projectId: input.projectId,
3494
4284
  repositoryLinkId: input.repositoryLinkId,
3495
4285
  runnerId: input.runnerId,
3496
- rootDir: path6.resolve(input.rootDir),
4286
+ rootDir: path7.resolve(input.rootDir),
3497
4287
  apiUrl: input.apiUrl,
3498
4288
  pid: child.pid,
3499
4289
  status: "running",
3500
4290
  startedAt: now,
3501
4291
  updatedAt: now,
3502
- hostname: os3.hostname(),
4292
+ hostname: os4.hostname(),
3503
4293
  logPath
3504
4294
  };
3505
4295
  await writeRunnerDaemonMetadata(metadata, metadataDir);
@@ -3507,8 +4297,8 @@ async function startRunnerDaemon(input) {
3507
4297
  }
3508
4298
  async function restartRunnerDaemonProcess(metadata, args, input = {}) {
3509
4299
  const metadataDir = input.metadataDir ?? defaultRunnerMetadataDir();
3510
- await mkdir5(metadataDir, { recursive: true });
3511
- const logPath = metadata.logPath ?? path6.join(metadataDir, `${runnerDaemonKey(metadata)}.log`);
4300
+ await mkdir6(metadataDir, { recursive: true });
4301
+ const logPath = metadata.logPath ?? path7.join(metadataDir, `${runnerDaemonKey(metadata)}.log`);
3512
4302
  const logFd = openSync(logPath, "a");
3513
4303
  const launch = resolveRunnerDaemonLaunch({
3514
4304
  args,
@@ -3536,7 +4326,7 @@ async function restartRunnerDaemonProcess(metadata, args, input = {}) {
3536
4326
  status: "running",
3537
4327
  startedAt: now,
3538
4328
  updatedAt: now,
3539
- hostname: os3.hostname(),
4329
+ hostname: os4.hostname(),
3540
4330
  logPath
3541
4331
  };
3542
4332
  await writeRunnerDaemonMetadata(replacement, metadataDir);
@@ -3545,12 +4335,12 @@ async function restartRunnerDaemonProcess(metadata, args, input = {}) {
3545
4335
  async function listRunnerDaemonMetadata(input, metadataDir = defaultRunnerMetadataDir()) {
3546
4336
  let entries;
3547
4337
  try {
3548
- entries = await readdir2(metadataDir);
4338
+ entries = await readdir3(metadataDir);
3549
4339
  } catch {
3550
4340
  return [];
3551
4341
  }
3552
4342
  const records = await Promise.all(
3553
- entries.filter((entry) => entry.endsWith(".json")).map(async (entry) => readRunnerDaemonMetadataFile(path6.join(metadataDir, entry)))
4343
+ entries.filter((entry) => entry.endsWith(".json")).map(async (entry) => readRunnerDaemonMetadataFile(path7.join(metadataDir, entry)))
3554
4344
  );
3555
4345
  return records.filter((record) => Boolean(record)).filter((record) => record.accountId === input.accountId && record.projectId === input.projectId && record.repositoryLinkId === input.repositoryLinkId).filter((record) => !input.runnerId || record.runnerId === input.runnerId).sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt));
3556
4346
  }
@@ -3558,8 +4348,8 @@ async function readRunnerDaemonMetadata(input, metadataDir = defaultRunnerMetada
3558
4348
  return readRunnerDaemonMetadataFile(runnerDaemonMetadataPath(input, metadataDir));
3559
4349
  }
3560
4350
  async function writeRunnerDaemonMetadata(metadata, metadataDir = defaultRunnerMetadataDir()) {
3561
- await mkdir5(metadataDir, { recursive: true });
3562
- await writeFile5(runnerDaemonMetadataPath(metadata, metadataDir), JSON.stringify(metadata, null, 2), { encoding: "utf8", mode: 384 });
4351
+ await mkdir6(metadataDir, { recursive: true });
4352
+ await writeFile6(runnerDaemonMetadataPath(metadata, metadataDir), JSON.stringify(metadata, null, 2), { encoding: "utf8", mode: 384 });
3563
4353
  }
3564
4354
  async function markRunnerDaemonStopped(metadata, metadataDir = defaultRunnerMetadataDir()) {
3565
4355
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -3621,14 +4411,14 @@ function runnerDaemonUptime(metadata, now = Date.now()) {
3621
4411
  return `${seconds}s`;
3622
4412
  }
3623
4413
  function runnerDaemonMetadataPath(input, metadataDir) {
3624
- return path6.join(metadataDir, `${runnerDaemonKey(input)}.json`);
4414
+ return path7.join(metadataDir, `${runnerDaemonKey(input)}.json`);
3625
4415
  }
3626
4416
  function runnerDaemonKey(input) {
3627
- return createHash2("sha256").update(`${input.accountId}:${input.projectId}:${input.repositoryLinkId}:${input.runnerId}`).digest("hex");
4417
+ return createHash3("sha256").update(`${input.accountId}:${input.projectId}:${input.repositoryLinkId}:${input.runnerId}`).digest("hex");
3628
4418
  }
3629
4419
  async function readRunnerDaemonMetadataFile(filePath) {
3630
4420
  try {
3631
- const parsed = JSON.parse(await readFile4(filePath, "utf8"));
4421
+ const parsed = JSON.parse(await readFile5(filePath, "utf8"));
3632
4422
  if (parsed.schemaVersion !== 1 || !parsed.runnerId || !parsed.projectId || !parsed.repositoryLinkId) {
3633
4423
  return void 0;
3634
4424
  }
@@ -3640,10 +4430,10 @@ async function readRunnerDaemonMetadataFile(filePath) {
3640
4430
 
3641
4431
  // src/runner-service.ts
3642
4432
  import { spawn as spawn3 } from "node:child_process";
3643
- import { createHash as createHash3 } from "node:crypto";
3644
- import { mkdir as mkdir6, readFile as readFile5, rm as rm2, writeFile as writeFile6 } from "node:fs/promises";
3645
- import os4 from "node:os";
3646
- import path7 from "node:path";
4433
+ import { createHash as createHash4 } from "node:crypto";
4434
+ import { mkdir as mkdir7, readFile as readFile6, rm as rm3, writeFile as writeFile7 } from "node:fs/promises";
4435
+ import os5 from "node:os";
4436
+ import path8 from "node:path";
3647
4437
  function detectRunnerServicePlatform(platform = process.platform) {
3648
4438
  if (platform === "darwin") return "launchd";
3649
4439
  if (platform === "linux") return "systemd";
@@ -3654,19 +4444,19 @@ function createRunnerServiceDescriptor(input) {
3654
4444
  if (platform === "unsupported") {
3655
4445
  throw new Error("Startup services are supported for user-level launchd on macOS and systemd user services on Linux.");
3656
4446
  }
3657
- const homeDir = input.homeDir ?? os4.homedir();
4447
+ const homeDir = input.homeDir ?? os5.homedir();
3658
4448
  const serviceName = runnerServiceName(input);
3659
4449
  const serviceFilePath = runnerServiceFilePath(platform, serviceName, homeDir);
3660
4450
  const now = (/* @__PURE__ */ new Date()).toISOString();
3661
4451
  const command = [input.executablePath ?? process.execPath, input.scriptPath ?? process.argv[1], ...input.args];
3662
- const logPath = path7.join(input.metadataDir ?? defaultRunnerMetadataDir(), `${runnerServiceKey(input)}.service.log`);
4452
+ const logPath = path8.join(input.metadataDir ?? defaultRunnerMetadataDir(), `${runnerServiceKey(input)}.service.log`);
3663
4453
  const metadata = {
3664
4454
  schemaVersion: 1,
3665
4455
  accountId: input.accountId,
3666
4456
  projectId: input.projectId,
3667
4457
  repositoryLinkId: input.repositoryLinkId,
3668
4458
  runnerId: input.runnerId,
3669
- rootDir: path7.resolve(input.rootDir),
4459
+ rootDir: path8.resolve(input.rootDir),
3670
4460
  apiUrl: input.apiUrl,
3671
4461
  serviceName,
3672
4462
  serviceFilePath,
@@ -3683,9 +4473,9 @@ function createRunnerServiceDescriptor(input) {
3683
4473
  }
3684
4474
  async function installRunnerService(input, options = {}) {
3685
4475
  const descriptor = createRunnerServiceDescriptor(input);
3686
- await mkdir6(path7.dirname(descriptor.metadata.serviceFilePath), { recursive: true });
3687
- await mkdir6(input.metadataDir ?? defaultRunnerMetadataDir(), { recursive: true });
3688
- await writeFile6(descriptor.metadata.serviceFilePath, descriptor.content, { encoding: "utf8", mode: 384 });
4476
+ await mkdir7(path8.dirname(descriptor.metadata.serviceFilePath), { recursive: true });
4477
+ await mkdir7(input.metadataDir ?? defaultRunnerMetadataDir(), { recursive: true });
4478
+ await writeFile7(descriptor.metadata.serviceFilePath, descriptor.content, { encoding: "utf8", mode: 384 });
3689
4479
  await writeRunnerServiceMetadata(descriptor.metadata, input.metadataDir);
3690
4480
  if (options.activate !== false) {
3691
4481
  const activation = await activateRunnerService(descriptor.metadata);
@@ -3701,13 +4491,13 @@ async function removeRunnerService(input) {
3701
4491
  return void 0;
3702
4492
  }
3703
4493
  await deactivateRunnerService(metadata).catch(() => void 0);
3704
- await rm2(metadata.serviceFilePath, { force: true });
3705
- await rm2(runnerServiceMetadataPath(input, input.metadataDir ?? defaultRunnerMetadataDir()), { force: true });
4494
+ await rm3(metadata.serviceFilePath, { force: true });
4495
+ await rm3(runnerServiceMetadataPath(input, input.metadataDir ?? defaultRunnerMetadataDir()), { force: true });
3706
4496
  return { ...metadata, status: "removed", updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
3707
4497
  }
3708
4498
  async function readRunnerServiceMetadata(input, metadataDir = defaultRunnerMetadataDir()) {
3709
4499
  try {
3710
- const parsed = JSON.parse(await readFile5(runnerServiceMetadataPath(input, metadataDir), "utf8"));
4500
+ const parsed = JSON.parse(await readFile6(runnerServiceMetadataPath(input, metadataDir), "utf8"));
3711
4501
  if (parsed.schemaVersion !== 1 || !parsed.serviceName || !parsed.serviceFilePath) {
3712
4502
  return void 0;
3713
4503
  }
@@ -3717,8 +4507,8 @@ async function readRunnerServiceMetadata(input, metadataDir = defaultRunnerMetad
3717
4507
  }
3718
4508
  }
3719
4509
  async function writeRunnerServiceMetadata(metadata, metadataDir = defaultRunnerMetadataDir()) {
3720
- await mkdir6(metadataDir, { recursive: true });
3721
- await writeFile6(runnerServiceMetadataPath(metadata, metadataDir), JSON.stringify(metadata, null, 2), { encoding: "utf8", mode: 384 });
4510
+ await mkdir7(metadataDir, { recursive: true });
4511
+ await writeFile7(runnerServiceMetadataPath(metadata, metadataDir), JSON.stringify(metadata, null, 2), { encoding: "utf8", mode: 384 });
3722
4512
  }
3723
4513
  async function runnerServiceRuntimeStatus(metadata) {
3724
4514
  if (metadata.platform === "launchd") {
@@ -3801,18 +4591,18 @@ WantedBy=default.target
3801
4591
  }
3802
4592
  function runnerServiceFilePath(platform, serviceName, homeDir) {
3803
4593
  if (platform === "launchd") {
3804
- return path7.join(homeDir, "Library", "LaunchAgents", `${serviceName}.plist`);
4594
+ return path8.join(homeDir, "Library", "LaunchAgents", `${serviceName}.plist`);
3805
4595
  }
3806
- return path7.join(homeDir, ".config", "systemd", "user", `${serviceName}.service`);
4596
+ return path8.join(homeDir, ".config", "systemd", "user", `${serviceName}.service`);
3807
4597
  }
3808
4598
  function runnerServiceMetadataPath(input, metadataDir) {
3809
- return path7.join(metadataDir, `${runnerServiceKey(input)}.service.json`);
4599
+ return path8.join(metadataDir, `${runnerServiceKey(input)}.service.json`);
3810
4600
  }
3811
4601
  function runnerServiceName(input) {
3812
4602
  return `com.amistio.runner.${runnerServiceKey(input).slice(0, 20)}`;
3813
4603
  }
3814
4604
  function runnerServiceKey(input) {
3815
- return createHash3("sha256").update(`${input.accountId}:${input.projectId}:${input.repositoryLinkId}:${input.runnerId}`).digest("hex");
4605
+ return createHash4("sha256").update(`${input.accountId}:${input.projectId}:${input.repositoryLinkId}:${input.runnerId}`).digest("hex");
3816
4606
  }
3817
4607
  function launchdDomain() {
3818
4608
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
@@ -3988,9 +4778,9 @@ function tokens(value) {
3988
4778
 
3989
4779
  // src/sync.ts
3990
4780
  import { execFile as execFile3 } from "node:child_process";
3991
- import { createHash as createHash4 } from "node:crypto";
3992
- import { mkdir as mkdir7, readdir as readdir3, readFile as readFile6, stat as stat3, writeFile as writeFile7 } from "node:fs/promises";
3993
- import path8 from "node:path";
4781
+ import { createHash as createHash5 } from "node:crypto";
4782
+ import { mkdir as mkdir8, readdir as readdir4, readFile as readFile7, stat as stat3, writeFile as writeFile8 } from "node:fs/promises";
4783
+ import path9 from "node:path";
3994
4784
  import { promisify as promisify3 } from "node:util";
3995
4785
  var execFileAsync3 = promisify3(execFile3);
3996
4786
  var legacySyncRoots = ["architecture", "context", "decisions", "features", "memory", "plans", "prompts", "workflows"];
@@ -4086,7 +4876,7 @@ async function readLocalSyncedDocuments(rootDir) {
4086
4876
  const documentFiles = await findBrainDocumentFiles(rootDir);
4087
4877
  const documents = [];
4088
4878
  for (const fullPath of documentFiles) {
4089
- const raw = await readFile6(fullPath, "utf8");
4879
+ const raw = await readFile7(fullPath, "utf8");
4090
4880
  const repoPath = toRepoPath(rootDir, fullPath);
4091
4881
  const parsed = parseSyncedDocument(raw, repoPath);
4092
4882
  if (!parsed) {
@@ -4136,8 +4926,8 @@ async function materializeBrainDocuments(rootDir, documents, options = {}) {
4136
4926
  result.skipped.push(document.repoPath);
4137
4927
  continue;
4138
4928
  }
4139
- await mkdir7(path8.dirname(fullPath), { recursive: true });
4140
- await writeFile7(fullPath, createSyncedDocumentContent(document), "utf8");
4929
+ await mkdir8(path9.dirname(fullPath), { recursive: true });
4930
+ await writeFile8(fullPath, createSyncedDocumentContent(document), "utf8");
4141
4931
  result.written.push(document.repoPath);
4142
4932
  }
4143
4933
  return result;
@@ -4266,7 +5056,7 @@ function parseSyncedHtml(content) {
4266
5056
  }
4267
5057
  async function readExistingSyncedDocument(fullPath) {
4268
5058
  try {
4269
- const raw = await readFile6(fullPath, "utf8");
5059
+ const raw = await readFile7(fullPath, "utf8");
4270
5060
  const parsed = parseSyncedDocument(raw, fullPath);
4271
5061
  if (!parsed) {
4272
5062
  return { exists: true };
@@ -4291,7 +5081,7 @@ async function readExistingSyncedDocument(fullPath) {
4291
5081
  async function findBrainDocumentFiles(rootDir) {
4292
5082
  const files = [];
4293
5083
  for (const syncRoot of [...syncRoots, htmlSyncRoot]) {
4294
- const fullRoot = path8.join(rootDir, syncRoot);
5084
+ const fullRoot = path9.join(rootDir, syncRoot);
4295
5085
  if (!await exists2(fullRoot)) {
4296
5086
  continue;
4297
5087
  }
@@ -4300,8 +5090,8 @@ async function findBrainDocumentFiles(rootDir) {
4300
5090
  return files;
4301
5091
  }
4302
5092
  async function walkBrainDocumentFiles(directory, files) {
4303
- for (const entry of await readdir3(directory, { withFileTypes: true })) {
4304
- const fullPath = path8.join(directory, entry.name);
5093
+ for (const entry of await readdir4(directory, { withFileTypes: true })) {
5094
+ const fullPath = path9.join(directory, entry.name);
4305
5095
  if (entry.isDirectory()) {
4306
5096
  await walkBrainDocumentFiles(fullPath, files);
4307
5097
  } else if (entry.isFile() && /\.(md|mdx|html?)$/i.test(entry.name)) {
@@ -4310,23 +5100,23 @@ async function walkBrainDocumentFiles(directory, files) {
4310
5100
  }
4311
5101
  }
4312
5102
  function safeRepoPath(rootDir, repoPath) {
4313
- if (path8.isAbsolute(repoPath)) {
5103
+ if (path9.isAbsolute(repoPath)) {
4314
5104
  throw new Error(`Refusing to use absolute repo path: ${repoPath}`);
4315
5105
  }
4316
- const normalized = path8.normalize(repoPath);
4317
- if (normalized === ".." || normalized.startsWith(`..${path8.sep}`)) {
5106
+ const normalized = path9.normalize(repoPath);
5107
+ if (normalized === ".." || normalized.startsWith(`..${path9.sep}`)) {
4318
5108
  throw new Error(`Refusing to use path outside the repository: ${repoPath}`);
4319
5109
  }
4320
- const root = path8.resolve(rootDir);
4321
- const fullPath = path8.resolve(root, normalized);
4322
- if (!fullPath.startsWith(`${root}${path8.sep}`)) {
5110
+ const root = path9.resolve(rootDir);
5111
+ const fullPath = path9.resolve(root, normalized);
5112
+ if (!fullPath.startsWith(`${root}${path9.sep}`)) {
4323
5113
  throw new Error(`Refusing to use path outside the repository: ${repoPath}`);
4324
5114
  }
4325
5115
  return fullPath;
4326
5116
  }
4327
5117
  function isControlPlanePath(repoPath) {
4328
- const normalized = path8.normalize(repoPath);
4329
- return syncRoots.some((syncRoot) => normalized === syncRoot || normalized.startsWith(`${syncRoot}${path8.sep}`)) || normalized === htmlSyncRoot || normalized.startsWith(`${htmlSyncRoot}${path8.sep}`);
5118
+ const normalized = path9.normalize(repoPath);
5119
+ return syncRoots.some((syncRoot) => normalized === syncRoot || normalized.startsWith(`${syncRoot}${path9.sep}`)) || normalized === htmlSyncRoot || normalized.startsWith(`${htmlSyncRoot}${path9.sep}`);
4330
5120
  }
4331
5121
  function canonicalControlPlaneRepoPath(repoPath) {
4332
5122
  const normalized = repoPath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
@@ -4337,16 +5127,16 @@ function canonicalControlPlaneRepoPath(repoPath) {
4337
5127
  return normalized;
4338
5128
  }
4339
5129
  function toRepoPath(rootDir, fullPath) {
4340
- return path8.relative(rootDir, fullPath).split(path8.sep).join("/");
5130
+ return path9.relative(rootDir, fullPath).split(path9.sep).join("/");
4341
5131
  }
4342
5132
  function inferTitle(content, repoPath) {
4343
5133
  const heading = content.split("\n").find((line) => line.startsWith("# "))?.replace(/^#\s+/, "").trim();
4344
5134
  if (heading) return heading;
4345
5135
  const htmlHeading = content.match(/<h1\b[^>]*>([\s\S]*?)<\/h1>/i)?.[1]?.replace(/<[^>]+>/g, "").trim();
4346
- return htmlHeading || path8.basename(repoPath, path8.extname(repoPath));
5136
+ return htmlHeading || path9.basename(repoPath, path9.extname(repoPath));
4347
5137
  }
4348
5138
  async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingDocuments, options) {
4349
- const root = path8.resolve(rootDir);
5139
+ const root = path9.resolve(rootDir);
4350
5140
  const maxBytes = (options.maxFileKb ?? defaultAutoSyncMaxFileKb) * 1024;
4351
5141
  const syncedAt = options.syncedAt ?? (/* @__PURE__ */ new Date()).toISOString();
4352
5142
  const existingById = new Map(existingDocuments.map((document) => [document.documentId, document]));
@@ -4371,7 +5161,7 @@ async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingD
4371
5161
  skipped.push({ repoPath: normalizedRepoPath, reason: "tooLarge" });
4372
5162
  continue;
4373
5163
  }
4374
- const content = await readFile6(fullPath, "utf8").catch(() => void 0);
5164
+ const content = await readFile7(fullPath, "utf8").catch(() => void 0);
4375
5165
  if (content === void 0) {
4376
5166
  skipped.push({ repoPath: normalizedRepoPath, reason: "unreadable" });
4377
5167
  continue;
@@ -4436,7 +5226,7 @@ async function listAutoSyncCandidatePaths(rootDir) {
4436
5226
  }
4437
5227
  const files = [];
4438
5228
  for (const syncRoot of [...syncRoots, htmlSyncRoot, ...legacySyncRoots]) {
4439
- const fullRoot = path8.join(rootDir, syncRoot);
5229
+ const fullRoot = path9.join(rootDir, syncRoot);
4440
5230
  if (await exists2(fullRoot)) {
4441
5231
  await walkAutoSyncFiles(rootDir, fullRoot, files);
4442
5232
  }
@@ -4444,9 +5234,9 @@ async function listAutoSyncCandidatePaths(rootDir) {
4444
5234
  return uniqueSortedRepoPaths(files);
4445
5235
  }
4446
5236
  async function walkAutoSyncFiles(rootDir, directory, files) {
4447
- for (const entry of await readdir3(directory, { withFileTypes: true }).catch(() => [])) {
4448
- const fullPath = path8.join(directory, entry.name);
4449
- const repoPath = normalizeRepoPath3(path8.relative(rootDir, fullPath));
5237
+ for (const entry of await readdir4(directory, { withFileTypes: true }).catch(() => [])) {
5238
+ const fullPath = path9.join(directory, entry.name);
5239
+ const repoPath = normalizeRepoPath3(path9.relative(rootDir, fullPath));
4450
5240
  if (entry.isDirectory()) {
4451
5241
  if (!autoSyncExcludedDirectoryNames.has(entry.name)) {
4452
5242
  await walkAutoSyncFiles(rootDir, fullPath, files);
@@ -4480,7 +5270,7 @@ function legacyDocumentTypeForRepoPath(repoPath) {
4480
5270
  return root && root in documentTypeByRoot ? documentTypeByRoot[root] : void 0;
4481
5271
  }
4482
5272
  function stableExternalDocumentId(metadata, repoPath) {
4483
- return `doc_external_${createHash4("sha256").update(`${metadata.amistioAccountId}:${metadata.amistioProjectId}:${metadata.repositoryLinkId}:${repoPath}`).digest("hex").slice(0, 24)}`;
5273
+ return `doc_external_${createHash5("sha256").update(`${metadata.amistioAccountId}:${metadata.amistioProjectId}:${metadata.repositoryLinkId}:${repoPath}`).digest("hex").slice(0, 24)}`;
4484
5274
  }
4485
5275
  function normalizeRepoPath3(repoPath) {
4486
5276
  return repoPath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
@@ -4508,9 +5298,9 @@ async function exists2(filePath) {
4508
5298
  }
4509
5299
 
4510
5300
  // src/tool-session-store.ts
4511
- import { mkdir as mkdir8, readFile as readFile7, writeFile as writeFile8 } from "node:fs/promises";
4512
- import os5 from "node:os";
4513
- import path9 from "node:path";
5301
+ import { mkdir as mkdir9, readFile as readFile8, writeFile as writeFile9 } from "node:fs/promises";
5302
+ import os6 from "node:os";
5303
+ import path10 from "node:path";
4514
5304
  var LocalToolSessionStore = class {
4515
5305
  constructor(filePath = defaultSessionStorePath()) {
4516
5306
  this.filePath = filePath;
@@ -4524,12 +5314,12 @@ var LocalToolSessionStore = class {
4524
5314
  async setProviderSessionId(toolSessionId, toolName, providerSessionId) {
4525
5315
  const data = await this.read();
4526
5316
  data[toolSessionId] = { toolName, providerSessionId, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
4527
- await mkdir8(path9.dirname(this.filePath), { recursive: true });
4528
- await writeFile8(this.filePath, JSON.stringify(data, null, 2), "utf8");
5317
+ await mkdir9(path10.dirname(this.filePath), { recursive: true });
5318
+ await writeFile9(this.filePath, JSON.stringify(data, null, 2), "utf8");
4529
5319
  }
4530
5320
  async read() {
4531
5321
  try {
4532
- return JSON.parse(await readFile7(this.filePath, "utf8"));
5322
+ return JSON.parse(await readFile8(this.filePath, "utf8"));
4533
5323
  } catch {
4534
5324
  return {};
4535
5325
  }
@@ -4537,16 +5327,16 @@ var LocalToolSessionStore = class {
4537
5327
  };
4538
5328
  function defaultSessionStorePath() {
4539
5329
  if (process.platform === "darwin") {
4540
- return path9.join(os5.homedir(), "Library", "Application Support", "Amistio", "tool-sessions.json");
5330
+ return path10.join(os6.homedir(), "Library", "Application Support", "Amistio", "tool-sessions.json");
4541
5331
  }
4542
5332
  if (process.platform === "win32") {
4543
- return path9.join(process.env.APPDATA ?? os5.homedir(), "Amistio", "tool-sessions.json");
5333
+ return path10.join(process.env.APPDATA ?? os6.homedir(), "Amistio", "tool-sessions.json");
4544
5334
  }
4545
- return path9.join(process.env.XDG_STATE_HOME ?? path9.join(os5.homedir(), ".local", "state"), "amistio", "tool-sessions.json");
5335
+ return path10.join(process.env.XDG_STATE_HOME ?? path10.join(os6.homedir(), ".local", "state"), "amistio", "tool-sessions.json");
4546
5336
  }
4547
5337
 
4548
5338
  // src/work-runner.ts
4549
- import path10 from "node:path";
5339
+ import path11 from "node:path";
4550
5340
  var generationResultStart = "AMISTIO_BRAIN_GENERATION_RESULT_START";
4551
5341
  var generationResultEnd = "AMISTIO_BRAIN_GENERATION_RESULT_END";
4552
5342
  var assistantAnswerStart = "AMISTIO_ASSISTANT_ANSWER_START";
@@ -4563,6 +5353,10 @@ var projectContextRefreshStart = "AMISTIO_PROJECT_CONTEXT_REFRESH_START";
4563
5353
  var projectContextRefreshEnd = "AMISTIO_PROJECT_CONTEXT_REFRESH_END";
4564
5354
  var implementationVerificationStart = "AMISTIO_IMPLEMENTATION_VERIFICATION_START";
4565
5355
  var implementationVerificationEnd = "AMISTIO_IMPLEMENTATION_VERIFICATION_END";
5356
+ var testQualityStart = "AMISTIO_TEST_QUALITY_START";
5357
+ var testQualityEnd = "AMISTIO_TEST_QUALITY_END";
5358
+ var implementationTestGateStart = "AMISTIO_IMPLEMENTATION_TEST_GATE_START";
5359
+ var implementationTestGateEnd = "AMISTIO_IMPLEMENTATION_TEST_GATE_END";
4566
5360
  var projectContextMissingAreaMaxLength = 160;
4567
5361
  var projectContextCoverageWarningMaxLength = 300;
4568
5362
  var projectContextVerificationPlanMaxLength = 300;
@@ -4570,6 +5364,94 @@ var projectContextTopLevelWarningMaxLength = 600;
4570
5364
  var validProjectContextSliceKinds = /* @__PURE__ */ new Set(["overview", "architecture", "domain", "data", "api", "frontend", "backend", "cli", "workflow", "operations", "security", "testing", "unknown"]);
4571
5365
  var validProjectContextEntityTypes = /* @__PURE__ */ new Set(["project", "system", "component", "domain", "tool", "decision", "feature", "risk", "team", "workflow", "unknown"]);
4572
5366
  var validProjectContextRelationTypes = /* @__PURE__ */ new Set(["uses", "depends_on", "decides", "supersedes", "touches", "blocks", "implements", "mentions"]);
5367
+ var canonicalAppEvaluationCategories = /* @__PURE__ */ new Map([
5368
+ ["verification", "verification"],
5369
+ ["verify", "verification"],
5370
+ ["test", "verification"],
5371
+ ["tests", "verification"],
5372
+ ["testing", "verification"],
5373
+ ["check", "verification"],
5374
+ ["checks", "verification"],
5375
+ ["ci", "verification"],
5376
+ ["continuousintegration", "verification"],
5377
+ ["qa", "verification"],
5378
+ ["qualityassurance", "verification"],
5379
+ ["build", "verification"],
5380
+ ["lint", "verification"],
5381
+ ["linting", "verification"],
5382
+ ["typecheck", "verification"],
5383
+ ["typechecking", "verification"],
5384
+ ["productdrift", "productDrift"],
5385
+ ["drift", "productDrift"],
5386
+ ["docs", "productDrift"],
5387
+ ["doc", "productDrift"],
5388
+ ["documentation", "productDrift"],
5389
+ ["docsdrift", "productDrift"],
5390
+ ["documentationdrift", "productDrift"],
5391
+ ["contextdrift", "productDrift"],
5392
+ ["productdocumentationdrift", "productDrift"],
5393
+ ["plancleanup", "planCleanup"],
5394
+ ["plan", "planCleanup"],
5395
+ ["plans", "planCleanup"],
5396
+ ["staleplan", "planCleanup"],
5397
+ ["staleplans", "planCleanup"],
5398
+ ["cleanup", "planCleanup"],
5399
+ ["cleanupplan", "planCleanup"],
5400
+ ["superseded", "planCleanup"],
5401
+ ["obsolete", "planCleanup"],
5402
+ ["promptrot", "promptRot"],
5403
+ ["prompt", "promptRot"],
5404
+ ["prompts", "promptRot"],
5405
+ ["template", "promptRot"],
5406
+ ["templates", "promptRot"],
5407
+ ["instruction", "promptRot"],
5408
+ ["instructions", "promptRot"],
5409
+ ["runnerprompt", "promptRot"],
5410
+ ["runnerprompttemplate", "promptRot"],
5411
+ ["prompttemplate", "promptRot"],
5412
+ ["missingmemory", "missingMemory"],
5413
+ ["memory", "missingMemory"],
5414
+ ["memories", "missingMemory"],
5415
+ ["lesson", "missingMemory"],
5416
+ ["lessons", "missingMemory"],
5417
+ ["workflow", "missingMemory"],
5418
+ ["workflowlesson", "missingMemory"],
5419
+ ["workflows", "missingMemory"],
5420
+ ["releasereadiness", "releaseReadiness"],
5421
+ ["release", "releaseReadiness"],
5422
+ ["deploy", "releaseReadiness"],
5423
+ ["deployment", "releaseReadiness"],
5424
+ ["readiness", "releaseReadiness"],
5425
+ ["production", "releaseReadiness"],
5426
+ ["productionreadiness", "releaseReadiness"],
5427
+ ["ux", "ux"],
5428
+ ["ui", "ux"],
5429
+ ["userexperience", "ux"],
5430
+ ["accessibility", "accessibility"],
5431
+ ["a11y", "accessibility"],
5432
+ ["accessible", "accessibility"],
5433
+ ["performance", "performance"],
5434
+ ["perf", "performance"],
5435
+ ["reliability", "reliability"],
5436
+ ["resilience", "reliability"],
5437
+ ["resiliency", "reliability"],
5438
+ ["stability", "reliability"],
5439
+ ["uptime", "reliability"],
5440
+ ["securityposture", "securityPosture"],
5441
+ ["security", "securityPosture"],
5442
+ ["posture", "securityPosture"],
5443
+ ["vulnerability", "securityPosture"],
5444
+ ["vulnerabilities", "securityPosture"],
5445
+ ["vulnerabilityposture", "securityPosture"],
5446
+ ["other", "other"],
5447
+ ["technicaldebt", "other"],
5448
+ ["debt", "other"],
5449
+ ["maintainability", "other"],
5450
+ ["codequality", "other"],
5451
+ ["architecture", "other"],
5452
+ ["operations", "other"],
5453
+ ["ops", "other"]
5454
+ ]);
4573
5455
  var canonicalProjectContextCitationSources = /* @__PURE__ */ new Map([
4574
5456
  ["projectbrain", "projectBrain"],
4575
5457
  ["approvedbrain", "projectBrain"],
@@ -4594,6 +5476,7 @@ function createImplementationVerificationPrompt(workItem) {
4594
5476
  `Project ID: ${workItem.projectId}`,
4595
5477
  `Implementation verification ID: ${workItem.implementationVerificationId ?? "unknown"}`,
4596
5478
  `Source work item ID: ${workItem.sourceWorkItemId ?? "unknown"}`,
5479
+ ...autopilotPromptLines(workItem),
4597
5480
  "",
4598
5481
  "## Verification Request",
4599
5482
  "",
@@ -4627,34 +5510,132 @@ function createImplementationVerificationPrompt(workItem) {
4627
5510
  "Do not put Markdown fences around the markers. Do not implement or fix anything during this verification pass."
4628
5511
  ].join("\n");
4629
5512
  }
4630
- function createWorkExecutionPrompt(workItem, context) {
4631
- if (workItem.workKind === "brainGeneration") {
4632
- return createBrainGenerationPrompt(workItem);
4633
- }
4634
- if (workItem.workKind === "planRevision") {
4635
- return createPlanRevisionPrompt(workItem, context?.planRevision);
4636
- }
4637
- if (workItem.workKind === "assistantQuestion") {
4638
- return createAssistantQuestionPrompt(workItem, context?.assistantQuestion);
4639
- }
4640
- if (workItem.workKind === "impactPreview") {
4641
- return createImpactPreviewPrompt(workItem, context?.impactPreview);
4642
- }
4643
- if (workItem.workKind === "issueDiagnosis") {
4644
- return createIssueDiagnosisPrompt(workItem, context?.issueDiagnosis);
4645
- }
4646
- if (workItem.workKind === "securityPostureScan") {
4647
- return createSecurityPostureScanPrompt(workItem, context?.securityPostureScan);
4648
- }
4649
- if (workItem.workKind === "appEvaluationScan") {
4650
- return createAppEvaluationScanPrompt(workItem, context?.appEvaluationScan);
4651
- }
4652
- if (workItem.workKind === "projectContextRefresh") {
4653
- return createProjectContextRefreshPrompt(workItem, context?.projectContextRefresh);
5513
+ function createTestQualityScanPrompt(workItem, context) {
5514
+ const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 20).map((document) => [
5515
+ `### ${document.title}`,
5516
+ `documentId: ${document.documentId}`,
5517
+ `documentType: ${document.documentType}`,
5518
+ `repoPath: ${document.repoPath}`,
5519
+ `revision: ${document.revision}`,
5520
+ document.content.slice(0, 3e3)
5521
+ ].join("\n")).join("\n\n");
5522
+ return [
5523
+ "# Amistio Test Quality Scan",
5524
+ "",
5525
+ "You are running locally through the Amistio CLI inside the user's repository.",
5526
+ "Run a non-mutating daily test, coverage, and quality scan for this repository.",
5527
+ "Do not modify files, create branches, commit, push, install packages, run implementation prompts, or make unaudited network calls.",
5528
+ "Use safe local metadata to infer package managers and scripts, then run existing lint, typecheck, test, coverage, and build commands only when they already exist.",
5529
+ "Return missing tests, missing coverage, low coverage, failing quality commands, and broken whole-app verification as reviewable findings that can become plans.",
5530
+ "",
5531
+ "## Work Item",
5532
+ "",
5533
+ `Title: ${workItem.title}`,
5534
+ `Work item ID: ${workItem.workItemId}`,
5535
+ `Project ID: ${workItem.projectId}`,
5536
+ `Test quality scan ID: ${workItem.testQualityScanId ?? "unknown"}`,
5537
+ "",
5538
+ "## Approved Project Brain Context",
5539
+ "",
5540
+ approvedContext || "No approved project-brain records were loaded. Inspect safe local metadata and summarize the context gap.",
5541
+ "",
5542
+ "## Requirements",
5543
+ "",
5544
+ "- Prefer the repository whole-app verification script when one exists, then focused package scripts.",
5545
+ "- Detect package managers from lockfiles and package metadata without uploading raw source.",
5546
+ "- Capture bounded summaries, exit codes, coverage metrics, and repository-relative safe paths only.",
5547
+ "- If coverage cannot be measured, return a missingCoverage finding instead of guessing.",
5548
+ "- If tests are missing or impossible to run, return a missingTests or testGap finding with a concrete plan and verification steps.",
5549
+ "- Redact secrets, absolute paths, env vars, process lists, tokens, provider sessions, and command lines containing secret-looking values.",
5550
+ "",
5551
+ "## Output Contract",
5552
+ "",
5553
+ "Print exactly one JSON object between the markers below. The CLI will submit only this structured test scan result back to Amistio.",
5554
+ "Accepted command kind values: lint, typecheck, unitTest, integrationTest, e2eTest, coverage, build, verify, other.",
5555
+ "Accepted command status values: passed, failed, skipped, missing, blocked.",
5556
+ "Accepted finding categories: missingTests, missingCoverage, lowCoverage, failingTests, failingQuality, missingCommand, flakyTests, unverifiedImplementation, testGap, other.",
5557
+ "",
5558
+ testQualityStart,
5559
+ '{"summary":"Whole-app verification exists, but coverage reporting is missing for one package.","profile":{"testProfileId":"test_profile_detected","repositoryLinkId":"repository_link_placeholder","status":"detected","enabled":true,"detectedPackageManager":"pnpm","packageManagers":["pnpm"],"commands":[{"commandId":"verify","kind":"verify","label":"Whole-app verification","source":"detected","scriptName":"verify","workingDirectory":".","required":true}],"coverageThresholds":{"lines":80},"defaultWholeAppCommandId":"verify","focusedCommandIds":[],"lastDetectedAt":"2026-01-01T00:00:00.000Z"},"commandSummaries":[{"commandId":"verify","kind":"verify","label":"Whole-app verification","status":"passed","exitCode":0,"summary":"The repository verification script completed successfully.","safePaths":["package.json"]}],"coverage":{"status":"missing","thresholds":{"lines":80},"summary":"No coverage report was found."},"findings":[{"title":"Coverage report is missing","category":"missingCoverage","severity":"medium","confidence":"high","summary":"The repository test profile does not produce a coverage summary.","affectedSurfaces":["Test verification"],"evidence":["The scan found a test command but no coverage output."],"safePaths":["package.json"],"suggestedAction":"Add or document a coverage command for the affected package and include it in whole-app verification.","verificationPlan":["Run the new coverage command locally","Confirm coverage appears in the Test panel"],"dedupeKey":"missing-coverage"}],"blockedReasons":[],"redactionState":{"status":"clean","redactedFields":[]},"verificationPlan":["Review generated Test findings","Run the whole-app verification command before implementation handoff"],"warnings":[]}',
5560
+ testQualityEnd,
5561
+ "",
5562
+ "Do not put Markdown fences around the markers. Do not implement fixes during this scan."
5563
+ ].join("\n");
5564
+ }
5565
+ function createImplementationTestGatePrompt(workItem) {
5566
+ return [
5567
+ "# Amistio Implementation Test Gate",
5568
+ "",
5569
+ "You are running locally through the Amistio CLI inside the user's repository.",
5570
+ "Run the required test gate before implementation completion, PR handoff, or runner-managed push.",
5571
+ "Do not modify files, install packages unless an already-approved preflight policy permits it, commit, push, or upload raw source.",
5572
+ "Run focused checks for the touched scope first when available, then the whole-app verification command when practical.",
5573
+ "If tests are missing or impossible, return blocked with a test-gap justification and a finding instead of marking the gate passed.",
5574
+ "",
5575
+ "## Work Item",
5576
+ "",
5577
+ `Title: ${workItem.title}`,
5578
+ `Work item ID: ${workItem.workItemId}`,
5579
+ `Project ID: ${workItem.projectId}`,
5580
+ `Implementation test gate ID: ${workItem.implementationTestGateId ?? "unknown"}`,
5581
+ `Source work item ID: ${workItem.sourceWorkItemId ?? "unknown"}`,
5582
+ "",
5583
+ "## Gate Request",
5584
+ "",
5585
+ workItem.sourceWish ?? "Run focused checks and whole-app verification before marking implementation complete.",
5586
+ "",
5587
+ "## Data Safety",
5588
+ "",
5589
+ "- Persist only bounded summaries, coverage metrics, exit codes, and repository-relative paths.",
5590
+ "- Do not include raw source, secrets, env vars, process lists, absolute local paths, credential values, tokens, provider sessions, or destructive shell output.",
5591
+ "- Use safePaths for repository-relative paths only; never include /absolute paths or ../ traversal.",
5592
+ "",
5593
+ "## Output Contract",
5594
+ "",
5595
+ "Print exactly one JSON object between the markers below. The CLI will submit only this structured gate result back to Amistio.",
5596
+ "Accepted outcome values: passed, failed, blocked, overridden.",
5597
+ "",
5598
+ implementationTestGateStart,
5599
+ '{"outcome":"passed","summary":"Focused checks and whole-app verification passed.","commandSummaries":[{"commandId":"verify","kind":"verify","label":"Whole-app verification","status":"passed","exitCode":0,"summary":"The repository verification script completed successfully.","safePaths":["package.json"]}],"coverage":{"status":"unknown","thresholds":{},"summary":"No coverage threshold was configured for this gate."},"findings":[],"blockedReasons":[],"redactionState":{"status":"clean","redactedFields":[]},"verificationPlan":["Record this gate result before marking implementation complete"],"warnings":[]}',
5600
+ implementationTestGateEnd,
5601
+ "",
5602
+ "Do not put Markdown fences around the markers."
5603
+ ].join("\n");
5604
+ }
5605
+ function createWorkExecutionPrompt(workItem, context) {
5606
+ if (workItem.workKind === "brainGeneration") {
5607
+ return createBrainGenerationPrompt(workItem);
5608
+ }
5609
+ if (workItem.workKind === "planRevision") {
5610
+ return createPlanRevisionPrompt(workItem, context?.planRevision);
5611
+ }
5612
+ if (workItem.workKind === "assistantQuestion") {
5613
+ return createAssistantQuestionPrompt(workItem, context?.assistantQuestion);
5614
+ }
5615
+ if (workItem.workKind === "impactPreview") {
5616
+ return createImpactPreviewPrompt(workItem, context?.impactPreview);
5617
+ }
5618
+ if (workItem.workKind === "issueDiagnosis") {
5619
+ return createIssueDiagnosisPrompt(workItem, context?.issueDiagnosis);
5620
+ }
5621
+ if (workItem.workKind === "securityPostureScan") {
5622
+ return createSecurityPostureScanPrompt(workItem, context?.securityPostureScan);
5623
+ }
5624
+ if (workItem.workKind === "appEvaluationScan") {
5625
+ return createAppEvaluationScanPrompt(workItem, context?.appEvaluationScan);
5626
+ }
5627
+ if (workItem.workKind === "projectContextRefresh") {
5628
+ return createProjectContextRefreshPrompt(workItem, context?.projectContextRefresh);
4654
5629
  }
4655
5630
  if (workItem.workKind === "implementationVerification") {
4656
5631
  return createImplementationVerificationPrompt(workItem);
4657
5632
  }
5633
+ if (workItem.workKind === "testQualityScan") {
5634
+ return createTestQualityScanPrompt(workItem, context?.testQualityScan);
5635
+ }
5636
+ if (workItem.workKind === "implementationTestGate") {
5637
+ return createImplementationTestGatePrompt(workItem);
5638
+ }
4658
5639
  return [
4659
5640
  "# Amistio Work Execution",
4660
5641
  "",
@@ -4669,6 +5650,7 @@ function createWorkExecutionPrompt(workItem, context) {
4669
5650
  `Implementation scope: ${workItem.implementationScopeId ?? workItem.controllingAdrId ?? "work-item"}`,
4670
5651
  `Execution branch: ${workItem.executionBranch ?? "managed by Amistio CLI"}`,
4671
5652
  `Execution worktree key: ${workItem.executionWorktreeKey ?? "managed by Amistio CLI"}`,
5653
+ ...autopilotPromptLines(workItem),
4672
5654
  "",
4673
5655
  "## Rules",
4674
5656
  "",
@@ -4683,6 +5665,27 @@ function createWorkExecutionPrompt(workItem, context) {
4683
5665
  "- Run relevant verification commands when feasible and summarize results."
4684
5666
  ].join("\n");
4685
5667
  }
5668
+ function autopilotPromptLines(workItem) {
5669
+ const autopilot = autopilotWorkMetadata(workItem);
5670
+ if (!autopilot.autopilotAuthorizationId) {
5671
+ return [];
5672
+ }
5673
+ const lines = [
5674
+ `Autopilot authorization ID: ${autopilot.autopilotAuthorizationId}`,
5675
+ ...autopilot.autopilotCandidateId ? [`Autopilot candidate ID: ${autopilot.autopilotCandidateId}`] : [],
5676
+ ...autopilot.autopilotCandidateType ? [`Autopilot candidate type: ${autopilot.autopilotCandidateType}`] : [],
5677
+ `Autopilot outcome: ${autopilot.autopilotClassificationOutcome ?? "unknown"}`,
5678
+ `Autopilot policy: ${autopilot.autopilotPolicyVersion ?? "unknown"}`
5679
+ ];
5680
+ if (autopilot.autopilotAuthorization?.summary) {
5681
+ lines.push(`Autopilot summary: ${autopilot.autopilotAuthorization.summary}`);
5682
+ }
5683
+ lines.push("Autopilot satisfies the app approval gate only for the audited low-risk work item; it does not loosen local repository, credential, or data-safety boundaries.");
5684
+ return lines;
5685
+ }
5686
+ function autopilotWorkMetadata(workItem) {
5687
+ return workItem;
5688
+ }
4686
5689
  function createProjectContextRefreshPrompt(workItem, context) {
4687
5690
  const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 24).map((document) => [
4688
5691
  `### ${document.title}`,
@@ -4854,6 +5857,8 @@ function createAppEvaluationScanPrompt(workItem, context) {
4854
5857
  "- Check app verification health: lint, typecheck, test, build, CI references, flaky or missing gates.",
4855
5858
  "- Check product and docs drift between approved context, plans, prompts, and current implementation.",
4856
5859
  "- Check old plans and prompts for cleanup candidates, but only propose lifecycle actions such as markCompleted, markSuperseded, archive, keepActive, or none.",
5860
+ "- Treat active proposed, approved, ready, or in-progress plans/prompts with accepted controlling ADRs, features, or work artifacts as active work. Do not classify them as cleanup material solely because they are older or not yet fully implemented; use keepActive when they still describe valid pending or approved work.",
5861
+ "- When lifecycle metadata disagrees across indexes, frontmatter, feature specs, ADRs, and implementation evidence, cite the conflict and propose a metadata correction or verification step instead of archival/removal.",
4857
5862
  "- Check missing memory or workflow updates when repeated lessons or operational rules are visible.",
4858
5863
  "- Check release readiness, UX, accessibility, performance, reliability, and security-posture follow-through at a summary level.",
4859
5864
  "",
@@ -4866,6 +5871,10 @@ function createAppEvaluationScanPrompt(workItem, context) {
4866
5871
  "## Output Contract",
4867
5872
  "",
4868
5873
  "Print exactly one JSON object between the markers below. The CLI will submit only this structured scan result back to Amistio.",
5874
+ "Accepted category values: verification, productDrift, planCleanup, promptRot, missingMemory, releaseReadiness, ux, accessibility, performance, reliability, securityPosture, other.",
5875
+ "Accepted severity values: info, low, medium, high, critical.",
5876
+ "Accepted confidence values: low, medium, high.",
5877
+ "Accepted proposedLifecycleAction values: createPlan, updatePlan, markCompleted, markImplemented, markSuperseded, markBlocked, archive, keepActive, none.",
4869
5878
  "",
4870
5879
  appEvaluationStart,
4871
5880
  '{"summary":"The app is generally healthy, but one release-readiness plan needs cleanup and verification evidence should be refreshed.","baselineVersion":"amistio-app-evaluation-v1","findings":[{"title":"Stale release plan should be marked superseded","category":"planCleanup","severity":"low","confidence":"high","summary":"A release plan appears to describe a workflow that has since been replaced by a newer accepted plan.","affectedSurfaces":["docs/plans"],"evidence":["A newer plan references the same scope and status, while the older plan remains proposed."],"suggestedAction":"Prepare a cleanup update that marks the stale plan superseded and links to the newer plan.","verificationPlan":["Review both plan files","Confirm the newer plan is accepted before changing lifecycle state"],"safePaths":["docs/plans/PLAN-example.md"],"proposedLifecycleAction":"markSuperseded","relatedArtifactIds":[]}],"verificationPlan":["Review App Evaluation findings for approval","Run the canonical verify gate before implementing approved actions"]}',
@@ -5196,7 +6205,8 @@ function parseAppEvaluationScanResult(output) {
5196
6205
  }
5197
6206
  const payload = output.slice(start + appEvaluationStart.length, end).trim();
5198
6207
  const parsed = JSON.parse(stripJsonFence(payload));
5199
- return appEvaluationScanResultSchema.parse(parsed);
6208
+ const normalized = normalizeAppEvaluationScanResultPaths(normalizeAppEvaluationScanResultEnums(parsed));
6209
+ return appEvaluationScanResultSchema.parse(normalized);
5200
6210
  }
5201
6211
  function parseProjectContextRefreshResult(output, options = {}) {
5202
6212
  const start = output.indexOf(projectContextRefreshStart);
@@ -5219,6 +6229,26 @@ function parseImplementationVerificationResult(output) {
5219
6229
  const parsed = JSON.parse(stripJsonFence(payload));
5220
6230
  return implementationVerificationResultSchema.parse(parsed);
5221
6231
  }
6232
+ function parseTestQualityScanResult(output) {
6233
+ const start = output.indexOf(testQualityStart);
6234
+ const end = output.indexOf(testQualityEnd, start + testQualityStart.length);
6235
+ if (start === -1 || end === -1 || end <= start) {
6236
+ throw new Error("Local AI scan did not return an Amistio test quality block.");
6237
+ }
6238
+ const payload = output.slice(start + testQualityStart.length, end).trim();
6239
+ const parsed = JSON.parse(stripJsonFence(payload));
6240
+ return testQualityScanResultSchema.parse(parsed);
6241
+ }
6242
+ function parseImplementationTestGateResult(output) {
6243
+ const start = output.indexOf(implementationTestGateStart);
6244
+ const end = output.indexOf(implementationTestGateEnd, start + implementationTestGateStart.length);
6245
+ if (start === -1 || end === -1 || end <= start) {
6246
+ throw new Error("Local AI gate did not return an Amistio implementation test gate block.");
6247
+ }
6248
+ const payload = output.slice(start + implementationTestGateStart.length, end).trim();
6249
+ const parsed = JSON.parse(stripJsonFence(payload));
6250
+ return implementationTestGateResultSchema.parse(parsed);
6251
+ }
5222
6252
  function projectContextRefreshSubmissionFailureSummary(result) {
5223
6253
  if (result.refresh.status !== "failed" && result.workItem.status !== "failed") {
5224
6254
  return void 0;
@@ -5320,6 +6350,75 @@ function normalizeProjectContextRefreshEnums(value) {
5320
6350
  }
5321
6351
  return normalized;
5322
6352
  }
6353
+ function normalizeAppEvaluationScanResultEnums(value) {
6354
+ if (!isObjectRecord(value)) {
6355
+ return value;
6356
+ }
6357
+ const normalized = { ...value };
6358
+ if (Array.isArray(normalized.findings)) {
6359
+ normalized.findings = normalized.findings.map((finding) => {
6360
+ if (!isObjectRecord(finding)) {
6361
+ return finding;
6362
+ }
6363
+ const normalizedFinding = { ...finding };
6364
+ normalizedFinding.category = normalizeAppEvaluationFindingCategory(finding.category);
6365
+ return normalizedFinding;
6366
+ });
6367
+ }
6368
+ return normalized;
6369
+ }
6370
+ function normalizeAppEvaluationFindingCategory(value) {
6371
+ if (typeof value !== "string") {
6372
+ return value;
6373
+ }
6374
+ const trimmed = value.trim();
6375
+ if (!trimmed) {
6376
+ return "other";
6377
+ }
6378
+ const direct = canonicalAppEvaluationCategories.get(normalizeEnumKey(trimmed));
6379
+ if (direct) {
6380
+ return direct;
6381
+ }
6382
+ for (const segment of trimmed.split(/[\/,|]+/)) {
6383
+ const segmentCategory = canonicalAppEvaluationCategories.get(normalizeEnumKey(segment));
6384
+ if (segmentCategory) {
6385
+ return segmentCategory;
6386
+ }
6387
+ }
6388
+ return "other";
6389
+ }
6390
+ function normalizeEnumKey(value) {
6391
+ return value.trim().replace(/[^A-Za-z0-9]+/g, "").toLowerCase();
6392
+ }
6393
+ function normalizeAppEvaluationScanResultPaths(value) {
6394
+ if (!isObjectRecord(value)) {
6395
+ return value;
6396
+ }
6397
+ const normalized = { ...value };
6398
+ if (Array.isArray(normalized.findings)) {
6399
+ normalized.findings = normalized.findings.map((finding) => {
6400
+ if (!isObjectRecord(finding)) {
6401
+ return finding;
6402
+ }
6403
+ const normalizedFinding = { ...finding };
6404
+ if (Array.isArray(normalizedFinding.safePaths)) {
6405
+ normalizedFinding.safePaths = normalizedFinding.safePaths.map((safePath) => typeof safePath === "string" ? normalizeAppEvaluationRepoPath(safePath) : safePath);
6406
+ }
6407
+ if (typeof normalizedFinding.proposedPlanRepoPath === "string") {
6408
+ normalizedFinding.proposedPlanRepoPath = normalizeAppEvaluationRepoPath(normalizedFinding.proposedPlanRepoPath);
6409
+ }
6410
+ return normalizedFinding;
6411
+ });
6412
+ }
6413
+ return normalized;
6414
+ }
6415
+ function normalizeAppEvaluationRepoPath(value) {
6416
+ try {
6417
+ return normalizeRelativeProjectContextPath(value);
6418
+ } catch {
6419
+ throw new Error("App evaluation scan contains an unsafe repository path.");
6420
+ }
6421
+ }
5323
6422
  function normalizeProjectContextSliceKind(value) {
5324
6423
  if (typeof value !== "string") {
5325
6424
  return "unknown";
@@ -5430,15 +6529,15 @@ function normalizeProjectContextRepoPath(value, options) {
5430
6529
  if (!trimmed || /^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(trimmed) || /^file:/i.test(trimmed) || /^[A-Za-z]:($|[^\\/])/.test(trimmed)) {
5431
6530
  throwUnsafeProjectContextPath();
5432
6531
  }
5433
- const absolute = trimmed.startsWith("/") || trimmed.startsWith("\\\\") || path10.isAbsolute(trimmed) || path10.win32.isAbsolute(trimmed);
6532
+ const absolute = trimmed.startsWith("/") || trimmed.startsWith("\\\\") || path11.isAbsolute(trimmed) || path11.win32.isAbsolute(trimmed);
5434
6533
  if (!absolute) {
5435
6534
  return normalizeRelativeProjectContextPath(trimmed);
5436
6535
  }
5437
6536
  if (!options.repositoryRoot) {
5438
6537
  throwUnsafeProjectContextPath();
5439
6538
  }
5440
- const useWindowsPathRules = path10.win32.isAbsolute(trimmed) && !trimmed.startsWith("/");
5441
- const relativePath = useWindowsPathRules ? path10.win32.relative(path10.win32.resolve(options.repositoryRoot), path10.win32.resolve(trimmed)) : path10.relative(path10.resolve(options.repositoryRoot), path10.resolve(trimmed));
6539
+ const useWindowsPathRules = path11.win32.isAbsolute(trimmed) && !trimmed.startsWith("/");
6540
+ const relativePath = useWindowsPathRules ? path11.win32.relative(path11.win32.resolve(options.repositoryRoot), path11.win32.resolve(trimmed)) : path11.relative(path11.resolve(options.repositoryRoot), path11.resolve(trimmed));
5442
6541
  return normalizeRelativeProjectContextPath(relativePath);
5443
6542
  }
5444
6543
  function normalizeRelativeProjectContextPath(value) {
@@ -5490,7 +6589,7 @@ function stripJsonFence(value) {
5490
6589
  }
5491
6590
 
5492
6591
  // src/runner-status.ts
5493
- import { createHash as createHash5 } from "node:crypto";
6592
+ import { createHash as createHash6 } from "node:crypto";
5494
6593
  var watchStateReminderMs = 60 * 1e3;
5495
6594
  function formatWatchStartupContext(input) {
5496
6595
  return [
@@ -5517,20 +6616,20 @@ function watchStateKey(action) {
5517
6616
  return [action.kind, action.message, action.workItemId, action.documentId, action.runnerId].filter(Boolean).join(":");
5518
6617
  }
5519
6618
  function stableRunnerId(input) {
5520
- const digest = createHash5("sha256").update(`${input.accountId}:${input.projectId}:${input.repositoryLinkId}:${input.machineId}`).digest("hex").slice(0, 20);
6619
+ const digest = createHash6("sha256").update(`${input.accountId}:${input.projectId}:${input.repositoryLinkId}:${input.machineId}`).digest("hex").slice(0, 20);
5521
6620
  return `runner_${digest}`;
5522
6621
  }
5523
6622
 
5524
6623
  // src/runner-resources.ts
5525
- import os6 from "node:os";
6624
+ import os7 from "node:os";
5526
6625
  var defaultRuntime = {
5527
6626
  nowMs: () => Date.now(),
5528
6627
  memoryUsage: () => process.memoryUsage(),
5529
6628
  uptime: () => process.uptime(),
5530
6629
  cpuUsage: () => process.cpuUsage(),
5531
- totalmem: () => os6.totalmem(),
5532
- freemem: () => os6.freemem(),
5533
- loadavg: () => os6.loadavg()
6630
+ totalmem: () => os7.totalmem(),
6631
+ freemem: () => os7.freemem(),
6632
+ loadavg: () => os7.loadavg()
5534
6633
  };
5535
6634
  var previousRunnerResourceSample;
5536
6635
  function sampleCurrentRunnerResourceUsage() {
@@ -5642,9 +6741,9 @@ function roundNumber(value, digits) {
5642
6741
 
5643
6742
  // src/importer.ts
5644
6743
  import { execFile as execFile4 } from "node:child_process";
5645
- import { createHash as createHash6 } from "node:crypto";
5646
- import { readdir as readdir4, readFile as readFile8, stat as stat4 } from "node:fs/promises";
5647
- import path11 from "node:path";
6744
+ import { createHash as createHash7 } from "node:crypto";
6745
+ import { readdir as readdir5, readFile as readFile9, stat as stat4 } from "node:fs/promises";
6746
+ import path12 from "node:path";
5648
6747
  import { promisify as promisify4 } from "node:util";
5649
6748
  var execFileAsync4 = promisify4(execFile4);
5650
6749
  var defaultMaxFileKb = 256;
@@ -5665,12 +6764,12 @@ var documentFolderByType = {
5665
6764
  workflow: "docs/workflows"
5666
6765
  };
5667
6766
  async function inspectLocalRepository(rootDir, defaultBranch) {
5668
- const requestedRoot = path11.resolve(rootDir);
6767
+ const requestedRoot = path12.resolve(rootDir);
5669
6768
  const root = await runGit2(["-C", requestedRoot, "rev-parse", "--show-toplevel"]).catch(() => requestedRoot);
5670
6769
  const detectedBranch = await runGit2(["-C", root, "symbolic-ref", "--quiet", "--short", "HEAD"]).catch(() => defaultBranch);
5671
6770
  const originUrl = await runGit2(["-C", root, "remote", "get-url", "origin"]).catch(() => void 0);
5672
6771
  const parsedCloneUrl = originUrl ? parseOptionalOriginCloneUrl(originUrl) : void 0;
5673
- const repoName = (parsedCloneUrl?.repoName ?? path11.basename(root)) || "repository";
6772
+ const repoName = (parsedCloneUrl?.repoName ?? path12.basename(root)) || "repository";
5674
6773
  const fingerprintSeed = parsedCloneUrl ? `origin:${parsedCloneUrl.normalizedKey}` : `repo:${repoName}:${detectedBranch || defaultBranch}`;
5675
6774
  return {
5676
6775
  rootDir: root,
@@ -5682,7 +6781,7 @@ async function inspectLocalRepository(rootDir, defaultBranch) {
5682
6781
  };
5683
6782
  }
5684
6783
  async function scanLegacyDocuments(options) {
5685
- const rootDir = path11.resolve(options.rootDir);
6784
+ const rootDir = path12.resolve(options.rootDir);
5686
6785
  const maxBytes = (options.maxFileKb ?? defaultMaxFileKb) * 1024;
5687
6786
  const skipped = [];
5688
6787
  const candidates = [];
@@ -5702,7 +6801,7 @@ async function scanLegacyDocuments(options) {
5702
6801
  skipped.push({ repoPath, reason: "excluded" });
5703
6802
  continue;
5704
6803
  }
5705
- const fullPath = path11.join(rootDir, ...repoPath.split("/"));
6804
+ const fullPath = path12.join(rootDir, ...repoPath.split("/"));
5706
6805
  const fileStat = await stat4(fullPath).catch(() => void 0);
5707
6806
  if (!fileStat?.isFile()) {
5708
6807
  skipped.push({ repoPath, reason: "unreadable" });
@@ -5712,7 +6811,7 @@ async function scanLegacyDocuments(options) {
5712
6811
  skipped.push({ repoPath, reason: "tooLarge" });
5713
6812
  continue;
5714
6813
  }
5715
- const content = await readFile8(fullPath, "utf8").catch(() => void 0);
6814
+ const content = await readFile9(fullPath, "utf8").catch(() => void 0);
5716
6815
  if (content === void 0) {
5717
6816
  skipped.push({ repoPath, reason: "unreadable" });
5718
6817
  continue;
@@ -5802,10 +6901,10 @@ async function listRepositoryPaths(rootDir) {
5802
6901
  return files;
5803
6902
  }
5804
6903
  async function walkRepository(rootDir, directory, files) {
5805
- const entries = await readdir4(directory, { withFileTypes: true }).catch(() => []);
6904
+ const entries = await readdir5(directory, { withFileTypes: true }).catch(() => []);
5806
6905
  for (const entry of entries) {
5807
- const fullPath = path11.join(directory, entry.name);
5808
- const repoPath = normalizeRepoPath4(path11.relative(rootDir, fullPath));
6906
+ const fullPath = path12.join(directory, entry.name);
6907
+ const repoPath = normalizeRepoPath4(path12.relative(rootDir, fullPath));
5809
6908
  if (entry.isDirectory()) {
5810
6909
  if (!excludedDirectoryNames.has(entry.name)) {
5811
6910
  await walkRepository(rootDir, fullPath, files);
@@ -5873,9 +6972,9 @@ function uniqueDestinationPath(basePath, sourcePath, usedPaths) {
5873
6972
  usedPaths.add(basePath);
5874
6973
  return basePath;
5875
6974
  }
5876
- const extension = path11.posix.extname(basePath) || ".md";
5877
- const directory = path11.posix.dirname(basePath);
5878
- const basename = path11.posix.basename(basePath, extension);
6975
+ const extension = path12.posix.extname(basePath) || ".md";
6976
+ const directory = path12.posix.dirname(basePath);
6977
+ const basename = path12.posix.basename(basePath, extension);
5879
6978
  const uniquePath = `${directory}/${basename}-${hashText(sourcePath, 8)}${extension}`;
5880
6979
  usedPaths.add(uniquePath);
5881
6980
  return uniquePath;
@@ -5948,7 +7047,7 @@ function inferTitle2(content, repoPath) {
5948
7047
  if (heading) return heading;
5949
7048
  const htmlHeading = body.match(/<h1\b[^>]*>([\s\S]*?)<\/h1>/i)?.[1]?.replace(/<[^>]+>/g, "").trim();
5950
7049
  if (htmlHeading) return htmlHeading;
5951
- const basename = path11.posix.basename(repoPath, path11.posix.extname(repoPath)).replace(/[-_]+/g, " ").trim();
7050
+ const basename = path12.posix.basename(repoPath, path12.posix.extname(repoPath)).replace(/[-_]+/g, " ").trim();
5952
7051
  return titleCase(basename || "Imported Document");
5953
7052
  }
5954
7053
  function stripFrontmatter(content) {
@@ -5970,7 +7069,7 @@ function stableImportDocumentId(accountId, projectId, repositoryLinkId, sourcePa
5970
7069
  return `doc_import_${hashText(`${accountId}\0${projectId}\0${repositoryLinkId}\0${sourcePath}`, 24)}`;
5971
7070
  }
5972
7071
  function hashText(value, length) {
5973
- return createHash6("sha256").update(value).digest("hex").slice(0, length);
7072
+ return createHash7("sha256").update(value).digest("hex").slice(0, length);
5974
7073
  }
5975
7074
  function normalizeRepoPath4(value) {
5976
7075
  return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
@@ -5982,7 +7081,7 @@ async function runGit2(args) {
5982
7081
 
5983
7082
  // src/runner-actions.ts
5984
7083
  import { spawn as spawn4 } from "node:child_process";
5985
- import path12 from "node:path";
7084
+ import path13 from "node:path";
5986
7085
  function buildBackgroundRunnerArgs(options) {
5987
7086
  const args = [
5988
7087
  "run",
@@ -5992,7 +7091,7 @@ function buildBackgroundRunnerArgs(options) {
5992
7091
  "--runner-id",
5993
7092
  options.runnerId,
5994
7093
  "--root",
5995
- path12.resolve(options.root),
7094
+ path13.resolve(options.root),
5996
7095
  "--session",
5997
7096
  options.session,
5998
7097
  "--interval-seconds",
@@ -6026,6 +7125,7 @@ function buildBackgroundRunnerArgs(options) {
6026
7125
  if (options.maxIterations !== void 0) {
6027
7126
  args.push("--max-iterations", String(options.maxIterations));
6028
7127
  }
7128
+ args.push("--max-concurrent-work", String(options.maxConcurrentWork));
6029
7129
  args.push("--max-preflight-attempts", String(options.maxPreflightAttempts));
6030
7130
  args.push("--tool-timeout-seconds", String(options.toolTimeoutSeconds));
6031
7131
  if (!options.stream) {
@@ -6109,10 +7209,12 @@ function truncateProcessOutput(value) {
6109
7209
 
6110
7210
  // src/git-worktree.ts
6111
7211
  import { execFile as execFile5 } from "node:child_process";
6112
- import { mkdir as mkdir9, stat as stat5 } from "node:fs/promises";
6113
- import path13 from "node:path";
7212
+ import { copyFile, lstat, mkdir as mkdir10, readdir as readdir6, stat as stat5 } from "node:fs/promises";
7213
+ import path14 from "node:path";
6114
7214
  import { promisify as promisify5 } from "node:util";
6115
7215
  var execFileAsync5 = promisify5(execFile5);
7216
+ var exactLocalEnvironmentFiles = /* @__PURE__ */ new Set([".env", ".env.local", ".env.development", ".env.development.local", ".env.test", ".env.test.local", ".env.production", ".env.production.local"]);
7217
+ var localEnvironmentFilePattern = /^\.env\.[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*\.local$/;
6116
7218
  function needsGitWorktreeIsolation(workItem) {
6117
7219
  return (workItem.workKind ?? "implementation") === "implementation";
6118
7220
  }
@@ -6137,20 +7239,22 @@ async function prepareGitWorktreeIsolation(rootDir, workItem) {
6137
7239
  const worktreePath = localWorktreePath(repoRoot, identity.worktreeKey);
6138
7240
  if (await pathExists(worktreePath)) {
6139
7241
  await assertExistingWorktree(worktreePath, identity.branch);
6140
- return { ...identity, baseRevision, worktreePath };
7242
+ const preparedLocalEnvironmentFileCount2 = await prepareLocalWorktreeEnvironment(repoRoot, worktreePath);
7243
+ return { ...identity, baseRevision, worktreePath, ...preparedLocalEnvironmentFileCount2 ? { preparedLocalEnvironmentFileCount: preparedLocalEnvironmentFileCount2 } : {} };
6141
7244
  }
6142
- await mkdir9(path13.dirname(worktreePath), { recursive: true });
7245
+ await mkdir10(path14.dirname(worktreePath), { recursive: true });
6143
7246
  const branchExists = await gitCommandSucceeds(repoRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${identity.branch}`]);
6144
7247
  const worktreeArgs = branchExists ? ["worktree", "add", worktreePath, identity.branch] : ["worktree", "add", "-b", identity.branch, worktreePath, baseRevision];
6145
7248
  await gitOutput(repoRoot, worktreeArgs).catch((error) => {
6146
7249
  throw new Error(`Could not create Git worktree ${identity.worktreeKey} on ${identity.branch}: ${errorMessage2(error)}`);
6147
7250
  });
6148
- return { ...identity, baseRevision, worktreePath };
7251
+ const preparedLocalEnvironmentFileCount = await prepareLocalWorktreeEnvironment(repoRoot, worktreePath);
7252
+ return { ...identity, baseRevision, worktreePath, ...preparedLocalEnvironmentFileCount ? { preparedLocalEnvironmentFileCount } : {} };
6149
7253
  }
6150
7254
  function localWorktreePath(repoRoot, worktreeKey) {
6151
- const repoName = path13.basename(repoRoot);
7255
+ const repoName = path14.basename(repoRoot);
6152
7256
  const worktreeSlug = worktreeKey.split("/").filter(Boolean).pop() ?? "work";
6153
- return path13.join(path13.dirname(repoRoot), `${repoName}.worktrees`, worktreeSlug);
7257
+ return path14.join(path14.dirname(repoRoot), `${repoName}.worktrees`, worktreeSlug);
6154
7258
  }
6155
7259
  async function assertExistingWorktree(worktreePath, branch) {
6156
7260
  await gitOutput(worktreePath, ["rev-parse", "--is-inside-work-tree"]);
@@ -6172,6 +7276,55 @@ async function assertBaseRevision(repoRoot, baseRevision, currentHead) {
6172
7276
  throw new Error(`Work item base revision ${baseRevision} is not an ancestor of ${currentHead}; refresh the work item before implementation.`);
6173
7277
  }
6174
7278
  }
7279
+ async function prepareLocalWorktreeEnvironment(repoRoot, worktreePath) {
7280
+ const candidates = await localEnvironmentFileCandidates(repoRoot);
7281
+ let preparedCount = 0;
7282
+ for (const candidate of candidates) {
7283
+ const sourcePath = path14.join(repoRoot, candidate);
7284
+ const targetPath = path14.join(worktreePath, candidate);
7285
+ if (await pathExists(targetPath)) {
7286
+ continue;
7287
+ }
7288
+ if (await gitCommandSucceeds(repoRoot, ["ls-files", "--error-unmatch", "--", candidate])) {
7289
+ continue;
7290
+ }
7291
+ if (!await gitCommandSucceeds(worktreePath, ["check-ignore", "--quiet", "--", candidate])) {
7292
+ continue;
7293
+ }
7294
+ try {
7295
+ await copyFile(sourcePath, targetPath);
7296
+ preparedCount += 1;
7297
+ } catch (error) {
7298
+ throw new Error(`Could not prepare local worktree environment files: ${safeFileError(error)}`);
7299
+ }
7300
+ }
7301
+ return preparedCount;
7302
+ }
7303
+ async function localEnvironmentFileCandidates(repoRoot) {
7304
+ const names = new Set(exactLocalEnvironmentFiles);
7305
+ for (const entry of await readdir6(repoRoot)) {
7306
+ if (isAllowedLocalEnvironmentFile(entry)) {
7307
+ names.add(entry);
7308
+ }
7309
+ }
7310
+ const candidates = [];
7311
+ for (const name of [...names].sort()) {
7312
+ if (!isRootFileName(name)) {
7313
+ continue;
7314
+ }
7315
+ const source = await lstat(path14.join(repoRoot, name)).catch(() => void 0);
7316
+ if (source?.isFile()) {
7317
+ candidates.push(name);
7318
+ }
7319
+ }
7320
+ return candidates;
7321
+ }
7322
+ function isAllowedLocalEnvironmentFile(name) {
7323
+ return exactLocalEnvironmentFiles.has(name) || localEnvironmentFilePattern.test(name);
7324
+ }
7325
+ function isRootFileName(name) {
7326
+ return name === path14.basename(name) && !name.includes("/") && !name.includes("\\");
7327
+ }
6175
7328
  async function gitOutput(cwd, args) {
6176
7329
  const { stdout } = await execFileAsync5("git", args, { cwd, maxBuffer: 1024 * 1024 });
6177
7330
  return stdout.trim();
@@ -6194,10 +7347,16 @@ function slugify(value) {
6194
7347
  function errorMessage2(error) {
6195
7348
  return error instanceof Error ? error.message : String(error);
6196
7349
  }
7350
+ function safeFileError(error) {
7351
+ if (typeof error === "object" && error !== null && "code" in error && typeof error.code === "string") {
7352
+ return error.code;
7353
+ }
7354
+ return error instanceof Error ? error.name : "unknown file error";
7355
+ }
6197
7356
 
6198
7357
  // src/implementation-handoff.ts
6199
7358
  import { execFile as execFile6 } from "node:child_process";
6200
- import path14 from "node:path";
7359
+ import path15 from "node:path";
6201
7360
  import { promisify as promisify6 } from "node:util";
6202
7361
  var execFileAsync6 = promisify6(execFile6);
6203
7362
  async function completeImplementationHandoff(input) {
@@ -6213,9 +7372,17 @@ async function completeImplementationHandoff(input) {
6213
7372
  headBranch
6214
7373
  };
6215
7374
  try {
7375
+ const artifactResult = await materializeApprovedArtifacts(input);
7376
+ if (artifactResult.blocked.length) {
7377
+ return blockedHandoff({
7378
+ ...common,
7379
+ artifacts: artifactResult,
7380
+ message: `Implementation handoff is blocked because ${artifactResult.blocked.length} approved artifact${artifactResult.blocked.length === 1 ? "" : "s"} could not be materialized safely.`
7381
+ });
7382
+ }
6216
7383
  const unmergedFiles = await gitOutput2(run, input.worktreePath, ["diff", "--name-only", "--diff-filter=U"]);
6217
7384
  if (unmergedFiles.trim()) {
6218
- return blockedHandoff({ ...common, message: "Implementation handoff is blocked because the worktree has unresolved merge conflicts." });
7385
+ return blockedHandoff({ ...common, artifacts: artifactResult, message: "Implementation handoff is blocked because the worktree has unresolved merge conflicts." });
6219
7386
  }
6220
7387
  const status = await gitOutput2(run, input.worktreePath, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
6221
7388
  if (!status) {
@@ -6223,6 +7390,7 @@ async function completeImplementationHandoff(input) {
6223
7390
  ...common,
6224
7391
  status: "noChanges",
6225
7392
  cleanupStatus: "notApplicable",
7393
+ artifacts: artifactResult,
6226
7394
  message: "Local execution completed with no repository changes to hand off."
6227
7395
  };
6228
7396
  }
@@ -6234,14 +7402,16 @@ async function completeImplementationHandoff(input) {
6234
7402
  ...common,
6235
7403
  provider,
6236
7404
  remoteName,
7405
+ artifacts: artifactResult,
6237
7406
  message: "Automated pull request handoff currently requires a GitHub remote. Commit and push manually, or link a GitHub repository."
6238
7407
  });
6239
7408
  }
6240
7409
  await gitOutput2(run, input.worktreePath, ["add", "-A"]);
6241
7410
  await gitOutput2(run, input.worktreePath, ["commit", "-m", commitSubject(input.workItem), "-m", commitBody(input)]);
7411
+ await rebaseBranchFromRemoteBase(run, input.worktreePath, { baseBranch, remoteName });
6242
7412
  const commitSha = await gitOutput2(run, input.worktreePath, ["rev-parse", "HEAD"]);
6243
7413
  await gitOutput2(run, input.worktreePath, ["push", "--set-upstream", remoteName, headBranch]);
6244
- const pullRequest = await ensureGithubPullRequest(run, input.worktreePath, { baseBranch, headBranch, workItem: input.workItem, ...input.verificationSummary ? { verificationSummary: input.verificationSummary } : {} });
7414
+ const pullRequest = await ensureGithubPullRequest(run, input.worktreePath, { artifacts: artifactResult, baseBranch, headBranch, workItem: input.workItem, ...input.verificationSummary ? { verificationSummary: input.verificationSummary } : {} });
6245
7415
  const cleanup = await cleanupWorktree(run, input);
6246
7416
  return {
6247
7417
  provider: "github",
@@ -6254,12 +7424,70 @@ async function completeImplementationHandoff(input) {
6254
7424
  prUrl: pullRequest.url,
6255
7425
  cleanupStatus: cleanup.status,
6256
7426
  ...cleanup.message ? { cleanupMessage: cleanup.message } : {},
7427
+ artifacts: artifactResult,
6257
7428
  message: cleanup.status === "completed" ? "GitHub pull request is ready for review and the local worktree was cleaned up." : "GitHub pull request is ready for review; local worktree cleanup needs attention."
6258
7429
  };
6259
7430
  } catch (error) {
6260
7431
  return blockedHandoff({ ...common, message: "Implementation handoff is blocked and the local worktree was preserved for recovery.", error: safeErrorMessage(error) });
6261
7432
  }
6262
7433
  }
7434
+ async function materializeApprovedArtifacts(input) {
7435
+ const { selected: artifacts, skipped } = selectApprovedWorkArtifacts(input.workItem, input.approvedArtifacts ?? []);
7436
+ if (!artifacts.length) {
7437
+ return { included: [], skipped, blocked: [] };
7438
+ }
7439
+ let materialized;
7440
+ try {
7441
+ materialized = await materializeBrainDocuments(input.worktreePath, artifacts);
7442
+ } catch (error) {
7443
+ return { included: [], skipped, blocked: artifacts.map((artifact) => artifactStatus(artifact, "blocked", safeErrorMessage(error))) };
7444
+ }
7445
+ return {
7446
+ included: materialized.written.map((repoPath) => artifactStatus(findArtifactByPath(artifacts, repoPath), "included", "Approved artifact materialized into the implementation worktree.")),
7447
+ skipped: [...skipped, ...materialized.skipped.map((repoPath) => artifactStatus(findArtifactByPath(artifacts, repoPath), "skipped", "Approved artifact was already current in the implementation worktree."))],
7448
+ blocked: materialized.conflicts.map((conflict) => artifactStatus(findArtifactByPath(artifacts, conflictRepoPath(conflict)), "blocked", conflict))
7449
+ };
7450
+ }
7451
+ function selectApprovedWorkArtifacts(workItem, documents) {
7452
+ const draftId = workItem.generatedDraftId;
7453
+ const explicitDocumentIds = new Set([workItem.reviewDocumentId, workItem.impactDocumentId].filter((value) => Boolean(value)));
7454
+ const scopeIds = new Set([workItem.workItemId, workItem.controllingAdrId, workItem.implementationScopeId].filter((value) => Boolean(value)));
7455
+ const skipped = [];
7456
+ const selected = documents.filter((document) => {
7457
+ const inScope = Boolean(draftId && document.frontmatter.generatedDraftId === draftId || explicitDocumentIds.has(document.documentId) || explicitDocumentIds.has(document.id) || [document.frontmatter.workItemId, document.frontmatter.generationWorkItemId, document.frontmatter.revisionWorkItemId, document.frontmatter.controllingAdrId, document.frontmatter.adrId, document.frontmatter.implementationScopeId].some((value) => typeof value === "string" && scopeIds.has(value)));
7458
+ if (!inScope) return false;
7459
+ if (document.status !== "approved" && document.syncState !== "approved" && document.syncState !== "synced") {
7460
+ skipped.push(artifactStatus(document, "skipped", "Artifact is not approved for repository inclusion."));
7461
+ return false;
7462
+ }
7463
+ if (!["markdown", "html", void 0].includes(document.contentFormat)) {
7464
+ skipped.push(artifactStatus(document, "skipped", "Artifact format is not supported for implementation PR inclusion."));
7465
+ return false;
7466
+ }
7467
+ return true;
7468
+ });
7469
+ const byDocumentId = /* @__PURE__ */ new Map();
7470
+ for (const document of selected) {
7471
+ byDocumentId.set(document.documentId, document);
7472
+ }
7473
+ return { selected: [...byDocumentId.values()], skipped };
7474
+ }
7475
+ function findArtifactByPath(artifacts, repoPath) {
7476
+ return artifacts.find((artifact) => artifact.repoPath === repoPath) ?? artifacts[0];
7477
+ }
7478
+ function artifactStatus(artifact, status, message) {
7479
+ return {
7480
+ documentId: artifact.documentId,
7481
+ title: truncate(artifact.title, 200),
7482
+ repoPath: artifact.repoPath,
7483
+ contentFormat: artifact.contentFormat ?? "markdown",
7484
+ status,
7485
+ message: truncate(message, 600)
7486
+ };
7487
+ }
7488
+ function conflictRepoPath(conflict) {
7489
+ return conflict.split(":")[0]?.trim() ?? conflict;
7490
+ }
6263
7491
  async function ensureGithubPullRequest(run, cwd, input) {
6264
7492
  const existing = await run("gh", ["pr", "list", "--head", input.headBranch, "--base", input.baseBranch, "--state", "open", "--json", "number,url", "--limit", "1"], { cwd });
6265
7493
  const parsed = parsePullRequestList(existing.stdout);
@@ -6283,7 +7511,7 @@ async function cleanupWorktree(run, input) {
6283
7511
  return { status: "failed", message: "Cleanup skipped because the worktree is not clean after PR handoff." };
6284
7512
  }
6285
7513
  try {
6286
- await gitOutput2(run, input.primaryRepoRoot || path14.dirname(input.worktreePath), ["worktree", "remove", input.worktreePath]);
7514
+ await gitOutput2(run, input.primaryRepoRoot || path15.dirname(input.worktreePath), ["worktree", "remove", input.worktreePath]);
6287
7515
  return { status: "completed" };
6288
7516
  } catch (error) {
6289
7517
  return { status: "failed", message: `Cleanup failed: ${safeErrorMessage(error)}` };
@@ -6296,6 +7524,10 @@ async function resolveRemoteName(run, cwd) {
6296
7524
  }
6297
7525
  return remotes.includes("origin") ? "origin" : remotes[0];
6298
7526
  }
7527
+ async function rebaseBranchFromRemoteBase(run, cwd, input) {
7528
+ await gitOutput2(run, cwd, ["fetch", input.remoteName, input.baseBranch]);
7529
+ await gitOutput2(run, cwd, ["rebase", "FETCH_HEAD"]);
7530
+ }
6299
7531
  async function gitOutput2(run, cwd, args) {
6300
7532
  const result = await run("git", args, { cwd });
6301
7533
  return result.stdout.trim();
@@ -6351,9 +7583,23 @@ function pullRequestBody(input) {
6351
7583
  `Base branch: ${input.baseBranch}`,
6352
7584
  `Head branch: ${input.headBranch}`,
6353
7585
  "",
7586
+ ...artifactSummaryLines(input.artifacts),
7587
+ "",
6354
7588
  input.verificationSummary ?? "Verification summary was not reported by the local runner."
6355
7589
  ].join("\n");
6356
7590
  }
7591
+ function artifactSummaryLines(artifacts) {
7592
+ if (!artifacts || !artifacts.included.length && !artifacts.skipped.length && !artifacts.blocked.length) {
7593
+ return ["Approved artifacts: none materialized for this handoff."];
7594
+ }
7595
+ const lines = [
7596
+ `Approved artifacts: ${artifacts.included.length} included, ${artifacts.skipped.length} already current, ${artifacts.blocked.length} blocked.`
7597
+ ];
7598
+ for (const artifact of artifacts.included.slice(0, 12)) {
7599
+ lines.push(`- ${artifact.repoPath}${artifact.title ? ` (${artifact.title})` : ""}`);
7600
+ }
7601
+ return lines;
7602
+ }
6357
7603
  function truncate(value, maxLength) {
6358
7604
  return value.length <= maxLength ? value : value.slice(0, maxLength - 1).trimEnd();
6359
7605
  }
@@ -6394,7 +7640,8 @@ var DEFAULT_MAX_PREFLIGHT_ATTEMPTS = 3;
6394
7640
  var DEFAULT_TOOL_TIMEOUT_SECONDS = 30 * 60;
6395
7641
  var RUNNER_WORK_LEASE_SECONDS = 300;
6396
7642
  var RUNNER_WORK_LEASE_RENEWAL_MS = 12e4;
6397
- var runnerSupportedWorkKinds = ["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan", "appEvaluationScan", "projectContextRefresh", "implementationVerification"];
7643
+ var MAX_CONCURRENT_RUNNER_WORK = 4;
7644
+ var runnerSupportedWorkKinds = ["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan", "appEvaluationScan", "projectContextRefresh", "implementationVerification", "testQualityScan", "implementationTestGate"];
6398
7645
  program.name("amistio").description("Amistio project brain CLI").version(CLI_VERSION);
6399
7646
  program.command("init").description("Create Amistio control-plane folders for a new project").option("--root <path>", "Repository root", defaultRoot).action(async (options) => {
6400
7647
  const created = await initControlPlane(options.root);
@@ -6677,7 +7924,7 @@ work.command("list").description("List queued work without claiming it").option(
6677
7924
  return;
6678
7925
  }
6679
7926
  for (const item of workItems) {
6680
- console.log(`${item.workItemId} [${item.status}] ${item.title}`);
7927
+ console.log(`${item.workItemId} [${item.status}] ${item.title}${formatAutopilotListSuffix(item)}`);
6681
7928
  }
6682
7929
  });
6683
7930
  work.command("prompt").description("Print or write an approved work prompt without claiming a runner lease").argument("[workItemId]", "Work item ID. Defaults to the newest approved work item.").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--root <path>", "Repository root", defaultRoot).option("--out <path>", "Write the prompt to a file instead of stdout").action(async (workItemId, options) => {
@@ -6694,7 +7941,7 @@ work.command("prompt").description("Print or write an approved work prompt witho
6694
7941
  }
6695
7942
  const prompt = await createRunnerWorkPrompt(context.client, context.metadata.amistioProjectId, workItem);
6696
7943
  if (options.out) {
6697
- await writeFile9(options.out, prompt, "utf8");
7944
+ await writeFile10(options.out, prompt, "utf8");
6698
7945
  console.log(`Wrote work prompt to ${options.out}.`);
6699
7946
  } else {
6700
7947
  console.log(prompt);
@@ -6743,7 +7990,7 @@ program.command("orchestrate").description("Update the Amistio control plane thr
6743
7990
  process.exitCode = result.exitCode;
6744
7991
  }
6745
7992
  });
6746
- program.command("run").description("Claim and run approved Amistio work locally").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--runner-id <runnerId>", "Stable runner ID").option("--root <path>", "Repository root", defaultRoot).option("--tool <name>", "Local tool to use: auto, none, opencode, claude, codex, copilot, gemini, aider, cursor-agent").option("--invocation-channel <channel>", "Local invocation channel: auto, sdk, or command", parseInvocationChannel).option("--model <model>", "Model to request when the selected local tool supports model selection").option("--provider <providerId>", "Provider id for provider-backed model configuration").option("--model-id <modelId>", "Provider catalog model id to request").option("--model-variant <variant>", "Provider catalog model variant to request").option("--reasoning-effort <effort>", "Reasoning effort: auto, low, medium, high, or xhigh", parseReasoningEffort).option("--tool-command <command>", "Custom local command. Use {promptFile} and {root} placeholders when supported").option("--session <policy>", "Tool session policy: auto, new, continue:<toolSessionId>, or none", "auto").option("--dry-run", "Claim work and print the generated execution prompt without running a tool").option("--watch", "Keep polling for approved work until stopped").option("--background", "Start a detached background runner that watches for approved work").option("--interval-seconds <seconds>", "Polling interval for --watch", parsePositiveInteger, 10).option("--max-iterations <count>", "Stop watch mode after this many polling attempts", parsePositiveInteger).option("--max-preflight-attempts <count>", "Fail setup/preflight failures after this many claimed attempts", parsePositiveInteger, DEFAULT_MAX_PREFLIGHT_ATTEMPTS).option("--tool-timeout-seconds <seconds>", "Fail local tool execution after this many seconds", parsePositiveInteger, DEFAULT_TOOL_TIMEOUT_SECONDS).option("--no-stream", "Capture local tool output instead of streaming it").option("--verbose", "Print detailed runner errors while watching").action(async (options, command) => {
7993
+ program.command("run").description("Claim and run approved Amistio work locally").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--runner-id <runnerId>", "Stable runner ID").option("--root <path>", "Repository root", defaultRoot).option("--tool <name>", "Local tool to use: auto, none, opencode, claude, codex, copilot, gemini, aider, cursor-agent").option("--invocation-channel <channel>", "Local invocation channel: auto, sdk, or command", parseInvocationChannel).option("--model <model>", "Model to request when the selected local tool supports model selection").option("--provider <providerId>", "Provider id for provider-backed model configuration").option("--model-id <modelId>", "Provider catalog model id to request").option("--model-variant <variant>", "Provider catalog model variant to request").option("--reasoning-effort <effort>", "Reasoning effort: auto, low, medium, high, or xhigh", parseReasoningEffort).option("--tool-command <command>", "Custom local command. Use {promptFile} and {root} placeholders when supported").option("--session <policy>", "Tool session policy: auto, new, continue:<toolSessionId>, or none", "auto").option("--dry-run", "Claim work and print the generated execution prompt without running a tool").option("--watch", "Keep polling for approved work until stopped").option("--background", "Start a detached background runner that watches for approved work").option("--interval-seconds <seconds>", "Polling interval for --watch", parsePositiveInteger, 10).option("--max-iterations <count>", "Stop watch mode after this many polling attempts", parsePositiveInteger).option("--max-concurrent-work <count>", "Maximum approved work items to run in parallel in --watch mode", parsePositiveInteger, 1).option("--max-preflight-attempts <count>", "Fail setup/preflight failures after this many claimed attempts", parsePositiveInteger, DEFAULT_MAX_PREFLIGHT_ATTEMPTS).option("--tool-timeout-seconds <seconds>", "Fail local tool execution after this many seconds", parsePositiveInteger, DEFAULT_TOOL_TIMEOUT_SECONDS).option("--no-stream", "Capture local tool output instead of streaming it").option("--verbose", "Print detailed runner errors while watching").action(async (options, command) => {
6747
7994
  const context = await loadPairedApiContext(options.root, options.apiUrl);
6748
7995
  if (!context) {
6749
7996
  console.log("Repository is not paired. Run `amistio pair` first.");
@@ -6761,6 +8008,11 @@ program.command("run").description("Claim and run approved Amistio work locally"
6761
8008
  machineId: runnerMachineId()
6762
8009
  });
6763
8010
  const resolvedOptions = { ...options, runnerId };
8011
+ if (resolvedOptions.maxConcurrentWork > MAX_CONCURRENT_RUNNER_WORK) {
8012
+ console.log(`--max-concurrent-work is capped at ${MAX_CONCURRENT_RUNNER_WORK}.`);
8013
+ process.exitCode = 1;
8014
+ return;
8015
+ }
6764
8016
  if (options.background) {
6765
8017
  if (options.dryRun) {
6766
8018
  console.log("Background runners cannot be started in dry-run mode.");
@@ -6772,7 +8024,7 @@ program.command("run").description("Claim and run approved Amistio work locally"
6772
8024
  projectId: context.metadata.amistioProjectId,
6773
8025
  repositoryLinkId: context.metadata.repositoryLinkId,
6774
8026
  runnerId,
6775
- rootDir: path15.resolve(options.root),
8027
+ rootDir: path16.resolve(options.root),
6776
8028
  apiUrl: options.apiUrl,
6777
8029
  args: buildBackgroundRunnerArgs(resolvedOptions)
6778
8030
  });
@@ -6915,7 +8167,7 @@ runner.command("stop").description("Stop a background runner for the paired repo
6915
8167
  console.log(stopResult === "stopped" ? `Stopped background runner ${record.runnerId}.` : `Marked background runner ${record.runnerId} stopped; process was not running.`);
6916
8168
  });
6917
8169
  var runnerService = runner.command("service").description("Manage a user-level startup service for the paired runner");
6918
- runnerService.command("install").description("Install a user-level startup service for this paired repository runner").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--root <path>", "Repository root", defaultRoot).option("--runner-id <runnerId>", "Stable runner ID").option("--tool <name>", "Local tool to use: auto, opencode, claude, codex, copilot, gemini, aider, cursor-agent").option("--invocation-channel <channel>", "Local invocation channel: auto, sdk, or command", parseInvocationChannel).option("--model <model>", "Model to request when the selected local tool supports model selection").option("--provider <providerId>", "Provider id for provider-backed model configuration").option("--model-id <modelId>", "Provider catalog model id to request").option("--model-variant <variant>", "Provider catalog model variant to request").option("--reasoning-effort <effort>", "Reasoning effort: auto, low, medium, high, or xhigh", parseReasoningEffort).option("--session <policy>", "Tool session policy: auto, new, continue:<toolSessionId>, or none", "auto").option("--interval-seconds <seconds>", "Polling interval for the service runner", parsePositiveInteger, 10).option("--max-preflight-attempts <count>", "Fail setup/preflight failures after this many claimed attempts", parsePositiveInteger, DEFAULT_MAX_PREFLIGHT_ATTEMPTS).option("--tool-timeout-seconds <seconds>", "Fail local tool execution after this many seconds", parsePositiveInteger, DEFAULT_TOOL_TIMEOUT_SECONDS).option("--no-stream", "Capture local tool output instead of streaming it").option("--verbose", "Print detailed runner errors").option("--dry-run", "Print the startup service descriptor without installing it").action(async (options) => {
8170
+ runnerService.command("install").description("Install a user-level startup service for this paired repository runner").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--root <path>", "Repository root", defaultRoot).option("--runner-id <runnerId>", "Stable runner ID").option("--tool <name>", "Local tool to use: auto, opencode, claude, codex, copilot, gemini, aider, cursor-agent").option("--invocation-channel <channel>", "Local invocation channel: auto, sdk, or command", parseInvocationChannel).option("--model <model>", "Model to request when the selected local tool supports model selection").option("--provider <providerId>", "Provider id for provider-backed model configuration").option("--model-id <modelId>", "Provider catalog model id to request").option("--model-variant <variant>", "Provider catalog model variant to request").option("--reasoning-effort <effort>", "Reasoning effort: auto, low, medium, high, or xhigh", parseReasoningEffort).option("--session <policy>", "Tool session policy: auto, new, continue:<toolSessionId>, or none", "auto").option("--interval-seconds <seconds>", "Polling interval for the service runner", parsePositiveInteger, 10).option("--max-concurrent-work <count>", "Maximum approved work items to run in parallel in --watch mode", parsePositiveInteger, 1).option("--max-preflight-attempts <count>", "Fail setup/preflight failures after this many claimed attempts", parsePositiveInteger, DEFAULT_MAX_PREFLIGHT_ATTEMPTS).option("--tool-timeout-seconds <seconds>", "Fail local tool execution after this many seconds", parsePositiveInteger, DEFAULT_TOOL_TIMEOUT_SECONDS).option("--no-stream", "Capture local tool output instead of streaming it").option("--verbose", "Print detailed runner errors").option("--dry-run", "Print the startup service descriptor without installing it").action(async (options) => {
6919
8171
  const context = await loadPairedApiContext(options.root, options.apiUrl);
6920
8172
  if (!context) {
6921
8173
  console.log("Repository is not paired. Run `amistio pair` first.");
@@ -6938,13 +8190,18 @@ runnerService.command("install").description("Install a user-level startup servi
6938
8190
  repositoryLinkId: context.metadata.repositoryLinkId,
6939
8191
  machineId: runnerMachineId()
6940
8192
  });
8193
+ if (options.maxConcurrentWork > MAX_CONCURRENT_RUNNER_WORK) {
8194
+ console.log(`--max-concurrent-work is capped at ${MAX_CONCURRENT_RUNNER_WORK}.`);
8195
+ process.exitCode = 1;
8196
+ return;
8197
+ }
6941
8198
  const args = buildBackgroundRunnerArgs({ ...options, runnerId, apiUrl: options.apiUrl, root: options.root });
6942
8199
  const serviceInput = {
6943
8200
  accountId: context.metadata.amistioAccountId,
6944
8201
  projectId: context.metadata.amistioProjectId,
6945
8202
  repositoryLinkId: context.metadata.repositoryLinkId,
6946
8203
  runnerId,
6947
- rootDir: path15.resolve(options.root),
8204
+ rootDir: path16.resolve(options.root),
6948
8205
  apiUrl: options.apiUrl,
6949
8206
  args,
6950
8207
  platform
@@ -7015,7 +8272,20 @@ async function runWatchIteration({ command, context, options, runnerId }) {
7015
8272
  runnerId
7016
8273
  });
7017
8274
  }
7018
- return await runNextWorkItem({
8275
+ if (!options.dryRun) {
8276
+ const replayResult = await replayPendingResultFinalizations({
8277
+ accountId: context.metadata.amistioAccountId,
8278
+ apiClient: context.client,
8279
+ projectId: context.metadata.amistioProjectId,
8280
+ repositoryLinkId: context.metadata.repositoryLinkId,
8281
+ runnerId
8282
+ });
8283
+ if (replayResult) {
8284
+ return replayResult;
8285
+ }
8286
+ }
8287
+ const activeClaimLaneIds = claimLaneIds(options.maxConcurrentWork);
8288
+ const runLane = (claimLaneId, laneIndex) => runNextWorkItem({
7019
8289
  apiClient: context.client,
7020
8290
  projectId: context.metadata.amistioProjectId,
7021
8291
  repositoryLinkId: context.metadata.repositoryLinkId,
@@ -7044,8 +8314,16 @@ async function runWatchIteration({ command, context, options, runnerId }) {
7044
8314
  suppressIdleOutput: Boolean(options.watch),
7045
8315
  maxPreflightAttempts: options.maxPreflightAttempts,
7046
8316
  toolTimeoutMs: options.toolTimeoutSeconds * 1e3,
7047
- verbose: Boolean(options.verbose)
8317
+ verbose: Boolean(options.verbose),
8318
+ claimLaneId,
8319
+ maxConcurrentWork: options.maxConcurrentWork,
8320
+ activeClaimLaneIds,
8321
+ skipRunnerCommands: laneIndex > 0
7048
8322
  });
8323
+ if (options.watch && !options.dryRun && options.maxConcurrentWork > 1) {
8324
+ return aggregateRunnerLaneResults(await Promise.all(activeClaimLaneIds.map((claimLaneId, laneIndex) => runLane(claimLaneId, laneIndex))));
8325
+ }
8326
+ return await runLane("default", 0);
7049
8327
  } catch (error) {
7050
8328
  if (!options.watch) {
7051
8329
  throw error;
@@ -7073,6 +8351,23 @@ ${detail}`);
7073
8351
  return { status: "failed", exitCode: 1, message };
7074
8352
  }
7075
8353
  }
8354
+ function claimLaneIds(maxConcurrentWork) {
8355
+ return Array.from({ length: maxConcurrentWork }, (_, index) => index === 0 ? "default" : `lane_${index + 1}`);
8356
+ }
8357
+ function supportsConcurrentLocalToolExecution(toolConfig) {
8358
+ return toolConfig.tool === "none" || toolConfig.effectiveInvocationChannel === "command" || toolConfig.effectiveTool === "custom";
8359
+ }
8360
+ function aggregateRunnerLaneResults(results) {
8361
+ const stopResult = results.find((result) => result.stopRunner);
8362
+ if (stopResult) return stopResult;
8363
+ const failedResult = results.find((result) => result.status === "failed");
8364
+ if (failedResult) return failedResult;
8365
+ const blockedResult = results.find((result) => result.status === "blocked");
8366
+ if (blockedResult) return blockedResult;
8367
+ const completedResult = results.find((result) => result.status === "completed" || result.status === "preview");
8368
+ if (completedResult) return completedResult;
8369
+ return results.find((result) => result.status === "idle") ?? { status: "idle", exitCode: 0 };
8370
+ }
7076
8371
  async function runAutoSyncCycle({ context, maxFileKb, quiet, quietDisabled, root, runnerId }) {
7077
8372
  const projectId = context.metadata.amistioProjectId;
7078
8373
  const repositoryLinkId = context.metadata.repositoryLinkId;
@@ -7152,7 +8447,11 @@ async function runNextWorkItem({
7152
8447
  commandContext,
7153
8448
  suppressIdleOutput,
7154
8449
  toolTimeoutMs,
7155
- verbose
8450
+ verbose,
8451
+ claimLaneId,
8452
+ maxConcurrentWork,
8453
+ activeClaimLaneIds,
8454
+ skipRunnerCommands
7156
8455
  }) {
7157
8456
  const toolConfig = await resolveRunnerToolConfig({
7158
8457
  apiClient,
@@ -7166,19 +8465,27 @@ async function runNextWorkItem({
7166
8465
  ...explicitTool ? { explicitTool } : {},
7167
8466
  ...toolCommand ? { toolCommand } : {}
7168
8467
  });
7169
- await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, toolConfig.ready ? "online" : "blocked", runnerHeartbeatMetadata(toolConfig));
7170
- const commandResult = await runPendingRunnerCommand(apiClient, commandContext, runnerHeartbeatMetadata(toolConfig));
7171
- if (commandResult.handled) {
7172
- if (commandResult.message) {
7173
- console.log(commandResult.message);
8468
+ const effectiveMaxConcurrentWork = supportsConcurrentLocalToolExecution(toolConfig) ? maxConcurrentWork : 1;
8469
+ const effectiveActiveClaimLaneIds = claimLaneIds(effectiveMaxConcurrentWork);
8470
+ const heartbeatConcurrency = { maxConcurrentWork: effectiveMaxConcurrentWork, activeClaimLaneIds: effectiveActiveClaimLaneIds };
8471
+ if (claimLaneId !== "default" && effectiveMaxConcurrentWork === 1) {
8472
+ return { status: "idle", exitCode: 0 };
8473
+ }
8474
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, toolConfig.ready ? "online" : "blocked", runnerHeartbeatMetadata(toolConfig, currentRunnerMode(), heartbeatConcurrency));
8475
+ if (!skipRunnerCommands) {
8476
+ const commandResult = await runPendingRunnerCommand(apiClient, commandContext, runnerHeartbeatMetadata(toolConfig, currentRunnerMode(), heartbeatConcurrency));
8477
+ if (commandResult.handled) {
8478
+ if (commandResult.message) {
8479
+ console.log(commandResult.message);
8480
+ }
8481
+ return { status: commandResult.succeeded ? "completed" : "failed", exitCode: commandResult.succeeded ? 0 : 1, ...commandResult.stopRunner ? { stopRunner: true } : {} };
7174
8482
  }
7175
- return { status: commandResult.succeeded ? "completed" : "failed", exitCode: commandResult.succeeded ? 0 : 1, ...commandResult.stopRunner ? { stopRunner: true } : {} };
7176
8483
  }
7177
8484
  if (!toolConfig.ready) {
7178
8485
  console.log(toolConfig.message);
7179
8486
  return { status: "blocked", exitCode: 1 };
7180
8487
  }
7181
- const result = await apiClient.claimWork(projectId, runnerId, repositoryLinkId, RUNNER_WORK_LEASE_SECONDS, runnerIsolationCapabilityMetadata());
8488
+ const result = await apiClient.claimWork(projectId, runnerId, repositoryLinkId, RUNNER_WORK_LEASE_SECONDS, runnerIsolationCapabilityMetadata({ claimLaneId, maxConcurrentWork: effectiveMaxConcurrentWork }));
7182
8489
  if (!result.workItem) {
7183
8490
  const nextAction = await loadProjectNextAction(apiClient, projectId, repositoryLinkId, root);
7184
8491
  const message = formatProjectNextAction(nextAction);
@@ -7196,17 +8503,17 @@ async function runNextWorkItem({
7196
8503
  });
7197
8504
  if (dryRun || toolConfig.tool === "none") {
7198
8505
  console.log(prompt);
7199
- await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
8506
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig, currentRunnerMode(), heartbeatConcurrency));
7200
8507
  return { status: "preview", exitCode: 0 };
7201
8508
  }
7202
- const worktreeIsolation = await prepareWorktreeForClaimedItem({ apiClient, maxPreflightAttempts, projectId, repositoryLinkId, root, runnerId, toolConfig, workItem: result.workItem });
8509
+ const worktreeIsolation = await prepareWorktreeForClaimedItem({ apiClient, heartbeatConcurrency, maxPreflightAttempts, projectId, repositoryLinkId, root, runnerId, toolConfig, workItem: result.workItem });
7203
8510
  if (worktreeIsolation.status !== "ready") {
7204
8511
  return { status: worktreeIsolation.status === "failed" ? "failed" : "blocked", exitCode: 1, message: worktreeIsolation.message };
7205
8512
  }
7206
8513
  const executionRoot = worktreeIsolation.isolation?.worktreePath ?? root;
7207
8514
  const isolationTelemetry = workItemIsolationTelemetry(result.workItem, worktreeIsolation.isolation);
7208
8515
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "running", {
7209
- ...runnerHeartbeatMetadata(toolConfig),
8516
+ ...runnerHeartbeatMetadata(toolConfig, currentRunnerMode(), heartbeatConcurrency),
7210
8517
  currentWorkItemId: result.workItem.workItemId,
7211
8518
  ...isolationTelemetry.implementationScopeId ? { currentImplementationScopeId: isolationTelemetry.implementationScopeId } : {},
7212
8519
  ...isolationTelemetry.executionWorktreeKey ? { currentWorktreeKey: isolationTelemetry.executionWorktreeKey } : {},
@@ -7229,6 +8536,10 @@ async function runNextWorkItem({
7229
8536
  isolationTelemetry
7230
8537
  });
7231
8538
  console.log(`Claimed ${result.workItem.workItemId}. Running ${preview.toolName}: ${preview.displayCommand}`);
8539
+ const autopilotClaimLine = formatAutopilotClaimLine(result.workItem);
8540
+ if (autopilotClaimLine) {
8541
+ console.log(autopilotClaimLine);
8542
+ }
7232
8543
  await recordRunnerMilestone(apiClient, projectId, result.workItem, runnerId, repositoryLinkId, {
7233
8544
  status: "running",
7234
8545
  summary: `Local runner started ${preview.toolName} execution.`,
@@ -7239,7 +8550,7 @@ async function runNextWorkItem({
7239
8550
  const providerSessionStore = new LocalToolSessionStore();
7240
8551
  const providerSessionId = sessionContext.toolSession ? await providerSessionStore.getProviderSessionId(sessionContext.toolSession.toolSessionId, preview.toolName) : void 0;
7241
8552
  let toolResult;
7242
- const stopLeaseRenewal = startWorkLeaseRenewal({ apiClient, projectId, repositoryLinkId, runnerId, toolConfig, workItem: result.workItem, telemetry: isolationTelemetry });
8553
+ const stopLeaseRenewal = startWorkLeaseRenewal({ apiClient, projectId, repositoryLinkId, runnerId, toolConfig, workItem: result.workItem, telemetry: isolationTelemetry, heartbeatConcurrency });
7243
8554
  try {
7244
8555
  toolResult = await runLocalTool({
7245
8556
  rootDir: executionRoot,
@@ -7265,7 +8576,7 @@ async function runNextWorkItem({
7265
8576
  const durationMs2 = Date.now() - startedAt;
7266
8577
  const message = `${preview.toolName} failed before returning a result.`;
7267
8578
  const settlements = await Promise.allSettled([
7268
- apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig)),
8579
+ apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig, currentRunnerMode(), heartbeatConcurrency)),
7269
8580
  markToolSessionBlocked(apiClient, projectId, sessionContext.toolSession, errorMessage3(error)),
7270
8581
  apiClient.updateWorkStatus(projectId, result.workItem.workItemId, "failed", `run_failed_${result.workItem.workItemId}_${result.workItem.attempt}_${runnerId}`, runnerId, {
7271
8582
  ...isolationTelemetry,
@@ -7448,6 +8759,42 @@ async function runNextWorkItem({
7448
8759
  return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
7449
8760
  }
7450
8761
  }
8762
+ if (result.workItem.workKind === "testQualityScan") {
8763
+ try {
8764
+ return await finalizeTestQualityScanWork({
8765
+ apiClient,
8766
+ durationMs: Date.now() - startedAt,
8767
+ projectId,
8768
+ repositoryLinkId,
8769
+ runnerId,
8770
+ sessionContext,
8771
+ toolConfig,
8772
+ toolName: preview.toolName,
8773
+ toolResult,
8774
+ workItem: result.workItem
8775
+ });
8776
+ } catch (error) {
8777
+ return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
8778
+ }
8779
+ }
8780
+ if (result.workItem.workKind === "implementationTestGate") {
8781
+ try {
8782
+ return await finalizeImplementationTestGateWork({
8783
+ apiClient,
8784
+ durationMs: Date.now() - startedAt,
8785
+ projectId,
8786
+ repositoryLinkId,
8787
+ runnerId,
8788
+ sessionContext,
8789
+ toolConfig,
8790
+ toolName: preview.toolName,
8791
+ toolResult,
8792
+ workItem: result.workItem
8793
+ });
8794
+ } catch (error) {
8795
+ return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
8796
+ }
8797
+ }
7451
8798
  let finalStatus = toolResult.exitCode === 0 ? "completed" : "failed";
7452
8799
  const durationMs = Date.now() - startedAt;
7453
8800
  const failureExcerpt = toolResult.exitCode === 0 ? void 0 : truncateLogExcerpt(toolResult.stderr || toolResult.stdout);
@@ -7462,14 +8809,27 @@ async function runNextWorkItem({
7462
8809
  metadata: { executionWorktreeKey: isolationTelemetry.executionWorktreeKey ?? "", executionBranch: isolationTelemetry.executionBranch ?? "" }
7463
8810
  });
7464
8811
  const repositoryLink = await loadWorkItemRepositoryLink(apiClient, projectId, result.workItem.repositoryLinkId ?? repositoryLinkId);
7465
- implementationHandoff = await completeImplementationHandoff({
7466
- primaryRepoRoot: root,
7467
- ...repositoryLink ? { repositoryLink } : {},
7468
- verificationSummary: "Local execution reported completion.",
7469
- workItem: result.workItem,
7470
- ...worktreeIsolation.isolation ? { worktreeIsolation: worktreeIsolation.isolation } : {},
7471
- worktreePath: executionRoot
8812
+ const approvedArtifacts = await apiClient.listBrainDocuments(projectId).then((response) => response.documents).catch((error) => {
8813
+ implementationHandoff = {
8814
+ status: "blocked",
8815
+ cleanupStatus: "pending",
8816
+ artifacts: { included: [], skipped: [], blocked: [] },
8817
+ message: "Implementation handoff is blocked because approved artifact metadata could not be loaded.",
8818
+ error: truncateLogExcerpt(errorDetail(error))
8819
+ };
8820
+ return void 0;
7472
8821
  });
8822
+ if (!implementationHandoff) {
8823
+ implementationHandoff = await completeImplementationHandoff({
8824
+ ...approvedArtifacts ? { approvedArtifacts } : {},
8825
+ primaryRepoRoot: root,
8826
+ ...repositoryLink ? { repositoryLink } : {},
8827
+ verificationSummary: "Local execution reported completion.",
8828
+ workItem: result.workItem,
8829
+ ...worktreeIsolation.isolation ? { worktreeIsolation: worktreeIsolation.isolation } : {},
8830
+ worktreePath: executionRoot
8831
+ });
8832
+ }
7473
8833
  finalStatus = implementationHandoff.status === "prReady" || implementationHandoff.status === "noChanges" ? "completed" : "blocked";
7474
8834
  finalMessage = implementationHandoff.message ?? "Implementation handoff finished.";
7475
8835
  finalError = implementationHandoff.error;
@@ -7487,31 +8847,48 @@ async function runNextWorkItem({
7487
8847
  ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
7488
8848
  ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
7489
8849
  });
7490
- const statusResult = await apiClient.updateWorkStatus(
7491
- projectId,
7492
- result.workItem.workItemId,
7493
- finalStatus,
7494
- `run_${result.workItem.workItemId}_${randomUUID()}`,
7495
- runnerId,
7496
- {
7497
- tool: preview.toolName,
7498
- ...toolResult.model ? { model: toolResult.model } : {},
7499
- durationMs,
7500
- message: finalMessage,
7501
- ...isolationTelemetry,
7502
- ...finalStatus === "blocked" ? { blockerReason: finalMessage } : {},
7503
- sessionPolicy: sessionContext.policy,
7504
- sessionDecision: sessionContext.decision,
7505
- sessionDecisionReason: sessionContext.reason,
7506
- ...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
7507
- ...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {},
7508
- ...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
7509
- ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
7510
- ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {},
7511
- ...implementationHandoff ? { implementationHandoff } : {},
7512
- ...finalError ? { error: finalError } : {}
8850
+ let statusResult;
8851
+ try {
8852
+ statusResult = await apiClient.updateWorkStatus(
8853
+ projectId,
8854
+ result.workItem.workItemId,
8855
+ finalStatus,
8856
+ `run_${result.workItem.workItemId}_${randomUUID()}`,
8857
+ runnerId,
8858
+ {
8859
+ tool: preview.toolName,
8860
+ ...toolResult.model ? { model: toolResult.model } : {},
8861
+ durationMs,
8862
+ message: finalMessage,
8863
+ ...isolationTelemetry,
8864
+ ...finalStatus === "blocked" ? { blockerReason: finalMessage } : {},
8865
+ sessionPolicy: sessionContext.policy,
8866
+ sessionDecision: sessionContext.decision,
8867
+ sessionDecisionReason: sessionContext.reason,
8868
+ ...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
8869
+ ...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {},
8870
+ ...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
8871
+ ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
8872
+ ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {},
8873
+ ...implementationHandoff ? { implementationHandoff } : {},
8874
+ ...finalError ? { error: finalError } : {}
8875
+ }
8876
+ );
8877
+ } catch (error) {
8878
+ if (error instanceof AmistioApiError && error.status === 409 && error.detail.includes("implementation_test_gate_required")) {
8879
+ const gateMessage = "Implementation test gate was queued and must pass before completion or PR handoff is finalized.";
8880
+ await recordRunnerMilestone(apiClient, projectId, result.workItem, runnerId, repositoryLinkId, {
8881
+ status: "queued",
8882
+ summary: gateMessage,
8883
+ idempotencyKey: `runner_milestone_test_gate_required_${result.workItem.workItemId}_${result.workItem.attempt}`,
8884
+ metadata: { tool: preview.toolName, durationMs, executionWorktreeKey: isolationTelemetry.executionWorktreeKey ?? "", executionBranch: isolationTelemetry.executionBranch ?? "" }
8885
+ });
8886
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig, currentRunnerMode(), heartbeatConcurrency));
8887
+ console.log(gateMessage);
8888
+ return { status: "blocked", exitCode: 0 };
7513
8889
  }
7514
- );
8890
+ throw error;
8891
+ }
7515
8892
  await recordRunnerMilestone(apiClient, projectId, result.workItem, runnerId, repositoryLinkId, {
7516
8893
  status: finalStatus,
7517
8894
  summary: finalMessage,
@@ -7525,19 +8902,27 @@ async function runNextWorkItem({
7525
8902
  executionBranch: isolationTelemetry.executionBranch ?? "",
7526
8903
  ...implementationHandoff?.status ? { handoffStatus: implementationHandoff.status } : {},
7527
8904
  ...implementationHandoff?.prUrl ? { prUrl: implementationHandoff.prUrl } : {},
7528
- ...implementationHandoff?.cleanupStatus ? { cleanupStatus: implementationHandoff.cleanupStatus } : {}
8905
+ ...implementationHandoff?.cleanupStatus ? { cleanupStatus: implementationHandoff.cleanupStatus } : {},
8906
+ ...implementationHandoff?.artifacts ? artifactHandoffMetadata(implementationHandoff.artifacts) : {}
7529
8907
  }
7530
8908
  });
7531
- await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
8909
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig, currentRunnerMode(), heartbeatConcurrency));
7532
8910
  const durationSeconds = Math.round(durationMs / 1e3);
7533
8911
  console.log(`Marked ${statusResult.workItem.workItemId} ${statusResult.workItem.status} after ${durationSeconds}s.`);
7534
8912
  return { status: finalStatus, exitCode: finalStatus === "completed" ? toolResult.exitCode : 1 };
7535
8913
  }
8914
+ function artifactHandoffMetadata(artifacts) {
8915
+ return {
8916
+ artifactIncludedCount: artifacts.included.length,
8917
+ artifactSkippedCount: artifacts.skipped.length,
8918
+ artifactBlockedCount: artifacts.blocked.length
8919
+ };
8920
+ }
7536
8921
  async function loadWorkItemRepositoryLink(apiClient, projectId, repositoryLinkId) {
7537
8922
  const { repositoryLinks } = await apiClient.listRepositoryLinks(projectId);
7538
8923
  return repositoryLinks.find((link) => link.repositoryLinkId === repositoryLinkId && link.status !== "revoked");
7539
8924
  }
7540
- async function prepareWorktreeForClaimedItem({ apiClient, maxPreflightAttempts, projectId, repositoryLinkId, root, runnerId, toolConfig, workItem }) {
8925
+ async function prepareWorktreeForClaimedItem({ apiClient, heartbeatConcurrency, maxPreflightAttempts, projectId, repositoryLinkId, root, runnerId, toolConfig, workItem }) {
7541
8926
  if (!needsGitWorktreeIsolation(workItem)) {
7542
8927
  return { status: "ready" };
7543
8928
  }
@@ -7552,9 +8937,9 @@ async function prepareWorktreeForClaimedItem({ apiClient, maxPreflightAttempts,
7552
8937
  const isolation = await prepareGitWorktreeIsolation(root, workItem);
7553
8938
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
7554
8939
  status: "running",
7555
- summary: `Prepared Git worktree ${isolation.worktreeKey} on ${isolation.branch}.`,
8940
+ summary: isolation.preparedLocalEnvironmentFileCount ? `Prepared Git worktree ${isolation.worktreeKey} on ${isolation.branch} and local environment files.` : `Prepared Git worktree ${isolation.worktreeKey} on ${isolation.branch}.`,
7556
8941
  idempotencyKey: `runner_milestone_worktree_${workItem.workItemId}_${workItem.attempt}`,
7557
- metadata: { executionWorktreeKey: isolation.worktreeKey, executionBranch: isolation.branch, implementationScopeId: isolation.implementationScopeId }
8942
+ metadata: { executionWorktreeKey: isolation.worktreeKey, executionBranch: isolation.branch, implementationScopeId: isolation.implementationScopeId, ...isolation.preparedLocalEnvironmentFileCount ? { preparedLocalEnvironmentFileCount: isolation.preparedLocalEnvironmentFileCount } : {} }
7558
8943
  });
7559
8944
  return { status: "ready", isolation };
7560
8945
  } catch (error) {
@@ -7575,14 +8960,14 @@ async function prepareWorktreeForClaimedItem({ apiClient, maxPreflightAttempts,
7575
8960
  metadata: { executionWorktreeKey: telemetry.executionWorktreeKey ?? "", executionBranch: telemetry.executionBranch ?? "", implementationScopeId: telemetry.implementationScopeId ?? "", attempt: workItem.attempt, maxAttempts: maxPreflightAttempts, error: message }
7576
8961
  });
7577
8962
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", {
7578
- ...runnerHeartbeatMetadata(toolConfig),
8963
+ ...runnerHeartbeatMetadata(toolConfig, currentRunnerMode(), heartbeatConcurrency),
7579
8964
  preferenceMessage: statusMessage
7580
8965
  });
7581
8966
  console.error(statusMessage);
7582
8967
  return { status: finalAttempt ? "failed" : "retrying", message: statusMessage };
7583
8968
  }
7584
8969
  }
7585
- function startWorkLeaseRenewal({ apiClient, projectId, repositoryLinkId, runnerId, toolConfig, workItem, telemetry }) {
8970
+ function startWorkLeaseRenewal({ apiClient, projectId, repositoryLinkId, runnerId, toolConfig, workItem, telemetry, heartbeatConcurrency }) {
7586
8971
  let stopped = false;
7587
8972
  const renew = async () => {
7588
8973
  if (stopped) return;
@@ -7593,7 +8978,7 @@ function startWorkLeaseRenewal({ apiClient, projectId, repositoryLinkId, runnerI
7593
8978
  leaseExpiresAt
7594
8979
  });
7595
8980
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "running", {
7596
- ...runnerHeartbeatMetadata(toolConfig),
8981
+ ...runnerHeartbeatMetadata(toolConfig, currentRunnerMode(), heartbeatConcurrency),
7597
8982
  currentWorkItemId: workItem.workItemId,
7598
8983
  ...telemetry.implementationScopeId ? { currentImplementationScopeId: telemetry.implementationScopeId } : {},
7599
8984
  ...telemetry.executionWorktreeKey ? { currentWorktreeKey: telemetry.executionWorktreeKey } : {},
@@ -7609,6 +8994,7 @@ function startWorkLeaseRenewal({ apiClient, projectId, repositoryLinkId, runnerI
7609
8994
  workItemId: workItem.workItemId,
7610
8995
  workTitle: workItem.title,
7611
8996
  ...workItem.workKind ? { workKind: workItem.workKind } : {},
8997
+ ...workItem.claimLaneId ? { claimLaneId: workItem.claimLaneId } : {},
7612
8998
  message: "Runner could not renew the active work lease.",
7613
8999
  error: detail,
7614
9000
  machineId: runnerMachineId()
@@ -7648,6 +9034,7 @@ async function recordFinalizationFailure({ apiClient, durationMs, error, isolati
7648
9034
  workItemId: workItem.workItemId,
7649
9035
  workTitle: workItem.title,
7650
9036
  ...workItem.workKind ? { workKind: workItem.workKind } : {},
9037
+ ...workItem.claimLaneId ? { claimLaneId: workItem.claimLaneId } : {},
7651
9038
  tool: toolName,
7652
9039
  durationMs,
7653
9040
  message,
@@ -7672,6 +9059,7 @@ function workItemIsolationTelemetry(workItem, isolation) {
7672
9059
  const repositoryLockId = isolation?.repositoryLockId ?? workItem.repositoryLockId;
7673
9060
  return {
7674
9061
  ...needsGitWorktreeIsolation(workItem) ? { isolationMode: "gitWorktree" } : workItem.isolationMode ? { isolationMode: workItem.isolationMode } : {},
9062
+ ...workItem.claimLaneId ? { claimLaneId: workItem.claimLaneId } : {},
7675
9063
  ...workItem.claimLeaseId ? { claimLeaseId: workItem.claimLeaseId } : {},
7676
9064
  ...workItem.controllingAdrId ? { controllingAdrId: workItem.controllingAdrId } : {},
7677
9065
  ...implementationScopeId ? { implementationScopeId } : {},
@@ -7683,17 +9071,60 @@ function workItemIsolationTelemetry(workItem, isolation) {
7683
9071
  };
7684
9072
  }
7685
9073
  async function recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, input) {
9074
+ const autopilot = autopilotWorkMetadata2(workItem);
9075
+ const metadata = { ...autopilotRunnerMetadata(workItem), ...workItem.claimLaneId ? { claimLaneId: workItem.claimLaneId } : {}, ...input.metadata ?? {} };
7686
9076
  await apiClient.recordActivityEvent(projectId, {
7687
9077
  eventType: "runnerMilestone",
7688
9078
  runnerId,
7689
9079
  repositoryLinkId,
9080
+ ...workItem.claimLaneId ? { claimLaneId: workItem.claimLaneId } : {},
7690
9081
  relatedWorkItemId: workItem.workItemId,
7691
9082
  ...workItem.reviewDocumentId ? { relatedDocumentId: workItem.reviewDocumentId } : workItem.impactDocumentId ? { relatedDocumentId: workItem.impactDocumentId } : {},
7692
9083
  ...workItem.issueId ? { relatedIssueId: workItem.issueId } : {},
9084
+ ...autopilot.autopilotAuthorizationId ? { relatedAutopilotAuthorizationId: autopilot.autopilotAuthorizationId } : {},
9085
+ ...autopilot.autopilotCandidateId ? { relatedAutopilotCandidateId: autopilot.autopilotCandidateId } : {},
7693
9086
  ...workItem.generatedDraftId ? { generatedDraftId: workItem.generatedDraftId } : {},
7694
- ...input
9087
+ ...input,
9088
+ ...Object.keys(metadata).length ? { metadata } : {}
7695
9089
  }).catch(() => void 0);
7696
9090
  }
9091
+ function formatAutopilotListSuffix(workItem) {
9092
+ const autopilot = autopilotWorkMetadata2(workItem);
9093
+ if (!autopilot.autopilotAuthorizationId) {
9094
+ return "";
9095
+ }
9096
+ const details = [
9097
+ autopilot.autopilotClassificationOutcome ?? "authorized",
9098
+ workItem.workKind ?? "implementation",
9099
+ autopilot.autopilotCandidateType,
9100
+ autopilot.autopilotPolicyVersion ? `policy ${autopilot.autopilotPolicyVersion}` : void 0,
9101
+ `auth ${autopilot.autopilotAuthorizationId}`,
9102
+ autopilot.autopilotCandidateId ? `candidate ${autopilot.autopilotCandidateId}` : void 0
9103
+ ].filter(Boolean).join(", ");
9104
+ return ` (autopilot: ${details})`;
9105
+ }
9106
+ function formatAutopilotClaimLine(workItem) {
9107
+ const autopilot = autopilotWorkMetadata2(workItem);
9108
+ if (!autopilot.autopilotAuthorizationId) {
9109
+ return void 0;
9110
+ }
9111
+ const candidate = autopilot.autopilotCandidateId ? `, candidate ${autopilot.autopilotCandidateId}` : "";
9112
+ const candidateType = autopilot.autopilotCandidateType ? `, ${autopilot.autopilotCandidateType}` : "";
9113
+ return `Autopilot authorization: ${autopilot.autopilotAuthorizationId} (${autopilot.autopilotClassificationOutcome ?? "authorized"}, ${workItem.workKind ?? "implementation"}${candidateType}, policy ${autopilot.autopilotPolicyVersion ?? "unknown"}${candidate}). Local runner safety rules still apply.`;
9114
+ }
9115
+ function autopilotRunnerMetadata(workItem) {
9116
+ const autopilot = autopilotWorkMetadata2(workItem);
9117
+ return {
9118
+ ...autopilot.autopilotAuthorizationId ? { autopilotAuthorizationId: autopilot.autopilotAuthorizationId } : {},
9119
+ ...autopilot.autopilotCandidateId ? { autopilotCandidateId: autopilot.autopilotCandidateId } : {},
9120
+ ...autopilot.autopilotCandidateType ? { autopilotCandidateType: autopilot.autopilotCandidateType } : {},
9121
+ ...autopilot.autopilotClassificationOutcome ? { autopilotOutcome: autopilot.autopilotClassificationOutcome } : {},
9122
+ ...autopilot.autopilotPolicyVersion ? { autopilotPolicyVersion: autopilot.autopilotPolicyVersion } : {}
9123
+ };
9124
+ }
9125
+ function autopilotWorkMetadata2(workItem) {
9126
+ return workItem;
9127
+ }
7697
9128
  function logRejectedSettlements(action, settlements) {
7698
9129
  for (const settlement of settlements) {
7699
9130
  if (settlement.status === "rejected") {
@@ -7762,6 +9193,201 @@ function runnerCommandLabel(commandKind) {
7762
9193
  if (commandKind === "restart") return "restart";
7763
9194
  return "remove";
7764
9195
  }
9196
+ async function replayPendingBrainGenerationFinalizations({ accountId, apiClient, projectId, repositoryLinkId, runnerId }) {
9197
+ const pendingEntries = await listPendingBrainGenerationFinalizations({ accountId, projectId, repositoryLinkId, runnerId });
9198
+ if (!pendingEntries.length) {
9199
+ return void 0;
9200
+ }
9201
+ let completedCount = 0;
9202
+ for (const entry of pendingEntries) {
9203
+ const replay = await submitBrainGenerationFinalizationEntry(apiClient, entry, { recordReplayTelemetry: true });
9204
+ if (replay.status === "completed") {
9205
+ completedCount += 1;
9206
+ continue;
9207
+ }
9208
+ return { status: "failed", exitCode: 1, message: replay.message };
9209
+ }
9210
+ const message = `Replayed ${completedCount} pending brain generation finalization${completedCount === 1 ? "" : "s"}.`;
9211
+ console.log(message);
9212
+ return { status: "completed", exitCode: 0, message };
9213
+ }
9214
+ async function replayPendingResultFinalizations({ accountId, apiClient, projectId, repositoryLinkId, runnerId }) {
9215
+ const brainReplay = await replayPendingBrainGenerationFinalizations({ accountId, apiClient, projectId, repositoryLinkId, runnerId });
9216
+ if (brainReplay) {
9217
+ return brainReplay;
9218
+ }
9219
+ const pendingEntries = await listPendingDurableResultFinalizations({ accountId, projectId, repositoryLinkId, runnerId });
9220
+ if (!pendingEntries.length) {
9221
+ return void 0;
9222
+ }
9223
+ let completedCount = 0;
9224
+ for (const entry of pendingEntries) {
9225
+ const replay = await submitDurableResultFinalizationEntry(apiClient, entry, { recordReplayTelemetry: true });
9226
+ if (replay.status === "completed") {
9227
+ completedCount += 1;
9228
+ continue;
9229
+ }
9230
+ return { status: "failed", exitCode: 1, message: replay.message };
9231
+ }
9232
+ const message = `Replayed ${completedCount} pending runner result finalization${completedCount === 1 ? "" : "s"}.`;
9233
+ console.log(message);
9234
+ return { status: "completed", exitCode: 0, message };
9235
+ }
9236
+ async function submitBrainGenerationFinalizationEntry(apiClient, entry, options = {}) {
9237
+ let result;
9238
+ try {
9239
+ result = await apiClient.submitBrainGenerationResult(entry.projectId, entry.workItemId, entry.result);
9240
+ } catch (error) {
9241
+ const detail = truncateLogExcerpt(errorMessage3(error));
9242
+ if (isRetryableApiError(error)) {
9243
+ const updated = await markBrainGenerationFinalizationRetry(entry, detail);
9244
+ const message2 = `Pending brain generation finalization ${entry.workItemId} could not be replayed yet (${updated.retryCount} attempt${updated.retryCount === 1 ? "" : "s"}): ${detail}`;
9245
+ console.error(message2);
9246
+ return { status: "failed", message: message2, retryable: true };
9247
+ }
9248
+ await markBrainGenerationFinalizationTerminal(entry, detail);
9249
+ const message = `Pending brain generation finalization ${entry.workItemId} reached a terminal API failure: ${detail}`;
9250
+ console.error(message);
9251
+ return { status: "failed", message, retryable: false };
9252
+ }
9253
+ await deleteBrainGenerationFinalizationEntry(entry).catch((error) => {
9254
+ console.error(`delete pending brain generation finalization ${entry.workItemId} failed: ${errorMessage3(error)}`);
9255
+ });
9256
+ if (options.recordReplayTelemetry) {
9257
+ await recordRunnerMilestone(apiClient, entry.projectId, result.workItem, entry.runnerId, entry.repositoryLinkId, {
9258
+ status: entry.result.status,
9259
+ summary: entry.result.status === "completed" ? "Replayed pending brain generation result finalization." : "Replayed pending brain generation failure finalization.",
9260
+ idempotencyKey: `runner_milestone_generation_replayed_${entry.workItemId}_${result.workItem.idempotencyKey}`,
9261
+ metadata: { artifactCount: result.documents.length, replayedFinalization: true, workKind: entry.workKind }
9262
+ });
9263
+ await apiClient.sendRunnerHeartbeat(entry.projectId, entry.runnerId, entry.repositoryLinkId, "online", {
9264
+ ...runnerHeartbeatMetadata(),
9265
+ preferenceMessage: "Pending result finalization replayed."
9266
+ }).catch(() => void 0);
9267
+ }
9268
+ return { status: "completed", workItem: result.workItem, documentCount: result.documents.length };
9269
+ }
9270
+ async function submitDurableResultFinalizationEntry(apiClient, entry, options = {}) {
9271
+ let response;
9272
+ try {
9273
+ response = await submitDurableResultMutation(apiClient, entry);
9274
+ } catch (error) {
9275
+ const detail = truncateLogExcerpt(errorMessage3(error));
9276
+ if (isRetryableApiError(error)) {
9277
+ const updated = await markDurableResultFinalizationRetry(entry, detail);
9278
+ const message2 = `Pending ${runnerResultFinalizationLabel(entry)} finalization ${entry.workItemId} could not be replayed yet (${updated.retryCount} attempt${updated.retryCount === 1 ? "" : "s"}): ${detail}`;
9279
+ console.error(message2);
9280
+ return { status: "failed", message: message2, retryable: true };
9281
+ }
9282
+ await markDurableResultFinalizationTerminal(entry, detail);
9283
+ const message = `Pending ${runnerResultFinalizationLabel(entry)} finalization ${entry.workItemId} reached a terminal API failure: ${detail}`;
9284
+ console.error(message);
9285
+ return { status: "failed", message, retryable: false };
9286
+ }
9287
+ const workItem = durableResultResponseWorkItem(response);
9288
+ await deleteDurableResultFinalizationEntry(entry).catch((error) => {
9289
+ console.error(`delete pending ${runnerResultFinalizationLabel(entry)} finalization ${entry.workItemId} failed: ${errorMessage3(error)}`);
9290
+ });
9291
+ if (options.recordReplayTelemetry) {
9292
+ await recordRunnerMilestone(apiClient, entry.projectId, workItem, entry.runnerId, entry.repositoryLinkId, {
9293
+ status: entry.result.status,
9294
+ summary: entry.result.status === "completed" ? `Replayed pending ${runnerResultFinalizationLabel(entry)} finalization.` : `Replayed pending ${runnerResultFinalizationLabel(entry)} failure finalization.`,
9295
+ idempotencyKey: `runner_milestone_result_replayed_${entry.workItemId}_${workItem.idempotencyKey}`,
9296
+ metadata: { replayedFinalization: true, resultKind: entry.resultKind, workKind: entry.workKind }
9297
+ });
9298
+ await apiClient.sendRunnerHeartbeat(entry.projectId, entry.runnerId, entry.repositoryLinkId, "online", {
9299
+ ...runnerHeartbeatMetadata(),
9300
+ preferenceMessage: "Pending result finalization replayed."
9301
+ }).catch(() => void 0);
9302
+ }
9303
+ return { status: "completed", workItem, response };
9304
+ }
9305
+ async function submitDurableResultMutation(apiClient, entry) {
9306
+ if (entry.resultKind === "assistantResult") {
9307
+ return apiClient.submitAssistantResult(entry.projectId, entry.workItemId, entry.result);
9308
+ }
9309
+ if (entry.resultKind === "impactPreviewResult") {
9310
+ return apiClient.submitImpactPreviewResult(entry.projectId, entry.workItemId, entry.result);
9311
+ }
9312
+ if (entry.resultKind === "issueDiagnosisResult") {
9313
+ return apiClient.submitIssueDiagnosisResult(entry.projectId, entry.workItemId, entry.result);
9314
+ }
9315
+ if (entry.resultKind === "securityPostureScanResult") {
9316
+ return apiClient.submitSecurityPostureScanResult(entry.projectId, entry.workItemId, entry.result);
9317
+ }
9318
+ if (entry.resultKind === "appEvaluationScanResult") {
9319
+ return apiClient.submitAppEvaluationScanResult(entry.projectId, entry.workItemId, entry.result);
9320
+ }
9321
+ if (entry.resultKind === "projectContextRefreshResult") {
9322
+ return apiClient.submitProjectContextRefreshResult(entry.projectId, entry.workItemId, entry.result);
9323
+ }
9324
+ if (entry.resultKind === "testQualityScanResult") {
9325
+ return apiClient.submitTestQualityScanResult(entry.projectId, entry.workItemId, entry.result);
9326
+ }
9327
+ if (entry.resultKind === "implementationTestGateResult") {
9328
+ return apiClient.submitImplementationTestGateResult(entry.projectId, entry.workItemId, entry.result);
9329
+ }
9330
+ return apiClient.submitImplementationVerificationResult(entry.projectId, entry.workItemId, entry.result);
9331
+ }
9332
+ function durableResultResponseWorkItem(response) {
9333
+ if (response && typeof response === "object" && "workItem" in response) {
9334
+ return response.workItem;
9335
+ }
9336
+ throw new Error("Runner result finalization response did not include a work item.");
9337
+ }
9338
+ function runnerResultFinalizationLabel(entry) {
9339
+ if (entry.workKind === "appEvaluationScan") return "app evaluation scan";
9340
+ if (entry.workKind === "securityPostureScan") return "security posture scan";
9341
+ if (entry.workKind === "projectContextRefresh") return "project context refresh";
9342
+ if (entry.workKind === "implementationVerification") return "implementation verification";
9343
+ if (entry.workKind === "testQualityScan") return "test quality scan";
9344
+ if (entry.workKind === "implementationTestGate") return "implementation test gate";
9345
+ if (entry.workKind === "issueDiagnosis") return "issue diagnosis";
9346
+ if (entry.workKind === "impactPreview") return "impact preview";
9347
+ return "assistant answer";
9348
+ }
9349
+ async function submitStagedDurableResultFinalization(apiClient, input) {
9350
+ const entry = createDurableResultFinalizationEntry(input);
9351
+ await upsertDurableResultFinalizationEntry(entry);
9352
+ return submitDurableResultFinalizationEntry(apiClient, entry);
9353
+ }
9354
+ async function submitPrimaryRunnerResult(apiClient, input) {
9355
+ const replay = await submitStagedDurableResultFinalization(apiClient, input);
9356
+ if (replay.status === "failed") {
9357
+ if (!replay.retryable) {
9358
+ throw new Error(replay.message);
9359
+ }
9360
+ return void 0;
9361
+ }
9362
+ return replay.response;
9363
+ }
9364
+ function pendingSessionTelemetry(sessionContext) {
9365
+ return {
9366
+ sessionPolicy: sessionContext.policy,
9367
+ sessionDecision: sessionContext.decision,
9368
+ sessionDecisionReason: sessionContext.reason,
9369
+ ...sessionContext.toolSession ? { toolSessionId: sessionContext.toolSession.toolSessionId } : {},
9370
+ ...sessionContext.toolSession?.sessionGroupKey ? { sessionGroupKey: sessionContext.toolSession.sessionGroupKey } : {}
9371
+ };
9372
+ }
9373
+ async function finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status, toolResult, workItemId }) {
9374
+ const settlements = await Promise.allSettled([
9375
+ finalizeToolSession({
9376
+ apiClient,
9377
+ projectId,
9378
+ status,
9379
+ runnerId,
9380
+ workItemId,
9381
+ stdout: toolResult.stdout,
9382
+ ...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
9383
+ ...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
9384
+ ...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
9385
+ ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
9386
+ ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
9387
+ })
9388
+ ]);
9389
+ logRejectedSettlements("finalize runner result tool session", settlements);
9390
+ }
7765
9391
  async function finalizeBrainGenerationWork({
7766
9392
  apiClient,
7767
9393
  durationMs,
@@ -7787,55 +9413,97 @@ ${toolResult.stderr}`);
7787
9413
  generationError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
7788
9414
  }
7789
9415
  const finalStatus = artifacts ? "completed" : "failed";
7790
- const updatedToolSession = await finalizeToolSession({
7791
- apiClient,
7792
- projectId,
7793
- status: finalStatus,
7794
- runnerId,
7795
- workItemId: workItem.workItemId,
7796
- stdout: toolResult.stdout,
7797
- ...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
7798
- ...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
7799
- ...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
7800
- ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
7801
- ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
7802
- });
7803
9416
  const sessionTelemetry = {
7804
9417
  sessionPolicy: sessionContext.policy,
7805
9418
  sessionDecision: sessionContext.decision,
7806
9419
  sessionDecisionReason: sessionContext.reason,
7807
- ...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
7808
- ...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
9420
+ ...sessionContext.toolSession ? { toolSessionId: sessionContext.toolSession.toolSessionId } : {},
9421
+ ...sessionContext.toolSession?.sessionGroupKey ? { sessionGroupKey: sessionContext.toolSession.sessionGroupKey } : {}
7809
9422
  };
7810
9423
  if (artifacts) {
7811
9424
  const completionMessage = workItem.workKind === "planRevision" ? `${toolName} returned a revised plan for review.` : `${toolName} generated ${artifacts.length} brain artifact${artifacts.length === 1 ? "" : "s"}.`;
7812
- const result = await apiClient.submitBrainGenerationResult(projectId, workItem.workItemId, {
9425
+ const resultMutation = {
7813
9426
  status: "completed",
7814
9427
  runnerId,
7815
- idempotencyKey: `generation_${workItem.workItemId}_${randomUUID()}`,
9428
+ idempotencyKey: `generation_${workItem.workItemId}_${workItem.attempt}_${randomUUID()}`,
7816
9429
  artifacts,
7817
9430
  tool: toolName,
7818
9431
  durationMs,
7819
9432
  ...sessionTelemetry,
7820
9433
  message: completionMessage
9434
+ };
9435
+ const entry = createBrainGenerationFinalizationEntry({
9436
+ accountId: workItem.accountId,
9437
+ projectId,
9438
+ repositoryLinkId,
9439
+ runnerId,
9440
+ workItemId: workItem.workItemId,
9441
+ workKind: brainGenerationFinalizationWorkKind(workItem.workKind),
9442
+ attempt: workItem.attempt,
9443
+ idempotencyKey: resultMutation.idempotencyKey,
9444
+ result: resultMutation
7821
9445
  });
9446
+ await upsertBrainGenerationFinalizationEntry(entry);
9447
+ const replay = await submitBrainGenerationFinalizationEntry(apiClient, entry);
9448
+ if (replay.status === "failed") {
9449
+ if (!replay.retryable) {
9450
+ throw new Error(replay.message);
9451
+ }
9452
+ return { status: "failed", exitCode: 1 };
9453
+ }
9454
+ const toolSessionSettlement = await Promise.allSettled([
9455
+ finalizeToolSession({
9456
+ apiClient,
9457
+ projectId,
9458
+ status: finalStatus,
9459
+ runnerId,
9460
+ workItemId: workItem.workItemId,
9461
+ stdout: toolResult.stdout,
9462
+ ...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
9463
+ ...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
9464
+ ...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
9465
+ ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
9466
+ ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
9467
+ })
9468
+ ]);
9469
+ logRejectedSettlements("finalize generation tool session", toolSessionSettlement);
7822
9470
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
7823
9471
  status: "completed",
7824
9472
  summary: completionMessage,
7825
- idempotencyKey: `runner_milestone_generation_completed_${workItem.workItemId}_${result.workItem.idempotencyKey}`,
7826
- metadata: { tool: toolName, durationMs, artifactCount: result.documents.length, verificationSummary: "Generated artifacts were accepted by the Amistio API for review." }
9473
+ idempotencyKey: `runner_milestone_generation_completed_${workItem.workItemId}_${entry.idempotencyKey}`,
9474
+ metadata: { tool: toolName, durationMs, artifactCount: replay.documentCount, verificationSummary: "Generated artifacts were accepted by the Amistio API for review." }
7827
9475
  });
7828
- await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
7829
- console.log(workItem.workKind === "planRevision" ? "Revised plan returned for review." : `Generated ${result.documents.length} brain artifact${result.documents.length === 1 ? "" : "s"} for review.`);
9476
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig)).catch((error) => {
9477
+ console.error(`send generation completion heartbeat failed: ${errorMessage3(error)}`);
9478
+ });
9479
+ console.log(workItem.workKind === "planRevision" ? "Revised plan returned for review." : `Generated ${replay.documentCount} brain artifact${replay.documentCount === 1 ? "" : "s"} for review.`);
7830
9480
  return { status: "completed", exitCode: 0 };
7831
9481
  }
9482
+ const updatedToolSession = await finalizeToolSession({
9483
+ apiClient,
9484
+ projectId,
9485
+ status: finalStatus,
9486
+ runnerId,
9487
+ workItemId: workItem.workItemId,
9488
+ stdout: toolResult.stdout,
9489
+ ...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
9490
+ ...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
9491
+ ...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
9492
+ ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
9493
+ ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
9494
+ });
9495
+ const failedSessionTelemetry = {
9496
+ ...sessionTelemetry,
9497
+ ...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
9498
+ ...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
9499
+ };
7832
9500
  const failedResult = await apiClient.submitBrainGenerationResult(projectId, workItem.workItemId, {
7833
9501
  status: "failed",
7834
9502
  runnerId,
7835
9503
  idempotencyKey: `generation_${workItem.workItemId}_${randomUUID()}`,
7836
9504
  tool: toolName,
7837
9505
  durationMs,
7838
- ...sessionTelemetry,
9506
+ ...failedSessionTelemetry,
7839
9507
  message: `${toolName} did not produce valid brain artifacts.`,
7840
9508
  ...generationError ? { error: generationError } : {}
7841
9509
  });
@@ -7849,6 +9517,9 @@ ${toolResult.stderr}`);
7849
9517
  console.error(generationError ?? "Local runner generation failed.");
7850
9518
  return { status: "failed", exitCode: toolResult.exitCode || 1 };
7851
9519
  }
9520
+ function brainGenerationFinalizationWorkKind(workKind) {
9521
+ return workKind === "planRevision" ? "planRevision" : "brainGeneration";
9522
+ }
7852
9523
  async function finalizeAssistantQuestionWork({
7853
9524
  apiClient,
7854
9525
  durationMs,
@@ -7874,28 +9545,9 @@ ${toolResult.stderr}`);
7874
9545
  answerError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
7875
9546
  }
7876
9547
  const finalStatus = answerResult ? "completed" : "failed";
7877
- const updatedToolSession = await finalizeToolSession({
7878
- apiClient,
7879
- projectId,
7880
- status: finalStatus,
7881
- runnerId,
7882
- workItemId: workItem.workItemId,
7883
- stdout: toolResult.stdout,
7884
- ...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
7885
- ...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
7886
- ...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
7887
- ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
7888
- ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
7889
- });
7890
- const sessionTelemetry = {
7891
- sessionPolicy: sessionContext.policy,
7892
- sessionDecision: sessionContext.decision,
7893
- sessionDecisionReason: sessionContext.reason,
7894
- ...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
7895
- ...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
7896
- };
9548
+ const sessionTelemetry = pendingSessionTelemetry(sessionContext);
7897
9549
  if (answerResult) {
7898
- const result = await apiClient.submitAssistantResult(projectId, workItem.workItemId, {
9550
+ const resultMutation = {
7899
9551
  status: "completed",
7900
9552
  runnerId,
7901
9553
  idempotencyKey: `assistant_${workItem.workItemId}_${randomUUID()}`,
@@ -7906,7 +9558,21 @@ ${toolResult.stderr}`);
7906
9558
  durationMs,
7907
9559
  ...sessionTelemetry,
7908
9560
  message: `${toolName} returned a project knowledge answer.`
9561
+ };
9562
+ const result = await submitPrimaryRunnerResult(apiClient, {
9563
+ accountId: workItem.accountId,
9564
+ projectId,
9565
+ repositoryLinkId,
9566
+ runnerId,
9567
+ workItemId: workItem.workItemId,
9568
+ workKind: "assistantQuestion",
9569
+ resultKind: "assistantResult",
9570
+ attempt: workItem.attempt,
9571
+ idempotencyKey: resultMutation.idempotencyKey,
9572
+ result: resultMutation
7909
9573
  });
9574
+ if (!result) return { status: "failed", exitCode: 1 };
9575
+ await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
7910
9576
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
7911
9577
  status: "completed",
7912
9578
  summary: `${toolName} returned a project knowledge answer.`,
@@ -7917,7 +9583,7 @@ ${toolResult.stderr}`);
7917
9583
  console.log("Project knowledge answer returned.");
7918
9584
  return { status: "completed", exitCode: 0 };
7919
9585
  }
7920
- const failedResult = await apiClient.submitAssistantResult(projectId, workItem.workItemId, {
9586
+ const failedMutation = {
7921
9587
  status: "failed",
7922
9588
  runnerId,
7923
9589
  idempotencyKey: `assistant_${workItem.workItemId}_${randomUUID()}`,
@@ -7926,7 +9592,21 @@ ${toolResult.stderr}`);
7926
9592
  ...sessionTelemetry,
7927
9593
  message: `${toolName} did not produce a valid project knowledge answer.`,
7928
9594
  ...answerError ? { error: answerError } : {}
9595
+ };
9596
+ const failedResult = await submitPrimaryRunnerResult(apiClient, {
9597
+ accountId: workItem.accountId,
9598
+ projectId,
9599
+ repositoryLinkId,
9600
+ runnerId,
9601
+ workItemId: workItem.workItemId,
9602
+ workKind: "assistantQuestion",
9603
+ resultKind: "assistantResult",
9604
+ attempt: workItem.attempt,
9605
+ idempotencyKey: failedMutation.idempotencyKey,
9606
+ result: failedMutation
7929
9607
  });
9608
+ if (!failedResult) return { status: "failed", exitCode: 1 };
9609
+ await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
7930
9610
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
7931
9611
  status: "failed",
7932
9612
  summary: answerError ?? `${toolName} did not produce a valid project knowledge answer.`,
@@ -7963,29 +9643,10 @@ ${toolResult.stderr}`);
7963
9643
  previewError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
7964
9644
  }
7965
9645
  const finalStatus = report ? "completed" : "failed";
7966
- const updatedToolSession = await finalizeToolSession({
7967
- apiClient,
7968
- projectId,
7969
- status: finalStatus,
7970
- runnerId,
7971
- workItemId: workItem.workItemId,
7972
- stdout: toolResult.stdout,
7973
- ...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
7974
- ...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
7975
- ...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
7976
- ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
7977
- ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
7978
- });
7979
- const sessionTelemetry = {
7980
- sessionPolicy: sessionContext.policy,
7981
- sessionDecision: sessionContext.decision,
7982
- sessionDecisionReason: sessionContext.reason,
7983
- ...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
7984
- ...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
7985
- };
9646
+ const sessionTelemetry = pendingSessionTelemetry(sessionContext);
7986
9647
  if (report) {
7987
9648
  const metadata = await readProjectLink(root).catch(() => void 0);
7988
- const result = await apiClient.submitImpactPreviewResult(projectId, workItem.workItemId, {
9649
+ const resultMutation = {
7989
9650
  status: "completed",
7990
9651
  runnerId,
7991
9652
  idempotencyKey: `impact_${workItem.workItemId}_${randomUUID()}`,
@@ -7997,7 +9658,21 @@ ${toolResult.stderr}`);
7997
9658
  durationMs,
7998
9659
  ...sessionTelemetry,
7999
9660
  message: `${toolName} returned an implementation impact preview.`
9661
+ };
9662
+ const result = await submitPrimaryRunnerResult(apiClient, {
9663
+ accountId: workItem.accountId,
9664
+ projectId,
9665
+ repositoryLinkId,
9666
+ runnerId,
9667
+ workItemId: workItem.workItemId,
9668
+ workKind: "impactPreview",
9669
+ resultKind: "impactPreviewResult",
9670
+ attempt: workItem.attempt,
9671
+ idempotencyKey: resultMutation.idempotencyKey,
9672
+ result: resultMutation
8000
9673
  });
9674
+ if (!result) return { status: "failed", exitCode: 1 };
9675
+ await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
8001
9676
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
8002
9677
  status: "completed",
8003
9678
  summary: `${toolName} returned an implementation impact preview.`,
@@ -8008,7 +9683,7 @@ ${toolResult.stderr}`);
8008
9683
  console.log(result.implementationWorkItem ? "Impact preview returned; implementation work is now queued." : "Impact preview returned.");
8009
9684
  return { status: "completed", exitCode: 0 };
8010
9685
  }
8011
- const failedResult = await apiClient.submitImpactPreviewResult(projectId, workItem.workItemId, {
9686
+ const failedMutation = {
8012
9687
  status: "failed",
8013
9688
  runnerId,
8014
9689
  idempotencyKey: `impact_${workItem.workItemId}_${randomUUID()}`,
@@ -8017,7 +9692,21 @@ ${toolResult.stderr}`);
8017
9692
  ...sessionTelemetry,
8018
9693
  message: `${toolName} did not produce a valid impact preview.`,
8019
9694
  ...previewError ? { error: previewError } : {}
9695
+ };
9696
+ const failedResult = await submitPrimaryRunnerResult(apiClient, {
9697
+ accountId: workItem.accountId,
9698
+ projectId,
9699
+ repositoryLinkId,
9700
+ runnerId,
9701
+ workItemId: workItem.workItemId,
9702
+ workKind: "impactPreview",
9703
+ resultKind: "impactPreviewResult",
9704
+ attempt: workItem.attempt,
9705
+ idempotencyKey: failedMutation.idempotencyKey,
9706
+ result: failedMutation
8020
9707
  });
9708
+ if (!failedResult) return { status: "failed", exitCode: 1 };
9709
+ await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
8021
9710
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
8022
9711
  status: "failed",
8023
9712
  summary: previewError ?? `${toolName} did not produce a valid impact preview.`,
@@ -8053,28 +9742,9 @@ ${toolResult.stderr}`);
8053
9742
  diagnosisError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
8054
9743
  }
8055
9744
  const finalStatus = diagnosis ? "completed" : "failed";
8056
- const updatedToolSession = await finalizeToolSession({
8057
- apiClient,
8058
- projectId,
8059
- status: finalStatus,
8060
- runnerId,
8061
- workItemId: workItem.workItemId,
8062
- stdout: toolResult.stdout,
8063
- ...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
8064
- ...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
8065
- ...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
8066
- ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
8067
- ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
8068
- });
8069
- const sessionTelemetry = {
8070
- sessionPolicy: sessionContext.policy,
8071
- sessionDecision: sessionContext.decision,
8072
- sessionDecisionReason: sessionContext.reason,
8073
- ...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
8074
- ...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
8075
- };
9745
+ const sessionTelemetry = pendingSessionTelemetry(sessionContext);
8076
9746
  if (diagnosis) {
8077
- const result = await apiClient.submitIssueDiagnosisResult(projectId, workItem.workItemId, {
9747
+ const resultMutation = {
8078
9748
  status: "completed",
8079
9749
  runnerId,
8080
9750
  idempotencyKey: `issue_${workItem.workItemId}_${randomUUID()}`,
@@ -8083,7 +9753,21 @@ ${toolResult.stderr}`);
8083
9753
  durationMs,
8084
9754
  ...sessionTelemetry,
8085
9755
  message: `${toolName} returned an issue root-cause analysis.`
9756
+ };
9757
+ const result = await submitPrimaryRunnerResult(apiClient, {
9758
+ accountId: workItem.accountId,
9759
+ projectId,
9760
+ repositoryLinkId,
9761
+ runnerId,
9762
+ workItemId: workItem.workItemId,
9763
+ workKind: "issueDiagnosis",
9764
+ resultKind: "issueDiagnosisResult",
9765
+ attempt: workItem.attempt,
9766
+ idempotencyKey: resultMutation.idempotencyKey,
9767
+ result: resultMutation
8086
9768
  });
9769
+ if (!result) return { status: "failed", exitCode: 1 };
9770
+ await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
8087
9771
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
8088
9772
  status: "completed",
8089
9773
  summary: `${toolName} returned an issue root-cause analysis.`,
@@ -8094,7 +9778,7 @@ ${toolResult.stderr}`);
8094
9778
  console.log("Issue diagnosis returned for approval.");
8095
9779
  return { status: "completed", exitCode: 0 };
8096
9780
  }
8097
- const failedResult = await apiClient.submitIssueDiagnosisResult(projectId, workItem.workItemId, {
9781
+ const failedMutation = {
8098
9782
  status: "failed",
8099
9783
  runnerId,
8100
9784
  idempotencyKey: `issue_${workItem.workItemId}_${randomUUID()}`,
@@ -8103,7 +9787,21 @@ ${toolResult.stderr}`);
8103
9787
  ...sessionTelemetry,
8104
9788
  message: `${toolName} did not produce a valid issue diagnosis.`,
8105
9789
  ...diagnosisError ? { error: diagnosisError } : {}
9790
+ };
9791
+ const failedResult = await submitPrimaryRunnerResult(apiClient, {
9792
+ accountId: workItem.accountId,
9793
+ projectId,
9794
+ repositoryLinkId,
9795
+ runnerId,
9796
+ workItemId: workItem.workItemId,
9797
+ workKind: "issueDiagnosis",
9798
+ resultKind: "issueDiagnosisResult",
9799
+ attempt: workItem.attempt,
9800
+ idempotencyKey: failedMutation.idempotencyKey,
9801
+ result: failedMutation
8106
9802
  });
9803
+ if (!failedResult) return { status: "failed", exitCode: 1 };
9804
+ await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
8107
9805
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
8108
9806
  status: "failed",
8109
9807
  summary: diagnosisError ?? `${toolName} did not produce a valid issue diagnosis.`,
@@ -8139,28 +9837,9 @@ ${toolResult.stderr}`);
8139
9837
  scanError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
8140
9838
  }
8141
9839
  const finalStatus = scanResult ? "completed" : "failed";
8142
- const updatedToolSession = await finalizeToolSession({
8143
- apiClient,
8144
- projectId,
8145
- status: finalStatus,
8146
- runnerId,
8147
- workItemId: workItem.workItemId,
8148
- stdout: toolResult.stdout,
8149
- ...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
8150
- ...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
8151
- ...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
8152
- ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
8153
- ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
8154
- });
8155
- const sessionTelemetry = {
8156
- sessionPolicy: sessionContext.policy,
8157
- sessionDecision: sessionContext.decision,
8158
- sessionDecisionReason: sessionContext.reason,
8159
- ...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
8160
- ...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
8161
- };
9840
+ const sessionTelemetry = pendingSessionTelemetry(sessionContext);
8162
9841
  if (scanResult) {
8163
- const result = await apiClient.submitSecurityPostureScanResult(projectId, workItem.workItemId, {
9842
+ const resultMutation = {
8164
9843
  status: "completed",
8165
9844
  runnerId,
8166
9845
  idempotencyKey: `security_${workItem.workItemId}_${randomUUID()}`,
@@ -8169,7 +9848,21 @@ ${toolResult.stderr}`);
8169
9848
  durationMs,
8170
9849
  ...sessionTelemetry,
8171
9850
  message: `${toolName} returned a security posture scan.`
9851
+ };
9852
+ const result = await submitPrimaryRunnerResult(apiClient, {
9853
+ accountId: workItem.accountId,
9854
+ projectId,
9855
+ repositoryLinkId,
9856
+ runnerId,
9857
+ workItemId: workItem.workItemId,
9858
+ workKind: "securityPostureScan",
9859
+ resultKind: "securityPostureScanResult",
9860
+ attempt: workItem.attempt,
9861
+ idempotencyKey: resultMutation.idempotencyKey,
9862
+ result: resultMutation
8172
9863
  });
9864
+ if (!result) return { status: "failed", exitCode: 1 };
9865
+ await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
8173
9866
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
8174
9867
  status: "completed",
8175
9868
  summary: `${toolName} returned a security posture scan.`,
@@ -8180,7 +9873,7 @@ ${toolResult.stderr}`);
8180
9873
  console.log("Security posture scan returned for review.");
8181
9874
  return { status: "completed", exitCode: 0 };
8182
9875
  }
8183
- const failedResult = await apiClient.submitSecurityPostureScanResult(projectId, workItem.workItemId, {
9876
+ const failedMutation = {
8184
9877
  status: "failed",
8185
9878
  runnerId,
8186
9879
  idempotencyKey: `security_${workItem.workItemId}_${randomUUID()}`,
@@ -8189,7 +9882,21 @@ ${toolResult.stderr}`);
8189
9882
  ...sessionTelemetry,
8190
9883
  message: `${toolName} did not produce a valid security posture scan.`,
8191
9884
  ...scanError ? { error: scanError } : {}
9885
+ };
9886
+ const failedResult = await submitPrimaryRunnerResult(apiClient, {
9887
+ accountId: workItem.accountId,
9888
+ projectId,
9889
+ repositoryLinkId,
9890
+ runnerId,
9891
+ workItemId: workItem.workItemId,
9892
+ workKind: "securityPostureScan",
9893
+ resultKind: "securityPostureScanResult",
9894
+ attempt: workItem.attempt,
9895
+ idempotencyKey: failedMutation.idempotencyKey,
9896
+ result: failedMutation
8192
9897
  });
9898
+ if (!failedResult) return { status: "failed", exitCode: 1 };
9899
+ await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
8193
9900
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
8194
9901
  status: "failed",
8195
9902
  summary: scanError ?? `${toolName} did not produce a valid security posture scan.`,
@@ -8225,28 +9932,9 @@ ${toolResult.stderr}`);
8225
9932
  scanError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
8226
9933
  }
8227
9934
  const finalStatus = scanResult ? "completed" : "failed";
8228
- const updatedToolSession = await finalizeToolSession({
8229
- apiClient,
8230
- projectId,
8231
- status: finalStatus,
8232
- runnerId,
8233
- workItemId: workItem.workItemId,
8234
- stdout: toolResult.stdout,
8235
- ...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
8236
- ...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
8237
- ...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
8238
- ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
8239
- ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
8240
- });
8241
- const sessionTelemetry = {
8242
- sessionPolicy: sessionContext.policy,
8243
- sessionDecision: sessionContext.decision,
8244
- sessionDecisionReason: sessionContext.reason,
8245
- ...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
8246
- ...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
8247
- };
9935
+ const sessionTelemetry = pendingSessionTelemetry(sessionContext);
8248
9936
  if (scanResult) {
8249
- const result = await apiClient.submitAppEvaluationScanResult(projectId, workItem.workItemId, {
9937
+ const resultMutation = {
8250
9938
  status: "completed",
8251
9939
  runnerId,
8252
9940
  idempotencyKey: `app_evaluation_${workItem.workItemId}_${randomUUID()}`,
@@ -8255,7 +9943,21 @@ ${toolResult.stderr}`);
8255
9943
  durationMs,
8256
9944
  ...sessionTelemetry,
8257
9945
  message: `${toolName} returned an app evaluation scan.`
9946
+ };
9947
+ const result = await submitPrimaryRunnerResult(apiClient, {
9948
+ accountId: workItem.accountId,
9949
+ projectId,
9950
+ repositoryLinkId,
9951
+ runnerId,
9952
+ workItemId: workItem.workItemId,
9953
+ workKind: "appEvaluationScan",
9954
+ resultKind: "appEvaluationScanResult",
9955
+ attempt: workItem.attempt,
9956
+ idempotencyKey: resultMutation.idempotencyKey,
9957
+ result: resultMutation
8258
9958
  });
9959
+ if (!result) return { status: "failed", exitCode: 1 };
9960
+ await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
8259
9961
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
8260
9962
  status: "completed",
8261
9963
  summary: `${toolName} returned an app evaluation scan.`,
@@ -8266,7 +9968,7 @@ ${toolResult.stderr}`);
8266
9968
  console.log("App evaluation scan returned for review.");
8267
9969
  return { status: "completed", exitCode: 0 };
8268
9970
  }
8269
- const failedResult = await apiClient.submitAppEvaluationScanResult(projectId, workItem.workItemId, {
9971
+ const failedMutation = {
8270
9972
  status: "failed",
8271
9973
  runnerId,
8272
9974
  idempotencyKey: `app_evaluation_${workItem.workItemId}_${randomUUID()}`,
@@ -8275,7 +9977,21 @@ ${toolResult.stderr}`);
8275
9977
  ...sessionTelemetry,
8276
9978
  message: `${toolName} did not produce a valid app evaluation scan.`,
8277
9979
  ...scanError ? { error: scanError } : {}
9980
+ };
9981
+ const failedResult = await submitPrimaryRunnerResult(apiClient, {
9982
+ accountId: workItem.accountId,
9983
+ projectId,
9984
+ repositoryLinkId,
9985
+ runnerId,
9986
+ workItemId: workItem.workItemId,
9987
+ workKind: "appEvaluationScan",
9988
+ resultKind: "appEvaluationScanResult",
9989
+ attempt: workItem.attempt,
9990
+ idempotencyKey: failedMutation.idempotencyKey,
9991
+ result: failedMutation
8278
9992
  });
9993
+ if (!failedResult) return { status: "failed", exitCode: 1 };
9994
+ await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
8279
9995
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
8280
9996
  status: "failed",
8281
9997
  summary: scanError ?? `${toolName} did not produce a valid app evaluation scan.`,
@@ -8312,28 +10028,9 @@ ${toolResult.stderr}`, { repositoryRoot: executionRoot });
8312
10028
  refreshError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
8313
10029
  }
8314
10030
  const finalStatus = refreshResult ? "completed" : "failed";
8315
- const updatedToolSession = await finalizeToolSession({
8316
- apiClient,
8317
- projectId,
8318
- status: finalStatus,
8319
- runnerId,
8320
- workItemId: workItem.workItemId,
8321
- stdout: toolResult.stdout,
8322
- ...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
8323
- ...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
8324
- ...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
8325
- ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
8326
- ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
8327
- });
8328
- const sessionTelemetry = {
8329
- sessionPolicy: sessionContext.policy,
8330
- sessionDecision: sessionContext.decision,
8331
- sessionDecisionReason: sessionContext.reason,
8332
- ...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
8333
- ...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
8334
- };
10031
+ const sessionTelemetry = pendingSessionTelemetry(sessionContext);
8335
10032
  if (refreshResult) {
8336
- const result = await apiClient.submitProjectContextRefreshResult(projectId, workItem.workItemId, {
10033
+ const resultMutation = {
8337
10034
  status: "completed",
8338
10035
  runnerId,
8339
10036
  idempotencyKey: `project_context_${workItem.workItemId}_${randomUUID()}`,
@@ -8342,7 +10039,21 @@ ${toolResult.stderr}`, { repositoryRoot: executionRoot });
8342
10039
  durationMs,
8343
10040
  ...sessionTelemetry,
8344
10041
  message: `${toolName} returned a project context refresh.`
10042
+ };
10043
+ const result = await submitPrimaryRunnerResult(apiClient, {
10044
+ accountId: workItem.accountId,
10045
+ projectId,
10046
+ repositoryLinkId,
10047
+ runnerId,
10048
+ workItemId: workItem.workItemId,
10049
+ workKind: "projectContextRefresh",
10050
+ resultKind: "projectContextRefreshResult",
10051
+ attempt: workItem.attempt,
10052
+ idempotencyKey: resultMutation.idempotencyKey,
10053
+ result: resultMutation
8345
10054
  });
10055
+ if (!result) return { status: "failed", exitCode: 1 };
10056
+ await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
8346
10057
  const failureSummary = projectContextRefreshSubmissionFailureSummary(result);
8347
10058
  if (failureSummary) {
8348
10059
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
@@ -8365,7 +10076,7 @@ ${toolResult.stderr}`, { repositoryRoot: executionRoot });
8365
10076
  console.log("Project context refresh returned for review.");
8366
10077
  return { status: "completed", exitCode: 0 };
8367
10078
  }
8368
- const failedResult = await apiClient.submitProjectContextRefreshResult(projectId, workItem.workItemId, {
10079
+ const failedMutation = {
8369
10080
  status: "failed",
8370
10081
  runnerId,
8371
10082
  idempotencyKey: `project_context_${workItem.workItemId}_${randomUUID()}`,
@@ -8374,7 +10085,21 @@ ${toolResult.stderr}`, { repositoryRoot: executionRoot });
8374
10085
  ...sessionTelemetry,
8375
10086
  message: `${toolName} did not produce a valid project context refresh.`,
8376
10087
  ...refreshError ? { error: refreshError } : {}
10088
+ };
10089
+ const failedResult = await submitPrimaryRunnerResult(apiClient, {
10090
+ accountId: workItem.accountId,
10091
+ projectId,
10092
+ repositoryLinkId,
10093
+ runnerId,
10094
+ workItemId: workItem.workItemId,
10095
+ workKind: "projectContextRefresh",
10096
+ resultKind: "projectContextRefreshResult",
10097
+ attempt: workItem.attempt,
10098
+ idempotencyKey: failedMutation.idempotencyKey,
10099
+ result: failedMutation
8377
10100
  });
10101
+ if (!failedResult) return { status: "failed", exitCode: 1 };
10102
+ await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
8378
10103
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
8379
10104
  status: "failed",
8380
10105
  summary: refreshError ?? `${toolName} did not produce a valid project context refresh.`,
@@ -8410,28 +10135,9 @@ ${toolResult.stderr}`);
8410
10135
  verificationError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
8411
10136
  }
8412
10137
  const finalStatus = verificationResult ? "completed" : "failed";
8413
- const updatedToolSession = await finalizeToolSession({
8414
- apiClient,
8415
- projectId,
8416
- status: finalStatus,
8417
- runnerId,
8418
- workItemId: workItem.workItemId,
8419
- stdout: toolResult.stdout,
8420
- ...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
8421
- ...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
8422
- ...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
8423
- ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
8424
- ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
8425
- });
8426
- const sessionTelemetry = {
8427
- sessionPolicy: sessionContext.policy,
8428
- sessionDecision: sessionContext.decision,
8429
- sessionDecisionReason: sessionContext.reason,
8430
- ...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
8431
- ...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
8432
- };
10138
+ const sessionTelemetry = pendingSessionTelemetry(sessionContext);
8433
10139
  if (verificationResult) {
8434
- const result = await apiClient.submitImplementationVerificationResult(projectId, workItem.workItemId, {
10140
+ const resultMutation = {
8435
10141
  status: "completed",
8436
10142
  runnerId,
8437
10143
  idempotencyKey: `implementation_verification_${workItem.workItemId}_${randomUUID()}`,
@@ -8440,7 +10146,21 @@ ${toolResult.stderr}`);
8440
10146
  durationMs,
8441
10147
  ...sessionTelemetry,
8442
10148
  message: `${toolName} returned implementation verification proof.`
10149
+ };
10150
+ const result = await submitPrimaryRunnerResult(apiClient, {
10151
+ accountId: workItem.accountId,
10152
+ projectId,
10153
+ repositoryLinkId,
10154
+ runnerId,
10155
+ workItemId: workItem.workItemId,
10156
+ workKind: "implementationVerification",
10157
+ resultKind: "implementationVerificationResult",
10158
+ attempt: workItem.attempt,
10159
+ idempotencyKey: resultMutation.idempotencyKey,
10160
+ result: resultMutation
8443
10161
  });
10162
+ if (!result) return { status: "failed", exitCode: 1 };
10163
+ await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
8444
10164
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
8445
10165
  status: verificationResult.outcome === "verifiedImplemented" ? "completed" : verificationResult.outcome === "verificationBlocked" ? "blocked" : "warning",
8446
10166
  summary: verificationResult.summary,
@@ -8451,7 +10171,7 @@ ${toolResult.stderr}`);
8451
10171
  console.log("Implementation verification returned for review.");
8452
10172
  return { status: "completed", exitCode: 0 };
8453
10173
  }
8454
- const failedResult = await apiClient.submitImplementationVerificationResult(projectId, workItem.workItemId, {
10174
+ const failedMutation = {
8455
10175
  status: "failed",
8456
10176
  runnerId,
8457
10177
  idempotencyKey: `implementation_verification_${workItem.workItemId}_${randomUUID()}`,
@@ -8460,7 +10180,21 @@ ${toolResult.stderr}`);
8460
10180
  ...sessionTelemetry,
8461
10181
  message: `${toolName} did not produce valid implementation verification proof.`,
8462
10182
  ...verificationError ? { error: verificationError } : {}
10183
+ };
10184
+ const failedResult = await submitPrimaryRunnerResult(apiClient, {
10185
+ accountId: workItem.accountId,
10186
+ projectId,
10187
+ repositoryLinkId,
10188
+ runnerId,
10189
+ workItemId: workItem.workItemId,
10190
+ workKind: "implementationVerification",
10191
+ resultKind: "implementationVerificationResult",
10192
+ attempt: workItem.attempt,
10193
+ idempotencyKey: failedMutation.idempotencyKey,
10194
+ result: failedMutation
8463
10195
  });
10196
+ if (!failedResult) return { status: "failed", exitCode: 1 };
10197
+ await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
8464
10198
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
8465
10199
  status: "failed",
8466
10200
  summary: verificationError ?? `${toolName} did not produce valid implementation verification proof.`,
@@ -8471,6 +10205,196 @@ ${toolResult.stderr}`);
8471
10205
  console.error(verificationError ?? "Local runner implementation verification failed.");
8472
10206
  return { status: "failed", exitCode: toolResult.exitCode || 1 };
8473
10207
  }
10208
+ async function finalizeTestQualityScanWork({
10209
+ apiClient,
10210
+ durationMs,
10211
+ projectId,
10212
+ repositoryLinkId,
10213
+ runnerId,
10214
+ sessionContext,
10215
+ toolConfig,
10216
+ toolName,
10217
+ toolResult,
10218
+ workItem
10219
+ }) {
10220
+ let scanResult = void 0;
10221
+ let scanError;
10222
+ if (toolResult.exitCode === 0) {
10223
+ try {
10224
+ scanResult = parseTestQualityScanResult(`${toolResult.stdout}
10225
+ ${toolResult.stderr}`);
10226
+ } catch (error) {
10227
+ scanError = errorMessage3(error);
10228
+ }
10229
+ } else {
10230
+ scanError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
10231
+ }
10232
+ const finalStatus = scanResult ? "completed" : "failed";
10233
+ const sessionTelemetry = pendingSessionTelemetry(sessionContext);
10234
+ if (scanResult) {
10235
+ const resultMutation = {
10236
+ status: "completed",
10237
+ runnerId,
10238
+ idempotencyKey: `test_quality_${workItem.workItemId}_${randomUUID()}`,
10239
+ result: scanResult,
10240
+ tool: toolName,
10241
+ durationMs,
10242
+ ...sessionTelemetry,
10243
+ message: `${toolName} returned a test quality scan.`
10244
+ };
10245
+ const result = await submitPrimaryRunnerResult(apiClient, {
10246
+ accountId: workItem.accountId,
10247
+ projectId,
10248
+ repositoryLinkId,
10249
+ runnerId,
10250
+ workItemId: workItem.workItemId,
10251
+ workKind: "testQualityScan",
10252
+ resultKind: "testQualityScanResult",
10253
+ attempt: workItem.attempt,
10254
+ idempotencyKey: resultMutation.idempotencyKey,
10255
+ result: resultMutation
10256
+ });
10257
+ if (!result) return { status: "failed", exitCode: 1 };
10258
+ await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
10259
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
10260
+ status: scanResult.findings.some((finding) => finding.severity === "critical" || finding.severity === "high") ? "warning" : "completed",
10261
+ summary: scanResult.summary,
10262
+ idempotencyKey: `runner_milestone_test_quality_completed_${workItem.workItemId}_${result.workItem.idempotencyKey}`,
10263
+ metadata: { tool: toolName, durationMs, findingCount: scanResult.findings.length, commandCount: scanResult.commandSummaries.length, verificationSummary: scanResult.verificationPlan.join(" | ") }
10264
+ });
10265
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
10266
+ console.log("Test quality scan returned for review.");
10267
+ return { status: "completed", exitCode: 0 };
10268
+ }
10269
+ const failedMutation = {
10270
+ status: "failed",
10271
+ runnerId,
10272
+ idempotencyKey: `test_quality_${workItem.workItemId}_${randomUUID()}`,
10273
+ tool: toolName,
10274
+ durationMs,
10275
+ ...sessionTelemetry,
10276
+ message: `${toolName} did not produce a valid test quality scan.`,
10277
+ ...scanError ? { error: scanError } : {}
10278
+ };
10279
+ const failedResult = await submitPrimaryRunnerResult(apiClient, {
10280
+ accountId: workItem.accountId,
10281
+ projectId,
10282
+ repositoryLinkId,
10283
+ runnerId,
10284
+ workItemId: workItem.workItemId,
10285
+ workKind: "testQualityScan",
10286
+ resultKind: "testQualityScanResult",
10287
+ attempt: workItem.attempt,
10288
+ idempotencyKey: failedMutation.idempotencyKey,
10289
+ result: failedMutation
10290
+ });
10291
+ if (!failedResult) return { status: "failed", exitCode: 1 };
10292
+ await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
10293
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
10294
+ status: "failed",
10295
+ summary: scanError ?? `${toolName} did not produce a valid test quality scan.`,
10296
+ idempotencyKey: `runner_milestone_test_quality_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
10297
+ metadata: { tool: toolName, durationMs, verificationSummary: "Test quality output did not include valid structured JSON." }
10298
+ });
10299
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
10300
+ console.error(scanError ?? "Local runner test quality scan failed.");
10301
+ return { status: "failed", exitCode: toolResult.exitCode || 1 };
10302
+ }
10303
+ async function finalizeImplementationTestGateWork({
10304
+ apiClient,
10305
+ durationMs,
10306
+ projectId,
10307
+ repositoryLinkId,
10308
+ runnerId,
10309
+ sessionContext,
10310
+ toolConfig,
10311
+ toolName,
10312
+ toolResult,
10313
+ workItem
10314
+ }) {
10315
+ let gateResult = void 0;
10316
+ let gateError;
10317
+ if (toolResult.exitCode === 0) {
10318
+ try {
10319
+ gateResult = parseImplementationTestGateResult(`${toolResult.stdout}
10320
+ ${toolResult.stderr}`);
10321
+ } catch (error) {
10322
+ gateError = errorMessage3(error);
10323
+ }
10324
+ } else {
10325
+ gateError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
10326
+ }
10327
+ const finalStatus = gateResult ? "completed" : "failed";
10328
+ const sessionTelemetry = pendingSessionTelemetry(sessionContext);
10329
+ if (gateResult) {
10330
+ const resultMutation = {
10331
+ status: "completed",
10332
+ runnerId,
10333
+ idempotencyKey: `implementation_test_gate_${workItem.workItemId}_${randomUUID()}`,
10334
+ result: gateResult,
10335
+ tool: toolName,
10336
+ durationMs,
10337
+ ...sessionTelemetry,
10338
+ message: `${toolName} returned an implementation test gate result.`
10339
+ };
10340
+ const result = await submitPrimaryRunnerResult(apiClient, {
10341
+ accountId: workItem.accountId,
10342
+ projectId,
10343
+ repositoryLinkId,
10344
+ runnerId,
10345
+ workItemId: workItem.workItemId,
10346
+ workKind: "implementationTestGate",
10347
+ resultKind: "implementationTestGateResult",
10348
+ attempt: workItem.attempt,
10349
+ idempotencyKey: resultMutation.idempotencyKey,
10350
+ result: resultMutation
10351
+ });
10352
+ if (!result) return { status: "failed", exitCode: 1 };
10353
+ await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
10354
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
10355
+ status: gateResult.outcome === "passed" ? "completed" : gateResult.outcome === "blocked" ? "blocked" : "failed",
10356
+ summary: gateResult.summary,
10357
+ idempotencyKey: `runner_milestone_implementation_test_gate_completed_${workItem.workItemId}_${result.workItem.idempotencyKey}`,
10358
+ metadata: { tool: toolName, durationMs, outcome: gateResult.outcome, findingCount: gateResult.findings.length, commandCount: gateResult.commandSummaries.length, verificationSummary: gateResult.verificationPlan.join(" | ") }
10359
+ });
10360
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
10361
+ console.log("Implementation test gate returned for review.");
10362
+ return { status: gateResult.outcome === "passed" ? "completed" : "failed", exitCode: gateResult.outcome === "passed" ? 0 : 1 };
10363
+ }
10364
+ const failedMutation = {
10365
+ status: "failed",
10366
+ runnerId,
10367
+ idempotencyKey: `implementation_test_gate_${workItem.workItemId}_${randomUUID()}`,
10368
+ tool: toolName,
10369
+ durationMs,
10370
+ ...sessionTelemetry,
10371
+ message: `${toolName} did not produce a valid implementation test gate result.`,
10372
+ ...gateError ? { error: gateError } : {}
10373
+ };
10374
+ const failedResult = await submitPrimaryRunnerResult(apiClient, {
10375
+ accountId: workItem.accountId,
10376
+ projectId,
10377
+ repositoryLinkId,
10378
+ runnerId,
10379
+ workItemId: workItem.workItemId,
10380
+ workKind: "implementationTestGate",
10381
+ resultKind: "implementationTestGateResult",
10382
+ attempt: workItem.attempt,
10383
+ idempotencyKey: failedMutation.idempotencyKey,
10384
+ result: failedMutation
10385
+ });
10386
+ if (!failedResult) return { status: "failed", exitCode: 1 };
10387
+ await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
10388
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
10389
+ status: "failed",
10390
+ summary: gateError ?? `${toolName} did not produce a valid implementation test gate result.`,
10391
+ idempotencyKey: `runner_milestone_implementation_test_gate_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
10392
+ metadata: { tool: toolName, durationMs, verificationSummary: "Implementation test gate output did not include valid structured JSON." }
10393
+ });
10394
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
10395
+ console.error(gateError ?? "Local runner implementation test gate failed.");
10396
+ return { status: "failed", exitCode: toolResult.exitCode || 1 };
10397
+ }
8474
10398
  async function createRunnerWorkPrompt(apiClient, projectId, workItem) {
8475
10399
  if (workItem.workKind === "assistantQuestion") {
8476
10400
  const emptyProjectContext = { maps: [], refreshes: [], misses: [] };
@@ -8527,6 +10451,15 @@ async function createRunnerWorkPrompt(apiClient, projectId, workItem) {
8527
10451
  appEvaluationScan: { documents: documents2 }
8528
10452
  });
8529
10453
  }
10454
+ if (workItem.workKind === "testQualityScan") {
10455
+ const { documents: documents2 } = await apiClient.listBrainDocuments(projectId);
10456
+ return createWorkExecutionPrompt(workItem, {
10457
+ testQualityScan: { documents: documents2 }
10458
+ });
10459
+ }
10460
+ if (workItem.workKind === "implementationTestGate") {
10461
+ return createWorkExecutionPrompt(workItem);
10462
+ }
8530
10463
  if (workItem.workKind === "projectContextRefresh") {
8531
10464
  const emptyProjectContext = { maps: [], refreshes: [], misses: [] };
8532
10465
  const [{ documents: documents2 }, context] = await Promise.all([
@@ -8767,10 +10700,10 @@ function parseReasoningEffort(value) {
8767
10700
  throw new Error(`Expected reasoning effort auto, low, medium, high, or xhigh; received ${value}.`);
8768
10701
  }
8769
10702
  function inferRepoName(root) {
8770
- return path15.basename(path15.resolve(root)) || "repository";
10703
+ return path16.basename(path16.resolve(root)) || "repository";
8771
10704
  }
8772
10705
  function createRepoFingerprint(accountId, projectId, repositoryLinkId) {
8773
- return createHash7("sha256").update(`${accountId}:${projectId}:${repositoryLinkId}`).digest("hex");
10706
+ return createHash8("sha256").update(`${accountId}:${projectId}:${repositoryLinkId}`).digest("hex");
8774
10707
  }
8775
10708
  function defaultApiUrl() {
8776
10709
  const envApiUrl = process.env[AMISTIO_API_URL_ENV]?.trim();
@@ -9014,22 +10947,26 @@ function toRunnerToolCapabilities(tools) {
9014
10947
  supportsGitWorktreeIsolation: true
9015
10948
  }));
9016
10949
  }
9017
- function runnerIsolationCapabilityMetadata() {
10950
+ function runnerIsolationCapabilityMetadata(laneMetadata = {}) {
9018
10951
  return {
9019
10952
  machineId: runnerMachineId(),
9020
10953
  supportedWorkKinds: runnerSupportedWorkKinds,
9021
10954
  supportsBranchIsolation: true,
9022
- supportsGitWorktreeIsolation: true
10955
+ supportsGitWorktreeIsolation: true,
10956
+ ...laneMetadata.claimLaneId ? { claimLaneId: laneMetadata.claimLaneId } : {},
10957
+ ...laneMetadata.maxConcurrentWork !== void 0 ? { maxConcurrentWork: laneMetadata.maxConcurrentWork } : {}
9023
10958
  };
9024
10959
  }
9025
- function runnerHeartbeatMetadata(toolConfig, mode = currentRunnerMode()) {
10960
+ function runnerHeartbeatMetadata(toolConfig, mode = currentRunnerMode(), concurrencyMetadata = { maxConcurrentWork: 1, activeClaimLaneIds: ["default"] }) {
9026
10961
  const modelConfig = toolConfig ? toolConfigModelOptions(toolConfig) : {};
9027
10962
  const effectiveModelConfig = toolConfig?.ready ? modelConfig : {};
9028
10963
  return {
9029
10964
  version: CLI_VERSION,
9030
10965
  mode,
9031
- hostname: os7.hostname(),
10966
+ hostname: os8.hostname(),
9032
10967
  ...runnerIsolationCapabilityMetadata(),
10968
+ maxConcurrentWork: concurrencyMetadata.maxConcurrentWork,
10969
+ activeClaimLaneIds: concurrencyMetadata.activeClaimLaneIds,
9033
10970
  resourceUsage: sampleCurrentRunnerResourceUsage(),
9034
10971
  ...toolConfig?.capabilities ? { capabilities: toolConfig.capabilities } : {},
9035
10972
  ...toolConfig?.requestedTool ? { requestedTool: toolConfig.requestedTool } : {},
@@ -9051,7 +10988,7 @@ function runnerHeartbeatMetadata(toolConfig, mode = currentRunnerMode()) {
9051
10988
  };
9052
10989
  }
9053
10990
  function runnerMachineId() {
9054
- return createHash7("sha256").update(`${os7.hostname()}:${os7.platform()}:${os7.arch()}`).digest("hex").slice(0, 20);
10991
+ return createHash8("sha256").update(`${os8.hostname()}:${os8.platform()}:${os8.arch()}`).digest("hex").slice(0, 20);
9055
10992
  }
9056
10993
  async function delay(milliseconds) {
9057
10994
  await new Promise((resolve) => setTimeout(resolve, milliseconds));