@h9-foundry/agentforge-cli 0.6.0 → 0.7.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/index.d.ts.map +1 -1
- package/dist/index.js +109 -11
- package/dist/index.js.map +1 -1
- package/dist/internal/builtin-agents.d.ts.map +1 -1
- package/dist/internal/builtin-agents.js +651 -28
- package/dist/internal/builtin-agents.js.map +1 -1
- package/package.json +8 -8
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { agentManifestSchema, agentOutputSchema, designArtifactSchema, implementationArtifactSchema, implementationInventorySchema, qaRequestSchema, planningArtifactSchema } from "@h9-foundry/agentforge-schemas";
|
|
3
|
+
import { agentManifestSchema, agentOutputSchema, designArtifactSchema, implementationArtifactSchema, implementationInventorySchema, qaArtifactSchema, qaEvidenceNormalizationSchema, qaRequestSchema, securityArtifactSchema, securityEvidenceNormalizationSchema, securityRequestSchema, planningArtifactSchema } from "@h9-foundry/agentforge-schemas";
|
|
4
4
|
const contextCollectorAgent = {
|
|
5
5
|
manifest: agentManifestSchema.parse({
|
|
6
6
|
version: 1,
|
|
@@ -83,6 +83,71 @@ function buildValidationCommand(packageManager, scriptName, packageName) {
|
|
|
83
83
|
}
|
|
84
84
|
return `${packageManager} ${scriptName}`;
|
|
85
85
|
}
|
|
86
|
+
function collectValidationCommands(repoRoot, packageManager, packageScopes) {
|
|
87
|
+
const discoveredValidationCommands = [];
|
|
88
|
+
const registerScripts = (packageJsonPath, source, packageName) => {
|
|
89
|
+
const scripts = parsePackageScripts(packageJsonPath);
|
|
90
|
+
for (const scriptName of Object.keys(scripts)) {
|
|
91
|
+
const command = buildValidationCommand(packageManager, scriptName, packageName);
|
|
92
|
+
discoveredValidationCommands.push({
|
|
93
|
+
command,
|
|
94
|
+
source,
|
|
95
|
+
classification: allowedValidationScriptNames.has(scriptName) ? "approval_required" : "deny",
|
|
96
|
+
reason: allowedValidationScriptNames.has(scriptName)
|
|
97
|
+
? "Discovered from a bounded repository script; execution would still require approval."
|
|
98
|
+
: "Command is not in the bounded allowlist for workflow validation."
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
if (!repoRoot) {
|
|
103
|
+
return discoveredValidationCommands;
|
|
104
|
+
}
|
|
105
|
+
registerScripts(join(repoRoot, "package.json"), "package-script");
|
|
106
|
+
for (const packageScope of packageScopes) {
|
|
107
|
+
const packageJsonPath = join(repoRoot, packageScope, "package.json");
|
|
108
|
+
if (!existsSync(packageJsonPath)) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
112
|
+
const packageName = isRecord(parsed) && typeof parsed.name === "string" ? parsed.name : packageScope;
|
|
113
|
+
registerScripts(packageJsonPath, "workspace-script", packageName);
|
|
114
|
+
}
|
|
115
|
+
return discoveredValidationCommands;
|
|
116
|
+
}
|
|
117
|
+
function loadBundleArtifactKinds(bundlePath) {
|
|
118
|
+
if (!existsSync(bundlePath)) {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
const parsed = JSON.parse(readFileSync(bundlePath, "utf8"));
|
|
122
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.lifecycleArtifacts)) {
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
return parsed.lifecycleArtifacts
|
|
126
|
+
.map((artifact) => (isRecord(artifact) && typeof artifact.artifactKind === "string" ? artifact.artifactKind : undefined))
|
|
127
|
+
.filter((artifactKind) => Boolean(artifactKind));
|
|
128
|
+
}
|
|
129
|
+
function loadBundleArtifactPayloadPaths(bundlePath) {
|
|
130
|
+
if (!existsSync(bundlePath)) {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
const parsed = JSON.parse(readFileSync(bundlePath, "utf8"));
|
|
134
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.lifecycleArtifacts)) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
return parsed.lifecycleArtifacts.flatMap((artifact) => {
|
|
138
|
+
if (!isRecord(artifact) || !isRecord(artifact.payload)) {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
const payload = artifact.payload;
|
|
142
|
+
if (Array.isArray(payload.affectedPaths)) {
|
|
143
|
+
return asStringArray(payload.affectedPaths);
|
|
144
|
+
}
|
|
145
|
+
if (Array.isArray(payload.evidenceSources)) {
|
|
146
|
+
return asStringArray(payload.evidenceSources);
|
|
147
|
+
}
|
|
148
|
+
return [];
|
|
149
|
+
});
|
|
150
|
+
}
|
|
86
151
|
function derivePackageScope(pathValue) {
|
|
87
152
|
const segments = pathValue.split("/").filter(Boolean);
|
|
88
153
|
if (segments.length < 2) {
|
|
@@ -100,6 +165,45 @@ function getWorkflowInput(stateSlice, key) {
|
|
|
100
165
|
}
|
|
101
166
|
return stateSlice.workflowInputs[key];
|
|
102
167
|
}
|
|
168
|
+
function buildLifecycleArtifactEnvelopeBase(state, displayName, summary, inputRefs, issueRefs = []) {
|
|
169
|
+
return {
|
|
170
|
+
schemaVersion: state.version,
|
|
171
|
+
workflow: {
|
|
172
|
+
name: state.workflow,
|
|
173
|
+
displayName
|
|
174
|
+
},
|
|
175
|
+
source: {
|
|
176
|
+
sourceType: "workflow-run",
|
|
177
|
+
runId: state.runId,
|
|
178
|
+
inputRefs: [...inputRefs],
|
|
179
|
+
issueRefs: [...issueRefs]
|
|
180
|
+
},
|
|
181
|
+
status: "complete",
|
|
182
|
+
generatedAt: new Date().toISOString(),
|
|
183
|
+
repo: {
|
|
184
|
+
root: state.repo.root,
|
|
185
|
+
name: state.repo.name,
|
|
186
|
+
branch: state.repo.branch
|
|
187
|
+
},
|
|
188
|
+
provenance: {
|
|
189
|
+
generatedBy: "agentforge-runtime",
|
|
190
|
+
schemaVersion: state.version,
|
|
191
|
+
executionEnvironment: state.context.ciExecution ? "ci" : "local",
|
|
192
|
+
repoRoot: state.repo.root
|
|
193
|
+
},
|
|
194
|
+
redaction: {
|
|
195
|
+
applied: true,
|
|
196
|
+
strategyVersion: "1.0.0",
|
|
197
|
+
categories: ["github-token", "api-key", "aws-key", "bearer-token", "password", "private-key"]
|
|
198
|
+
},
|
|
199
|
+
auditLink: {
|
|
200
|
+
entryIds: [],
|
|
201
|
+
findingIds: [],
|
|
202
|
+
proposedActionIds: []
|
|
203
|
+
},
|
|
204
|
+
summary
|
|
205
|
+
};
|
|
206
|
+
}
|
|
103
207
|
function buildArtifactEnvelopeBase(state, summary, inputRefs, issueRefs) {
|
|
104
208
|
return {
|
|
105
209
|
schemaVersion: state.version,
|
|
@@ -593,6 +697,546 @@ const qaIntakeAgent = {
|
|
|
593
697
|
});
|
|
594
698
|
}
|
|
595
699
|
};
|
|
700
|
+
const securityIntakeAgent = {
|
|
701
|
+
manifest: agentManifestSchema.parse({
|
|
702
|
+
version: 1,
|
|
703
|
+
name: "security-intake",
|
|
704
|
+
displayName: "Security Intake",
|
|
705
|
+
category: "security",
|
|
706
|
+
runtime: {
|
|
707
|
+
minVersion: "0.1.0",
|
|
708
|
+
kind: "deterministic"
|
|
709
|
+
},
|
|
710
|
+
permissions: {
|
|
711
|
+
model: false,
|
|
712
|
+
network: false,
|
|
713
|
+
tools: [],
|
|
714
|
+
readPaths: [".agentops/requests/**", ".agentops/runs/**"],
|
|
715
|
+
writePaths: []
|
|
716
|
+
},
|
|
717
|
+
inputs: ["workflowInputs", "repo"],
|
|
718
|
+
outputs: ["summary", "metadata"],
|
|
719
|
+
contextPolicy: {
|
|
720
|
+
sections: ["workflowInputs", "repo", "context"],
|
|
721
|
+
minimalContext: true
|
|
722
|
+
},
|
|
723
|
+
catalog: {
|
|
724
|
+
domain: "security",
|
|
725
|
+
supportLevel: "internal",
|
|
726
|
+
maturity: "mvp",
|
|
727
|
+
trustScope: "official-core-only"
|
|
728
|
+
},
|
|
729
|
+
trust: {
|
|
730
|
+
tier: "core",
|
|
731
|
+
source: "official",
|
|
732
|
+
reviewed: true
|
|
733
|
+
}
|
|
734
|
+
}),
|
|
735
|
+
outputSchema: agentOutputSchema,
|
|
736
|
+
async execute({ stateSlice }) {
|
|
737
|
+
const securityRequest = getWorkflowInput(stateSlice, "securityRequest");
|
|
738
|
+
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
739
|
+
const referencedArtifactKinds = getWorkflowInput(stateSlice, "securityTargetArtifactKinds") ?? [];
|
|
740
|
+
if (!securityRequest) {
|
|
741
|
+
throw new Error("security-review requires a validated security request before runtime execution.");
|
|
742
|
+
}
|
|
743
|
+
const targetType = securityRequest.targetRef.endsWith("bundle.json") ? "artifact-bundle" : "local-reference";
|
|
744
|
+
return agentOutputSchema.parse({
|
|
745
|
+
summary: `Loaded security request from ${requestFile ?? ".agentops/requests/security.yaml"} targeting ${securityRequest.targetRef}.`,
|
|
746
|
+
findings: [],
|
|
747
|
+
proposedActions: [],
|
|
748
|
+
lifecycleArtifacts: [],
|
|
749
|
+
requestedTools: [],
|
|
750
|
+
blockedActionFlags: [],
|
|
751
|
+
metadata: {
|
|
752
|
+
...securityRequestSchema.parse({
|
|
753
|
+
...securityRequest,
|
|
754
|
+
evidenceSources: [...new Set([securityRequest.targetRef, ...securityRequest.evidenceSources])]
|
|
755
|
+
}),
|
|
756
|
+
targetType,
|
|
757
|
+
referencedArtifactKinds
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
const securityEvidenceNormalizationAgent = {
|
|
763
|
+
manifest: agentManifestSchema.parse({
|
|
764
|
+
version: 1,
|
|
765
|
+
name: "security-evidence-normalizer",
|
|
766
|
+
displayName: "Security Evidence Normalizer",
|
|
767
|
+
category: "security",
|
|
768
|
+
runtime: {
|
|
769
|
+
minVersion: "0.1.0",
|
|
770
|
+
kind: "deterministic"
|
|
771
|
+
},
|
|
772
|
+
permissions: {
|
|
773
|
+
model: false,
|
|
774
|
+
network: false,
|
|
775
|
+
tools: [],
|
|
776
|
+
readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/*.json", "**/*.md", "**/package.json"],
|
|
777
|
+
writePaths: []
|
|
778
|
+
},
|
|
779
|
+
inputs: ["workflowInputs", "repo", "agentResults"],
|
|
780
|
+
outputs: ["summary", "metadata"],
|
|
781
|
+
contextPolicy: {
|
|
782
|
+
sections: ["workflowInputs", "repo", "agentResults"],
|
|
783
|
+
minimalContext: true
|
|
784
|
+
},
|
|
785
|
+
catalog: {
|
|
786
|
+
domain: "security",
|
|
787
|
+
supportLevel: "internal",
|
|
788
|
+
maturity: "mvp",
|
|
789
|
+
trustScope: "official-core-only"
|
|
790
|
+
},
|
|
791
|
+
trust: {
|
|
792
|
+
tier: "core",
|
|
793
|
+
source: "official",
|
|
794
|
+
reviewed: true
|
|
795
|
+
}
|
|
796
|
+
}),
|
|
797
|
+
outputSchema: agentOutputSchema,
|
|
798
|
+
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.");
|
|
802
|
+
}
|
|
803
|
+
const repoRoot = stateSlice.repo?.root;
|
|
804
|
+
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])];
|
|
814
|
+
const missingEvidenceSources = normalizedEvidenceSources.filter((pathValue) => repoRoot && !existsSync(join(repoRoot, pathValue)));
|
|
815
|
+
if (missingEvidenceSources.length > 0) {
|
|
816
|
+
throw new Error(`Security evidence source not found: ${missingEvidenceSources[0]}`);
|
|
817
|
+
}
|
|
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."
|
|
827
|
+
];
|
|
828
|
+
const provenanceRefs = [
|
|
829
|
+
securityRequest.targetRef,
|
|
830
|
+
...securityRequest.evidenceSources,
|
|
831
|
+
...referencedArtifactKinds.map((artifactKind) => `${securityRequest.targetRef}#${artifactKind}`)
|
|
832
|
+
];
|
|
833
|
+
const normalization = securityEvidenceNormalizationSchema.parse({
|
|
834
|
+
targetRef: securityRequest.targetRef,
|
|
835
|
+
targetType,
|
|
836
|
+
referencedArtifactKinds,
|
|
837
|
+
normalizedEvidenceSources,
|
|
838
|
+
missingEvidenceSources: [],
|
|
839
|
+
normalizedFocusAreas,
|
|
840
|
+
securitySignals,
|
|
841
|
+
provenanceRefs: [...new Set(provenanceRefs)],
|
|
842
|
+
affectedPackages
|
|
843
|
+
});
|
|
844
|
+
return agentOutputSchema.parse({
|
|
845
|
+
summary: `Normalized security evidence for ${securityRequest.targetRef}.`,
|
|
846
|
+
findings: [],
|
|
847
|
+
proposedActions: [],
|
|
848
|
+
lifecycleArtifacts: [],
|
|
849
|
+
requestedTools: [],
|
|
850
|
+
blockedActionFlags: [],
|
|
851
|
+
metadata: normalization
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
const securityAnalystAgent = {
|
|
856
|
+
manifest: agentManifestSchema.parse({
|
|
857
|
+
version: 1,
|
|
858
|
+
name: "security-analyst",
|
|
859
|
+
displayName: "Security Analyst",
|
|
860
|
+
category: "security",
|
|
861
|
+
runtime: {
|
|
862
|
+
minVersion: "0.1.0",
|
|
863
|
+
kind: "reasoning"
|
|
864
|
+
},
|
|
865
|
+
permissions: {
|
|
866
|
+
model: true,
|
|
867
|
+
network: false,
|
|
868
|
+
tools: [],
|
|
869
|
+
readPaths: ["**/*"],
|
|
870
|
+
writePaths: []
|
|
871
|
+
},
|
|
872
|
+
inputs: ["workflowInputs", "repo", "changes", "agentResults"],
|
|
873
|
+
outputs: ["lifecycleArtifacts"],
|
|
874
|
+
contextPolicy: {
|
|
875
|
+
sections: ["workflowInputs", "repo", "changes", "agentResults"],
|
|
876
|
+
minimalContext: true
|
|
877
|
+
},
|
|
878
|
+
catalog: {
|
|
879
|
+
domain: "security",
|
|
880
|
+
supportLevel: "internal",
|
|
881
|
+
maturity: "mvp",
|
|
882
|
+
trustScope: "official-core-only"
|
|
883
|
+
},
|
|
884
|
+
trust: {
|
|
885
|
+
tier: "core",
|
|
886
|
+
source: "official",
|
|
887
|
+
reviewed: true
|
|
888
|
+
}
|
|
889
|
+
}),
|
|
890
|
+
outputSchema: agentOutputSchema,
|
|
891
|
+
async execute({ state, stateSlice }) {
|
|
892
|
+
const securityRequest = getWorkflowInput(stateSlice, "securityRequest");
|
|
893
|
+
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
894
|
+
if (!securityRequest) {
|
|
895
|
+
throw new Error("security-review requires validated security inputs before security analysis.");
|
|
896
|
+
}
|
|
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
|
+
return agentOutputSchema.parse({
|
|
974
|
+
summary,
|
|
975
|
+
findings: [],
|
|
976
|
+
proposedActions: [],
|
|
977
|
+
lifecycleArtifacts: [securityReport],
|
|
978
|
+
requestedTools: [],
|
|
979
|
+
blockedActionFlags: [],
|
|
980
|
+
confidence: 0.76,
|
|
981
|
+
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
|
+
}
|
|
995
|
+
}
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
const qaEvidenceNormalizationAgent = {
|
|
1000
|
+
manifest: agentManifestSchema.parse({
|
|
1001
|
+
version: 1,
|
|
1002
|
+
name: "qa-evidence-normalizer",
|
|
1003
|
+
displayName: "QA Evidence Normalizer",
|
|
1004
|
+
category: "qa",
|
|
1005
|
+
runtime: {
|
|
1006
|
+
minVersion: "0.1.0",
|
|
1007
|
+
kind: "deterministic"
|
|
1008
|
+
},
|
|
1009
|
+
permissions: {
|
|
1010
|
+
model: false,
|
|
1011
|
+
network: false,
|
|
1012
|
+
tools: [],
|
|
1013
|
+
readPaths: [".agentops/requests/**", ".agentops/runs/**", "**/package.json", "**/*.json", "**/*.xml", "**/*.log", "**/*.md"],
|
|
1014
|
+
writePaths: []
|
|
1015
|
+
},
|
|
1016
|
+
inputs: ["workflowInputs", "repo", "agentResults"],
|
|
1017
|
+
outputs: ["summary", "metadata"],
|
|
1018
|
+
contextPolicy: {
|
|
1019
|
+
sections: ["workflowInputs", "repo", "agentResults"],
|
|
1020
|
+
minimalContext: true
|
|
1021
|
+
},
|
|
1022
|
+
catalog: {
|
|
1023
|
+
domain: "test",
|
|
1024
|
+
supportLevel: "internal",
|
|
1025
|
+
maturity: "mvp",
|
|
1026
|
+
trustScope: "official-core-only"
|
|
1027
|
+
},
|
|
1028
|
+
trust: {
|
|
1029
|
+
tier: "core",
|
|
1030
|
+
source: "official",
|
|
1031
|
+
reviewed: true
|
|
1032
|
+
}
|
|
1033
|
+
}),
|
|
1034
|
+
outputSchema: agentOutputSchema,
|
|
1035
|
+
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.");
|
|
1039
|
+
}
|
|
1040
|
+
const repoRoot = stateSlice.repo?.root;
|
|
1041
|
+
const packageManager = stateSlice.repo?.packageManager || "pnpm";
|
|
1042
|
+
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])];
|
|
1050
|
+
const missingEvidenceSources = normalizedEvidenceSources.filter((pathValue) => repoRoot && !existsSync(join(repoRoot, pathValue)));
|
|
1051
|
+
if (missingEvidenceSources.length > 0) {
|
|
1052
|
+
throw new Error(`QA evidence source not found: ${missingEvidenceSources[0]}`);
|
|
1053
|
+
}
|
|
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)) {
|
|
1058
|
+
return [];
|
|
1059
|
+
}
|
|
1060
|
+
const implementationArtifact = parsed.lifecycleArtifacts.find((artifact) => isRecord(artifact) &&
|
|
1061
|
+
artifact.artifactKind === "implementation-proposal" &&
|
|
1062
|
+
isRecord(artifact.payload) &&
|
|
1063
|
+
Array.isArray(artifact.payload.affectedPaths));
|
|
1064
|
+
return implementationArtifact?.payload?.affectedPaths ?? [];
|
|
1065
|
+
})())
|
|
1066
|
+
: [];
|
|
1067
|
+
const affectedPackages = [...new Set(bundleAffectedPaths.map((pathValue) => derivePackageScope(pathValue)).filter((value) => Boolean(value)))];
|
|
1068
|
+
const allowedValidationCommands = collectValidationCommands(repoRoot, packageManager, affectedPackages).filter((entry) => entry.classification === "approval_required");
|
|
1069
|
+
const allowlistedCommands = new Set(allowedValidationCommands.map((entry) => entry.command));
|
|
1070
|
+
const normalizedExecutedChecks = qaRequest.executedChecks.map(normalizeRequestedCommand);
|
|
1071
|
+
const unrecognizedExecutedChecks = normalizedExecutedChecks.filter((command) => !allowlistedCommands.has(command));
|
|
1072
|
+
const normalization = qaEvidenceNormalizationSchema.parse({
|
|
1073
|
+
targetRef: qaRequest.targetRef,
|
|
1074
|
+
targetType,
|
|
1075
|
+
referencedArtifactKinds,
|
|
1076
|
+
normalizedEvidenceSources,
|
|
1077
|
+
missingEvidenceSources: [],
|
|
1078
|
+
normalizedExecutedChecks,
|
|
1079
|
+
unrecognizedExecutedChecks,
|
|
1080
|
+
affectedPackages,
|
|
1081
|
+
allowedValidationCommands
|
|
1082
|
+
});
|
|
1083
|
+
return agentOutputSchema.parse({
|
|
1084
|
+
summary: `Normalized QA evidence across ${normalization.normalizedEvidenceSources.length} source(s) and ${normalization.allowedValidationCommands.length} allowlisted validation command(s).`,
|
|
1085
|
+
findings: [],
|
|
1086
|
+
proposedActions: [],
|
|
1087
|
+
lifecycleArtifacts: [],
|
|
1088
|
+
requestedTools: [],
|
|
1089
|
+
blockedActionFlags: [],
|
|
1090
|
+
metadata: normalization
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
const qaAnalystAgent = {
|
|
1095
|
+
manifest: agentManifestSchema.parse({
|
|
1096
|
+
version: 1,
|
|
1097
|
+
name: "qa-analyst",
|
|
1098
|
+
displayName: "QA Analyst",
|
|
1099
|
+
category: "qa",
|
|
1100
|
+
runtime: {
|
|
1101
|
+
minVersion: "0.1.0",
|
|
1102
|
+
kind: "reasoning"
|
|
1103
|
+
},
|
|
1104
|
+
permissions: {
|
|
1105
|
+
model: true,
|
|
1106
|
+
network: false,
|
|
1107
|
+
tools: [],
|
|
1108
|
+
readPaths: ["**/*"],
|
|
1109
|
+
writePaths: []
|
|
1110
|
+
},
|
|
1111
|
+
inputs: ["workflowInputs", "repo", "changes", "agentResults"],
|
|
1112
|
+
outputs: ["lifecycleArtifacts"],
|
|
1113
|
+
contextPolicy: {
|
|
1114
|
+
sections: ["workflowInputs", "repo", "changes", "agentResults"],
|
|
1115
|
+
minimalContext: true
|
|
1116
|
+
},
|
|
1117
|
+
catalog: {
|
|
1118
|
+
domain: "test",
|
|
1119
|
+
supportLevel: "internal",
|
|
1120
|
+
maturity: "mvp",
|
|
1121
|
+
trustScope: "official-core-only"
|
|
1122
|
+
},
|
|
1123
|
+
trust: {
|
|
1124
|
+
tier: "core",
|
|
1125
|
+
source: "official",
|
|
1126
|
+
reviewed: true
|
|
1127
|
+
}
|
|
1128
|
+
}),
|
|
1129
|
+
outputSchema: agentOutputSchema,
|
|
1130
|
+
async execute({ state, stateSlice }) {
|
|
1131
|
+
const qaRequest = getWorkflowInput(stateSlice, "qaRequest");
|
|
1132
|
+
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
1133
|
+
if (!qaRequest) {
|
|
1134
|
+
throw new Error("qa-review requires validated QA inputs before QA analysis.");
|
|
1135
|
+
}
|
|
1136
|
+
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);
|
|
1140
|
+
const normalizedFocusAreas = asStringArray(intakeMetadata.focusAreas);
|
|
1141
|
+
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
|
|
1146
|
+
: typeof intakeMetadata.targetType === "string"
|
|
1147
|
+
? intakeMetadata.targetType
|
|
1148
|
+
: "local-reference";
|
|
1149
|
+
const evidenceSources = normalizedEvidenceSources.length > 0
|
|
1150
|
+
? normalizedEvidenceSources
|
|
1151
|
+
: [...new Set([qaRequest.targetRef, ...qaRequest.evidenceSources])];
|
|
1152
|
+
const executedChecks = normalizedExecutedChecks.length > 0 ? normalizedExecutedChecks : qaRequest.executedChecks;
|
|
1153
|
+
const focusAreas = normalizedFocusAreas.length > 0 ? normalizedFocusAreas : qaRequest.focusAreas;
|
|
1154
|
+
const findings = focusAreas.length > 0
|
|
1155
|
+
? focusAreas.map((focusArea, index) => ({
|
|
1156
|
+
id: `qa-finding-${index + 1}`,
|
|
1157
|
+
title: `Inspect ${focusArea} evidence before promotion`,
|
|
1158
|
+
summary: `QA review flagged ${focusArea} as an area requiring bounded interpretation for ${qaRequest.targetRef}.`,
|
|
1159
|
+
severity: qaRequest.releaseContext === "blocking" ? "high" : qaRequest.releaseContext === "candidate" ? "medium" : "low",
|
|
1160
|
+
rationale: `The MVP QA workflow remains read-only and request-driven, so ${focusArea} still depends on referenced evidence rather than automatic execution.`,
|
|
1161
|
+
confidence: 0.74,
|
|
1162
|
+
location: qaRequest.targetRef,
|
|
1163
|
+
tags: ["qa", focusArea]
|
|
1164
|
+
}))
|
|
1165
|
+
: [
|
|
1166
|
+
{
|
|
1167
|
+
id: "qa-finding-1",
|
|
1168
|
+
title: "Review referenced QA evidence before promotion",
|
|
1169
|
+
summary: `QA review requires bounded interpretation of the referenced evidence for ${qaRequest.targetRef}.`,
|
|
1170
|
+
severity: qaRequest.releaseContext === "blocking" ? "high" : "medium",
|
|
1171
|
+
rationale: "The current QA workflow synthesizes a report from validated references and does not execute arbitrary test commands.",
|
|
1172
|
+
confidence: 0.71,
|
|
1173
|
+
location: qaRequest.targetRef,
|
|
1174
|
+
tags: ["qa", "evidence"]
|
|
1175
|
+
}
|
|
1176
|
+
];
|
|
1177
|
+
const coverageGaps = [
|
|
1178
|
+
...(evidenceSources.length === 0 ? ["No QA evidence sources were provided beyond the target reference."] : []),
|
|
1179
|
+
...missingEvidenceSources.map((pathValue) => `Referenced QA evidence is missing: ${pathValue}`),
|
|
1180
|
+
...(focusAreas.includes("coverage") ? ["Coverage evidence still needs deterministic normalization before it can be promoted to an official QA signal."] : []),
|
|
1181
|
+
...(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}`)
|
|
1183
|
+
];
|
|
1184
|
+
const recommendedNextChecks = [
|
|
1185
|
+
...executedChecks.map((command) => `Review the recorded output for \`${command}\` before promotion.`),
|
|
1186
|
+
...focusAreas.map((focusArea) => `Confirm whether ${focusArea} needs additional deterministic QA evidence.`),
|
|
1187
|
+
...(normalizedConstraints.length > 0 ? [`Keep QA follow-up bounded by: ${normalizedConstraints.join("; ")}.`] : [])
|
|
1188
|
+
];
|
|
1189
|
+
const summary = `QA report prepared for ${qaRequest.targetRef}.`;
|
|
1190
|
+
const qaReport = qaArtifactSchema.parse({
|
|
1191
|
+
...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/qa.yaml", qaRequest.targetRef, ...qaRequest.evidenceSources], []),
|
|
1192
|
+
artifactKind: "qa-report",
|
|
1193
|
+
lifecycleDomain: "test",
|
|
1194
|
+
workflow: {
|
|
1195
|
+
name: state.workflow,
|
|
1196
|
+
displayName: "QA Review"
|
|
1197
|
+
},
|
|
1198
|
+
payload: {
|
|
1199
|
+
targetRef: qaRequest.targetRef,
|
|
1200
|
+
evidenceSources,
|
|
1201
|
+
executedChecks,
|
|
1202
|
+
findings,
|
|
1203
|
+
coverageGaps,
|
|
1204
|
+
recommendedNextChecks: recommendedNextChecks.length > 0
|
|
1205
|
+
? recommendedNextChecks
|
|
1206
|
+
: ["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."
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
return agentOutputSchema.parse({
|
|
1215
|
+
summary,
|
|
1216
|
+
findings: [],
|
|
1217
|
+
proposedActions: [],
|
|
1218
|
+
lifecycleArtifacts: [qaReport],
|
|
1219
|
+
requestedTools: [],
|
|
1220
|
+
blockedActionFlags: [],
|
|
1221
|
+
confidence: 0.74,
|
|
1222
|
+
metadata: {
|
|
1223
|
+
deterministicInputs: {
|
|
1224
|
+
targetRef: qaRequest.targetRef,
|
|
1225
|
+
targetType,
|
|
1226
|
+
evidenceSources,
|
|
1227
|
+
executedChecks,
|
|
1228
|
+
focusAreas,
|
|
1229
|
+
constraints: normalizedConstraints
|
|
1230
|
+
},
|
|
1231
|
+
synthesizedAssessment: {
|
|
1232
|
+
releaseContext: qaRequest.releaseContext,
|
|
1233
|
+
recommendedNextChecks: qaReport.payload.recommendedNextChecks,
|
|
1234
|
+
coverageGaps: qaReport.payload.coverageGaps
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
596
1240
|
const implementationInventoryAgent = {
|
|
597
1241
|
manifest: agentManifestSchema.parse({
|
|
598
1242
|
version: 1,
|
|
@@ -662,33 +1306,7 @@ const implementationInventoryAgent = {
|
|
|
662
1306
|
const policySurfaces = [
|
|
663
1307
|
...new Set(resolvedAffectedPaths.filter((pathValue) => pathValue.includes("policy") || pathValue.includes(".agentops/policy.yaml")))
|
|
664
1308
|
];
|
|
665
|
-
const discoveredValidationCommands =
|
|
666
|
-
const registerScripts = (packageJsonPath, source, packageName) => {
|
|
667
|
-
const scripts = parsePackageScripts(packageJsonPath);
|
|
668
|
-
for (const scriptName of Object.keys(scripts)) {
|
|
669
|
-
const command = buildValidationCommand(packageManager, scriptName, packageName);
|
|
670
|
-
discoveredValidationCommands.push({
|
|
671
|
-
command,
|
|
672
|
-
source,
|
|
673
|
-
classification: allowedValidationScriptNames.has(scriptName) ? "approval_required" : "deny",
|
|
674
|
-
reason: allowedValidationScriptNames.has(scriptName)
|
|
675
|
-
? "Discovered from a bounded repository script; execution would still require approval."
|
|
676
|
-
: "Command is not in the bounded allowlist for implementation validation."
|
|
677
|
-
});
|
|
678
|
-
}
|
|
679
|
-
};
|
|
680
|
-
if (repoRoot) {
|
|
681
|
-
registerScripts(join(repoRoot, "package.json"), "package-script");
|
|
682
|
-
for (const packageScope of affectedPackages) {
|
|
683
|
-
const packageJsonPath = join(repoRoot, packageScope, "package.json");
|
|
684
|
-
if (!existsSync(packageJsonPath)) {
|
|
685
|
-
continue;
|
|
686
|
-
}
|
|
687
|
-
const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
688
|
-
const packageName = isRecord(parsed) && typeof parsed.name === "string" ? parsed.name : packageScope;
|
|
689
|
-
registerScripts(packageJsonPath, "workspace-script", packageName);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
1309
|
+
const discoveredValidationCommands = collectValidationCommands(repoRoot, packageManager, affectedPackages);
|
|
692
1310
|
const normalizedRequestedCommands = implementationRequest.validationCommands.map(normalizeRequestedCommand);
|
|
693
1311
|
const allowlistedCommands = new Set(discoveredValidationCommands
|
|
694
1312
|
.filter((entry) => entry.classification === "approval_required")
|
|
@@ -1218,6 +1836,11 @@ export function createBuiltinAgentRegistry() {
|
|
|
1218
1836
|
["design-inventory", designInventoryAgent],
|
|
1219
1837
|
["implementation-intake", implementationIntakeAgent],
|
|
1220
1838
|
["qa-intake", qaIntakeAgent],
|
|
1839
|
+
["security-intake", securityIntakeAgent],
|
|
1840
|
+
["security-evidence-normalizer", securityEvidenceNormalizationAgent],
|
|
1841
|
+
["security-analyst", securityAnalystAgent],
|
|
1842
|
+
["qa-evidence-normalizer", qaEvidenceNormalizationAgent],
|
|
1843
|
+
["qa-analyst", qaAnalystAgent],
|
|
1221
1844
|
["implementation-inventory", implementationInventoryAgent],
|
|
1222
1845
|
["implementation-planner", implementationPlannerAgent],
|
|
1223
1846
|
["design-analyst", designAnalystAgent],
|