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