@h9-foundry/agentforge-cli 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/bin.js +17 -5
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +482 -21
- package/dist/index.js.map +1 -1
- package/dist/internal/builtin-agents.d.ts.map +1 -1
- package/dist/internal/builtin-agents.js +1351 -37
- package/dist/internal/builtin-agents.js.map +1 -1
- package/package.json +8 -8
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { agentManifestSchema, agentOutputSchema, designArtifactSchema, githubActionsEvidenceSchema, implementationArtifactSchema, implementationInventorySchema, incidentArtifactSchema, incidentEvidenceNormalizationSchema, incidentRequestSchema, maintenanceArtifactSchema, maintenanceEvidenceNormalizationSchema, maintenanceRequestSchema, qaArtifactSchema, qaEvidenceNormalizationSchema, qaRequestSchema, releaseApprovalRecommendationSchema, releaseArtifactSchema, releaseEvidenceNormalizationSchema, releaseRequestSchema, securityArtifactSchema, securityEvidenceNormalizationSchema, securityRequestSchema, planningArtifactSchema } from "@h9-foundry/agentforge-schemas";
|
|
3
|
+
import { agentManifestSchema, agentOutputSchema, attestationVerificationEvidenceSchema, buildkiteCiEvidenceExportSchema, ciEvidenceSchema, deploymentGateArtifactSchema, deploymentGateEvidenceNormalizationSchema, deploymentRequestSchema, dependencyIntegrityEvidenceSchema, genericCiEvidenceExportSchema, designArtifactSchema, githubActionsEvidenceSchema, gitlabCiEvidenceExportSchema, jenkinsCiEvidenceExportSchema, implementationArtifactSchema, implementationInventorySchema, incidentArtifactSchema, incidentEvidenceNormalizationSchema, incidentRequestSchema, maintenanceArtifactSchema, maintenanceEvidenceNormalizationSchema, maintenanceRequestSchema, pipelineArtifactSchema, pipelineEvidenceNormalizationSchema, pipelineRequestSchema, qaArtifactSchema, qaEvidenceNormalizationSchema, qaRequestSchema, releaseApprovalRecommendationSchema, releaseArtifactSchema, releaseCiEvidenceSummarySchema, releaseEvidenceNormalizationSchema, releaseRequestSchema, securityArtifactSchema, securityEvidenceNormalizationSchema, securityRequestSchema, planningArtifactSchema } from "@h9-foundry/agentforge-schemas";
|
|
4
4
|
const contextCollectorAgent = {
|
|
5
5
|
manifest: agentManifestSchema.parse({
|
|
6
6
|
version: 1,
|
|
@@ -99,7 +99,14 @@ function resolveWorkspacePackage(root, packageName) {
|
|
|
99
99
|
}
|
|
100
100
|
return {};
|
|
101
101
|
}
|
|
102
|
+
const dependencyManifestSections = [
|
|
103
|
+
"dependencies",
|
|
104
|
+
"devDependencies",
|
|
105
|
+
"peerDependencies",
|
|
106
|
+
"optionalDependencies"
|
|
107
|
+
];
|
|
102
108
|
const allowedValidationScriptNames = new Set(["test", "lint", "typecheck", "build", "build:packages", "release:verify"]);
|
|
109
|
+
const fallbackValidationPackageManagers = ["pnpm", "npm", "yarn"];
|
|
103
110
|
function normalizeRequestedCommand(command) {
|
|
104
111
|
return command.trim().replace(/\s+/g, " ");
|
|
105
112
|
}
|
|
@@ -109,20 +116,29 @@ function buildValidationCommand(packageManager, scriptName, packageName) {
|
|
|
109
116
|
}
|
|
110
117
|
return `${packageManager} ${scriptName}`;
|
|
111
118
|
}
|
|
119
|
+
function resolveValidationCommandManagers(packageManager, packageName) {
|
|
120
|
+
if (packageManager !== "unknown") {
|
|
121
|
+
return [packageManager];
|
|
122
|
+
}
|
|
123
|
+
// Generic repos without lockfiles still need deterministic command matching for bounded root scripts.
|
|
124
|
+
return packageName ? ["pnpm"] : fallbackValidationPackageManagers;
|
|
125
|
+
}
|
|
112
126
|
function collectValidationCommands(repoRoot, packageManager, packageScopes) {
|
|
113
127
|
const discoveredValidationCommands = [];
|
|
114
128
|
const registerScripts = (packageJsonPath, source, packageName) => {
|
|
115
129
|
const scripts = parsePackageScripts(packageJsonPath);
|
|
116
130
|
for (const scriptName of Object.keys(scripts)) {
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
131
|
+
for (const commandPackageManager of resolveValidationCommandManagers(packageManager, packageName)) {
|
|
132
|
+
const command = buildValidationCommand(commandPackageManager, scriptName, packageName);
|
|
133
|
+
discoveredValidationCommands.push({
|
|
134
|
+
command,
|
|
135
|
+
source,
|
|
136
|
+
classification: allowedValidationScriptNames.has(scriptName) ? "approval_required" : "deny",
|
|
137
|
+
reason: allowedValidationScriptNames.has(scriptName)
|
|
138
|
+
? "Discovered from a bounded repository script; execution would still require approval."
|
|
139
|
+
: "Command is not in the bounded allowlist for workflow validation."
|
|
140
|
+
});
|
|
141
|
+
}
|
|
126
142
|
}
|
|
127
143
|
};
|
|
128
144
|
if (!repoRoot) {
|
|
@@ -140,6 +156,183 @@ function collectValidationCommands(repoRoot, packageManager, packageScopes) {
|
|
|
140
156
|
}
|
|
141
157
|
return discoveredValidationCommands;
|
|
142
158
|
}
|
|
159
|
+
function findDependencyLockfile(repoRoot, packageManager) {
|
|
160
|
+
if (!repoRoot) {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
const orderedCandidates = packageManager === "pnpm"
|
|
164
|
+
? ["pnpm-lock.yaml", "package-lock.json", "yarn.lock"]
|
|
165
|
+
: packageManager === "npm"
|
|
166
|
+
? ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]
|
|
167
|
+
: packageManager === "yarn"
|
|
168
|
+
? ["yarn.lock", "pnpm-lock.yaml", "package-lock.json"]
|
|
169
|
+
: ["pnpm-lock.yaml", "package-lock.json", "yarn.lock"];
|
|
170
|
+
return orderedCandidates.find((lockfilePath) => existsSync(join(repoRoot, lockfilePath)));
|
|
171
|
+
}
|
|
172
|
+
function readPackageManifestDependencies(repoRoot, manifestPath) {
|
|
173
|
+
if (!repoRoot) {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
const absolutePath = join(repoRoot, manifestPath);
|
|
177
|
+
if (!existsSync(absolutePath)) {
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
const parsed = JSON.parse(readFileSync(absolutePath, "utf8"));
|
|
181
|
+
if (!isRecord(parsed)) {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
const packageName = typeof parsed.name === "string" && parsed.name.length > 0 ? parsed.name : manifestPath.replace(/\/package\.json$/, "");
|
|
185
|
+
const dependencyEntries = [];
|
|
186
|
+
for (const section of dependencyManifestSections) {
|
|
187
|
+
const sectionValue = parsed[section];
|
|
188
|
+
if (!isRecord(sectionValue)) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
for (const [dependencyName, requestedVersion] of Object.entries(sectionValue)) {
|
|
192
|
+
if (typeof requestedVersion !== "string") {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
dependencyEntries.push({
|
|
196
|
+
manifestPath,
|
|
197
|
+
packageName,
|
|
198
|
+
dependencyName,
|
|
199
|
+
dependencyType: section,
|
|
200
|
+
requestedVersion
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
packageName,
|
|
206
|
+
dependencyEntries
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function resolveDependencyManifestPaths(repoRoot, candidatePaths) {
|
|
210
|
+
if (!repoRoot) {
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
const normalizedCandidates = candidatePaths.length > 0
|
|
214
|
+
? candidatePaths
|
|
215
|
+
: existsSync(join(repoRoot, "package.json"))
|
|
216
|
+
? ["package.json"]
|
|
217
|
+
: [];
|
|
218
|
+
const manifestPaths = normalizedCandidates.flatMap((candidatePath) => {
|
|
219
|
+
const normalizedCandidate = candidatePath.replace(/^\.\//, "");
|
|
220
|
+
const manifestPath = normalizedCandidate.endsWith("package.json")
|
|
221
|
+
? normalizedCandidate
|
|
222
|
+
: `${normalizedCandidate.replace(/\/$/, "")}/package.json`;
|
|
223
|
+
return existsSync(join(repoRoot, manifestPath)) ? [manifestPath] : [];
|
|
224
|
+
});
|
|
225
|
+
return [...new Set(manifestPaths)];
|
|
226
|
+
}
|
|
227
|
+
function collectDependencyIntegrityEvidence(repoRoot, packageManager, manifestPaths) {
|
|
228
|
+
if (!repoRoot || manifestPaths.length === 0) {
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
const manifests = manifestPaths
|
|
232
|
+
.map((manifestPath) => readPackageManifestDependencies(repoRoot, manifestPath))
|
|
233
|
+
.filter((manifest) => Boolean(manifest));
|
|
234
|
+
if (manifests.length === 0) {
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
const inventoryEntries = manifests.flatMap((manifest) => manifest.dependencyEntries);
|
|
238
|
+
const packageNames = [...new Set(manifests.map((manifest) => manifest.packageName))];
|
|
239
|
+
const lockfilePath = findDependencyLockfile(repoRoot, packageManager);
|
|
240
|
+
const integrityStatus = lockfilePath
|
|
241
|
+
? "verified-lockfile"
|
|
242
|
+
: inventoryEntries.length === 0
|
|
243
|
+
? "manifest-only"
|
|
244
|
+
: "missing-lockfile";
|
|
245
|
+
return [
|
|
246
|
+
dependencyIntegrityEvidenceSchema.parse({
|
|
247
|
+
inventoryFormat: "workspace-inventory",
|
|
248
|
+
packageManager,
|
|
249
|
+
integrityStatus,
|
|
250
|
+
lockfilePath,
|
|
251
|
+
manifestPaths,
|
|
252
|
+
packageNames,
|
|
253
|
+
packageCount: packageNames.length,
|
|
254
|
+
dependencyEntryCount: inventoryEntries.length,
|
|
255
|
+
inventoryEntries,
|
|
256
|
+
provenanceSource: "workspace-scan",
|
|
257
|
+
provenanceRefs: [...new Set([...manifestPaths, ...(lockfilePath ? [lockfilePath] : [])])]
|
|
258
|
+
})
|
|
259
|
+
];
|
|
260
|
+
}
|
|
261
|
+
function buildDependencyIntegritySignals(evidenceEntries) {
|
|
262
|
+
return evidenceEntries.flatMap((evidence) => {
|
|
263
|
+
const signals = [
|
|
264
|
+
`Dependency inventory covers ${evidence.packageCount} manifest(s) with ${evidence.dependencyEntryCount} declared dependency entr${evidence.dependencyEntryCount === 1 ? "y" : "ies"}.`
|
|
265
|
+
];
|
|
266
|
+
if (evidence.integrityStatus === "verified-lockfile" && evidence.lockfilePath) {
|
|
267
|
+
signals.push(`Workspace dependency integrity is verified against ${evidence.lockfilePath}.`);
|
|
268
|
+
}
|
|
269
|
+
else if (evidence.integrityStatus === "missing-lockfile") {
|
|
270
|
+
signals.push("Workspace dependency manifests were found without a recognized lockfile.");
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
signals.push("Workspace dependency inventory is manifest-only and does not include lockfile verification.");
|
|
274
|
+
}
|
|
275
|
+
return signals;
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
function loadAttestationVerificationEvidence(bundlePath) {
|
|
279
|
+
if (!existsSync(bundlePath) || !bundlePath.endsWith(".json")) {
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
const parsed = JSON.parse(readFileSync(bundlePath, "utf8"));
|
|
283
|
+
const candidate = isRecord(parsed) ? { ...parsed, sourcePath: parsed.sourcePath ?? bundlePath } : parsed;
|
|
284
|
+
const result = attestationVerificationEvidenceSchema.safeParse(candidate);
|
|
285
|
+
return result.success ? result.data : undefined;
|
|
286
|
+
}
|
|
287
|
+
function normalizeAttestationVerificationEvidence(repoRoot, evidenceSources) {
|
|
288
|
+
const seen = new Set();
|
|
289
|
+
const normalized = [];
|
|
290
|
+
for (const pathValue of evidenceSources) {
|
|
291
|
+
if (!repoRoot) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const evidence = loadAttestationVerificationEvidence(join(repoRoot, pathValue));
|
|
295
|
+
if (!evidence) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const key = `${evidence.verifier}:${evidence.subject}:${evidence.status}:${evidence.sourcePath ?? pathValue}`;
|
|
299
|
+
if (seen.has(key)) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
seen.add(key);
|
|
303
|
+
normalized.push(evidence);
|
|
304
|
+
}
|
|
305
|
+
return normalized;
|
|
306
|
+
}
|
|
307
|
+
function buildReleaseTrustSummary(evidenceEntries) {
|
|
308
|
+
if (evidenceEntries.length === 0) {
|
|
309
|
+
return ["Trusted publishing remains reviewed separately; no attestation verification evidence was supplied."];
|
|
310
|
+
}
|
|
311
|
+
const failedCount = evidenceEntries.filter((entry) => entry.status === "failed").length;
|
|
312
|
+
const verifiedCount = evidenceEntries.filter((entry) => entry.status === "verified").length;
|
|
313
|
+
const skippedCount = evidenceEntries.filter((entry) => entry.status === "skipped").length;
|
|
314
|
+
const summary = [];
|
|
315
|
+
if (verifiedCount > 0) {
|
|
316
|
+
summary.push(`Verified ${verifiedCount} attestation or provenance evidence export${verifiedCount === 1 ? "" : "s"}.`);
|
|
317
|
+
}
|
|
318
|
+
if (failedCount > 0) {
|
|
319
|
+
summary.push(`Detected ${failedCount} attestation verification failure${failedCount === 1 ? "" : "s"} that require release follow-up.`);
|
|
320
|
+
}
|
|
321
|
+
if (skippedCount > 0) {
|
|
322
|
+
summary.push(`Skipped ${skippedCount} attestation verification evidence export${skippedCount === 1 ? "" : "s"} based on the supplied local evidence.`);
|
|
323
|
+
}
|
|
324
|
+
summary.push("Trusted publishing remains reviewed separately from bounded attestation verification.");
|
|
325
|
+
return summary;
|
|
326
|
+
}
|
|
327
|
+
function resolveReleaseTrustStatus(evidenceEntries) {
|
|
328
|
+
if (evidenceEntries.some((entry) => entry.status === "failed")) {
|
|
329
|
+
return "attestation-verification-failed";
|
|
330
|
+
}
|
|
331
|
+
if (evidenceEntries.some((entry) => entry.status === "verified")) {
|
|
332
|
+
return "attestation-verified-trusted-publishing-reviewed-separately";
|
|
333
|
+
}
|
|
334
|
+
return "trusted-publishing-reviewed-separately";
|
|
335
|
+
}
|
|
143
336
|
function loadBundleArtifactKinds(bundlePath) {
|
|
144
337
|
if (!existsSync(bundlePath)) {
|
|
145
338
|
return [];
|
|
@@ -152,6 +345,82 @@ function loadBundleArtifactKinds(bundlePath) {
|
|
|
152
345
|
.map((artifact) => (isRecord(artifact) && typeof artifact.artifactKind === "string" ? artifact.artifactKind : undefined))
|
|
153
346
|
.filter((artifactKind) => Boolean(artifactKind));
|
|
154
347
|
}
|
|
348
|
+
function loadBundleLifecycleArtifact(bundlePath, artifactKind) {
|
|
349
|
+
if (!existsSync(bundlePath)) {
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
352
|
+
const parsed = JSON.parse(readFileSync(bundlePath, "utf8"));
|
|
353
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.lifecycleArtifacts)) {
|
|
354
|
+
return undefined;
|
|
355
|
+
}
|
|
356
|
+
return parsed.lifecycleArtifacts.find((artifact) => isRecord(artifact) && artifact.artifactKind === artifactKind);
|
|
357
|
+
}
|
|
358
|
+
function evaluateReleaseReportReadiness(bundlePath) {
|
|
359
|
+
const artifact = loadBundleLifecycleArtifact(bundlePath, "release-report");
|
|
360
|
+
if (!artifact) {
|
|
361
|
+
return {
|
|
362
|
+
ready: false,
|
|
363
|
+
detail: `Release report bundle is missing a release-report artifact: ${bundlePath}`
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
const parsed = releaseArtifactSchema.safeParse(artifact);
|
|
367
|
+
if (!parsed.success) {
|
|
368
|
+
return {
|
|
369
|
+
ready: false,
|
|
370
|
+
detail: `Release report bundle could not be parsed as a bounded release-report artifact: ${bundlePath}`
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
const releaseArtifact = parsed.data;
|
|
374
|
+
if (releaseArtifact.status !== "complete") {
|
|
375
|
+
return {
|
|
376
|
+
ready: false,
|
|
377
|
+
detail: `Release report ${bundlePath} is ${releaseArtifact.status} and cannot satisfy a deployment gate yet.`
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
if (releaseArtifact.payload.readinessStatus !== "ready") {
|
|
381
|
+
return {
|
|
382
|
+
ready: false,
|
|
383
|
+
detail: `Release report ${bundlePath} is ${releaseArtifact.payload.readinessStatus} and cannot satisfy a deployment gate yet.`
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
ready: true,
|
|
388
|
+
detail: `Using ${bundlePath} as a ready release-report reference.`
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
function evaluatePipelineReportReadiness(bundlePath) {
|
|
392
|
+
const artifact = loadBundleLifecycleArtifact(bundlePath, "pipeline-report");
|
|
393
|
+
if (!artifact) {
|
|
394
|
+
return {
|
|
395
|
+
ready: false,
|
|
396
|
+
detail: `Pipeline report bundle is missing a pipeline-report artifact: ${bundlePath}`
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
const parsed = pipelineArtifactSchema.safeParse(artifact);
|
|
400
|
+
if (!parsed.success) {
|
|
401
|
+
return {
|
|
402
|
+
ready: false,
|
|
403
|
+
detail: `Pipeline report bundle could not be parsed as a bounded pipeline-report artifact: ${bundlePath}`
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
const pipelineArtifact = parsed.data;
|
|
407
|
+
if (pipelineArtifact.status !== "complete") {
|
|
408
|
+
return {
|
|
409
|
+
ready: false,
|
|
410
|
+
detail: `Pipeline report ${bundlePath} is ${pipelineArtifact.status} and cannot satisfy a deployment gate yet.`
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
if (pipelineArtifact.payload.reviewStatus !== "ready") {
|
|
414
|
+
return {
|
|
415
|
+
ready: false,
|
|
416
|
+
detail: `Pipeline report ${bundlePath} is ${pipelineArtifact.payload.reviewStatus} and cannot satisfy a deployment gate yet.`
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
return {
|
|
420
|
+
ready: true,
|
|
421
|
+
detail: `Using ${bundlePath} as a ready pipeline-report reference.`
|
|
422
|
+
};
|
|
423
|
+
}
|
|
155
424
|
function loadBundleArtifactPayloadPaths(bundlePath) {
|
|
156
425
|
if (!existsSync(bundlePath)) {
|
|
157
426
|
return [];
|
|
@@ -247,6 +516,292 @@ function normalizeGitHubActionsEvidence(repoRoot, evidenceSources) {
|
|
|
247
516
|
provenanceRefs
|
|
248
517
|
};
|
|
249
518
|
}
|
|
519
|
+
function mapGitHubActionsEvidenceToCiEvidence(evidence) {
|
|
520
|
+
return ciEvidenceSchema.parse({
|
|
521
|
+
platform: "github-actions",
|
|
522
|
+
providerName: "GitHub Actions",
|
|
523
|
+
host: "github.com",
|
|
524
|
+
repository: evidence.repository,
|
|
525
|
+
pipelineName: evidence.workflowName,
|
|
526
|
+
pipelineRunId: `${evidence.workflowRunId}`,
|
|
527
|
+
runAttempt: evidence.runAttempt,
|
|
528
|
+
event: evidence.event,
|
|
529
|
+
branch: evidence.headBranch,
|
|
530
|
+
commitSha: evidence.headSha,
|
|
531
|
+
status: evidence.status,
|
|
532
|
+
conclusion: evidence.conclusion,
|
|
533
|
+
htmlUrl: evidence.htmlUrl,
|
|
534
|
+
jobs: evidence.jobs,
|
|
535
|
+
artifacts: [],
|
|
536
|
+
provenanceSource: "local-export"
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
function loadGitLabCiEvidenceExport(bundlePath) {
|
|
540
|
+
if (!existsSync(bundlePath) || !bundlePath.endsWith(".json")) {
|
|
541
|
+
return undefined;
|
|
542
|
+
}
|
|
543
|
+
const parsed = JSON.parse(readFileSync(bundlePath, "utf8"));
|
|
544
|
+
const candidate = isRecord(parsed) ? { ...parsed, sourcePath: parsed.sourcePath ?? bundlePath } : parsed;
|
|
545
|
+
const result = gitlabCiEvidenceExportSchema.safeParse(candidate);
|
|
546
|
+
return result.success ? result.data : undefined;
|
|
547
|
+
}
|
|
548
|
+
function loadBuildkiteCiEvidenceExport(bundlePath) {
|
|
549
|
+
if (!existsSync(bundlePath) || !bundlePath.endsWith(".json")) {
|
|
550
|
+
return undefined;
|
|
551
|
+
}
|
|
552
|
+
const parsed = JSON.parse(readFileSync(bundlePath, "utf8"));
|
|
553
|
+
const candidate = isRecord(parsed) ? { ...parsed, sourcePath: parsed.sourcePath ?? bundlePath } : parsed;
|
|
554
|
+
const result = buildkiteCiEvidenceExportSchema.safeParse(candidate);
|
|
555
|
+
return result.success ? result.data : undefined;
|
|
556
|
+
}
|
|
557
|
+
function loadGenericCiEvidenceExport(bundlePath) {
|
|
558
|
+
if (!existsSync(bundlePath) || !bundlePath.endsWith(".json")) {
|
|
559
|
+
return undefined;
|
|
560
|
+
}
|
|
561
|
+
const parsed = JSON.parse(readFileSync(bundlePath, "utf8"));
|
|
562
|
+
const candidate = isRecord(parsed) ? { ...parsed, sourcePath: parsed.sourcePath ?? bundlePath } : parsed;
|
|
563
|
+
const result = genericCiEvidenceExportSchema.safeParse(candidate);
|
|
564
|
+
return result.success ? result.data : undefined;
|
|
565
|
+
}
|
|
566
|
+
function loadJenkinsCiEvidenceExport(bundlePath) {
|
|
567
|
+
if (!existsSync(bundlePath) || !bundlePath.endsWith(".json")) {
|
|
568
|
+
return undefined;
|
|
569
|
+
}
|
|
570
|
+
const parsed = JSON.parse(readFileSync(bundlePath, "utf8"));
|
|
571
|
+
const candidate = isRecord(parsed) ? { ...parsed, sourcePath: parsed.sourcePath ?? bundlePath } : parsed;
|
|
572
|
+
const result = jenkinsCiEvidenceExportSchema.safeParse(candidate);
|
|
573
|
+
return result.success ? result.data : undefined;
|
|
574
|
+
}
|
|
575
|
+
function mapGitLabCiStatus(status) {
|
|
576
|
+
switch (status) {
|
|
577
|
+
case "pending":
|
|
578
|
+
return { status: "queued" };
|
|
579
|
+
case "running":
|
|
580
|
+
return { status: "in_progress" };
|
|
581
|
+
case "success":
|
|
582
|
+
return { status: "completed", conclusion: "success" };
|
|
583
|
+
case "failed":
|
|
584
|
+
return { status: "completed", conclusion: "failure" };
|
|
585
|
+
case "canceled":
|
|
586
|
+
return { status: "completed", conclusion: "cancelled" };
|
|
587
|
+
case "skipped":
|
|
588
|
+
return { status: "completed", conclusion: "skipped" };
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
function normalizeGitLabCiEvidence(repoRoot, evidenceSources) {
|
|
592
|
+
return evidenceSources.flatMap((pathValue) => {
|
|
593
|
+
if (!repoRoot) {
|
|
594
|
+
return [];
|
|
595
|
+
}
|
|
596
|
+
const normalized = loadGitLabCiEvidenceExport(join(repoRoot, pathValue));
|
|
597
|
+
if (!normalized) {
|
|
598
|
+
return [];
|
|
599
|
+
}
|
|
600
|
+
const pipelineStatus = mapGitLabCiStatus(normalized.status);
|
|
601
|
+
const jobs = normalized.jobs.map((job) => {
|
|
602
|
+
const jobStatus = mapGitLabCiStatus(job.status);
|
|
603
|
+
return {
|
|
604
|
+
name: job.name,
|
|
605
|
+
status: jobStatus.status,
|
|
606
|
+
conclusion: jobStatus.conclusion,
|
|
607
|
+
htmlUrl: job.webUrl,
|
|
608
|
+
startedAt: job.startedAt,
|
|
609
|
+
completedAt: job.completedAt
|
|
610
|
+
};
|
|
611
|
+
});
|
|
612
|
+
const evidence = ciEvidenceSchema.parse({
|
|
613
|
+
platform: "gitlab-ci",
|
|
614
|
+
host: normalized.host,
|
|
615
|
+
repository: normalized.projectPath,
|
|
616
|
+
pipelineName: normalized.pipelineName,
|
|
617
|
+
pipelineRunId: `${normalized.pipelineId}`,
|
|
618
|
+
runAttempt: normalized.runAttempt,
|
|
619
|
+
event: normalized.event,
|
|
620
|
+
branch: normalized.branch,
|
|
621
|
+
commitSha: normalized.commitSha,
|
|
622
|
+
status: pipelineStatus.status,
|
|
623
|
+
conclusion: pipelineStatus.conclusion,
|
|
624
|
+
htmlUrl: normalized.webUrl,
|
|
625
|
+
jobs,
|
|
626
|
+
artifacts: [],
|
|
627
|
+
provenanceSource: "local-export"
|
|
628
|
+
});
|
|
629
|
+
return [evidence];
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
function normalizeGenericCiEvidence(repoRoot, evidenceSources) {
|
|
633
|
+
return evidenceSources.flatMap((pathValue) => {
|
|
634
|
+
if (!repoRoot) {
|
|
635
|
+
return [];
|
|
636
|
+
}
|
|
637
|
+
const normalized = loadGenericCiEvidenceExport(join(repoRoot, pathValue));
|
|
638
|
+
if (!normalized) {
|
|
639
|
+
return [];
|
|
640
|
+
}
|
|
641
|
+
const evidence = ciEvidenceSchema.parse({
|
|
642
|
+
platform: "generic-ci",
|
|
643
|
+
providerName: normalized.providerName,
|
|
644
|
+
host: normalized.host,
|
|
645
|
+
repository: normalized.repository,
|
|
646
|
+
pipelineName: normalized.pipelineName,
|
|
647
|
+
pipelineRunId: normalized.pipelineRunId,
|
|
648
|
+
runAttempt: normalized.runAttempt,
|
|
649
|
+
event: normalized.event,
|
|
650
|
+
branch: normalized.branch,
|
|
651
|
+
commitSha: normalized.commitSha,
|
|
652
|
+
status: normalized.status,
|
|
653
|
+
conclusion: normalized.conclusion,
|
|
654
|
+
htmlUrl: normalized.htmlUrl,
|
|
655
|
+
jobs: normalized.jobs,
|
|
656
|
+
artifacts: normalized.artifacts,
|
|
657
|
+
provenanceSource: "local-export"
|
|
658
|
+
});
|
|
659
|
+
return [evidence];
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
function normalizeBuildkiteCiEvidence(repoRoot, evidenceSources) {
|
|
663
|
+
return evidenceSources.flatMap((pathValue) => {
|
|
664
|
+
if (!repoRoot) {
|
|
665
|
+
return [];
|
|
666
|
+
}
|
|
667
|
+
const normalized = loadBuildkiteCiEvidenceExport(join(repoRoot, pathValue));
|
|
668
|
+
if (!normalized) {
|
|
669
|
+
return [];
|
|
670
|
+
}
|
|
671
|
+
const evidence = ciEvidenceSchema.parse({
|
|
672
|
+
platform: "buildkite",
|
|
673
|
+
providerName: "Buildkite",
|
|
674
|
+
host: normalized.host,
|
|
675
|
+
repository: normalized.repository,
|
|
676
|
+
pipelineName: normalized.pipelineName,
|
|
677
|
+
pipelineRunId: normalized.pipelineRunId,
|
|
678
|
+
runAttempt: normalized.runAttempt,
|
|
679
|
+
event: normalized.event,
|
|
680
|
+
branch: normalized.branch,
|
|
681
|
+
commitSha: normalized.commitSha,
|
|
682
|
+
status: normalized.status,
|
|
683
|
+
conclusion: normalized.conclusion,
|
|
684
|
+
htmlUrl: normalized.htmlUrl,
|
|
685
|
+
jobs: normalized.jobs,
|
|
686
|
+
artifacts: normalized.artifacts,
|
|
687
|
+
provenanceSource: "local-export"
|
|
688
|
+
});
|
|
689
|
+
return [evidence];
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
function normalizeJenkinsCiEvidence(repoRoot, evidenceSources) {
|
|
693
|
+
return evidenceSources.flatMap((pathValue) => {
|
|
694
|
+
if (!repoRoot) {
|
|
695
|
+
return [];
|
|
696
|
+
}
|
|
697
|
+
const normalized = loadJenkinsCiEvidenceExport(join(repoRoot, pathValue));
|
|
698
|
+
if (!normalized) {
|
|
699
|
+
return [];
|
|
700
|
+
}
|
|
701
|
+
const evidence = ciEvidenceSchema.parse({
|
|
702
|
+
platform: "jenkins-ci",
|
|
703
|
+
providerName: "Jenkins",
|
|
704
|
+
host: normalized.host,
|
|
705
|
+
repository: normalized.repository,
|
|
706
|
+
pipelineName: normalized.pipelineName,
|
|
707
|
+
pipelineRunId: normalized.pipelineRunId,
|
|
708
|
+
runAttempt: normalized.runAttempt,
|
|
709
|
+
event: normalized.event,
|
|
710
|
+
branch: normalized.branch,
|
|
711
|
+
commitSha: normalized.commitSha,
|
|
712
|
+
status: normalized.status,
|
|
713
|
+
conclusion: normalized.conclusion,
|
|
714
|
+
htmlUrl: normalized.htmlUrl,
|
|
715
|
+
jobs: normalized.jobs,
|
|
716
|
+
artifacts: normalized.artifacts,
|
|
717
|
+
provenanceSource: "local-export"
|
|
718
|
+
});
|
|
719
|
+
return [evidence];
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
function normalizeImportedCiEvidence(repoRoot, evidenceSources) {
|
|
723
|
+
const seen = new Set();
|
|
724
|
+
const normalized = [];
|
|
725
|
+
for (const pathValue of evidenceSources) {
|
|
726
|
+
if (!repoRoot) {
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
const absolutePath = join(repoRoot, pathValue);
|
|
730
|
+
if (!existsSync(absolutePath) || !absolutePath.endsWith(".json")) {
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
const evidence = (() => {
|
|
734
|
+
const githubActions = loadGitHubActionsEvidence(absolutePath);
|
|
735
|
+
if (githubActions) {
|
|
736
|
+
return mapGitHubActionsEvidenceToCiEvidence(githubActions);
|
|
737
|
+
}
|
|
738
|
+
const gitlab = loadGitLabCiEvidenceExport(absolutePath);
|
|
739
|
+
if (gitlab) {
|
|
740
|
+
return normalizeGitLabCiEvidence(repoRoot, [pathValue])[0];
|
|
741
|
+
}
|
|
742
|
+
const buildkite = loadBuildkiteCiEvidenceExport(absolutePath);
|
|
743
|
+
if (buildkite) {
|
|
744
|
+
return normalizeBuildkiteCiEvidence(repoRoot, [pathValue])[0];
|
|
745
|
+
}
|
|
746
|
+
const jenkins = loadJenkinsCiEvidenceExport(absolutePath);
|
|
747
|
+
if (jenkins) {
|
|
748
|
+
return normalizeJenkinsCiEvidence(repoRoot, [pathValue])[0];
|
|
749
|
+
}
|
|
750
|
+
const generic = loadGenericCiEvidenceExport(absolutePath);
|
|
751
|
+
if (generic) {
|
|
752
|
+
return normalizeGenericCiEvidence(repoRoot, [pathValue])[0];
|
|
753
|
+
}
|
|
754
|
+
return undefined;
|
|
755
|
+
})();
|
|
756
|
+
if (!evidence) {
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
const key = `${evidence.platform}:${evidence.host}:${evidence.pipelineRunId}:${evidence.pipelineName}:${evidence.repository}`;
|
|
760
|
+
if (seen.has(key)) {
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
seen.add(key);
|
|
764
|
+
normalized.push(evidence);
|
|
765
|
+
}
|
|
766
|
+
return normalized;
|
|
767
|
+
}
|
|
768
|
+
function summarizeCiEvidenceFailures(evidence) {
|
|
769
|
+
const failedJobs = evidence.jobs
|
|
770
|
+
.filter((job) => job.status === "completed" && isFailingGitHubActionsConclusion(job.conclusion))
|
|
771
|
+
.map((job) => `${evidence.pipelineName} / ${job.name}`);
|
|
772
|
+
const runLevelFailure = failedJobs.length === 0 &&
|
|
773
|
+
evidence.status === "completed" &&
|
|
774
|
+
isFailingGitHubActionsConclusion(evidence.conclusion)
|
|
775
|
+
? [`${evidence.pipelineName} / pipeline-run`]
|
|
776
|
+
: [];
|
|
777
|
+
return [...failedJobs, ...runLevelFailure];
|
|
778
|
+
}
|
|
779
|
+
function formatCiEvidenceStatus(evidence) {
|
|
780
|
+
if (evidence.status === "completed" && evidence.conclusion) {
|
|
781
|
+
return evidence.conclusion;
|
|
782
|
+
}
|
|
783
|
+
return evidence.status;
|
|
784
|
+
}
|
|
785
|
+
function summarizeCiEvidenceForRelease(evidence) {
|
|
786
|
+
const provider = evidence.providerName ?? evidence.pipelineName;
|
|
787
|
+
const displayLabel = `${provider} (${evidence.platform}) pipeline \`${evidence.pipelineName}\` run \`${evidence.pipelineRunId}\``;
|
|
788
|
+
return releaseCiEvidenceSummarySchema.parse({
|
|
789
|
+
provider,
|
|
790
|
+
platform: evidence.platform,
|
|
791
|
+
host: evidence.host,
|
|
792
|
+
repository: evidence.repository,
|
|
793
|
+
pipelineName: evidence.pipelineName,
|
|
794
|
+
pipelineRunId: evidence.pipelineRunId,
|
|
795
|
+
status: evidence.status,
|
|
796
|
+
conclusion: evidence.conclusion,
|
|
797
|
+
branch: evidence.branch,
|
|
798
|
+
commitSha: evidence.commitSha,
|
|
799
|
+
failingChecks: summarizeCiEvidenceFailures(evidence),
|
|
800
|
+
provenanceSource: evidence.provenanceSource,
|
|
801
|
+
displayLabel,
|
|
802
|
+
statusSummary: `${displayLabel} completed from ${evidence.provenanceSource} evidence with ${formatCiEvidenceStatus(evidence)}.`
|
|
803
|
+
});
|
|
804
|
+
}
|
|
250
805
|
function derivePackageScope(pathValue) {
|
|
251
806
|
const segments = pathValue.split("/").filter(Boolean);
|
|
252
807
|
if (segments.length < 2) {
|
|
@@ -268,7 +823,7 @@ function getWorkflowInput(stateSlice, key) {
|
|
|
268
823
|
}
|
|
269
824
|
return stateSlice.workflowInputs[key];
|
|
270
825
|
}
|
|
271
|
-
function buildLifecycleArtifactEnvelopeBase(state, displayName, summary, inputRefs, issueRefs = [], githubRefs = []) {
|
|
826
|
+
function buildLifecycleArtifactEnvelopeBase(state, displayName, summary, inputRefs, issueRefs = [], scmRefs = [], githubRefs = []) {
|
|
272
827
|
return {
|
|
273
828
|
schemaVersion: state.version,
|
|
274
829
|
workflow: {
|
|
@@ -280,6 +835,7 @@ function buildLifecycleArtifactEnvelopeBase(state, displayName, summary, inputRe
|
|
|
280
835
|
runId: state.runId,
|
|
281
836
|
inputRefs: [...inputRefs],
|
|
282
837
|
issueRefs: [...issueRefs],
|
|
838
|
+
scmRefs: [...scmRefs],
|
|
283
839
|
githubRefs: [...githubRefs]
|
|
284
840
|
},
|
|
285
841
|
status: "complete",
|
|
@@ -308,7 +864,7 @@ function buildLifecycleArtifactEnvelopeBase(state, displayName, summary, inputRe
|
|
|
308
864
|
summary
|
|
309
865
|
};
|
|
310
866
|
}
|
|
311
|
-
function buildArtifactEnvelopeBase(state, summary, inputRefs, issueRefs, githubRefs = []) {
|
|
867
|
+
function buildArtifactEnvelopeBase(state, summary, inputRefs, issueRefs, scmRefs = [], githubRefs = []) {
|
|
312
868
|
return {
|
|
313
869
|
schemaVersion: state.version,
|
|
314
870
|
workflow: {
|
|
@@ -319,6 +875,7 @@ function buildArtifactEnvelopeBase(state, summary, inputRefs, issueRefs, githubR
|
|
|
319
875
|
runId: state.runId,
|
|
320
876
|
inputRefs: [...inputRefs],
|
|
321
877
|
issueRefs: [...issueRefs],
|
|
878
|
+
scmRefs: [...scmRefs],
|
|
322
879
|
githubRefs: [...githubRefs]
|
|
323
880
|
},
|
|
324
881
|
status: "complete",
|
|
@@ -445,6 +1002,7 @@ const planningAnalystAgent = {
|
|
|
445
1002
|
outputSchema: agentOutputSchema,
|
|
446
1003
|
async execute({ state, stateSlice }) {
|
|
447
1004
|
const planningRequest = getWorkflowInput(stateSlice, "planningRequest");
|
|
1005
|
+
const planningScmRefs = getWorkflowInput(stateSlice, "planningScmRefs") ?? [];
|
|
448
1006
|
const planningGithubRefs = getWorkflowInput(stateSlice, "planningGithubRefs") ?? [];
|
|
449
1007
|
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
450
1008
|
if (!planningRequest) {
|
|
@@ -469,7 +1027,7 @@ const planningAnalystAgent = {
|
|
|
469
1027
|
];
|
|
470
1028
|
const summary = `Planning brief scoped ${objectives.length} objective(s) for ${state.repo.name}.`;
|
|
471
1029
|
const planningBrief = planningArtifactSchema.parse({
|
|
472
|
-
...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/planning.yaml"], planningRequest.issueRefs, planningGithubRefs),
|
|
1030
|
+
...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/planning.yaml"], planningRequest.issueRefs, planningScmRefs, planningGithubRefs),
|
|
473
1031
|
artifactKind: "planning-brief",
|
|
474
1032
|
lifecycleDomain: "plan",
|
|
475
1033
|
workflow: {
|
|
@@ -1064,6 +1622,7 @@ const maintenanceIntakeAgent = {
|
|
|
1064
1622
|
const maintenanceRequest = getWorkflowInput(stateSlice, "maintenanceRequest");
|
|
1065
1623
|
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
1066
1624
|
const maintenanceIssueRefs = getWorkflowInput(stateSlice, "maintenanceIssueRefs") ?? [];
|
|
1625
|
+
const maintenanceScmRefs = getWorkflowInput(stateSlice, "maintenanceScmRefs") ?? [];
|
|
1067
1626
|
const maintenanceGithubRefs = getWorkflowInput(stateSlice, "maintenanceGithubRefs") ?? [];
|
|
1068
1627
|
if (!maintenanceRequest) {
|
|
1069
1628
|
throw new Error("maintenance-triage requires a validated maintenance request before runtime execution.");
|
|
@@ -1084,6 +1643,7 @@ const maintenanceIntakeAgent = {
|
|
|
1084
1643
|
issueRefs: [...new Set(maintenanceRequest.issueRefs)]
|
|
1085
1644
|
}),
|
|
1086
1645
|
maintenanceIssueRefs,
|
|
1646
|
+
maintenanceScmRefs,
|
|
1087
1647
|
maintenanceGithubRefs,
|
|
1088
1648
|
evidenceSourceCount: maintenanceRequest.dependencyAlertRefs.length +
|
|
1089
1649
|
maintenanceRequest.docsTaskRefs.length +
|
|
@@ -1256,6 +1816,7 @@ const maintenanceAnalystAgent = {
|
|
|
1256
1816
|
async execute({ state, stateSlice }) {
|
|
1257
1817
|
const maintenanceRequest = getWorkflowInput(stateSlice, "maintenanceRequest");
|
|
1258
1818
|
const maintenanceIssueRefs = getWorkflowInput(stateSlice, "maintenanceIssueRefs") ?? [];
|
|
1819
|
+
const maintenanceScmRefs = getWorkflowInput(stateSlice, "maintenanceScmRefs") ?? [];
|
|
1259
1820
|
const maintenanceGithubRefs = getWorkflowInput(stateSlice, "maintenanceGithubRefs") ?? [];
|
|
1260
1821
|
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
1261
1822
|
if (!maintenanceRequest) {
|
|
@@ -1320,7 +1881,7 @@ const maintenanceAnalystAgent = {
|
|
|
1320
1881
|
];
|
|
1321
1882
|
const summary = `Maintenance report prepared for ${maintenanceRequest.maintenanceGoal}.`;
|
|
1322
1883
|
const maintenanceReport = maintenanceArtifactSchema.parse({
|
|
1323
|
-
...buildLifecycleArtifactEnvelopeBase(state, "Maintenance Triage", summary, [requestFile ?? ".agentops/requests/maintenance.yaml", ...evidenceSources], maintenanceIssueRefs, maintenanceGithubRefs),
|
|
1884
|
+
...buildLifecycleArtifactEnvelopeBase(state, "Maintenance Triage", summary, [requestFile ?? ".agentops/requests/maintenance.yaml", ...evidenceSources], maintenanceIssueRefs, maintenanceScmRefs, maintenanceGithubRefs),
|
|
1324
1885
|
artifactKind: "maintenance-report",
|
|
1325
1886
|
lifecycleDomain: "maintain",
|
|
1326
1887
|
payload: {
|
|
@@ -1415,6 +1976,7 @@ const incidentAnalystAgent = {
|
|
|
1415
1976
|
const incidentRequest = getWorkflowInput(stateSlice, "incidentRequest");
|
|
1416
1977
|
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
1417
1978
|
const incidentIssueRefs = getWorkflowInput(stateSlice, "incidentIssueRefs") ?? [];
|
|
1979
|
+
const incidentScmRefs = getWorkflowInput(stateSlice, "incidentScmRefs") ?? [];
|
|
1418
1980
|
const incidentGithubRefs = getWorkflowInput(stateSlice, "incidentGithubRefs") ?? [];
|
|
1419
1981
|
if (!incidentRequest) {
|
|
1420
1982
|
throw new Error("incident-handoff requires validated incident inputs before incident analysis.");
|
|
@@ -1457,7 +2019,7 @@ const incidentAnalystAgent = {
|
|
|
1457
2019
|
];
|
|
1458
2020
|
const summary = `Incident brief prepared for ${incidentRequest.incidentSummary}.`;
|
|
1459
2021
|
const incidentBrief = incidentArtifactSchema.parse({
|
|
1460
|
-
...buildLifecycleArtifactEnvelopeBase(state, "Incident Handoff", summary, [requestFile ?? ".agentops/requests/incident.yaml", ...evidenceSources], incidentIssueRefs, incidentGithubRefs),
|
|
2022
|
+
...buildLifecycleArtifactEnvelopeBase(state, "Incident Handoff", summary, [requestFile ?? ".agentops/requests/incident.yaml", ...evidenceSources], incidentIssueRefs, incidentScmRefs, incidentGithubRefs),
|
|
1461
2023
|
artifactKind: "incident-brief",
|
|
1462
2024
|
lifecycleDomain: "operate",
|
|
1463
2025
|
redaction: {
|
|
@@ -1551,6 +2113,7 @@ const releaseIntakeAgent = {
|
|
|
1551
2113
|
async execute({ stateSlice }) {
|
|
1552
2114
|
const releaseRequest = getWorkflowInput(stateSlice, "releaseRequest");
|
|
1553
2115
|
const releaseIssueRefs = getWorkflowInput(stateSlice, "releaseIssueRefs") ?? [];
|
|
2116
|
+
const releaseScmRefs = getWorkflowInput(stateSlice, "releaseScmRefs") ?? [];
|
|
1554
2117
|
const releaseGithubRefs = getWorkflowInput(stateSlice, "releaseGithubRefs") ?? [];
|
|
1555
2118
|
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
1556
2119
|
if (!releaseRequest) {
|
|
@@ -1566,6 +2129,7 @@ const releaseIntakeAgent = {
|
|
|
1566
2129
|
metadata: {
|
|
1567
2130
|
...releaseRequestSchema.parse(releaseRequest),
|
|
1568
2131
|
releaseIssueRefs,
|
|
2132
|
+
releaseScmRefs,
|
|
1569
2133
|
releaseGithubRefs,
|
|
1570
2134
|
evidenceSourceCount: releaseRequest.qaReportRefs.length + releaseRequest.securityReportRefs.length + releaseRequest.evidenceSources.length
|
|
1571
2135
|
}
|
|
@@ -1625,6 +2189,10 @@ const releaseEvidenceNormalizationAgent = {
|
|
|
1625
2189
|
? asStringArray(intakeMetadata.evidenceSources)
|
|
1626
2190
|
: releaseRequest.evidenceSources;
|
|
1627
2191
|
const normalizedEvidenceSources = [...new Set([...qaReportRefs, ...securityReportRefs, ...evidenceSources])];
|
|
2192
|
+
const ciEvidence = normalizeImportedCiEvidence(repoRoot, normalizedEvidenceSources);
|
|
2193
|
+
const ciEvidenceSummary = ciEvidence.map((entry) => summarizeCiEvidenceForRelease(entry));
|
|
2194
|
+
const attestationVerificationEvidence = normalizeAttestationVerificationEvidence(repoRoot, normalizedEvidenceSources);
|
|
2195
|
+
const trustSummary = buildReleaseTrustSummary(attestationVerificationEvidence);
|
|
1628
2196
|
const missingEvidenceSources = normalizedEvidenceSources.filter((pathValue) => repoRoot && !existsSync(join(repoRoot, pathValue)));
|
|
1629
2197
|
if (missingEvidenceSources.length > 0) {
|
|
1630
2198
|
throw new Error(`Release evidence source not found: ${missingEvidenceSources[0]}`);
|
|
@@ -1643,6 +2211,17 @@ const releaseEvidenceNormalizationAgent = {
|
|
|
1643
2211
|
manifestPath: resolved.manifestPath
|
|
1644
2212
|
};
|
|
1645
2213
|
});
|
|
2214
|
+
const dependencyManifestPaths = resolveDependencyManifestPaths(repoRoot, [
|
|
2215
|
+
"package.json",
|
|
2216
|
+
...versionResolutions
|
|
2217
|
+
.map((entry) => entry.manifestPath)
|
|
2218
|
+
.filter((value) => Boolean(value))
|
|
2219
|
+
.map((manifestPath) => manifestPath.replace(`${repoRoot ?? ""}/`, ""))
|
|
2220
|
+
]);
|
|
2221
|
+
const dependencyIntegrityEvidence = collectDependencyIntegrityEvidence(repoRoot, stateSlice.repo?.packageManager || "unknown", dependencyManifestPaths);
|
|
2222
|
+
const dependencyIntegritySignals = buildDependencyIntegritySignals(dependencyIntegrityEvidence);
|
|
2223
|
+
const hasMissingDependencyIntegrity = dependencyIntegrityEvidence.some((entry) => entry.integrityStatus === "missing-lockfile");
|
|
2224
|
+
const hasFailedAttestationVerification = attestationVerificationEvidence.some((entry) => entry.status === "failed");
|
|
1646
2225
|
const missingPackages = versionResolutions.filter((entry) => entry.status === "package-missing").map((entry) => entry.name);
|
|
1647
2226
|
const versionCheckStatus = missingPackages.length === 0 ? "passed" : "failed";
|
|
1648
2227
|
const baseReadinessStatus = qaReportRefs.length > 0 && securityReportRefs.length > 0
|
|
@@ -1650,7 +2229,13 @@ const releaseEvidenceNormalizationAgent = {
|
|
|
1650
2229
|
: qaReportRefs.length > 0 || securityReportRefs.length > 0
|
|
1651
2230
|
? "partial"
|
|
1652
2231
|
: "blocked";
|
|
1653
|
-
const readinessStatus = missingPackages.length > 0
|
|
2232
|
+
const readinessStatus = missingPackages.length > 0
|
|
2233
|
+
? "blocked"
|
|
2234
|
+
: hasFailedAttestationVerification
|
|
2235
|
+
? "blocked"
|
|
2236
|
+
: hasMissingDependencyIntegrity && baseReadinessStatus === "ready"
|
|
2237
|
+
? "partial"
|
|
2238
|
+
: baseReadinessStatus;
|
|
1654
2239
|
const localReadinessChecks = [
|
|
1655
2240
|
{
|
|
1656
2241
|
name: "qa-report-refs",
|
|
@@ -1673,6 +2258,32 @@ const releaseEvidenceNormalizationAgent = {
|
|
|
1673
2258
|
? `Using ${evidenceSources.length} bounded local release evidence source(s).`
|
|
1674
2259
|
: "No additional local release evidence sources were supplied."
|
|
1675
2260
|
},
|
|
2261
|
+
{
|
|
2262
|
+
name: "imported-ci-evidence",
|
|
2263
|
+
status: ciEvidence.length > 0 ? "passed" : "skipped",
|
|
2264
|
+
detail: ciEvidence.length > 0
|
|
2265
|
+
? `Using ${ciEvidence.length} imported CI evidence export(s) across ${[...new Set(ciEvidence.map((entry) => entry.pipelineName))].length} pipeline(s).`
|
|
2266
|
+
: "No imported CI evidence exports were supplied."
|
|
2267
|
+
},
|
|
2268
|
+
{
|
|
2269
|
+
name: "dependency-integrity",
|
|
2270
|
+
status: dependencyIntegrityEvidence.length === 0
|
|
2271
|
+
? "skipped"
|
|
2272
|
+
: hasMissingDependencyIntegrity
|
|
2273
|
+
? "failed"
|
|
2274
|
+
: "passed",
|
|
2275
|
+
detail: dependencyIntegritySignals[0] ??
|
|
2276
|
+
"No dependency manifests were available for bounded integrity verification."
|
|
2277
|
+
},
|
|
2278
|
+
{
|
|
2279
|
+
name: "attestation-verification",
|
|
2280
|
+
status: attestationVerificationEvidence.length === 0
|
|
2281
|
+
? "skipped"
|
|
2282
|
+
: hasFailedAttestationVerification
|
|
2283
|
+
? "failed"
|
|
2284
|
+
: "passed",
|
|
2285
|
+
detail: trustSummary[0]
|
|
2286
|
+
},
|
|
1676
2287
|
{
|
|
1677
2288
|
name: "workspace-version-targets",
|
|
1678
2289
|
status: versionCheckStatus,
|
|
@@ -1706,6 +2317,8 @@ const releaseEvidenceNormalizationAgent = {
|
|
|
1706
2317
|
];
|
|
1707
2318
|
const provenanceRefs = [
|
|
1708
2319
|
...normalizedEvidenceSources,
|
|
2320
|
+
...dependencyIntegrityEvidence.flatMap((entry) => entry.provenanceRefs),
|
|
2321
|
+
...attestationVerificationEvidence.flatMap((entry) => entry.provenanceRefs),
|
|
1709
2322
|
...versionResolutions
|
|
1710
2323
|
.map((entry) => entry.manifestPath)
|
|
1711
2324
|
.filter((value) => Boolean(value))
|
|
@@ -1715,6 +2328,10 @@ const releaseEvidenceNormalizationAgent = {
|
|
|
1715
2328
|
securityReportRefs,
|
|
1716
2329
|
normalizedEvidenceSources,
|
|
1717
2330
|
missingEvidenceSources: [],
|
|
2331
|
+
ciEvidence,
|
|
2332
|
+
ciEvidenceSummary,
|
|
2333
|
+
dependencyIntegrityEvidence,
|
|
2334
|
+
attestationVerificationEvidence,
|
|
1718
2335
|
versionResolutions: versionResolutions.map((entry) => ({
|
|
1719
2336
|
name: entry.name,
|
|
1720
2337
|
targetVersion: entry.targetVersion,
|
|
@@ -1724,6 +2341,7 @@ const releaseEvidenceNormalizationAgent = {
|
|
|
1724
2341
|
localReadinessChecks,
|
|
1725
2342
|
readinessStatus,
|
|
1726
2343
|
approvalRecommendations,
|
|
2344
|
+
trustSummary,
|
|
1727
2345
|
provenanceRefs: [...new Set(provenanceRefs)]
|
|
1728
2346
|
});
|
|
1729
2347
|
return agentOutputSchema.parse({
|
|
@@ -1776,6 +2394,7 @@ const releaseAnalystAgent = {
|
|
|
1776
2394
|
async execute({ state, stateSlice }) {
|
|
1777
2395
|
const releaseRequest = getWorkflowInput(stateSlice, "releaseRequest");
|
|
1778
2396
|
const releaseIssueRefs = getWorkflowInput(stateSlice, "releaseIssueRefs") ?? [];
|
|
2397
|
+
const releaseScmRefs = getWorkflowInput(stateSlice, "releaseScmRefs") ?? [];
|
|
1779
2398
|
const releaseGithubRefs = getWorkflowInput(stateSlice, "releaseGithubRefs") ?? [];
|
|
1780
2399
|
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
1781
2400
|
if (!releaseRequest) {
|
|
@@ -1801,6 +2420,13 @@ const releaseAnalystAgent = {
|
|
|
1801
2420
|
: releaseRequest.evidenceSources;
|
|
1802
2421
|
const constraints = asStringArray(intakeMetadata.constraints);
|
|
1803
2422
|
const allEvidenceRefs = [...new Set([...qaReportRefs, ...securityReportRefs, ...evidenceSources])];
|
|
2423
|
+
const importedCiEvidence = normalizedEvidence?.ciEvidence ?? [];
|
|
2424
|
+
const ciEvidenceSummary = normalizedEvidence?.ciEvidenceSummary ?? importedCiEvidence.map((entry) => summarizeCiEvidenceForRelease(entry));
|
|
2425
|
+
const dependencyIntegrityEvidence = normalizedEvidence?.dependencyIntegrityEvidence ?? [];
|
|
2426
|
+
const dependencyIntegritySignals = buildDependencyIntegritySignals(dependencyIntegrityEvidence);
|
|
2427
|
+
const attestationVerificationEvidence = normalizedEvidence?.attestationVerificationEvidence ?? [];
|
|
2428
|
+
const trustSummary = normalizedEvidence?.trustSummary ?? buildReleaseTrustSummary(attestationVerificationEvidence);
|
|
2429
|
+
const importedCiFailures = [...new Set(ciEvidenceSummary.flatMap((entry) => entry.failingChecks))];
|
|
1804
2430
|
const versionResolutions = normalizedEvidence?.versionResolutions ?? [];
|
|
1805
2431
|
const verificationChecks = normalizedEvidence?.localReadinessChecks ?? [
|
|
1806
2432
|
{
|
|
@@ -1823,6 +2449,13 @@ const releaseAnalystAgent = {
|
|
|
1823
2449
|
detail: evidenceSources.length > 0
|
|
1824
2450
|
? `Using ${evidenceSources.length} bounded local release evidence source(s).`
|
|
1825
2451
|
: "No additional local release evidence sources were supplied."
|
|
2452
|
+
},
|
|
2453
|
+
{
|
|
2454
|
+
name: "imported-ci-evidence",
|
|
2455
|
+
status: importedCiEvidence.length > 0 ? "passed" : "skipped",
|
|
2456
|
+
detail: importedCiEvidence.length > 0
|
|
2457
|
+
? `Using ${importedCiEvidence.length} imported CI evidence export(s) across ${ciEvidenceSummary.map((entry) => entry.provider).join(", ")}.`
|
|
2458
|
+
: "No imported CI evidence exports were supplied."
|
|
1826
2459
|
}
|
|
1827
2460
|
];
|
|
1828
2461
|
const readinessStatus = normalizedEvidence?.readinessStatus ??
|
|
@@ -1845,7 +2478,10 @@ const releaseAnalystAgent = {
|
|
|
1845
2478
|
...(versionResolutions.length > 0
|
|
1846
2479
|
? [`Resolved ${versionResolutions.length} workspace version target(s) before any publish or promotion step.`]
|
|
1847
2480
|
: []),
|
|
2481
|
+
...dependencyIntegritySignals.map((signal) => `${signal} Review dependency integrity before any publish or promotion step.`),
|
|
2482
|
+
...trustSummary.map((line) => `${line} Keep publish execution separate from verification.`),
|
|
1848
2483
|
"Review the bounded QA and security evidence before invoking any publish or promotion step.",
|
|
2484
|
+
...ciEvidenceSummary.map((entry) => `Review ${entry.displayLabel} (${formatCiEvidenceStatus(entry)}) before any publish or promotion step.`),
|
|
1849
2485
|
"Run `agentforge release check --json` and `agentforge release verify --json` before any release cut.",
|
|
1850
2486
|
...approvalRecommendations.map((recommendation) => `${recommendation.action}: ${recommendation.classification.replaceAll("_", " ")} (${recommendation.reason})`),
|
|
1851
2487
|
"Keep trusted publishing and tag or publish actions outside this default read-only workflow path."
|
|
@@ -1856,10 +2492,11 @@ const releaseAnalystAgent = {
|
|
|
1856
2492
|
];
|
|
1857
2493
|
const externalDependencies = [
|
|
1858
2494
|
...(qaReportRefs.length > 0 ? ["Validated QA report inputs remain available for reviewer inspection."] : []),
|
|
1859
|
-
...(securityReportRefs.length > 0 ? ["Validated security report inputs remain available for reviewer inspection."] : [])
|
|
2495
|
+
...(securityReportRefs.length > 0 ? ["Validated security report inputs remain available for reviewer inspection."] : []),
|
|
2496
|
+
...ciEvidenceSummary.map((entry) => `${entry.displayLabel} remains available for reviewer inspection.`)
|
|
1860
2497
|
];
|
|
1861
2498
|
const releaseReport = releaseArtifactSchema.parse({
|
|
1862
|
-
...buildLifecycleArtifactEnvelopeBase(state, "Release Readiness", summary, [requestFile ?? ".agentops/requests/release.yaml", ...allEvidenceRefs], releaseIssueRefs, releaseGithubRefs),
|
|
2499
|
+
...buildLifecycleArtifactEnvelopeBase(state, "Release Readiness", summary, [requestFile ?? ".agentops/requests/release.yaml", ...allEvidenceRefs], releaseIssueRefs, releaseScmRefs, releaseGithubRefs),
|
|
1863
2500
|
artifactKind: "release-report",
|
|
1864
2501
|
lifecycleDomain: "release",
|
|
1865
2502
|
payload: {
|
|
@@ -1868,9 +2505,12 @@ const releaseAnalystAgent = {
|
|
|
1868
2505
|
readinessStatus,
|
|
1869
2506
|
verificationChecks: verificationChecks.map((check) => ({ ...check })),
|
|
1870
2507
|
versionResolutions,
|
|
2508
|
+
ciEvidenceSummary,
|
|
2509
|
+
dependencyIntegritySignals,
|
|
2510
|
+
trustSummary,
|
|
1871
2511
|
approvalRecommendations: approvalRecommendations.map((recommendation) => releaseApprovalRecommendationSchema.parse(recommendation)),
|
|
1872
2512
|
publishingPlan,
|
|
1873
|
-
trustStatus:
|
|
2513
|
+
trustStatus: resolveReleaseTrustStatus(attestationVerificationEvidence),
|
|
1874
2514
|
publishedPackages: [],
|
|
1875
2515
|
tagRefs: [],
|
|
1876
2516
|
provenanceRefs: allEvidenceRefs,
|
|
@@ -1892,6 +2532,10 @@ const releaseAnalystAgent = {
|
|
|
1892
2532
|
qaReportRefs,
|
|
1893
2533
|
securityReportRefs,
|
|
1894
2534
|
evidenceSources,
|
|
2535
|
+
ciEvidence: importedCiEvidence,
|
|
2536
|
+
ciEvidenceSummary,
|
|
2537
|
+
dependencyIntegrityEvidence,
|
|
2538
|
+
attestationVerificationEvidence,
|
|
1895
2539
|
constraints,
|
|
1896
2540
|
normalizedEvidence: normalizedEvidence ?? null
|
|
1897
2541
|
},
|
|
@@ -1899,18 +2543,20 @@ const releaseAnalystAgent = {
|
|
|
1899
2543
|
readinessStatus,
|
|
1900
2544
|
approvalRecommendations,
|
|
1901
2545
|
publishingPlan,
|
|
1902
|
-
rollbackNotes
|
|
2546
|
+
rollbackNotes,
|
|
2547
|
+
trustSummary,
|
|
2548
|
+
importedCiFailures
|
|
1903
2549
|
}
|
|
1904
2550
|
}
|
|
1905
2551
|
});
|
|
1906
2552
|
}
|
|
1907
2553
|
};
|
|
1908
|
-
const
|
|
2554
|
+
const pipelineIntakeAgent = {
|
|
1909
2555
|
manifest: agentManifestSchema.parse({
|
|
1910
2556
|
version: 1,
|
|
1911
|
-
name: "
|
|
1912
|
-
displayName: "
|
|
1913
|
-
category: "
|
|
2557
|
+
name: "pipeline-intake",
|
|
2558
|
+
displayName: "Pipeline Intake",
|
|
2559
|
+
category: "release",
|
|
1914
2560
|
runtime: {
|
|
1915
2561
|
minVersion: "0.1.0",
|
|
1916
2562
|
kind: "deterministic"
|
|
@@ -1919,7 +2565,72 @@ const securityEvidenceNormalizationAgent = {
|
|
|
1919
2565
|
model: false,
|
|
1920
2566
|
network: false,
|
|
1921
2567
|
tools: [],
|
|
1922
|
-
readPaths: [".agentops/requests/**", ".agentops/runs/**"
|
|
2568
|
+
readPaths: [".agentops/requests/**", ".agentops/runs/**"],
|
|
2569
|
+
writePaths: []
|
|
2570
|
+
},
|
|
2571
|
+
inputs: ["workflowInputs", "repo"],
|
|
2572
|
+
outputs: ["summary", "metadata"],
|
|
2573
|
+
contextPolicy: {
|
|
2574
|
+
sections: ["workflowInputs", "repo", "context"],
|
|
2575
|
+
minimalContext: true
|
|
2576
|
+
},
|
|
2577
|
+
catalog: {
|
|
2578
|
+
domain: "release",
|
|
2579
|
+
supportLevel: "internal",
|
|
2580
|
+
maturity: "mvp",
|
|
2581
|
+
trustScope: "official-core-only"
|
|
2582
|
+
},
|
|
2583
|
+
trust: {
|
|
2584
|
+
tier: "core",
|
|
2585
|
+
source: "official",
|
|
2586
|
+
reviewed: true
|
|
2587
|
+
}
|
|
2588
|
+
}),
|
|
2589
|
+
outputSchema: agentOutputSchema,
|
|
2590
|
+
async execute({ stateSlice }) {
|
|
2591
|
+
const pipelineRequest = getWorkflowInput(stateSlice, "pipelineRequest");
|
|
2592
|
+
const pipelineIssueRefs = getWorkflowInput(stateSlice, "pipelineIssueRefs") ?? [];
|
|
2593
|
+
const pipelineScmRefs = getWorkflowInput(stateSlice, "pipelineScmRefs") ?? [];
|
|
2594
|
+
const pipelineGithubRefs = getWorkflowInput(stateSlice, "pipelineGithubRefs") ?? [];
|
|
2595
|
+
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
2596
|
+
if (!pipelineRequest) {
|
|
2597
|
+
throw new Error("pipeline-evidence-review requires a validated pipeline request before runtime execution.");
|
|
2598
|
+
}
|
|
2599
|
+
return agentOutputSchema.parse({
|
|
2600
|
+
summary: `Loaded pipeline request from ${requestFile ?? ".agentops/requests/pipeline.yaml"} for ${pipelineRequest.pipelineScope}.`,
|
|
2601
|
+
findings: [],
|
|
2602
|
+
proposedActions: [],
|
|
2603
|
+
lifecycleArtifacts: [],
|
|
2604
|
+
requestedTools: [],
|
|
2605
|
+
blockedActionFlags: [],
|
|
2606
|
+
metadata: {
|
|
2607
|
+
...pipelineRequestSchema.parse(pipelineRequest),
|
|
2608
|
+
pipelineIssueRefs,
|
|
2609
|
+
pipelineScmRefs,
|
|
2610
|
+
pipelineGithubRefs,
|
|
2611
|
+
evidenceSourceCount: pipelineRequest.evidenceSources.length +
|
|
2612
|
+
pipelineRequest.qaReportRefs.length +
|
|
2613
|
+
pipelineRequest.securityReportRefs.length +
|
|
2614
|
+
pipelineRequest.releaseReportRefs.length
|
|
2615
|
+
}
|
|
2616
|
+
});
|
|
2617
|
+
}
|
|
2618
|
+
};
|
|
2619
|
+
const pipelineEvidenceNormalizationAgent = {
|
|
2620
|
+
manifest: agentManifestSchema.parse({
|
|
2621
|
+
version: 1,
|
|
2622
|
+
name: "pipeline-evidence-normalizer",
|
|
2623
|
+
displayName: "Pipeline Evidence Normalizer",
|
|
2624
|
+
category: "release",
|
|
2625
|
+
runtime: {
|
|
2626
|
+
minVersion: "0.1.0",
|
|
2627
|
+
kind: "deterministic"
|
|
2628
|
+
},
|
|
2629
|
+
permissions: {
|
|
2630
|
+
model: false,
|
|
2631
|
+
network: false,
|
|
2632
|
+
tools: [],
|
|
2633
|
+
readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/*.json", "**/*.md"],
|
|
1923
2634
|
writePaths: []
|
|
1924
2635
|
},
|
|
1925
2636
|
inputs: ["workflowInputs", "repo", "agentResults"],
|
|
@@ -1929,7 +2640,7 @@ const securityEvidenceNormalizationAgent = {
|
|
|
1929
2640
|
minimalContext: true
|
|
1930
2641
|
},
|
|
1931
2642
|
catalog: {
|
|
1932
|
-
domain: "
|
|
2643
|
+
domain: "release",
|
|
1933
2644
|
supportLevel: "internal",
|
|
1934
2645
|
maturity: "mvp",
|
|
1935
2646
|
trustScope: "official-core-only"
|
|
@@ -1942,8 +2653,576 @@ const securityEvidenceNormalizationAgent = {
|
|
|
1942
2653
|
}),
|
|
1943
2654
|
outputSchema: agentOutputSchema,
|
|
1944
2655
|
async execute({ stateSlice }) {
|
|
1945
|
-
const
|
|
1946
|
-
if (!
|
|
2656
|
+
const pipelineRequest = getWorkflowInput(stateSlice, "pipelineRequest");
|
|
2657
|
+
if (!pipelineRequest) {
|
|
2658
|
+
throw new Error("pipeline-evidence-review requires validated pipeline request inputs before evidence normalization.");
|
|
2659
|
+
}
|
|
2660
|
+
const repoRoot = stateSlice.repo?.root;
|
|
2661
|
+
const intakeMetadata = isRecord(stateSlice.agentResults?.intake?.metadata) ? stateSlice.agentResults.intake.metadata : {};
|
|
2662
|
+
const qaReportRefs = asStringArray(intakeMetadata.qaReportRefs).length > 0
|
|
2663
|
+
? asStringArray(intakeMetadata.qaReportRefs)
|
|
2664
|
+
: pipelineRequest.qaReportRefs;
|
|
2665
|
+
const securityReportRefs = asStringArray(intakeMetadata.securityReportRefs).length > 0
|
|
2666
|
+
? asStringArray(intakeMetadata.securityReportRefs)
|
|
2667
|
+
: pipelineRequest.securityReportRefs;
|
|
2668
|
+
const releaseReportRefs = asStringArray(intakeMetadata.releaseReportRefs).length > 0
|
|
2669
|
+
? asStringArray(intakeMetadata.releaseReportRefs)
|
|
2670
|
+
: pipelineRequest.releaseReportRefs;
|
|
2671
|
+
const evidenceSources = asStringArray(intakeMetadata.evidenceSources).length > 0
|
|
2672
|
+
? asStringArray(intakeMetadata.evidenceSources)
|
|
2673
|
+
: pipelineRequest.evidenceSources;
|
|
2674
|
+
const normalizedEvidenceSources = [...new Set([...qaReportRefs, ...securityReportRefs, ...releaseReportRefs, ...evidenceSources])];
|
|
2675
|
+
const missingEvidenceSources = normalizedEvidenceSources.filter((pathValue) => repoRoot && !existsSync(join(repoRoot, pathValue)));
|
|
2676
|
+
if (missingEvidenceSources.length > 0) {
|
|
2677
|
+
throw new Error(`Pipeline evidence source not found: ${missingEvidenceSources[0]}`);
|
|
2678
|
+
}
|
|
2679
|
+
const referencedArtifactKinds = [
|
|
2680
|
+
...new Set([...qaReportRefs, ...securityReportRefs, ...releaseReportRefs].flatMap((bundleRef) => repoRoot ? loadBundleArtifactKinds(join(repoRoot, bundleRef)) : []))
|
|
2681
|
+
];
|
|
2682
|
+
const ciEvidence = normalizeImportedCiEvidence(repoRoot, normalizedEvidenceSources);
|
|
2683
|
+
const ciEvidenceSummary = ciEvidence.map((entry) => summarizeCiEvidenceForRelease(entry));
|
|
2684
|
+
const failingChecks = ciEvidenceSummary.flatMap((entry) => entry.failingChecks);
|
|
2685
|
+
const verificationChecks = [
|
|
2686
|
+
{
|
|
2687
|
+
name: "referenced-artifacts",
|
|
2688
|
+
status: qaReportRefs.length + securityReportRefs.length + releaseReportRefs.length > 0 ? "passed" : "skipped",
|
|
2689
|
+
detail: qaReportRefs.length + securityReportRefs.length + releaseReportRefs.length > 0
|
|
2690
|
+
? `Validated ${qaReportRefs.length + securityReportRefs.length + releaseReportRefs.length} referenced artifact bundle(s).`
|
|
2691
|
+
: "No upstream lifecycle artifact references were supplied."
|
|
2692
|
+
},
|
|
2693
|
+
{
|
|
2694
|
+
name: "imported-ci-evidence",
|
|
2695
|
+
status: ciEvidence.length === 0 ? "failed" : failingChecks.length > 0 ? "failed" : "passed",
|
|
2696
|
+
detail: ciEvidence.length === 0
|
|
2697
|
+
? "No imported CI evidence exports were supplied."
|
|
2698
|
+
: failingChecks.length > 0
|
|
2699
|
+
? `Imported CI evidence still reports failing checks: ${failingChecks.join(", ")}.`
|
|
2700
|
+
: `Using ${ciEvidence.length} imported CI evidence export(s) across ${[...new Set(ciEvidence.map((entry) => entry.pipelineName))].length} pipeline(s).`
|
|
2701
|
+
}
|
|
2702
|
+
];
|
|
2703
|
+
const reviewStatus = ciEvidence.length === 0 || failingChecks.length > 0
|
|
2704
|
+
? "blocked"
|
|
2705
|
+
: "ready";
|
|
2706
|
+
const normalization = pipelineEvidenceNormalizationSchema.parse({
|
|
2707
|
+
qaReportRefs,
|
|
2708
|
+
securityReportRefs,
|
|
2709
|
+
releaseReportRefs,
|
|
2710
|
+
normalizedEvidenceSources,
|
|
2711
|
+
missingEvidenceSources: [],
|
|
2712
|
+
ciEvidence,
|
|
2713
|
+
ciEvidenceSummary,
|
|
2714
|
+
referencedArtifactKinds,
|
|
2715
|
+
verificationChecks,
|
|
2716
|
+
reviewStatus,
|
|
2717
|
+
provenanceRefs: normalizedEvidenceSources
|
|
2718
|
+
});
|
|
2719
|
+
return agentOutputSchema.parse({
|
|
2720
|
+
summary: `Normalized pipeline evidence across ${normalization.normalizedEvidenceSources.length} source(s) and ${normalization.ciEvidence.length} imported CI evidence export(s).`,
|
|
2721
|
+
findings: [],
|
|
2722
|
+
proposedActions: [],
|
|
2723
|
+
lifecycleArtifacts: [],
|
|
2724
|
+
requestedTools: [],
|
|
2725
|
+
blockedActionFlags: [],
|
|
2726
|
+
metadata: normalization
|
|
2727
|
+
});
|
|
2728
|
+
}
|
|
2729
|
+
};
|
|
2730
|
+
const pipelineAnalystAgent = {
|
|
2731
|
+
manifest: agentManifestSchema.parse({
|
|
2732
|
+
version: 1,
|
|
2733
|
+
name: "pipeline-analyst",
|
|
2734
|
+
displayName: "Pipeline Analyst",
|
|
2735
|
+
category: "release",
|
|
2736
|
+
runtime: {
|
|
2737
|
+
minVersion: "0.1.0",
|
|
2738
|
+
kind: "reasoning"
|
|
2739
|
+
},
|
|
2740
|
+
permissions: {
|
|
2741
|
+
model: true,
|
|
2742
|
+
network: false,
|
|
2743
|
+
tools: [],
|
|
2744
|
+
readPaths: ["**/*"],
|
|
2745
|
+
writePaths: []
|
|
2746
|
+
},
|
|
2747
|
+
inputs: ["workflowInputs", "repo", "agentResults"],
|
|
2748
|
+
outputs: ["lifecycleArtifacts"],
|
|
2749
|
+
contextPolicy: {
|
|
2750
|
+
sections: ["workflowInputs", "repo", "agentResults"],
|
|
2751
|
+
minimalContext: true
|
|
2752
|
+
},
|
|
2753
|
+
catalog: {
|
|
2754
|
+
domain: "release",
|
|
2755
|
+
supportLevel: "internal",
|
|
2756
|
+
maturity: "mvp",
|
|
2757
|
+
trustScope: "official-core-only"
|
|
2758
|
+
},
|
|
2759
|
+
trust: {
|
|
2760
|
+
tier: "core",
|
|
2761
|
+
source: "official",
|
|
2762
|
+
reviewed: true
|
|
2763
|
+
}
|
|
2764
|
+
}),
|
|
2765
|
+
outputSchema: agentOutputSchema,
|
|
2766
|
+
async execute({ state, stateSlice }) {
|
|
2767
|
+
const pipelineRequest = getWorkflowInput(stateSlice, "pipelineRequest");
|
|
2768
|
+
const pipelineIssueRefs = getWorkflowInput(stateSlice, "pipelineIssueRefs") ?? [];
|
|
2769
|
+
const pipelineScmRefs = getWorkflowInput(stateSlice, "pipelineScmRefs") ?? [];
|
|
2770
|
+
const pipelineGithubRefs = getWorkflowInput(stateSlice, "pipelineGithubRefs") ?? [];
|
|
2771
|
+
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
2772
|
+
if (!pipelineRequest) {
|
|
2773
|
+
throw new Error("pipeline-evidence-review requires validated pipeline inputs before analysis.");
|
|
2774
|
+
}
|
|
2775
|
+
const evidenceMetadata = pipelineEvidenceNormalizationSchema.safeParse(stateSlice.agentResults?.evidence?.metadata);
|
|
2776
|
+
const normalizedEvidence = evidenceMetadata.success ? evidenceMetadata.data : undefined;
|
|
2777
|
+
const evidenceSources = normalizedEvidence?.normalizedEvidenceSources && normalizedEvidence.normalizedEvidenceSources.length > 0
|
|
2778
|
+
? normalizedEvidence.normalizedEvidenceSources
|
|
2779
|
+
: pipelineRequest.evidenceSources;
|
|
2780
|
+
const ciEvidenceSummary = normalizedEvidence?.ciEvidenceSummary ?? [];
|
|
2781
|
+
const verificationChecks = normalizedEvidence?.verificationChecks ?? [];
|
|
2782
|
+
const referencedArtifactKinds = normalizedEvidence?.referencedArtifactKinds ?? [];
|
|
2783
|
+
const blockers = [
|
|
2784
|
+
...verificationChecks
|
|
2785
|
+
.filter((check) => check.status === "failed")
|
|
2786
|
+
.map((check) => check.detail ?? `${check.name} failed during deterministic pipeline review.`)
|
|
2787
|
+
];
|
|
2788
|
+
const riskSummary = [
|
|
2789
|
+
...(pipelineRequest.focusAreas.length > 0
|
|
2790
|
+
? [`Focused review areas still require human interpretation: ${pipelineRequest.focusAreas.join(", ")}.`]
|
|
2791
|
+
: []),
|
|
2792
|
+
...(referencedArtifactKinds.length > 0
|
|
2793
|
+
? [`Referenced lifecycle artifacts remain in scope: ${referencedArtifactKinds.join(", ")}.`]
|
|
2794
|
+
: []),
|
|
2795
|
+
...(ciEvidenceSummary.length > 0
|
|
2796
|
+
? [`Imported CI provenance spans ${[...new Set(ciEvidenceSummary.map((entry) => entry.provider))].join(", ")}.`]
|
|
2797
|
+
: [])
|
|
2798
|
+
];
|
|
2799
|
+
const recommendedNextSteps = [
|
|
2800
|
+
...ciEvidenceSummary.map((entry) => `Review ${entry.displayLabel} (${formatCiEvidenceStatus(entry)}) before moving to deployment or promotion review.`),
|
|
2801
|
+
...(pipelineRequest.qaReportRefs.length + pipelineRequest.securityReportRefs.length + pipelineRequest.releaseReportRefs.length > 0
|
|
2802
|
+
? ["Carry the validated QA, security, and release artifacts forward into deployment-gate-review."]
|
|
2803
|
+
: ["Attach downstream QA, security, or release artifacts before using this pipeline report as a deployment gate input."]),
|
|
2804
|
+
...(pipelineRequest.constraints.length > 0 ? [`Keep follow-up bounded by: ${pipelineRequest.constraints.join("; ")}.`] : [])
|
|
2805
|
+
];
|
|
2806
|
+
const reviewStatus = blockers.length > 0 ? "blocked" : normalizedEvidence?.reviewStatus ?? "ready";
|
|
2807
|
+
const summary = `Pipeline report prepared for ${pipelineRequest.pipelineScope}.`;
|
|
2808
|
+
const pipelineReport = pipelineArtifactSchema.parse({
|
|
2809
|
+
...buildLifecycleArtifactEnvelopeBase(state, "Pipeline Evidence Review", summary, [requestFile ?? ".agentops/requests/pipeline.yaml", ...new Set(evidenceSources)], pipelineIssueRefs, pipelineScmRefs, pipelineGithubRefs),
|
|
2810
|
+
artifactKind: "pipeline-report",
|
|
2811
|
+
lifecycleDomain: "release",
|
|
2812
|
+
payload: {
|
|
2813
|
+
pipelineScope: pipelineRequest.pipelineScope,
|
|
2814
|
+
evidenceSources,
|
|
2815
|
+
verificationChecks,
|
|
2816
|
+
ciEvidenceSummary,
|
|
2817
|
+
reviewStatus,
|
|
2818
|
+
blockers,
|
|
2819
|
+
riskSummary,
|
|
2820
|
+
recommendedNextSteps: recommendedNextSteps.length > 0 ? recommendedNextSteps : ["Capture additional bounded CI evidence before follow-on review."],
|
|
2821
|
+
referencedArtifactKinds,
|
|
2822
|
+
provenanceRefs: normalizedEvidence?.provenanceRefs ?? evidenceSources
|
|
2823
|
+
}
|
|
2824
|
+
});
|
|
2825
|
+
return agentOutputSchema.parse({
|
|
2826
|
+
summary,
|
|
2827
|
+
findings: [],
|
|
2828
|
+
proposedActions: [],
|
|
2829
|
+
lifecycleArtifacts: [pipelineReport],
|
|
2830
|
+
requestedTools: [],
|
|
2831
|
+
blockedActionFlags: [],
|
|
2832
|
+
confidence: 0.76,
|
|
2833
|
+
metadata: {
|
|
2834
|
+
deterministicInputs: {
|
|
2835
|
+
evidenceSources,
|
|
2836
|
+
ciEvidenceSummary,
|
|
2837
|
+
verificationChecks,
|
|
2838
|
+
referencedArtifactKinds,
|
|
2839
|
+
focusAreas: pipelineRequest.focusAreas,
|
|
2840
|
+
constraints: pipelineRequest.constraints
|
|
2841
|
+
},
|
|
2842
|
+
synthesizedAssessment: {
|
|
2843
|
+
reviewStatus,
|
|
2844
|
+
blockers,
|
|
2845
|
+
riskSummary,
|
|
2846
|
+
recommendedNextSteps: pipelineReport.payload.recommendedNextSteps
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
});
|
|
2850
|
+
}
|
|
2851
|
+
};
|
|
2852
|
+
const deploymentGateIntakeAgent = {
|
|
2853
|
+
manifest: agentManifestSchema.parse({
|
|
2854
|
+
version: 1,
|
|
2855
|
+
name: "deployment-gate-intake",
|
|
2856
|
+
displayName: "Deployment Gate Intake",
|
|
2857
|
+
category: "release",
|
|
2858
|
+
runtime: {
|
|
2859
|
+
minVersion: "0.1.0",
|
|
2860
|
+
kind: "deterministic"
|
|
2861
|
+
},
|
|
2862
|
+
permissions: {
|
|
2863
|
+
model: false,
|
|
2864
|
+
network: false,
|
|
2865
|
+
tools: [],
|
|
2866
|
+
readPaths: [".agentops/requests/**", ".agentops/runs/**"],
|
|
2867
|
+
writePaths: []
|
|
2868
|
+
},
|
|
2869
|
+
inputs: ["workflowInputs", "repo"],
|
|
2870
|
+
outputs: ["summary", "metadata"],
|
|
2871
|
+
contextPolicy: {
|
|
2872
|
+
sections: ["workflowInputs", "repo", "context"],
|
|
2873
|
+
minimalContext: true
|
|
2874
|
+
},
|
|
2875
|
+
catalog: {
|
|
2876
|
+
domain: "release",
|
|
2877
|
+
supportLevel: "internal",
|
|
2878
|
+
maturity: "mvp",
|
|
2879
|
+
trustScope: "official-core-only"
|
|
2880
|
+
},
|
|
2881
|
+
trust: {
|
|
2882
|
+
tier: "core",
|
|
2883
|
+
source: "official",
|
|
2884
|
+
reviewed: true
|
|
2885
|
+
}
|
|
2886
|
+
}),
|
|
2887
|
+
outputSchema: agentOutputSchema,
|
|
2888
|
+
async execute({ stateSlice }) {
|
|
2889
|
+
const deploymentRequest = getWorkflowInput(stateSlice, "deploymentRequest");
|
|
2890
|
+
const deploymentIssueRefs = getWorkflowInput(stateSlice, "deploymentIssueRefs") ?? [];
|
|
2891
|
+
const deploymentScmRefs = getWorkflowInput(stateSlice, "deploymentScmRefs") ?? [];
|
|
2892
|
+
const deploymentGithubRefs = getWorkflowInput(stateSlice, "deploymentGithubRefs") ?? [];
|
|
2893
|
+
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
2894
|
+
if (!deploymentRequest) {
|
|
2895
|
+
throw new Error("deployment-gate-review requires a validated deployment request before runtime execution.");
|
|
2896
|
+
}
|
|
2897
|
+
return agentOutputSchema.parse({
|
|
2898
|
+
summary: `Loaded deployment request from ${requestFile ?? ".agentops/requests/deployment.yaml"} for ${deploymentRequest.targetEnvironment}.`,
|
|
2899
|
+
findings: [],
|
|
2900
|
+
proposedActions: [],
|
|
2901
|
+
lifecycleArtifacts: [],
|
|
2902
|
+
requestedTools: [],
|
|
2903
|
+
blockedActionFlags: [],
|
|
2904
|
+
metadata: {
|
|
2905
|
+
...deploymentRequestSchema.parse(deploymentRequest),
|
|
2906
|
+
deploymentIssueRefs,
|
|
2907
|
+
deploymentScmRefs,
|
|
2908
|
+
deploymentGithubRefs,
|
|
2909
|
+
evidenceSourceCount: deploymentRequest.evidenceSources.length +
|
|
2910
|
+
deploymentRequest.qaReportRefs.length +
|
|
2911
|
+
deploymentRequest.securityReportRefs.length +
|
|
2912
|
+
deploymentRequest.releaseReportRefs.length +
|
|
2913
|
+
deploymentRequest.pipelineReportRefs.length
|
|
2914
|
+
}
|
|
2915
|
+
});
|
|
2916
|
+
}
|
|
2917
|
+
};
|
|
2918
|
+
const deploymentGateEvidenceNormalizationAgent = {
|
|
2919
|
+
manifest: agentManifestSchema.parse({
|
|
2920
|
+
version: 1,
|
|
2921
|
+
name: "deployment-gate-evidence-normalizer",
|
|
2922
|
+
displayName: "Deployment Gate Evidence Normalizer",
|
|
2923
|
+
category: "release",
|
|
2924
|
+
runtime: {
|
|
2925
|
+
minVersion: "0.1.0",
|
|
2926
|
+
kind: "deterministic"
|
|
2927
|
+
},
|
|
2928
|
+
permissions: {
|
|
2929
|
+
model: false,
|
|
2930
|
+
network: false,
|
|
2931
|
+
tools: [],
|
|
2932
|
+
readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/*.json", "**/*.md"],
|
|
2933
|
+
writePaths: []
|
|
2934
|
+
},
|
|
2935
|
+
inputs: ["workflowInputs", "repo", "agentResults"],
|
|
2936
|
+
outputs: ["summary", "metadata"],
|
|
2937
|
+
contextPolicy: {
|
|
2938
|
+
sections: ["workflowInputs", "repo", "agentResults"],
|
|
2939
|
+
minimalContext: true
|
|
2940
|
+
},
|
|
2941
|
+
catalog: {
|
|
2942
|
+
domain: "release",
|
|
2943
|
+
supportLevel: "internal",
|
|
2944
|
+
maturity: "mvp",
|
|
2945
|
+
trustScope: "official-core-only"
|
|
2946
|
+
},
|
|
2947
|
+
trust: {
|
|
2948
|
+
tier: "core",
|
|
2949
|
+
source: "official",
|
|
2950
|
+
reviewed: true
|
|
2951
|
+
}
|
|
2952
|
+
}),
|
|
2953
|
+
outputSchema: agentOutputSchema,
|
|
2954
|
+
async execute({ stateSlice }) {
|
|
2955
|
+
const deploymentRequest = getWorkflowInput(stateSlice, "deploymentRequest");
|
|
2956
|
+
if (!deploymentRequest) {
|
|
2957
|
+
throw new Error("deployment-gate-review requires validated deployment request inputs before evidence normalization.");
|
|
2958
|
+
}
|
|
2959
|
+
const repoRoot = stateSlice.repo?.root;
|
|
2960
|
+
const intakeMetadata = isRecord(stateSlice.agentResults?.intake?.metadata) ? stateSlice.agentResults.intake.metadata : {};
|
|
2961
|
+
const qaReportRefs = asStringArray(intakeMetadata.qaReportRefs).length > 0
|
|
2962
|
+
? asStringArray(intakeMetadata.qaReportRefs)
|
|
2963
|
+
: deploymentRequest.qaReportRefs;
|
|
2964
|
+
const securityReportRefs = asStringArray(intakeMetadata.securityReportRefs).length > 0
|
|
2965
|
+
? asStringArray(intakeMetadata.securityReportRefs)
|
|
2966
|
+
: deploymentRequest.securityReportRefs;
|
|
2967
|
+
const releaseReportRefs = asStringArray(intakeMetadata.releaseReportRefs).length > 0
|
|
2968
|
+
? asStringArray(intakeMetadata.releaseReportRefs)
|
|
2969
|
+
: deploymentRequest.releaseReportRefs;
|
|
2970
|
+
const pipelineReportRefs = asStringArray(intakeMetadata.pipelineReportRefs).length > 0
|
|
2971
|
+
? asStringArray(intakeMetadata.pipelineReportRefs)
|
|
2972
|
+
: deploymentRequest.pipelineReportRefs;
|
|
2973
|
+
const evidenceSources = asStringArray(intakeMetadata.evidenceSources).length > 0
|
|
2974
|
+
? asStringArray(intakeMetadata.evidenceSources)
|
|
2975
|
+
: deploymentRequest.evidenceSources;
|
|
2976
|
+
const normalizedEvidenceSources = [
|
|
2977
|
+
...new Set([...qaReportRefs, ...securityReportRefs, ...releaseReportRefs, ...pipelineReportRefs, ...evidenceSources])
|
|
2978
|
+
];
|
|
2979
|
+
const missingEvidenceSources = normalizedEvidenceSources.filter((pathValue) => repoRoot && !existsSync(join(repoRoot, pathValue)));
|
|
2980
|
+
if (missingEvidenceSources.length > 0) {
|
|
2981
|
+
throw new Error(`Deployment evidence source not found: ${missingEvidenceSources[0]}`);
|
|
2982
|
+
}
|
|
2983
|
+
const referencedArtifactKinds = [
|
|
2984
|
+
...new Set([...qaReportRefs, ...securityReportRefs, ...releaseReportRefs, ...pipelineReportRefs].flatMap((bundleRef) => repoRoot ? loadBundleArtifactKinds(join(repoRoot, bundleRef)) : []))
|
|
2985
|
+
];
|
|
2986
|
+
const releaseReportReadiness = releaseReportRefs.map((bundleRef) => repoRoot
|
|
2987
|
+
? evaluateReleaseReportReadiness(join(repoRoot, bundleRef))
|
|
2988
|
+
: { ready: false, detail: `Release report reference cannot be evaluated without a repository root: ${bundleRef}` });
|
|
2989
|
+
const pipelineReportReadiness = pipelineReportRefs.map((bundleRef) => repoRoot
|
|
2990
|
+
? evaluatePipelineReportReadiness(join(repoRoot, bundleRef))
|
|
2991
|
+
: { ready: false, detail: `Pipeline report reference cannot be evaluated without a repository root: ${bundleRef}` });
|
|
2992
|
+
const ciEvidence = normalizeImportedCiEvidence(repoRoot, normalizedEvidenceSources);
|
|
2993
|
+
const ciEvidenceSummary = ciEvidence.map((entry) => summarizeCiEvidenceForRelease(entry));
|
|
2994
|
+
const failingChecks = ciEvidenceSummary.flatMap((entry) => entry.failingChecks);
|
|
2995
|
+
const verificationChecks = [
|
|
2996
|
+
{
|
|
2997
|
+
name: "qa-report-refs",
|
|
2998
|
+
status: qaReportRefs.length > 0 ? "passed" : "skipped",
|
|
2999
|
+
detail: qaReportRefs.length > 0 ? `Using ${qaReportRefs.length} validated QA report reference(s).` : "No QA report references were supplied."
|
|
3000
|
+
},
|
|
3001
|
+
{
|
|
3002
|
+
name: "security-report-refs",
|
|
3003
|
+
status: securityReportRefs.length > 0 ? "passed" : "skipped",
|
|
3004
|
+
detail: securityReportRefs.length > 0
|
|
3005
|
+
? `Using ${securityReportRefs.length} validated security report reference(s).`
|
|
3006
|
+
: "No security report references were supplied."
|
|
3007
|
+
},
|
|
3008
|
+
{
|
|
3009
|
+
name: "release-report-refs",
|
|
3010
|
+
status: releaseReportRefs.length === 0
|
|
3011
|
+
? "skipped"
|
|
3012
|
+
: releaseReportReadiness.some((entry) => !entry.ready)
|
|
3013
|
+
? "failed"
|
|
3014
|
+
: "passed",
|
|
3015
|
+
detail: releaseReportRefs.length > 0
|
|
3016
|
+
? releaseReportReadiness.some((entry) => !entry.ready)
|
|
3017
|
+
? releaseReportReadiness.filter((entry) => !entry.ready).map((entry) => entry.detail).join(" ")
|
|
3018
|
+
: `Using ${releaseReportRefs.length} ready release report reference(s).`
|
|
3019
|
+
: "No release report references were supplied."
|
|
3020
|
+
},
|
|
3021
|
+
{
|
|
3022
|
+
name: "pipeline-report-refs",
|
|
3023
|
+
status: pipelineReportRefs.length === 0
|
|
3024
|
+
? "skipped"
|
|
3025
|
+
: pipelineReportReadiness.some((entry) => !entry.ready)
|
|
3026
|
+
? "failed"
|
|
3027
|
+
: "passed",
|
|
3028
|
+
detail: pipelineReportRefs.length > 0
|
|
3029
|
+
? pipelineReportReadiness.some((entry) => !entry.ready)
|
|
3030
|
+
? pipelineReportReadiness.filter((entry) => !entry.ready).map((entry) => entry.detail).join(" ")
|
|
3031
|
+
: `Using ${pipelineReportRefs.length} ready pipeline report reference(s).`
|
|
3032
|
+
: "No pipeline report references were supplied."
|
|
3033
|
+
},
|
|
3034
|
+
{
|
|
3035
|
+
name: "imported-ci-evidence",
|
|
3036
|
+
status: ciEvidence.length === 0 ? "failed" : failingChecks.length > 0 ? "failed" : "passed",
|
|
3037
|
+
detail: ciEvidence.length === 0
|
|
3038
|
+
? "No imported CI evidence exports were supplied."
|
|
3039
|
+
: failingChecks.length > 0
|
|
3040
|
+
? `Imported CI evidence still reports failing checks: ${failingChecks.join(", ")}.`
|
|
3041
|
+
: `Using ${ciEvidence.length} imported CI evidence export(s) across ${[...new Set(ciEvidence.map((entry) => entry.pipelineName))].length} pipeline(s).`
|
|
3042
|
+
}
|
|
3043
|
+
];
|
|
3044
|
+
const gateStatus = ciEvidence.length === 0 || failingChecks.length > 0
|
|
3045
|
+
? "blocked"
|
|
3046
|
+
: qaReportRefs.length > 0 &&
|
|
3047
|
+
securityReportRefs.length > 0 &&
|
|
3048
|
+
releaseReportRefs.length > 0 &&
|
|
3049
|
+
pipelineReportRefs.length > 0
|
|
3050
|
+
? "ready_for_approval"
|
|
3051
|
+
: "conditionally_ready";
|
|
3052
|
+
const normalization = deploymentGateEvidenceNormalizationSchema.parse({
|
|
3053
|
+
qaReportRefs,
|
|
3054
|
+
securityReportRefs,
|
|
3055
|
+
releaseReportRefs,
|
|
3056
|
+
pipelineReportRefs,
|
|
3057
|
+
normalizedEvidenceSources,
|
|
3058
|
+
missingEvidenceSources: [],
|
|
3059
|
+
ciEvidence,
|
|
3060
|
+
ciEvidenceSummary,
|
|
3061
|
+
referencedArtifactKinds,
|
|
3062
|
+
verificationChecks,
|
|
3063
|
+
gateStatus,
|
|
3064
|
+
provenanceRefs: normalizedEvidenceSources
|
|
3065
|
+
});
|
|
3066
|
+
return agentOutputSchema.parse({
|
|
3067
|
+
summary: `Normalized deployment-gate evidence across ${normalization.normalizedEvidenceSources.length} source(s) and ${normalization.ciEvidence.length} imported CI evidence export(s).`,
|
|
3068
|
+
findings: [],
|
|
3069
|
+
proposedActions: [],
|
|
3070
|
+
lifecycleArtifacts: [],
|
|
3071
|
+
requestedTools: [],
|
|
3072
|
+
blockedActionFlags: [],
|
|
3073
|
+
metadata: normalization
|
|
3074
|
+
});
|
|
3075
|
+
}
|
|
3076
|
+
};
|
|
3077
|
+
const deploymentGateAnalystAgent = {
|
|
3078
|
+
manifest: agentManifestSchema.parse({
|
|
3079
|
+
version: 1,
|
|
3080
|
+
name: "deployment-gate-analyst",
|
|
3081
|
+
displayName: "Deployment Gate Analyst",
|
|
3082
|
+
category: "release",
|
|
3083
|
+
runtime: {
|
|
3084
|
+
minVersion: "0.1.0",
|
|
3085
|
+
kind: "reasoning"
|
|
3086
|
+
},
|
|
3087
|
+
permissions: {
|
|
3088
|
+
model: true,
|
|
3089
|
+
network: false,
|
|
3090
|
+
tools: [],
|
|
3091
|
+
readPaths: ["**/*"],
|
|
3092
|
+
writePaths: []
|
|
3093
|
+
},
|
|
3094
|
+
inputs: ["workflowInputs", "repo", "agentResults"],
|
|
3095
|
+
outputs: ["lifecycleArtifacts"],
|
|
3096
|
+
contextPolicy: {
|
|
3097
|
+
sections: ["workflowInputs", "repo", "agentResults"],
|
|
3098
|
+
minimalContext: true
|
|
3099
|
+
},
|
|
3100
|
+
catalog: {
|
|
3101
|
+
domain: "release",
|
|
3102
|
+
supportLevel: "internal",
|
|
3103
|
+
maturity: "mvp",
|
|
3104
|
+
trustScope: "official-core-only"
|
|
3105
|
+
},
|
|
3106
|
+
trust: {
|
|
3107
|
+
tier: "core",
|
|
3108
|
+
source: "official",
|
|
3109
|
+
reviewed: true
|
|
3110
|
+
}
|
|
3111
|
+
}),
|
|
3112
|
+
outputSchema: agentOutputSchema,
|
|
3113
|
+
async execute({ state, stateSlice }) {
|
|
3114
|
+
const deploymentRequest = getWorkflowInput(stateSlice, "deploymentRequest");
|
|
3115
|
+
const deploymentIssueRefs = getWorkflowInput(stateSlice, "deploymentIssueRefs") ?? [];
|
|
3116
|
+
const deploymentScmRefs = getWorkflowInput(stateSlice, "deploymentScmRefs") ?? [];
|
|
3117
|
+
const deploymentGithubRefs = getWorkflowInput(stateSlice, "deploymentGithubRefs") ?? [];
|
|
3118
|
+
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
3119
|
+
if (!deploymentRequest) {
|
|
3120
|
+
throw new Error("deployment-gate-review requires validated deployment inputs before analysis.");
|
|
3121
|
+
}
|
|
3122
|
+
const evidenceMetadata = deploymentGateEvidenceNormalizationSchema.safeParse(stateSlice.agentResults?.evidence?.metadata);
|
|
3123
|
+
const normalizedEvidence = evidenceMetadata.success ? evidenceMetadata.data : undefined;
|
|
3124
|
+
const evidenceSources = normalizedEvidence?.normalizedEvidenceSources && normalizedEvidence.normalizedEvidenceSources.length > 0
|
|
3125
|
+
? normalizedEvidence.normalizedEvidenceSources
|
|
3126
|
+
: deploymentRequest.evidenceSources;
|
|
3127
|
+
const ciEvidenceSummary = normalizedEvidence?.ciEvidenceSummary ?? [];
|
|
3128
|
+
const verificationChecks = normalizedEvidence?.verificationChecks ?? [];
|
|
3129
|
+
const referencedArtifactKinds = normalizedEvidence?.referencedArtifactKinds ?? [];
|
|
3130
|
+
const blockers = [
|
|
3131
|
+
...verificationChecks
|
|
3132
|
+
.filter((check) => check.status === "failed")
|
|
3133
|
+
.map((check) => check.detail ?? `${check.name} failed during deterministic deployment-gate review.`)
|
|
3134
|
+
];
|
|
3135
|
+
const requiredFollowUpChecks = [
|
|
3136
|
+
...verificationChecks
|
|
3137
|
+
.filter((check) => check.status === "skipped")
|
|
3138
|
+
.map((check) => check.detail ?? `${check.name} still needs explicit follow-up.`),
|
|
3139
|
+
...ciEvidenceSummary.map((entry) => `Confirm ${entry.displayLabel} remains current for the ${deploymentRequest.targetEnvironment} candidate.`),
|
|
3140
|
+
...(blockers.length === 0 ? ["Obtain explicit maintainer approval before any deploy, publish, or promotion action."] : [])
|
|
3141
|
+
];
|
|
3142
|
+
const gateStatus = blockers.length > 0 ? "blocked" : normalizedEvidence?.gateStatus ?? "conditionally_ready";
|
|
3143
|
+
const summary = `Deployment gate report prepared for ${deploymentRequest.targetEnvironment}.`;
|
|
3144
|
+
const deploymentGateReport = deploymentGateArtifactSchema.parse({
|
|
3145
|
+
...buildLifecycleArtifactEnvelopeBase(state, "Deployment Gate Review", summary, [requestFile ?? ".agentops/requests/deployment.yaml", ...new Set(evidenceSources)], deploymentIssueRefs, deploymentScmRefs, deploymentGithubRefs),
|
|
3146
|
+
artifactKind: "deployment-gate-report",
|
|
3147
|
+
lifecycleDomain: "release",
|
|
3148
|
+
payload: {
|
|
3149
|
+
deploymentScope: deploymentRequest.deploymentScope,
|
|
3150
|
+
targetEnvironment: deploymentRequest.targetEnvironment,
|
|
3151
|
+
evidenceSources,
|
|
3152
|
+
verificationChecks,
|
|
3153
|
+
ciEvidenceSummary,
|
|
3154
|
+
gateStatus,
|
|
3155
|
+
blockers,
|
|
3156
|
+
requiredFollowUpChecks,
|
|
3157
|
+
referencedArtifactKinds,
|
|
3158
|
+
provenanceRefs: normalizedEvidence?.provenanceRefs ?? evidenceSources
|
|
3159
|
+
}
|
|
3160
|
+
});
|
|
3161
|
+
return agentOutputSchema.parse({
|
|
3162
|
+
summary,
|
|
3163
|
+
findings: [],
|
|
3164
|
+
proposedActions: [],
|
|
3165
|
+
lifecycleArtifacts: [deploymentGateReport],
|
|
3166
|
+
requestedTools: [],
|
|
3167
|
+
blockedActionFlags: [],
|
|
3168
|
+
confidence: 0.77,
|
|
3169
|
+
metadata: {
|
|
3170
|
+
deterministicInputs: {
|
|
3171
|
+
targetEnvironment: deploymentRequest.targetEnvironment,
|
|
3172
|
+
evidenceSources,
|
|
3173
|
+
ciEvidenceSummary,
|
|
3174
|
+
verificationChecks,
|
|
3175
|
+
referencedArtifactKinds,
|
|
3176
|
+
constraints: deploymentRequest.constraints
|
|
3177
|
+
},
|
|
3178
|
+
synthesizedAssessment: {
|
|
3179
|
+
gateStatus,
|
|
3180
|
+
blockers,
|
|
3181
|
+
requiredFollowUpChecks: deploymentGateReport.payload.requiredFollowUpChecks
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
});
|
|
3185
|
+
}
|
|
3186
|
+
};
|
|
3187
|
+
const securityEvidenceNormalizationAgent = {
|
|
3188
|
+
manifest: agentManifestSchema.parse({
|
|
3189
|
+
version: 1,
|
|
3190
|
+
name: "security-evidence-normalizer",
|
|
3191
|
+
displayName: "Security Evidence Normalizer",
|
|
3192
|
+
category: "security",
|
|
3193
|
+
runtime: {
|
|
3194
|
+
minVersion: "0.1.0",
|
|
3195
|
+
kind: "deterministic"
|
|
3196
|
+
},
|
|
3197
|
+
permissions: {
|
|
3198
|
+
model: false,
|
|
3199
|
+
network: false,
|
|
3200
|
+
tools: [],
|
|
3201
|
+
readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/*.json", "**/*.md", "**/package.json"],
|
|
3202
|
+
writePaths: []
|
|
3203
|
+
},
|
|
3204
|
+
inputs: ["workflowInputs", "repo", "agentResults"],
|
|
3205
|
+
outputs: ["summary", "metadata"],
|
|
3206
|
+
contextPolicy: {
|
|
3207
|
+
sections: ["workflowInputs", "repo", "agentResults"],
|
|
3208
|
+
minimalContext: true
|
|
3209
|
+
},
|
|
3210
|
+
catalog: {
|
|
3211
|
+
domain: "security",
|
|
3212
|
+
supportLevel: "internal",
|
|
3213
|
+
maturity: "mvp",
|
|
3214
|
+
trustScope: "official-core-only"
|
|
3215
|
+
},
|
|
3216
|
+
trust: {
|
|
3217
|
+
tier: "core",
|
|
3218
|
+
source: "official",
|
|
3219
|
+
reviewed: true
|
|
3220
|
+
}
|
|
3221
|
+
}),
|
|
3222
|
+
outputSchema: agentOutputSchema,
|
|
3223
|
+
async execute({ stateSlice }) {
|
|
3224
|
+
const securityRequest = getWorkflowInput(stateSlice, "securityRequest");
|
|
3225
|
+
if (!securityRequest) {
|
|
1947
3226
|
throw new Error("security-review requires validated security request inputs before evidence normalization.");
|
|
1948
3227
|
}
|
|
1949
3228
|
const repoRoot = stateSlice.repo?.root;
|
|
@@ -1965,15 +3244,19 @@ const securityEvidenceNormalizationAgent = {
|
|
|
1965
3244
|
const affectedPackages = targetType === "artifact-bundle"
|
|
1966
3245
|
? [...new Set(loadBundleArtifactPayloadPaths(targetPath).map(derivePackageScope).filter((value) => Boolean(value)))]
|
|
1967
3246
|
: [];
|
|
3247
|
+
const dependencyIntegrityEvidence = collectDependencyIntegrityEvidence(repoRoot, stateSlice.repo?.packageManager || "unknown", resolveDependencyManifestPaths(repoRoot, ["package.json", ...affectedPackages]));
|
|
3248
|
+
const dependencyIntegritySignals = buildDependencyIntegritySignals(dependencyIntegrityEvidence);
|
|
1968
3249
|
const securitySignals = [
|
|
1969
3250
|
...(referencedArtifactKinds.length > 0 ? [`Referenced artifact kinds: ${referencedArtifactKinds.join(", ")}`] : []),
|
|
1970
3251
|
...(affectedPackages.length > 0 ? [`Affected packages inferred from bounded artifact payloads: ${affectedPackages.join(", ")}`] : []),
|
|
1971
3252
|
...(normalizedFocusAreas.length > 0 ? [`Requested focus areas: ${normalizedFocusAreas.join(", ")}`] : []),
|
|
3253
|
+
...dependencyIntegritySignals,
|
|
1972
3254
|
"Security evidence collection remains local, read-only, and bounded to validated references."
|
|
1973
3255
|
];
|
|
1974
3256
|
const provenanceRefs = [
|
|
1975
3257
|
securityRequest.targetRef,
|
|
1976
3258
|
...securityRequest.evidenceSources,
|
|
3259
|
+
...dependencyIntegrityEvidence.flatMap((entry) => entry.provenanceRefs),
|
|
1977
3260
|
...referencedArtifactKinds.map((artifactKind) => `${securityRequest.targetRef}#${artifactKind}`)
|
|
1978
3261
|
];
|
|
1979
3262
|
const normalization = securityEvidenceNormalizationSchema.parse({
|
|
@@ -1984,6 +3267,7 @@ const securityEvidenceNormalizationAgent = {
|
|
|
1984
3267
|
missingEvidenceSources: [],
|
|
1985
3268
|
normalizedFocusAreas,
|
|
1986
3269
|
securitySignals,
|
|
3270
|
+
dependencyIntegrityEvidence,
|
|
1987
3271
|
provenanceRefs: [...new Set(provenanceRefs)],
|
|
1988
3272
|
affectedPackages
|
|
1989
3273
|
});
|
|
@@ -2037,6 +3321,7 @@ const securityAnalystAgent = {
|
|
|
2037
3321
|
async execute({ state, stateSlice }) {
|
|
2038
3322
|
const securityRequest = getWorkflowInput(stateSlice, "securityRequest");
|
|
2039
3323
|
const securityIssueRefs = getWorkflowInput(stateSlice, "securityIssueRefs") ?? [];
|
|
3324
|
+
const securityScmRefs = getWorkflowInput(stateSlice, "securityScmRefs") ?? [];
|
|
2040
3325
|
const securityGithubRefs = getWorkflowInput(stateSlice, "securityGithubRefs") ?? [];
|
|
2041
3326
|
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
2042
3327
|
if (!securityRequest) {
|
|
@@ -2048,6 +3333,8 @@ const securityAnalystAgent = {
|
|
|
2048
3333
|
const referencedArtifactKinds = normalizedEvidence?.referencedArtifactKinds ?? asStringArray(intakeMetadata.referencedArtifactKinds);
|
|
2049
3334
|
const normalizedFocusAreas = normalizedEvidence?.normalizedFocusAreas ?? asStringArray(intakeMetadata.focusAreas);
|
|
2050
3335
|
const normalizedConstraints = asStringArray(intakeMetadata.constraints);
|
|
3336
|
+
const dependencyIntegrityEvidence = normalizedEvidence?.dependencyIntegrityEvidence ?? [];
|
|
3337
|
+
const dependencyIntegritySignals = buildDependencyIntegritySignals(dependencyIntegrityEvidence);
|
|
2051
3338
|
const evidenceSources = normalizedEvidence?.normalizedEvidenceSources && normalizedEvidence.normalizedEvidenceSources.length > 0
|
|
2052
3339
|
? normalizedEvidence.normalizedEvidenceSources
|
|
2053
3340
|
: asStringArray(intakeMetadata.evidenceSources).length > 0
|
|
@@ -2084,6 +3371,7 @@ const securityAnalystAgent = {
|
|
|
2084
3371
|
];
|
|
2085
3372
|
const followUpWork = [
|
|
2086
3373
|
...(normalizedEvidence?.securitySignals ?? []),
|
|
3374
|
+
...dependencyIntegritySignals,
|
|
2087
3375
|
...(referencedArtifactKinds.length > 0
|
|
2088
3376
|
? [`Confirm the security posture for referenced artifacts: ${referencedArtifactKinds.join(", ")}.`]
|
|
2089
3377
|
: []),
|
|
@@ -2094,7 +3382,7 @@ const securityAnalystAgent = {
|
|
|
2094
3382
|
...buildLifecycleArtifactEnvelopeBase(state, "Security Review", summary, [
|
|
2095
3383
|
requestFile ?? ".agentops/requests/security.yaml",
|
|
2096
3384
|
...(normalizedEvidence?.provenanceRefs ?? [securityRequest.targetRef, ...securityRequest.evidenceSources])
|
|
2097
|
-
], securityIssueRefs, securityGithubRefs),
|
|
3385
|
+
], securityIssueRefs, securityScmRefs, securityGithubRefs),
|
|
2098
3386
|
artifactKind: "security-report",
|
|
2099
3387
|
lifecycleDomain: "security",
|
|
2100
3388
|
redaction: {
|
|
@@ -2115,7 +3403,8 @@ const securityAnalystAgent = {
|
|
|
2115
3403
|
: securityRequest.releaseContext === "candidate"
|
|
2116
3404
|
? "candidate release requires explicit security review before promotion."
|
|
2117
3405
|
: "no release context was supplied; security output remains advisory.",
|
|
2118
|
-
followUpWork
|
|
3406
|
+
followUpWork,
|
|
3407
|
+
dependencyIntegritySignals
|
|
2119
3408
|
}
|
|
2120
3409
|
});
|
|
2121
3410
|
return agentOutputSchema.parse({
|
|
@@ -2133,6 +3422,7 @@ const securityAnalystAgent = {
|
|
|
2133
3422
|
focusAreas,
|
|
2134
3423
|
constraints: normalizedConstraints,
|
|
2135
3424
|
referencedArtifactKinds,
|
|
3425
|
+
dependencyIntegrityEvidence,
|
|
2136
3426
|
normalizedEvidence: normalizedEvidence ?? null
|
|
2137
3427
|
},
|
|
2138
3428
|
synthesizedAssessment: {
|
|
@@ -2217,6 +3507,7 @@ const qaEvidenceNormalizationAgent = {
|
|
|
2217
3507
|
const allowlistedCommands = new Set(allowedValidationCommands.map((entry) => entry.command));
|
|
2218
3508
|
const normalizedExecutedChecks = qaRequest.executedChecks.map(normalizeRequestedCommand);
|
|
2219
3509
|
const unrecognizedExecutedChecks = normalizedExecutedChecks.filter((command) => !allowlistedCommands.has(command));
|
|
3510
|
+
const ciEvidence = normalizeImportedCiEvidence(repoRoot, normalizedEvidenceSources);
|
|
2220
3511
|
const githubActions = normalizeGitHubActionsEvidence(repoRoot, normalizedEvidenceSources);
|
|
2221
3512
|
const normalization = qaEvidenceNormalizationSchema.parse({
|
|
2222
3513
|
targetRef: qaRequest.targetRef,
|
|
@@ -2228,10 +3519,11 @@ const qaEvidenceNormalizationAgent = {
|
|
|
2228
3519
|
unrecognizedExecutedChecks,
|
|
2229
3520
|
affectedPackages,
|
|
2230
3521
|
allowedValidationCommands,
|
|
3522
|
+
ciEvidence,
|
|
2231
3523
|
githubActions
|
|
2232
3524
|
});
|
|
2233
3525
|
return agentOutputSchema.parse({
|
|
2234
|
-
summary: `Normalized QA evidence across ${normalization.normalizedEvidenceSources.length} source(s), ${normalization.allowedValidationCommands.length} allowlisted validation command(s),
|
|
3526
|
+
summary: `Normalized QA evidence across ${normalization.normalizedEvidenceSources.length} source(s), ${normalization.allowedValidationCommands.length} allowlisted validation command(s), ${normalization.githubActions.evidence.length} GitHub Actions export(s), and ${normalization.ciEvidence.length} generic CI evidence export(s).`,
|
|
2235
3527
|
findings: [],
|
|
2236
3528
|
proposedActions: [],
|
|
2237
3529
|
lifecycleArtifacts: [],
|
|
@@ -2280,6 +3572,7 @@ const qaAnalystAgent = {
|
|
|
2280
3572
|
async execute({ state, stateSlice }) {
|
|
2281
3573
|
const qaRequest = getWorkflowInput(stateSlice, "qaRequest");
|
|
2282
3574
|
const qaIssueRefs = getWorkflowInput(stateSlice, "qaIssueRefs") ?? [];
|
|
3575
|
+
const qaScmRefs = getWorkflowInput(stateSlice, "qaScmRefs") ?? [];
|
|
2283
3576
|
const qaGithubRefs = getWorkflowInput(stateSlice, "qaGithubRefs") ?? [];
|
|
2284
3577
|
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
2285
3578
|
if (!qaRequest) {
|
|
@@ -2302,6 +3595,10 @@ const qaAnalystAgent = {
|
|
|
2302
3595
|
failingChecks: [],
|
|
2303
3596
|
provenanceRefs: []
|
|
2304
3597
|
};
|
|
3598
|
+
const normalizedCiEvidence = normalizedEvidence?.ciEvidence ?? [];
|
|
3599
|
+
const normalizedCiEvidenceSummary = normalizedCiEvidence.map((entry) => summarizeCiEvidenceForRelease(entry));
|
|
3600
|
+
const normalizedCiWorkflowNames = [...new Set(normalizedCiEvidence.map((entry) => entry.pipelineName))];
|
|
3601
|
+
const normalizedCiFailingChecks = [...new Set(normalizedCiEvidence.flatMap((entry) => summarizeCiEvidenceFailures(entry)))];
|
|
2305
3602
|
const targetType = normalizedEvidence?.targetType
|
|
2306
3603
|
? normalizedEvidence.targetType
|
|
2307
3604
|
: typeof intakeMetadata.targetType === "string"
|
|
@@ -2341,12 +3638,14 @@ const qaAnalystAgent = {
|
|
|
2341
3638
|
...(focusAreas.includes("coverage") ? ["Coverage evidence still needs deterministic normalization before it can be promoted to an official QA signal."] : []),
|
|
2342
3639
|
...(executedChecks.length === 0 ? ["No executed validation checks were recorded in the request."] : []),
|
|
2343
3640
|
...unrecognizedExecutedChecks.map((command) => `Executed check is outside the bounded allowlist and still needs manual interpretation: ${command}`),
|
|
2344
|
-
...normalizedGithubActions.failingChecks.map((checkName) => `GitHub Actions evidence still reports a failing check that needs manual review: ${checkName}`)
|
|
3641
|
+
...normalizedGithubActions.failingChecks.map((checkName) => `GitHub Actions evidence still reports a failing check that needs manual review: ${checkName}`),
|
|
3642
|
+
...normalizedCiFailingChecks.map((checkName) => `Imported CI evidence still reports a failing check that needs manual review: ${checkName}`)
|
|
2345
3643
|
];
|
|
2346
3644
|
const recommendedNextChecks = [
|
|
2347
3645
|
...executedChecks.map((command) => `Review the recorded output for \`${command}\` before promotion.`),
|
|
2348
3646
|
...focusAreas.map((focusArea) => `Confirm whether ${focusArea} needs additional deterministic QA evidence.`),
|
|
2349
3647
|
...normalizedGithubActions.workflowNames.map((workflowName) => `Review the exported GitHub Actions evidence for workflow \`${workflowName}\` before promotion.`),
|
|
3648
|
+
...normalizedCiWorkflowNames.map((workflowName) => `Review the imported CI evidence for pipeline \`${workflowName}\` before promotion.`),
|
|
2350
3649
|
...(normalizedConstraints.length > 0 ? [`Keep QA follow-up bounded by: ${normalizedConstraints.join("; ")}.`] : [])
|
|
2351
3650
|
];
|
|
2352
3651
|
const summary = `QA report prepared for ${qaRequest.targetRef}.`;
|
|
@@ -2356,7 +3655,7 @@ const qaAnalystAgent = {
|
|
|
2356
3655
|
? "candidate release still requires explicit QA review before promotion."
|
|
2357
3656
|
: "no release context was supplied; QA output remains advisory.";
|
|
2358
3657
|
const qaReport = qaArtifactSchema.parse({
|
|
2359
|
-
...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/qa.yaml", qaRequest.targetRef, ...qaRequest.evidenceSources], qaIssueRefs, qaGithubRefs),
|
|
3658
|
+
...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/qa.yaml", qaRequest.targetRef, ...qaRequest.evidenceSources], qaIssueRefs, qaScmRefs, qaGithubRefs),
|
|
2360
3659
|
artifactKind: "qa-report",
|
|
2361
3660
|
lifecycleDomain: "test",
|
|
2362
3661
|
workflow: {
|
|
@@ -2367,13 +3666,21 @@ const qaAnalystAgent = {
|
|
|
2367
3666
|
targetRef: qaRequest.targetRef,
|
|
2368
3667
|
evidenceSources,
|
|
2369
3668
|
executedChecks,
|
|
3669
|
+
ciEvidenceSummary: normalizedCiEvidenceSummary,
|
|
2370
3670
|
findings,
|
|
2371
3671
|
coverageGaps,
|
|
2372
3672
|
recommendedNextChecks: recommendedNextChecks.length > 0
|
|
2373
3673
|
? recommendedNextChecks
|
|
2374
3674
|
: ["Capture additional bounded QA evidence before promotion."],
|
|
2375
|
-
releaseImpact: normalizedGithubActions.failingChecks.length > 0
|
|
2376
|
-
? `${releaseImpactBase}
|
|
3675
|
+
releaseImpact: normalizedGithubActions.failingChecks.length > 0 || normalizedCiFailingChecks.length > 0
|
|
3676
|
+
? `${releaseImpactBase} ${[
|
|
3677
|
+
normalizedGithubActions.failingChecks.length > 0
|
|
3678
|
+
? `GitHub Actions evidence still shows failing checks: ${normalizedGithubActions.failingChecks.join(", ")}.`
|
|
3679
|
+
: undefined,
|
|
3680
|
+
normalizedCiFailingChecks.length > 0
|
|
3681
|
+
? `Imported CI evidence still shows failing checks: ${normalizedCiFailingChecks.join(", ")}.`
|
|
3682
|
+
: undefined
|
|
3683
|
+
].filter((value) => Boolean(value)).join(" ")}`
|
|
2377
3684
|
: releaseImpactBase
|
|
2378
3685
|
}
|
|
2379
3686
|
});
|
|
@@ -2393,6 +3700,7 @@ const qaAnalystAgent = {
|
|
|
2393
3700
|
executedChecks,
|
|
2394
3701
|
focusAreas,
|
|
2395
3702
|
constraints: normalizedConstraints,
|
|
3703
|
+
ciEvidence: normalizedCiEvidence,
|
|
2396
3704
|
githubActions: normalizedGithubActions
|
|
2397
3705
|
},
|
|
2398
3706
|
synthesizedAssessment: {
|
|
@@ -2602,7 +3910,7 @@ const implementationPlannerAgent = {
|
|
|
2602
3910
|
];
|
|
2603
3911
|
const summary = `Implementation proposal prepared for ${implementationRequest.implementationGoal}.`;
|
|
2604
3912
|
const implementationProposal = implementationArtifactSchema.parse({
|
|
2605
|
-
...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/implementation.yaml", implementationRequest.designRecordRef], designRecord.source.issueRefs, designRecord.source.githubRefs),
|
|
3913
|
+
...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/implementation.yaml", implementationRequest.designRecordRef], designRecord.source.issueRefs, designRecord.source.scmRefs, designRecord.source.githubRefs),
|
|
2606
3914
|
artifactKind: "implementation-proposal",
|
|
2607
3915
|
lifecycleDomain: "build",
|
|
2608
3916
|
workflow: {
|
|
@@ -2715,7 +4023,7 @@ const designAnalystAgent = {
|
|
|
2715
4023
|
];
|
|
2716
4024
|
const summary = `Design record prepared for ${designRequest.decisionTarget}.`;
|
|
2717
4025
|
const designRecord = designArtifactSchema.parse({
|
|
2718
|
-
...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/design.yaml", designRequest.planningBriefRef], planningBrief.source.issueRefs, planningBrief.source.githubRefs),
|
|
4026
|
+
...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/design.yaml", designRequest.planningBriefRef], planningBrief.source.issueRefs, planningBrief.source.scmRefs, planningBrief.source.githubRefs),
|
|
2719
4027
|
artifactKind: "design-record",
|
|
2720
4028
|
lifecycleDomain: "design",
|
|
2721
4029
|
workflow: {
|
|
@@ -3013,6 +4321,12 @@ export function createBuiltinAgentRegistry() {
|
|
|
3013
4321
|
["release-intake", releaseIntakeAgent],
|
|
3014
4322
|
["release-evidence-normalizer", releaseEvidenceNormalizationAgent],
|
|
3015
4323
|
["release-analyst", releaseAnalystAgent],
|
|
4324
|
+
["pipeline-intake", pipelineIntakeAgent],
|
|
4325
|
+
["pipeline-evidence-normalizer", pipelineEvidenceNormalizationAgent],
|
|
4326
|
+
["pipeline-analyst", pipelineAnalystAgent],
|
|
4327
|
+
["deployment-gate-intake", deploymentGateIntakeAgent],
|
|
4328
|
+
["deployment-gate-evidence-normalizer", deploymentGateEvidenceNormalizationAgent],
|
|
4329
|
+
["deployment-gate-analyst", deploymentGateAnalystAgent],
|
|
3016
4330
|
["security-evidence-normalizer", securityEvidenceNormalizationAgent],
|
|
3017
4331
|
["security-analyst", securityAnalystAgent],
|
|
3018
4332
|
["qa-evidence-normalizer", qaEvidenceNormalizationAgent],
|