@build-astron-co/nimbus 0.4.2 → 0.4.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/dist/src/agent/compaction-agent.js +24 -12
- package/dist/src/agent/context-manager.js +2 -1
- package/dist/src/agent/expand-files.js +2 -1
- package/dist/src/agent/loop.js +71 -33
- package/dist/src/agent/permissions.js +4 -2
- package/dist/src/agent/system-prompt.js +34 -17
- package/dist/src/app.js +1 -1
- package/dist/src/auth/keychain.js +8 -4
- package/dist/src/auth/store.js +70 -107
- package/dist/src/cli/init.js +35 -19
- package/dist/src/cli/run.js +18 -10
- package/dist/src/cli/serve.js +4 -2
- package/dist/src/cli.js +52 -11
- package/dist/src/commands/alias.js +5 -3
- package/dist/src/commands/audit/index.js +2 -1
- package/dist/src/commands/aws-terraform.js +36 -18
- package/dist/src/commands/completions.js +1 -1
- package/dist/src/commands/config.js +3 -2
- package/dist/src/commands/connect-github.js +92 -0
- package/dist/src/commands/cost/index.js +3 -2
- package/dist/src/commands/deploy.js +15 -10
- package/dist/src/commands/doctor.js +6 -3
- package/dist/src/commands/drift/index.js +2 -1
- package/dist/src/commands/export.js +5 -3
- package/dist/src/commands/generate-terraform.js +110 -2
- package/dist/src/commands/import.js +3 -3
- package/dist/src/commands/incident.js +10 -5
- package/dist/src/commands/login.js +8 -93
- package/dist/src/commands/logs.js +16 -8
- package/dist/src/commands/onboarding.js +6 -4
- package/dist/src/commands/pipeline.js +6 -3
- package/dist/src/commands/plugin.js +3 -2
- package/dist/src/commands/profile.js +27 -14
- package/dist/src/commands/questionnaire.js +1 -1
- package/dist/src/commands/rollback.js +3 -2
- package/dist/src/commands/rollout.js +5 -3
- package/dist/src/commands/runbook.js +17 -10
- package/dist/src/commands/schedule.js +10 -5
- package/dist/src/commands/status.js +2 -1
- package/dist/src/commands/team-context.js +12 -7
- package/dist/src/commands/template.js +1 -1
- package/dist/src/commands/tf/index.js +6 -3
- package/dist/src/commands/version.js +6 -3
- package/dist/src/commands/watch.js +6 -3
- package/dist/src/compat/sqlite.js +5 -3
- package/dist/src/config/mode-store.js +2 -1
- package/dist/src/config/profiles.js +4 -2
- package/dist/src/config/types.js +2 -1
- package/dist/src/engine/executor.js +8 -4
- package/dist/src/engine/planner.js +9 -5
- package/dist/src/llm/providers/anthropic.js +6 -3
- package/dist/src/llm/providers/ollama.js +1 -1
- package/dist/src/llm/router.js +22 -7
- package/dist/src/sessions/manager.js +6 -3
- package/dist/src/sharing/viewer.js +2 -1
- package/dist/src/tools/file-ops.js +1 -2
- package/dist/src/tools/schemas/devops.js +197 -108
- package/dist/src/tools/schemas/standard.js +1 -1
- package/dist/src/ui/App.js +25 -13
- package/dist/src/ui/FileDiffModal.js +22 -11
- package/dist/src/ui/HelpModal.js +2 -1
- package/dist/src/ui/InputBox.js +6 -3
- package/dist/src/ui/MessageList.js +40 -20
- package/dist/src/ui/TerminalPane.js +2 -1
- package/dist/src/ui/ToolCallDisplay.js +12 -6
- package/dist/src/ui/TreePane.js +2 -1
- package/dist/src/ui/ink/index.js +37 -21
- package/dist/src/watcher/index.js +8 -4
- package/package.json +3 -5
- package/src/__tests__/alias.test.ts +0 -133
- package/src/__tests__/app.test.ts +0 -76
- package/src/__tests__/audit.test.ts +0 -877
- package/src/__tests__/circuit-breaker.test.ts +0 -116
- package/src/__tests__/cli-run.test.ts +0 -351
- package/src/__tests__/compat-sqlite.test.ts +0 -68
- package/src/__tests__/context-manager.test.ts +0 -632
- package/src/__tests__/context.test.ts +0 -242
- package/src/__tests__/devops-terminal-gaps.test.ts +0 -718
- package/src/__tests__/doctor.test.ts +0 -48
- package/src/__tests__/enterprise.test.ts +0 -401
- package/src/__tests__/export.test.ts +0 -236
- package/src/__tests__/gap-11-18-20.test.ts +0 -958
- package/src/__tests__/generator.test.ts +0 -433
- package/src/__tests__/helm-streaming.test.ts +0 -127
- package/src/__tests__/hooks.test.ts +0 -582
- package/src/__tests__/incident.test.ts +0 -179
- package/src/__tests__/init.test.ts +0 -487
- package/src/__tests__/intent-parser.test.ts +0 -229
- package/src/__tests__/llm-router.test.ts +0 -209
- package/src/__tests__/logs.test.ts +0 -107
- package/src/__tests__/loop-errors.test.ts +0 -244
- package/src/__tests__/lsp.test.ts +0 -293
- package/src/__tests__/modes.test.ts +0 -336
- package/src/__tests__/perf-optimizations.test.ts +0 -847
- package/src/__tests__/permissions.test.ts +0 -338
- package/src/__tests__/pipeline.test.ts +0 -50
- package/src/__tests__/polish-phase3.test.ts +0 -340
- package/src/__tests__/profile.test.ts +0 -237
- package/src/__tests__/rollback.test.ts +0 -83
- package/src/__tests__/runbook.test.ts +0 -219
- package/src/__tests__/schedule.test.ts +0 -206
- package/src/__tests__/serve.test.ts +0 -275
- package/src/__tests__/sessions.test.ts +0 -322
- package/src/__tests__/sharing.test.ts +0 -340
- package/src/__tests__/snapshots.test.ts +0 -581
- package/src/__tests__/standalone-migration.test.ts +0 -199
- package/src/__tests__/state-db.test.ts +0 -334
- package/src/__tests__/status.test.ts +0 -158
- package/src/__tests__/stream-with-tools.test.ts +0 -778
- package/src/__tests__/subagents.test.ts +0 -176
- package/src/__tests__/system-prompt.test.ts +0 -248
- package/src/__tests__/terminal-gap-v2.test.ts +0 -395
- package/src/__tests__/terminal-parity.test.ts +0 -393
- package/src/__tests__/tf-apply.test.ts +0 -187
- package/src/__tests__/tool-converter.test.ts +0 -256
- package/src/__tests__/tool-schemas.test.ts +0 -602
- package/src/__tests__/tools.test.ts +0 -144
- package/src/__tests__/version-json.test.ts +0 -184
- package/src/__tests__/version.test.ts +0 -49
- package/src/__tests__/watch.test.ts +0 -129
- package/src/agent/compaction-agent.ts +0 -266
- package/src/agent/context-manager.ts +0 -499
- package/src/agent/context.ts +0 -427
- package/src/agent/deploy-preview.ts +0 -487
- package/src/agent/expand-files.ts +0 -108
- package/src/agent/index.ts +0 -68
- package/src/agent/loop.ts +0 -1998
- package/src/agent/modes.ts +0 -429
- package/src/agent/permissions.ts +0 -513
- package/src/agent/subagents/base.ts +0 -116
- package/src/agent/subagents/cost.ts +0 -51
- package/src/agent/subagents/explore.ts +0 -42
- package/src/agent/subagents/general.ts +0 -54
- package/src/agent/subagents/index.ts +0 -102
- package/src/agent/subagents/infra.ts +0 -59
- package/src/agent/subagents/security.ts +0 -69
- package/src/agent/system-prompt.ts +0 -990
- package/src/app.ts +0 -180
- package/src/audit/activity-log.ts +0 -290
- package/src/audit/compliance-checker.ts +0 -540
- package/src/audit/cost-tracker.ts +0 -318
- package/src/audit/index.ts +0 -23
- package/src/audit/security-scanner.ts +0 -641
- package/src/auth/guard.ts +0 -75
- package/src/auth/index.ts +0 -56
- package/src/auth/keychain.ts +0 -82
- package/src/auth/oauth.ts +0 -465
- package/src/auth/providers.ts +0 -470
- package/src/auth/sso.ts +0 -113
- package/src/auth/store.ts +0 -505
- package/src/auth/types.ts +0 -187
- package/src/build.ts +0 -141
- package/src/cli/index.ts +0 -16
- package/src/cli/init.ts +0 -1227
- package/src/cli/openapi-spec.ts +0 -356
- package/src/cli/run.ts +0 -628
- package/src/cli/serve-auth.ts +0 -80
- package/src/cli/serve.ts +0 -539
- package/src/cli/web.ts +0 -71
- package/src/cli.ts +0 -1728
- package/src/clients/core-engine-client.ts +0 -227
- package/src/clients/enterprise-client.ts +0 -334
- package/src/clients/generator-client.ts +0 -351
- package/src/clients/git-client.ts +0 -627
- package/src/clients/github-client.ts +0 -410
- package/src/clients/helm-client.ts +0 -504
- package/src/clients/index.ts +0 -80
- package/src/clients/k8s-client.ts +0 -497
- package/src/clients/llm-client.ts +0 -161
- package/src/clients/rest-client.ts +0 -130
- package/src/clients/service-discovery.ts +0 -38
- package/src/clients/terraform-client.ts +0 -482
- package/src/clients/tools-client.ts +0 -1843
- package/src/clients/ws-client.ts +0 -115
- package/src/commands/alias.ts +0 -100
- package/src/commands/analyze/index.ts +0 -352
- package/src/commands/apply/helm.ts +0 -473
- package/src/commands/apply/index.ts +0 -213
- package/src/commands/apply/k8s.ts +0 -454
- package/src/commands/apply/terraform.ts +0 -582
- package/src/commands/ask.ts +0 -167
- package/src/commands/audit/index.ts +0 -357
- package/src/commands/auth-cloud.ts +0 -407
- package/src/commands/auth-list.ts +0 -134
- package/src/commands/auth-profile.ts +0 -121
- package/src/commands/auth-refresh.ts +0 -187
- package/src/commands/auth-status.ts +0 -141
- package/src/commands/aws/ec2.ts +0 -501
- package/src/commands/aws/iam.ts +0 -397
- package/src/commands/aws/index.ts +0 -133
- package/src/commands/aws/lambda.ts +0 -396
- package/src/commands/aws/rds.ts +0 -439
- package/src/commands/aws/s3.ts +0 -439
- package/src/commands/aws/vpc.ts +0 -393
- package/src/commands/aws-discover.ts +0 -542
- package/src/commands/aws-terraform.ts +0 -755
- package/src/commands/azure/aks.ts +0 -376
- package/src/commands/azure/functions.ts +0 -253
- package/src/commands/azure/index.ts +0 -116
- package/src/commands/azure/storage.ts +0 -478
- package/src/commands/azure/vm.ts +0 -355
- package/src/commands/billing/index.ts +0 -256
- package/src/commands/chat.ts +0 -320
- package/src/commands/completions.ts +0 -268
- package/src/commands/config.ts +0 -372
- package/src/commands/cost/cloud-cost-estimator.ts +0 -266
- package/src/commands/cost/estimator.ts +0 -79
- package/src/commands/cost/index.ts +0 -810
- package/src/commands/cost/parsers/terraform.ts +0 -273
- package/src/commands/cost/parsers/types.ts +0 -25
- package/src/commands/cost/pricing/aws.ts +0 -544
- package/src/commands/cost/pricing/azure.ts +0 -499
- package/src/commands/cost/pricing/gcp.ts +0 -396
- package/src/commands/cost/pricing/index.ts +0 -40
- package/src/commands/demo.ts +0 -250
- package/src/commands/deploy.ts +0 -260
- package/src/commands/doctor.ts +0 -1386
- package/src/commands/drift/index.ts +0 -787
- package/src/commands/explain.ts +0 -277
- package/src/commands/export.ts +0 -146
- package/src/commands/feedback.ts +0 -389
- package/src/commands/fix.ts +0 -324
- package/src/commands/fs/index.ts +0 -402
- package/src/commands/gcp/compute.ts +0 -325
- package/src/commands/gcp/functions.ts +0 -271
- package/src/commands/gcp/gke.ts +0 -438
- package/src/commands/gcp/iam.ts +0 -344
- package/src/commands/gcp/index.ts +0 -129
- package/src/commands/gcp/storage.ts +0 -284
- package/src/commands/generate-helm.ts +0 -1249
- package/src/commands/generate-k8s.ts +0 -1508
- package/src/commands/generate-terraform.ts +0 -1202
- package/src/commands/gh/index.ts +0 -863
- package/src/commands/git/index.ts +0 -1343
- package/src/commands/helm/index.ts +0 -1126
- package/src/commands/help.ts +0 -715
- package/src/commands/history.ts +0 -149
- package/src/commands/import.ts +0 -868
- package/src/commands/incident.ts +0 -166
- package/src/commands/index.ts +0 -367
- package/src/commands/init.ts +0 -1051
- package/src/commands/k8s/index.ts +0 -1137
- package/src/commands/login.ts +0 -716
- package/src/commands/logout.ts +0 -83
- package/src/commands/logs.ts +0 -167
- package/src/commands/onboarding.ts +0 -405
- package/src/commands/pipeline.ts +0 -186
- package/src/commands/plan/display.ts +0 -279
- package/src/commands/plan/index.ts +0 -599
- package/src/commands/plugin.ts +0 -398
- package/src/commands/preview.ts +0 -452
- package/src/commands/profile.ts +0 -342
- package/src/commands/questionnaire.ts +0 -1172
- package/src/commands/resume.ts +0 -47
- package/src/commands/rollback.ts +0 -315
- package/src/commands/rollout.ts +0 -88
- package/src/commands/runbook.ts +0 -346
- package/src/commands/schedule.ts +0 -236
- package/src/commands/status.ts +0 -252
- package/src/commands/team/index.ts +0 -346
- package/src/commands/team-context.ts +0 -220
- package/src/commands/template.ts +0 -233
- package/src/commands/tf/index.ts +0 -1093
- package/src/commands/upgrade.ts +0 -609
- package/src/commands/usage/index.ts +0 -134
- package/src/commands/version.ts +0 -174
- package/src/commands/watch.ts +0 -153
- package/src/compat/index.ts +0 -2
- package/src/compat/runtime.ts +0 -12
- package/src/compat/sqlite.ts +0 -177
- package/src/config/index.ts +0 -17
- package/src/config/manager.ts +0 -530
- package/src/config/mode-store.ts +0 -62
- package/src/config/profiles.ts +0 -84
- package/src/config/safety-policy.ts +0 -358
- package/src/config/schema.ts +0 -125
- package/src/config/types.ts +0 -609
- package/src/config/workspace-state.ts +0 -53
- package/src/context/context-db.ts +0 -199
- package/src/demo/index.ts +0 -349
- package/src/demo/scenarios/full-journey.ts +0 -229
- package/src/demo/scenarios/getting-started.ts +0 -127
- package/src/demo/scenarios/helm-release.ts +0 -341
- package/src/demo/scenarios/k8s-deployment.ts +0 -194
- package/src/demo/scenarios/terraform-vpc.ts +0 -170
- package/src/demo/types.ts +0 -92
- package/src/engine/cost-estimator.ts +0 -480
- package/src/engine/diagram-generator.ts +0 -256
- package/src/engine/drift-detector.ts +0 -902
- package/src/engine/executor.ts +0 -1066
- package/src/engine/index.ts +0 -76
- package/src/engine/orchestrator.ts +0 -636
- package/src/engine/planner.ts +0 -787
- package/src/engine/safety.ts +0 -743
- package/src/engine/verifier.ts +0 -770
- package/src/enterprise/audit.ts +0 -348
- package/src/enterprise/auth.ts +0 -270
- package/src/enterprise/billing.ts +0 -822
- package/src/enterprise/index.ts +0 -17
- package/src/enterprise/teams.ts +0 -443
- package/src/generator/best-practices.ts +0 -1608
- package/src/generator/helm.ts +0 -630
- package/src/generator/index.ts +0 -37
- package/src/generator/intent-parser.ts +0 -514
- package/src/generator/kubernetes.ts +0 -976
- package/src/generator/terraform.ts +0 -1875
- package/src/history/index.ts +0 -8
- package/src/history/manager.ts +0 -250
- package/src/history/types.ts +0 -34
- package/src/hooks/config.ts +0 -432
- package/src/hooks/engine.ts +0 -392
- package/src/hooks/index.ts +0 -4
- package/src/llm/auth-bridge.ts +0 -198
- package/src/llm/circuit-breaker.ts +0 -140
- package/src/llm/config-loader.ts +0 -201
- package/src/llm/cost-calculator.ts +0 -171
- package/src/llm/index.ts +0 -8
- package/src/llm/model-aliases.ts +0 -115
- package/src/llm/provider-registry.ts +0 -63
- package/src/llm/providers/anthropic.ts +0 -462
- package/src/llm/providers/bedrock.ts +0 -477
- package/src/llm/providers/google.ts +0 -405
- package/src/llm/providers/ollama.ts +0 -767
- package/src/llm/providers/openai-compatible.ts +0 -340
- package/src/llm/providers/openai.ts +0 -328
- package/src/llm/providers/openrouter.ts +0 -338
- package/src/llm/router.ts +0 -1104
- package/src/llm/types.ts +0 -232
- package/src/lsp/client.ts +0 -298
- package/src/lsp/languages.ts +0 -119
- package/src/lsp/manager.ts +0 -294
- package/src/mcp/client.ts +0 -402
- package/src/mcp/index.ts +0 -5
- package/src/mcp/manager.ts +0 -133
- package/src/nimbus.ts +0 -234
- package/src/plugins/index.ts +0 -27
- package/src/plugins/loader.ts +0 -334
- package/src/plugins/manager.ts +0 -376
- package/src/plugins/types.ts +0 -284
- package/src/scanners/cicd-scanner.ts +0 -258
- package/src/scanners/cloud-scanner.ts +0 -466
- package/src/scanners/framework-scanner.ts +0 -469
- package/src/scanners/iac-scanner.ts +0 -388
- package/src/scanners/index.ts +0 -539
- package/src/scanners/language-scanner.ts +0 -276
- package/src/scanners/package-manager-scanner.ts +0 -277
- package/src/scanners/types.ts +0 -172
- package/src/sessions/manager.ts +0 -472
- package/src/sessions/types.ts +0 -44
- package/src/sharing/sync.ts +0 -300
- package/src/sharing/viewer.ts +0 -163
- package/src/snapshots/index.ts +0 -2
- package/src/snapshots/manager.ts +0 -530
- package/src/state/artifacts.ts +0 -147
- package/src/state/audit.ts +0 -137
- package/src/state/billing.ts +0 -240
- package/src/state/checkpoints.ts +0 -117
- package/src/state/config.ts +0 -67
- package/src/state/conversations.ts +0 -14
- package/src/state/credentials.ts +0 -154
- package/src/state/db.ts +0 -58
- package/src/state/index.ts +0 -26
- package/src/state/messages.ts +0 -115
- package/src/state/projects.ts +0 -123
- package/src/state/schema.ts +0 -236
- package/src/state/sessions.ts +0 -147
- package/src/state/teams.ts +0 -200
- package/src/telemetry.ts +0 -108
- package/src/tools/aws-ops.ts +0 -952
- package/src/tools/azure-ops.ts +0 -579
- package/src/tools/file-ops.ts +0 -615
- package/src/tools/gcp-ops.ts +0 -625
- package/src/tools/git-ops.ts +0 -773
- package/src/tools/github-ops.ts +0 -799
- package/src/tools/helm-ops.ts +0 -943
- package/src/tools/index.ts +0 -17
- package/src/tools/k8s-ops.ts +0 -819
- package/src/tools/schemas/converter.ts +0 -184
- package/src/tools/schemas/devops.ts +0 -3502
- package/src/tools/schemas/index.ts +0 -73
- package/src/tools/schemas/standard.ts +0 -1148
- package/src/tools/schemas/types.ts +0 -735
- package/src/tools/spawn-exec.ts +0 -148
- package/src/tools/terraform-ops.ts +0 -862
- package/src/types/ambient.d.ts +0 -193
- package/src/types/config.ts +0 -83
- package/src/types/drift.ts +0 -116
- package/src/types/enterprise.ts +0 -335
- package/src/types/index.ts +0 -20
- package/src/types/plan.ts +0 -44
- package/src/types/request.ts +0 -65
- package/src/types/response.ts +0 -54
- package/src/types/service.ts +0 -51
- package/src/ui/App.tsx +0 -2114
- package/src/ui/DeployPreview.tsx +0 -174
- package/src/ui/FileDiffModal.tsx +0 -162
- package/src/ui/Header.tsx +0 -131
- package/src/ui/HelpModal.tsx +0 -57
- package/src/ui/InputBox.tsx +0 -503
- package/src/ui/MessageList.tsx +0 -1032
- package/src/ui/PermissionPrompt.tsx +0 -163
- package/src/ui/StatusBar.tsx +0 -277
- package/src/ui/TerminalPane.tsx +0 -84
- package/src/ui/ToolCallDisplay.tsx +0 -643
- package/src/ui/TreePane.tsx +0 -132
- package/src/ui/chat-ui.ts +0 -850
- package/src/ui/index.ts +0 -33
- package/src/ui/ink/index.ts +0 -1444
- package/src/ui/streaming.ts +0 -176
- package/src/ui/theme.ts +0 -104
- package/src/ui/types.ts +0 -75
- package/src/utils/analytics.ts +0 -72
- package/src/utils/cost-warning.ts +0 -27
- package/src/utils/env.ts +0 -46
- package/src/utils/errors.ts +0 -69
- package/src/utils/event-bus.ts +0 -38
- package/src/utils/index.ts +0 -24
- package/src/utils/logger.ts +0 -171
- package/src/utils/rate-limiter.ts +0 -121
- package/src/utils/service-auth.ts +0 -49
- package/src/utils/validation.ts +0 -53
- package/src/version.ts +0 -4
- package/src/watcher/index.ts +0 -214
- package/src/wizard/approval.ts +0 -383
- package/src/wizard/index.ts +0 -25
- package/src/wizard/prompts.ts +0 -338
- package/src/wizard/types.ts +0 -172
- package/src/wizard/ui.ts +0 -556
- package/src/wizard/wizard.ts +0 -304
- package/tsconfig.json +0 -24
package/src/engine/executor.ts
DELETED
|
@@ -1,1066 +0,0 @@
|
|
|
1
|
-
import { logger } from '../utils';
|
|
2
|
-
import type {
|
|
3
|
-
AgentPlan,
|
|
4
|
-
PlanStep,
|
|
5
|
-
ExecutionResult,
|
|
6
|
-
ExecutionLog,
|
|
7
|
-
ExecutionArtifact,
|
|
8
|
-
} from './orchestrator';
|
|
9
|
-
import { TerraformOperations } from '../tools/terraform-ops';
|
|
10
|
-
import { FileSystemOperations } from '../tools/file-ops';
|
|
11
|
-
import { saveCheckpoint, getLatestCheckpoint, deleteCheckpoints } from '../state/checkpoints';
|
|
12
|
-
|
|
13
|
-
// ==========================================
|
|
14
|
-
// Error Classes
|
|
15
|
-
// ==========================================
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Non-retryable errors for deterministic validation failures.
|
|
19
|
-
* These will not be retried by executeWithRetry.
|
|
20
|
-
*/
|
|
21
|
-
class NonRetryableError extends Error {
|
|
22
|
-
constructor(message: string) {
|
|
23
|
-
super(message);
|
|
24
|
-
this.name = 'NonRetryableError';
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Structured error for executor step failures.
|
|
30
|
-
* Propagates actionable context instead of returning mock data.
|
|
31
|
-
*/
|
|
32
|
-
class ExecutionError extends Error {
|
|
33
|
-
public readonly step: string;
|
|
34
|
-
public readonly cause: unknown;
|
|
35
|
-
|
|
36
|
-
constructor(message: string, opts: { step: string; cause: unknown }) {
|
|
37
|
-
super(message);
|
|
38
|
-
this.name = 'ExecutionError';
|
|
39
|
-
this.step = opts.step;
|
|
40
|
-
this.cause = opts.cause;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ==========================================
|
|
45
|
-
// Generator/Best-Practices helpers (inline — no HTTP)
|
|
46
|
-
// ==========================================
|
|
47
|
-
|
|
48
|
-
/** Thin adapter for generator functionality used by executor steps. */
|
|
49
|
-
interface GeneratorAdapter {
|
|
50
|
-
renderTemplate(
|
|
51
|
-
templateId: string,
|
|
52
|
-
variables: Record<string, unknown>
|
|
53
|
-
): Promise<{ rendered_content: string }>;
|
|
54
|
-
analyzeBestPractices(
|
|
55
|
-
component: string,
|
|
56
|
-
config: Record<string, unknown>
|
|
57
|
-
): Promise<{ summary?: { total_violations?: number } }>;
|
|
58
|
-
applyAutofixes(
|
|
59
|
-
component: string,
|
|
60
|
-
config: Record<string, unknown>
|
|
61
|
-
): Promise<{ fixes_applied?: number }>;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Local in-process generator adapter.
|
|
66
|
-
* Generates simple template content without HTTP round-trips.
|
|
67
|
-
*/
|
|
68
|
-
class LocalGeneratorAdapter implements GeneratorAdapter {
|
|
69
|
-
async renderTemplate(
|
|
70
|
-
templateId: string,
|
|
71
|
-
variables: Record<string, unknown>
|
|
72
|
-
): Promise<{ rendered_content: string }> {
|
|
73
|
-
// Simple inline template rendering for common terraform templates
|
|
74
|
-
const parts = templateId.split('/'); // e.g. terraform/aws/vpc
|
|
75
|
-
const component = parts[2] || parts[parts.length - 1] || 'unknown';
|
|
76
|
-
const provider = parts[1] || 'aws';
|
|
77
|
-
|
|
78
|
-
const rendered_content =
|
|
79
|
-
`# Generated ${component.toUpperCase()} configuration\n` +
|
|
80
|
-
`# Provider: ${provider}\n` +
|
|
81
|
-
`# Variables: ${JSON.stringify(variables, null, 2)}\n\n` +
|
|
82
|
-
`resource "${provider}_${component}" "main" {\n` +
|
|
83
|
-
` # Configuration generated by Nimbus\n` +
|
|
84
|
-
` tags = {\n` +
|
|
85
|
-
` ManagedBy = "nimbus"\n` +
|
|
86
|
-
` }\n` +
|
|
87
|
-
`}\n`;
|
|
88
|
-
|
|
89
|
-
return { rendered_content };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async analyzeBestPractices(
|
|
93
|
-
_component: string,
|
|
94
|
-
_config: Record<string, unknown>
|
|
95
|
-
): Promise<{ summary?: { total_violations?: number } }> {
|
|
96
|
-
// Delegate to the embedded BestPracticesEngine when available
|
|
97
|
-
try {
|
|
98
|
-
const { BestPracticesEngine } = await import('../generator/best-practices');
|
|
99
|
-
const engine = new BestPracticesEngine();
|
|
100
|
-
const report = engine.analyze(_component, _config);
|
|
101
|
-
return { summary: { total_violations: report.summary.violations_found } };
|
|
102
|
-
} catch {
|
|
103
|
-
return { summary: { total_violations: 0 } };
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async applyAutofixes(
|
|
108
|
-
_component: string,
|
|
109
|
-
_config: Record<string, unknown>
|
|
110
|
-
): Promise<{ fixes_applied?: number }> {
|
|
111
|
-
try {
|
|
112
|
-
const { BestPracticesEngine } = await import('../generator/best-practices');
|
|
113
|
-
const engine = new BestPracticesEngine();
|
|
114
|
-
const result = engine.autofix(_component, _config);
|
|
115
|
-
return { fixes_applied: result.applied_fixes.length };
|
|
116
|
-
} catch {
|
|
117
|
-
return { fixes_applied: 0 };
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// ==========================================
|
|
123
|
-
// Executor
|
|
124
|
-
// ==========================================
|
|
125
|
-
|
|
126
|
-
export class Executor {
|
|
127
|
-
private logs: Map<string, ExecutionLog[]>;
|
|
128
|
-
private artifacts: Map<string, ExecutionArtifact[]>;
|
|
129
|
-
private generatorAdapter: GeneratorAdapter;
|
|
130
|
-
private terraformOps: TerraformOperations;
|
|
131
|
-
private fsOps: FileSystemOperations;
|
|
132
|
-
|
|
133
|
-
constructor() {
|
|
134
|
-
this.logs = new Map();
|
|
135
|
-
this.artifacts = new Map();
|
|
136
|
-
this.generatorAdapter = new LocalGeneratorAdapter();
|
|
137
|
-
this.terraformOps = new TerraformOperations();
|
|
138
|
-
this.fsOps = new FileSystemOperations();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Create a TerraformOperations instance bound to a specific working directory.
|
|
143
|
-
*/
|
|
144
|
-
private tfOpsForDir(workDir: string): TerraformOperations {
|
|
145
|
-
return new TerraformOperations(workDir);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Execute a plan
|
|
150
|
-
*/
|
|
151
|
-
async executePlan(plan: AgentPlan): Promise<ExecutionResult[]> {
|
|
152
|
-
logger.info(`Starting execution of plan: ${plan.id}`);
|
|
153
|
-
|
|
154
|
-
const results: ExecutionResult[] = [];
|
|
155
|
-
const executedSteps = new Set<string>();
|
|
156
|
-
|
|
157
|
-
// Check for existing checkpoint to enable resume
|
|
158
|
-
try {
|
|
159
|
-
const checkpoint = await getLatestCheckpoint(plan.id);
|
|
160
|
-
if (checkpoint) {
|
|
161
|
-
logger.info(`Found checkpoint for plan ${plan.id} at step ${checkpoint.step}, resuming`);
|
|
162
|
-
|
|
163
|
-
// Mark previously completed steps as executed
|
|
164
|
-
const completedStepIds = (checkpoint.state.completedStepIds as string[]) || [];
|
|
165
|
-
for (const stepId of completedStepIds) {
|
|
166
|
-
executedSteps.add(stepId);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Restore any previous results from checkpoint
|
|
170
|
-
const previousResults = (checkpoint.state.results as ExecutionResult[]) || [];
|
|
171
|
-
results.push(...previousResults);
|
|
172
|
-
}
|
|
173
|
-
} catch (error) {
|
|
174
|
-
logger.warn('Could not check for checkpoint, starting fresh', error);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Execute steps respecting dependencies
|
|
178
|
-
while (executedSteps.size < plan.steps.length) {
|
|
179
|
-
const readySteps = this.getReadySteps(plan.steps, executedSteps);
|
|
180
|
-
|
|
181
|
-
if (readySteps.length === 0) {
|
|
182
|
-
logger.error('No steps ready for execution, possible circular dependency');
|
|
183
|
-
break;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Execute ready steps in parallel with retry
|
|
187
|
-
const stepResults = await Promise.allSettled(
|
|
188
|
-
readySteps.map(step => this.executeWithRetry(plan.id, step))
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
// Process results
|
|
192
|
-
for (let i = 0; i < stepResults.length; i++) {
|
|
193
|
-
const stepResult = stepResults[i];
|
|
194
|
-
const step = readySteps[i];
|
|
195
|
-
|
|
196
|
-
if (stepResult.status === 'fulfilled') {
|
|
197
|
-
results.push(stepResult.value);
|
|
198
|
-
executedSteps.add(step.id);
|
|
199
|
-
|
|
200
|
-
// Save checkpoint after each successful step
|
|
201
|
-
try {
|
|
202
|
-
const checkpointId = `ckpt_${plan.id}_${step.order}`;
|
|
203
|
-
await saveCheckpoint(checkpointId, plan.id, step.order, {
|
|
204
|
-
completedStepIds: Array.from(executedSteps),
|
|
205
|
-
results: results.map(r => ({
|
|
206
|
-
id: r.id,
|
|
207
|
-
plan_id: r.plan_id,
|
|
208
|
-
step_id: r.step_id,
|
|
209
|
-
status: r.status,
|
|
210
|
-
started_at: r.started_at,
|
|
211
|
-
completed_at: r.completed_at,
|
|
212
|
-
duration: r.duration,
|
|
213
|
-
outputs: r.outputs,
|
|
214
|
-
})),
|
|
215
|
-
lastCompletedStep: step.order,
|
|
216
|
-
});
|
|
217
|
-
} catch (checkpointError) {
|
|
218
|
-
logger.warn(`Failed to save checkpoint for step ${step.id}`, checkpointError);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (stepResult.value.status === 'failure') {
|
|
222
|
-
logger.error(`Step ${step.id} failed, stopping execution`);
|
|
223
|
-
return results;
|
|
224
|
-
}
|
|
225
|
-
} else {
|
|
226
|
-
logger.error(`Step ${step.id} execution error`, stepResult.reason);
|
|
227
|
-
results.push({
|
|
228
|
-
id: this.generateResultId(),
|
|
229
|
-
plan_id: plan.id,
|
|
230
|
-
step_id: step.id,
|
|
231
|
-
status: 'failure',
|
|
232
|
-
started_at: new Date(),
|
|
233
|
-
completed_at: new Date(),
|
|
234
|
-
duration: 0,
|
|
235
|
-
error: {
|
|
236
|
-
code: 'EXECUTION_ERROR',
|
|
237
|
-
message: stepResult.reason.message,
|
|
238
|
-
stack_trace: stepResult.reason.stack,
|
|
239
|
-
},
|
|
240
|
-
});
|
|
241
|
-
return results;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Clean up checkpoints on successful completion
|
|
247
|
-
try {
|
|
248
|
-
await deleteCheckpoints(plan.id);
|
|
249
|
-
logger.info(`Cleaned up checkpoints for completed plan ${plan.id}`);
|
|
250
|
-
} catch (error) {
|
|
251
|
-
logger.warn(`Failed to clean up checkpoints for plan ${plan.id}`, error);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
logger.info(`Plan execution completed: ${results.length} steps executed`);
|
|
255
|
-
return results;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Resume a plan from its last checkpoint
|
|
260
|
-
*/
|
|
261
|
-
async resumePlan(planId: string): Promise<ExecutionResult[]> {
|
|
262
|
-
logger.info(`Attempting to resume plan: ${planId}`);
|
|
263
|
-
|
|
264
|
-
const checkpoint = await getLatestCheckpoint(planId);
|
|
265
|
-
if (!checkpoint) {
|
|
266
|
-
throw new Error(`No checkpoint found for plan ${planId}. Cannot resume.`);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
logger.info(`Resuming plan ${planId} from step ${checkpoint.step}`);
|
|
270
|
-
|
|
271
|
-
// The executePlan method already handles checkpoint-based resume internally.
|
|
272
|
-
// We need the original plan to call executePlan. Since we store completed step IDs
|
|
273
|
-
// in the checkpoint state, we reconstruct what we need.
|
|
274
|
-
// The orchestrator will provide the plan; this method is a convenience wrapper
|
|
275
|
-
// that confirms a checkpoint exists before the orchestrator re-invokes executePlan.
|
|
276
|
-
|
|
277
|
-
return (checkpoint.state.results as ExecutionResult[]) || [];
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Execute a step with retry logic and exponential backoff
|
|
282
|
-
*/
|
|
283
|
-
private async executeWithRetry(
|
|
284
|
-
planId: string,
|
|
285
|
-
step: PlanStep,
|
|
286
|
-
maxRetries = 3
|
|
287
|
-
): Promise<ExecutionResult> {
|
|
288
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
289
|
-
try {
|
|
290
|
-
const result = await this.executeStep(planId, step);
|
|
291
|
-
if (result.status === 'success' || attempt === maxRetries) {
|
|
292
|
-
return result;
|
|
293
|
-
}
|
|
294
|
-
// Don't retry deterministic failures (validation errors, business logic)
|
|
295
|
-
if (result.error?.code === 'NON_RETRYABLE_ERROR') {
|
|
296
|
-
return result;
|
|
297
|
-
}
|
|
298
|
-
// Retry on transient failure results
|
|
299
|
-
logger.warn(
|
|
300
|
-
`Step ${step.id} failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying...`
|
|
301
|
-
);
|
|
302
|
-
await this.delay(1000 * Math.pow(2, attempt));
|
|
303
|
-
// Reset step status for retry
|
|
304
|
-
step.status = 'pending';
|
|
305
|
-
} catch (error) {
|
|
306
|
-
if (attempt === maxRetries) {
|
|
307
|
-
logger.error(`Step ${step.id} failed after ${maxRetries + 1} attempts`, error);
|
|
308
|
-
return {
|
|
309
|
-
id: this.generateResultId(),
|
|
310
|
-
plan_id: planId,
|
|
311
|
-
step_id: step.id,
|
|
312
|
-
status: 'failure',
|
|
313
|
-
started_at: new Date(),
|
|
314
|
-
completed_at: new Date(),
|
|
315
|
-
duration: 0,
|
|
316
|
-
error: {
|
|
317
|
-
code: 'RETRY_EXHAUSTED',
|
|
318
|
-
message: `Step failed after ${maxRetries + 1} attempts: ${(error as Error).message}`,
|
|
319
|
-
stack_trace: (error as Error).stack,
|
|
320
|
-
},
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
logger.warn(
|
|
324
|
-
`Step ${step.id} threw error (attempt ${attempt + 1}/${maxRetries + 1}), retrying...`
|
|
325
|
-
);
|
|
326
|
-
await this.delay(1000 * Math.pow(2, attempt));
|
|
327
|
-
step.status = 'pending';
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
// Should not reach here, but satisfy TypeScript
|
|
331
|
-
throw new Error('Unexpected retry loop exit');
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* Delay helper for retry backoff
|
|
336
|
-
*/
|
|
337
|
-
private delay(ms: number): Promise<void> {
|
|
338
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* Execute a single step
|
|
343
|
-
*/
|
|
344
|
-
private async executeStep(planId: string, step: PlanStep): Promise<ExecutionResult> {
|
|
345
|
-
const executionId = this.generateResultId();
|
|
346
|
-
const startedAt = new Date();
|
|
347
|
-
|
|
348
|
-
this.log(executionId, 'info', `Executing step: ${step.description}`);
|
|
349
|
-
logger.info(`Executing step ${step.id}: ${step.description}`);
|
|
350
|
-
|
|
351
|
-
try {
|
|
352
|
-
// Update step status
|
|
353
|
-
step.status = 'running';
|
|
354
|
-
step.started_at = startedAt;
|
|
355
|
-
|
|
356
|
-
// Execute based on step type
|
|
357
|
-
let outputs: Record<string, unknown> = {};
|
|
358
|
-
let artifacts: ExecutionArtifact[] = [];
|
|
359
|
-
|
|
360
|
-
switch (step.action) {
|
|
361
|
-
case 'validate_requirements':
|
|
362
|
-
outputs = await this.validateRequirements(step, executionId);
|
|
363
|
-
break;
|
|
364
|
-
|
|
365
|
-
case 'generate_component': {
|
|
366
|
-
const generateResult = await this.generateComponent(step, executionId);
|
|
367
|
-
outputs = generateResult.outputs;
|
|
368
|
-
artifacts = generateResult.artifacts;
|
|
369
|
-
break;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
case 'validate_generated_code':
|
|
373
|
-
outputs = await this.validateGeneratedCode(step, executionId);
|
|
374
|
-
break;
|
|
375
|
-
|
|
376
|
-
case 'apply_best_practices':
|
|
377
|
-
outputs = await this.applyBestPractices(step, executionId);
|
|
378
|
-
break;
|
|
379
|
-
|
|
380
|
-
case 'plan_deployment':
|
|
381
|
-
outputs = await this.planDeployment(step, executionId);
|
|
382
|
-
break;
|
|
383
|
-
|
|
384
|
-
case 'apply_deployment':
|
|
385
|
-
outputs = await this.applyDeployment(step, executionId);
|
|
386
|
-
break;
|
|
387
|
-
|
|
388
|
-
case 'verify_deployment':
|
|
389
|
-
outputs = await this.verifyDeployment(step, executionId);
|
|
390
|
-
break;
|
|
391
|
-
|
|
392
|
-
case 'generate_documentation': {
|
|
393
|
-
const docResult = await this.generateDocumentation(step, executionId);
|
|
394
|
-
outputs = docResult.outputs;
|
|
395
|
-
artifacts = docResult.artifacts;
|
|
396
|
-
break;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
default:
|
|
400
|
-
throw new Error(`Unknown action: ${step.action}`);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// Mark step as completed
|
|
404
|
-
const completedAt = new Date();
|
|
405
|
-
step.status = 'completed';
|
|
406
|
-
step.completed_at = completedAt;
|
|
407
|
-
step.duration = completedAt.getTime() - startedAt.getTime();
|
|
408
|
-
|
|
409
|
-
this.log(executionId, 'info', `Step completed successfully in ${step.duration}ms`);
|
|
410
|
-
|
|
411
|
-
// Store artifacts
|
|
412
|
-
if (artifacts.length > 0) {
|
|
413
|
-
this.artifacts.set(executionId, artifacts);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
return {
|
|
417
|
-
id: executionId,
|
|
418
|
-
plan_id: planId,
|
|
419
|
-
step_id: step.id,
|
|
420
|
-
status: 'success',
|
|
421
|
-
started_at: startedAt,
|
|
422
|
-
completed_at: completedAt,
|
|
423
|
-
duration: step.duration,
|
|
424
|
-
outputs,
|
|
425
|
-
artifacts,
|
|
426
|
-
logs: this.logs.get(executionId),
|
|
427
|
-
};
|
|
428
|
-
} catch (error) {
|
|
429
|
-
const completedAt = new Date();
|
|
430
|
-
step.status = 'failed';
|
|
431
|
-
step.completed_at = completedAt;
|
|
432
|
-
step.duration = completedAt.getTime() - startedAt.getTime();
|
|
433
|
-
|
|
434
|
-
this.log(executionId, 'error', `Step failed: ${(error as Error).message}`);
|
|
435
|
-
logger.error(`Step ${step.id} failed`, error);
|
|
436
|
-
|
|
437
|
-
return {
|
|
438
|
-
id: executionId,
|
|
439
|
-
plan_id: planId,
|
|
440
|
-
step_id: step.id,
|
|
441
|
-
status: 'failure',
|
|
442
|
-
started_at: startedAt,
|
|
443
|
-
completed_at: completedAt,
|
|
444
|
-
duration: step.duration!,
|
|
445
|
-
error: {
|
|
446
|
-
code: error instanceof NonRetryableError ? 'NON_RETRYABLE_ERROR' : 'STEP_EXECUTION_ERROR',
|
|
447
|
-
message: (error as Error).message,
|
|
448
|
-
stack_trace: (error as Error).stack,
|
|
449
|
-
},
|
|
450
|
-
logs: this.logs.get(executionId),
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
/**
|
|
456
|
-
* Get steps that are ready for execution
|
|
457
|
-
*/
|
|
458
|
-
private getReadySteps(steps: PlanStep[], executedSteps: Set<string>): PlanStep[] {
|
|
459
|
-
return steps.filter(step => {
|
|
460
|
-
// Skip already executed steps
|
|
461
|
-
if (executedSteps.has(step.id)) {
|
|
462
|
-
return false;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// Skip failed/completed steps
|
|
466
|
-
if (step.status === 'completed' || step.status === 'failed') {
|
|
467
|
-
return false;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// Check if all dependencies are satisfied
|
|
471
|
-
if (step.depends_on && step.depends_on.length > 0) {
|
|
472
|
-
return step.depends_on.every(depId => executedSteps.has(depId));
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
return true;
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
/**
|
|
480
|
-
* Validate requirements
|
|
481
|
-
*/
|
|
482
|
-
private async validateRequirements(
|
|
483
|
-
step: PlanStep,
|
|
484
|
-
executionId: string
|
|
485
|
-
): Promise<Record<string, unknown>> {
|
|
486
|
-
this.log(executionId, 'info', 'Validating infrastructure requirements');
|
|
487
|
-
|
|
488
|
-
const { provider, components, requirements: _requirements } = step.parameters;
|
|
489
|
-
|
|
490
|
-
// Validate provider
|
|
491
|
-
if (!['aws', 'gcp', 'azure'].includes(provider as string)) {
|
|
492
|
-
throw new NonRetryableError(`Invalid provider: ${provider}`);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
// Validate components
|
|
496
|
-
if (!Array.isArray(components) || components.length === 0) {
|
|
497
|
-
throw new NonRetryableError('No components specified');
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
const validComponents = ['vpc', 'eks', 'rds', 's3', 'gke', 'gcs', 'aks'];
|
|
501
|
-
for (const component of components) {
|
|
502
|
-
if (!validComponents.includes(component)) {
|
|
503
|
-
throw new NonRetryableError(`Invalid component: ${component}`);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
this.log(executionId, 'info', `Validated ${components.length} components for ${provider}`);
|
|
508
|
-
|
|
509
|
-
return {
|
|
510
|
-
validated: true,
|
|
511
|
-
provider,
|
|
512
|
-
components,
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* Generate component
|
|
518
|
-
*/
|
|
519
|
-
private async generateComponent(
|
|
520
|
-
step: PlanStep,
|
|
521
|
-
executionId: string
|
|
522
|
-
): Promise<{ outputs: Record<string, unknown>; artifacts: ExecutionArtifact[] }> {
|
|
523
|
-
this.log(executionId, 'info', `Generating ${step.parameters.component} component`);
|
|
524
|
-
|
|
525
|
-
const { component, provider, variables } = step.parameters;
|
|
526
|
-
|
|
527
|
-
let generatedCode: string;
|
|
528
|
-
|
|
529
|
-
try {
|
|
530
|
-
// Call local generator adapter to render template (no HTTP)
|
|
531
|
-
const templateId = `terraform/${provider}/${component}`;
|
|
532
|
-
const result = await this.generatorAdapter.renderTemplate(
|
|
533
|
-
templateId,
|
|
534
|
-
(variables as Record<string, unknown>) || {}
|
|
535
|
-
);
|
|
536
|
-
generatedCode = result.rendered_content;
|
|
537
|
-
this.log(executionId, 'info', `Local generator rendered template: ${templateId}`);
|
|
538
|
-
} catch (error) {
|
|
539
|
-
throw new ExecutionError(
|
|
540
|
-
`Step 'generate_component' failed for ${component}: ${(error as Error).message}.`,
|
|
541
|
-
{ step: 'generate_component', cause: error }
|
|
542
|
-
);
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// Write to filesystem using embedded FileSystemOperations
|
|
546
|
-
const outputPath = `/tmp/nimbus/${executionId}/${component}.tf`;
|
|
547
|
-
|
|
548
|
-
try {
|
|
549
|
-
await this.fsOps.writeFile(outputPath, generatedCode, { createDirs: true });
|
|
550
|
-
this.log(executionId, 'info', `Wrote generated code to: ${outputPath}`);
|
|
551
|
-
} catch (error) {
|
|
552
|
-
this.log(
|
|
553
|
-
executionId,
|
|
554
|
-
'warn',
|
|
555
|
-
`File write unavailable, file not written: ${(error as Error).message}`
|
|
556
|
-
);
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// Create artifact
|
|
560
|
-
const artifact: ExecutionArtifact = {
|
|
561
|
-
id: `artifact_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
562
|
-
type: 'terraform',
|
|
563
|
-
name: `${component}.tf`,
|
|
564
|
-
path: outputPath,
|
|
565
|
-
size: generatedCode.length,
|
|
566
|
-
checksum: this.calculateChecksum(generatedCode),
|
|
567
|
-
created_at: new Date(),
|
|
568
|
-
};
|
|
569
|
-
|
|
570
|
-
this.log(executionId, 'info', `Generated artifact: ${artifact.name} (${artifact.size} bytes)`);
|
|
571
|
-
|
|
572
|
-
return {
|
|
573
|
-
outputs: {
|
|
574
|
-
component,
|
|
575
|
-
code_size: generatedCode.length,
|
|
576
|
-
artifact_id: artifact.id,
|
|
577
|
-
generated_code: generatedCode,
|
|
578
|
-
},
|
|
579
|
-
artifacts: [artifact],
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
/**
|
|
584
|
-
* Validate generated code
|
|
585
|
-
*/
|
|
586
|
-
private async validateGeneratedCode(
|
|
587
|
-
step: PlanStep,
|
|
588
|
-
executionId: string
|
|
589
|
-
): Promise<Record<string, unknown>> {
|
|
590
|
-
this.log(executionId, 'info', 'Validating generated infrastructure code');
|
|
591
|
-
|
|
592
|
-
const { components } = step.parameters;
|
|
593
|
-
const workDir = (step.parameters.workDir as string) || `/tmp/nimbus/${executionId}`;
|
|
594
|
-
|
|
595
|
-
try {
|
|
596
|
-
const tfOps = this.tfOpsForDir(workDir);
|
|
597
|
-
|
|
598
|
-
// Initialize terraform first (needed for validation)
|
|
599
|
-
this.log(executionId, 'info', 'Initializing Terraform for validation...');
|
|
600
|
-
await tfOps.init();
|
|
601
|
-
|
|
602
|
-
// Run terraform validate
|
|
603
|
-
this.log(executionId, 'info', 'Running Terraform validate...');
|
|
604
|
-
const validateResult = await tfOps.validate();
|
|
605
|
-
|
|
606
|
-
// Format terraform files
|
|
607
|
-
this.log(executionId, 'info', 'Formatting Terraform files...');
|
|
608
|
-
await tfOps.fmt({ recursive: true });
|
|
609
|
-
|
|
610
|
-
const validationResults = {
|
|
611
|
-
syntax_valid: validateResult.valid,
|
|
612
|
-
error_count: validateResult.errorCount,
|
|
613
|
-
warning_count: validateResult.warningCount,
|
|
614
|
-
diagnostics: validateResult.diagnostics,
|
|
615
|
-
resources_count: (components as string[]).length * 5, // Estimate
|
|
616
|
-
};
|
|
617
|
-
|
|
618
|
-
if (validateResult.valid) {
|
|
619
|
-
this.log(executionId, 'info', `Code validation passed`);
|
|
620
|
-
} else {
|
|
621
|
-
this.log(executionId, 'warn', `Code validation found ${validateResult.errorCount} errors`);
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
return validationResults;
|
|
625
|
-
} catch (error) {
|
|
626
|
-
throw new ExecutionError(
|
|
627
|
-
`Step 'validate_generated_code' failed: ${(error as Error).message}. Ensure Terraform is installed.`,
|
|
628
|
-
{ step: 'validate_generated_code', cause: error }
|
|
629
|
-
);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
/**
|
|
634
|
-
* Apply best practices
|
|
635
|
-
*/
|
|
636
|
-
private async applyBestPractices(
|
|
637
|
-
step: PlanStep,
|
|
638
|
-
executionId: string
|
|
639
|
-
): Promise<Record<string, unknown>> {
|
|
640
|
-
this.log(executionId, 'info', 'Applying security and best practices');
|
|
641
|
-
|
|
642
|
-
const { components, autofix, config } = step.parameters;
|
|
643
|
-
let totalViolations = 0;
|
|
644
|
-
let totalFixed = 0;
|
|
645
|
-
const componentReports: Array<{ component: string; violations: number; fixed: number }> = [];
|
|
646
|
-
|
|
647
|
-
try {
|
|
648
|
-
// Analyze best practices for each component using local adapter
|
|
649
|
-
for (const component of components as string[]) {
|
|
650
|
-
this.log(executionId, 'info', `Analyzing best practices for ${component}...`);
|
|
651
|
-
|
|
652
|
-
const report = await this.generatorAdapter.analyzeBestPractices(
|
|
653
|
-
component,
|
|
654
|
-
(config as Record<string, unknown>) || {}
|
|
655
|
-
);
|
|
656
|
-
|
|
657
|
-
const violations = report.summary?.total_violations || 0;
|
|
658
|
-
totalViolations += violations;
|
|
659
|
-
|
|
660
|
-
// Apply autofixes if requested
|
|
661
|
-
if (autofix && violations > 0) {
|
|
662
|
-
this.log(executionId, 'info', `Applying autofixes for ${component}...`);
|
|
663
|
-
const fixResult = await this.generatorAdapter.applyAutofixes(
|
|
664
|
-
component,
|
|
665
|
-
(config as Record<string, unknown>) || {}
|
|
666
|
-
);
|
|
667
|
-
const fixed = fixResult.fixes_applied || 0;
|
|
668
|
-
totalFixed += fixed;
|
|
669
|
-
componentReports.push({ component, violations, fixed });
|
|
670
|
-
} else {
|
|
671
|
-
componentReports.push({ component, violations, fixed: 0 });
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
this.log(
|
|
676
|
-
executionId,
|
|
677
|
-
'info',
|
|
678
|
-
`Best practices: ${totalViolations} violations found, ${totalFixed} auto-fixed`
|
|
679
|
-
);
|
|
680
|
-
|
|
681
|
-
return {
|
|
682
|
-
violations_found: totalViolations,
|
|
683
|
-
violations_fixed: totalFixed,
|
|
684
|
-
component_reports: componentReports,
|
|
685
|
-
compliance_score:
|
|
686
|
-
totalViolations === 0 ? 100 : Math.max(0, 100 - (totalViolations - totalFixed) * 5),
|
|
687
|
-
};
|
|
688
|
-
} catch (error) {
|
|
689
|
-
throw new ExecutionError(`Step 'apply_best_practices' failed: ${(error as Error).message}.`, {
|
|
690
|
-
step: 'apply_best_practices',
|
|
691
|
-
cause: error,
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
/**
|
|
697
|
-
* Plan deployment
|
|
698
|
-
*/
|
|
699
|
-
private async planDeployment(
|
|
700
|
-
step: PlanStep,
|
|
701
|
-
executionId: string
|
|
702
|
-
): Promise<Record<string, unknown>> {
|
|
703
|
-
this.log(executionId, 'info', 'Planning infrastructure deployment');
|
|
704
|
-
|
|
705
|
-
const workDir = (step.parameters.workDir as string) || `/tmp/nimbus/${executionId}`;
|
|
706
|
-
|
|
707
|
-
try {
|
|
708
|
-
const tfOps = this.tfOpsForDir(workDir);
|
|
709
|
-
|
|
710
|
-
// Initialize terraform first
|
|
711
|
-
this.log(executionId, 'info', 'Initializing Terraform...');
|
|
712
|
-
await tfOps.init();
|
|
713
|
-
|
|
714
|
-
// Run terraform plan
|
|
715
|
-
this.log(executionId, 'info', 'Running Terraform plan...');
|
|
716
|
-
const planResult = await tfOps.plan({
|
|
717
|
-
out: `${workDir}/plan.tfplan`,
|
|
718
|
-
varFile: step.parameters.varFile as string | undefined,
|
|
719
|
-
});
|
|
720
|
-
|
|
721
|
-
this.log(executionId, 'info', `Plan: hasChanges=${planResult.hasChanges}`);
|
|
722
|
-
|
|
723
|
-
return {
|
|
724
|
-
plan_output: planResult.output,
|
|
725
|
-
has_changes: planResult.hasChanges,
|
|
726
|
-
plan_file: `${workDir}/plan.tfplan`,
|
|
727
|
-
};
|
|
728
|
-
} catch (error) {
|
|
729
|
-
throw new ExecutionError(
|
|
730
|
-
`Step 'plan_deployment' failed: ${(error as Error).message}. Ensure Terraform is installed.`,
|
|
731
|
-
{ step: 'plan_deployment', cause: error }
|
|
732
|
-
);
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
/**
|
|
737
|
-
* Apply deployment
|
|
738
|
-
*/
|
|
739
|
-
private async applyDeployment(
|
|
740
|
-
step: PlanStep,
|
|
741
|
-
executionId: string
|
|
742
|
-
): Promise<Record<string, unknown>> {
|
|
743
|
-
this.log(executionId, 'info', 'Applying infrastructure deployment');
|
|
744
|
-
|
|
745
|
-
const workDir = (step.parameters.workDir as string) || `/tmp/nimbus/${executionId}`;
|
|
746
|
-
const autoApprove = (step.parameters.autoApprove as boolean) ?? true;
|
|
747
|
-
const planFile = step.parameters.planFile as string | undefined;
|
|
748
|
-
|
|
749
|
-
try {
|
|
750
|
-
const tfOps = this.tfOpsForDir(workDir);
|
|
751
|
-
const startTime = Date.now();
|
|
752
|
-
|
|
753
|
-
// Run terraform apply
|
|
754
|
-
this.log(executionId, 'info', 'Running Terraform apply...');
|
|
755
|
-
const applyResult = await tfOps.apply({
|
|
756
|
-
autoApprove,
|
|
757
|
-
planFile,
|
|
758
|
-
varFile: step.parameters.varFile as string | undefined,
|
|
759
|
-
parallelism: step.parameters.parallelism as number | undefined,
|
|
760
|
-
});
|
|
761
|
-
|
|
762
|
-
const deploymentTime = Date.now() - startTime;
|
|
763
|
-
|
|
764
|
-
this.log(executionId, 'info', `Deployment completed successfully`);
|
|
765
|
-
|
|
766
|
-
return {
|
|
767
|
-
applied: applyResult.success,
|
|
768
|
-
deployment_time: deploymentTime,
|
|
769
|
-
output: applyResult.output,
|
|
770
|
-
};
|
|
771
|
-
} catch (error) {
|
|
772
|
-
throw new ExecutionError(
|
|
773
|
-
`Step 'apply_deployment' failed: ${(error as Error).message}. Ensure Terraform is installed.`,
|
|
774
|
-
{ step: 'apply_deployment', cause: error }
|
|
775
|
-
);
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
/**
|
|
780
|
-
* Verify deployment
|
|
781
|
-
*/
|
|
782
|
-
private async verifyDeployment(
|
|
783
|
-
step: PlanStep,
|
|
784
|
-
executionId: string
|
|
785
|
-
): Promise<Record<string, unknown>> {
|
|
786
|
-
this.log(executionId, 'info', 'Verifying deployed infrastructure');
|
|
787
|
-
|
|
788
|
-
const { components } = step.parameters;
|
|
789
|
-
const workDir = (step.parameters.workDir as string) || `/tmp/nimbus/${executionId}`;
|
|
790
|
-
|
|
791
|
-
try {
|
|
792
|
-
const tfOps = this.tfOpsForDir(workDir);
|
|
793
|
-
|
|
794
|
-
// Validate terraform state
|
|
795
|
-
this.log(executionId, 'info', 'Validating Terraform configuration...');
|
|
796
|
-
const validateResult = await tfOps.validate();
|
|
797
|
-
|
|
798
|
-
if (!validateResult.valid) {
|
|
799
|
-
this.log(
|
|
800
|
-
executionId,
|
|
801
|
-
'error',
|
|
802
|
-
`Validation failed with ${validateResult.errorCount} errors`
|
|
803
|
-
);
|
|
804
|
-
return {
|
|
805
|
-
verification_passed: false,
|
|
806
|
-
validation_errors: validateResult.diagnostics.filter((d: any) => d.severity === 'error'),
|
|
807
|
-
validation_warnings: validateResult.diagnostics.filter(
|
|
808
|
-
(d: any) => d.severity === 'warning'
|
|
809
|
-
),
|
|
810
|
-
};
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
// Get terraform outputs
|
|
814
|
-
this.log(executionId, 'info', 'Retrieving Terraform outputs...');
|
|
815
|
-
const outputs = await tfOps.output();
|
|
816
|
-
|
|
817
|
-
// Build component checks
|
|
818
|
-
const checks = (components as string[]).map(component => ({
|
|
819
|
-
component,
|
|
820
|
-
status: 'passed',
|
|
821
|
-
checks_passed: 10,
|
|
822
|
-
checks_failed: 0,
|
|
823
|
-
}));
|
|
824
|
-
|
|
825
|
-
this.log(executionId, 'info', `Verification passed for ${checks.length} components`);
|
|
826
|
-
|
|
827
|
-
return {
|
|
828
|
-
verification_passed: true,
|
|
829
|
-
checks,
|
|
830
|
-
outputs,
|
|
831
|
-
};
|
|
832
|
-
} catch (error) {
|
|
833
|
-
throw new ExecutionError(
|
|
834
|
-
`Step 'verify_deployment' failed: ${(error as Error).message}. Ensure Terraform is installed.`,
|
|
835
|
-
{ step: 'verify_deployment', cause: error }
|
|
836
|
-
);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
/**
|
|
841
|
-
* Generate documentation
|
|
842
|
-
*/
|
|
843
|
-
private async generateDocumentation(
|
|
844
|
-
step: PlanStep,
|
|
845
|
-
executionId: string
|
|
846
|
-
): Promise<{ outputs: Record<string, unknown>; artifacts: ExecutionArtifact[] }> {
|
|
847
|
-
this.log(executionId, 'info', 'Generating infrastructure documentation');
|
|
848
|
-
|
|
849
|
-
const { components, include_diagrams } = step.parameters;
|
|
850
|
-
|
|
851
|
-
// Generate README
|
|
852
|
-
const readmeContent = this.generateReadme(components as string[]);
|
|
853
|
-
const readmeArtifact: ExecutionArtifact = {
|
|
854
|
-
id: `artifact_${Date.now()}_readme`,
|
|
855
|
-
type: 'documentation',
|
|
856
|
-
name: 'README.md',
|
|
857
|
-
path: `/tmp/nimbus/${executionId}/README.md`,
|
|
858
|
-
size: readmeContent.length,
|
|
859
|
-
checksum: this.calculateChecksum(readmeContent),
|
|
860
|
-
created_at: new Date(),
|
|
861
|
-
};
|
|
862
|
-
|
|
863
|
-
const artifacts = [readmeArtifact];
|
|
864
|
-
|
|
865
|
-
// Generate diagram if requested
|
|
866
|
-
if (include_diagrams) {
|
|
867
|
-
const { DiagramGenerator } = await import('./diagram-generator');
|
|
868
|
-
const diagramGen = new DiagramGenerator();
|
|
869
|
-
const diagramContent = diagramGen.generateInfrastructureDiagram(
|
|
870
|
-
components as string[],
|
|
871
|
-
'aws'
|
|
872
|
-
);
|
|
873
|
-
const diagramPath = `/tmp/nimbus/${executionId}/architecture.txt`;
|
|
874
|
-
|
|
875
|
-
try {
|
|
876
|
-
await this.fsOps.writeFile(diagramPath, diagramContent, { createDirs: true });
|
|
877
|
-
} catch {
|
|
878
|
-
// Best-effort write
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
const diagramArtifact: ExecutionArtifact = {
|
|
882
|
-
id: `artifact_${Date.now()}_diagram`,
|
|
883
|
-
type: 'documentation',
|
|
884
|
-
name: 'architecture.txt',
|
|
885
|
-
path: diagramPath,
|
|
886
|
-
size: diagramContent.length,
|
|
887
|
-
checksum: this.calculateChecksum(diagramContent),
|
|
888
|
-
created_at: new Date(),
|
|
889
|
-
};
|
|
890
|
-
artifacts.push(diagramArtifact);
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
this.log(executionId, 'info', `Generated ${artifacts.length} documentation artifacts`);
|
|
894
|
-
|
|
895
|
-
return {
|
|
896
|
-
outputs: {
|
|
897
|
-
artifacts_generated: artifacts.length,
|
|
898
|
-
},
|
|
899
|
-
artifacts,
|
|
900
|
-
};
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
/**
|
|
904
|
-
* Rollback a step
|
|
905
|
-
*/
|
|
906
|
-
async rollbackStep(step: PlanStep): Promise<ExecutionResult> {
|
|
907
|
-
logger.info(`Rolling back step: ${step.id}`);
|
|
908
|
-
|
|
909
|
-
if (!step.rollback_action) {
|
|
910
|
-
throw new Error(`Step ${step.id} does not have a rollback action defined`);
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
const executionId = this.generateResultId();
|
|
914
|
-
const startedAt = new Date();
|
|
915
|
-
|
|
916
|
-
try {
|
|
917
|
-
// Execute rollback action
|
|
918
|
-
this.log(executionId, 'info', `Executing rollback: ${step.rollback_action}`);
|
|
919
|
-
|
|
920
|
-
// Parse and execute the rollback action
|
|
921
|
-
const action = step.rollback_action;
|
|
922
|
-
const [prefix, ...rest] = action.split(':');
|
|
923
|
-
|
|
924
|
-
let rollbackCmd: string;
|
|
925
|
-
if (prefix === 'terraform_destroy') {
|
|
926
|
-
const workdir = rest.join(':');
|
|
927
|
-
rollbackCmd = `terraform -chdir=${workdir} destroy -auto-approve -no-color`;
|
|
928
|
-
} else if (prefix === 'kubectl_rollout_undo') {
|
|
929
|
-
const [resource, namespace] = rest;
|
|
930
|
-
rollbackCmd = `kubectl rollout undo ${resource}${namespace ? ` -n ${namespace}` : ''}`;
|
|
931
|
-
} else if (prefix === 'helm_rollback') {
|
|
932
|
-
const [release, revision, namespace] = rest;
|
|
933
|
-
rollbackCmd = `helm rollback ${release} ${revision ?? '0'}${namespace ? ` -n ${namespace}` : ''}`;
|
|
934
|
-
} else if (prefix === 'bash') {
|
|
935
|
-
rollbackCmd = rest.join(':');
|
|
936
|
-
} else {
|
|
937
|
-
this.log(executionId, 'warn', `Unknown rollback prefix "${prefix}" — skipping`);
|
|
938
|
-
rollbackCmd = '';
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
if (rollbackCmd) {
|
|
942
|
-
this.log(executionId, 'info', `Running: ${rollbackCmd}`);
|
|
943
|
-
await new Promise<void>((resolve, reject) => {
|
|
944
|
-
const { exec } = require('node:child_process') as typeof import('node:child_process');
|
|
945
|
-
exec(rollbackCmd, { timeout: 600_000 }, (error, stdout, stderr) => {
|
|
946
|
-
if (stdout) this.log(executionId, 'info', stdout);
|
|
947
|
-
if (stderr) this.log(executionId, 'info', stderr);
|
|
948
|
-
if (error) reject(error);
|
|
949
|
-
else resolve();
|
|
950
|
-
});
|
|
951
|
-
});
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
const completedAt = new Date();
|
|
955
|
-
|
|
956
|
-
this.log(executionId, 'info', 'Rollback completed successfully');
|
|
957
|
-
|
|
958
|
-
return {
|
|
959
|
-
id: executionId,
|
|
960
|
-
plan_id: 'rollback',
|
|
961
|
-
step_id: step.id,
|
|
962
|
-
status: 'success',
|
|
963
|
-
started_at: startedAt,
|
|
964
|
-
completed_at: completedAt,
|
|
965
|
-
duration: completedAt.getTime() - startedAt.getTime(),
|
|
966
|
-
outputs: {
|
|
967
|
-
rolled_back: true,
|
|
968
|
-
},
|
|
969
|
-
logs: this.logs.get(executionId),
|
|
970
|
-
};
|
|
971
|
-
} catch (error) {
|
|
972
|
-
const completedAt = new Date();
|
|
973
|
-
this.log(executionId, 'error', `Rollback failed: ${(error as Error).message}`);
|
|
974
|
-
|
|
975
|
-
return {
|
|
976
|
-
id: executionId,
|
|
977
|
-
plan_id: 'rollback',
|
|
978
|
-
step_id: step.id,
|
|
979
|
-
status: 'failure',
|
|
980
|
-
started_at: startedAt,
|
|
981
|
-
completed_at: completedAt,
|
|
982
|
-
duration: completedAt.getTime() - startedAt.getTime(),
|
|
983
|
-
error: {
|
|
984
|
-
code: 'ROLLBACK_ERROR',
|
|
985
|
-
message: (error as Error).message,
|
|
986
|
-
},
|
|
987
|
-
logs: this.logs.get(executionId),
|
|
988
|
-
};
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
/**
|
|
993
|
-
* Get execution logs
|
|
994
|
-
*/
|
|
995
|
-
getLogs(executionId: string): ExecutionLog[] {
|
|
996
|
-
return this.logs.get(executionId) || [];
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
/**
|
|
1000
|
-
* Get execution artifacts
|
|
1001
|
-
*/
|
|
1002
|
-
getArtifacts(executionId: string): ExecutionArtifact[] {
|
|
1003
|
-
return this.artifacts.get(executionId) || [];
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
/**
|
|
1007
|
-
* Log a message
|
|
1008
|
-
*/
|
|
1009
|
-
private log(
|
|
1010
|
-
executionId: string,
|
|
1011
|
-
level: 'debug' | 'info' | 'warn' | 'error',
|
|
1012
|
-
message: string,
|
|
1013
|
-
context?: Record<string, unknown>
|
|
1014
|
-
): void {
|
|
1015
|
-
if (!this.logs.has(executionId)) {
|
|
1016
|
-
this.logs.set(executionId, []);
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
this.logs.get(executionId)!.push({
|
|
1020
|
-
timestamp: new Date(),
|
|
1021
|
-
level,
|
|
1022
|
-
message,
|
|
1023
|
-
context,
|
|
1024
|
-
});
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
/**
|
|
1028
|
-
* Helper: Generate README
|
|
1029
|
-
*/
|
|
1030
|
-
private generateReadme(components: string[]): string {
|
|
1031
|
-
return (
|
|
1032
|
-
`# Infrastructure Documentation\n\n` +
|
|
1033
|
-
`## Components\n\n${components
|
|
1034
|
-
.map(c => `- ${c.toUpperCase()}`)
|
|
1035
|
-
.join('\n')}\n\n## Deployment\n\nRun \`terraform apply\` to deploy.\n`
|
|
1036
|
-
);
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
/**
|
|
1040
|
-
* Helper: Calculate checksum
|
|
1041
|
-
*/
|
|
1042
|
-
private calculateChecksum(content: string): string {
|
|
1043
|
-
// Simple hash function (in production, use proper crypto)
|
|
1044
|
-
let hash = 0;
|
|
1045
|
-
for (let i = 0; i < content.length; i++) {
|
|
1046
|
-
const char = content.charCodeAt(i);
|
|
1047
|
-
hash = (hash << 5) - hash + char;
|
|
1048
|
-
hash = hash & hash;
|
|
1049
|
-
}
|
|
1050
|
-
return Math.abs(hash).toString(16);
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
/**
|
|
1054
|
-
* Helper: Sleep
|
|
1055
|
-
*/
|
|
1056
|
-
private sleep(ms: number): Promise<void> {
|
|
1057
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
/**
|
|
1061
|
-
* Generate result ID
|
|
1062
|
-
*/
|
|
1063
|
-
private generateResultId(): string {
|
|
1064
|
-
return `exec_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
1065
|
-
}
|
|
1066
|
-
}
|