@amistio/cli 0.1.24 → 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/README.md +8 -3
- package/dist/index.js +1915 -249
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -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 =
|
|
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,
|
|
@@ -2526,6 +2942,26 @@ var ApiClient = class {
|
|
|
2526
2942
|
}
|
|
2527
2943
|
);
|
|
2528
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
|
+
}
|
|
2529
2965
|
async request(urlPath, schema, init) {
|
|
2530
2966
|
const response = await fetch(resolveApiUrl(this.options.apiUrl, urlPath), {
|
|
2531
2967
|
...init,
|
|
@@ -2691,6 +3127,115 @@ var brainGenerationFinalizationEntrySchema = z4.object({
|
|
|
2691
3127
|
updatedAt: z4.string().min(1),
|
|
2692
3128
|
lastError: z4.string().optional()
|
|
2693
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);
|
|
2694
3239
|
function defaultResultFinalizationOutboxDir() {
|
|
2695
3240
|
return path5.join(os2.homedir(), ".config", "amistio", "result-finalizations");
|
|
2696
3241
|
}
|
|
@@ -2713,6 +3258,26 @@ function createBrainGenerationFinalizationEntry(input, now = (/* @__PURE__ */ ne
|
|
|
2713
3258
|
updatedAt: now
|
|
2714
3259
|
});
|
|
2715
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
|
+
}
|
|
2716
3281
|
async function upsertBrainGenerationFinalizationEntry(entry, outboxDir = defaultResultFinalizationOutboxDir()) {
|
|
2717
3282
|
await mkdir5(outboxDir, { recursive: true });
|
|
2718
3283
|
const filePath = brainGenerationFinalizationEntryPath(entry, outboxDir);
|
|
@@ -2745,6 +3310,38 @@ async function listPendingBrainGenerationFinalizations(scope, outboxDir = defaul
|
|
|
2745
3310
|
}
|
|
2746
3311
|
return pending.sort((first, second) => Date.parse(first.createdAt) - Date.parse(second.createdAt));
|
|
2747
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
|
+
}
|
|
2748
3345
|
async function markBrainGenerationFinalizationRetry(entry, error, outboxDir = defaultResultFinalizationOutboxDir()) {
|
|
2749
3346
|
const updated = brainGenerationFinalizationEntrySchema.parse({
|
|
2750
3347
|
...entry,
|
|
@@ -2769,6 +3366,30 @@ async function markBrainGenerationFinalizationTerminal(entry, error, outboxDir =
|
|
|
2769
3366
|
async function deleteBrainGenerationFinalizationEntry(entry, outboxDir = defaultResultFinalizationOutboxDir()) {
|
|
2770
3367
|
await rm(brainGenerationFinalizationEntryPath(entry, outboxDir), { force: true });
|
|
2771
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
|
+
}
|
|
2772
3393
|
function brainGenerationFinalizationEntryPath(entry, outboxDir) {
|
|
2773
3394
|
return path5.join(outboxDir, `${brainGenerationFinalizationEntryKey(entry)}.json`);
|
|
2774
3395
|
}
|
|
@@ -2776,6 +3397,13 @@ function brainGenerationFinalizationEntryKey(entry) {
|
|
|
2776
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);
|
|
2777
3398
|
return `brain-generation-${hash}`;
|
|
2778
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
|
+
}
|
|
2779
3407
|
function truncateLocalError(error) {
|
|
2780
3408
|
const trimmed = error.trim();
|
|
2781
3409
|
return trimmed.length > 600 ? `${trimmed.slice(0, 600)}...` : trimmed;
|
|
@@ -4725,6 +5353,10 @@ var projectContextRefreshStart = "AMISTIO_PROJECT_CONTEXT_REFRESH_START";
|
|
|
4725
5353
|
var projectContextRefreshEnd = "AMISTIO_PROJECT_CONTEXT_REFRESH_END";
|
|
4726
5354
|
var implementationVerificationStart = "AMISTIO_IMPLEMENTATION_VERIFICATION_START";
|
|
4727
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";
|
|
4728
5360
|
var projectContextMissingAreaMaxLength = 160;
|
|
4729
5361
|
var projectContextCoverageWarningMaxLength = 300;
|
|
4730
5362
|
var projectContextVerificationPlanMaxLength = 300;
|
|
@@ -4732,6 +5364,94 @@ var projectContextTopLevelWarningMaxLength = 600;
|
|
|
4732
5364
|
var validProjectContextSliceKinds = /* @__PURE__ */ new Set(["overview", "architecture", "domain", "data", "api", "frontend", "backend", "cli", "workflow", "operations", "security", "testing", "unknown"]);
|
|
4733
5365
|
var validProjectContextEntityTypes = /* @__PURE__ */ new Set(["project", "system", "component", "domain", "tool", "decision", "feature", "risk", "team", "workflow", "unknown"]);
|
|
4734
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
|
+
]);
|
|
4735
5455
|
var canonicalProjectContextCitationSources = /* @__PURE__ */ new Map([
|
|
4736
5456
|
["projectbrain", "projectBrain"],
|
|
4737
5457
|
["approvedbrain", "projectBrain"],
|
|
@@ -4756,6 +5476,7 @@ function createImplementationVerificationPrompt(workItem) {
|
|
|
4756
5476
|
`Project ID: ${workItem.projectId}`,
|
|
4757
5477
|
`Implementation verification ID: ${workItem.implementationVerificationId ?? "unknown"}`,
|
|
4758
5478
|
`Source work item ID: ${workItem.sourceWorkItemId ?? "unknown"}`,
|
|
5479
|
+
...autopilotPromptLines(workItem),
|
|
4759
5480
|
"",
|
|
4760
5481
|
"## Verification Request",
|
|
4761
5482
|
"",
|
|
@@ -4789,23 +5510,115 @@ function createImplementationVerificationPrompt(workItem) {
|
|
|
4789
5510
|
"Do not put Markdown fences around the markers. Do not implement or fix anything during this verification pass."
|
|
4790
5511
|
].join("\n");
|
|
4791
5512
|
}
|
|
4792
|
-
function
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
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") {
|
|
4809
5622
|
return createSecurityPostureScanPrompt(workItem, context?.securityPostureScan);
|
|
4810
5623
|
}
|
|
4811
5624
|
if (workItem.workKind === "appEvaluationScan") {
|
|
@@ -4817,6 +5630,12 @@ function createWorkExecutionPrompt(workItem, context) {
|
|
|
4817
5630
|
if (workItem.workKind === "implementationVerification") {
|
|
4818
5631
|
return createImplementationVerificationPrompt(workItem);
|
|
4819
5632
|
}
|
|
5633
|
+
if (workItem.workKind === "testQualityScan") {
|
|
5634
|
+
return createTestQualityScanPrompt(workItem, context?.testQualityScan);
|
|
5635
|
+
}
|
|
5636
|
+
if (workItem.workKind === "implementationTestGate") {
|
|
5637
|
+
return createImplementationTestGatePrompt(workItem);
|
|
5638
|
+
}
|
|
4820
5639
|
return [
|
|
4821
5640
|
"# Amistio Work Execution",
|
|
4822
5641
|
"",
|
|
@@ -4831,6 +5650,7 @@ function createWorkExecutionPrompt(workItem, context) {
|
|
|
4831
5650
|
`Implementation scope: ${workItem.implementationScopeId ?? workItem.controllingAdrId ?? "work-item"}`,
|
|
4832
5651
|
`Execution branch: ${workItem.executionBranch ?? "managed by Amistio CLI"}`,
|
|
4833
5652
|
`Execution worktree key: ${workItem.executionWorktreeKey ?? "managed by Amistio CLI"}`,
|
|
5653
|
+
...autopilotPromptLines(workItem),
|
|
4834
5654
|
"",
|
|
4835
5655
|
"## Rules",
|
|
4836
5656
|
"",
|
|
@@ -4845,6 +5665,27 @@ function createWorkExecutionPrompt(workItem, context) {
|
|
|
4845
5665
|
"- Run relevant verification commands when feasible and summarize results."
|
|
4846
5666
|
].join("\n");
|
|
4847
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
|
+
}
|
|
4848
5689
|
function createProjectContextRefreshPrompt(workItem, context) {
|
|
4849
5690
|
const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 24).map((document) => [
|
|
4850
5691
|
`### ${document.title}`,
|
|
@@ -5016,6 +5857,8 @@ function createAppEvaluationScanPrompt(workItem, context) {
|
|
|
5016
5857
|
"- Check app verification health: lint, typecheck, test, build, CI references, flaky or missing gates.",
|
|
5017
5858
|
"- Check product and docs drift between approved context, plans, prompts, and current implementation.",
|
|
5018
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.",
|
|
5019
5862
|
"- Check missing memory or workflow updates when repeated lessons or operational rules are visible.",
|
|
5020
5863
|
"- Check release readiness, UX, accessibility, performance, reliability, and security-posture follow-through at a summary level.",
|
|
5021
5864
|
"",
|
|
@@ -5028,6 +5871,10 @@ function createAppEvaluationScanPrompt(workItem, context) {
|
|
|
5028
5871
|
"## Output Contract",
|
|
5029
5872
|
"",
|
|
5030
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.",
|
|
5031
5878
|
"",
|
|
5032
5879
|
appEvaluationStart,
|
|
5033
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"]}',
|
|
@@ -5358,7 +6205,8 @@ function parseAppEvaluationScanResult(output) {
|
|
|
5358
6205
|
}
|
|
5359
6206
|
const payload = output.slice(start + appEvaluationStart.length, end).trim();
|
|
5360
6207
|
const parsed = JSON.parse(stripJsonFence(payload));
|
|
5361
|
-
|
|
6208
|
+
const normalized = normalizeAppEvaluationScanResultPaths(normalizeAppEvaluationScanResultEnums(parsed));
|
|
6209
|
+
return appEvaluationScanResultSchema.parse(normalized);
|
|
5362
6210
|
}
|
|
5363
6211
|
function parseProjectContextRefreshResult(output, options = {}) {
|
|
5364
6212
|
const start = output.indexOf(projectContextRefreshStart);
|
|
@@ -5381,6 +6229,26 @@ function parseImplementationVerificationResult(output) {
|
|
|
5381
6229
|
const parsed = JSON.parse(stripJsonFence(payload));
|
|
5382
6230
|
return implementationVerificationResultSchema.parse(parsed);
|
|
5383
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
|
+
}
|
|
5384
6252
|
function projectContextRefreshSubmissionFailureSummary(result) {
|
|
5385
6253
|
if (result.refresh.status !== "failed" && result.workItem.status !== "failed") {
|
|
5386
6254
|
return void 0;
|
|
@@ -5482,6 +6350,75 @@ function normalizeProjectContextRefreshEnums(value) {
|
|
|
5482
6350
|
}
|
|
5483
6351
|
return normalized;
|
|
5484
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
|
+
}
|
|
5485
6422
|
function normalizeProjectContextSliceKind(value) {
|
|
5486
6423
|
if (typeof value !== "string") {
|
|
5487
6424
|
return "unknown";
|
|
@@ -6188,6 +7125,7 @@ function buildBackgroundRunnerArgs(options) {
|
|
|
6188
7125
|
if (options.maxIterations !== void 0) {
|
|
6189
7126
|
args.push("--max-iterations", String(options.maxIterations));
|
|
6190
7127
|
}
|
|
7128
|
+
args.push("--max-concurrent-work", String(options.maxConcurrentWork));
|
|
6191
7129
|
args.push("--max-preflight-attempts", String(options.maxPreflightAttempts));
|
|
6192
7130
|
args.push("--tool-timeout-seconds", String(options.toolTimeoutSeconds));
|
|
6193
7131
|
if (!options.stream) {
|
|
@@ -6271,10 +7209,12 @@ function truncateProcessOutput(value) {
|
|
|
6271
7209
|
|
|
6272
7210
|
// src/git-worktree.ts
|
|
6273
7211
|
import { execFile as execFile5 } from "node:child_process";
|
|
6274
|
-
import { mkdir as mkdir10, stat as stat5 } from "node:fs/promises";
|
|
7212
|
+
import { copyFile, lstat, mkdir as mkdir10, readdir as readdir6, stat as stat5 } from "node:fs/promises";
|
|
6275
7213
|
import path14 from "node:path";
|
|
6276
7214
|
import { promisify as promisify5 } from "node:util";
|
|
6277
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$/;
|
|
6278
7218
|
function needsGitWorktreeIsolation(workItem) {
|
|
6279
7219
|
return (workItem.workKind ?? "implementation") === "implementation";
|
|
6280
7220
|
}
|
|
@@ -6299,7 +7239,8 @@ async function prepareGitWorktreeIsolation(rootDir, workItem) {
|
|
|
6299
7239
|
const worktreePath = localWorktreePath(repoRoot, identity.worktreeKey);
|
|
6300
7240
|
if (await pathExists(worktreePath)) {
|
|
6301
7241
|
await assertExistingWorktree(worktreePath, identity.branch);
|
|
6302
|
-
|
|
7242
|
+
const preparedLocalEnvironmentFileCount2 = await prepareLocalWorktreeEnvironment(repoRoot, worktreePath);
|
|
7243
|
+
return { ...identity, baseRevision, worktreePath, ...preparedLocalEnvironmentFileCount2 ? { preparedLocalEnvironmentFileCount: preparedLocalEnvironmentFileCount2 } : {} };
|
|
6303
7244
|
}
|
|
6304
7245
|
await mkdir10(path14.dirname(worktreePath), { recursive: true });
|
|
6305
7246
|
const branchExists = await gitCommandSucceeds(repoRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${identity.branch}`]);
|
|
@@ -6307,7 +7248,8 @@ async function prepareGitWorktreeIsolation(rootDir, workItem) {
|
|
|
6307
7248
|
await gitOutput(repoRoot, worktreeArgs).catch((error) => {
|
|
6308
7249
|
throw new Error(`Could not create Git worktree ${identity.worktreeKey} on ${identity.branch}: ${errorMessage2(error)}`);
|
|
6309
7250
|
});
|
|
6310
|
-
|
|
7251
|
+
const preparedLocalEnvironmentFileCount = await prepareLocalWorktreeEnvironment(repoRoot, worktreePath);
|
|
7252
|
+
return { ...identity, baseRevision, worktreePath, ...preparedLocalEnvironmentFileCount ? { preparedLocalEnvironmentFileCount } : {} };
|
|
6311
7253
|
}
|
|
6312
7254
|
function localWorktreePath(repoRoot, worktreeKey) {
|
|
6313
7255
|
const repoName = path14.basename(repoRoot);
|
|
@@ -6334,6 +7276,55 @@ async function assertBaseRevision(repoRoot, baseRevision, currentHead) {
|
|
|
6334
7276
|
throw new Error(`Work item base revision ${baseRevision} is not an ancestor of ${currentHead}; refresh the work item before implementation.`);
|
|
6335
7277
|
}
|
|
6336
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
|
+
}
|
|
6337
7328
|
async function gitOutput(cwd, args) {
|
|
6338
7329
|
const { stdout } = await execFileAsync5("git", args, { cwd, maxBuffer: 1024 * 1024 });
|
|
6339
7330
|
return stdout.trim();
|
|
@@ -6356,6 +7347,12 @@ function slugify(value) {
|
|
|
6356
7347
|
function errorMessage2(error) {
|
|
6357
7348
|
return error instanceof Error ? error.message : String(error);
|
|
6358
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
|
+
}
|
|
6359
7356
|
|
|
6360
7357
|
// src/implementation-handoff.ts
|
|
6361
7358
|
import { execFile as execFile6 } from "node:child_process";
|
|
@@ -6375,9 +7372,17 @@ async function completeImplementationHandoff(input) {
|
|
|
6375
7372
|
headBranch
|
|
6376
7373
|
};
|
|
6377
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
|
+
}
|
|
6378
7383
|
const unmergedFiles = await gitOutput2(run, input.worktreePath, ["diff", "--name-only", "--diff-filter=U"]);
|
|
6379
7384
|
if (unmergedFiles.trim()) {
|
|
6380
|
-
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." });
|
|
6381
7386
|
}
|
|
6382
7387
|
const status = await gitOutput2(run, input.worktreePath, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
|
|
6383
7388
|
if (!status) {
|
|
@@ -6385,6 +7390,7 @@ async function completeImplementationHandoff(input) {
|
|
|
6385
7390
|
...common,
|
|
6386
7391
|
status: "noChanges",
|
|
6387
7392
|
cleanupStatus: "notApplicable",
|
|
7393
|
+
artifacts: artifactResult,
|
|
6388
7394
|
message: "Local execution completed with no repository changes to hand off."
|
|
6389
7395
|
};
|
|
6390
7396
|
}
|
|
@@ -6396,14 +7402,16 @@ async function completeImplementationHandoff(input) {
|
|
|
6396
7402
|
...common,
|
|
6397
7403
|
provider,
|
|
6398
7404
|
remoteName,
|
|
7405
|
+
artifacts: artifactResult,
|
|
6399
7406
|
message: "Automated pull request handoff currently requires a GitHub remote. Commit and push manually, or link a GitHub repository."
|
|
6400
7407
|
});
|
|
6401
7408
|
}
|
|
6402
7409
|
await gitOutput2(run, input.worktreePath, ["add", "-A"]);
|
|
6403
7410
|
await gitOutput2(run, input.worktreePath, ["commit", "-m", commitSubject(input.workItem), "-m", commitBody(input)]);
|
|
7411
|
+
await rebaseBranchFromRemoteBase(run, input.worktreePath, { baseBranch, remoteName });
|
|
6404
7412
|
const commitSha = await gitOutput2(run, input.worktreePath, ["rev-parse", "HEAD"]);
|
|
6405
7413
|
await gitOutput2(run, input.worktreePath, ["push", "--set-upstream", remoteName, headBranch]);
|
|
6406
|
-
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 } : {} });
|
|
6407
7415
|
const cleanup = await cleanupWorktree(run, input);
|
|
6408
7416
|
return {
|
|
6409
7417
|
provider: "github",
|
|
@@ -6416,12 +7424,70 @@ async function completeImplementationHandoff(input) {
|
|
|
6416
7424
|
prUrl: pullRequest.url,
|
|
6417
7425
|
cleanupStatus: cleanup.status,
|
|
6418
7426
|
...cleanup.message ? { cleanupMessage: cleanup.message } : {},
|
|
7427
|
+
artifacts: artifactResult,
|
|
6419
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."
|
|
6420
7429
|
};
|
|
6421
7430
|
} catch (error) {
|
|
6422
7431
|
return blockedHandoff({ ...common, message: "Implementation handoff is blocked and the local worktree was preserved for recovery.", error: safeErrorMessage(error) });
|
|
6423
7432
|
}
|
|
6424
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
|
+
}
|
|
6425
7491
|
async function ensureGithubPullRequest(run, cwd, input) {
|
|
6426
7492
|
const existing = await run("gh", ["pr", "list", "--head", input.headBranch, "--base", input.baseBranch, "--state", "open", "--json", "number,url", "--limit", "1"], { cwd });
|
|
6427
7493
|
const parsed = parsePullRequestList(existing.stdout);
|
|
@@ -6458,6 +7524,10 @@ async function resolveRemoteName(run, cwd) {
|
|
|
6458
7524
|
}
|
|
6459
7525
|
return remotes.includes("origin") ? "origin" : remotes[0];
|
|
6460
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
|
+
}
|
|
6461
7531
|
async function gitOutput2(run, cwd, args) {
|
|
6462
7532
|
const result = await run("git", args, { cwd });
|
|
6463
7533
|
return result.stdout.trim();
|
|
@@ -6513,9 +7583,23 @@ function pullRequestBody(input) {
|
|
|
6513
7583
|
`Base branch: ${input.baseBranch}`,
|
|
6514
7584
|
`Head branch: ${input.headBranch}`,
|
|
6515
7585
|
"",
|
|
7586
|
+
...artifactSummaryLines(input.artifacts),
|
|
7587
|
+
"",
|
|
6516
7588
|
input.verificationSummary ?? "Verification summary was not reported by the local runner."
|
|
6517
7589
|
].join("\n");
|
|
6518
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
|
+
}
|
|
6519
7603
|
function truncate(value, maxLength) {
|
|
6520
7604
|
return value.length <= maxLength ? value : value.slice(0, maxLength - 1).trimEnd();
|
|
6521
7605
|
}
|
|
@@ -6556,7 +7640,8 @@ var DEFAULT_MAX_PREFLIGHT_ATTEMPTS = 3;
|
|
|
6556
7640
|
var DEFAULT_TOOL_TIMEOUT_SECONDS = 30 * 60;
|
|
6557
7641
|
var RUNNER_WORK_LEASE_SECONDS = 300;
|
|
6558
7642
|
var RUNNER_WORK_LEASE_RENEWAL_MS = 12e4;
|
|
6559
|
-
var
|
|
7643
|
+
var MAX_CONCURRENT_RUNNER_WORK = 4;
|
|
7644
|
+
var runnerSupportedWorkKinds = ["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan", "appEvaluationScan", "projectContextRefresh", "implementationVerification", "testQualityScan", "implementationTestGate"];
|
|
6560
7645
|
program.name("amistio").description("Amistio project brain CLI").version(CLI_VERSION);
|
|
6561
7646
|
program.command("init").description("Create Amistio control-plane folders for a new project").option("--root <path>", "Repository root", defaultRoot).action(async (options) => {
|
|
6562
7647
|
const created = await initControlPlane(options.root);
|
|
@@ -6839,7 +7924,7 @@ work.command("list").description("List queued work without claiming it").option(
|
|
|
6839
7924
|
return;
|
|
6840
7925
|
}
|
|
6841
7926
|
for (const item of workItems) {
|
|
6842
|
-
console.log(`${item.workItemId} [${item.status}] ${item.title}`);
|
|
7927
|
+
console.log(`${item.workItemId} [${item.status}] ${item.title}${formatAutopilotListSuffix(item)}`);
|
|
6843
7928
|
}
|
|
6844
7929
|
});
|
|
6845
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) => {
|
|
@@ -6905,7 +7990,7 @@ program.command("orchestrate").description("Update the Amistio control plane thr
|
|
|
6905
7990
|
process.exitCode = result.exitCode;
|
|
6906
7991
|
}
|
|
6907
7992
|
});
|
|
6908
|
-
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) => {
|
|
6909
7994
|
const context = await loadPairedApiContext(options.root, options.apiUrl);
|
|
6910
7995
|
if (!context) {
|
|
6911
7996
|
console.log("Repository is not paired. Run `amistio pair` first.");
|
|
@@ -6923,6 +8008,11 @@ program.command("run").description("Claim and run approved Amistio work locally"
|
|
|
6923
8008
|
machineId: runnerMachineId()
|
|
6924
8009
|
});
|
|
6925
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
|
+
}
|
|
6926
8016
|
if (options.background) {
|
|
6927
8017
|
if (options.dryRun) {
|
|
6928
8018
|
console.log("Background runners cannot be started in dry-run mode.");
|
|
@@ -7077,7 +8167,7 @@ runner.command("stop").description("Stop a background runner for the paired repo
|
|
|
7077
8167
|
console.log(stopResult === "stopped" ? `Stopped background runner ${record.runnerId}.` : `Marked background runner ${record.runnerId} stopped; process was not running.`);
|
|
7078
8168
|
});
|
|
7079
8169
|
var runnerService = runner.command("service").description("Manage a user-level startup service for the paired runner");
|
|
7080
|
-
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) => {
|
|
7081
8171
|
const context = await loadPairedApiContext(options.root, options.apiUrl);
|
|
7082
8172
|
if (!context) {
|
|
7083
8173
|
console.log("Repository is not paired. Run `amistio pair` first.");
|
|
@@ -7100,6 +8190,11 @@ runnerService.command("install").description("Install a user-level startup servi
|
|
|
7100
8190
|
repositoryLinkId: context.metadata.repositoryLinkId,
|
|
7101
8191
|
machineId: runnerMachineId()
|
|
7102
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
|
+
}
|
|
7103
8198
|
const args = buildBackgroundRunnerArgs({ ...options, runnerId, apiUrl: options.apiUrl, root: options.root });
|
|
7104
8199
|
const serviceInput = {
|
|
7105
8200
|
accountId: context.metadata.amistioAccountId,
|
|
@@ -7178,7 +8273,7 @@ async function runWatchIteration({ command, context, options, runnerId }) {
|
|
|
7178
8273
|
});
|
|
7179
8274
|
}
|
|
7180
8275
|
if (!options.dryRun) {
|
|
7181
|
-
const replayResult = await
|
|
8276
|
+
const replayResult = await replayPendingResultFinalizations({
|
|
7182
8277
|
accountId: context.metadata.amistioAccountId,
|
|
7183
8278
|
apiClient: context.client,
|
|
7184
8279
|
projectId: context.metadata.amistioProjectId,
|
|
@@ -7189,7 +8284,8 @@ async function runWatchIteration({ command, context, options, runnerId }) {
|
|
|
7189
8284
|
return replayResult;
|
|
7190
8285
|
}
|
|
7191
8286
|
}
|
|
7192
|
-
|
|
8287
|
+
const activeClaimLaneIds = claimLaneIds(options.maxConcurrentWork);
|
|
8288
|
+
const runLane = (claimLaneId, laneIndex) => runNextWorkItem({
|
|
7193
8289
|
apiClient: context.client,
|
|
7194
8290
|
projectId: context.metadata.amistioProjectId,
|
|
7195
8291
|
repositoryLinkId: context.metadata.repositoryLinkId,
|
|
@@ -7218,8 +8314,16 @@ async function runWatchIteration({ command, context, options, runnerId }) {
|
|
|
7218
8314
|
suppressIdleOutput: Boolean(options.watch),
|
|
7219
8315
|
maxPreflightAttempts: options.maxPreflightAttempts,
|
|
7220
8316
|
toolTimeoutMs: options.toolTimeoutSeconds * 1e3,
|
|
7221
|
-
verbose: Boolean(options.verbose)
|
|
8317
|
+
verbose: Boolean(options.verbose),
|
|
8318
|
+
claimLaneId,
|
|
8319
|
+
maxConcurrentWork: options.maxConcurrentWork,
|
|
8320
|
+
activeClaimLaneIds,
|
|
8321
|
+
skipRunnerCommands: laneIndex > 0
|
|
7222
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);
|
|
7223
8327
|
} catch (error) {
|
|
7224
8328
|
if (!options.watch) {
|
|
7225
8329
|
throw error;
|
|
@@ -7247,6 +8351,23 @@ ${detail}`);
|
|
|
7247
8351
|
return { status: "failed", exitCode: 1, message };
|
|
7248
8352
|
}
|
|
7249
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
|
+
}
|
|
7250
8371
|
async function runAutoSyncCycle({ context, maxFileKb, quiet, quietDisabled, root, runnerId }) {
|
|
7251
8372
|
const projectId = context.metadata.amistioProjectId;
|
|
7252
8373
|
const repositoryLinkId = context.metadata.repositoryLinkId;
|
|
@@ -7326,7 +8447,11 @@ async function runNextWorkItem({
|
|
|
7326
8447
|
commandContext,
|
|
7327
8448
|
suppressIdleOutput,
|
|
7328
8449
|
toolTimeoutMs,
|
|
7329
|
-
verbose
|
|
8450
|
+
verbose,
|
|
8451
|
+
claimLaneId,
|
|
8452
|
+
maxConcurrentWork,
|
|
8453
|
+
activeClaimLaneIds,
|
|
8454
|
+
skipRunnerCommands
|
|
7330
8455
|
}) {
|
|
7331
8456
|
const toolConfig = await resolveRunnerToolConfig({
|
|
7332
8457
|
apiClient,
|
|
@@ -7340,19 +8465,27 @@ async function runNextWorkItem({
|
|
|
7340
8465
|
...explicitTool ? { explicitTool } : {},
|
|
7341
8466
|
...toolCommand ? { toolCommand } : {}
|
|
7342
8467
|
});
|
|
7343
|
-
|
|
7344
|
-
const
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
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 } : {} };
|
|
7348
8482
|
}
|
|
7349
|
-
return { status: commandResult.succeeded ? "completed" : "failed", exitCode: commandResult.succeeded ? 0 : 1, ...commandResult.stopRunner ? { stopRunner: true } : {} };
|
|
7350
8483
|
}
|
|
7351
8484
|
if (!toolConfig.ready) {
|
|
7352
8485
|
console.log(toolConfig.message);
|
|
7353
8486
|
return { status: "blocked", exitCode: 1 };
|
|
7354
8487
|
}
|
|
7355
|
-
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 }));
|
|
7356
8489
|
if (!result.workItem) {
|
|
7357
8490
|
const nextAction = await loadProjectNextAction(apiClient, projectId, repositoryLinkId, root);
|
|
7358
8491
|
const message = formatProjectNextAction(nextAction);
|
|
@@ -7370,17 +8503,17 @@ async function runNextWorkItem({
|
|
|
7370
8503
|
});
|
|
7371
8504
|
if (dryRun || toolConfig.tool === "none") {
|
|
7372
8505
|
console.log(prompt);
|
|
7373
|
-
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
|
|
8506
|
+
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig, currentRunnerMode(), heartbeatConcurrency));
|
|
7374
8507
|
return { status: "preview", exitCode: 0 };
|
|
7375
8508
|
}
|
|
7376
|
-
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 });
|
|
7377
8510
|
if (worktreeIsolation.status !== "ready") {
|
|
7378
8511
|
return { status: worktreeIsolation.status === "failed" ? "failed" : "blocked", exitCode: 1, message: worktreeIsolation.message };
|
|
7379
8512
|
}
|
|
7380
8513
|
const executionRoot = worktreeIsolation.isolation?.worktreePath ?? root;
|
|
7381
8514
|
const isolationTelemetry = workItemIsolationTelemetry(result.workItem, worktreeIsolation.isolation);
|
|
7382
8515
|
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "running", {
|
|
7383
|
-
...runnerHeartbeatMetadata(toolConfig),
|
|
8516
|
+
...runnerHeartbeatMetadata(toolConfig, currentRunnerMode(), heartbeatConcurrency),
|
|
7384
8517
|
currentWorkItemId: result.workItem.workItemId,
|
|
7385
8518
|
...isolationTelemetry.implementationScopeId ? { currentImplementationScopeId: isolationTelemetry.implementationScopeId } : {},
|
|
7386
8519
|
...isolationTelemetry.executionWorktreeKey ? { currentWorktreeKey: isolationTelemetry.executionWorktreeKey } : {},
|
|
@@ -7403,6 +8536,10 @@ async function runNextWorkItem({
|
|
|
7403
8536
|
isolationTelemetry
|
|
7404
8537
|
});
|
|
7405
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
|
+
}
|
|
7406
8543
|
await recordRunnerMilestone(apiClient, projectId, result.workItem, runnerId, repositoryLinkId, {
|
|
7407
8544
|
status: "running",
|
|
7408
8545
|
summary: `Local runner started ${preview.toolName} execution.`,
|
|
@@ -7413,7 +8550,7 @@ async function runNextWorkItem({
|
|
|
7413
8550
|
const providerSessionStore = new LocalToolSessionStore();
|
|
7414
8551
|
const providerSessionId = sessionContext.toolSession ? await providerSessionStore.getProviderSessionId(sessionContext.toolSession.toolSessionId, preview.toolName) : void 0;
|
|
7415
8552
|
let toolResult;
|
|
7416
|
-
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 });
|
|
7417
8554
|
try {
|
|
7418
8555
|
toolResult = await runLocalTool({
|
|
7419
8556
|
rootDir: executionRoot,
|
|
@@ -7439,7 +8576,7 @@ async function runNextWorkItem({
|
|
|
7439
8576
|
const durationMs2 = Date.now() - startedAt;
|
|
7440
8577
|
const message = `${preview.toolName} failed before returning a result.`;
|
|
7441
8578
|
const settlements = await Promise.allSettled([
|
|
7442
|
-
apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig)),
|
|
8579
|
+
apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig, currentRunnerMode(), heartbeatConcurrency)),
|
|
7443
8580
|
markToolSessionBlocked(apiClient, projectId, sessionContext.toolSession, errorMessage3(error)),
|
|
7444
8581
|
apiClient.updateWorkStatus(projectId, result.workItem.workItemId, "failed", `run_failed_${result.workItem.workItemId}_${result.workItem.attempt}_${runnerId}`, runnerId, {
|
|
7445
8582
|
...isolationTelemetry,
|
|
@@ -7622,6 +8759,42 @@ async function runNextWorkItem({
|
|
|
7622
8759
|
return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
|
|
7623
8760
|
}
|
|
7624
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
|
+
}
|
|
7625
8798
|
let finalStatus = toolResult.exitCode === 0 ? "completed" : "failed";
|
|
7626
8799
|
const durationMs = Date.now() - startedAt;
|
|
7627
8800
|
const failureExcerpt = toolResult.exitCode === 0 ? void 0 : truncateLogExcerpt(toolResult.stderr || toolResult.stdout);
|
|
@@ -7636,14 +8809,27 @@ async function runNextWorkItem({
|
|
|
7636
8809
|
metadata: { executionWorktreeKey: isolationTelemetry.executionWorktreeKey ?? "", executionBranch: isolationTelemetry.executionBranch ?? "" }
|
|
7637
8810
|
});
|
|
7638
8811
|
const repositoryLink = await loadWorkItemRepositoryLink(apiClient, projectId, result.workItem.repositoryLinkId ?? repositoryLinkId);
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
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;
|
|
7646
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
|
+
}
|
|
7647
8833
|
finalStatus = implementationHandoff.status === "prReady" || implementationHandoff.status === "noChanges" ? "completed" : "blocked";
|
|
7648
8834
|
finalMessage = implementationHandoff.message ?? "Implementation handoff finished.";
|
|
7649
8835
|
finalError = implementationHandoff.error;
|
|
@@ -7661,31 +8847,48 @@ async function runNextWorkItem({
|
|
|
7661
8847
|
...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
|
|
7662
8848
|
...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
|
|
7663
8849
|
});
|
|
7664
|
-
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
7670
|
-
|
|
7671
|
-
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
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 };
|
|
7687
8889
|
}
|
|
7688
|
-
|
|
8890
|
+
throw error;
|
|
8891
|
+
}
|
|
7689
8892
|
await recordRunnerMilestone(apiClient, projectId, result.workItem, runnerId, repositoryLinkId, {
|
|
7690
8893
|
status: finalStatus,
|
|
7691
8894
|
summary: finalMessage,
|
|
@@ -7699,19 +8902,27 @@ async function runNextWorkItem({
|
|
|
7699
8902
|
executionBranch: isolationTelemetry.executionBranch ?? "",
|
|
7700
8903
|
...implementationHandoff?.status ? { handoffStatus: implementationHandoff.status } : {},
|
|
7701
8904
|
...implementationHandoff?.prUrl ? { prUrl: implementationHandoff.prUrl } : {},
|
|
7702
|
-
...implementationHandoff?.cleanupStatus ? { cleanupStatus: implementationHandoff.cleanupStatus } : {}
|
|
8905
|
+
...implementationHandoff?.cleanupStatus ? { cleanupStatus: implementationHandoff.cleanupStatus } : {},
|
|
8906
|
+
...implementationHandoff?.artifacts ? artifactHandoffMetadata(implementationHandoff.artifacts) : {}
|
|
7703
8907
|
}
|
|
7704
8908
|
});
|
|
7705
|
-
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
|
|
8909
|
+
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig, currentRunnerMode(), heartbeatConcurrency));
|
|
7706
8910
|
const durationSeconds = Math.round(durationMs / 1e3);
|
|
7707
8911
|
console.log(`Marked ${statusResult.workItem.workItemId} ${statusResult.workItem.status} after ${durationSeconds}s.`);
|
|
7708
8912
|
return { status: finalStatus, exitCode: finalStatus === "completed" ? toolResult.exitCode : 1 };
|
|
7709
8913
|
}
|
|
8914
|
+
function artifactHandoffMetadata(artifacts) {
|
|
8915
|
+
return {
|
|
8916
|
+
artifactIncludedCount: artifacts.included.length,
|
|
8917
|
+
artifactSkippedCount: artifacts.skipped.length,
|
|
8918
|
+
artifactBlockedCount: artifacts.blocked.length
|
|
8919
|
+
};
|
|
8920
|
+
}
|
|
7710
8921
|
async function loadWorkItemRepositoryLink(apiClient, projectId, repositoryLinkId) {
|
|
7711
8922
|
const { repositoryLinks } = await apiClient.listRepositoryLinks(projectId);
|
|
7712
8923
|
return repositoryLinks.find((link) => link.repositoryLinkId === repositoryLinkId && link.status !== "revoked");
|
|
7713
8924
|
}
|
|
7714
|
-
async function prepareWorktreeForClaimedItem({ apiClient, maxPreflightAttempts, projectId, repositoryLinkId, root, runnerId, toolConfig, workItem }) {
|
|
8925
|
+
async function prepareWorktreeForClaimedItem({ apiClient, heartbeatConcurrency, maxPreflightAttempts, projectId, repositoryLinkId, root, runnerId, toolConfig, workItem }) {
|
|
7715
8926
|
if (!needsGitWorktreeIsolation(workItem)) {
|
|
7716
8927
|
return { status: "ready" };
|
|
7717
8928
|
}
|
|
@@ -7726,9 +8937,9 @@ async function prepareWorktreeForClaimedItem({ apiClient, maxPreflightAttempts,
|
|
|
7726
8937
|
const isolation = await prepareGitWorktreeIsolation(root, workItem);
|
|
7727
8938
|
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
7728
8939
|
status: "running",
|
|
7729
|
-
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}.`,
|
|
7730
8941
|
idempotencyKey: `runner_milestone_worktree_${workItem.workItemId}_${workItem.attempt}`,
|
|
7731
|
-
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 } : {} }
|
|
7732
8943
|
});
|
|
7733
8944
|
return { status: "ready", isolation };
|
|
7734
8945
|
} catch (error) {
|
|
@@ -7749,14 +8960,14 @@ async function prepareWorktreeForClaimedItem({ apiClient, maxPreflightAttempts,
|
|
|
7749
8960
|
metadata: { executionWorktreeKey: telemetry.executionWorktreeKey ?? "", executionBranch: telemetry.executionBranch ?? "", implementationScopeId: telemetry.implementationScopeId ?? "", attempt: workItem.attempt, maxAttempts: maxPreflightAttempts, error: message }
|
|
7750
8961
|
});
|
|
7751
8962
|
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", {
|
|
7752
|
-
...runnerHeartbeatMetadata(toolConfig),
|
|
8963
|
+
...runnerHeartbeatMetadata(toolConfig, currentRunnerMode(), heartbeatConcurrency),
|
|
7753
8964
|
preferenceMessage: statusMessage
|
|
7754
8965
|
});
|
|
7755
8966
|
console.error(statusMessage);
|
|
7756
8967
|
return { status: finalAttempt ? "failed" : "retrying", message: statusMessage };
|
|
7757
8968
|
}
|
|
7758
8969
|
}
|
|
7759
|
-
function startWorkLeaseRenewal({ apiClient, projectId, repositoryLinkId, runnerId, toolConfig, workItem, telemetry }) {
|
|
8970
|
+
function startWorkLeaseRenewal({ apiClient, projectId, repositoryLinkId, runnerId, toolConfig, workItem, telemetry, heartbeatConcurrency }) {
|
|
7760
8971
|
let stopped = false;
|
|
7761
8972
|
const renew = async () => {
|
|
7762
8973
|
if (stopped) return;
|
|
@@ -7767,7 +8978,7 @@ function startWorkLeaseRenewal({ apiClient, projectId, repositoryLinkId, runnerI
|
|
|
7767
8978
|
leaseExpiresAt
|
|
7768
8979
|
});
|
|
7769
8980
|
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "running", {
|
|
7770
|
-
...runnerHeartbeatMetadata(toolConfig),
|
|
8981
|
+
...runnerHeartbeatMetadata(toolConfig, currentRunnerMode(), heartbeatConcurrency),
|
|
7771
8982
|
currentWorkItemId: workItem.workItemId,
|
|
7772
8983
|
...telemetry.implementationScopeId ? { currentImplementationScopeId: telemetry.implementationScopeId } : {},
|
|
7773
8984
|
...telemetry.executionWorktreeKey ? { currentWorktreeKey: telemetry.executionWorktreeKey } : {},
|
|
@@ -7783,6 +8994,7 @@ function startWorkLeaseRenewal({ apiClient, projectId, repositoryLinkId, runnerI
|
|
|
7783
8994
|
workItemId: workItem.workItemId,
|
|
7784
8995
|
workTitle: workItem.title,
|
|
7785
8996
|
...workItem.workKind ? { workKind: workItem.workKind } : {},
|
|
8997
|
+
...workItem.claimLaneId ? { claimLaneId: workItem.claimLaneId } : {},
|
|
7786
8998
|
message: "Runner could not renew the active work lease.",
|
|
7787
8999
|
error: detail,
|
|
7788
9000
|
machineId: runnerMachineId()
|
|
@@ -7822,6 +9034,7 @@ async function recordFinalizationFailure({ apiClient, durationMs, error, isolati
|
|
|
7822
9034
|
workItemId: workItem.workItemId,
|
|
7823
9035
|
workTitle: workItem.title,
|
|
7824
9036
|
...workItem.workKind ? { workKind: workItem.workKind } : {},
|
|
9037
|
+
...workItem.claimLaneId ? { claimLaneId: workItem.claimLaneId } : {},
|
|
7825
9038
|
tool: toolName,
|
|
7826
9039
|
durationMs,
|
|
7827
9040
|
message,
|
|
@@ -7846,6 +9059,7 @@ function workItemIsolationTelemetry(workItem, isolation) {
|
|
|
7846
9059
|
const repositoryLockId = isolation?.repositoryLockId ?? workItem.repositoryLockId;
|
|
7847
9060
|
return {
|
|
7848
9061
|
...needsGitWorktreeIsolation(workItem) ? { isolationMode: "gitWorktree" } : workItem.isolationMode ? { isolationMode: workItem.isolationMode } : {},
|
|
9062
|
+
...workItem.claimLaneId ? { claimLaneId: workItem.claimLaneId } : {},
|
|
7849
9063
|
...workItem.claimLeaseId ? { claimLeaseId: workItem.claimLeaseId } : {},
|
|
7850
9064
|
...workItem.controllingAdrId ? { controllingAdrId: workItem.controllingAdrId } : {},
|
|
7851
9065
|
...implementationScopeId ? { implementationScopeId } : {},
|
|
@@ -7857,19 +9071,62 @@ function workItemIsolationTelemetry(workItem, isolation) {
|
|
|
7857
9071
|
};
|
|
7858
9072
|
}
|
|
7859
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 ?? {} };
|
|
7860
9076
|
await apiClient.recordActivityEvent(projectId, {
|
|
7861
9077
|
eventType: "runnerMilestone",
|
|
7862
9078
|
runnerId,
|
|
7863
9079
|
repositoryLinkId,
|
|
9080
|
+
...workItem.claimLaneId ? { claimLaneId: workItem.claimLaneId } : {},
|
|
7864
9081
|
relatedWorkItemId: workItem.workItemId,
|
|
7865
9082
|
...workItem.reviewDocumentId ? { relatedDocumentId: workItem.reviewDocumentId } : workItem.impactDocumentId ? { relatedDocumentId: workItem.impactDocumentId } : {},
|
|
7866
9083
|
...workItem.issueId ? { relatedIssueId: workItem.issueId } : {},
|
|
9084
|
+
...autopilot.autopilotAuthorizationId ? { relatedAutopilotAuthorizationId: autopilot.autopilotAuthorizationId } : {},
|
|
9085
|
+
...autopilot.autopilotCandidateId ? { relatedAutopilotCandidateId: autopilot.autopilotCandidateId } : {},
|
|
7867
9086
|
...workItem.generatedDraftId ? { generatedDraftId: workItem.generatedDraftId } : {},
|
|
7868
|
-
...input
|
|
9087
|
+
...input,
|
|
9088
|
+
...Object.keys(metadata).length ? { metadata } : {}
|
|
7869
9089
|
}).catch(() => void 0);
|
|
7870
9090
|
}
|
|
7871
|
-
function
|
|
7872
|
-
|
|
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
|
+
}
|
|
9128
|
+
function logRejectedSettlements(action, settlements) {
|
|
9129
|
+
for (const settlement of settlements) {
|
|
7873
9130
|
if (settlement.status === "rejected") {
|
|
7874
9131
|
console.error(`${action} failed: ${errorMessage3(settlement.reason)}`);
|
|
7875
9132
|
}
|
|
@@ -7954,6 +9211,28 @@ async function replayPendingBrainGenerationFinalizations({ accountId, apiClient,
|
|
|
7954
9211
|
console.log(message);
|
|
7955
9212
|
return { status: "completed", exitCode: 0, message };
|
|
7956
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
|
+
}
|
|
7957
9236
|
async function submitBrainGenerationFinalizationEntry(apiClient, entry, options = {}) {
|
|
7958
9237
|
let result;
|
|
7959
9238
|
try {
|
|
@@ -7988,6 +9267,127 @@ async function submitBrainGenerationFinalizationEntry(apiClient, entry, options
|
|
|
7988
9267
|
}
|
|
7989
9268
|
return { status: "completed", workItem: result.workItem, documentCount: result.documents.length };
|
|
7990
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
|
+
}
|
|
7991
9391
|
async function finalizeBrainGenerationWork({
|
|
7992
9392
|
apiClient,
|
|
7993
9393
|
durationMs,
|
|
@@ -8145,28 +9545,9 @@ ${toolResult.stderr}`);
|
|
|
8145
9545
|
answerError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
|
|
8146
9546
|
}
|
|
8147
9547
|
const finalStatus = answerResult ? "completed" : "failed";
|
|
8148
|
-
const
|
|
8149
|
-
apiClient,
|
|
8150
|
-
projectId,
|
|
8151
|
-
status: finalStatus,
|
|
8152
|
-
runnerId,
|
|
8153
|
-
workItemId: workItem.workItemId,
|
|
8154
|
-
stdout: toolResult.stdout,
|
|
8155
|
-
...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
|
|
8156
|
-
...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
|
|
8157
|
-
...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
|
|
8158
|
-
...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
|
|
8159
|
-
...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
|
|
8160
|
-
});
|
|
8161
|
-
const sessionTelemetry = {
|
|
8162
|
-
sessionPolicy: sessionContext.policy,
|
|
8163
|
-
sessionDecision: sessionContext.decision,
|
|
8164
|
-
sessionDecisionReason: sessionContext.reason,
|
|
8165
|
-
...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
|
|
8166
|
-
...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
|
|
8167
|
-
};
|
|
9548
|
+
const sessionTelemetry = pendingSessionTelemetry(sessionContext);
|
|
8168
9549
|
if (answerResult) {
|
|
8169
|
-
const
|
|
9550
|
+
const resultMutation = {
|
|
8170
9551
|
status: "completed",
|
|
8171
9552
|
runnerId,
|
|
8172
9553
|
idempotencyKey: `assistant_${workItem.workItemId}_${randomUUID()}`,
|
|
@@ -8177,7 +9558,21 @@ ${toolResult.stderr}`);
|
|
|
8177
9558
|
durationMs,
|
|
8178
9559
|
...sessionTelemetry,
|
|
8179
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
|
|
8180
9573
|
});
|
|
9574
|
+
if (!result) return { status: "failed", exitCode: 1 };
|
|
9575
|
+
await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
|
|
8181
9576
|
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
8182
9577
|
status: "completed",
|
|
8183
9578
|
summary: `${toolName} returned a project knowledge answer.`,
|
|
@@ -8188,7 +9583,7 @@ ${toolResult.stderr}`);
|
|
|
8188
9583
|
console.log("Project knowledge answer returned.");
|
|
8189
9584
|
return { status: "completed", exitCode: 0 };
|
|
8190
9585
|
}
|
|
8191
|
-
const
|
|
9586
|
+
const failedMutation = {
|
|
8192
9587
|
status: "failed",
|
|
8193
9588
|
runnerId,
|
|
8194
9589
|
idempotencyKey: `assistant_${workItem.workItemId}_${randomUUID()}`,
|
|
@@ -8197,7 +9592,21 @@ ${toolResult.stderr}`);
|
|
|
8197
9592
|
...sessionTelemetry,
|
|
8198
9593
|
message: `${toolName} did not produce a valid project knowledge answer.`,
|
|
8199
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
|
|
8200
9607
|
});
|
|
9608
|
+
if (!failedResult) return { status: "failed", exitCode: 1 };
|
|
9609
|
+
await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
|
|
8201
9610
|
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
8202
9611
|
status: "failed",
|
|
8203
9612
|
summary: answerError ?? `${toolName} did not produce a valid project knowledge answer.`,
|
|
@@ -8234,29 +9643,10 @@ ${toolResult.stderr}`);
|
|
|
8234
9643
|
previewError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
|
|
8235
9644
|
}
|
|
8236
9645
|
const finalStatus = report ? "completed" : "failed";
|
|
8237
|
-
const
|
|
8238
|
-
apiClient,
|
|
8239
|
-
projectId,
|
|
8240
|
-
status: finalStatus,
|
|
8241
|
-
runnerId,
|
|
8242
|
-
workItemId: workItem.workItemId,
|
|
8243
|
-
stdout: toolResult.stdout,
|
|
8244
|
-
...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
|
|
8245
|
-
...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
|
|
8246
|
-
...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
|
|
8247
|
-
...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
|
|
8248
|
-
...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
|
|
8249
|
-
});
|
|
8250
|
-
const sessionTelemetry = {
|
|
8251
|
-
sessionPolicy: sessionContext.policy,
|
|
8252
|
-
sessionDecision: sessionContext.decision,
|
|
8253
|
-
sessionDecisionReason: sessionContext.reason,
|
|
8254
|
-
...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
|
|
8255
|
-
...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
|
|
8256
|
-
};
|
|
9646
|
+
const sessionTelemetry = pendingSessionTelemetry(sessionContext);
|
|
8257
9647
|
if (report) {
|
|
8258
9648
|
const metadata = await readProjectLink(root).catch(() => void 0);
|
|
8259
|
-
const
|
|
9649
|
+
const resultMutation = {
|
|
8260
9650
|
status: "completed",
|
|
8261
9651
|
runnerId,
|
|
8262
9652
|
idempotencyKey: `impact_${workItem.workItemId}_${randomUUID()}`,
|
|
@@ -8268,7 +9658,21 @@ ${toolResult.stderr}`);
|
|
|
8268
9658
|
durationMs,
|
|
8269
9659
|
...sessionTelemetry,
|
|
8270
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
|
|
8271
9673
|
});
|
|
9674
|
+
if (!result) return { status: "failed", exitCode: 1 };
|
|
9675
|
+
await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
|
|
8272
9676
|
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
8273
9677
|
status: "completed",
|
|
8274
9678
|
summary: `${toolName} returned an implementation impact preview.`,
|
|
@@ -8279,7 +9683,7 @@ ${toolResult.stderr}`);
|
|
|
8279
9683
|
console.log(result.implementationWorkItem ? "Impact preview returned; implementation work is now queued." : "Impact preview returned.");
|
|
8280
9684
|
return { status: "completed", exitCode: 0 };
|
|
8281
9685
|
}
|
|
8282
|
-
const
|
|
9686
|
+
const failedMutation = {
|
|
8283
9687
|
status: "failed",
|
|
8284
9688
|
runnerId,
|
|
8285
9689
|
idempotencyKey: `impact_${workItem.workItemId}_${randomUUID()}`,
|
|
@@ -8288,7 +9692,21 @@ ${toolResult.stderr}`);
|
|
|
8288
9692
|
...sessionTelemetry,
|
|
8289
9693
|
message: `${toolName} did not produce a valid impact preview.`,
|
|
8290
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
|
|
8291
9707
|
});
|
|
9708
|
+
if (!failedResult) return { status: "failed", exitCode: 1 };
|
|
9709
|
+
await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
|
|
8292
9710
|
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
8293
9711
|
status: "failed",
|
|
8294
9712
|
summary: previewError ?? `${toolName} did not produce a valid impact preview.`,
|
|
@@ -8324,28 +9742,9 @@ ${toolResult.stderr}`);
|
|
|
8324
9742
|
diagnosisError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
|
|
8325
9743
|
}
|
|
8326
9744
|
const finalStatus = diagnosis ? "completed" : "failed";
|
|
8327
|
-
const
|
|
8328
|
-
apiClient,
|
|
8329
|
-
projectId,
|
|
8330
|
-
status: finalStatus,
|
|
8331
|
-
runnerId,
|
|
8332
|
-
workItemId: workItem.workItemId,
|
|
8333
|
-
stdout: toolResult.stdout,
|
|
8334
|
-
...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
|
|
8335
|
-
...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
|
|
8336
|
-
...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
|
|
8337
|
-
...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
|
|
8338
|
-
...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
|
|
8339
|
-
});
|
|
8340
|
-
const sessionTelemetry = {
|
|
8341
|
-
sessionPolicy: sessionContext.policy,
|
|
8342
|
-
sessionDecision: sessionContext.decision,
|
|
8343
|
-
sessionDecisionReason: sessionContext.reason,
|
|
8344
|
-
...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
|
|
8345
|
-
...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
|
|
8346
|
-
};
|
|
9745
|
+
const sessionTelemetry = pendingSessionTelemetry(sessionContext);
|
|
8347
9746
|
if (diagnosis) {
|
|
8348
|
-
const
|
|
9747
|
+
const resultMutation = {
|
|
8349
9748
|
status: "completed",
|
|
8350
9749
|
runnerId,
|
|
8351
9750
|
idempotencyKey: `issue_${workItem.workItemId}_${randomUUID()}`,
|
|
@@ -8354,7 +9753,21 @@ ${toolResult.stderr}`);
|
|
|
8354
9753
|
durationMs,
|
|
8355
9754
|
...sessionTelemetry,
|
|
8356
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
|
|
8357
9768
|
});
|
|
9769
|
+
if (!result) return { status: "failed", exitCode: 1 };
|
|
9770
|
+
await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
|
|
8358
9771
|
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
8359
9772
|
status: "completed",
|
|
8360
9773
|
summary: `${toolName} returned an issue root-cause analysis.`,
|
|
@@ -8365,7 +9778,7 @@ ${toolResult.stderr}`);
|
|
|
8365
9778
|
console.log("Issue diagnosis returned for approval.");
|
|
8366
9779
|
return { status: "completed", exitCode: 0 };
|
|
8367
9780
|
}
|
|
8368
|
-
const
|
|
9781
|
+
const failedMutation = {
|
|
8369
9782
|
status: "failed",
|
|
8370
9783
|
runnerId,
|
|
8371
9784
|
idempotencyKey: `issue_${workItem.workItemId}_${randomUUID()}`,
|
|
@@ -8374,7 +9787,21 @@ ${toolResult.stderr}`);
|
|
|
8374
9787
|
...sessionTelemetry,
|
|
8375
9788
|
message: `${toolName} did not produce a valid issue diagnosis.`,
|
|
8376
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
|
|
8377
9802
|
});
|
|
9803
|
+
if (!failedResult) return { status: "failed", exitCode: 1 };
|
|
9804
|
+
await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
|
|
8378
9805
|
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
8379
9806
|
status: "failed",
|
|
8380
9807
|
summary: diagnosisError ?? `${toolName} did not produce a valid issue diagnosis.`,
|
|
@@ -8410,28 +9837,9 @@ ${toolResult.stderr}`);
|
|
|
8410
9837
|
scanError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
|
|
8411
9838
|
}
|
|
8412
9839
|
const finalStatus = scanResult ? "completed" : "failed";
|
|
8413
|
-
const
|
|
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
|
-
};
|
|
9840
|
+
const sessionTelemetry = pendingSessionTelemetry(sessionContext);
|
|
8433
9841
|
if (scanResult) {
|
|
8434
|
-
const
|
|
9842
|
+
const resultMutation = {
|
|
8435
9843
|
status: "completed",
|
|
8436
9844
|
runnerId,
|
|
8437
9845
|
idempotencyKey: `security_${workItem.workItemId}_${randomUUID()}`,
|
|
@@ -8440,7 +9848,21 @@ ${toolResult.stderr}`);
|
|
|
8440
9848
|
durationMs,
|
|
8441
9849
|
...sessionTelemetry,
|
|
8442
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
|
|
8443
9863
|
});
|
|
9864
|
+
if (!result) return { status: "failed", exitCode: 1 };
|
|
9865
|
+
await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
|
|
8444
9866
|
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
8445
9867
|
status: "completed",
|
|
8446
9868
|
summary: `${toolName} returned a security posture scan.`,
|
|
@@ -8451,7 +9873,7 @@ ${toolResult.stderr}`);
|
|
|
8451
9873
|
console.log("Security posture scan returned for review.");
|
|
8452
9874
|
return { status: "completed", exitCode: 0 };
|
|
8453
9875
|
}
|
|
8454
|
-
const
|
|
9876
|
+
const failedMutation = {
|
|
8455
9877
|
status: "failed",
|
|
8456
9878
|
runnerId,
|
|
8457
9879
|
idempotencyKey: `security_${workItem.workItemId}_${randomUUID()}`,
|
|
@@ -8460,7 +9882,21 @@ ${toolResult.stderr}`);
|
|
|
8460
9882
|
...sessionTelemetry,
|
|
8461
9883
|
message: `${toolName} did not produce a valid security posture scan.`,
|
|
8462
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
|
|
8463
9897
|
});
|
|
9898
|
+
if (!failedResult) return { status: "failed", exitCode: 1 };
|
|
9899
|
+
await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
|
|
8464
9900
|
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
8465
9901
|
status: "failed",
|
|
8466
9902
|
summary: scanError ?? `${toolName} did not produce a valid security posture scan.`,
|
|
@@ -8496,28 +9932,9 @@ ${toolResult.stderr}`);
|
|
|
8496
9932
|
scanError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
|
|
8497
9933
|
}
|
|
8498
9934
|
const finalStatus = scanResult ? "completed" : "failed";
|
|
8499
|
-
const
|
|
8500
|
-
apiClient,
|
|
8501
|
-
projectId,
|
|
8502
|
-
status: finalStatus,
|
|
8503
|
-
runnerId,
|
|
8504
|
-
workItemId: workItem.workItemId,
|
|
8505
|
-
stdout: toolResult.stdout,
|
|
8506
|
-
...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
|
|
8507
|
-
...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
|
|
8508
|
-
...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
|
|
8509
|
-
...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
|
|
8510
|
-
...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
|
|
8511
|
-
});
|
|
8512
|
-
const sessionTelemetry = {
|
|
8513
|
-
sessionPolicy: sessionContext.policy,
|
|
8514
|
-
sessionDecision: sessionContext.decision,
|
|
8515
|
-
sessionDecisionReason: sessionContext.reason,
|
|
8516
|
-
...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
|
|
8517
|
-
...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
|
|
8518
|
-
};
|
|
9935
|
+
const sessionTelemetry = pendingSessionTelemetry(sessionContext);
|
|
8519
9936
|
if (scanResult) {
|
|
8520
|
-
const
|
|
9937
|
+
const resultMutation = {
|
|
8521
9938
|
status: "completed",
|
|
8522
9939
|
runnerId,
|
|
8523
9940
|
idempotencyKey: `app_evaluation_${workItem.workItemId}_${randomUUID()}`,
|
|
@@ -8526,7 +9943,21 @@ ${toolResult.stderr}`);
|
|
|
8526
9943
|
durationMs,
|
|
8527
9944
|
...sessionTelemetry,
|
|
8528
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
|
|
8529
9958
|
});
|
|
9959
|
+
if (!result) return { status: "failed", exitCode: 1 };
|
|
9960
|
+
await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
|
|
8530
9961
|
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
8531
9962
|
status: "completed",
|
|
8532
9963
|
summary: `${toolName} returned an app evaluation scan.`,
|
|
@@ -8537,7 +9968,7 @@ ${toolResult.stderr}`);
|
|
|
8537
9968
|
console.log("App evaluation scan returned for review.");
|
|
8538
9969
|
return { status: "completed", exitCode: 0 };
|
|
8539
9970
|
}
|
|
8540
|
-
const
|
|
9971
|
+
const failedMutation = {
|
|
8541
9972
|
status: "failed",
|
|
8542
9973
|
runnerId,
|
|
8543
9974
|
idempotencyKey: `app_evaluation_${workItem.workItemId}_${randomUUID()}`,
|
|
@@ -8546,7 +9977,21 @@ ${toolResult.stderr}`);
|
|
|
8546
9977
|
...sessionTelemetry,
|
|
8547
9978
|
message: `${toolName} did not produce a valid app evaluation scan.`,
|
|
8548
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
|
|
8549
9992
|
});
|
|
9993
|
+
if (!failedResult) return { status: "failed", exitCode: 1 };
|
|
9994
|
+
await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
|
|
8550
9995
|
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
8551
9996
|
status: "failed",
|
|
8552
9997
|
summary: scanError ?? `${toolName} did not produce a valid app evaluation scan.`,
|
|
@@ -8583,28 +10028,9 @@ ${toolResult.stderr}`, { repositoryRoot: executionRoot });
|
|
|
8583
10028
|
refreshError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
|
|
8584
10029
|
}
|
|
8585
10030
|
const finalStatus = refreshResult ? "completed" : "failed";
|
|
8586
|
-
const
|
|
8587
|
-
apiClient,
|
|
8588
|
-
projectId,
|
|
8589
|
-
status: finalStatus,
|
|
8590
|
-
runnerId,
|
|
8591
|
-
workItemId: workItem.workItemId,
|
|
8592
|
-
stdout: toolResult.stdout,
|
|
8593
|
-
...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
|
|
8594
|
-
...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
|
|
8595
|
-
...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
|
|
8596
|
-
...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
|
|
8597
|
-
...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
|
|
8598
|
-
});
|
|
8599
|
-
const sessionTelemetry = {
|
|
8600
|
-
sessionPolicy: sessionContext.policy,
|
|
8601
|
-
sessionDecision: sessionContext.decision,
|
|
8602
|
-
sessionDecisionReason: sessionContext.reason,
|
|
8603
|
-
...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
|
|
8604
|
-
...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
|
|
8605
|
-
};
|
|
10031
|
+
const sessionTelemetry = pendingSessionTelemetry(sessionContext);
|
|
8606
10032
|
if (refreshResult) {
|
|
8607
|
-
const
|
|
10033
|
+
const resultMutation = {
|
|
8608
10034
|
status: "completed",
|
|
8609
10035
|
runnerId,
|
|
8610
10036
|
idempotencyKey: `project_context_${workItem.workItemId}_${randomUUID()}`,
|
|
@@ -8613,7 +10039,21 @@ ${toolResult.stderr}`, { repositoryRoot: executionRoot });
|
|
|
8613
10039
|
durationMs,
|
|
8614
10040
|
...sessionTelemetry,
|
|
8615
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
|
|
8616
10054
|
});
|
|
10055
|
+
if (!result) return { status: "failed", exitCode: 1 };
|
|
10056
|
+
await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
|
|
8617
10057
|
const failureSummary = projectContextRefreshSubmissionFailureSummary(result);
|
|
8618
10058
|
if (failureSummary) {
|
|
8619
10059
|
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
@@ -8636,7 +10076,7 @@ ${toolResult.stderr}`, { repositoryRoot: executionRoot });
|
|
|
8636
10076
|
console.log("Project context refresh returned for review.");
|
|
8637
10077
|
return { status: "completed", exitCode: 0 };
|
|
8638
10078
|
}
|
|
8639
|
-
const
|
|
10079
|
+
const failedMutation = {
|
|
8640
10080
|
status: "failed",
|
|
8641
10081
|
runnerId,
|
|
8642
10082
|
idempotencyKey: `project_context_${workItem.workItemId}_${randomUUID()}`,
|
|
@@ -8645,7 +10085,21 @@ ${toolResult.stderr}`, { repositoryRoot: executionRoot });
|
|
|
8645
10085
|
...sessionTelemetry,
|
|
8646
10086
|
message: `${toolName} did not produce a valid project context refresh.`,
|
|
8647
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
|
|
8648
10100
|
});
|
|
10101
|
+
if (!failedResult) return { status: "failed", exitCode: 1 };
|
|
10102
|
+
await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
|
|
8649
10103
|
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
8650
10104
|
status: "failed",
|
|
8651
10105
|
summary: refreshError ?? `${toolName} did not produce a valid project context refresh.`,
|
|
@@ -8681,28 +10135,9 @@ ${toolResult.stderr}`);
|
|
|
8681
10135
|
verificationError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
|
|
8682
10136
|
}
|
|
8683
10137
|
const finalStatus = verificationResult ? "completed" : "failed";
|
|
8684
|
-
const
|
|
8685
|
-
apiClient,
|
|
8686
|
-
projectId,
|
|
8687
|
-
status: finalStatus,
|
|
8688
|
-
runnerId,
|
|
8689
|
-
workItemId: workItem.workItemId,
|
|
8690
|
-
stdout: toolResult.stdout,
|
|
8691
|
-
...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
|
|
8692
|
-
...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
|
|
8693
|
-
...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
|
|
8694
|
-
...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
|
|
8695
|
-
...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
|
|
8696
|
-
});
|
|
8697
|
-
const sessionTelemetry = {
|
|
8698
|
-
sessionPolicy: sessionContext.policy,
|
|
8699
|
-
sessionDecision: sessionContext.decision,
|
|
8700
|
-
sessionDecisionReason: sessionContext.reason,
|
|
8701
|
-
...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
|
|
8702
|
-
...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
|
|
8703
|
-
};
|
|
10138
|
+
const sessionTelemetry = pendingSessionTelemetry(sessionContext);
|
|
8704
10139
|
if (verificationResult) {
|
|
8705
|
-
const
|
|
10140
|
+
const resultMutation = {
|
|
8706
10141
|
status: "completed",
|
|
8707
10142
|
runnerId,
|
|
8708
10143
|
idempotencyKey: `implementation_verification_${workItem.workItemId}_${randomUUID()}`,
|
|
@@ -8711,7 +10146,21 @@ ${toolResult.stderr}`);
|
|
|
8711
10146
|
durationMs,
|
|
8712
10147
|
...sessionTelemetry,
|
|
8713
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
|
|
8714
10161
|
});
|
|
10162
|
+
if (!result) return { status: "failed", exitCode: 1 };
|
|
10163
|
+
await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
|
|
8715
10164
|
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
8716
10165
|
status: verificationResult.outcome === "verifiedImplemented" ? "completed" : verificationResult.outcome === "verificationBlocked" ? "blocked" : "warning",
|
|
8717
10166
|
summary: verificationResult.summary,
|
|
@@ -8722,7 +10171,7 @@ ${toolResult.stderr}`);
|
|
|
8722
10171
|
console.log("Implementation verification returned for review.");
|
|
8723
10172
|
return { status: "completed", exitCode: 0 };
|
|
8724
10173
|
}
|
|
8725
|
-
const
|
|
10174
|
+
const failedMutation = {
|
|
8726
10175
|
status: "failed",
|
|
8727
10176
|
runnerId,
|
|
8728
10177
|
idempotencyKey: `implementation_verification_${workItem.workItemId}_${randomUUID()}`,
|
|
@@ -8731,7 +10180,21 @@ ${toolResult.stderr}`);
|
|
|
8731
10180
|
...sessionTelemetry,
|
|
8732
10181
|
message: `${toolName} did not produce valid implementation verification proof.`,
|
|
8733
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
|
|
8734
10195
|
});
|
|
10196
|
+
if (!failedResult) return { status: "failed", exitCode: 1 };
|
|
10197
|
+
await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
|
|
8735
10198
|
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
8736
10199
|
status: "failed",
|
|
8737
10200
|
summary: verificationError ?? `${toolName} did not produce valid implementation verification proof.`,
|
|
@@ -8742,6 +10205,196 @@ ${toolResult.stderr}`);
|
|
|
8742
10205
|
console.error(verificationError ?? "Local runner implementation verification failed.");
|
|
8743
10206
|
return { status: "failed", exitCode: toolResult.exitCode || 1 };
|
|
8744
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
|
+
}
|
|
8745
10398
|
async function createRunnerWorkPrompt(apiClient, projectId, workItem) {
|
|
8746
10399
|
if (workItem.workKind === "assistantQuestion") {
|
|
8747
10400
|
const emptyProjectContext = { maps: [], refreshes: [], misses: [] };
|
|
@@ -8798,6 +10451,15 @@ async function createRunnerWorkPrompt(apiClient, projectId, workItem) {
|
|
|
8798
10451
|
appEvaluationScan: { documents: documents2 }
|
|
8799
10452
|
});
|
|
8800
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
|
+
}
|
|
8801
10463
|
if (workItem.workKind === "projectContextRefresh") {
|
|
8802
10464
|
const emptyProjectContext = { maps: [], refreshes: [], misses: [] };
|
|
8803
10465
|
const [{ documents: documents2 }, context] = await Promise.all([
|
|
@@ -9285,15 +10947,17 @@ function toRunnerToolCapabilities(tools) {
|
|
|
9285
10947
|
supportsGitWorktreeIsolation: true
|
|
9286
10948
|
}));
|
|
9287
10949
|
}
|
|
9288
|
-
function runnerIsolationCapabilityMetadata() {
|
|
10950
|
+
function runnerIsolationCapabilityMetadata(laneMetadata = {}) {
|
|
9289
10951
|
return {
|
|
9290
10952
|
machineId: runnerMachineId(),
|
|
9291
10953
|
supportedWorkKinds: runnerSupportedWorkKinds,
|
|
9292
10954
|
supportsBranchIsolation: true,
|
|
9293
|
-
supportsGitWorktreeIsolation: true
|
|
10955
|
+
supportsGitWorktreeIsolation: true,
|
|
10956
|
+
...laneMetadata.claimLaneId ? { claimLaneId: laneMetadata.claimLaneId } : {},
|
|
10957
|
+
...laneMetadata.maxConcurrentWork !== void 0 ? { maxConcurrentWork: laneMetadata.maxConcurrentWork } : {}
|
|
9294
10958
|
};
|
|
9295
10959
|
}
|
|
9296
|
-
function runnerHeartbeatMetadata(toolConfig, mode = currentRunnerMode()) {
|
|
10960
|
+
function runnerHeartbeatMetadata(toolConfig, mode = currentRunnerMode(), concurrencyMetadata = { maxConcurrentWork: 1, activeClaimLaneIds: ["default"] }) {
|
|
9297
10961
|
const modelConfig = toolConfig ? toolConfigModelOptions(toolConfig) : {};
|
|
9298
10962
|
const effectiveModelConfig = toolConfig?.ready ? modelConfig : {};
|
|
9299
10963
|
return {
|
|
@@ -9301,6 +10965,8 @@ function runnerHeartbeatMetadata(toolConfig, mode = currentRunnerMode()) {
|
|
|
9301
10965
|
mode,
|
|
9302
10966
|
hostname: os8.hostname(),
|
|
9303
10967
|
...runnerIsolationCapabilityMetadata(),
|
|
10968
|
+
maxConcurrentWork: concurrencyMetadata.maxConcurrentWork,
|
|
10969
|
+
activeClaimLaneIds: concurrencyMetadata.activeClaimLaneIds,
|
|
9304
10970
|
resourceUsage: sampleCurrentRunnerResourceUsage(),
|
|
9305
10971
|
...toolConfig?.capabilities ? { capabilities: toolConfig.capabilities } : {},
|
|
9306
10972
|
...toolConfig?.requestedTool ? { requestedTool: toolConfig.requestedTool } : {},
|