@apexops/cli 0.10.0-next.0 → 0.10.0-next.2
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-projection-metadata/copilot-cli-agent-tools.json +8 -0
- package/assets/client-projections/github-copilot-cli/.github/agents/apex-architect.agent.md +34 -22
- package/assets/client-projections/github-copilot-cli/.github/agents/apex-operator.agent.md +8 -4
- package/assets/client-projections/github-copilot-cli/.github/agents/apex-planner.agent.md +23 -17
- package/assets/client-projections/github-copilot-cli/.github/agents/apex-requirements.agent.md +40 -21
- package/assets/client-projections/github-copilot-cli/.github/agents/apex.agent.md +39 -7
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-agent-authoring.instructions.md +14 -0
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-artifact-contracts.instructions.md +12 -0
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-automation.instructions.md +12 -0
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-azure-yaml.instructions.md +12 -0
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-bicep.instructions.md +12 -0
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-code-quality.instructions.md +12 -0
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-context.instructions.md +12 -0
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-documentation.instructions.md +12 -0
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-governance.instructions.md +12 -0
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-instruction-authoring.instructions.md +12 -0
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-javascript.instructions.md +11 -0
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-json.instructions.md +11 -0
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-markdown.instructions.md +13 -0
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-powershell.instructions.md +11 -0
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-prompt-authoring.instructions.md +12 -0
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-python.instructions.md +11 -0
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-safe-file-edits.instructions.md +11 -0
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-safe-shell.instructions.md +11 -0
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-shell.instructions.md +11 -0
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-skill-authoring.instructions.md +13 -0
- package/assets/client-projections/github-copilot-cli/.github/instructions/apex-terraform.instructions.md +12 -0
- package/assets/client-projections/github-copilot-cli/.github/mcp.json +5 -1
- package/assets/client-projections/github-copilot-vscode/.github/agents/apex-architect.agent.md +34 -22
- package/assets/client-projections/github-copilot-vscode/.github/agents/apex-codegen.agent.md +16 -8
- package/assets/client-projections/github-copilot-vscode/.github/agents/apex-operator.agent.md +8 -4
- package/assets/client-projections/github-copilot-vscode/.github/agents/apex-planner.agent.md +23 -17
- package/assets/client-projections/github-copilot-vscode/.github/agents/apex-requirements.agent.md +40 -21
- package/assets/client-projections/github-copilot-vscode/.github/agents/apex-reviewer.agent.md +26 -12
- package/assets/client-projections/github-copilot-vscode/.github/agents/apex-validator.agent.md +21 -10
- package/assets/client-projections/github-copilot-vscode/.github/agents/apex.agent.md +39 -7
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-agent-authoring.instructions.md +14 -0
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-artifact-contracts.instructions.md +12 -0
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-automation.instructions.md +12 -0
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-azure-yaml.instructions.md +12 -0
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-bicep.instructions.md +12 -0
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-code-quality.instructions.md +12 -0
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-context.instructions.md +12 -0
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-documentation.instructions.md +12 -0
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-governance.instructions.md +12 -0
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-instruction-authoring.instructions.md +12 -0
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-javascript.instructions.md +11 -0
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-json.instructions.md +11 -0
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-markdown.instructions.md +13 -0
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-powershell.instructions.md +11 -0
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-prompt-authoring.instructions.md +12 -0
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-python.instructions.md +11 -0
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-safe-file-edits.instructions.md +11 -0
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-safe-shell.instructions.md +11 -0
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-shell.instructions.md +11 -0
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-skill-authoring.instructions.md +13 -0
- package/assets/client-projections/github-copilot-vscode/.github/instructions/apex-terraform.instructions.md +12 -0
- package/assets/client-projections/github-copilot-vscode/.vscode/mcp.json +8 -0
- package/assets/config/runtime-bundle.v1.json +7 -7
- package/assets/customizations/.github/agents/apex-architect.agent.md +34 -22
- package/assets/customizations/.github/agents/apex-codegen.agent.md +16 -8
- package/assets/customizations/.github/agents/apex-operator.agent.md +8 -4
- package/assets/customizations/.github/agents/apex-planner.agent.md +23 -17
- package/assets/customizations/.github/agents/apex-requirements.agent.md +40 -21
- package/assets/customizations/.github/agents/apex-reviewer.agent.md +26 -12
- package/assets/customizations/.github/agents/apex-validator.agent.md +21 -10
- package/assets/customizations/.github/agents/apex.agent.md +39 -7
- package/assets/customizations/.github/instructions/apex-agent-authoring.instructions.md +14 -0
- package/assets/customizations/.github/instructions/apex-artifact-contracts.instructions.md +12 -0
- package/assets/customizations/.github/instructions/apex-automation.instructions.md +12 -0
- package/assets/customizations/.github/instructions/apex-azure-yaml.instructions.md +12 -0
- package/assets/customizations/.github/instructions/apex-bicep.instructions.md +12 -0
- package/assets/customizations/.github/instructions/apex-code-quality.instructions.md +12 -0
- package/assets/customizations/.github/instructions/apex-context.instructions.md +12 -0
- package/assets/customizations/.github/instructions/apex-documentation.instructions.md +12 -0
- package/assets/customizations/.github/instructions/apex-governance.instructions.md +12 -0
- package/assets/customizations/.github/instructions/apex-instruction-authoring.instructions.md +12 -0
- package/assets/customizations/.github/instructions/apex-javascript.instructions.md +11 -0
- package/assets/customizations/.github/instructions/apex-json.instructions.md +11 -0
- package/assets/customizations/.github/instructions/apex-markdown.instructions.md +13 -0
- package/assets/customizations/.github/instructions/apex-powershell.instructions.md +11 -0
- package/assets/customizations/.github/instructions/apex-prompt-authoring.instructions.md +12 -0
- package/assets/customizations/.github/instructions/apex-python.instructions.md +11 -0
- package/assets/customizations/.github/instructions/apex-safe-file-edits.instructions.md +11 -0
- package/assets/customizations/.github/instructions/apex-safe-shell.instructions.md +11 -0
- package/assets/customizations/.github/instructions/apex-shell.instructions.md +11 -0
- package/assets/customizations/.github/instructions/apex-skill-authoring.instructions.md +13 -0
- package/assets/customizations/.github/instructions/apex-terraform.instructions.md +12 -0
- package/assets/customizations/.github/mcp.json +5 -1
- package/assets/customizations/.vscode/mcp.json +8 -0
- package/assets/customizations/manifest.json +31 -10
- package/assets/manifest.json +914 -74
- package/dist/azure-mcp-environment.d.ts +2 -0
- package/dist/azure-mcp-environment.js +7 -0
- package/dist/azure-mcp.d.ts +2 -0
- package/dist/azure-mcp.js +21 -0
- package/dist/cli.js +3 -0
- package/dist/mcp.js +22 -0
- package/dist/service.d.ts +26 -0
- package/dist/service.js +380 -7
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -5
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { azureMcpEnvironment } from "./azure-mcp-environment.js";
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
const azureMcpEntrypoint = require.resolve("@azure/mcp");
|
|
7
|
+
const child = spawn(process.execPath, [azureMcpEntrypoint, "server", "start"], {
|
|
8
|
+
stdio: "inherit",
|
|
9
|
+
env: azureMcpEnvironment(process.env),
|
|
10
|
+
});
|
|
11
|
+
child.on("error", (error) => {
|
|
12
|
+
process.stderr.write(`${error.message}\n`);
|
|
13
|
+
process.exitCode = 1;
|
|
14
|
+
});
|
|
15
|
+
child.on("exit", (code, signal) => {
|
|
16
|
+
if (signal !== null)
|
|
17
|
+
process.kill(process.pid, signal);
|
|
18
|
+
else
|
|
19
|
+
process.exitCode = code ?? 1;
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=azure-mcp.js.map
|
package/dist/cli.js
CHANGED
|
@@ -426,6 +426,9 @@ export async function execute(argv, root = process.cwd(), options = {}) {
|
|
|
426
426
|
...(typeof flags.target === "string" ? { targetScope: flags.target } : {}),
|
|
427
427
|
iacTool: flags.iac === "terraform" ? "terraform" : "bicep",
|
|
428
428
|
});
|
|
429
|
+
case "project delete":
|
|
430
|
+
confirmed(flags, "project delete");
|
|
431
|
+
return service.deleteProject(required(flags, "project"), true);
|
|
429
432
|
case "project use":
|
|
430
433
|
return service.use(required(flags, "project"), typeof flags.run === "string" ? flags.run : undefined);
|
|
431
434
|
case "project show":
|
package/dist/mcp.js
CHANGED
|
@@ -53,6 +53,18 @@ const inputSubmission = z
|
|
|
53
53
|
.min(1),
|
|
54
54
|
})
|
|
55
55
|
.strict();
|
|
56
|
+
const projectCreateInput = z
|
|
57
|
+
.object({
|
|
58
|
+
projectId: z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
|
|
59
|
+
displayName: z.string().min(1).max(256),
|
|
60
|
+
environment: z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
|
|
61
|
+
targetScope: z.string().min(1).max(1024).optional(),
|
|
62
|
+
iacTool: z.enum(["bicep", "terraform"]),
|
|
63
|
+
})
|
|
64
|
+
.strict();
|
|
65
|
+
const projectIdInput = z.object({ projectId: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/) }).strict();
|
|
66
|
+
const projectUseInput = projectIdInput.extend({ runId: z.string().min(1).optional() });
|
|
67
|
+
const projectDeleteInput = projectIdInput.extend({ confirm: z.literal(true) });
|
|
56
68
|
const normalizeOutputs = (outputs) => outputs.map(({ kind, value, summary }) => ({
|
|
57
69
|
kind,
|
|
58
70
|
value,
|
|
@@ -75,6 +87,16 @@ export function createMcpServer(service) {
|
|
|
75
87
|
inputSchema: { taskId: z.string() },
|
|
76
88
|
}, async ({ taskId }) => result(await service.taskContext(taskId)));
|
|
77
89
|
server.registerTool("recordInput", { description: "Record answers for the exact pending kernel input request", inputSchema: inputSubmission }, async (input) => result(await service.recordInput(input)));
|
|
90
|
+
server.registerTool("projectCreate", {
|
|
91
|
+
description: "Create and select a new project with its initial environment run",
|
|
92
|
+
inputSchema: projectCreateInput,
|
|
93
|
+
}, async (input) => result(await service.createProject(input)));
|
|
94
|
+
server.registerTool("projectList", { description: "List projects in the current workspace" }, async () => result({ projects: await service.listProjects() }));
|
|
95
|
+
server.registerTool("projectUse", { description: "Select an existing project and optionally one of its runs", inputSchema: projectUseInput }, async ({ projectId, runId }) => result(await service.use(projectId, runId)));
|
|
96
|
+
server.registerTool("projectDelete", {
|
|
97
|
+
description: "Delete a project and all of its run-bound state after explicit confirmation",
|
|
98
|
+
inputSchema: projectDeleteInput,
|
|
99
|
+
}, async ({ projectId, confirm }) => result(await service.deleteProject(projectId, confirm)));
|
|
78
100
|
server.registerTool("stageArtifact", {
|
|
79
101
|
inputSchema: {
|
|
80
102
|
taskId: z.string(),
|
package/dist/service.d.ts
CHANGED
|
@@ -88,6 +88,13 @@ declare const ARTIFACTS: {
|
|
|
88
88
|
}>>;
|
|
89
89
|
assumptions: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>;
|
|
90
90
|
unknowns: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>;
|
|
91
|
+
businessContext: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
92
|
+
successCriteria: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
93
|
+
nonFunctionalRequirements: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
94
|
+
securityAndCompliance: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
95
|
+
budgetAndOperations: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
96
|
+
regionalConstraints: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
97
|
+
architectureHandoff: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
91
98
|
}>];
|
|
92
99
|
readonly "workload-decision-manifest": readonly ["workload-decision-manifest", import("@sinclair/typebox").TObject<{
|
|
93
100
|
schemaVersion: import("@sinclair/typebox").TLiteral<"1.0.0">;
|
|
@@ -579,6 +586,10 @@ export declare class ApexService {
|
|
|
579
586
|
projectId: string;
|
|
580
587
|
displayName: string;
|
|
581
588
|
}>>;
|
|
589
|
+
deleteProject(projectId: ProjectId, confirmed: boolean): Promise<{
|
|
590
|
+
deleted: ProjectId;
|
|
591
|
+
selected?: Selection;
|
|
592
|
+
}>;
|
|
582
593
|
use(projectId: ProjectId, runId?: RunId): Promise<Selection>;
|
|
583
594
|
show(projectId?: ProjectId): Promise<unknown>;
|
|
584
595
|
search(term: string, limit?: number): Promise<Array<{
|
|
@@ -649,6 +660,18 @@ export declare class ApexService {
|
|
|
649
660
|
}>;
|
|
650
661
|
private acceptTaskOutputs;
|
|
651
662
|
private renderRequirementsDocument;
|
|
663
|
+
private requirementsReviewDirectory;
|
|
664
|
+
private reviewMarkdownText;
|
|
665
|
+
private materializeRequirementsReviewPackage;
|
|
666
|
+
private materializeRequirementsChallengeFindings;
|
|
667
|
+
private architectureReviewDirectory;
|
|
668
|
+
private materializeArchitectureReviewPackage;
|
|
669
|
+
private materializeArchitectureChallengeFindings;
|
|
670
|
+
private planReviewDirectory;
|
|
671
|
+
private materializePlanReviewPackage;
|
|
672
|
+
private materializePlanChallengeFindings;
|
|
673
|
+
private materializeReviewerSummary;
|
|
674
|
+
private materializeValidationReport;
|
|
652
675
|
private requirementsDocumentMarkdown;
|
|
653
676
|
private requirementsDocumentTemplateContent;
|
|
654
677
|
cancelTask(taskId: string): Promise<void>;
|
|
@@ -656,6 +679,9 @@ export declare class ApexService {
|
|
|
656
679
|
private assertReviewResolution;
|
|
657
680
|
decideGateNumber(gateNumber: number, decision: "approved" | "rejected", actor: string, options?: GateDecisionOptions): Promise<ApprovalEvidenceV1>;
|
|
658
681
|
preview(options: PreviewOptions): Promise<DeploymentPreviewV1>;
|
|
682
|
+
private operationsReviewDirectory;
|
|
683
|
+
private materializeOperationsPreviewPackage;
|
|
684
|
+
private materializeOperationsApprovalPackage;
|
|
659
685
|
currentPreview(): Promise<string>;
|
|
660
686
|
currentApproval(): Promise<ApprovalEvidenceV1>;
|
|
661
687
|
deploy(expectedPreviewHash?: string): Promise<{
|
package/dist/service.js
CHANGED
|
@@ -111,7 +111,20 @@ const REQUIREMENTS_INTAKE = [
|
|
|
111
111
|
round: "business-discovery",
|
|
112
112
|
questions: [
|
|
113
113
|
{ id: "workload", prompt: "Briefly describe the workload and its users." },
|
|
114
|
-
{
|
|
114
|
+
{
|
|
115
|
+
id: "industry",
|
|
116
|
+
prompt: "Choose the industry or domain that best fits the workload.",
|
|
117
|
+
options: [
|
|
118
|
+
"financial-services",
|
|
119
|
+
"healthcare",
|
|
120
|
+
"retail",
|
|
121
|
+
"public-sector",
|
|
122
|
+
"manufacturing",
|
|
123
|
+
"education",
|
|
124
|
+
"technology",
|
|
125
|
+
"other",
|
|
126
|
+
],
|
|
127
|
+
},
|
|
115
128
|
{
|
|
116
129
|
id: "delivery-scenario",
|
|
117
130
|
prompt: "Is this a greenfield build, migration, modernization, or extension?",
|
|
@@ -139,6 +152,7 @@ const REQUIREMENTS_INTAKE = [
|
|
|
139
152
|
{
|
|
140
153
|
id: "data-sensitivity",
|
|
141
154
|
prompt: "Choose the data classification or explicitly defer it.",
|
|
155
|
+
options: ["public", "internal", "confidential", "restricted"],
|
|
142
156
|
valueType: "data-classification",
|
|
143
157
|
},
|
|
144
158
|
{ id: "iac-preference", prompt: "Choose the preferred infrastructure tool.", options: ["bicep", "terraform"] },
|
|
@@ -151,7 +165,22 @@ const REQUIREMENTS_INTAKE = [
|
|
|
151
165
|
{ id: "prohibited-services", prompt: "List prohibited services or use 'none'." },
|
|
152
166
|
{
|
|
153
167
|
id: "service-preferences",
|
|
154
|
-
prompt: "
|
|
168
|
+
prompt: "Select preferred Azure services. Select all that apply; architecture chooses the exact combination.",
|
|
169
|
+
options: [
|
|
170
|
+
"app-service",
|
|
171
|
+
"container-apps",
|
|
172
|
+
"azure-functions",
|
|
173
|
+
"aks",
|
|
174
|
+
"azure-sql",
|
|
175
|
+
"azure-cosmos-db",
|
|
176
|
+
"storage",
|
|
177
|
+
"service-bus",
|
|
178
|
+
"event-hubs",
|
|
179
|
+
"api-management",
|
|
180
|
+
"azure-monitor",
|
|
181
|
+
"application-insights",
|
|
182
|
+
],
|
|
183
|
+
multiSelect: true,
|
|
155
184
|
},
|
|
156
185
|
{ id: "sku-preferences", prompt: "State required SKUs, tier floors, reservations, or use 'no preference'." },
|
|
157
186
|
{ id: "environment-overrides", prompt: "State environment-specific service or sizing overrides, or 'none'." },
|
|
@@ -162,22 +191,49 @@ const REQUIREMENTS_INTAKE = [
|
|
|
162
191
|
questions: [
|
|
163
192
|
{
|
|
164
193
|
id: "compliance",
|
|
165
|
-
prompt: "
|
|
194
|
+
prompt: "Select applicable compliance and regulatory scopes, or explicitly defer them.",
|
|
195
|
+
options: ["gdpr", "hipaa", "pci-dss", "iso-27001", "soc-2", "other"],
|
|
196
|
+
multiSelect: true,
|
|
166
197
|
valueType: "compliance",
|
|
167
198
|
},
|
|
168
199
|
{
|
|
169
200
|
id: "security-controls",
|
|
170
|
-
prompt: "
|
|
201
|
+
prompt: "Select required security controls. The Azure baseline recommends managed identity, private access, Key Vault, and diagnostic logging.",
|
|
202
|
+
options: [
|
|
203
|
+
"managed-identity",
|
|
204
|
+
"private-endpoints",
|
|
205
|
+
"private-dns",
|
|
206
|
+
"disable-public-network-access",
|
|
207
|
+
"key-vault",
|
|
208
|
+
"platform-managed-encryption",
|
|
209
|
+
"customer-managed-keys",
|
|
210
|
+
"diagnostic-logging",
|
|
211
|
+
],
|
|
212
|
+
multiSelect: true,
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
id: "authentication",
|
|
216
|
+
prompt: "Select the required authentication and authorization mechanisms.",
|
|
217
|
+
options: ["microsoft-entra-id", "managed-identity", "external-oidc", "api-key", "anonymous"],
|
|
218
|
+
multiSelect: true,
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: "region",
|
|
222
|
+
prompt: "Choose the Azure region, or explicitly defer the decision.",
|
|
223
|
+
options: ["swedencentral", "westeurope", "germanywestcentral"],
|
|
171
224
|
},
|
|
172
|
-
{ id: "authentication", prompt: "Describe authentication and authorization requirements." },
|
|
173
|
-
{ id: "region", prompt: "State the required Azure region or use 'deferred: <owner>'." },
|
|
174
225
|
{ id: "availability-recovery", prompt: "State availability, RTO, RPO, backup, and recovery requirements." },
|
|
175
226
|
{
|
|
176
227
|
id: "recovery",
|
|
177
228
|
prompt: "Provide RTO and RPO in whole minutes or explicitly defer them.",
|
|
178
229
|
valueType: "recovery",
|
|
179
230
|
},
|
|
180
|
-
{
|
|
231
|
+
{
|
|
232
|
+
id: "operations",
|
|
233
|
+
prompt: "Select operational capabilities required for the workload.",
|
|
234
|
+
options: ["azure-monitor", "application-insights", "log-analytics", "managed-alerts", "24x7-support"],
|
|
235
|
+
multiSelect: true,
|
|
236
|
+
},
|
|
181
237
|
],
|
|
182
238
|
},
|
|
183
239
|
];
|
|
@@ -536,6 +592,45 @@ export class ApexService {
|
|
|
536
592
|
const projects = await Promise.all(names.sort().map(async (projectId) => this.projects.getProject(projectId)));
|
|
537
593
|
return projects.map(({ projectId, displayName }) => ({ projectId, displayName }));
|
|
538
594
|
}
|
|
595
|
+
async deleteProject(projectId, confirmed) {
|
|
596
|
+
if (!confirmed)
|
|
597
|
+
throw new ApexError("APEX_USAGE", "project deletion requires explicit confirmation", EXIT_CODES.usage);
|
|
598
|
+
const projects = await this.listProjects();
|
|
599
|
+
if (!projects.some((project) => project.projectId === projectId)) {
|
|
600
|
+
throw new ApexError("APEX_NOT_FOUND", `Project not found: ${projectId}`, EXIT_CODES.notFound);
|
|
601
|
+
}
|
|
602
|
+
if (projects.length <= 1) {
|
|
603
|
+
throw new ApexError("APEX_CONFLICT", "Cannot delete the only project in a workspace", EXIT_CODES.conflict);
|
|
604
|
+
}
|
|
605
|
+
const selection = await this.selection();
|
|
606
|
+
const nextSelection = selection.projectId !== projectId
|
|
607
|
+
? undefined
|
|
608
|
+
: await (async () => {
|
|
609
|
+
const replacement = projects.find((project) => project.projectId !== projectId);
|
|
610
|
+
const replacementProjectId = replacement.projectId;
|
|
611
|
+
const runId = await this.latestRun(replacementProjectId);
|
|
612
|
+
await this.projects.getRun(replacementProjectId, runId);
|
|
613
|
+
return { projectId: replacementProjectId, runId };
|
|
614
|
+
})();
|
|
615
|
+
const projectDirectory = this.projects.projectDirectory(projectId);
|
|
616
|
+
await this.assertSafeExistingPath(join(this.root, ".apex"), projectDirectory);
|
|
617
|
+
const runDirectory = join(projectDirectory, "runs");
|
|
618
|
+
await this.assertSafeExistingPath(projectDirectory, runDirectory);
|
|
619
|
+
const runIds = await readdir(runDirectory);
|
|
620
|
+
for (const runId of runIds) {
|
|
621
|
+
const workDirectory = join(this.root, ".apex", "work", runId);
|
|
622
|
+
if (await this.pathExistsLstat(workDirectory)) {
|
|
623
|
+
await this.assertSafeExistingPath(join(this.root, ".apex"), workDirectory);
|
|
624
|
+
await rm(workDirectory, { recursive: true, force: true });
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
await rm(projectDirectory, { recursive: true, force: true });
|
|
628
|
+
if (nextSelection === undefined)
|
|
629
|
+
return { deleted: projectId };
|
|
630
|
+
await this.writeSelection(nextSelection);
|
|
631
|
+
await this.append(await this.run(nextSelection), "selection.changed", nextSelection);
|
|
632
|
+
return { deleted: projectId, selected: nextSelection };
|
|
633
|
+
}
|
|
539
634
|
async use(projectId, runId) {
|
|
540
635
|
await this.projects.getProject(projectId);
|
|
541
636
|
const selectedRun = runId ?? (await this.latestRun(projectId));
|
|
@@ -983,6 +1078,42 @@ export class ApexService {
|
|
|
983
1078
|
}),
|
|
984
1079
|
legacy,
|
|
985
1080
|
});
|
|
1081
|
+
if (descriptor.id === "requirements" && renderedDocument !== undefined) {
|
|
1082
|
+
await this.materializeRequirementsReviewPackage(run, requirementsOutput.value, renderedDocument.outputHash, events);
|
|
1083
|
+
}
|
|
1084
|
+
if (descriptor.id === "requirements-review") {
|
|
1085
|
+
await this.materializeRequirementsChallengeFindings(run, outputs[0].value);
|
|
1086
|
+
}
|
|
1087
|
+
if (descriptor.id === "architecture") {
|
|
1088
|
+
const architectureOutput = outputs.find(({ kind }) => kind === "architecture");
|
|
1089
|
+
const costOutput = outputs.find(({ kind }) => kind === "cost-estimate");
|
|
1090
|
+
const decisionsOutput = outputs.find(({ kind }) => kind === "workload-decision-manifest");
|
|
1091
|
+
if (architectureOutput === undefined || costOutput === undefined || decisionsOutput === undefined) {
|
|
1092
|
+
throw new ApexError("APEX_INTERNAL", "Architecture review package is missing an accepted output", EXIT_CODES.internal);
|
|
1093
|
+
}
|
|
1094
|
+
await this.materializeArchitectureReviewPackage(run, architectureOutput.value, costOutput.value, decisionsOutput.value, outputHashes);
|
|
1095
|
+
}
|
|
1096
|
+
if (descriptor.id === "architecture-review") {
|
|
1097
|
+
await this.materializeArchitectureChallengeFindings(run, outputs[0].value);
|
|
1098
|
+
}
|
|
1099
|
+
if (descriptor.id === "plan") {
|
|
1100
|
+
const intentOutput = outputs.find(({ kind }) => kind === "implementation-intent");
|
|
1101
|
+
const bindingOutput = outputs.find(({ kind }) => kind === "iac-binding");
|
|
1102
|
+
const inputsOutput = outputs.find(({ kind }) => kind === "environment-inputs");
|
|
1103
|
+
if (intentOutput === undefined || bindingOutput === undefined || inputsOutput === undefined) {
|
|
1104
|
+
throw new ApexError("APEX_INTERNAL", "Planner review package is missing an accepted output", EXIT_CODES.internal);
|
|
1105
|
+
}
|
|
1106
|
+
await this.materializePlanReviewPackage(run, intentOutput.value, bindingOutput.value, inputsOutput.value, outputHashes);
|
|
1107
|
+
}
|
|
1108
|
+
if (descriptor.id === "plan-review") {
|
|
1109
|
+
await this.materializePlanChallengeFindings(run, outputs[0].value);
|
|
1110
|
+
}
|
|
1111
|
+
if (descriptor.reviewSubject !== undefined) {
|
|
1112
|
+
await this.materializeReviewerSummary(run, descriptor.reviewSubject, outputs[0].value);
|
|
1113
|
+
}
|
|
1114
|
+
if (descriptor.id === "validation-bicep" || descriptor.id === "validation-terraform") {
|
|
1115
|
+
await this.materializeValidationReport(run, outputs[0].value, descriptor.id);
|
|
1116
|
+
}
|
|
986
1117
|
if (descriptor.reviewSubject !== undefined) {
|
|
987
1118
|
if (reviewBlockers.length === 0 && descriptor.gate !== undefined) {
|
|
988
1119
|
await this.openRunGate(await this.currentRun(), descriptor.gate, dependencyHash);
|
|
@@ -1002,6 +1133,172 @@ export class ApexService {
|
|
|
1002
1133
|
outputHash: await this.objects.putJson({ contentType: "text/markdown", content }),
|
|
1003
1134
|
};
|
|
1004
1135
|
}
|
|
1136
|
+
requirementsReviewDirectory(run) {
|
|
1137
|
+
return join(this.root, "agent-output", run.projectId, run.runId);
|
|
1138
|
+
}
|
|
1139
|
+
reviewMarkdownText(value) {
|
|
1140
|
+
return value.replaceAll("\\", "\\\\").replaceAll("|", "\\|").replaceAll("\r\n", "\n").replaceAll("\n", "<br>");
|
|
1141
|
+
}
|
|
1142
|
+
async materializeRequirementsReviewPackage(run, requirements, requirementsDocumentHash, events) {
|
|
1143
|
+
const directory = this.requirementsReviewDirectory(run);
|
|
1144
|
+
const document = await this.objects.getJson(requirementsDocumentHash);
|
|
1145
|
+
if (document.contentType !== "text/markdown") {
|
|
1146
|
+
throw new ApexError("APEX_INTERNAL", "Requirements review document is invalid", EXIT_CODES.internal);
|
|
1147
|
+
}
|
|
1148
|
+
const input = this.recordedRequirementsInput(events) ?? {};
|
|
1149
|
+
const text = (value) => {
|
|
1150
|
+
if (value === undefined)
|
|
1151
|
+
return "Deferred";
|
|
1152
|
+
if (typeof value === "string")
|
|
1153
|
+
return this.reviewMarkdownText(value);
|
|
1154
|
+
if (Array.isArray(value))
|
|
1155
|
+
return value.map((item) => this.reviewMarkdownText(item)).join(", ");
|
|
1156
|
+
if (value.kind === "budget")
|
|
1157
|
+
return `${value.amount} ${this.reviewMarkdownText(value.currency)} monthly`;
|
|
1158
|
+
if (value.kind === "recovery")
|
|
1159
|
+
return `RTO ${value.rtoMinutes} minutes; RPO ${value.rpoMinutes} minutes`;
|
|
1160
|
+
if (value.kind === "data-classification")
|
|
1161
|
+
return this.reviewMarkdownText(value.classification);
|
|
1162
|
+
if (value.kind === "compliance")
|
|
1163
|
+
return value.scopes.map((scope) => this.reviewMarkdownText(scope)).join(", ");
|
|
1164
|
+
return value.kind === "deferred" ? `Deferred to ${this.reviewMarkdownText(value.owner)}` : "Unknown";
|
|
1165
|
+
};
|
|
1166
|
+
const list = (value) => {
|
|
1167
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
1168
|
+
return "- None recorded.";
|
|
1169
|
+
return value.map((item) => `- ${this.reviewMarkdownText(item)}`).join("\n");
|
|
1170
|
+
};
|
|
1171
|
+
await mkdir(directory, { recursive: true });
|
|
1172
|
+
await Promise.all([
|
|
1173
|
+
atomicWriteBytes(join(directory, "01-requirements.md"), Buffer.from(document.content, "utf8")),
|
|
1174
|
+
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")),
|
|
1175
|
+
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")),
|
|
1176
|
+
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")),
|
|
1177
|
+
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")),
|
|
1178
|
+
]);
|
|
1179
|
+
}
|
|
1180
|
+
async materializeRequirementsChallengeFindings(run, review) {
|
|
1181
|
+
const findings = review.findings.length === 0
|
|
1182
|
+
? "No challenger findings remain open."
|
|
1183
|
+
: review.findings
|
|
1184
|
+
.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)}`}`)
|
|
1185
|
+
.join("\n\n");
|
|
1186
|
+
await atomicWriteBytes(join(this.requirementsReviewDirectory(run), "challenger-findings.md"), Buffer.from(`# Challenger Findings\n\n${findings}\n`, "utf8"));
|
|
1187
|
+
}
|
|
1188
|
+
architectureReviewDirectory(run) {
|
|
1189
|
+
return join(this.root, "agent-output", run.projectId, run.runId, "architecture");
|
|
1190
|
+
}
|
|
1191
|
+
async materializeArchitectureReviewPackage(run, architecture, cost, decisions, hashes) {
|
|
1192
|
+
const directory = this.architectureReviewDirectory(run);
|
|
1193
|
+
const list = (items) => items.length === 0 ? "- None." : items.map((item) => `- ${this.reviewMarkdownText(item)}`).join("\n");
|
|
1194
|
+
const table = (headers, rows) => [
|
|
1195
|
+
`| ${headers.join(" | ")} |`,
|
|
1196
|
+
`| ${headers.map(() => "---").join(" | ")} |`,
|
|
1197
|
+
...rows.map((row) => `| ${row.join(" | ")} |`),
|
|
1198
|
+
].join("\n");
|
|
1199
|
+
const costRows = cost.lineItems.map((item) => [
|
|
1200
|
+
this.reviewMarkdownText(item.service),
|
|
1201
|
+
this.reviewMarkdownText(item.sku),
|
|
1202
|
+
String(item.quantity),
|
|
1203
|
+
`${item.monthlyCost.toFixed(2)} ${this.reviewMarkdownText(cost.currency)}`,
|
|
1204
|
+
this.reviewMarkdownText(item.uncertainty.confidence),
|
|
1205
|
+
]);
|
|
1206
|
+
const skuRows = decisions.skuDecisions.map((decision) => [
|
|
1207
|
+
this.reviewMarkdownText(decision.logicalId),
|
|
1208
|
+
this.reviewMarkdownText(decision.service),
|
|
1209
|
+
this.reviewMarkdownText(decision.sku),
|
|
1210
|
+
String(decision.quantity),
|
|
1211
|
+
this.reviewMarkdownText(decision.rationale),
|
|
1212
|
+
]);
|
|
1213
|
+
await mkdir(directory, { recursive: true });
|
|
1214
|
+
await Promise.all([
|
|
1215
|
+
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")),
|
|
1216
|
+
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) => [
|
|
1217
|
+
this.reviewMarkdownText(component.id),
|
|
1218
|
+
this.reviewMarkdownText(component.service),
|
|
1219
|
+
this.reviewMarkdownText(component.purpose),
|
|
1220
|
+
component.requirementIds.map((id) => this.reviewMarkdownText(id)).join(", "),
|
|
1221
|
+
]))}\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")),
|
|
1222
|
+
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")),
|
|
1223
|
+
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")),
|
|
1224
|
+
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")),
|
|
1225
|
+
]);
|
|
1226
|
+
}
|
|
1227
|
+
async materializeArchitectureChallengeFindings(run, review) {
|
|
1228
|
+
const findings = review.findings.length === 0
|
|
1229
|
+
? "No challenger findings remain open."
|
|
1230
|
+
: review.findings
|
|
1231
|
+
.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)}`}`)
|
|
1232
|
+
.join("\n\n");
|
|
1233
|
+
await atomicWriteBytes(join(this.architectureReviewDirectory(run), "challenger-findings.md"), Buffer.from(`# Challenger Findings\n\n${findings}\n`, "utf8"));
|
|
1234
|
+
}
|
|
1235
|
+
planReviewDirectory(run) {
|
|
1236
|
+
return join(this.root, "agent-output", run.projectId, run.runId, "plan");
|
|
1237
|
+
}
|
|
1238
|
+
async materializePlanReviewPackage(run, intent, binding, inputs, hashes) {
|
|
1239
|
+
const directory = this.planReviewDirectory(run);
|
|
1240
|
+
const table = (headers, rows) => [
|
|
1241
|
+
`| ${headers.join(" | ")} |`,
|
|
1242
|
+
`| ${headers.map(() => "---").join(" | ")} |`,
|
|
1243
|
+
...rows.map((row) => `| ${row.join(" | ")} |`),
|
|
1244
|
+
].join("\n");
|
|
1245
|
+
const resourceRows = intent.resources.map((resource) => [
|
|
1246
|
+
this.reviewMarkdownText(resource.id),
|
|
1247
|
+
this.reviewMarkdownText(resource.type),
|
|
1248
|
+
this.reviewMarkdownText(resource.purpose),
|
|
1249
|
+
resource.dependsOn.map((dependency) => this.reviewMarkdownText(dependency)).join(", "),
|
|
1250
|
+
resource.controls.map((control) => this.reviewMarkdownText(control)).join(", "),
|
|
1251
|
+
]);
|
|
1252
|
+
const bindingRows = Object.entries(binding.resourceBindings).map(([id, value]) => [
|
|
1253
|
+
this.reviewMarkdownText(id),
|
|
1254
|
+
this.reviewMarkdownText(value.implementation),
|
|
1255
|
+
this.reviewMarkdownText(value.version),
|
|
1256
|
+
this.reviewMarkdownText(JSON.stringify(value.parameters)),
|
|
1257
|
+
]);
|
|
1258
|
+
const inputRows = Object.entries(inputs.inputs).map(([name, value]) => [
|
|
1259
|
+
this.reviewMarkdownText(name),
|
|
1260
|
+
value.kind === "value" ? "value" : "secret-reference",
|
|
1261
|
+
this.reviewMarkdownText(value.kind === "value" ? String(value.value) : `${value.provider}:${value.reference}`),
|
|
1262
|
+
]);
|
|
1263
|
+
await mkdir(directory, { recursive: true });
|
|
1264
|
+
await Promise.all([
|
|
1265
|
+
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")),
|
|
1266
|
+
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)
|
|
1267
|
+
.map(([kind, hash]) => `- ${this.reviewMarkdownText(kind)}: ${hash}`)
|
|
1268
|
+
.join("\n")}\n`, "utf8")),
|
|
1269
|
+
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")),
|
|
1270
|
+
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")),
|
|
1271
|
+
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")),
|
|
1272
|
+
]);
|
|
1273
|
+
}
|
|
1274
|
+
async materializePlanChallengeFindings(run, review) {
|
|
1275
|
+
const findings = review.findings.length === 0
|
|
1276
|
+
? "No challenger findings remain open."
|
|
1277
|
+
: review.findings
|
|
1278
|
+
.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)}`}`)
|
|
1279
|
+
.join("\n\n");
|
|
1280
|
+
await atomicWriteBytes(join(this.planReviewDirectory(run), "challenger-findings.md"), Buffer.from(`# Challenger Findings\n\n${findings}\n`, "utf8"));
|
|
1281
|
+
}
|
|
1282
|
+
async materializeReviewerSummary(run, subject, review) {
|
|
1283
|
+
const findings = review.findings.length === 0
|
|
1284
|
+
? "No findings remain open."
|
|
1285
|
+
: review.findings
|
|
1286
|
+
.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)}`}`)
|
|
1287
|
+
.join("\n\n");
|
|
1288
|
+
const directory = join(this.root, "agent-output", run.projectId, run.runId, "reviews");
|
|
1289
|
+
await mkdir(directory, { recursive: true });
|
|
1290
|
+
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"));
|
|
1291
|
+
}
|
|
1292
|
+
async materializeValidationReport(run, evidence, taskType) {
|
|
1293
|
+
const directory = join(this.root, "agent-output", run.projectId, run.runId, "validation");
|
|
1294
|
+
const entries = evidence.entries.length === 0
|
|
1295
|
+
? "- No validation evidence entries were produced."
|
|
1296
|
+
: evidence.entries
|
|
1297
|
+
.map(({ kind, hash, bytes, required, retention }) => `- ${this.reviewMarkdownText(kind)}: ${hash} (${bytes} bytes; ${required ? "required" : "optional"}; ${this.reviewMarkdownText(retention)})`)
|
|
1298
|
+
.join("\n");
|
|
1299
|
+
await mkdir(directory, { recursive: true });
|
|
1300
|
+
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"));
|
|
1301
|
+
}
|
|
1005
1302
|
async requirementsDocumentMarkdown(value, artifactHash) {
|
|
1006
1303
|
if (!Value.Check(RequirementsV1Schema, value)) {
|
|
1007
1304
|
throw new ApexError("APEX_INTERNAL", "Requirements document source is invalid", EXIT_CODES.internal);
|
|
@@ -1177,6 +1474,8 @@ export class ApexService {
|
|
|
1177
1474
|
...(previewHash === undefined ? {} : { previewHash }),
|
|
1178
1475
|
...(validatorIds.length === 0 ? {} : { validatorIds }),
|
|
1179
1476
|
}, gateNumber === 4 ? (events.at(-1)?.hash ?? null) : undefined);
|
|
1477
|
+
if (gateNumber === 4)
|
|
1478
|
+
await this.materializeOperationsApprovalPackage(run, approval, approvalHash);
|
|
1180
1479
|
return approval;
|
|
1181
1480
|
}
|
|
1182
1481
|
async preview(options) {
|
|
@@ -1248,6 +1547,7 @@ export class ApexService {
|
|
|
1248
1547
|
...(validation.omittedValidatorIds.length === 0 ? {} : { omittedValidatorIds: validation.omittedValidatorIds }),
|
|
1249
1548
|
...(attestationHash === undefined ? {} : { attestationHash }),
|
|
1250
1549
|
});
|
|
1550
|
+
await this.materializeOperationsPreviewPackage(run, preview, previewObjectHash);
|
|
1251
1551
|
await this.openRunGate(await this.currentRun(), 4, preview.previewHash);
|
|
1252
1552
|
return preview;
|
|
1253
1553
|
}
|
|
@@ -1295,9 +1595,64 @@ export class ApexService {
|
|
|
1295
1595
|
evidenceMode: validation.evidenceMode,
|
|
1296
1596
|
...(validation.omittedValidatorIds.length === 0 ? {} : { omittedValidatorIds: validation.omittedValidatorIds }),
|
|
1297
1597
|
});
|
|
1598
|
+
await this.materializeOperationsPreviewPackage(run, preview, previewObjectHash);
|
|
1298
1599
|
await this.openRunGate(await this.currentRun(), 4, preview.previewHash);
|
|
1299
1600
|
return preview;
|
|
1300
1601
|
}
|
|
1602
|
+
operationsReviewDirectory(run) {
|
|
1603
|
+
return join(this.root, "agent-output", run.projectId, run.runId, "operations");
|
|
1604
|
+
}
|
|
1605
|
+
async materializeOperationsPreviewPackage(run, preview, previewObjectHash) {
|
|
1606
|
+
const directory = this.operationsReviewDirectory(run);
|
|
1607
|
+
const changes = preview.changes.length === 0
|
|
1608
|
+
? "- No changes."
|
|
1609
|
+
: preview.changes
|
|
1610
|
+
.map(({ resourceId, action, material, details }) => {
|
|
1611
|
+
const detail = details === undefined ? "" : `: ${this.reviewMarkdownText(details)}`;
|
|
1612
|
+
return `- ${this.reviewMarkdownText(action)} ${this.reviewMarkdownText(resourceId)}${material ? " (material)" : ""}${detail}`;
|
|
1613
|
+
})
|
|
1614
|
+
.join("\n");
|
|
1615
|
+
const previewContent = [
|
|
1616
|
+
"# Deployment Preview",
|
|
1617
|
+
"",
|
|
1618
|
+
`- Operation: ${this.reviewMarkdownText(preview.operation)}`,
|
|
1619
|
+
`- Target: ${this.reviewMarkdownText(preview.target)}`,
|
|
1620
|
+
`- Expires: ${this.reviewMarkdownText(preview.expiresAt)}`,
|
|
1621
|
+
"- Recipient: bound by the Gate 4 approval evidence",
|
|
1622
|
+
"",
|
|
1623
|
+
"## Changes",
|
|
1624
|
+
"",
|
|
1625
|
+
changes,
|
|
1626
|
+
"",
|
|
1627
|
+
"## Blockers",
|
|
1628
|
+
"",
|
|
1629
|
+
preview.blockers.length === 0
|
|
1630
|
+
? "- None."
|
|
1631
|
+
: preview.blockers.map((blocker) => `- ${this.reviewMarkdownText(blocker)}`).join("\n"),
|
|
1632
|
+
"",
|
|
1633
|
+
].join("\n");
|
|
1634
|
+
await mkdir(directory, { recursive: true });
|
|
1635
|
+
await Promise.all([
|
|
1636
|
+
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")),
|
|
1637
|
+
atomicWriteBytes(join(directory, "deployment-preview.md"), Buffer.from(previewContent, "utf8")),
|
|
1638
|
+
]);
|
|
1639
|
+
}
|
|
1640
|
+
async materializeOperationsApprovalPackage(run, approval, approvalHash) {
|
|
1641
|
+
const content = [
|
|
1642
|
+
"# Gate 4 Approval",
|
|
1643
|
+
"",
|
|
1644
|
+
`- Decision: ${this.reviewMarkdownText(approval.decision)}`,
|
|
1645
|
+
`- Actor: ${this.reviewMarkdownText(approval.actor)}`,
|
|
1646
|
+
`- Recipient: ${this.reviewMarkdownText(approval.recipientIdentity ?? "Unavailable")}`,
|
|
1647
|
+
`- Preview hash: ${approval.previewHash ?? "Unavailable"}`,
|
|
1648
|
+
`- Approval hash: ${approvalHash}`,
|
|
1649
|
+
`- Expires: ${approval.expiresAt === undefined ? "Unavailable" : this.reviewMarkdownText(approval.expiresAt)}`,
|
|
1650
|
+
"",
|
|
1651
|
+
"This approval authorizes only the exact preview above.",
|
|
1652
|
+
"",
|
|
1653
|
+
].join("\n");
|
|
1654
|
+
await atomicWriteBytes(join(this.operationsReviewDirectory(run), "approval.md"), Buffer.from(content, "utf8"));
|
|
1655
|
+
}
|
|
1301
1656
|
async currentPreview() {
|
|
1302
1657
|
const run = await this.currentRun();
|
|
1303
1658
|
const events = await this.journal(run).replay();
|
|
@@ -2401,6 +2756,23 @@ export class ApexService {
|
|
|
2401
2756
|
return typeof text === "string" && text.length > 0 ? [`${id}: ${text}`] : [];
|
|
2402
2757
|
});
|
|
2403
2758
|
const unknowns = requirements.filter(({ status }) => status !== "confirmed").map(({ statement }) => statement);
|
|
2759
|
+
const text = (id) => {
|
|
2760
|
+
const value = this.renderInputValue(input?.[id]);
|
|
2761
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
2762
|
+
};
|
|
2763
|
+
const joinValues = (...ids) => {
|
|
2764
|
+
const values = ids.map(text).filter((value) => value !== undefined);
|
|
2765
|
+
return values.length === 0 ? undefined : values.join("; ");
|
|
2766
|
+
};
|
|
2767
|
+
const reviewFields = {
|
|
2768
|
+
businessContext: joinValues("industry", "delivery-scenario", "workload-pattern"),
|
|
2769
|
+
successCriteria: text("scale"),
|
|
2770
|
+
nonFunctionalRequirements: text("availability-recovery") ?? text("recovery"),
|
|
2771
|
+
securityAndCompliance: joinValues("security-controls", "compliance", "authentication", "data-sensitivity"),
|
|
2772
|
+
budgetAndOperations: joinValues("budget", "operations"),
|
|
2773
|
+
regionalConstraints: text("region"),
|
|
2774
|
+
architectureHandoff: text("service-preferences"),
|
|
2775
|
+
};
|
|
2404
2776
|
return {
|
|
2405
2777
|
requirements: requirements.length > 0
|
|
2406
2778
|
? requirements
|
|
@@ -2415,6 +2787,7 @@ export class ApexService {
|
|
|
2415
2787
|
],
|
|
2416
2788
|
assumptions,
|
|
2417
2789
|
unknowns,
|
|
2790
|
+
...Object.fromEntries(Object.entries(reviewFields).filter(([, value]) => value !== undefined)),
|
|
2418
2791
|
};
|
|
2419
2792
|
}
|
|
2420
2793
|
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.2";
|
|
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.2";
|
|
2
2
|
//# sourceMappingURL=version.js.map
|