@h9-foundry/agentforge-cli 0.5.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 +197 -10
- package/dist/index.js.map +1 -1
- package/dist/internal/builtin-agents.d.ts.map +1 -1
- package/dist/internal/builtin-agents.js +1053 -2
- package/dist/internal/builtin-agents.js.map +1 -1
- package/package.json +8 -8
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { agentManifestSchema, agentOutputSchema, designArtifactSchema, 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,
|
|
@@ -63,12 +63,147 @@ function isRecord(value) {
|
|
|
63
63
|
function asStringArray(value) {
|
|
64
64
|
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
65
65
|
}
|
|
66
|
+
function parsePackageScripts(packageJsonPath) {
|
|
67
|
+
if (!existsSync(packageJsonPath)) {
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
71
|
+
if (!isRecord(parsed) || !isRecord(parsed.scripts)) {
|
|
72
|
+
return {};
|
|
73
|
+
}
|
|
74
|
+
return Object.fromEntries(Object.entries(parsed.scripts).filter((entry) => typeof entry[1] === "string"));
|
|
75
|
+
}
|
|
76
|
+
const allowedValidationScriptNames = new Set(["test", "lint", "typecheck", "build", "build:packages", "release:verify"]);
|
|
77
|
+
function normalizeRequestedCommand(command) {
|
|
78
|
+
return command.trim().replace(/\s+/g, " ");
|
|
79
|
+
}
|
|
80
|
+
function buildValidationCommand(packageManager, scriptName, packageName) {
|
|
81
|
+
if (packageName) {
|
|
82
|
+
return `${packageManager} --filter ${packageName} ${scriptName}`;
|
|
83
|
+
}
|
|
84
|
+
return `${packageManager} ${scriptName}`;
|
|
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
|
+
}
|
|
151
|
+
function derivePackageScope(pathValue) {
|
|
152
|
+
const segments = pathValue.split("/").filter(Boolean);
|
|
153
|
+
if (segments.length < 2) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
const [topLevel, scope] = segments;
|
|
157
|
+
if (topLevel === "packages" || topLevel === "agents" || topLevel === "adapters") {
|
|
158
|
+
return `${topLevel}/${scope}`;
|
|
159
|
+
}
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
66
162
|
function getWorkflowInput(stateSlice, key) {
|
|
67
163
|
if (!isRecord(stateSlice.workflowInputs)) {
|
|
68
164
|
return undefined;
|
|
69
165
|
}
|
|
70
166
|
return stateSlice.workflowInputs[key];
|
|
71
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
|
+
}
|
|
72
207
|
function buildArtifactEnvelopeBase(state, summary, inputRefs, issueRefs) {
|
|
73
208
|
return {
|
|
74
209
|
schemaVersion: state.version,
|
|
@@ -436,6 +571,913 @@ const designInventoryAgent = {
|
|
|
436
571
|
});
|
|
437
572
|
}
|
|
438
573
|
};
|
|
574
|
+
const implementationIntakeAgent = {
|
|
575
|
+
manifest: agentManifestSchema.parse({
|
|
576
|
+
version: 1,
|
|
577
|
+
name: "implementation-intake",
|
|
578
|
+
displayName: "Implementation Intake",
|
|
579
|
+
category: "implementation",
|
|
580
|
+
runtime: {
|
|
581
|
+
minVersion: "0.1.0",
|
|
582
|
+
kind: "deterministic"
|
|
583
|
+
},
|
|
584
|
+
permissions: {
|
|
585
|
+
model: false,
|
|
586
|
+
network: false,
|
|
587
|
+
tools: [],
|
|
588
|
+
readPaths: [".agentops/requests/**", ".agentops/runs/**"],
|
|
589
|
+
writePaths: []
|
|
590
|
+
},
|
|
591
|
+
inputs: ["workflowInputs", "repo"],
|
|
592
|
+
outputs: ["summary", "metadata"],
|
|
593
|
+
contextPolicy: {
|
|
594
|
+
sections: ["workflowInputs", "repo", "context"],
|
|
595
|
+
minimalContext: true
|
|
596
|
+
},
|
|
597
|
+
catalog: {
|
|
598
|
+
domain: "build",
|
|
599
|
+
supportLevel: "internal",
|
|
600
|
+
maturity: "mvp",
|
|
601
|
+
trustScope: "official-core-only"
|
|
602
|
+
},
|
|
603
|
+
trust: {
|
|
604
|
+
tier: "core",
|
|
605
|
+
source: "official",
|
|
606
|
+
reviewed: true
|
|
607
|
+
}
|
|
608
|
+
}),
|
|
609
|
+
outputSchema: agentOutputSchema,
|
|
610
|
+
async execute({ stateSlice }) {
|
|
611
|
+
const implementationRequest = getWorkflowInput(stateSlice, "implementationRequest");
|
|
612
|
+
const designRecord = getWorkflowInput(stateSlice, "designRecord");
|
|
613
|
+
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
614
|
+
if (!implementationRequest || !designRecord) {
|
|
615
|
+
throw new Error("implementation-proposal requires a validated implementation request and design record before runtime execution.");
|
|
616
|
+
}
|
|
617
|
+
return agentOutputSchema.parse({
|
|
618
|
+
summary: `Loaded implementation request from ${requestFile ?? ".agentops/requests/implementation.yaml"} with design record ${implementationRequest.designRecordRef}.`,
|
|
619
|
+
findings: [],
|
|
620
|
+
proposedActions: [],
|
|
621
|
+
lifecycleArtifacts: [],
|
|
622
|
+
requestedTools: [],
|
|
623
|
+
blockedActionFlags: [],
|
|
624
|
+
metadata: {
|
|
625
|
+
requestFile,
|
|
626
|
+
designRecordRef: implementationRequest.designRecordRef,
|
|
627
|
+
implementationGoal: implementationRequest.implementationGoal,
|
|
628
|
+
approvalMode: implementationRequest.approvalMode,
|
|
629
|
+
targetPaths: implementationRequest.targetPaths,
|
|
630
|
+
validationCommands: implementationRequest.validationCommands,
|
|
631
|
+
designDecisionSummary: designRecord.payload.decisionSummary
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
const qaIntakeAgent = {
|
|
637
|
+
manifest: agentManifestSchema.parse({
|
|
638
|
+
version: 1,
|
|
639
|
+
name: "qa-intake",
|
|
640
|
+
displayName: "QA Intake",
|
|
641
|
+
category: "qa",
|
|
642
|
+
runtime: {
|
|
643
|
+
minVersion: "0.1.0",
|
|
644
|
+
kind: "deterministic"
|
|
645
|
+
},
|
|
646
|
+
permissions: {
|
|
647
|
+
model: false,
|
|
648
|
+
network: false,
|
|
649
|
+
tools: [],
|
|
650
|
+
readPaths: [".agentops/requests/**", ".agentops/runs/**"],
|
|
651
|
+
writePaths: []
|
|
652
|
+
},
|
|
653
|
+
inputs: ["workflowInputs", "repo"],
|
|
654
|
+
outputs: ["summary", "metadata"],
|
|
655
|
+
contextPolicy: {
|
|
656
|
+
sections: ["workflowInputs", "repo", "context"],
|
|
657
|
+
minimalContext: true
|
|
658
|
+
},
|
|
659
|
+
catalog: {
|
|
660
|
+
domain: "test",
|
|
661
|
+
supportLevel: "internal",
|
|
662
|
+
maturity: "mvp",
|
|
663
|
+
trustScope: "official-core-only"
|
|
664
|
+
},
|
|
665
|
+
trust: {
|
|
666
|
+
tier: "core",
|
|
667
|
+
source: "official",
|
|
668
|
+
reviewed: true
|
|
669
|
+
}
|
|
670
|
+
}),
|
|
671
|
+
outputSchema: agentOutputSchema,
|
|
672
|
+
async execute({ stateSlice }) {
|
|
673
|
+
const qaRequest = getWorkflowInput(stateSlice, "qaRequest");
|
|
674
|
+
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
675
|
+
if (!qaRequest) {
|
|
676
|
+
throw new Error("qa-review requires a validated QA request before runtime execution.");
|
|
677
|
+
}
|
|
678
|
+
const targetType = qaRequest.targetRef.endsWith("bundle.json")
|
|
679
|
+
? "artifact-bundle"
|
|
680
|
+
: qaRequest.targetRef.endsWith(".xml") || qaRequest.targetRef.endsWith(".json") || qaRequest.targetRef.endsWith(".log")
|
|
681
|
+
? "validation-output"
|
|
682
|
+
: "local-reference";
|
|
683
|
+
return agentOutputSchema.parse({
|
|
684
|
+
summary: `Loaded QA request from ${requestFile ?? ".agentops/requests/qa.yaml"} targeting ${qaRequest.targetRef}.`,
|
|
685
|
+
findings: [],
|
|
686
|
+
proposedActions: [],
|
|
687
|
+
lifecycleArtifacts: [],
|
|
688
|
+
requestedTools: [],
|
|
689
|
+
blockedActionFlags: [],
|
|
690
|
+
metadata: {
|
|
691
|
+
...qaRequestSchema.parse({
|
|
692
|
+
...qaRequest,
|
|
693
|
+
evidenceSources: [...new Set([qaRequest.targetRef, ...qaRequest.evidenceSources])]
|
|
694
|
+
}),
|
|
695
|
+
targetType
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
}
|
|
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
|
+
};
|
|
1240
|
+
const implementationInventoryAgent = {
|
|
1241
|
+
manifest: agentManifestSchema.parse({
|
|
1242
|
+
version: 1,
|
|
1243
|
+
name: "implementation-inventory",
|
|
1244
|
+
displayName: "Implementation Inventory",
|
|
1245
|
+
category: "implementation",
|
|
1246
|
+
runtime: {
|
|
1247
|
+
minVersion: "0.1.0",
|
|
1248
|
+
kind: "deterministic"
|
|
1249
|
+
},
|
|
1250
|
+
permissions: {
|
|
1251
|
+
model: false,
|
|
1252
|
+
network: false,
|
|
1253
|
+
tools: [],
|
|
1254
|
+
readPaths: ["**/*"],
|
|
1255
|
+
writePaths: []
|
|
1256
|
+
},
|
|
1257
|
+
inputs: ["workflowInputs", "repo", "changes"],
|
|
1258
|
+
outputs: ["summary", "metadata"],
|
|
1259
|
+
contextPolicy: {
|
|
1260
|
+
sections: ["workflowInputs", "repo", "changes"],
|
|
1261
|
+
minimalContext: true
|
|
1262
|
+
},
|
|
1263
|
+
catalog: {
|
|
1264
|
+
domain: "build",
|
|
1265
|
+
supportLevel: "internal",
|
|
1266
|
+
maturity: "mvp",
|
|
1267
|
+
trustScope: "official-core-only"
|
|
1268
|
+
},
|
|
1269
|
+
trust: {
|
|
1270
|
+
tier: "core",
|
|
1271
|
+
source: "official",
|
|
1272
|
+
reviewed: true
|
|
1273
|
+
}
|
|
1274
|
+
}),
|
|
1275
|
+
outputSchema: agentOutputSchema,
|
|
1276
|
+
async execute({ stateSlice }) {
|
|
1277
|
+
const implementationRequest = getWorkflowInput(stateSlice, "implementationRequest");
|
|
1278
|
+
const designRecord = getWorkflowInput(stateSlice, "designRecord");
|
|
1279
|
+
if (!implementationRequest || !designRecord) {
|
|
1280
|
+
throw new Error("implementation-proposal requires deterministic inventory inputs before proposal analysis.");
|
|
1281
|
+
}
|
|
1282
|
+
const repoRoot = stateSlice.repo?.root;
|
|
1283
|
+
const packageManager = stateSlice.repo?.packageManager || "pnpm";
|
|
1284
|
+
const candidatePaths = [
|
|
1285
|
+
...new Set([
|
|
1286
|
+
...implementationRequest.targetPaths,
|
|
1287
|
+
...designRecord.payload.interfacesImpacted,
|
|
1288
|
+
...designRecord.payload.schemaChangesNeeded,
|
|
1289
|
+
...designRecord.payload.policyChangesNeeded
|
|
1290
|
+
])
|
|
1291
|
+
];
|
|
1292
|
+
const resolvedAffectedPaths = candidatePaths.filter((pathValue) => {
|
|
1293
|
+
if (!pathValue) {
|
|
1294
|
+
return false;
|
|
1295
|
+
}
|
|
1296
|
+
if (!repoRoot) {
|
|
1297
|
+
return true;
|
|
1298
|
+
}
|
|
1299
|
+
return existsSync(join(repoRoot, pathValue));
|
|
1300
|
+
});
|
|
1301
|
+
const affectedPackages = [...new Set(resolvedAffectedPaths.map(derivePackageScope).filter((value) => Boolean(value)))];
|
|
1302
|
+
const entrypoints = [
|
|
1303
|
+
...new Set(resolvedAffectedPaths.filter((pathValue) => pathValue.endsWith("src/index.ts") || pathValue.endsWith("package.json") || pathValue.endsWith("agent.manifest.json")))
|
|
1304
|
+
];
|
|
1305
|
+
const schemaSurfaces = [...new Set(resolvedAffectedPaths.filter((pathValue) => pathValue.includes("schema")))];
|
|
1306
|
+
const policySurfaces = [
|
|
1307
|
+
...new Set(resolvedAffectedPaths.filter((pathValue) => pathValue.includes("policy") || pathValue.includes(".agentops/policy.yaml")))
|
|
1308
|
+
];
|
|
1309
|
+
const discoveredValidationCommands = collectValidationCommands(repoRoot, packageManager, affectedPackages);
|
|
1310
|
+
const normalizedRequestedCommands = implementationRequest.validationCommands.map(normalizeRequestedCommand);
|
|
1311
|
+
const allowlistedCommands = new Set(discoveredValidationCommands
|
|
1312
|
+
.filter((entry) => entry.classification === "approval_required")
|
|
1313
|
+
.map((entry) => entry.command));
|
|
1314
|
+
for (const requestedCommand of normalizedRequestedCommands) {
|
|
1315
|
+
if (!allowlistedCommands.has(requestedCommand)) {
|
|
1316
|
+
throw new Error(`Implementation request contains non-allowlisted validation command: ${requestedCommand}`);
|
|
1317
|
+
}
|
|
1318
|
+
discoveredValidationCommands.push({
|
|
1319
|
+
command: requestedCommand,
|
|
1320
|
+
source: "request",
|
|
1321
|
+
classification: "approval_required",
|
|
1322
|
+
reason: "Requested command matches a discovered allowlisted validation script."
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
const inventory = implementationInventorySchema.parse({
|
|
1326
|
+
requestedTargetPaths: implementationRequest.targetPaths,
|
|
1327
|
+
resolvedAffectedPaths,
|
|
1328
|
+
affectedPackages,
|
|
1329
|
+
entrypoints,
|
|
1330
|
+
schemaSurfaces,
|
|
1331
|
+
policySurfaces,
|
|
1332
|
+
discoveredValidationCommands
|
|
1333
|
+
});
|
|
1334
|
+
return agentOutputSchema.parse({
|
|
1335
|
+
summary: `Collected deterministic implementation inventory across ${inventory.resolvedAffectedPaths.length} path(s) and ${inventory.discoveredValidationCommands.length} validation command(s).`,
|
|
1336
|
+
findings: [],
|
|
1337
|
+
proposedActions: [],
|
|
1338
|
+
lifecycleArtifacts: [],
|
|
1339
|
+
requestedTools: [],
|
|
1340
|
+
blockedActionFlags: [],
|
|
1341
|
+
metadata: inventory
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
};
|
|
1345
|
+
const implementationPlannerAgent = {
|
|
1346
|
+
manifest: agentManifestSchema.parse({
|
|
1347
|
+
version: 1,
|
|
1348
|
+
name: "implementation-planner",
|
|
1349
|
+
displayName: "Implementation Planner",
|
|
1350
|
+
category: "implementation",
|
|
1351
|
+
runtime: {
|
|
1352
|
+
minVersion: "0.1.0",
|
|
1353
|
+
kind: "reasoning"
|
|
1354
|
+
},
|
|
1355
|
+
permissions: {
|
|
1356
|
+
model: true,
|
|
1357
|
+
network: false,
|
|
1358
|
+
tools: [],
|
|
1359
|
+
readPaths: ["**/*"],
|
|
1360
|
+
writePaths: []
|
|
1361
|
+
},
|
|
1362
|
+
inputs: ["workflowInputs", "repo", "changes", "agentResults"],
|
|
1363
|
+
outputs: ["lifecycleArtifacts"],
|
|
1364
|
+
contextPolicy: {
|
|
1365
|
+
sections: ["workflowInputs", "repo", "changes", "agentResults"],
|
|
1366
|
+
minimalContext: true
|
|
1367
|
+
},
|
|
1368
|
+
catalog: {
|
|
1369
|
+
domain: "build",
|
|
1370
|
+
supportLevel: "official",
|
|
1371
|
+
maturity: "mvp",
|
|
1372
|
+
trustScope: "official-core-only"
|
|
1373
|
+
},
|
|
1374
|
+
trust: {
|
|
1375
|
+
tier: "core",
|
|
1376
|
+
source: "official",
|
|
1377
|
+
reviewed: true
|
|
1378
|
+
}
|
|
1379
|
+
}),
|
|
1380
|
+
outputSchema: agentOutputSchema,
|
|
1381
|
+
async execute({ state, stateSlice }) {
|
|
1382
|
+
const implementationRequest = getWorkflowInput(stateSlice, "implementationRequest");
|
|
1383
|
+
const designRecord = getWorkflowInput(stateSlice, "designRecord");
|
|
1384
|
+
const requestFile = getWorkflowInput(stateSlice, "requestFile");
|
|
1385
|
+
if (!implementationRequest || !designRecord) {
|
|
1386
|
+
throw new Error("implementation-proposal requires validated implementation inputs before proposal analysis.");
|
|
1387
|
+
}
|
|
1388
|
+
const inventoryMetadata = implementationInventorySchema.safeParse(stateSlice.agentResults?.inventory?.metadata);
|
|
1389
|
+
const inventory = inventoryMetadata.success ? inventoryMetadata.data : undefined;
|
|
1390
|
+
const normalizedAffectedPaths = inventory && inventory.resolvedAffectedPaths.length > 0
|
|
1391
|
+
? inventory.resolvedAffectedPaths
|
|
1392
|
+
: [
|
|
1393
|
+
...new Set([
|
|
1394
|
+
...implementationRequest.targetPaths,
|
|
1395
|
+
...designRecord.payload.interfacesImpacted,
|
|
1396
|
+
...designRecord.payload.schemaChangesNeeded,
|
|
1397
|
+
...designRecord.payload.policyChangesNeeded
|
|
1398
|
+
])
|
|
1399
|
+
];
|
|
1400
|
+
const finalAffectedPaths = normalizedAffectedPaths.length > 0
|
|
1401
|
+
? normalizedAffectedPaths
|
|
1402
|
+
: ["Repository paths still need deterministic build-surface confirmation."];
|
|
1403
|
+
const proposedChanges = [
|
|
1404
|
+
`Prepare a bounded implementation plan for ${implementationRequest.implementationGoal}.`,
|
|
1405
|
+
...finalAffectedPaths.slice(0, 5).map((pathValue) => `Plan targeted edits for ${pathValue}.`)
|
|
1406
|
+
];
|
|
1407
|
+
const selectedCommands = inventory
|
|
1408
|
+
? inventory.discoveredValidationCommands.filter((entry) => entry.classification === "approval_required" &&
|
|
1409
|
+
(implementationRequest.validationCommands.length === 0 || entry.source === "request"))
|
|
1410
|
+
: [];
|
|
1411
|
+
const validationPlan = selectedCommands.length > 0
|
|
1412
|
+
? selectedCommands.map((entry) => `Command \`${entry.command}\` is available but approval-required before execution.`)
|
|
1413
|
+
: ["Confirm allowlisted validation commands in the next deterministic implementation slice before execution."];
|
|
1414
|
+
const approvalRequiredSteps = implementationRequest.approvalMode === "apply-capable"
|
|
1415
|
+
? [
|
|
1416
|
+
"Any future patch application requires explicit approval before execution.",
|
|
1417
|
+
"Any future build or validation execution requires approval after allowlist review."
|
|
1418
|
+
]
|
|
1419
|
+
: ["The default path remains proposal-only; any patch or build execution requires a separate approved workflow."];
|
|
1420
|
+
const risks = [
|
|
1421
|
+
...(implementationRequest.targetPaths.length === 0
|
|
1422
|
+
? ["Target paths were not supplied, so affected surfaces may still broaden after deterministic discovery."]
|
|
1423
|
+
: []),
|
|
1424
|
+
...(implementationRequest.validationCommands.length === 0
|
|
1425
|
+
? ["Validation commands are not yet specified and will need deterministic allowlist confirmation later."]
|
|
1426
|
+
: [])
|
|
1427
|
+
];
|
|
1428
|
+
const openQuestions = [
|
|
1429
|
+
...(implementationRequest.constraints.length === 0
|
|
1430
|
+
? ["Which additional implementation constraints should be captured before execution work begins?"]
|
|
1431
|
+
: []),
|
|
1432
|
+
...(designRecord.payload.policyChangesNeeded.length > 0
|
|
1433
|
+
? ["Do the policy surfaces identified in the design record require a separate approval review?"]
|
|
1434
|
+
: [])
|
|
1435
|
+
];
|
|
1436
|
+
const summary = `Implementation proposal prepared for ${implementationRequest.implementationGoal}.`;
|
|
1437
|
+
const implementationProposal = implementationArtifactSchema.parse({
|
|
1438
|
+
...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/implementation.yaml", implementationRequest.designRecordRef], designRecord.source.issueRefs),
|
|
1439
|
+
artifactKind: "implementation-proposal",
|
|
1440
|
+
lifecycleDomain: "build",
|
|
1441
|
+
workflow: {
|
|
1442
|
+
name: state.workflow,
|
|
1443
|
+
displayName: "Implementation Proposal"
|
|
1444
|
+
},
|
|
1445
|
+
payload: {
|
|
1446
|
+
designRecordRef: implementationRequest.designRecordRef,
|
|
1447
|
+
implementationGoal: implementationRequest.implementationGoal,
|
|
1448
|
+
affectedPaths: finalAffectedPaths,
|
|
1449
|
+
proposedChanges,
|
|
1450
|
+
validationPlan,
|
|
1451
|
+
approvalRequiredSteps,
|
|
1452
|
+
risks,
|
|
1453
|
+
openQuestions
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
return agentOutputSchema.parse({
|
|
1457
|
+
summary,
|
|
1458
|
+
findings: [],
|
|
1459
|
+
proposedActions: [],
|
|
1460
|
+
lifecycleArtifacts: [implementationProposal],
|
|
1461
|
+
requestedTools: [],
|
|
1462
|
+
blockedActionFlags: [],
|
|
1463
|
+
confidence: 0.76,
|
|
1464
|
+
metadata: {
|
|
1465
|
+
deterministicInputs: {
|
|
1466
|
+
targetPaths: implementationRequest.targetPaths,
|
|
1467
|
+
validationCommands: implementationRequest.validationCommands,
|
|
1468
|
+
constraints: implementationRequest.constraints,
|
|
1469
|
+
designInterfaces: designRecord.payload.interfacesImpacted,
|
|
1470
|
+
inventory: inventory ?? null
|
|
1471
|
+
},
|
|
1472
|
+
synthesizedProposal: {
|
|
1473
|
+
affectedPaths: finalAffectedPaths,
|
|
1474
|
+
approvalRequiredSteps,
|
|
1475
|
+
openQuestions
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
};
|
|
439
1481
|
const designAnalystAgent = {
|
|
440
1482
|
manifest: agentManifestSchema.parse({
|
|
441
1483
|
version: 1,
|
|
@@ -792,6 +1834,15 @@ export function createBuiltinAgentRegistry() {
|
|
|
792
1834
|
["planning-analyst", planningAnalystAgent],
|
|
793
1835
|
["design-intake", designIntakeAgent],
|
|
794
1836
|
["design-inventory", designInventoryAgent],
|
|
1837
|
+
["implementation-intake", implementationIntakeAgent],
|
|
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],
|
|
1844
|
+
["implementation-inventory", implementationInventoryAgent],
|
|
1845
|
+
["implementation-planner", implementationPlannerAgent],
|
|
795
1846
|
["design-analyst", designAnalystAgent],
|
|
796
1847
|
["code-review", codeReviewAgent],
|
|
797
1848
|
["security-audit", securityAuditAgent],
|