@apexops/cli 0.10.0-next.1 → 0.10.0-next.3
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/assets/client-projections/github-copilot-cli/.github/agents/apex-architect.agent.md +26 -16
- package/assets/client-projections/github-copilot-cli/.github/agents/apex-operator.agent.md +7 -3
- package/assets/client-projections/github-copilot-cli/.github/agents/apex-planner.agent.md +13 -8
- package/assets/client-projections/github-copilot-cli/.github/agents/apex-requirements.agent.md +32 -22
- package/assets/client-projections/github-copilot-cli/.github/agents/apex.agent.md +15 -6
- package/assets/client-projections/github-copilot-vscode/.github/agents/apex-architect.agent.md +26 -16
- package/assets/client-projections/github-copilot-vscode/.github/agents/apex-codegen.agent.md +6 -3
- package/assets/client-projections/github-copilot-vscode/.github/agents/apex-operator.agent.md +7 -3
- package/assets/client-projections/github-copilot-vscode/.github/agents/apex-planner.agent.md +13 -8
- package/assets/client-projections/github-copilot-vscode/.github/agents/apex-requirements.agent.md +32 -22
- package/assets/client-projections/github-copilot-vscode/.github/agents/apex-reviewer.agent.md +16 -7
- package/assets/client-projections/github-copilot-vscode/.github/agents/apex-validator.agent.md +11 -5
- package/assets/client-projections/github-copilot-vscode/.github/agents/apex.agent.md +15 -6
- package/assets/config/runtime-bundle.v1.json +7 -7
- package/assets/customizations/.github/agents/apex-architect.agent.md +26 -16
- package/assets/customizations/.github/agents/apex-codegen.agent.md +6 -3
- package/assets/customizations/.github/agents/apex-operator.agent.md +7 -3
- package/assets/customizations/.github/agents/apex-planner.agent.md +13 -8
- package/assets/customizations/.github/agents/apex-requirements.agent.md +32 -22
- package/assets/customizations/.github/agents/apex-reviewer.agent.md +16 -7
- package/assets/customizations/.github/agents/apex-validator.agent.md +11 -5
- package/assets/customizations/.github/agents/apex.agent.md +15 -6
- package/assets/customizations/manifest.json +1 -1
- package/assets/manifest.json +61 -61
- package/dist/service.d.ts +22 -0
- package/dist/service.js +302 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -5
package/dist/service.js
CHANGED
|
@@ -161,8 +161,7 @@ const REQUIREMENTS_INTAKE = [
|
|
|
161
161
|
{
|
|
162
162
|
round: "service-preferences",
|
|
163
163
|
questions: [
|
|
164
|
-
{ id: "
|
|
165
|
-
{ id: "prohibited-services", prompt: "List prohibited services or use 'none'." },
|
|
164
|
+
{ id: "prohibited-services", prompt: "List prohibited services, or explicitly defer the constraint." },
|
|
166
165
|
{
|
|
167
166
|
id: "service-preferences",
|
|
168
167
|
prompt: "Select preferred Azure services. Select all that apply; architecture chooses the exact combination.",
|
|
@@ -222,10 +221,22 @@ const REQUIREMENTS_INTAKE = [
|
|
|
222
221
|
prompt: "Choose the Azure region, or explicitly defer the decision.",
|
|
223
222
|
options: ["swedencentral", "westeurope", "germanywestcentral"],
|
|
224
223
|
},
|
|
225
|
-
{
|
|
224
|
+
{
|
|
225
|
+
id: "availability-recovery",
|
|
226
|
+
prompt: "Select required availability, backup, and disaster-recovery capabilities.",
|
|
227
|
+
options: [
|
|
228
|
+
"single-region-availability",
|
|
229
|
+
"availability-zones",
|
|
230
|
+
"automated-backups",
|
|
231
|
+
"point-in-time-restore",
|
|
232
|
+
"immutable-backups",
|
|
233
|
+
"cross-region-disaster-recovery",
|
|
234
|
+
],
|
|
235
|
+
multiSelect: true,
|
|
236
|
+
},
|
|
226
237
|
{
|
|
227
238
|
id: "recovery",
|
|
228
|
-
prompt: "
|
|
239
|
+
prompt: "Set exact recovery targets: provide RTO and RPO in whole minutes, or explicitly defer them.",
|
|
229
240
|
valueType: "recovery",
|
|
230
241
|
},
|
|
231
242
|
{
|
|
@@ -1078,6 +1089,42 @@ export class ApexService {
|
|
|
1078
1089
|
}),
|
|
1079
1090
|
legacy,
|
|
1080
1091
|
});
|
|
1092
|
+
if (descriptor.id === "requirements" && renderedDocument !== undefined) {
|
|
1093
|
+
await this.materializeRequirementsReviewPackage(run, requirementsOutput.value, renderedDocument.outputHash, events);
|
|
1094
|
+
}
|
|
1095
|
+
if (descriptor.id === "requirements-review") {
|
|
1096
|
+
await this.materializeRequirementsChallengeFindings(run, outputs[0].value);
|
|
1097
|
+
}
|
|
1098
|
+
if (descriptor.id === "architecture") {
|
|
1099
|
+
const architectureOutput = outputs.find(({ kind }) => kind === "architecture");
|
|
1100
|
+
const costOutput = outputs.find(({ kind }) => kind === "cost-estimate");
|
|
1101
|
+
const decisionsOutput = outputs.find(({ kind }) => kind === "workload-decision-manifest");
|
|
1102
|
+
if (architectureOutput === undefined || costOutput === undefined || decisionsOutput === undefined) {
|
|
1103
|
+
throw new ApexError("APEX_INTERNAL", "Architecture review package is missing an accepted output", EXIT_CODES.internal);
|
|
1104
|
+
}
|
|
1105
|
+
await this.materializeArchitectureReviewPackage(run, architectureOutput.value, costOutput.value, decisionsOutput.value, outputHashes);
|
|
1106
|
+
}
|
|
1107
|
+
if (descriptor.id === "architecture-review") {
|
|
1108
|
+
await this.materializeArchitectureChallengeFindings(run, outputs[0].value);
|
|
1109
|
+
}
|
|
1110
|
+
if (descriptor.id === "plan") {
|
|
1111
|
+
const intentOutput = outputs.find(({ kind }) => kind === "implementation-intent");
|
|
1112
|
+
const bindingOutput = outputs.find(({ kind }) => kind === "iac-binding");
|
|
1113
|
+
const inputsOutput = outputs.find(({ kind }) => kind === "environment-inputs");
|
|
1114
|
+
if (intentOutput === undefined || bindingOutput === undefined || inputsOutput === undefined) {
|
|
1115
|
+
throw new ApexError("APEX_INTERNAL", "Planner review package is missing an accepted output", EXIT_CODES.internal);
|
|
1116
|
+
}
|
|
1117
|
+
await this.materializePlanReviewPackage(run, intentOutput.value, bindingOutput.value, inputsOutput.value, outputHashes);
|
|
1118
|
+
}
|
|
1119
|
+
if (descriptor.id === "plan-review") {
|
|
1120
|
+
await this.materializePlanChallengeFindings(run, outputs[0].value);
|
|
1121
|
+
}
|
|
1122
|
+
if (descriptor.reviewSubject !== undefined) {
|
|
1123
|
+
await this.materializeReviewerSummary(run, descriptor.reviewSubject, outputs[0].value);
|
|
1124
|
+
}
|
|
1125
|
+
if (descriptor.id === "validation-bicep" || descriptor.id === "validation-terraform") {
|
|
1126
|
+
await this.materializeValidationReport(run, outputs[0].value, descriptor.id);
|
|
1127
|
+
}
|
|
1081
1128
|
if (descriptor.reviewSubject !== undefined) {
|
|
1082
1129
|
if (reviewBlockers.length === 0 && descriptor.gate !== undefined) {
|
|
1083
1130
|
await this.openRunGate(await this.currentRun(), descriptor.gate, dependencyHash);
|
|
@@ -1097,6 +1144,172 @@ export class ApexService {
|
|
|
1097
1144
|
outputHash: await this.objects.putJson({ contentType: "text/markdown", content }),
|
|
1098
1145
|
};
|
|
1099
1146
|
}
|
|
1147
|
+
requirementsReviewDirectory(run) {
|
|
1148
|
+
return join(this.root, "agent-output", run.projectId, run.runId);
|
|
1149
|
+
}
|
|
1150
|
+
reviewMarkdownText(value) {
|
|
1151
|
+
return value.replaceAll("\\", "\\\\").replaceAll("|", "\\|").replaceAll("\r\n", "\n").replaceAll("\n", "<br>");
|
|
1152
|
+
}
|
|
1153
|
+
async materializeRequirementsReviewPackage(run, requirements, requirementsDocumentHash, events) {
|
|
1154
|
+
const directory = this.requirementsReviewDirectory(run);
|
|
1155
|
+
const document = await this.objects.getJson(requirementsDocumentHash);
|
|
1156
|
+
if (document.contentType !== "text/markdown") {
|
|
1157
|
+
throw new ApexError("APEX_INTERNAL", "Requirements review document is invalid", EXIT_CODES.internal);
|
|
1158
|
+
}
|
|
1159
|
+
const input = this.recordedRequirementsInput(events) ?? {};
|
|
1160
|
+
const text = (value) => {
|
|
1161
|
+
if (value === undefined)
|
|
1162
|
+
return "Deferred";
|
|
1163
|
+
if (typeof value === "string")
|
|
1164
|
+
return this.reviewMarkdownText(value);
|
|
1165
|
+
if (Array.isArray(value))
|
|
1166
|
+
return value.map((item) => this.reviewMarkdownText(item)).join(", ");
|
|
1167
|
+
if (value.kind === "budget")
|
|
1168
|
+
return `${value.amount} ${this.reviewMarkdownText(value.currency)} monthly`;
|
|
1169
|
+
if (value.kind === "recovery")
|
|
1170
|
+
return `RTO ${value.rtoMinutes} minutes; RPO ${value.rpoMinutes} minutes`;
|
|
1171
|
+
if (value.kind === "data-classification")
|
|
1172
|
+
return this.reviewMarkdownText(value.classification);
|
|
1173
|
+
if (value.kind === "compliance")
|
|
1174
|
+
return value.scopes.map((scope) => this.reviewMarkdownText(scope)).join(", ");
|
|
1175
|
+
return value.kind === "deferred" ? `Deferred to ${this.reviewMarkdownText(value.owner)}` : "Unknown";
|
|
1176
|
+
};
|
|
1177
|
+
const list = (value) => {
|
|
1178
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
1179
|
+
return "- None recorded.";
|
|
1180
|
+
return value.map((item) => `- ${this.reviewMarkdownText(item)}`).join("\n");
|
|
1181
|
+
};
|
|
1182
|
+
await mkdir(directory, { recursive: true });
|
|
1183
|
+
await Promise.all([
|
|
1184
|
+
atomicWriteBytes(join(directory, "01-requirements.md"), Buffer.from(document.content, "utf8")),
|
|
1185
|
+
atomicWriteBytes(join(directory, "README.md"), Buffer.from(`# ${this.reviewMarkdownText(run.projectId)}\n\nGenerated Gate 1 review package for run \`${this.reviewMarkdownText(run.runId)}\`. The APEX kernel state remains authoritative.\n\n- Environment: ${this.reviewMarkdownText(run.environment)}\n- Business context: ${this.reviewMarkdownText(requirements.businessContext ?? "Deferred")}\n- Requirements document hash: ${requirementsDocumentHash}\n- Review status: challenger findings pending\n`, "utf8")),
|
|
1186
|
+
atomicWriteBytes(join(directory, "service-recommendations.md"), Buffer.from(`# Service Recommendations\n\nThese are user-reviewed candidate services, not Architecture decisions.\n\n## Candidate Services\n${list(input["service-preferences"])}\n\n## Recommendation Rationale\n${this.reviewMarkdownText(requirements.architectureHandoff ?? "Architecture must evaluate the candidate services against approved requirements and current evidence.")}\n\n## Constraints\n- Retained services: ${text(input["retained-services"])}\n- Prohibited services: ${text(input["prohibited-services"])}\n- Environment overrides: ${text(input["environment-overrides"])}\n\nArchitecture must validate candidates against approved requirements, governance, and current evidence.\n`, "utf8")),
|
|
1187
|
+
atomicWriteBytes(join(directory, "sku-preferences.md"), Buffer.from(`# SKU Preferences\n\nUser constraints only. Architecture owns SKU selection.\n\n- Preference: ${text(input["sku-preferences"])}\n- Budget posture: ${text(input.budget)}\n- Scale: ${text(input.scale)}\n`, "utf8")),
|
|
1188
|
+
atomicWriteBytes(join(directory, "challenger-findings.md"), Buffer.from("# Challenger Findings\n\nRequirements challenger review is pending. Gate 1 cannot be approved until the reviewer completes this document.\n", "utf8")),
|
|
1189
|
+
]);
|
|
1190
|
+
}
|
|
1191
|
+
async materializeRequirementsChallengeFindings(run, review) {
|
|
1192
|
+
const findings = review.findings.length === 0
|
|
1193
|
+
? "No challenger findings remain open."
|
|
1194
|
+
: review.findings
|
|
1195
|
+
.map(({ id, severity, title, detail, resolution }) => `## ${this.reviewMarkdownText(id)}: ${this.reviewMarkdownText(title)}\n\n- Severity: ${this.reviewMarkdownText(severity)}\n- Finding: ${this.reviewMarkdownText(detail)}${resolution === undefined ? "" : `\n- Resolution: ${this.reviewMarkdownText(resolution)}`}`)
|
|
1196
|
+
.join("\n\n");
|
|
1197
|
+
await atomicWriteBytes(join(this.requirementsReviewDirectory(run), "challenger-findings.md"), Buffer.from(`# Challenger Findings\n\n${findings}\n`, "utf8"));
|
|
1198
|
+
}
|
|
1199
|
+
architectureReviewDirectory(run) {
|
|
1200
|
+
return join(this.root, "agent-output", run.projectId, run.runId, "architecture");
|
|
1201
|
+
}
|
|
1202
|
+
async materializeArchitectureReviewPackage(run, architecture, cost, decisions, hashes) {
|
|
1203
|
+
const directory = this.architectureReviewDirectory(run);
|
|
1204
|
+
const list = (items) => items.length === 0 ? "- None." : items.map((item) => `- ${this.reviewMarkdownText(item)}`).join("\n");
|
|
1205
|
+
const table = (headers, rows) => [
|
|
1206
|
+
`| ${headers.join(" | ")} |`,
|
|
1207
|
+
`| ${headers.map(() => "---").join(" | ")} |`,
|
|
1208
|
+
...rows.map((row) => `| ${row.join(" | ")} |`),
|
|
1209
|
+
].join("\n");
|
|
1210
|
+
const costRows = cost.lineItems.map((item) => [
|
|
1211
|
+
this.reviewMarkdownText(item.service),
|
|
1212
|
+
this.reviewMarkdownText(item.sku),
|
|
1213
|
+
String(item.quantity),
|
|
1214
|
+
`${item.monthlyCost.toFixed(2)} ${this.reviewMarkdownText(cost.currency)}`,
|
|
1215
|
+
this.reviewMarkdownText(item.uncertainty.confidence),
|
|
1216
|
+
]);
|
|
1217
|
+
const skuRows = decisions.skuDecisions.map((decision) => [
|
|
1218
|
+
this.reviewMarkdownText(decision.logicalId),
|
|
1219
|
+
this.reviewMarkdownText(decision.service),
|
|
1220
|
+
this.reviewMarkdownText(decision.sku),
|
|
1221
|
+
String(decision.quantity),
|
|
1222
|
+
this.reviewMarkdownText(decision.rationale),
|
|
1223
|
+
]);
|
|
1224
|
+
await mkdir(directory, { recursive: true });
|
|
1225
|
+
await Promise.all([
|
|
1226
|
+
atomicWriteBytes(join(directory, "README.md"), Buffer.from(`# ${this.reviewMarkdownText(architecture.title)}\n\nGenerated Gate 2 review package for run \`${this.reviewMarkdownText(run.runId)}\`. The APEX kernel state remains authoritative.\n\n- Architecture hash: ${hashes.architecture}\n- Cost estimate hash: ${hashes["cost-estimate"]}\n- Decision manifest hash: ${hashes["workload-decision-manifest"]}\n- Review status: challenger findings pending\n`, "utf8")),
|
|
1227
|
+
atomicWriteBytes(join(directory, "architecture-assessment.md"), Buffer.from(`# Architecture Assessment\n\n## Summary\n\n${this.reviewMarkdownText(architecture.summary)}\n\n## Components\n\n${table(["ID", "Service", "Purpose", "Requirements"], architecture.components.map((component) => [
|
|
1228
|
+
this.reviewMarkdownText(component.id),
|
|
1229
|
+
this.reviewMarkdownText(component.service),
|
|
1230
|
+
this.reviewMarkdownText(component.purpose),
|
|
1231
|
+
component.requirementIds.map((id) => this.reviewMarkdownText(id)).join(", "),
|
|
1232
|
+
]))}\n\n## Five WAF Pillars\n\n### Security\n\n${list(architecture.decisions)}\n\n### Reliability\n\n${list(decisions.sloDecisions.map((decision) => `${decision.logicalId}: ${decision.availabilityPercent}% availability, RTO ${decision.rtoMinutes}m, RPO ${decision.rpoMinutes}m`))}\n\n### Performance Efficiency\n\n- Review component capacity and scale decisions in the SKU comparison.\n\n### Cost Optimization\n\n- Review the current evidence-backed cost estimate.\n\n### Operational Excellence\n\n${list(architecture.risks)}\n\n## Alternatives And Trade-offs\n\nUser-confirmed decisions:\n${list(architecture.decisions)}\n\n## Risks\n\n${list(architecture.risks)}\n`, "utf8")),
|
|
1233
|
+
atomicWriteBytes(join(directory, "cost-estimate.md"), Buffer.from(`# Cost Estimate\n\n- Pricing date: ${this.reviewMarkdownText(cost.pricingDate)}\n- Monthly total: ${cost.totalMonthlyCost.toFixed(2)} ${this.reviewMarkdownText(cost.currency)}\n\n## Line Items\n\n${table(["Service", "SKU", "Quantity", "Monthly", "Confidence"], costRows)}\n\n## Evidence Appendix\n\n${cost.lineItems.map((item) => `- ${this.reviewMarkdownText(item.service)} / ${this.reviewMarkdownText(item.sku)}: ${this.reviewMarkdownText(item.source.provider)} — ${this.reviewMarkdownText(item.source.uri)} — retrieved ${this.reviewMarkdownText(item.source.retrievedAt)}; uncertainty ${this.reviewMarkdownText(item.uncertainty.basis)}`).join("\n")}\n\n## Assumptions\n\n${list(cost.assumptions)}\n`, "utf8")),
|
|
1234
|
+
atomicWriteBytes(join(directory, "sku-comparison.md"), Buffer.from(`# SKU Comparison\n\n${table(["Logical ID", "Service", "Selected SKU", "Quantity", "Rationale"], skuRows)}\n\nThese are user-confirmed Architecture decisions. Alternatives and rejected options must be recorded in the architecture assessment before Gate 2 approval.\n`, "utf8")),
|
|
1235
|
+
atomicWriteBytes(join(directory, "challenger-findings.md"), Buffer.from("# Challenger Findings\n\nArchitecture challenger review is pending. Gate 2 cannot be approved until the reviewer completes this document.\n", "utf8")),
|
|
1236
|
+
]);
|
|
1237
|
+
}
|
|
1238
|
+
async materializeArchitectureChallengeFindings(run, review) {
|
|
1239
|
+
const findings = review.findings.length === 0
|
|
1240
|
+
? "No challenger findings remain open."
|
|
1241
|
+
: review.findings
|
|
1242
|
+
.map(({ id, severity, title, detail, resolution }) => `## ${this.reviewMarkdownText(id)}: ${this.reviewMarkdownText(title)}\n\n- Severity: ${this.reviewMarkdownText(severity)}\n- Finding: ${this.reviewMarkdownText(detail)}${resolution === undefined ? "" : `\n- Resolution: ${this.reviewMarkdownText(resolution)}`}`)
|
|
1243
|
+
.join("\n\n");
|
|
1244
|
+
await atomicWriteBytes(join(this.architectureReviewDirectory(run), "challenger-findings.md"), Buffer.from(`# Challenger Findings\n\n${findings}\n`, "utf8"));
|
|
1245
|
+
}
|
|
1246
|
+
planReviewDirectory(run) {
|
|
1247
|
+
return join(this.root, "agent-output", run.projectId, run.runId, "plan");
|
|
1248
|
+
}
|
|
1249
|
+
async materializePlanReviewPackage(run, intent, binding, inputs, hashes) {
|
|
1250
|
+
const directory = this.planReviewDirectory(run);
|
|
1251
|
+
const table = (headers, rows) => [
|
|
1252
|
+
`| ${headers.join(" | ")} |`,
|
|
1253
|
+
`| ${headers.map(() => "---").join(" | ")} |`,
|
|
1254
|
+
...rows.map((row) => `| ${row.join(" | ")} |`),
|
|
1255
|
+
].join("\n");
|
|
1256
|
+
const resourceRows = intent.resources.map((resource) => [
|
|
1257
|
+
this.reviewMarkdownText(resource.id),
|
|
1258
|
+
this.reviewMarkdownText(resource.type),
|
|
1259
|
+
this.reviewMarkdownText(resource.purpose),
|
|
1260
|
+
resource.dependsOn.map((dependency) => this.reviewMarkdownText(dependency)).join(", "),
|
|
1261
|
+
resource.controls.map((control) => this.reviewMarkdownText(control)).join(", "),
|
|
1262
|
+
]);
|
|
1263
|
+
const bindingRows = Object.entries(binding.resourceBindings).map(([id, value]) => [
|
|
1264
|
+
this.reviewMarkdownText(id),
|
|
1265
|
+
this.reviewMarkdownText(value.implementation),
|
|
1266
|
+
this.reviewMarkdownText(value.version),
|
|
1267
|
+
this.reviewMarkdownText(JSON.stringify(value.parameters)),
|
|
1268
|
+
]);
|
|
1269
|
+
const inputRows = Object.entries(inputs.inputs).map(([name, value]) => [
|
|
1270
|
+
this.reviewMarkdownText(name),
|
|
1271
|
+
value.kind === "value" ? "value" : "secret-reference",
|
|
1272
|
+
this.reviewMarkdownText(value.kind === "value" ? String(value.value) : `${value.provider}:${value.reference}`),
|
|
1273
|
+
]);
|
|
1274
|
+
await mkdir(directory, { recursive: true });
|
|
1275
|
+
await Promise.all([
|
|
1276
|
+
atomicWriteBytes(join(directory, "README.md"), Buffer.from(`# Implementation Plan\n\nGenerated Gate 3 review package for run \`${this.reviewMarkdownText(run.runId)}\`. The APEX kernel state remains authoritative.\n\n- Intent hash: ${hashes["implementation-intent"]}\n- Binding hash: ${hashes["iac-binding"]}\n- Environment input hash: ${hashes["environment-inputs"]}\n- Review status: challenger findings pending\n`, "utf8")),
|
|
1277
|
+
atomicWriteBytes(join(directory, "implementation-plan.md"), Buffer.from(`# Implementation Plan\n\n## Logical Resources\n\n${table(["ID", "Type", "Purpose", "Depends On", "Controls"], resourceRows)}\n\n## Outputs\n\n${intent.outputs.map((output) => `- ${this.reviewMarkdownText(output)}`).join("\n")}\n\n## Source Artifacts\n\n${Object.entries(intent.sourceHashes)
|
|
1278
|
+
.map(([kind, hash]) => `- ${this.reviewMarkdownText(kind)}: ${hash}`)
|
|
1279
|
+
.join("\n")}\n`, "utf8")),
|
|
1280
|
+
atomicWriteBytes(join(directory, "iac-binding.md"), Buffer.from(`# IaC Binding\n\n- Track: ${this.reviewMarkdownText(binding.track)}\n- Intent hash: ${binding.intentHash}\n\n${table(["Logical ID", "Implementation", "Version", "Parameters"], bindingRows)}\n`, "utf8")),
|
|
1281
|
+
atomicWriteBytes(join(directory, "environment-inputs.md"), Buffer.from(`# Environment Inputs\n\n- Environment: ${this.reviewMarkdownText(inputs.environment)}\n\n${table(["Name", "Kind", "Reference or Value"], inputRows)}\n`, "utf8")),
|
|
1282
|
+
atomicWriteBytes(join(directory, "challenger-findings.md"), Buffer.from("# Challenger Findings\n\nImplementation-plan challenger review is pending. Gate 3 cannot be approved until the reviewer completes this document.\n", "utf8")),
|
|
1283
|
+
]);
|
|
1284
|
+
}
|
|
1285
|
+
async materializePlanChallengeFindings(run, review) {
|
|
1286
|
+
const findings = review.findings.length === 0
|
|
1287
|
+
? "No challenger findings remain open."
|
|
1288
|
+
: review.findings
|
|
1289
|
+
.map(({ id, severity, title, detail, resolution }) => `## ${this.reviewMarkdownText(id)}: ${this.reviewMarkdownText(title)}\n\n- Severity: ${this.reviewMarkdownText(severity)}\n- Finding: ${this.reviewMarkdownText(detail)}${resolution === undefined ? "" : `\n- Resolution: ${this.reviewMarkdownText(resolution)}`}`)
|
|
1290
|
+
.join("\n\n");
|
|
1291
|
+
await atomicWriteBytes(join(this.planReviewDirectory(run), "challenger-findings.md"), Buffer.from(`# Challenger Findings\n\n${findings}\n`, "utf8"));
|
|
1292
|
+
}
|
|
1293
|
+
async materializeReviewerSummary(run, subject, review) {
|
|
1294
|
+
const findings = review.findings.length === 0
|
|
1295
|
+
? "No findings remain open."
|
|
1296
|
+
: review.findings
|
|
1297
|
+
.map(({ id, severity, disposition, title, detail, evidenceRefs, resolution }) => `## ${this.reviewMarkdownText(id)}: ${this.reviewMarkdownText(title)}\n\n- Severity: ${this.reviewMarkdownText(severity)}\n- Disposition: ${this.reviewMarkdownText(disposition)}\n- Finding: ${this.reviewMarkdownText(detail)}\n- Evidence: ${evidenceRefs.map((reference) => this.reviewMarkdownText(reference)).join(", ") || "None"}${resolution === undefined ? "" : `\n- Resolution: ${this.reviewMarkdownText(resolution)}`}`)
|
|
1298
|
+
.join("\n\n");
|
|
1299
|
+
const directory = join(this.root, "agent-output", run.projectId, run.runId, "reviews");
|
|
1300
|
+
await mkdir(directory, { recursive: true });
|
|
1301
|
+
await atomicWriteBytes(join(directory, `${subject}-findings.md`), Buffer.from(`# ${this.reviewMarkdownText(subject)} Challenger Findings\n\n- Reviewed artifact kind: ${this.reviewMarkdownText(review.subjectKind)}\n- Review subject hash: ${review.subjectHash}\n- Reviewed at: ${this.reviewMarkdownText(review.reviewedAt)}\n\n${findings}\n`, "utf8"));
|
|
1302
|
+
}
|
|
1303
|
+
async materializeValidationReport(run, evidence, taskType) {
|
|
1304
|
+
const directory = join(this.root, "agent-output", run.projectId, run.runId, "validation");
|
|
1305
|
+
const entries = evidence.entries.length === 0
|
|
1306
|
+
? "- No validation evidence entries were produced."
|
|
1307
|
+
: evidence.entries
|
|
1308
|
+
.map(({ kind, hash, bytes, required, retention }) => `- ${this.reviewMarkdownText(kind)}: ${hash} (${bytes} bytes; ${required ? "required" : "optional"}; ${this.reviewMarkdownText(retention)})`)
|
|
1309
|
+
.join("\n");
|
|
1310
|
+
await mkdir(directory, { recursive: true });
|
|
1311
|
+
await atomicWriteBytes(join(directory, "validation-report.md"), Buffer.from(`# Validation Report\n\n- Task: ${this.reviewMarkdownText(taskType)}\n- Track: ${this.reviewMarkdownText(run.iacTool)}\n- Created: ${this.reviewMarkdownText(evidence.createdAt)}\n- Verdict: accepted deterministic validation evidence\n\n## Validator Evidence\n\n${entries}\n\nThe Validator reports evidence only. Artifact repair, risk acceptance, and gate decisions remain with their authorized owners.\n`, "utf8"));
|
|
1312
|
+
}
|
|
1100
1313
|
async requirementsDocumentMarkdown(value, artifactHash) {
|
|
1101
1314
|
if (!Value.Check(RequirementsV1Schema, value)) {
|
|
1102
1315
|
throw new ApexError("APEX_INTERNAL", "Requirements document source is invalid", EXIT_CODES.internal);
|
|
@@ -1272,6 +1485,8 @@ export class ApexService {
|
|
|
1272
1485
|
...(previewHash === undefined ? {} : { previewHash }),
|
|
1273
1486
|
...(validatorIds.length === 0 ? {} : { validatorIds }),
|
|
1274
1487
|
}, gateNumber === 4 ? (events.at(-1)?.hash ?? null) : undefined);
|
|
1488
|
+
if (gateNumber === 4)
|
|
1489
|
+
await this.materializeOperationsApprovalPackage(run, approval, approvalHash);
|
|
1275
1490
|
return approval;
|
|
1276
1491
|
}
|
|
1277
1492
|
async preview(options) {
|
|
@@ -1343,6 +1558,7 @@ export class ApexService {
|
|
|
1343
1558
|
...(validation.omittedValidatorIds.length === 0 ? {} : { omittedValidatorIds: validation.omittedValidatorIds }),
|
|
1344
1559
|
...(attestationHash === undefined ? {} : { attestationHash }),
|
|
1345
1560
|
});
|
|
1561
|
+
await this.materializeOperationsPreviewPackage(run, preview, previewObjectHash);
|
|
1346
1562
|
await this.openRunGate(await this.currentRun(), 4, preview.previewHash);
|
|
1347
1563
|
return preview;
|
|
1348
1564
|
}
|
|
@@ -1390,9 +1606,64 @@ export class ApexService {
|
|
|
1390
1606
|
evidenceMode: validation.evidenceMode,
|
|
1391
1607
|
...(validation.omittedValidatorIds.length === 0 ? {} : { omittedValidatorIds: validation.omittedValidatorIds }),
|
|
1392
1608
|
});
|
|
1609
|
+
await this.materializeOperationsPreviewPackage(run, preview, previewObjectHash);
|
|
1393
1610
|
await this.openRunGate(await this.currentRun(), 4, preview.previewHash);
|
|
1394
1611
|
return preview;
|
|
1395
1612
|
}
|
|
1613
|
+
operationsReviewDirectory(run) {
|
|
1614
|
+
return join(this.root, "agent-output", run.projectId, run.runId, "operations");
|
|
1615
|
+
}
|
|
1616
|
+
async materializeOperationsPreviewPackage(run, preview, previewObjectHash) {
|
|
1617
|
+
const directory = this.operationsReviewDirectory(run);
|
|
1618
|
+
const changes = preview.changes.length === 0
|
|
1619
|
+
? "- No changes."
|
|
1620
|
+
: preview.changes
|
|
1621
|
+
.map(({ resourceId, action, material, details }) => {
|
|
1622
|
+
const detail = details === undefined ? "" : `: ${this.reviewMarkdownText(details)}`;
|
|
1623
|
+
return `- ${this.reviewMarkdownText(action)} ${this.reviewMarkdownText(resourceId)}${material ? " (material)" : ""}${detail}`;
|
|
1624
|
+
})
|
|
1625
|
+
.join("\n");
|
|
1626
|
+
const previewContent = [
|
|
1627
|
+
"# Deployment Preview",
|
|
1628
|
+
"",
|
|
1629
|
+
`- Operation: ${this.reviewMarkdownText(preview.operation)}`,
|
|
1630
|
+
`- Target: ${this.reviewMarkdownText(preview.target)}`,
|
|
1631
|
+
`- Expires: ${this.reviewMarkdownText(preview.expiresAt)}`,
|
|
1632
|
+
"- Recipient: bound by the Gate 4 approval evidence",
|
|
1633
|
+
"",
|
|
1634
|
+
"## Changes",
|
|
1635
|
+
"",
|
|
1636
|
+
changes,
|
|
1637
|
+
"",
|
|
1638
|
+
"## Blockers",
|
|
1639
|
+
"",
|
|
1640
|
+
preview.blockers.length === 0
|
|
1641
|
+
? "- None."
|
|
1642
|
+
: preview.blockers.map((blocker) => `- ${this.reviewMarkdownText(blocker)}`).join("\n"),
|
|
1643
|
+
"",
|
|
1644
|
+
].join("\n");
|
|
1645
|
+
await mkdir(directory, { recursive: true });
|
|
1646
|
+
await Promise.all([
|
|
1647
|
+
atomicWriteBytes(join(directory, "README.md"), Buffer.from(`# Operations Review\n\nGenerated from exact kernel evidence for run \`${this.reviewMarkdownText(run.runId)}\`. The APEX kernel state remains authoritative.\n\n- Preview hash: ${preview.previewHash}\n- Preview object hash: ${previewObjectHash}\n- Gate 4 status: pending human terminal approval\n`, "utf8")),
|
|
1648
|
+
atomicWriteBytes(join(directory, "deployment-preview.md"), Buffer.from(previewContent, "utf8")),
|
|
1649
|
+
]);
|
|
1650
|
+
}
|
|
1651
|
+
async materializeOperationsApprovalPackage(run, approval, approvalHash) {
|
|
1652
|
+
const content = [
|
|
1653
|
+
"# Gate 4 Approval",
|
|
1654
|
+
"",
|
|
1655
|
+
`- Decision: ${this.reviewMarkdownText(approval.decision)}`,
|
|
1656
|
+
`- Actor: ${this.reviewMarkdownText(approval.actor)}`,
|
|
1657
|
+
`- Recipient: ${this.reviewMarkdownText(approval.recipientIdentity ?? "Unavailable")}`,
|
|
1658
|
+
`- Preview hash: ${approval.previewHash ?? "Unavailable"}`,
|
|
1659
|
+
`- Approval hash: ${approvalHash}`,
|
|
1660
|
+
`- Expires: ${approval.expiresAt === undefined ? "Unavailable" : this.reviewMarkdownText(approval.expiresAt)}`,
|
|
1661
|
+
"",
|
|
1662
|
+
"This approval authorizes only the exact preview above.",
|
|
1663
|
+
"",
|
|
1664
|
+
].join("\n");
|
|
1665
|
+
await atomicWriteBytes(join(this.operationsReviewDirectory(run), "approval.md"), Buffer.from(content, "utf8"));
|
|
1666
|
+
}
|
|
1396
1667
|
async currentPreview() {
|
|
1397
1668
|
const run = await this.currentRun();
|
|
1398
1669
|
const events = await this.journal(run).replay();
|
|
@@ -2201,6 +2472,15 @@ export class ApexService {
|
|
|
2201
2472
|
return this.inputRequest(event, run.ownerEpoch);
|
|
2202
2473
|
}
|
|
2203
2474
|
requirementsIntakeQuestions(round, events) {
|
|
2475
|
+
if (round.round === "service-preferences") {
|
|
2476
|
+
const scenario = this.recordedRequirementsInput(events)?.["delivery-scenario"];
|
|
2477
|
+
if (scenario !== "migration" && scenario !== "modernization" && scenario !== "extension")
|
|
2478
|
+
return round.questions;
|
|
2479
|
+
return [
|
|
2480
|
+
{ id: "retained-services", prompt: "List Azure services or integrations that must be retained." },
|
|
2481
|
+
...round.questions,
|
|
2482
|
+
];
|
|
2483
|
+
}
|
|
2204
2484
|
if (round.round !== "workload-pattern")
|
|
2205
2485
|
return round.questions;
|
|
2206
2486
|
const scenario = this.recordedRequirementsInput(events)?.["delivery-scenario"];
|
|
@@ -2496,6 +2776,23 @@ export class ApexService {
|
|
|
2496
2776
|
return typeof text === "string" && text.length > 0 ? [`${id}: ${text}`] : [];
|
|
2497
2777
|
});
|
|
2498
2778
|
const unknowns = requirements.filter(({ status }) => status !== "confirmed").map(({ statement }) => statement);
|
|
2779
|
+
const text = (id) => {
|
|
2780
|
+
const value = this.renderInputValue(input?.[id]);
|
|
2781
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
2782
|
+
};
|
|
2783
|
+
const joinValues = (...ids) => {
|
|
2784
|
+
const values = ids.map(text).filter((value) => value !== undefined);
|
|
2785
|
+
return values.length === 0 ? undefined : values.join("; ");
|
|
2786
|
+
};
|
|
2787
|
+
const reviewFields = {
|
|
2788
|
+
businessContext: joinValues("industry", "delivery-scenario", "workload-pattern"),
|
|
2789
|
+
successCriteria: text("scale"),
|
|
2790
|
+
nonFunctionalRequirements: text("availability-recovery") ?? text("recovery"),
|
|
2791
|
+
securityAndCompliance: joinValues("security-controls", "compliance", "authentication", "data-sensitivity"),
|
|
2792
|
+
budgetAndOperations: joinValues("budget", "operations"),
|
|
2793
|
+
regionalConstraints: text("region"),
|
|
2794
|
+
architectureHandoff: text("service-preferences"),
|
|
2795
|
+
};
|
|
2499
2796
|
return {
|
|
2500
2797
|
requirements: requirements.length > 0
|
|
2501
2798
|
? requirements
|
|
@@ -2510,6 +2807,7 @@ export class ApexService {
|
|
|
2510
2807
|
],
|
|
2511
2808
|
assumptions,
|
|
2512
2809
|
unknowns,
|
|
2810
|
+
...Object.fromEntries(Object.entries(reviewFields).filter(([, value]) => value !== undefined)),
|
|
2513
2811
|
};
|
|
2514
2812
|
}
|
|
2515
2813
|
renderInputValue(value) {
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const APEX_VERSION: "0.10.0-next.
|
|
1
|
+
export declare const APEX_VERSION: "0.10.0-next.3";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const APEX_VERSION = "0.10.0-next.
|
|
1
|
+
export const APEX_VERSION = "0.10.0-next.3";
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apexops/cli",
|
|
3
|
-
"version": "0.10.0-next.
|
|
3
|
+
"version": "0.10.0-next.3",
|
|
4
4
|
"description": "Portable no-shell CLI and MCP facade for APEX",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"assets"
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@apexops/capabilities": "0.10.0-next.
|
|
36
|
-
"@apexops/contracts": "0.10.0-next.
|
|
37
|
-
"@apexops/kernel": "0.10.0-next.
|
|
38
|
-
"@apexops/renderers": "0.10.0-next.
|
|
35
|
+
"@apexops/capabilities": "0.10.0-next.3",
|
|
36
|
+
"@apexops/contracts": "0.10.0-next.3",
|
|
37
|
+
"@apexops/kernel": "0.10.0-next.3",
|
|
38
|
+
"@apexops/renderers": "0.10.0-next.3",
|
|
39
39
|
"@azure/mcp": "3.0.0-beta.37",
|
|
40
40
|
"@hono/node-server": "2.0.11",
|
|
41
41
|
"@modelcontextprotocol/sdk": "1.29.0",
|