@h9-foundry/agentforge-cli 0.7.0 → 0.8.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.
@@ -1,6 +1,6 @@
1
- import { existsSync, readFileSync } from "node:fs";
1
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { agentManifestSchema, agentOutputSchema, designArtifactSchema, implementationArtifactSchema, implementationInventorySchema, qaArtifactSchema, qaEvidenceNormalizationSchema, qaRequestSchema, securityArtifactSchema, securityEvidenceNormalizationSchema, securityRequestSchema, planningArtifactSchema } from "@h9-foundry/agentforge-schemas";
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";
4
4
  const contextCollectorAgent = {
5
5
  manifest: agentManifestSchema.parse({
6
6
  version: 1,
@@ -73,6 +73,32 @@ function parsePackageScripts(packageJsonPath) {
73
73
  }
74
74
  return Object.fromEntries(Object.entries(parsed.scripts).filter((entry) => typeof entry[1] === "string"));
75
75
  }
76
+ function resolveWorkspacePackage(root, packageName) {
77
+ if (!root) {
78
+ return {};
79
+ }
80
+ for (const topLevel of ["packages", "agents", "adapters"]) {
81
+ const scopeRoot = join(root, topLevel);
82
+ if (!existsSync(scopeRoot)) {
83
+ continue;
84
+ }
85
+ for (const entry of readdirSync(scopeRoot)) {
86
+ const manifestPath = join(scopeRoot, entry, "package.json");
87
+ if (!existsSync(manifestPath)) {
88
+ continue;
89
+ }
90
+ const parsed = JSON.parse(readFileSync(manifestPath, "utf8"));
91
+ if (!isRecord(parsed) || parsed.name !== packageName || typeof parsed.version !== "string") {
92
+ continue;
93
+ }
94
+ return {
95
+ currentVersion: parsed.version,
96
+ manifestPath
97
+ };
98
+ }
99
+ }
100
+ return {};
101
+ }
76
102
  const allowedValidationScriptNames = new Set(["test", "lint", "typecheck", "build", "build:packages", "release:verify"]);
77
103
  function normalizeRequestedCommand(command) {
78
104
  return command.trim().replace(/\s+/g, " ");
@@ -148,6 +174,79 @@ function loadBundleArtifactPayloadPaths(bundlePath) {
148
174
  return [];
149
175
  });
150
176
  }
177
+ function loadBundleFinishedAt(bundlePath) {
178
+ if (!existsSync(bundlePath)) {
179
+ return undefined;
180
+ }
181
+ const parsed = JSON.parse(readFileSync(bundlePath, "utf8"));
182
+ return isRecord(parsed) && typeof parsed.finishedAt === "string" ? parsed.finishedAt : undefined;
183
+ }
184
+ function describeEvidenceObservation(repoRoot, pathValue) {
185
+ if (!repoRoot) {
186
+ return `Observed source ${pathValue} during deterministic intake.`;
187
+ }
188
+ const absolutePath = join(repoRoot, pathValue);
189
+ if (!existsSync(absolutePath)) {
190
+ return `Observed source ${pathValue} during deterministic intake.`;
191
+ }
192
+ const observedAt = pathValue.endsWith(".json") && pathValue.includes(".agentops/runs/")
193
+ ? loadBundleFinishedAt(absolutePath)
194
+ : undefined;
195
+ const fallbackObservedAt = observedAt ?? statSync(absolutePath).mtime.toISOString();
196
+ return `Observed source ${pathValue} at ${fallbackObservedAt}.`;
197
+ }
198
+ function loadGitHubActionsEvidence(bundlePath) {
199
+ if (!existsSync(bundlePath) || !bundlePath.endsWith(".json")) {
200
+ return undefined;
201
+ }
202
+ const parsed = JSON.parse(readFileSync(bundlePath, "utf8"));
203
+ const candidate = isRecord(parsed) ? { ...parsed, sourcePath: parsed.sourcePath ?? bundlePath } : parsed;
204
+ const result = githubActionsEvidenceSchema.safeParse(candidate);
205
+ return result.success ? result.data : undefined;
206
+ }
207
+ function isFailingGitHubActionsConclusion(conclusion) {
208
+ return Boolean(conclusion && !["success", "neutral", "skipped"].includes(conclusion));
209
+ }
210
+ function summarizeGitHubActionsFailures(evidence) {
211
+ const failedJobs = evidence.jobs
212
+ .filter((job) => job.status === "completed" && isFailingGitHubActionsConclusion(job.conclusion))
213
+ .map((job) => `${evidence.workflowName} / ${job.name}`);
214
+ const failedCheckRuns = evidence.checkRuns
215
+ .filter((checkRun) => checkRun.status === "completed" && isFailingGitHubActionsConclusion(checkRun.conclusion))
216
+ .map((checkRun) => `${evidence.workflowName} / ${checkRun.name}`);
217
+ const runLevelFailure = failedJobs.length === 0 &&
218
+ failedCheckRuns.length === 0 &&
219
+ evidence.status === "completed" &&
220
+ isFailingGitHubActionsConclusion(evidence.conclusion)
221
+ ? [`${evidence.workflowName} / workflow-run`]
222
+ : [];
223
+ return [...failedJobs, ...failedCheckRuns, ...runLevelFailure];
224
+ }
225
+ function normalizeGitHubActionsEvidence(repoRoot, evidenceSources) {
226
+ const evidence = evidenceSources.flatMap((pathValue) => {
227
+ if (!repoRoot) {
228
+ return [];
229
+ }
230
+ const normalized = loadGitHubActionsEvidence(join(repoRoot, pathValue));
231
+ return normalized ? [normalized] : [];
232
+ });
233
+ const workflowNames = [...new Set(evidence.map((entry) => entry.workflowName))];
234
+ const failingChecks = [...new Set(evidence.flatMap((entry) => summarizeGitHubActionsFailures(entry)))];
235
+ const provenanceRefs = [
236
+ ...new Set(evidence.flatMap((entry) => [
237
+ entry.sourcePath,
238
+ entry.htmlUrl,
239
+ ...entry.jobs.map((job) => job.htmlUrl),
240
+ ...entry.checkRuns.map((checkRun) => checkRun.detailsUrl)
241
+ ].filter((value) => Boolean(value))))
242
+ ];
243
+ return {
244
+ evidence,
245
+ workflowNames,
246
+ failingChecks,
247
+ provenanceRefs
248
+ };
249
+ }
151
250
  function derivePackageScope(pathValue) {
152
251
  const segments = pathValue.split("/").filter(Boolean);
153
252
  if (segments.length < 2) {
@@ -159,13 +258,17 @@ function derivePackageScope(pathValue) {
159
258
  }
160
259
  return undefined;
161
260
  }
261
+ function includesAnyKeyword(values, keywords) {
262
+ const haystack = values.join(" ").toLowerCase();
263
+ return keywords.some((keyword) => haystack.includes(keyword));
264
+ }
162
265
  function getWorkflowInput(stateSlice, key) {
163
266
  if (!isRecord(stateSlice.workflowInputs)) {
164
267
  return undefined;
165
268
  }
166
269
  return stateSlice.workflowInputs[key];
167
270
  }
168
- function buildLifecycleArtifactEnvelopeBase(state, displayName, summary, inputRefs, issueRefs = []) {
271
+ function buildLifecycleArtifactEnvelopeBase(state, displayName, summary, inputRefs, issueRefs = [], githubRefs = []) {
169
272
  return {
170
273
  schemaVersion: state.version,
171
274
  workflow: {
@@ -176,7 +279,8 @@ function buildLifecycleArtifactEnvelopeBase(state, displayName, summary, inputRe
176
279
  sourceType: "workflow-run",
177
280
  runId: state.runId,
178
281
  inputRefs: [...inputRefs],
179
- issueRefs: [...issueRefs]
282
+ issueRefs: [...issueRefs],
283
+ githubRefs: [...githubRefs]
180
284
  },
181
285
  status: "complete",
182
286
  generatedAt: new Date().toISOString(),
@@ -204,7 +308,7 @@ function buildLifecycleArtifactEnvelopeBase(state, displayName, summary, inputRe
204
308
  summary
205
309
  };
206
310
  }
207
- function buildArtifactEnvelopeBase(state, summary, inputRefs, issueRefs) {
311
+ function buildArtifactEnvelopeBase(state, summary, inputRefs, issueRefs, githubRefs = []) {
208
312
  return {
209
313
  schemaVersion: state.version,
210
314
  workflow: {
@@ -214,7 +318,8 @@ function buildArtifactEnvelopeBase(state, summary, inputRefs, issueRefs) {
214
318
  sourceType: "workflow-run",
215
319
  runId: state.runId,
216
320
  inputRefs: [...inputRefs],
217
- issueRefs: [...issueRefs]
321
+ issueRefs: [...issueRefs],
322
+ githubRefs: [...githubRefs]
218
323
  },
219
324
  status: "complete",
220
325
  generatedAt: new Date().toISOString(),
@@ -340,6 +445,7 @@ const planningAnalystAgent = {
340
445
  outputSchema: agentOutputSchema,
341
446
  async execute({ state, stateSlice }) {
342
447
  const planningRequest = getWorkflowInput(stateSlice, "planningRequest");
448
+ const planningGithubRefs = getWorkflowInput(stateSlice, "planningGithubRefs") ?? [];
343
449
  const requestFile = getWorkflowInput(stateSlice, "requestFile");
344
450
  if (!planningRequest) {
345
451
  throw new Error("planning-discovery requires a validated planning request before planning analysis.");
@@ -363,7 +469,7 @@ const planningAnalystAgent = {
363
469
  ];
364
470
  const summary = `Planning brief scoped ${objectives.length} objective(s) for ${state.repo.name}.`;
365
471
  const planningBrief = planningArtifactSchema.parse({
366
- ...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/planning.yaml"], planningRequest.issueRefs),
472
+ ...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/planning.yaml"], planningRequest.issueRefs, planningGithubRefs),
367
473
  artifactKind: "planning-brief",
368
474
  lifecycleDomain: "plan",
369
475
  workflow: {
@@ -759,12 +865,12 @@ const securityIntakeAgent = {
759
865
  });
760
866
  }
761
867
  };
762
- const securityEvidenceNormalizationAgent = {
868
+ const incidentIntakeAgent = {
763
869
  manifest: agentManifestSchema.parse({
764
870
  version: 1,
765
- name: "security-evidence-normalizer",
766
- displayName: "Security Evidence Normalizer",
767
- category: "security",
871
+ name: "incident-intake",
872
+ displayName: "Incident Intake",
873
+ category: "operate",
768
874
  runtime: {
769
875
  minVersion: "0.1.0",
770
876
  kind: "deterministic"
@@ -773,7 +879,66 @@ const securityEvidenceNormalizationAgent = {
773
879
  model: false,
774
880
  network: false,
775
881
  tools: [],
776
- readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/*.json", "**/*.md", "**/package.json"],
882
+ readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/*.json", "**/*.log", "**/*.md"],
883
+ writePaths: []
884
+ },
885
+ inputs: ["workflowInputs", "repo"],
886
+ outputs: ["summary", "metadata"],
887
+ contextPolicy: {
888
+ sections: ["workflowInputs", "repo", "context"],
889
+ minimalContext: true
890
+ },
891
+ catalog: {
892
+ domain: "operate",
893
+ supportLevel: "internal",
894
+ maturity: "mvp",
895
+ trustScope: "official-core-only"
896
+ },
897
+ trust: {
898
+ tier: "core",
899
+ source: "official",
900
+ reviewed: true
901
+ }
902
+ }),
903
+ outputSchema: agentOutputSchema,
904
+ async execute({ stateSlice }) {
905
+ const incidentRequest = getWorkflowInput(stateSlice, "incidentRequest");
906
+ const requestFile = getWorkflowInput(stateSlice, "requestFile");
907
+ if (!incidentRequest) {
908
+ throw new Error("incident-handoff requires a validated incident request before runtime execution.");
909
+ }
910
+ return agentOutputSchema.parse({
911
+ summary: `Loaded incident request from ${requestFile ?? ".agentops/requests/incident.yaml"} for ${incidentRequest.incidentSummary}.`,
912
+ findings: [],
913
+ proposedActions: [],
914
+ lifecycleArtifacts: [],
915
+ requestedTools: [],
916
+ blockedActionFlags: [],
917
+ metadata: {
918
+ ...incidentRequestSchema.parse({
919
+ ...incidentRequest,
920
+ evidenceSources: [...new Set(incidentRequest.evidenceSources)]
921
+ }),
922
+ evidenceSourceCount: incidentRequest.evidenceSources.length + incidentRequest.releaseReportRefs.length
923
+ }
924
+ });
925
+ }
926
+ };
927
+ const incidentEvidenceNormalizationAgent = {
928
+ manifest: agentManifestSchema.parse({
929
+ version: 1,
930
+ name: "incident-evidence-normalizer",
931
+ displayName: "Incident Evidence Normalizer",
932
+ category: "operate",
933
+ runtime: {
934
+ minVersion: "0.1.0",
935
+ kind: "deterministic"
936
+ },
937
+ permissions: {
938
+ model: false,
939
+ network: false,
940
+ tools: [],
941
+ readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/*.json", "**/*.log", "**/*.md", "**/*.txt"],
777
942
  writePaths: []
778
943
  },
779
944
  inputs: ["workflowInputs", "repo", "agentResults"],
@@ -783,7 +948,7 @@ const securityEvidenceNormalizationAgent = {
783
948
  minimalContext: true
784
949
  },
785
950
  catalog: {
786
- domain: "security",
951
+ domain: "operate",
787
952
  supportLevel: "internal",
788
953
  maturity: "mvp",
789
954
  trustScope: "official-core-only"
@@ -796,53 +961,60 @@ const securityEvidenceNormalizationAgent = {
796
961
  }),
797
962
  outputSchema: agentOutputSchema,
798
963
  async execute({ stateSlice }) {
799
- const securityRequest = getWorkflowInput(stateSlice, "securityRequest");
800
- if (!securityRequest) {
801
- throw new Error("security-review requires validated security request inputs before evidence normalization.");
964
+ const incidentRequest = getWorkflowInput(stateSlice, "incidentRequest");
965
+ if (!incidentRequest) {
966
+ throw new Error("incident-handoff requires validated incident request inputs before evidence normalization.");
802
967
  }
803
968
  const repoRoot = stateSlice.repo?.root;
804
969
  const intakeMetadata = isRecord(stateSlice.agentResults?.intake?.metadata) ? stateSlice.agentResults.intake.metadata : {};
805
- const targetType = typeof intakeMetadata.targetType === "string" && intakeMetadata.targetType === "artifact-bundle"
806
- ? "artifact-bundle"
807
- : "local-reference";
808
- const targetPath = repoRoot ? join(repoRoot, securityRequest.targetRef) : securityRequest.targetRef;
809
- if (repoRoot && !existsSync(targetPath)) {
810
- throw new Error(`Security target reference not found: ${securityRequest.targetRef}`);
811
- }
812
- const referencedArtifactKinds = targetType === "artifact-bundle" ? loadBundleArtifactKinds(targetPath) : [];
813
- const normalizedEvidenceSources = [...new Set([securityRequest.targetRef, ...securityRequest.evidenceSources])];
970
+ const releaseReportRefs = asStringArray(intakeMetadata.releaseReportRefs).length > 0
971
+ ? asStringArray(intakeMetadata.releaseReportRefs)
972
+ : incidentRequest.releaseReportRefs;
973
+ const evidenceSources = asStringArray(intakeMetadata.evidenceSources).length > 0
974
+ ? asStringArray(intakeMetadata.evidenceSources)
975
+ : incidentRequest.evidenceSources;
976
+ const severityHint = typeof intakeMetadata.severityHint === "string" ? intakeMetadata.severityHint : incidentRequest.severityHint;
977
+ const normalizedEvidenceSources = [...new Set([...evidenceSources, ...releaseReportRefs])];
814
978
  const missingEvidenceSources = normalizedEvidenceSources.filter((pathValue) => repoRoot && !existsSync(join(repoRoot, pathValue)));
815
979
  if (missingEvidenceSources.length > 0) {
816
- throw new Error(`Security evidence source not found: ${missingEvidenceSources[0]}`);
980
+ throw new Error(`Incident evidence source not found: ${missingEvidenceSources[0]}`);
817
981
  }
818
- const normalizedFocusAreas = securityRequest.focusAreas.length > 0 ? [...new Set(securityRequest.focusAreas)] : ["general-review"];
819
- const affectedPackages = targetType === "artifact-bundle"
820
- ? [...new Set(loadBundleArtifactPayloadPaths(targetPath).map(derivePackageScope).filter((value) => Boolean(value)))]
821
- : [];
822
- const securitySignals = [
823
- ...(referencedArtifactKinds.length > 0 ? [`Referenced artifact kinds: ${referencedArtifactKinds.join(", ")}`] : []),
824
- ...(affectedPackages.length > 0 ? [`Affected packages inferred from bounded artifact payloads: ${affectedPackages.join(", ")}`] : []),
825
- ...(normalizedFocusAreas.length > 0 ? [`Requested focus areas: ${normalizedFocusAreas.join(", ")}`] : []),
826
- "Security evidence collection remains local, read-only, and bounded to validated references."
982
+ const referencedArtifactKinds = [...new Set(releaseReportRefs.flatMap((pathValue) => (repoRoot ? loadBundleArtifactKinds(join(repoRoot, pathValue)) : [])))];
983
+ const timelineSummary = [
984
+ `Severity hint: ${severityHint}.`,
985
+ "Normalized staged incident evidence and release-report references before reasoning.",
986
+ ...normalizedEvidenceSources.map((pathValue) => describeEvidenceObservation(repoRoot, pathValue))
827
987
  ];
828
- const provenanceRefs = [
829
- securityRequest.targetRef,
830
- ...securityRequest.evidenceSources,
831
- ...referencedArtifactKinds.map((artifactKind) => `${securityRequest.targetRef}#${artifactKind}`)
988
+ const likelyImpactedAreas = [
989
+ ...(releaseReportRefs.length > 0 ? ["release-readiness"] : []),
990
+ ...(evidenceSources.length > 0 ? ["staged-operational-evidence"] : []),
991
+ ...(severityHint === "high" || severityHint === "critical" ? ["security-follow-up"] : [])
832
992
  ];
833
- const normalization = securityEvidenceNormalizationSchema.parse({
834
- targetRef: securityRequest.targetRef,
835
- targetType,
836
- referencedArtifactKinds,
993
+ const followUpWorkflowRefs = [
994
+ "maintenance-triage",
995
+ ...(releaseReportRefs.length > 0 ? ["release-readiness"] : []),
996
+ ...(severityHint === "high" || severityHint === "critical" ? ["security-review"] : [])
997
+ ];
998
+ const normalization = incidentEvidenceNormalizationSchema.parse({
999
+ incidentSummary: incidentRequest.incidentSummary,
1000
+ severityHint,
837
1001
  normalizedEvidenceSources,
838
1002
  missingEvidenceSources: [],
839
- normalizedFocusAreas,
840
- securitySignals,
841
- provenanceRefs: [...new Set(provenanceRefs)],
842
- affectedPackages
1003
+ releaseReportRefs,
1004
+ timelineSummary,
1005
+ likelyImpactedAreas: [...new Set(likelyImpactedAreas)],
1006
+ followUpWorkflowRefs: [...new Set(followUpWorkflowRefs)],
1007
+ provenanceRefs: [
1008
+ ...new Set([
1009
+ ...evidenceSources,
1010
+ ...releaseReportRefs.map((pathValue) => `${pathValue}#release-report`)
1011
+ ])
1012
+ ],
1013
+ redactionCategories: ["github-token", "api-key", "aws-key", "bearer-token", "password", "private-key", "operational-sensitive"],
1014
+ referencedArtifactKinds
843
1015
  });
844
1016
  return agentOutputSchema.parse({
845
- summary: `Normalized security evidence for ${securityRequest.targetRef}.`,
1017
+ summary: `Normalized incident evidence across ${normalization.normalizedEvidenceSources.length} staged source(s).`,
846
1018
  findings: [],
847
1019
  proposedActions: [],
848
1020
  lifecycleArtifacts: [],
@@ -852,31 +1024,31 @@ const securityEvidenceNormalizationAgent = {
852
1024
  });
853
1025
  }
854
1026
  };
855
- const securityAnalystAgent = {
1027
+ const maintenanceIntakeAgent = {
856
1028
  manifest: agentManifestSchema.parse({
857
1029
  version: 1,
858
- name: "security-analyst",
859
- displayName: "Security Analyst",
860
- category: "security",
1030
+ name: "maintenance-intake",
1031
+ displayName: "Maintenance Intake",
1032
+ category: "maintain",
861
1033
  runtime: {
862
1034
  minVersion: "0.1.0",
863
- kind: "reasoning"
1035
+ kind: "deterministic"
864
1036
  },
865
1037
  permissions: {
866
- model: true,
1038
+ model: false,
867
1039
  network: false,
868
1040
  tools: [],
869
- readPaths: ["**/*"],
1041
+ readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/*.json", "**/*.md", "**/*.txt"],
870
1042
  writePaths: []
871
1043
  },
872
- inputs: ["workflowInputs", "repo", "changes", "agentResults"],
873
- outputs: ["lifecycleArtifacts"],
1044
+ inputs: ["workflowInputs", "repo"],
1045
+ outputs: ["summary", "metadata"],
874
1046
  contextPolicy: {
875
- sections: ["workflowInputs", "repo", "changes", "agentResults"],
1047
+ sections: ["workflowInputs", "repo", "context"],
876
1048
  minimalContext: true
877
1049
  },
878
1050
  catalog: {
879
- domain: "security",
1051
+ domain: "maintain",
880
1052
  supportLevel: "internal",
881
1053
  maturity: "mvp",
882
1054
  trustScope: "official-core-only"
@@ -888,120 +1060,44 @@ const securityAnalystAgent = {
888
1060
  }
889
1061
  }),
890
1062
  outputSchema: agentOutputSchema,
891
- async execute({ state, stateSlice }) {
892
- const securityRequest = getWorkflowInput(stateSlice, "securityRequest");
1063
+ async execute({ stateSlice }) {
1064
+ const maintenanceRequest = getWorkflowInput(stateSlice, "maintenanceRequest");
893
1065
  const requestFile = getWorkflowInput(stateSlice, "requestFile");
894
- if (!securityRequest) {
895
- throw new Error("security-review requires validated security inputs before security analysis.");
1066
+ const maintenanceIssueRefs = getWorkflowInput(stateSlice, "maintenanceIssueRefs") ?? [];
1067
+ const maintenanceGithubRefs = getWorkflowInput(stateSlice, "maintenanceGithubRefs") ?? [];
1068
+ if (!maintenanceRequest) {
1069
+ throw new Error("maintenance-triage requires a validated maintenance request before runtime execution.");
896
1070
  }
897
- const intakeMetadata = isRecord(stateSlice.agentResults?.intake?.metadata) ? stateSlice.agentResults.intake.metadata : {};
898
- const evidenceMetadata = securityEvidenceNormalizationSchema.safeParse(stateSlice.agentResults?.evidence?.metadata);
899
- const normalizedEvidence = evidenceMetadata.success ? evidenceMetadata.data : undefined;
900
- const referencedArtifactKinds = normalizedEvidence?.referencedArtifactKinds ?? asStringArray(intakeMetadata.referencedArtifactKinds);
901
- const normalizedFocusAreas = normalizedEvidence?.normalizedFocusAreas ?? asStringArray(intakeMetadata.focusAreas);
902
- const normalizedConstraints = asStringArray(intakeMetadata.constraints);
903
- const evidenceSources = normalizedEvidence?.normalizedEvidenceSources && normalizedEvidence.normalizedEvidenceSources.length > 0
904
- ? normalizedEvidence.normalizedEvidenceSources
905
- : asStringArray(intakeMetadata.evidenceSources).length > 0
906
- ? asStringArray(intakeMetadata.evidenceSources)
907
- : [...new Set([securityRequest.targetRef, ...securityRequest.evidenceSources])];
908
- const focusAreas = normalizedFocusAreas.length > 0 ? normalizedFocusAreas : securityRequest.focusAreas;
909
- const inferredSeverity = securityRequest.releaseContext === "blocking" ? "high" : securityRequest.releaseContext === "candidate" ? "medium" : "low";
910
- const findings = focusAreas.length > 0
911
- ? focusAreas.map((focusArea, index) => ({
912
- id: `security-finding-${index + 1}`,
913
- title: `Inspect ${focusArea} evidence before promotion`,
914
- summary: `Security review flagged ${focusArea} for bounded follow-up on ${securityRequest.targetRef}.`,
915
- severity: inferredSeverity,
916
- rationale: "The MVP security workflow synthesizes a structured report from validated references before deterministic evidence normalization lands.",
917
- confidence: 0.76,
918
- location: securityRequest.targetRef,
919
- tags: ["security", focusArea]
920
- }))
921
- : [
922
- {
923
- id: "security-finding-1",
924
- title: "Inspect referenced security evidence before promotion",
925
- summary: `Security review requires bounded interpretation of the referenced evidence for ${securityRequest.targetRef}.`,
926
- severity: inferredSeverity,
927
- rationale: "The current security workflow is read-only and request-driven, so findings remain tied to validated local references rather than automatic scanning.",
928
- confidence: 0.72,
929
- location: securityRequest.targetRef,
930
- tags: ["security", "evidence"]
931
- }
932
- ];
933
- const mitigations = [
934
- ...focusAreas.map((focusArea) => `Review ${focusArea} evidence and document the release impact before promotion.`),
935
- ...(normalizedConstraints.length > 0 ? [`Keep security follow-up bounded by: ${normalizedConstraints.join("; ")}.`] : [])
936
- ];
937
- const followUpWork = [
938
- ...(normalizedEvidence?.securitySignals ?? []),
939
- ...(referencedArtifactKinds.length > 0
940
- ? [`Confirm the security posture for referenced artifacts: ${referencedArtifactKinds.join(", ")}.`]
941
- : []),
942
- "Use deterministic security evidence normalization outputs before broadening the workflow surface."
943
- ];
944
- const summary = `Security report prepared for ${securityRequest.targetRef}.`;
945
- const securityReport = securityArtifactSchema.parse({
946
- ...buildLifecycleArtifactEnvelopeBase(state, "Security Review", summary, [
947
- requestFile ?? ".agentops/requests/security.yaml",
948
- ...(normalizedEvidence?.provenanceRefs ?? [securityRequest.targetRef, ...securityRequest.evidenceSources])
949
- ]),
950
- artifactKind: "security-report",
951
- lifecycleDomain: "security",
952
- redaction: {
953
- applied: true,
954
- strategyVersion: "1.0.0",
955
- categories: ["github-token", "api-key", "aws-key", "bearer-token", "password", "private-key", "security-sensitive"]
956
- },
957
- payload: {
958
- targetRef: securityRequest.targetRef,
959
- evidenceSources,
960
- findings,
961
- severitySummary: `highest severity: ${inferredSeverity}; ${findings.length} synthesized security finding(s).`,
962
- mitigations: mitigations.length > 0
963
- ? mitigations
964
- : ["Review the referenced security evidence before promoting this workflow output."],
965
- releaseImpact: securityRequest.releaseContext === "blocking"
966
- ? "release-blocking security findings require resolution before promotion."
967
- : securityRequest.releaseContext === "candidate"
968
- ? "candidate release requires explicit security review before promotion."
969
- : "no release context was supplied; security output remains advisory.",
970
- followUpWork
971
- }
972
- });
973
1071
  return agentOutputSchema.parse({
974
- summary,
1072
+ summary: `Loaded maintenance request from ${requestFile ?? ".agentops/requests/maintenance.yaml"} for ${maintenanceRequest.maintenanceGoal}.`,
975
1073
  findings: [],
976
1074
  proposedActions: [],
977
- lifecycleArtifacts: [securityReport],
1075
+ lifecycleArtifacts: [],
978
1076
  requestedTools: [],
979
1077
  blockedActionFlags: [],
980
- confidence: 0.76,
981
1078
  metadata: {
982
- deterministicInputs: {
983
- targetRef: securityRequest.targetRef,
984
- evidenceSources,
985
- focusAreas,
986
- constraints: normalizedConstraints,
987
- referencedArtifactKinds,
988
- normalizedEvidence: normalizedEvidence ?? null
989
- },
990
- synthesizedAssessment: {
991
- severitySummary: securityReport.payload.severitySummary,
992
- mitigations: securityReport.payload.mitigations,
993
- followUpWork: securityReport.payload.followUpWork
994
- }
1079
+ ...maintenanceRequestSchema.parse({
1080
+ ...maintenanceRequest,
1081
+ dependencyAlertRefs: [...new Set(maintenanceRequest.dependencyAlertRefs)],
1082
+ docsTaskRefs: [...new Set(maintenanceRequest.docsTaskRefs)],
1083
+ releaseReportRefs: [...new Set(maintenanceRequest.releaseReportRefs)],
1084
+ issueRefs: [...new Set(maintenanceRequest.issueRefs)]
1085
+ }),
1086
+ maintenanceIssueRefs,
1087
+ maintenanceGithubRefs,
1088
+ evidenceSourceCount: maintenanceRequest.dependencyAlertRefs.length +
1089
+ maintenanceRequest.docsTaskRefs.length +
1090
+ maintenanceRequest.releaseReportRefs.length
995
1091
  }
996
1092
  });
997
1093
  }
998
1094
  };
999
- const qaEvidenceNormalizationAgent = {
1095
+ const maintenanceEvidenceNormalizerAgent = {
1000
1096
  manifest: agentManifestSchema.parse({
1001
1097
  version: 1,
1002
- name: "qa-evidence-normalizer",
1003
- displayName: "QA Evidence Normalizer",
1004
- category: "qa",
1098
+ name: "maintenance-evidence-normalizer",
1099
+ displayName: "Maintenance Evidence Normalizer",
1100
+ category: "maintain",
1005
1101
  runtime: {
1006
1102
  minVersion: "0.1.0",
1007
1103
  kind: "deterministic"
@@ -1010,7 +1106,7 @@ const qaEvidenceNormalizationAgent = {
1010
1106
  model: false,
1011
1107
  network: false,
1012
1108
  tools: [],
1013
- readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/package.json", "**/*.json", "**/*.xml", "**/*.log", "**/*.md"],
1109
+ readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/*.json", "**/*.md", "**/*.txt", "**/package.json"],
1014
1110
  writePaths: []
1015
1111
  },
1016
1112
  inputs: ["workflowInputs", "repo", "agentResults"],
@@ -1020,7 +1116,7 @@ const qaEvidenceNormalizationAgent = {
1020
1116
  minimalContext: true
1021
1117
  },
1022
1118
  catalog: {
1023
- domain: "test",
1119
+ domain: "maintain",
1024
1120
  supportLevel: "internal",
1025
1121
  maturity: "mvp",
1026
1122
  trustScope: "official-core-only"
@@ -1033,28 +1129,1080 @@ const qaEvidenceNormalizationAgent = {
1033
1129
  }),
1034
1130
  outputSchema: agentOutputSchema,
1035
1131
  async execute({ stateSlice }) {
1036
- const qaRequest = getWorkflowInput(stateSlice, "qaRequest");
1037
- if (!qaRequest) {
1038
- throw new Error("qa-review requires validated QA request inputs before evidence normalization.");
1132
+ const maintenanceRequest = getWorkflowInput(stateSlice, "maintenanceRequest");
1133
+ if (!maintenanceRequest) {
1134
+ throw new Error("maintenance-triage requires validated maintenance inputs before evidence normalization.");
1039
1135
  }
1040
1136
  const repoRoot = stateSlice.repo?.root;
1041
- const packageManager = stateSlice.repo?.packageManager || "pnpm";
1042
1137
  const intakeMetadata = isRecord(stateSlice.agentResults?.intake?.metadata) ? stateSlice.agentResults.intake.metadata : {};
1043
- const targetType = typeof intakeMetadata.targetType === "string" ? intakeMetadata.targetType : "local-reference";
1044
- const targetPath = repoRoot ? join(repoRoot, qaRequest.targetRef) : qaRequest.targetRef;
1045
- if (repoRoot && !existsSync(targetPath)) {
1046
- throw new Error(`QA target reference not found: ${qaRequest.targetRef}`);
1047
- }
1048
- const referencedArtifactKinds = targetType === "artifact-bundle" ? loadBundleArtifactKinds(targetPath) : [];
1049
- const normalizedEvidenceSources = [...new Set([qaRequest.targetRef, ...qaRequest.evidenceSources])];
1138
+ const dependencyAlertRefs = asStringArray(intakeMetadata.dependencyAlertRefs).length > 0
1139
+ ? asStringArray(intakeMetadata.dependencyAlertRefs)
1140
+ : maintenanceRequest.dependencyAlertRefs;
1141
+ const docsTaskRefs = asStringArray(intakeMetadata.docsTaskRefs).length > 0
1142
+ ? asStringArray(intakeMetadata.docsTaskRefs)
1143
+ : maintenanceRequest.docsTaskRefs;
1144
+ const releaseReportRefs = asStringArray(intakeMetadata.releaseReportRefs).length > 0
1145
+ ? asStringArray(intakeMetadata.releaseReportRefs)
1146
+ : maintenanceRequest.releaseReportRefs;
1147
+ const normalizedEvidenceSources = [...new Set([...dependencyAlertRefs, ...docsTaskRefs, ...releaseReportRefs])];
1050
1148
  const missingEvidenceSources = normalizedEvidenceSources.filter((pathValue) => repoRoot && !existsSync(join(repoRoot, pathValue)));
1051
1149
  if (missingEvidenceSources.length > 0) {
1052
- throw new Error(`QA evidence source not found: ${missingEvidenceSources[0]}`);
1150
+ throw new Error(`Maintenance evidence source not found: ${missingEvidenceSources[0]}`);
1053
1151
  }
1054
- const bundleAffectedPaths = targetType === "artifact-bundle" && referencedArtifactKinds.includes("implementation-proposal") && existsSync(targetPath)
1055
- ? asStringArray((() => {
1056
- const parsed = JSON.parse(readFileSync(targetPath, "utf8"));
1057
- if (!isRecord(parsed) || !Array.isArray(parsed.lifecycleArtifacts)) {
1152
+ const referencedArtifactKinds = [
1153
+ ...new Set(releaseReportRefs.flatMap((pathValue) => (repoRoot ? loadBundleArtifactKinds(join(repoRoot, pathValue)) : [])))
1154
+ ];
1155
+ const releasePayloadPaths = releaseReportRefs.flatMap((pathValue) => (repoRoot ? loadBundleArtifactPayloadPaths(join(repoRoot, pathValue)) : []));
1156
+ const affectedPackagesOrDocs = [
1157
+ ...new Set([...docsTaskRefs, ...releasePayloadPaths]
1158
+ .map((pathValue) => derivePackageScope(pathValue) ?? pathValue)
1159
+ .filter((value) => Boolean(value)))
1160
+ ];
1161
+ const maintenanceSignals = [
1162
+ ...normalizedEvidenceSources.map((pathValue) => describeEvidenceObservation(repoRoot, pathValue)),
1163
+ ...(dependencyAlertRefs.length > 0 ? ["Dependency alert references contribute bounded maintenance follow-up context."] : []),
1164
+ ...(docsTaskRefs.length > 0 ? ["Documentation task references contribute bounded maintenance follow-up context."] : []),
1165
+ ...(releaseReportRefs.length > 0 ? ["Release report references contribute bounded maintenance follow-up context."] : []),
1166
+ ...(referencedArtifactKinds.length > 0 ? [`Referenced artifact kinds: ${referencedArtifactKinds.join(", ")}`] : [])
1167
+ ];
1168
+ const routingInputs = [
1169
+ maintenanceRequest.maintenanceGoal,
1170
+ ...dependencyAlertRefs,
1171
+ ...docsTaskRefs,
1172
+ ...releaseReportRefs,
1173
+ ...maintenanceSignals
1174
+ ];
1175
+ const securitySignal = referencedArtifactKinds.includes("security-report") ||
1176
+ includesAnyKeyword(routingInputs, ["security", "vulnerability", "vuln", "cve", "advisory"]);
1177
+ const qaSignal = referencedArtifactKinds.includes("qa-report") ||
1178
+ includesAnyKeyword(routingInputs, ["qa", "test", "coverage", "flaky"]);
1179
+ const followUpWorkflowRefs = [
1180
+ ...new Set([
1181
+ ...(securitySignal ? ["security-review"] : []),
1182
+ ...(dependencyAlertRefs.length > 0 || docsTaskRefs.length > 0 ? ["implementation-proposal"] : []),
1183
+ ...(releaseReportRefs.length > 0 ? ["release-readiness"] : []),
1184
+ ...(qaSignal ? ["qa-review"] : [])
1185
+ ])
1186
+ ];
1187
+ const routingRecommendation = followUpWorkflowRefs[0] ?? "implementation-proposal";
1188
+ const provenanceRefs = [
1189
+ ...new Set([
1190
+ ...dependencyAlertRefs,
1191
+ ...docsTaskRefs,
1192
+ ...releaseReportRefs.map((pathValue) => `${pathValue}#release-report`)
1193
+ ])
1194
+ ];
1195
+ const normalization = maintenanceEvidenceNormalizationSchema.parse({
1196
+ maintenanceGoal: maintenanceRequest.maintenanceGoal,
1197
+ dependencyAlertRefs,
1198
+ docsTaskRefs,
1199
+ releaseReportRefs,
1200
+ normalizedEvidenceSources,
1201
+ missingEvidenceSources: [],
1202
+ referencedArtifactKinds,
1203
+ affectedPackagesOrDocs,
1204
+ maintenanceSignals,
1205
+ followUpWorkflowRefs,
1206
+ routingRecommendation,
1207
+ provenanceRefs
1208
+ });
1209
+ return agentOutputSchema.parse({
1210
+ summary: `Normalized maintenance evidence across ${normalization.normalizedEvidenceSources.length} source(s) for ${maintenanceRequest.maintenanceGoal}.`,
1211
+ findings: [],
1212
+ proposedActions: [],
1213
+ lifecycleArtifacts: [],
1214
+ requestedTools: [],
1215
+ blockedActionFlags: [],
1216
+ metadata: normalization
1217
+ });
1218
+ }
1219
+ };
1220
+ const maintenanceAnalystAgent = {
1221
+ manifest: agentManifestSchema.parse({
1222
+ version: 1,
1223
+ name: "maintenance-analyst",
1224
+ displayName: "Maintenance Analyst",
1225
+ category: "maintain",
1226
+ runtime: {
1227
+ minVersion: "0.1.0",
1228
+ kind: "reasoning"
1229
+ },
1230
+ permissions: {
1231
+ model: true,
1232
+ network: false,
1233
+ tools: [],
1234
+ readPaths: ["**/*"],
1235
+ writePaths: []
1236
+ },
1237
+ inputs: ["workflowInputs", "repo", "changes", "agentResults"],
1238
+ outputs: ["lifecycleArtifacts"],
1239
+ contextPolicy: {
1240
+ sections: ["workflowInputs", "repo", "changes", "agentResults"],
1241
+ minimalContext: true
1242
+ },
1243
+ catalog: {
1244
+ domain: "maintain",
1245
+ supportLevel: "internal",
1246
+ maturity: "mvp",
1247
+ trustScope: "official-core-only"
1248
+ },
1249
+ trust: {
1250
+ tier: "core",
1251
+ source: "official",
1252
+ reviewed: true
1253
+ }
1254
+ }),
1255
+ outputSchema: agentOutputSchema,
1256
+ async execute({ state, stateSlice }) {
1257
+ const maintenanceRequest = getWorkflowInput(stateSlice, "maintenanceRequest");
1258
+ const maintenanceIssueRefs = getWorkflowInput(stateSlice, "maintenanceIssueRefs") ?? [];
1259
+ const maintenanceGithubRefs = getWorkflowInput(stateSlice, "maintenanceGithubRefs") ?? [];
1260
+ const requestFile = getWorkflowInput(stateSlice, "requestFile");
1261
+ if (!maintenanceRequest) {
1262
+ throw new Error("maintenance-triage requires validated maintenance inputs before maintenance analysis.");
1263
+ }
1264
+ const intakeMetadata = isRecord(stateSlice.agentResults?.intake?.metadata) ? stateSlice.agentResults.intake.metadata : {};
1265
+ const evidenceMetadata = maintenanceEvidenceNormalizationSchema.safeParse(stateSlice.agentResults?.evidence?.metadata);
1266
+ const normalizedEvidence = evidenceMetadata.success ? evidenceMetadata.data : undefined;
1267
+ const dependencyAlertRefs = normalizedEvidence?.dependencyAlertRefs ??
1268
+ (asStringArray(intakeMetadata.dependencyAlertRefs).length > 0
1269
+ ? asStringArray(intakeMetadata.dependencyAlertRefs)
1270
+ : maintenanceRequest.dependencyAlertRefs);
1271
+ const docsTaskRefs = normalizedEvidence?.docsTaskRefs ??
1272
+ (asStringArray(intakeMetadata.docsTaskRefs).length > 0
1273
+ ? asStringArray(intakeMetadata.docsTaskRefs)
1274
+ : maintenanceRequest.docsTaskRefs);
1275
+ const releaseReportRefs = normalizedEvidence?.releaseReportRefs ??
1276
+ (asStringArray(intakeMetadata.releaseReportRefs).length > 0
1277
+ ? asStringArray(intakeMetadata.releaseReportRefs)
1278
+ : maintenanceRequest.releaseReportRefs);
1279
+ const normalizedConstraints = asStringArray(intakeMetadata.constraints);
1280
+ const evidenceSources = normalizedEvidence?.normalizedEvidenceSources ?? [...new Set([...dependencyAlertRefs, ...docsTaskRefs, ...releaseReportRefs])];
1281
+ const affectedPackagesOrDocs = normalizedEvidence?.affectedPackagesOrDocs ?? [];
1282
+ const followUpWorkflowRefs = normalizedEvidence?.followUpWorkflowRefs ?? [];
1283
+ const routingRecommendation = normalizedEvidence?.routingRecommendation ?? "implementation-proposal";
1284
+ const maintenanceSignals = normalizedEvidence?.maintenanceSignals ?? [];
1285
+ const referencedArtifactKinds = normalizedEvidence?.referencedArtifactKinds ?? [];
1286
+ const currentFindings = [
1287
+ ...(maintenanceSignals.length > 0
1288
+ ? maintenanceSignals
1289
+ : [
1290
+ ...(dependencyAlertRefs.length > 0 ? [`${dependencyAlertRefs.length} dependency alert reference(s) require maintenance triage.`] : []),
1291
+ ...(docsTaskRefs.length > 0 ? [`${docsTaskRefs.length} docs task reference(s) require maintenance triage.`] : []),
1292
+ ...(releaseReportRefs.length > 0 ? [`${releaseReportRefs.length} release-report reference(s) contribute maintenance follow-up context.`] : [])
1293
+ ])
1294
+ ];
1295
+ const recommendedActions = [
1296
+ ...dependencyAlertRefs.map((pathValue) => `Review dependency alert reference \`${pathValue}\` before choosing a follow-up workflow.`),
1297
+ ...docsTaskRefs.map((pathValue) => `Review docs task reference \`${pathValue}\` before choosing a follow-up workflow.`),
1298
+ ...releaseReportRefs.map((pathValue) => `Review release report reference \`${pathValue}\` for maintenance-linked follow-up work.`),
1299
+ ...(affectedPackagesOrDocs.length > 0 ? [`Review the affected maintenance surfaces: ${affectedPackagesOrDocs.join(", ")}.`] : []),
1300
+ ...(followUpWorkflowRefs.length > 0
1301
+ ? [`Route the next bounded follow-up through ${routingRecommendation} (${followUpWorkflowRefs.join(", ")} considered).`]
1302
+ : []),
1303
+ ...(normalizedConstraints.length > 0 ? [`Keep maintenance follow-up bounded by: ${normalizedConstraints.join("; ")}.`] : [])
1304
+ ];
1305
+ const priorityAssessment = releaseReportRefs.length > 0 || dependencyAlertRefs.length > 1
1306
+ ? "Elevated maintenance triage: release-linked or multi-alert follow-up should be prioritized before broader maintenance work."
1307
+ : "Routine maintenance triage: review bounded references and route follow-up deliberately.";
1308
+ const risks = [
1309
+ ...(releaseReportRefs.length > 0 ? ["Release-linked maintenance follow-up can drift if release-readiness is deferred."] : []),
1310
+ ...(dependencyAlertRefs.length > 0 ? ["Dependency alert follow-up can widen change scope once implementation work begins."] : []),
1311
+ ...(docsTaskRefs.length > 0 ? ["Documentation debt can diverge from implemented behavior if maintenance triage is deferred."] : []),
1312
+ ...(referencedArtifactKinds.includes("security-report")
1313
+ ? ["Security-linked maintenance follow-up should remain prioritized until the linked evidence is resolved."]
1314
+ : [])
1315
+ ];
1316
+ const stalenessSignals = [
1317
+ ...(dependencyAlertRefs.length > 0 ? ["Dependency alert follow-up remains pending review."] : []),
1318
+ ...(docsTaskRefs.length > 0 ? ["Documentation maintenance follow-up remains pending review."] : []),
1319
+ ...(releaseReportRefs.length > 0 ? ["Release-linked maintenance follow-up remains pending review."] : [])
1320
+ ];
1321
+ const summary = `Maintenance report prepared for ${maintenanceRequest.maintenanceGoal}.`;
1322
+ const maintenanceReport = maintenanceArtifactSchema.parse({
1323
+ ...buildLifecycleArtifactEnvelopeBase(state, "Maintenance Triage", summary, [requestFile ?? ".agentops/requests/maintenance.yaml", ...evidenceSources], maintenanceIssueRefs, maintenanceGithubRefs),
1324
+ artifactKind: "maintenance-report",
1325
+ lifecycleDomain: "maintain",
1326
+ payload: {
1327
+ maintenanceScope: maintenanceRequest.maintenanceGoal,
1328
+ evidenceSources,
1329
+ affectedPackagesOrDocs,
1330
+ currentFindings: currentFindings.length > 0
1331
+ ? currentFindings
1332
+ : ["Maintenance triage remained bounded to validated references; no additional findings were synthesized."],
1333
+ recommendedActions: recommendedActions.length > 0
1334
+ ? recommendedActions
1335
+ : ["Add at least one bounded maintenance reference before broadening the workflow surface."],
1336
+ routingRecommendation,
1337
+ followUpWorkflowRefs,
1338
+ risks,
1339
+ priorityAssessment,
1340
+ dependencyUpdates: dependencyAlertRefs,
1341
+ docsUpdates: docsTaskRefs,
1342
+ stalenessSignals,
1343
+ followUpIssues: maintenanceIssueRefs
1344
+ }
1345
+ });
1346
+ return agentOutputSchema.parse({
1347
+ summary,
1348
+ findings: [],
1349
+ proposedActions: [],
1350
+ lifecycleArtifacts: [maintenanceReport],
1351
+ requestedTools: [],
1352
+ blockedActionFlags: [],
1353
+ confidence: 0.73,
1354
+ metadata: {
1355
+ deterministicInputs: {
1356
+ evidenceSources,
1357
+ dependencyAlertRefs,
1358
+ docsTaskRefs,
1359
+ releaseReportRefs,
1360
+ affectedPackagesOrDocs,
1361
+ maintenanceSignals,
1362
+ referencedArtifactKinds,
1363
+ issueRefs: maintenanceIssueRefs,
1364
+ constraints: normalizedConstraints
1365
+ },
1366
+ synthesizedAssessment: {
1367
+ priorityAssessment: maintenanceReport.payload.priorityAssessment,
1368
+ recommendedActions: maintenanceReport.payload.recommendedActions,
1369
+ routingRecommendation: maintenanceReport.payload.routingRecommendation,
1370
+ followUpWorkflowRefs: maintenanceReport.payload.followUpWorkflowRefs,
1371
+ risks: maintenanceReport.payload.risks,
1372
+ followUpIssues: maintenanceReport.payload.followUpIssues
1373
+ }
1374
+ }
1375
+ });
1376
+ }
1377
+ };
1378
+ const incidentAnalystAgent = {
1379
+ manifest: agentManifestSchema.parse({
1380
+ version: 1,
1381
+ name: "incident-analyst",
1382
+ displayName: "Incident Analyst",
1383
+ category: "operate",
1384
+ runtime: {
1385
+ minVersion: "0.1.0",
1386
+ kind: "reasoning"
1387
+ },
1388
+ permissions: {
1389
+ model: true,
1390
+ network: false,
1391
+ tools: [],
1392
+ readPaths: ["**/*"],
1393
+ writePaths: []
1394
+ },
1395
+ inputs: ["workflowInputs", "repo", "changes", "agentResults"],
1396
+ outputs: ["lifecycleArtifacts"],
1397
+ contextPolicy: {
1398
+ sections: ["workflowInputs", "repo", "changes", "agentResults"],
1399
+ minimalContext: true
1400
+ },
1401
+ catalog: {
1402
+ domain: "operate",
1403
+ supportLevel: "internal",
1404
+ maturity: "mvp",
1405
+ trustScope: "official-core-only"
1406
+ },
1407
+ trust: {
1408
+ tier: "core",
1409
+ source: "official",
1410
+ reviewed: true
1411
+ }
1412
+ }),
1413
+ outputSchema: agentOutputSchema,
1414
+ async execute({ state, stateSlice }) {
1415
+ const incidentRequest = getWorkflowInput(stateSlice, "incidentRequest");
1416
+ const requestFile = getWorkflowInput(stateSlice, "requestFile");
1417
+ const incidentIssueRefs = getWorkflowInput(stateSlice, "incidentIssueRefs") ?? [];
1418
+ const incidentGithubRefs = getWorkflowInput(stateSlice, "incidentGithubRefs") ?? [];
1419
+ if (!incidentRequest) {
1420
+ throw new Error("incident-handoff requires validated incident inputs before incident analysis.");
1421
+ }
1422
+ const intakeMetadata = isRecord(stateSlice.agentResults?.intake?.metadata) ? stateSlice.agentResults.intake.metadata : {};
1423
+ const evidenceMetadata = incidentEvidenceNormalizationSchema.safeParse(stateSlice.agentResults?.evidence?.metadata);
1424
+ const normalizedEvidence = evidenceMetadata.success ? evidenceMetadata.data : undefined;
1425
+ const evidenceSources = normalizedEvidence?.normalizedEvidenceSources && normalizedEvidence.normalizedEvidenceSources.length > 0
1426
+ ? normalizedEvidence.normalizedEvidenceSources
1427
+ : asStringArray(intakeMetadata.evidenceSources).length > 0
1428
+ ? [
1429
+ ...new Set([
1430
+ ...asStringArray(intakeMetadata.evidenceSources),
1431
+ ...asStringArray(intakeMetadata.releaseReportRefs)
1432
+ ])
1433
+ ]
1434
+ : [...new Set([...incidentRequest.evidenceSources, ...incidentRequest.releaseReportRefs])];
1435
+ const severityHint = normalizedEvidence?.severityHint ??
1436
+ (typeof intakeMetadata.severityHint === "string" ? intakeMetadata.severityHint : incidentRequest.severityHint);
1437
+ const constraints = asStringArray(intakeMetadata.constraints);
1438
+ const followUpWorkflowRefs = normalizedEvidence?.followUpWorkflowRefs && normalizedEvidence.followUpWorkflowRefs.length > 0
1439
+ ? normalizedEvidence.followUpWorkflowRefs
1440
+ : [
1441
+ "maintenance-triage",
1442
+ ...(incidentRequest.releaseReportRefs.length > 0 ? ["release-readiness"] : []),
1443
+ ...(severityHint === "high" || severityHint === "critical" ? ["security-review"] : [])
1444
+ ];
1445
+ const likelyImpactedAreas = normalizedEvidence?.likelyImpactedAreas && normalizedEvidence.likelyImpactedAreas.length > 0
1446
+ ? normalizedEvidence.likelyImpactedAreas
1447
+ : [
1448
+ ...(incidentRequest.releaseReportRefs.length > 0 ? ["release-readiness"] : []),
1449
+ ...(incidentRequest.evidenceSources.length > 0 ? ["staged-operational-evidence"] : []),
1450
+ ...(severityHint === "high" || severityHint === "critical" ? ["security-follow-up"] : [])
1451
+ ];
1452
+ const openQuestions = [
1453
+ ...(incidentRequest.issueRefs.length === 0 ? ["Should this incident be linked to a tracked issue before escalation?"] : []),
1454
+ ...(incidentRequest.releaseReportRefs.length === 0
1455
+ ? ["Is there a release-report bundle that should be attached for additional provenance?"]
1456
+ : [])
1457
+ ];
1458
+ const summary = `Incident brief prepared for ${incidentRequest.incidentSummary}.`;
1459
+ const incidentBrief = incidentArtifactSchema.parse({
1460
+ ...buildLifecycleArtifactEnvelopeBase(state, "Incident Handoff", summary, [requestFile ?? ".agentops/requests/incident.yaml", ...evidenceSources], incidentIssueRefs, incidentGithubRefs),
1461
+ artifactKind: "incident-brief",
1462
+ lifecycleDomain: "operate",
1463
+ redaction: {
1464
+ applied: true,
1465
+ strategyVersion: "1.0.0",
1466
+ categories: normalizedEvidence?.redactionCategories ?? [
1467
+ "github-token",
1468
+ "api-key",
1469
+ "aws-key",
1470
+ "bearer-token",
1471
+ "password",
1472
+ "private-key",
1473
+ "operational-sensitive"
1474
+ ]
1475
+ },
1476
+ payload: {
1477
+ incidentSummary: incidentRequest.incidentSummary,
1478
+ evidenceSources,
1479
+ timelineSummary: normalizedEvidence?.timelineSummary ?? [
1480
+ `Severity hint: ${severityHint}.`,
1481
+ `Validated ${incidentRequest.evidenceSources.length} staged evidence source(s) and ${incidentRequest.releaseReportRefs.length} release-report reference(s) before reasoning.`
1482
+ ],
1483
+ likelyImpactedAreas: likelyImpactedAreas.length > 0
1484
+ ? likelyImpactedAreas
1485
+ : ["manual incident triage is still required to identify impacted repository areas."],
1486
+ followUpWorkflowRefs: [...new Set(followUpWorkflowRefs)],
1487
+ openQuestions
1488
+ }
1489
+ });
1490
+ return agentOutputSchema.parse({
1491
+ summary,
1492
+ findings: [],
1493
+ proposedActions: [],
1494
+ lifecycleArtifacts: [incidentBrief],
1495
+ requestedTools: [],
1496
+ blockedActionFlags: [],
1497
+ confidence: severityHint === "critical" ? 0.7 : 0.74,
1498
+ metadata: {
1499
+ deterministicInputs: {
1500
+ severityHint,
1501
+ evidenceSources,
1502
+ issueRefs: incidentIssueRefs,
1503
+ constraints,
1504
+ normalizedEvidence: normalizedEvidence ?? null
1505
+ },
1506
+ synthesizedAssessment: {
1507
+ likelyImpactedAreas: incidentBrief.payload.likelyImpactedAreas,
1508
+ followUpWorkflowRefs: incidentBrief.payload.followUpWorkflowRefs,
1509
+ openQuestions: incidentBrief.payload.openQuestions
1510
+ }
1511
+ }
1512
+ });
1513
+ }
1514
+ };
1515
+ const releaseIntakeAgent = {
1516
+ manifest: agentManifestSchema.parse({
1517
+ version: 1,
1518
+ name: "release-intake",
1519
+ displayName: "Release Intake",
1520
+ category: "release",
1521
+ runtime: {
1522
+ minVersion: "0.1.0",
1523
+ kind: "deterministic"
1524
+ },
1525
+ permissions: {
1526
+ model: false,
1527
+ network: false,
1528
+ tools: [],
1529
+ readPaths: [".agentops/requests/**", ".agentops/runs/**"],
1530
+ writePaths: []
1531
+ },
1532
+ inputs: ["workflowInputs", "repo"],
1533
+ outputs: ["summary", "metadata"],
1534
+ contextPolicy: {
1535
+ sections: ["workflowInputs", "repo", "context"],
1536
+ minimalContext: true
1537
+ },
1538
+ catalog: {
1539
+ domain: "release",
1540
+ supportLevel: "internal",
1541
+ maturity: "mvp",
1542
+ trustScope: "official-core-only"
1543
+ },
1544
+ trust: {
1545
+ tier: "core",
1546
+ source: "official",
1547
+ reviewed: true
1548
+ }
1549
+ }),
1550
+ outputSchema: agentOutputSchema,
1551
+ async execute({ stateSlice }) {
1552
+ const releaseRequest = getWorkflowInput(stateSlice, "releaseRequest");
1553
+ const releaseIssueRefs = getWorkflowInput(stateSlice, "releaseIssueRefs") ?? [];
1554
+ const releaseGithubRefs = getWorkflowInput(stateSlice, "releaseGithubRefs") ?? [];
1555
+ const requestFile = getWorkflowInput(stateSlice, "requestFile");
1556
+ if (!releaseRequest) {
1557
+ throw new Error("release-readiness requires a validated release request before runtime execution.");
1558
+ }
1559
+ return agentOutputSchema.parse({
1560
+ summary: `Loaded release request from ${requestFile ?? ".agentops/requests/release.yaml"} for ${releaseRequest.releaseScope}.`,
1561
+ findings: [],
1562
+ proposedActions: [],
1563
+ lifecycleArtifacts: [],
1564
+ requestedTools: [],
1565
+ blockedActionFlags: [],
1566
+ metadata: {
1567
+ ...releaseRequestSchema.parse(releaseRequest),
1568
+ releaseIssueRefs,
1569
+ releaseGithubRefs,
1570
+ evidenceSourceCount: releaseRequest.qaReportRefs.length + releaseRequest.securityReportRefs.length + releaseRequest.evidenceSources.length
1571
+ }
1572
+ });
1573
+ }
1574
+ };
1575
+ const releaseEvidenceNormalizationAgent = {
1576
+ manifest: agentManifestSchema.parse({
1577
+ version: 1,
1578
+ name: "release-evidence-normalizer",
1579
+ displayName: "Release Evidence Normalizer",
1580
+ category: "release",
1581
+ runtime: {
1582
+ minVersion: "0.1.0",
1583
+ kind: "deterministic"
1584
+ },
1585
+ permissions: {
1586
+ model: false,
1587
+ network: false,
1588
+ tools: [],
1589
+ readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/*.json", "**/*.md", "**/package.json"],
1590
+ writePaths: []
1591
+ },
1592
+ inputs: ["workflowInputs", "repo", "agentResults"],
1593
+ outputs: ["summary", "metadata"],
1594
+ contextPolicy: {
1595
+ sections: ["workflowInputs", "repo", "agentResults"],
1596
+ minimalContext: true
1597
+ },
1598
+ catalog: {
1599
+ domain: "release",
1600
+ supportLevel: "internal",
1601
+ maturity: "mvp",
1602
+ trustScope: "official-core-only"
1603
+ },
1604
+ trust: {
1605
+ tier: "core",
1606
+ source: "official",
1607
+ reviewed: true
1608
+ }
1609
+ }),
1610
+ outputSchema: agentOutputSchema,
1611
+ async execute({ stateSlice }) {
1612
+ const releaseRequest = getWorkflowInput(stateSlice, "releaseRequest");
1613
+ if (!releaseRequest) {
1614
+ throw new Error("release-readiness requires validated release request inputs before evidence normalization.");
1615
+ }
1616
+ const repoRoot = stateSlice.repo?.root;
1617
+ const intakeMetadata = isRecord(stateSlice.agentResults?.intake?.metadata) ? stateSlice.agentResults.intake.metadata : {};
1618
+ const qaReportRefs = asStringArray(intakeMetadata.qaReportRefs).length > 0
1619
+ ? asStringArray(intakeMetadata.qaReportRefs)
1620
+ : releaseRequest.qaReportRefs;
1621
+ const securityReportRefs = asStringArray(intakeMetadata.securityReportRefs).length > 0
1622
+ ? asStringArray(intakeMetadata.securityReportRefs)
1623
+ : releaseRequest.securityReportRefs;
1624
+ const evidenceSources = asStringArray(intakeMetadata.evidenceSources).length > 0
1625
+ ? asStringArray(intakeMetadata.evidenceSources)
1626
+ : releaseRequest.evidenceSources;
1627
+ const normalizedEvidenceSources = [...new Set([...qaReportRefs, ...securityReportRefs, ...evidenceSources])];
1628
+ const missingEvidenceSources = normalizedEvidenceSources.filter((pathValue) => repoRoot && !existsSync(join(repoRoot, pathValue)));
1629
+ if (missingEvidenceSources.length > 0) {
1630
+ throw new Error(`Release evidence source not found: ${missingEvidenceSources[0]}`);
1631
+ }
1632
+ const versionResolutions = releaseRequest.versionTargets.map((target) => {
1633
+ const resolved = resolveWorkspacePackage(repoRoot, target.name);
1634
+ return {
1635
+ name: target.name,
1636
+ targetVersion: target.version,
1637
+ currentVersion: resolved.currentVersion,
1638
+ status: !resolved.currentVersion
1639
+ ? "package-missing"
1640
+ : resolved.currentVersion === target.version
1641
+ ? "matches-target"
1642
+ : "pending-version-bump",
1643
+ manifestPath: resolved.manifestPath
1644
+ };
1645
+ });
1646
+ const missingPackages = versionResolutions.filter((entry) => entry.status === "package-missing").map((entry) => entry.name);
1647
+ const versionCheckStatus = missingPackages.length === 0 ? "passed" : "failed";
1648
+ const baseReadinessStatus = qaReportRefs.length > 0 && securityReportRefs.length > 0
1649
+ ? "ready"
1650
+ : qaReportRefs.length > 0 || securityReportRefs.length > 0
1651
+ ? "partial"
1652
+ : "blocked";
1653
+ const readinessStatus = missingPackages.length > 0 ? "blocked" : baseReadinessStatus;
1654
+ const localReadinessChecks = [
1655
+ {
1656
+ name: "qa-report-refs",
1657
+ status: qaReportRefs.length > 0 ? "passed" : "skipped",
1658
+ detail: qaReportRefs.length > 0
1659
+ ? `Using ${qaReportRefs.length} validated QA report reference(s).`
1660
+ : "No QA report references were supplied."
1661
+ },
1662
+ {
1663
+ name: "security-report-refs",
1664
+ status: securityReportRefs.length > 0 ? "passed" : "skipped",
1665
+ detail: securityReportRefs.length > 0
1666
+ ? `Using ${securityReportRefs.length} validated security report reference(s).`
1667
+ : "No security report references were supplied."
1668
+ },
1669
+ {
1670
+ name: "local-release-evidence",
1671
+ status: evidenceSources.length > 0 ? "passed" : "skipped",
1672
+ detail: evidenceSources.length > 0
1673
+ ? `Using ${evidenceSources.length} bounded local release evidence source(s).`
1674
+ : "No additional local release evidence sources were supplied."
1675
+ },
1676
+ {
1677
+ name: "workspace-version-targets",
1678
+ status: versionCheckStatus,
1679
+ detail: missingPackages.length === 0
1680
+ ? `Resolved ${versionResolutions.length} workspace version target(s).`
1681
+ : `Missing workspace package metadata for: ${missingPackages.join(", ")}.`
1682
+ }
1683
+ ];
1684
+ const approvalRecommendations = [
1685
+ {
1686
+ action: "publish-packages",
1687
+ classification: readinessStatus === "ready" ? "approval_required" : "deny",
1688
+ reason: readinessStatus === "ready"
1689
+ ? "Package publication remains outside the default read-only workflow path and needs explicit release approval."
1690
+ : "Keep package publication blocked until bounded release evidence is complete and normalized."
1691
+ },
1692
+ {
1693
+ action: "create-release-tag",
1694
+ classification: readinessStatus === "ready" ? "approval_required" : "deny",
1695
+ reason: readinessStatus === "ready"
1696
+ ? "Tag creation is a release-significant side effect and remains approval-gated."
1697
+ : "Do not create release tags while readiness remains partial or blocked."
1698
+ },
1699
+ {
1700
+ action: "promote-release",
1701
+ classification: readinessStatus === "ready" ? "approval_required" : "deny",
1702
+ reason: readinessStatus === "ready"
1703
+ ? "Promotion remains a release-significant side effect and requires explicit maintainer approval."
1704
+ : "Keep release promotion blocked until bounded QA and security evidence is complete."
1705
+ }
1706
+ ];
1707
+ const provenanceRefs = [
1708
+ ...normalizedEvidenceSources,
1709
+ ...versionResolutions
1710
+ .map((entry) => entry.manifestPath)
1711
+ .filter((value) => Boolean(value))
1712
+ ];
1713
+ const normalization = releaseEvidenceNormalizationSchema.parse({
1714
+ qaReportRefs,
1715
+ securityReportRefs,
1716
+ normalizedEvidenceSources,
1717
+ missingEvidenceSources: [],
1718
+ versionResolutions: versionResolutions.map((entry) => ({
1719
+ name: entry.name,
1720
+ targetVersion: entry.targetVersion,
1721
+ currentVersion: entry.currentVersion,
1722
+ status: entry.status
1723
+ })),
1724
+ localReadinessChecks,
1725
+ readinessStatus,
1726
+ approvalRecommendations,
1727
+ provenanceRefs: [...new Set(provenanceRefs)]
1728
+ });
1729
+ return agentOutputSchema.parse({
1730
+ summary: `Normalized release evidence across ${normalization.normalizedEvidenceSources.length} source(s) and ${normalization.versionResolutions.length} version target(s).`,
1731
+ findings: [],
1732
+ proposedActions: [],
1733
+ lifecycleArtifacts: [],
1734
+ requestedTools: [],
1735
+ blockedActionFlags: [],
1736
+ metadata: normalization
1737
+ });
1738
+ }
1739
+ };
1740
+ const releaseAnalystAgent = {
1741
+ manifest: agentManifestSchema.parse({
1742
+ version: 1,
1743
+ name: "release-analyst",
1744
+ displayName: "Release Analyst",
1745
+ category: "release",
1746
+ runtime: {
1747
+ minVersion: "0.1.0",
1748
+ kind: "reasoning"
1749
+ },
1750
+ permissions: {
1751
+ model: true,
1752
+ network: false,
1753
+ tools: [],
1754
+ readPaths: ["**/*"],
1755
+ writePaths: []
1756
+ },
1757
+ inputs: ["workflowInputs", "repo", "agentResults"],
1758
+ outputs: ["lifecycleArtifacts"],
1759
+ contextPolicy: {
1760
+ sections: ["workflowInputs", "repo", "agentResults"],
1761
+ minimalContext: true
1762
+ },
1763
+ catalog: {
1764
+ domain: "release",
1765
+ supportLevel: "internal",
1766
+ maturity: "mvp",
1767
+ trustScope: "official-core-only"
1768
+ },
1769
+ trust: {
1770
+ tier: "core",
1771
+ source: "official",
1772
+ reviewed: true
1773
+ }
1774
+ }),
1775
+ outputSchema: agentOutputSchema,
1776
+ async execute({ state, stateSlice }) {
1777
+ const releaseRequest = getWorkflowInput(stateSlice, "releaseRequest");
1778
+ const releaseIssueRefs = getWorkflowInput(stateSlice, "releaseIssueRefs") ?? [];
1779
+ const releaseGithubRefs = getWorkflowInput(stateSlice, "releaseGithubRefs") ?? [];
1780
+ const requestFile = getWorkflowInput(stateSlice, "requestFile");
1781
+ if (!releaseRequest) {
1782
+ throw new Error("release-readiness requires validated release inputs before release analysis.");
1783
+ }
1784
+ const intakeMetadata = isRecord(stateSlice.agentResults?.intake?.metadata) ? stateSlice.agentResults.intake.metadata : {};
1785
+ const evidenceMetadata = releaseEvidenceNormalizationSchema.safeParse(stateSlice.agentResults?.evidence?.metadata);
1786
+ const normalizedEvidence = evidenceMetadata.success ? evidenceMetadata.data : undefined;
1787
+ const qaReportRefs = normalizedEvidence?.qaReportRefs && normalizedEvidence.qaReportRefs.length > 0
1788
+ ? normalizedEvidence.qaReportRefs
1789
+ : asStringArray(intakeMetadata.qaReportRefs).length > 0
1790
+ ? asStringArray(intakeMetadata.qaReportRefs)
1791
+ : releaseRequest.qaReportRefs;
1792
+ const securityReportRefs = normalizedEvidence?.securityReportRefs && normalizedEvidence.securityReportRefs.length > 0
1793
+ ? normalizedEvidence.securityReportRefs
1794
+ : asStringArray(intakeMetadata.securityReportRefs).length > 0
1795
+ ? asStringArray(intakeMetadata.securityReportRefs)
1796
+ : releaseRequest.securityReportRefs;
1797
+ const evidenceSources = normalizedEvidence?.normalizedEvidenceSources && normalizedEvidence.normalizedEvidenceSources.length > 0
1798
+ ? normalizedEvidence.normalizedEvidenceSources
1799
+ : asStringArray(intakeMetadata.evidenceSources).length > 0
1800
+ ? asStringArray(intakeMetadata.evidenceSources)
1801
+ : releaseRequest.evidenceSources;
1802
+ const constraints = asStringArray(intakeMetadata.constraints);
1803
+ const allEvidenceRefs = [...new Set([...qaReportRefs, ...securityReportRefs, ...evidenceSources])];
1804
+ const versionResolutions = normalizedEvidence?.versionResolutions ?? [];
1805
+ const verificationChecks = normalizedEvidence?.localReadinessChecks ?? [
1806
+ {
1807
+ name: "qa-report-refs",
1808
+ status: qaReportRefs.length > 0 ? "passed" : "skipped",
1809
+ detail: qaReportRefs.length > 0
1810
+ ? `Using ${qaReportRefs.length} validated QA report reference(s).`
1811
+ : "No QA report references were supplied."
1812
+ },
1813
+ {
1814
+ name: "security-report-refs",
1815
+ status: securityReportRefs.length > 0 ? "passed" : "skipped",
1816
+ detail: securityReportRefs.length > 0
1817
+ ? `Using ${securityReportRefs.length} validated security report reference(s).`
1818
+ : "No security report references were supplied."
1819
+ },
1820
+ {
1821
+ name: "local-release-evidence",
1822
+ status: evidenceSources.length > 0 ? "passed" : "skipped",
1823
+ detail: evidenceSources.length > 0
1824
+ ? `Using ${evidenceSources.length} bounded local release evidence source(s).`
1825
+ : "No additional local release evidence sources were supplied."
1826
+ }
1827
+ ];
1828
+ const readinessStatus = normalizedEvidence?.readinessStatus ??
1829
+ (qaReportRefs.length > 0 && securityReportRefs.length > 0
1830
+ ? "ready"
1831
+ : qaReportRefs.length > 0 || securityReportRefs.length > 0
1832
+ ? "partial"
1833
+ : "blocked");
1834
+ const approvalRecommendations = normalizedEvidence?.approvalRecommendations ?? [
1835
+ {
1836
+ action: "publish-packages",
1837
+ classification: readinessStatus === "ready" ? "approval_required" : "deny",
1838
+ reason: readinessStatus === "ready"
1839
+ ? "Package publication remains outside the default read-only workflow path and needs explicit release approval."
1840
+ : "Keep package publication blocked until bounded release evidence is complete and normalized."
1841
+ }
1842
+ ];
1843
+ const summary = `Release report prepared for ${releaseRequest.releaseScope}.`;
1844
+ const publishingPlan = [
1845
+ ...(versionResolutions.length > 0
1846
+ ? [`Resolved ${versionResolutions.length} workspace version target(s) before any publish or promotion step.`]
1847
+ : []),
1848
+ "Review the bounded QA and security evidence before invoking any publish or promotion step.",
1849
+ "Run `agentforge release check --json` and `agentforge release verify --json` before any release cut.",
1850
+ ...approvalRecommendations.map((recommendation) => `${recommendation.action}: ${recommendation.classification.replaceAll("_", " ")} (${recommendation.reason})`),
1851
+ "Keep trusted publishing and tag or publish actions outside this default read-only workflow path."
1852
+ ];
1853
+ const rollbackNotes = [
1854
+ "Use the release report to decide whether to pause or defer promotion before any publish step.",
1855
+ "If readiness remains partial or blocked, keep the current version set unchanged and resolve evidence gaps first."
1856
+ ];
1857
+ const externalDependencies = [
1858
+ ...(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."] : [])
1860
+ ];
1861
+ const releaseReport = releaseArtifactSchema.parse({
1862
+ ...buildLifecycleArtifactEnvelopeBase(state, "Release Readiness", summary, [requestFile ?? ".agentops/requests/release.yaml", ...allEvidenceRefs], releaseIssueRefs, releaseGithubRefs),
1863
+ artifactKind: "release-report",
1864
+ lifecycleDomain: "release",
1865
+ payload: {
1866
+ releaseScope: releaseRequest.releaseScope,
1867
+ versionTargets: releaseRequest.versionTargets,
1868
+ readinessStatus,
1869
+ verificationChecks: verificationChecks.map((check) => ({ ...check })),
1870
+ versionResolutions,
1871
+ approvalRecommendations: approvalRecommendations.map((recommendation) => releaseApprovalRecommendationSchema.parse(recommendation)),
1872
+ publishingPlan,
1873
+ trustStatus: "trusted-publishing-reviewed-separately",
1874
+ publishedPackages: [],
1875
+ tagRefs: [],
1876
+ provenanceRefs: allEvidenceRefs,
1877
+ rollbackNotes,
1878
+ externalDependencies
1879
+ }
1880
+ });
1881
+ return agentOutputSchema.parse({
1882
+ summary,
1883
+ findings: [],
1884
+ proposedActions: [],
1885
+ lifecycleArtifacts: [releaseReport],
1886
+ requestedTools: [],
1887
+ blockedActionFlags: [],
1888
+ confidence: 0.77,
1889
+ metadata: {
1890
+ deterministicInputs: {
1891
+ versionTargets: releaseRequest.versionTargets,
1892
+ qaReportRefs,
1893
+ securityReportRefs,
1894
+ evidenceSources,
1895
+ constraints,
1896
+ normalizedEvidence: normalizedEvidence ?? null
1897
+ },
1898
+ synthesizedAssessment: {
1899
+ readinessStatus,
1900
+ approvalRecommendations,
1901
+ publishingPlan,
1902
+ rollbackNotes
1903
+ }
1904
+ }
1905
+ });
1906
+ }
1907
+ };
1908
+ const securityEvidenceNormalizationAgent = {
1909
+ manifest: agentManifestSchema.parse({
1910
+ version: 1,
1911
+ name: "security-evidence-normalizer",
1912
+ displayName: "Security Evidence Normalizer",
1913
+ category: "security",
1914
+ runtime: {
1915
+ minVersion: "0.1.0",
1916
+ kind: "deterministic"
1917
+ },
1918
+ permissions: {
1919
+ model: false,
1920
+ network: false,
1921
+ tools: [],
1922
+ readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/*.json", "**/*.md", "**/package.json"],
1923
+ writePaths: []
1924
+ },
1925
+ inputs: ["workflowInputs", "repo", "agentResults"],
1926
+ outputs: ["summary", "metadata"],
1927
+ contextPolicy: {
1928
+ sections: ["workflowInputs", "repo", "agentResults"],
1929
+ minimalContext: true
1930
+ },
1931
+ catalog: {
1932
+ domain: "security",
1933
+ supportLevel: "internal",
1934
+ maturity: "mvp",
1935
+ trustScope: "official-core-only"
1936
+ },
1937
+ trust: {
1938
+ tier: "core",
1939
+ source: "official",
1940
+ reviewed: true
1941
+ }
1942
+ }),
1943
+ outputSchema: agentOutputSchema,
1944
+ async execute({ stateSlice }) {
1945
+ const securityRequest = getWorkflowInput(stateSlice, "securityRequest");
1946
+ if (!securityRequest) {
1947
+ throw new Error("security-review requires validated security request inputs before evidence normalization.");
1948
+ }
1949
+ const repoRoot = stateSlice.repo?.root;
1950
+ const intakeMetadata = isRecord(stateSlice.agentResults?.intake?.metadata) ? stateSlice.agentResults.intake.metadata : {};
1951
+ const targetType = typeof intakeMetadata.targetType === "string" && intakeMetadata.targetType === "artifact-bundle"
1952
+ ? "artifact-bundle"
1953
+ : "local-reference";
1954
+ const targetPath = repoRoot ? join(repoRoot, securityRequest.targetRef) : securityRequest.targetRef;
1955
+ if (repoRoot && !existsSync(targetPath)) {
1956
+ throw new Error(`Security target reference not found: ${securityRequest.targetRef}`);
1957
+ }
1958
+ const referencedArtifactKinds = targetType === "artifact-bundle" ? loadBundleArtifactKinds(targetPath) : [];
1959
+ const normalizedEvidenceSources = [...new Set([securityRequest.targetRef, ...securityRequest.evidenceSources])];
1960
+ const missingEvidenceSources = normalizedEvidenceSources.filter((pathValue) => repoRoot && !existsSync(join(repoRoot, pathValue)));
1961
+ if (missingEvidenceSources.length > 0) {
1962
+ throw new Error(`Security evidence source not found: ${missingEvidenceSources[0]}`);
1963
+ }
1964
+ const normalizedFocusAreas = securityRequest.focusAreas.length > 0 ? [...new Set(securityRequest.focusAreas)] : ["general-review"];
1965
+ const affectedPackages = targetType === "artifact-bundle"
1966
+ ? [...new Set(loadBundleArtifactPayloadPaths(targetPath).map(derivePackageScope).filter((value) => Boolean(value)))]
1967
+ : [];
1968
+ const securitySignals = [
1969
+ ...(referencedArtifactKinds.length > 0 ? [`Referenced artifact kinds: ${referencedArtifactKinds.join(", ")}`] : []),
1970
+ ...(affectedPackages.length > 0 ? [`Affected packages inferred from bounded artifact payloads: ${affectedPackages.join(", ")}`] : []),
1971
+ ...(normalizedFocusAreas.length > 0 ? [`Requested focus areas: ${normalizedFocusAreas.join(", ")}`] : []),
1972
+ "Security evidence collection remains local, read-only, and bounded to validated references."
1973
+ ];
1974
+ const provenanceRefs = [
1975
+ securityRequest.targetRef,
1976
+ ...securityRequest.evidenceSources,
1977
+ ...referencedArtifactKinds.map((artifactKind) => `${securityRequest.targetRef}#${artifactKind}`)
1978
+ ];
1979
+ const normalization = securityEvidenceNormalizationSchema.parse({
1980
+ targetRef: securityRequest.targetRef,
1981
+ targetType,
1982
+ referencedArtifactKinds,
1983
+ normalizedEvidenceSources,
1984
+ missingEvidenceSources: [],
1985
+ normalizedFocusAreas,
1986
+ securitySignals,
1987
+ provenanceRefs: [...new Set(provenanceRefs)],
1988
+ affectedPackages
1989
+ });
1990
+ return agentOutputSchema.parse({
1991
+ summary: `Normalized security evidence for ${securityRequest.targetRef}.`,
1992
+ findings: [],
1993
+ proposedActions: [],
1994
+ lifecycleArtifacts: [],
1995
+ requestedTools: [],
1996
+ blockedActionFlags: [],
1997
+ metadata: normalization
1998
+ });
1999
+ }
2000
+ };
2001
+ const securityAnalystAgent = {
2002
+ manifest: agentManifestSchema.parse({
2003
+ version: 1,
2004
+ name: "security-analyst",
2005
+ displayName: "Security Analyst",
2006
+ category: "security",
2007
+ runtime: {
2008
+ minVersion: "0.1.0",
2009
+ kind: "reasoning"
2010
+ },
2011
+ permissions: {
2012
+ model: true,
2013
+ network: false,
2014
+ tools: [],
2015
+ readPaths: ["**/*"],
2016
+ writePaths: []
2017
+ },
2018
+ inputs: ["workflowInputs", "repo", "changes", "agentResults"],
2019
+ outputs: ["lifecycleArtifacts"],
2020
+ contextPolicy: {
2021
+ sections: ["workflowInputs", "repo", "changes", "agentResults"],
2022
+ minimalContext: true
2023
+ },
2024
+ catalog: {
2025
+ domain: "security",
2026
+ supportLevel: "internal",
2027
+ maturity: "mvp",
2028
+ trustScope: "official-core-only"
2029
+ },
2030
+ trust: {
2031
+ tier: "core",
2032
+ source: "official",
2033
+ reviewed: true
2034
+ }
2035
+ }),
2036
+ outputSchema: agentOutputSchema,
2037
+ async execute({ state, stateSlice }) {
2038
+ const securityRequest = getWorkflowInput(stateSlice, "securityRequest");
2039
+ const securityIssueRefs = getWorkflowInput(stateSlice, "securityIssueRefs") ?? [];
2040
+ const securityGithubRefs = getWorkflowInput(stateSlice, "securityGithubRefs") ?? [];
2041
+ const requestFile = getWorkflowInput(stateSlice, "requestFile");
2042
+ if (!securityRequest) {
2043
+ throw new Error("security-review requires validated security inputs before security analysis.");
2044
+ }
2045
+ const intakeMetadata = isRecord(stateSlice.agentResults?.intake?.metadata) ? stateSlice.agentResults.intake.metadata : {};
2046
+ const evidenceMetadata = securityEvidenceNormalizationSchema.safeParse(stateSlice.agentResults?.evidence?.metadata);
2047
+ const normalizedEvidence = evidenceMetadata.success ? evidenceMetadata.data : undefined;
2048
+ const referencedArtifactKinds = normalizedEvidence?.referencedArtifactKinds ?? asStringArray(intakeMetadata.referencedArtifactKinds);
2049
+ const normalizedFocusAreas = normalizedEvidence?.normalizedFocusAreas ?? asStringArray(intakeMetadata.focusAreas);
2050
+ const normalizedConstraints = asStringArray(intakeMetadata.constraints);
2051
+ const evidenceSources = normalizedEvidence?.normalizedEvidenceSources && normalizedEvidence.normalizedEvidenceSources.length > 0
2052
+ ? normalizedEvidence.normalizedEvidenceSources
2053
+ : asStringArray(intakeMetadata.evidenceSources).length > 0
2054
+ ? asStringArray(intakeMetadata.evidenceSources)
2055
+ : [...new Set([securityRequest.targetRef, ...securityRequest.evidenceSources])];
2056
+ const focusAreas = normalizedFocusAreas.length > 0 ? normalizedFocusAreas : securityRequest.focusAreas;
2057
+ const inferredSeverity = securityRequest.releaseContext === "blocking" ? "high" : securityRequest.releaseContext === "candidate" ? "medium" : "low";
2058
+ const findings = focusAreas.length > 0
2059
+ ? focusAreas.map((focusArea, index) => ({
2060
+ id: `security-finding-${index + 1}`,
2061
+ title: `Inspect ${focusArea} evidence before promotion`,
2062
+ summary: `Security review flagged ${focusArea} for bounded follow-up on ${securityRequest.targetRef}.`,
2063
+ severity: inferredSeverity,
2064
+ rationale: "The MVP security workflow synthesizes a structured report from validated references before deterministic evidence normalization lands.",
2065
+ confidence: 0.76,
2066
+ location: securityRequest.targetRef,
2067
+ tags: ["security", focusArea]
2068
+ }))
2069
+ : [
2070
+ {
2071
+ id: "security-finding-1",
2072
+ title: "Inspect referenced security evidence before promotion",
2073
+ summary: `Security review requires bounded interpretation of the referenced evidence for ${securityRequest.targetRef}.`,
2074
+ severity: inferredSeverity,
2075
+ rationale: "The current security workflow is read-only and request-driven, so findings remain tied to validated local references rather than automatic scanning.",
2076
+ confidence: 0.72,
2077
+ location: securityRequest.targetRef,
2078
+ tags: ["security", "evidence"]
2079
+ }
2080
+ ];
2081
+ const mitigations = [
2082
+ ...focusAreas.map((focusArea) => `Review ${focusArea} evidence and document the release impact before promotion.`),
2083
+ ...(normalizedConstraints.length > 0 ? [`Keep security follow-up bounded by: ${normalizedConstraints.join("; ")}.`] : [])
2084
+ ];
2085
+ const followUpWork = [
2086
+ ...(normalizedEvidence?.securitySignals ?? []),
2087
+ ...(referencedArtifactKinds.length > 0
2088
+ ? [`Confirm the security posture for referenced artifacts: ${referencedArtifactKinds.join(", ")}.`]
2089
+ : []),
2090
+ "Use deterministic security evidence normalization outputs before broadening the workflow surface."
2091
+ ];
2092
+ const summary = `Security report prepared for ${securityRequest.targetRef}.`;
2093
+ const securityReport = securityArtifactSchema.parse({
2094
+ ...buildLifecycleArtifactEnvelopeBase(state, "Security Review", summary, [
2095
+ requestFile ?? ".agentops/requests/security.yaml",
2096
+ ...(normalizedEvidence?.provenanceRefs ?? [securityRequest.targetRef, ...securityRequest.evidenceSources])
2097
+ ], securityIssueRefs, securityGithubRefs),
2098
+ artifactKind: "security-report",
2099
+ lifecycleDomain: "security",
2100
+ redaction: {
2101
+ applied: true,
2102
+ strategyVersion: "1.0.0",
2103
+ categories: ["github-token", "api-key", "aws-key", "bearer-token", "password", "private-key", "security-sensitive"]
2104
+ },
2105
+ payload: {
2106
+ targetRef: securityRequest.targetRef,
2107
+ evidenceSources,
2108
+ findings,
2109
+ severitySummary: `highest severity: ${inferredSeverity}; ${findings.length} synthesized security finding(s).`,
2110
+ mitigations: mitigations.length > 0
2111
+ ? mitigations
2112
+ : ["Review the referenced security evidence before promoting this workflow output."],
2113
+ releaseImpact: securityRequest.releaseContext === "blocking"
2114
+ ? "release-blocking security findings require resolution before promotion."
2115
+ : securityRequest.releaseContext === "candidate"
2116
+ ? "candidate release requires explicit security review before promotion."
2117
+ : "no release context was supplied; security output remains advisory.",
2118
+ followUpWork
2119
+ }
2120
+ });
2121
+ return agentOutputSchema.parse({
2122
+ summary,
2123
+ findings: [],
2124
+ proposedActions: [],
2125
+ lifecycleArtifacts: [securityReport],
2126
+ requestedTools: [],
2127
+ blockedActionFlags: [],
2128
+ confidence: 0.76,
2129
+ metadata: {
2130
+ deterministicInputs: {
2131
+ targetRef: securityRequest.targetRef,
2132
+ evidenceSources,
2133
+ focusAreas,
2134
+ constraints: normalizedConstraints,
2135
+ referencedArtifactKinds,
2136
+ normalizedEvidence: normalizedEvidence ?? null
2137
+ },
2138
+ synthesizedAssessment: {
2139
+ severitySummary: securityReport.payload.severitySummary,
2140
+ mitigations: securityReport.payload.mitigations,
2141
+ followUpWork: securityReport.payload.followUpWork
2142
+ }
2143
+ }
2144
+ });
2145
+ }
2146
+ };
2147
+ const qaEvidenceNormalizationAgent = {
2148
+ manifest: agentManifestSchema.parse({
2149
+ version: 1,
2150
+ name: "qa-evidence-normalizer",
2151
+ displayName: "QA Evidence Normalizer",
2152
+ category: "qa",
2153
+ runtime: {
2154
+ minVersion: "0.1.0",
2155
+ kind: "deterministic"
2156
+ },
2157
+ permissions: {
2158
+ model: false,
2159
+ network: false,
2160
+ tools: [],
2161
+ readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/package.json", "**/*.json", "**/*.xml", "**/*.log", "**/*.md"],
2162
+ writePaths: []
2163
+ },
2164
+ inputs: ["workflowInputs", "repo", "agentResults"],
2165
+ outputs: ["summary", "metadata"],
2166
+ contextPolicy: {
2167
+ sections: ["workflowInputs", "repo", "agentResults"],
2168
+ minimalContext: true
2169
+ },
2170
+ catalog: {
2171
+ domain: "test",
2172
+ supportLevel: "internal",
2173
+ maturity: "mvp",
2174
+ trustScope: "official-core-only"
2175
+ },
2176
+ trust: {
2177
+ tier: "core",
2178
+ source: "official",
2179
+ reviewed: true
2180
+ }
2181
+ }),
2182
+ outputSchema: agentOutputSchema,
2183
+ async execute({ stateSlice }) {
2184
+ const qaRequest = getWorkflowInput(stateSlice, "qaRequest");
2185
+ if (!qaRequest) {
2186
+ throw new Error("qa-review requires validated QA request inputs before evidence normalization.");
2187
+ }
2188
+ const repoRoot = stateSlice.repo?.root;
2189
+ const packageManager = stateSlice.repo?.packageManager || "pnpm";
2190
+ const intakeMetadata = isRecord(stateSlice.agentResults?.intake?.metadata) ? stateSlice.agentResults.intake.metadata : {};
2191
+ const targetType = typeof intakeMetadata.targetType === "string" ? intakeMetadata.targetType : "local-reference";
2192
+ const targetPath = repoRoot ? join(repoRoot, qaRequest.targetRef) : qaRequest.targetRef;
2193
+ if (repoRoot && !existsSync(targetPath)) {
2194
+ throw new Error(`QA target reference not found: ${qaRequest.targetRef}`);
2195
+ }
2196
+ const referencedArtifactKinds = targetType === "artifact-bundle" ? loadBundleArtifactKinds(targetPath) : [];
2197
+ const normalizedEvidenceSources = [...new Set([qaRequest.targetRef, ...qaRequest.evidenceSources])];
2198
+ const missingEvidenceSources = normalizedEvidenceSources.filter((pathValue) => repoRoot && !existsSync(join(repoRoot, pathValue)));
2199
+ if (missingEvidenceSources.length > 0) {
2200
+ throw new Error(`QA evidence source not found: ${missingEvidenceSources[0]}`);
2201
+ }
2202
+ const bundleAffectedPaths = targetType === "artifact-bundle" && referencedArtifactKinds.includes("implementation-proposal") && existsSync(targetPath)
2203
+ ? asStringArray((() => {
2204
+ const parsed = JSON.parse(readFileSync(targetPath, "utf8"));
2205
+ if (!isRecord(parsed) || !Array.isArray(parsed.lifecycleArtifacts)) {
1058
2206
  return [];
1059
2207
  }
1060
2208
  const implementationArtifact = parsed.lifecycleArtifacts.find((artifact) => isRecord(artifact) &&
@@ -1069,6 +2217,7 @@ const qaEvidenceNormalizationAgent = {
1069
2217
  const allowlistedCommands = new Set(allowedValidationCommands.map((entry) => entry.command));
1070
2218
  const normalizedExecutedChecks = qaRequest.executedChecks.map(normalizeRequestedCommand);
1071
2219
  const unrecognizedExecutedChecks = normalizedExecutedChecks.filter((command) => !allowlistedCommands.has(command));
2220
+ const githubActions = normalizeGitHubActionsEvidence(repoRoot, normalizedEvidenceSources);
1072
2221
  const normalization = qaEvidenceNormalizationSchema.parse({
1073
2222
  targetRef: qaRequest.targetRef,
1074
2223
  targetType,
@@ -1078,10 +2227,11 @@ const qaEvidenceNormalizationAgent = {
1078
2227
  normalizedExecutedChecks,
1079
2228
  unrecognizedExecutedChecks,
1080
2229
  affectedPackages,
1081
- allowedValidationCommands
2230
+ allowedValidationCommands,
2231
+ githubActions
1082
2232
  });
1083
2233
  return agentOutputSchema.parse({
1084
- summary: `Normalized QA evidence across ${normalization.normalizedEvidenceSources.length} source(s) and ${normalization.allowedValidationCommands.length} allowlisted validation command(s).`,
2234
+ summary: `Normalized QA evidence across ${normalization.normalizedEvidenceSources.length} source(s), ${normalization.allowedValidationCommands.length} allowlisted validation command(s), and ${normalization.githubActions.evidence.length} GitHub Actions evidence export(s).`,
1085
2235
  findings: [],
1086
2236
  proposedActions: [],
1087
2237
  lifecycleArtifacts: [],
@@ -1129,20 +2279,31 @@ const qaAnalystAgent = {
1129
2279
  outputSchema: agentOutputSchema,
1130
2280
  async execute({ state, stateSlice }) {
1131
2281
  const qaRequest = getWorkflowInput(stateSlice, "qaRequest");
2282
+ const qaIssueRefs = getWorkflowInput(stateSlice, "qaIssueRefs") ?? [];
2283
+ const qaGithubRefs = getWorkflowInput(stateSlice, "qaGithubRefs") ?? [];
1132
2284
  const requestFile = getWorkflowInput(stateSlice, "requestFile");
1133
2285
  if (!qaRequest) {
1134
2286
  throw new Error("qa-review requires validated QA inputs before QA analysis.");
1135
2287
  }
1136
2288
  const intakeMetadata = isRecord(stateSlice.agentResults?.intake?.metadata) ? stateSlice.agentResults.intake.metadata : {};
1137
- const evidenceMetadata = isRecord(stateSlice.agentResults?.evidence?.metadata) ? stateSlice.agentResults.evidence.metadata : {};
1138
- const normalizedEvidenceSources = asStringArray(evidenceMetadata.normalizedEvidenceSources);
1139
- const normalizedExecutedChecks = asStringArray(evidenceMetadata.normalizedExecutedChecks);
2289
+ const evidenceMetadata = qaEvidenceNormalizationSchema.safeParse(stateSlice.agentResults?.evidence?.metadata);
2290
+ const normalizedEvidence = evidenceMetadata.success ? evidenceMetadata.data : undefined;
2291
+ const normalizedEvidenceSources = normalizedEvidence?.normalizedEvidenceSources ?? [];
2292
+ const normalizedExecutedChecks = normalizedEvidence?.normalizedExecutedChecks ?? [];
1140
2293
  const normalizedFocusAreas = asStringArray(intakeMetadata.focusAreas);
1141
2294
  const normalizedConstraints = asStringArray(intakeMetadata.constraints);
1142
- const missingEvidenceSources = asStringArray(evidenceMetadata.missingEvidenceSources);
1143
- const unrecognizedExecutedChecks = asStringArray(evidenceMetadata.unrecognizedExecutedChecks);
1144
- const targetType = typeof evidenceMetadata.targetType === "string"
1145
- ? evidenceMetadata.targetType
2295
+ const missingEvidenceSources = normalizedEvidence?.missingEvidenceSources ?? [];
2296
+ const unrecognizedExecutedChecks = normalizedEvidence?.unrecognizedExecutedChecks ?? [];
2297
+ const normalizedGithubActions = normalizedEvidence
2298
+ ? normalizedEvidence.githubActions
2299
+ : {
2300
+ evidence: [],
2301
+ workflowNames: [],
2302
+ failingChecks: [],
2303
+ provenanceRefs: []
2304
+ };
2305
+ const targetType = normalizedEvidence?.targetType
2306
+ ? normalizedEvidence.targetType
1146
2307
  : typeof intakeMetadata.targetType === "string"
1147
2308
  ? intakeMetadata.targetType
1148
2309
  : "local-reference";
@@ -1179,16 +2340,23 @@ const qaAnalystAgent = {
1179
2340
  ...missingEvidenceSources.map((pathValue) => `Referenced QA evidence is missing: ${pathValue}`),
1180
2341
  ...(focusAreas.includes("coverage") ? ["Coverage evidence still needs deterministic normalization before it can be promoted to an official QA signal."] : []),
1181
2342
  ...(executedChecks.length === 0 ? ["No executed validation checks were recorded in the request."] : []),
1182
- ...unrecognizedExecutedChecks.map((command) => `Executed check is outside the bounded allowlist and still needs manual interpretation: ${command}`)
2343
+ ...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}`)
1183
2345
  ];
1184
2346
  const recommendedNextChecks = [
1185
2347
  ...executedChecks.map((command) => `Review the recorded output for \`${command}\` before promotion.`),
1186
2348
  ...focusAreas.map((focusArea) => `Confirm whether ${focusArea} needs additional deterministic QA evidence.`),
2349
+ ...normalizedGithubActions.workflowNames.map((workflowName) => `Review the exported GitHub Actions evidence for workflow \`${workflowName}\` before promotion.`),
1187
2350
  ...(normalizedConstraints.length > 0 ? [`Keep QA follow-up bounded by: ${normalizedConstraints.join("; ")}.`] : [])
1188
2351
  ];
1189
2352
  const summary = `QA report prepared for ${qaRequest.targetRef}.`;
2353
+ const releaseImpactBase = qaRequest.releaseContext === "blocking"
2354
+ ? "release-blocking QA findings require resolution before promotion."
2355
+ : qaRequest.releaseContext === "candidate"
2356
+ ? "candidate release still requires explicit QA review before promotion."
2357
+ : "no release context was supplied; QA output remains advisory.";
1190
2358
  const qaReport = qaArtifactSchema.parse({
1191
- ...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/qa.yaml", qaRequest.targetRef, ...qaRequest.evidenceSources], []),
2359
+ ...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/qa.yaml", qaRequest.targetRef, ...qaRequest.evidenceSources], qaIssueRefs, qaGithubRefs),
1192
2360
  artifactKind: "qa-report",
1193
2361
  lifecycleDomain: "test",
1194
2362
  workflow: {
@@ -1204,11 +2372,9 @@ const qaAnalystAgent = {
1204
2372
  recommendedNextChecks: recommendedNextChecks.length > 0
1205
2373
  ? recommendedNextChecks
1206
2374
  : ["Capture additional bounded QA evidence before promotion."],
1207
- releaseImpact: qaRequest.releaseContext === "blocking"
1208
- ? "release-blocking QA findings require resolution before promotion."
1209
- : qaRequest.releaseContext === "candidate"
1210
- ? "candidate release still requires explicit QA review before promotion."
1211
- : "no release context was supplied; QA output remains advisory."
2375
+ releaseImpact: normalizedGithubActions.failingChecks.length > 0
2376
+ ? `${releaseImpactBase} GitHub Actions evidence still shows failing checks: ${normalizedGithubActions.failingChecks.join(", ")}.`
2377
+ : releaseImpactBase
1212
2378
  }
1213
2379
  });
1214
2380
  return agentOutputSchema.parse({
@@ -1226,7 +2392,8 @@ const qaAnalystAgent = {
1226
2392
  evidenceSources,
1227
2393
  executedChecks,
1228
2394
  focusAreas,
1229
- constraints: normalizedConstraints
2395
+ constraints: normalizedConstraints,
2396
+ githubActions: normalizedGithubActions
1230
2397
  },
1231
2398
  synthesizedAssessment: {
1232
2399
  releaseContext: qaRequest.releaseContext,
@@ -1435,7 +2602,7 @@ const implementationPlannerAgent = {
1435
2602
  ];
1436
2603
  const summary = `Implementation proposal prepared for ${implementationRequest.implementationGoal}.`;
1437
2604
  const implementationProposal = implementationArtifactSchema.parse({
1438
- ...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/implementation.yaml", implementationRequest.designRecordRef], designRecord.source.issueRefs),
2605
+ ...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/implementation.yaml", implementationRequest.designRecordRef], designRecord.source.issueRefs, designRecord.source.githubRefs),
1439
2606
  artifactKind: "implementation-proposal",
1440
2607
  lifecycleDomain: "build",
1441
2608
  workflow: {
@@ -1548,7 +2715,7 @@ const designAnalystAgent = {
1548
2715
  ];
1549
2716
  const summary = `Design record prepared for ${designRequest.decisionTarget}.`;
1550
2717
  const designRecord = designArtifactSchema.parse({
1551
- ...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/design.yaml", designRequest.planningBriefRef], planningBrief.source.issueRefs),
2718
+ ...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/design.yaml", designRequest.planningBriefRef], planningBrief.source.issueRefs, planningBrief.source.githubRefs),
1552
2719
  artifactKind: "design-record",
1553
2720
  lifecycleDomain: "design",
1554
2721
  workflow: {
@@ -1837,6 +3004,15 @@ export function createBuiltinAgentRegistry() {
1837
3004
  ["implementation-intake", implementationIntakeAgent],
1838
3005
  ["qa-intake", qaIntakeAgent],
1839
3006
  ["security-intake", securityIntakeAgent],
3007
+ ["incident-intake", incidentIntakeAgent],
3008
+ ["incident-evidence-normalizer", incidentEvidenceNormalizationAgent],
3009
+ ["maintenance-intake", maintenanceIntakeAgent],
3010
+ ["maintenance-evidence-normalizer", maintenanceEvidenceNormalizerAgent],
3011
+ ["maintenance-analyst", maintenanceAnalystAgent],
3012
+ ["incident-analyst", incidentAnalystAgent],
3013
+ ["release-intake", releaseIntakeAgent],
3014
+ ["release-evidence-normalizer", releaseEvidenceNormalizationAgent],
3015
+ ["release-analyst", releaseAnalystAgent],
1840
3016
  ["security-evidence-normalizer", securityEvidenceNormalizationAgent],
1841
3017
  ["security-analyst", securityAnalystAgent],
1842
3018
  ["qa-evidence-normalizer", qaEvidenceNormalizationAgent],