@howlil/ez-agents 3.5.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +735 -537
- package/agents/ez-architect-agent.md +267 -0
- package/agents/ez-backend-agent.md +303 -0
- package/agents/ez-chief-strategist.md +271 -0
- package/agents/ez-codebase-mapper.md +770 -770
- package/agents/ez-context-manager.md +319 -0
- package/agents/ez-debugger.md +1255 -1255
- package/agents/ez-design-expert.md +347 -0
- package/agents/ez-devops-agent.md +331 -0
- package/agents/ez-executor.md +487 -487
- package/agents/ez-frontend-agent.md +322 -0
- package/agents/ez-phase-researcher.md +553 -553
- package/agents/ez-planner.md +1307 -1307
- package/agents/ez-product-engineer.md +435 -0
- package/agents/ez-project-researcher.md +629 -629
- package/agents/ez-qa-agent.md +320 -0
- package/agents/ez-release-agent.md +333 -333
- package/agents/ez-requirements-agent.md +377 -377
- package/agents/ez-roadmapper.md +650 -650
- package/agents/ez-technical-writer.md +551 -0
- package/agents/ez-ux-expert.md +393 -0
- package/agents/ez-verifier.md +579 -579
- package/bin/guards/autonomy-guard.cjs +346 -0
- package/bin/guards/context-budget-guard.cjs +278 -0
- package/bin/guards/hallucination-guard.cjs +380 -0
- package/bin/guards/hidden-state-guard.cjs +182 -0
- package/bin/guards/team-overhead-guard.cjs +266 -0
- package/bin/guards/tool-sprawl-guard.cjs +271 -0
- package/bin/lib/analytics/analytics-collector.cjs +86 -0
- package/bin/lib/analytics/analytics-reporter.cjs +130 -0
- package/bin/lib/analytics/cohort-analyzer.cjs +138 -0
- package/bin/lib/analytics/funnel-analyzer.cjs +147 -0
- package/bin/lib/analytics/nps-tracker.cjs +147 -0
- package/bin/lib/archetype-detector.cjs +289 -0
- package/bin/lib/assistant-adapter.cjs +361 -0
- package/bin/lib/audit-exec.cjs +175 -0
- package/bin/lib/auth.cjs +176 -0
- package/bin/lib/backup-service.cjs +422 -0
- package/bin/lib/bdd-validator.cjs +622 -0
- package/bin/lib/business-flow-mapper.cjs +429 -0
- package/bin/lib/circuit-breaker.cjs +276 -0
- package/bin/lib/code-complexity-analyzer.cjs +360 -0
- package/bin/lib/codebase-analyzer.cjs +241 -0
- package/bin/lib/commands.cjs +691 -0
- package/bin/lib/config.cjs +236 -0
- package/bin/lib/constraint-extractor.cjs +526 -0
- package/bin/lib/content-scanner.cjs +238 -0
- package/bin/lib/context-cache.cjs +154 -0
- package/bin/lib/context-compressor.cjs +102 -0
- package/bin/lib/context-deduplicator.cjs +105 -0
- package/bin/lib/context-errors.cjs +78 -0
- package/bin/lib/context-manager.cjs +338 -0
- package/bin/lib/context-metadata-tracker.cjs +140 -0
- package/bin/lib/context-relevance-scorer.cjs +99 -0
- package/bin/lib/core.cjs +507 -0
- package/bin/lib/cost-alerts.cjs +174 -0
- package/bin/lib/cost-tracker.cjs +275 -0
- package/bin/lib/crash-recovery.cjs +220 -0
- package/bin/lib/dependency-graph.cjs +319 -0
- package/bin/lib/deploy/deploy-audit-log.cjs +76 -0
- package/bin/lib/deploy/deploy-detector.cjs +69 -0
- package/bin/lib/deploy/deploy-env-manager.cjs +109 -0
- package/bin/lib/deploy/deploy-health-check.cjs +88 -0
- package/bin/lib/deploy/deploy-pre-flight.cjs +57 -0
- package/bin/lib/deploy/deploy-rollback.cjs +72 -0
- package/bin/lib/deploy/deploy-runner.cjs +97 -0
- package/bin/lib/deploy/deploy-status.cjs +74 -0
- package/bin/lib/discussion-synthesizer.cjs +439 -0
- package/bin/lib/error-cache.cjs +114 -0
- package/bin/lib/error-registry.cjs +177 -0
- package/bin/lib/file-access.cjs +207 -0
- package/bin/lib/file-lock.cjs +236 -0
- package/bin/lib/finops/budget-enforcer.cjs +126 -0
- package/bin/lib/finops/cost-reporter.cjs +132 -0
- package/bin/lib/finops/finops-analyzer.cjs +112 -0
- package/bin/lib/finops/spot-manager.cjs +118 -0
- package/bin/lib/framework-detector.cjs +396 -0
- package/bin/lib/frontmatter.cjs +313 -0
- package/bin/lib/fs-utils.cjs +153 -0
- package/bin/lib/gate-executor.cjs +272 -0
- package/bin/lib/gates/README.md +374 -0
- package/bin/lib/gates/gate-01-requirement.cjs +303 -0
- package/bin/lib/gates/gate-02-architecture.cjs +555 -0
- package/bin/lib/gates/gate-03-code.cjs +635 -0
- package/bin/lib/gates/gate-04-security.cjs +829 -0
- package/bin/lib/git-errors.cjs +83 -0
- package/bin/lib/git-utils.cjs +321 -0
- package/bin/lib/git-workflow-engine.cjs +1157 -0
- package/bin/lib/health-check.cjs +227 -0
- package/bin/lib/index.cjs +279 -0
- package/bin/lib/init.cjs +725 -0
- package/bin/lib/lock-logger.cjs +194 -0
- package/bin/lib/lock-state.cjs +263 -0
- package/bin/lib/lockfile-validator.cjs +227 -0
- package/bin/lib/log-rotation.cjs +71 -0
- package/bin/lib/logger.cjs +125 -0
- package/bin/lib/memory-compression.cjs +256 -0
- package/bin/lib/milestone.cjs +247 -0
- package/bin/lib/model-provider.cjs +241 -0
- package/bin/lib/package-manager-detector.cjs +203 -0
- package/bin/lib/package-manager-executor.cjs +385 -0
- package/bin/lib/package-manager-service.cjs +216 -0
- package/bin/lib/perf/api-monitor.cjs +88 -0
- package/bin/lib/perf/db-optimizer.cjs +78 -0
- package/bin/lib/perf/frontend-performance.cjs +56 -0
- package/bin/lib/perf/perf-analyzer.cjs +77 -0
- package/bin/lib/perf/perf-baseline.cjs +102 -0
- package/bin/lib/perf/perf-reporter.cjs +117 -0
- package/bin/lib/perf/regression-detector.cjs +92 -0
- package/bin/lib/phase.cjs +963 -0
- package/bin/lib/planning-write.cjs +123 -0
- package/bin/lib/project-reporter.cjs +565 -0
- package/bin/lib/quality-gate.cjs +332 -0
- package/bin/lib/quality-metrics.cjs +324 -0
- package/bin/lib/recovery-manager.cjs +98 -0
- package/bin/lib/release-validator.cjs +617 -0
- package/bin/lib/retry.cjs +119 -0
- package/bin/lib/roadmap.cjs +309 -0
- package/bin/lib/safe-exec.cjs +173 -0
- package/bin/lib/safe-path.cjs +130 -0
- package/bin/lib/security-errors.cjs +62 -0
- package/bin/lib/session-chain.cjs +304 -0
- package/bin/lib/session-errors.cjs +81 -0
- package/bin/lib/session-export.cjs +251 -0
- package/bin/lib/session-import.cjs +262 -0
- package/bin/lib/session-manager.cjs +280 -0
- package/bin/lib/skill-context.cjs +148 -0
- package/bin/lib/skill-matcher.cjs +236 -0
- package/bin/lib/skill-registry.cjs +360 -0
- package/bin/lib/skill-resolver.cjs +449 -0
- package/bin/lib/skill-triggers.cjs +90 -0
- package/bin/lib/skill-validator.cjs +270 -0
- package/bin/lib/skill-versioning.cjs +355 -0
- package/bin/lib/stack-detector.cjs +399 -0
- package/bin/lib/state.cjs +736 -0
- package/bin/lib/tech-debt-analyzer.cjs +309 -0
- package/bin/lib/temp-file.cjs +239 -0
- package/bin/lib/template.cjs +223 -0
- package/bin/lib/test-file-lock.cjs +112 -0
- package/bin/lib/test-graceful.cjs +93 -0
- package/bin/lib/test-logger.cjs +60 -0
- package/bin/lib/test-safe-exec.cjs +38 -0
- package/bin/lib/test-safe-path.cjs +33 -0
- package/bin/lib/test-temp-file.cjs +125 -0
- package/bin/lib/tier-manager.cjs +428 -0
- package/bin/lib/timeout-exec.cjs +63 -0
- package/bin/lib/tradeoff-analyzer.cjs +284 -0
- package/bin/lib/url-fetch.cjs +170 -0
- package/bin/lib/verify.cjs +863 -0
- package/bin/update.js +217 -214
- package/commands/deploy.cjs +53 -0
- package/commands/ez/add-tests.md +41 -41
- package/commands/ez/audit-milestone.md +36 -36
- package/commands/ez/complete-milestone.md +136 -136
- package/commands/ez/discuss-phase.md +90 -90
- package/commands/ez/execute-phase.md +52 -52
- package/commands/ez/help.md +22 -22
- package/commands/ez/map-codebase.md +71 -71
- package/commands/ez/new-milestone.md +44 -44
- package/commands/ez/new-project.md +51 -42
- package/commands/ez/plan-phase.md +53 -53
- package/commands/ez/progress.md +36 -36
- package/commands/ez/quick.md +45 -45
- package/commands/ez/resume-work.md +40 -40
- package/commands/ez/run-phase.md +580 -0
- package/commands/ez/settings.md +36 -36
- package/commands/ez/update.md +37 -37
- package/commands/ez/verify-work.md +402 -38
- package/commands/health-check.cjs +44 -0
- package/commands/rollback.cjs +47 -0
- package/ez-agents/bin/ez-tools.cjs +599 -2
- package/ez-agents/bin/guards/autonomy-guard.cjs +346 -0
- package/ez-agents/bin/guards/context-budget-guard.cjs +247 -0
- package/ez-agents/bin/guards/hallucination-guard.cjs +271 -0
- package/ez-agents/bin/guards/hidden-state-guard.cjs +182 -0
- package/ez-agents/bin/guards/team-overhead-guard.cjs +266 -0
- package/ez-agents/bin/guards/tool-sprawl-guard.cjs +271 -0
- package/ez-agents/bin/lib/analytics/analytics-collector.cjs +86 -0
- package/ez-agents/bin/lib/analytics/analytics-reporter.cjs +130 -0
- package/ez-agents/bin/lib/analytics/cohort-analyzer.cjs +138 -0
- package/ez-agents/bin/lib/analytics/funnel-analyzer.cjs +147 -0
- package/ez-agents/bin/lib/analytics/nps-tracker.cjs +147 -0
- package/ez-agents/bin/lib/archetype-detector.cjs +289 -0
- package/ez-agents/bin/lib/audit-exec.cjs +166 -167
- package/ez-agents/bin/lib/auth.cjs +176 -176
- package/ez-agents/bin/lib/backup-service.cjs +422 -0
- package/ez-agents/bin/lib/bdd-validator.cjs +622 -622
- package/ez-agents/bin/lib/business-flow-mapper.cjs +429 -0
- package/ez-agents/bin/lib/code-complexity-analyzer.cjs +360 -0
- package/ez-agents/bin/lib/codebase-analyzer.cjs +241 -0
- package/ez-agents/bin/lib/commands.cjs +685 -685
- package/ez-agents/bin/lib/config.cjs +41 -1
- package/ez-agents/bin/lib/constraint-extractor.cjs +526 -0
- package/ez-agents/bin/lib/content-scanner.cjs +238 -238
- package/ez-agents/bin/lib/context-cache.cjs +154 -154
- package/ez-agents/bin/lib/context-errors.cjs +71 -71
- package/ez-agents/bin/lib/context-manager.cjs +220 -220
- package/ez-agents/bin/lib/core.cjs +507 -512
- package/ez-agents/bin/lib/cost-tracker.cjs +243 -0
- package/ez-agents/bin/lib/crash-recovery.cjs +172 -0
- package/ez-agents/bin/lib/dependency-graph.cjs +319 -0
- package/ez-agents/bin/lib/deploy/deploy-audit-log.cjs +76 -0
- package/ez-agents/bin/lib/deploy/deploy-detector.cjs +69 -0
- package/ez-agents/bin/lib/deploy/deploy-env-manager.cjs +109 -0
- package/ez-agents/bin/lib/deploy/deploy-health-check.cjs +88 -0
- package/ez-agents/bin/lib/deploy/deploy-pre-flight.cjs +57 -0
- package/ez-agents/bin/lib/deploy/deploy-rollback.cjs +72 -0
- package/ez-agents/bin/lib/deploy/deploy-runner.cjs +97 -0
- package/ez-agents/bin/lib/deploy/deploy-status.cjs +74 -0
- package/ez-agents/bin/lib/file-access.cjs +207 -207
- package/ez-agents/bin/lib/finops/budget-enforcer.cjs +126 -0
- package/ez-agents/bin/lib/finops/cost-reporter.cjs +132 -0
- package/ez-agents/bin/lib/finops/finops-analyzer.cjs +112 -0
- package/ez-agents/bin/lib/finops/spot-manager.cjs +118 -0
- package/ez-agents/bin/lib/framework-detector.cjs +396 -0
- package/ez-agents/bin/lib/frontmatter.cjs +3 -1
- package/ez-agents/bin/lib/gates/README.md +374 -0
- package/ez-agents/bin/lib/gates/gate-01-requirement.cjs +303 -0
- package/ez-agents/bin/lib/gates/gate-02-architecture.cjs +555 -0
- package/ez-agents/bin/lib/gates/gate-03-code.cjs +635 -0
- package/ez-agents/bin/lib/gates/gate-04-security.cjs +829 -0
- package/ez-agents/bin/lib/git-errors.cjs +83 -83
- package/ez-agents/bin/lib/git-utils.cjs +321 -321
- package/ez-agents/bin/lib/git-workflow-engine.cjs +1157 -1157
- package/ez-agents/bin/lib/health-check.cjs +162 -162
- package/ez-agents/bin/lib/index.cjs +2 -8
- package/ez-agents/bin/lib/init.cjs +0 -2
- package/ez-agents/bin/lib/lockfile-validator.cjs +227 -227
- package/ez-agents/bin/lib/log-rotation.cjs +71 -0
- package/ez-agents/bin/lib/logger.cjs +22 -47
- package/ez-agents/bin/lib/memory-compression.cjs +256 -256
- package/ez-agents/bin/lib/package-manager-detector.cjs +203 -203
- package/ez-agents/bin/lib/package-manager-executor.cjs +385 -385
- package/ez-agents/bin/lib/package-manager-service.cjs +216 -216
- package/ez-agents/bin/lib/perf/api-monitor.cjs +88 -0
- package/ez-agents/bin/lib/perf/db-optimizer.cjs +78 -0
- package/ez-agents/bin/lib/perf/frontend-performance.cjs +56 -0
- package/ez-agents/bin/lib/perf/perf-analyzer.cjs +77 -0
- package/ez-agents/bin/lib/perf/perf-baseline.cjs +102 -0
- package/ez-agents/bin/lib/perf/perf-reporter.cjs +117 -0
- package/ez-agents/bin/lib/perf/regression-detector.cjs +92 -0
- package/ez-agents/bin/lib/project-reporter.cjs +502 -0
- package/ez-agents/bin/lib/quality-gate.cjs +332 -0
- package/ez-agents/bin/lib/recovery-manager.cjs +98 -0
- package/ez-agents/bin/lib/release-validator.cjs +617 -614
- package/ez-agents/bin/lib/security-errors.cjs +62 -0
- package/ez-agents/bin/lib/session-chain.cjs +304 -304
- package/ez-agents/bin/lib/session-errors.cjs +81 -81
- package/ez-agents/bin/lib/session-export.cjs +251 -251
- package/ez-agents/bin/lib/session-import.cjs +262 -262
- package/ez-agents/bin/lib/session-manager.cjs +280 -280
- package/ez-agents/bin/lib/skill-context.cjs +148 -0
- package/ez-agents/bin/lib/skill-matcher.cjs +236 -0
- package/ez-agents/bin/lib/skill-registry.cjs +341 -0
- package/ez-agents/bin/lib/skill-resolver.cjs +449 -0
- package/ez-agents/bin/lib/skill-triggers.cjs +90 -0
- package/ez-agents/bin/lib/skill-validator.cjs +270 -0
- package/ez-agents/bin/lib/skill-versioning.cjs +355 -0
- package/ez-agents/bin/lib/stack-detector.cjs +399 -0
- package/ez-agents/bin/lib/tech-debt-analyzer.cjs +309 -0
- package/ez-agents/bin/lib/tier-manager.cjs +428 -428
- package/ez-agents/bin/lib/tradeoff-analyzer.cjs +284 -0
- package/ez-agents/bin/lib/url-fetch.cjs +170 -170
- package/ez-agents/bin/lib/verify.cjs +863 -863
- package/ez-agents/references/decimal-phase-calculation.md +65 -65
- package/ez-agents/references/git-integration.md +248 -248
- package/ez-agents/references/git-planning-commit.md +38 -38
- package/ez-agents/references/metrics-schema.md +118 -118
- package/ez-agents/references/model-profile-resolution.md +34 -34
- package/ez-agents/references/model-profiles.md +93 -93
- package/ez-agents/references/phase-argument-parsing.md +61 -61
- package/ez-agents/references/planning-config.md +340 -340
- package/ez-agents/references/tier-strategy.md +103 -103
- package/ez-agents/references/ui-brand.md +160 -160
- package/ez-agents/references/verification-patterns.md +612 -612
- package/ez-agents/templates/DEBUG.md +164 -164
- package/ez-agents/templates/UAT.md +247 -247
- package/ez-agents/templates/agent-output-format.md +404 -0
- package/ez-agents/templates/bdd-feature.md +173 -173
- package/ez-agents/templates/codebase/architecture.md +255 -255
- package/ez-agents/templates/codebase/structure.md +285 -285
- package/ez-agents/templates/copilot-instructions.md +7 -7
- package/ez-agents/templates/debug-subagent-prompt.md +91 -91
- package/ez-agents/templates/discovery.md +146 -146
- package/ez-agents/templates/discussion.md +68 -68
- package/ez-agents/templates/handoff-protocol.md +294 -0
- package/ez-agents/templates/incident-runbook.md +205 -205
- package/ez-agents/templates/mode-workflow-templates.md +301 -0
- package/ez-agents/templates/phase-prompt.md +610 -610
- package/ez-agents/templates/planner-subagent-prompt.md +117 -117
- package/ez-agents/templates/project.md +184 -184
- package/ez-agents/templates/release-checklist.md +136 -133
- package/ez-agents/templates/research.md +552 -552
- package/ez-agents/templates/rollback-plan.md +201 -201
- package/ez-agents/templates/security-user-setup.md +244 -0
- package/ez-agents/templates/skill-validation-rules.md +476 -0
- package/ez-agents/templates/state.md +180 -176
- package/ez-agents/templates/summary-complex.md +59 -59
- package/ez-agents/tests/gates/gate-01-02.test.cjs +812 -0
- package/ez-agents/tests/gates/gate-03-04.test.cjs +762 -0
- package/ez-agents/tests/gates/gate-05-validator.test.cjs +145 -0
- package/ez-agents/tests/gates/gate-06-docs-validator.test.cjs +244 -0
- package/ez-agents/tests/gates/gate-07-release-validator.test.cjs +219 -0
- package/ez-agents/tests/guards/context-budget-guard.test.cjs +145 -0
- package/ez-agents/tests/guards/edge-case-guards.test.cjs +238 -0
- package/ez-agents/tests/guards/hallucination-guard.test.cjs +124 -0
- package/ez-agents/workflows/audit-milestone.md +1 -1
- package/ez-agents/workflows/autonomous.md +844 -844
- package/ez-agents/workflows/complete-milestone.md +1 -1
- package/ez-agents/workflows/discuss-phase.md +1 -1
- package/ez-agents/workflows/execute-phase.md +124 -3
- package/ez-agents/workflows/help.md +42 -181
- package/ez-agents/workflows/hotfix.md +291 -291
- package/ez-agents/workflows/new-milestone.md +713 -713
- package/ez-agents/workflows/new-project.md +1089 -1107
- package/ez-agents/workflows/plan-phase.md +0 -40
- package/ez-agents/workflows/release.md +253 -253
- package/ez-agents/workflows/resume-session.md +215 -215
- package/ez-agents/workflows/run-phase.md +531 -0
- package/ez-agents/workflows/settings.md +2 -35
- package/hooks/dist/ez-check-update.js +81 -81
- package/hooks/dist/ez-context-monitor.js +148 -141
- package/hooks/dist/ez-statusline.js +115 -115
- package/package.json +78 -71
- package/scripts/fix-qwen-installation.js +144 -144
- package/agents/ez-integration-checker.md +0 -443
- package/agents/ez-nyquist-auditor.md +0 -176
- package/agents/ez-observer-agent.md +0 -260
- package/agents/ez-plan-checker.md +0 -706
- package/agents/ez-research-synthesizer.md +0 -247
- package/agents/ez-scrum-master-agent.md +0 -242
- package/agents/ez-tech-lead-agent.md +0 -267
- package/agents/ez-ui-auditor.md +0 -439
- package/agents/ez-ui-checker.md +0 -300
- package/agents/ez-ui-researcher.md +0 -353
- package/commands/ez/add-phase.md +0 -43
- package/commands/ez/add-todo.md +0 -47
- package/commands/ez/arch-review.md +0 -102
- package/commands/ez/auth.md +0 -87
- package/commands/ez/autonomous.md +0 -41
- package/commands/ez/check-todos.md +0 -45
- package/commands/ez/cleanup.md +0 -18
- package/commands/ez/debug.md +0 -168
- package/commands/ez/export-session.md +0 -79
- package/commands/ez/gather-requirements.md +0 -117
- package/commands/ez/git-workflow.md +0 -72
- package/commands/ez/health.md +0 -22
- package/commands/ez/hotfix.md +0 -120
- package/commands/ez/import-session.md +0 -82
- package/commands/ez/insert-phase.md +0 -32
- package/commands/ez/join-discord.md +0 -18
- package/commands/ez/list-phase-assumptions.md +0 -46
- package/commands/ez/list-sessions.md +0 -96
- package/commands/ez/package-manager.md +0 -316
- package/commands/ez/pause-work.md +0 -38
- package/commands/ez/plan-milestone-gaps.md +0 -34
- package/commands/ez/preflight.md +0 -79
- package/commands/ez/reapply-patches.md +0 -124
- package/commands/ez/release.md +0 -153
- package/commands/ez/remove-phase.md +0 -31
- package/commands/ez/research-phase.md +0 -190
- package/commands/ez/resume.md +0 -107
- package/commands/ez/set-profile.md +0 -34
- package/commands/ez/standup.md +0 -85
- package/commands/ez/stats.md +0 -18
- package/commands/ez/ui-phase.md +0 -34
- package/commands/ez/ui-review.md +0 -32
- package/commands/ez/validate-phase.md +0 -35
- package/ez-agents/bin/lib/metrics-tracker.cjs +0 -406
- package/ez-agents/templates/UI-SPEC.md +0 -100
- package/ez-agents/templates/VALIDATION.md +0 -76
- package/ez-agents/templates/context.md +0 -352
- package/ez-agents/templates/verification-report.md +0 -322
- package/ez-agents/workflows/arch-review.md +0 -54
- package/ez-agents/workflows/export-session.md +0 -255
- package/ez-agents/workflows/gather-requirements.md +0 -206
- package/ez-agents/workflows/import-session.md +0 -303
- package/ez-agents/workflows/research-phase.md +0 -74
- package/ez-agents/workflows/standup.md +0 -64
- package/ez-agents/workflows/ui-phase.md +0 -290
- package/ez-agents/workflows/ui-review.md +0 -157
- package/ez-agents/workflows/validate-phase.md +0 -167
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Business Flow Mapper — Analyzes user journeys, data flow, and integration points
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - map(rootPath, stack): Identifies user journeys from route/file structure
|
|
6
|
+
* - analyzeDataFlow(rootPath): Traces data flow through imports and function calls
|
|
7
|
+
* - findIntegrationPoints(stack): Identifies external API integrations from dependencies
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
class BusinessFlowMapper {
|
|
14
|
+
constructor(rootPath) {
|
|
15
|
+
this.rootPath = rootPath;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Map business flows from codebase structure
|
|
20
|
+
* @param {string} rootPath - Root directory to analyze
|
|
21
|
+
* @param {object} stack - Stack object from StackDetector
|
|
22
|
+
* @returns {object} Object with journeys and entryPoints
|
|
23
|
+
*/
|
|
24
|
+
map(rootPath = this.rootPath, stack = {}) {
|
|
25
|
+
const routes = this._findRoutes(rootPath);
|
|
26
|
+
const journeys = this._extractJourneys(routes, stack);
|
|
27
|
+
const entryPoints = this._findEntryPoints(rootPath);
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
journeys,
|
|
31
|
+
entryPoints,
|
|
32
|
+
routes
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Analyze data flow through the codebase
|
|
38
|
+
* @param {string} rootPath - Root directory to analyze
|
|
39
|
+
* @returns {object} Object with flows and dataStores
|
|
40
|
+
*/
|
|
41
|
+
analyzeDataFlow(rootPath = this.rootPath) {
|
|
42
|
+
const sourceFiles = this._getSourceFiles(rootPath);
|
|
43
|
+
const flows = this._traceDataFlow(sourceFiles, rootPath);
|
|
44
|
+
const dataStores = this._findDataStores(sourceFiles, rootPath);
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
flows,
|
|
48
|
+
dataStores
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Find integration points from stack dependencies
|
|
54
|
+
* @param {object} stack - Stack object from StackDetector
|
|
55
|
+
* @returns {object} Object with integrations array
|
|
56
|
+
*/
|
|
57
|
+
findIntegrationPoints(stack = {}) {
|
|
58
|
+
const integrations = [];
|
|
59
|
+
const frameworks = stack.frameworks || [];
|
|
60
|
+
const infrastructure = stack.infrastructure || [];
|
|
61
|
+
const databases = stack.databases || [];
|
|
62
|
+
|
|
63
|
+
// Payment integrations
|
|
64
|
+
if (frameworks.includes('Stripe') || infrastructure.some(i => i.includes('Stripe'))) {
|
|
65
|
+
integrations.push({
|
|
66
|
+
name: 'Stripe',
|
|
67
|
+
type: 'payment',
|
|
68
|
+
purpose: 'Payment processing and subscriptions'
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Cloud integrations
|
|
73
|
+
if (infrastructure.some(i => i.includes('AWS'))) {
|
|
74
|
+
integrations.push({
|
|
75
|
+
name: 'AWS',
|
|
76
|
+
type: 'cloud',
|
|
77
|
+
purpose: 'Cloud infrastructure and services'
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (infrastructure.some(i => i.includes('Azure'))) {
|
|
81
|
+
integrations.push({
|
|
82
|
+
name: 'Azure',
|
|
83
|
+
type: 'cloud',
|
|
84
|
+
purpose: 'Cloud infrastructure and services'
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (infrastructure.some(i => i.includes('Google Cloud'))) {
|
|
88
|
+
integrations.push({
|
|
89
|
+
name: 'Google Cloud',
|
|
90
|
+
type: 'cloud',
|
|
91
|
+
purpose: 'Cloud infrastructure and services'
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Monitoring integrations
|
|
96
|
+
if (infrastructure.some(i => i.includes('Sentry'))) {
|
|
97
|
+
integrations.push({
|
|
98
|
+
name: 'Sentry',
|
|
99
|
+
type: 'monitoring',
|
|
100
|
+
purpose: 'Error tracking and monitoring'
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (infrastructure.some(i => i.includes('Datadog'))) {
|
|
104
|
+
integrations.push({
|
|
105
|
+
name: 'Datadog',
|
|
106
|
+
type: 'monitoring',
|
|
107
|
+
purpose: 'Infrastructure and application monitoring'
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Email integrations
|
|
112
|
+
if (infrastructure.some(i => i.includes('SendGrid') || i.includes('Mailgun'))) {
|
|
113
|
+
integrations.push({
|
|
114
|
+
name: 'Email Service',
|
|
115
|
+
type: 'communication',
|
|
116
|
+
purpose: 'Transactional email delivery'
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Database integrations
|
|
121
|
+
for (const db of databases) {
|
|
122
|
+
integrations.push({
|
|
123
|
+
name: db,
|
|
124
|
+
type: 'database',
|
|
125
|
+
purpose: 'Data persistence'
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Auth integrations
|
|
130
|
+
if (frameworks.includes('NextAuth.js') || frameworks.includes('Auth.js')) {
|
|
131
|
+
integrations.push({
|
|
132
|
+
name: 'NextAuth.js',
|
|
133
|
+
type: 'authentication',
|
|
134
|
+
purpose: 'User authentication and authorization'
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
integrations
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Find route files in the codebase
|
|
145
|
+
* @private
|
|
146
|
+
*/
|
|
147
|
+
_findRoutes(rootPath) {
|
|
148
|
+
const routes = [];
|
|
149
|
+
const routePatterns = [
|
|
150
|
+
'src/pages',
|
|
151
|
+
'src/app',
|
|
152
|
+
'src/routes',
|
|
153
|
+
'pages',
|
|
154
|
+
'app',
|
|
155
|
+
'routes',
|
|
156
|
+
'src/controllers',
|
|
157
|
+
'controllers'
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
for (const pattern of routePatterns) {
|
|
161
|
+
const routeDir = path.join(rootPath, pattern);
|
|
162
|
+
if (fs.existsSync(routeDir)) {
|
|
163
|
+
this._collectRoutes(routeDir, routes, pattern);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return routes;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Collect routes from directory
|
|
172
|
+
* @private
|
|
173
|
+
*/
|
|
174
|
+
_collectRoutes(dir, routes, basePath, depth = 0) {
|
|
175
|
+
if (depth > 5) return;
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
179
|
+
for (const entry of entries) {
|
|
180
|
+
const fullPath = path.join(dir, entry.name);
|
|
181
|
+
if (entry.isDirectory()) {
|
|
182
|
+
this._collectRoutes(fullPath, routes, basePath, depth + 1);
|
|
183
|
+
} else if (entry.isFile() && /\.(ts|tsx|js|jsx)$/.test(entry.name)) {
|
|
184
|
+
const relativePath = path.relative(path.join(this.rootPath, basePath), fullPath);
|
|
185
|
+
const routePath = this._fileToRoute(relativePath);
|
|
186
|
+
routes.push({
|
|
187
|
+
path: routePath,
|
|
188
|
+
file: fullPath,
|
|
189
|
+
name: path.basename(entry.name, path.extname(entry.name))
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
} catch (err) {
|
|
194
|
+
// Ignore errors
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Convert file path to route path
|
|
200
|
+
* @private
|
|
201
|
+
*/
|
|
202
|
+
_fileToRoute(filePath) {
|
|
203
|
+
let route = filePath
|
|
204
|
+
.replace(/^(page|route|layout)\./, '')
|
|
205
|
+
.replace(/\.tsx?$/, '')
|
|
206
|
+
.replace(/\.jsx?$/, '')
|
|
207
|
+
.replace(/\\/g, '/')
|
|
208
|
+
.replace(/\/index$/, '')
|
|
209
|
+
.replace(/\[(\w+)\]/g, ':$1');
|
|
210
|
+
|
|
211
|
+
return '/' + route;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Extract user journeys from routes
|
|
216
|
+
* @private
|
|
217
|
+
*/
|
|
218
|
+
_extractJourneys(routes, stack) {
|
|
219
|
+
const journeys = [];
|
|
220
|
+
|
|
221
|
+
// Group routes by prefix to identify journeys
|
|
222
|
+
const routeGroups = {};
|
|
223
|
+
for (const route of routes) {
|
|
224
|
+
const parts = route.path.split('/').filter(p => p && !p.startsWith(':'));
|
|
225
|
+
if (parts.length > 0) {
|
|
226
|
+
const prefix = parts[0];
|
|
227
|
+
if (!routeGroups[prefix]) {
|
|
228
|
+
routeGroups[prefix] = [];
|
|
229
|
+
}
|
|
230
|
+
routeGroups[prefix].push(route);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Create journeys from route groups
|
|
235
|
+
for (const [prefix, groupRoutes] of Object.entries(routeGroups)) {
|
|
236
|
+
const journey = {
|
|
237
|
+
name: this._capitalize(prefix),
|
|
238
|
+
path: `/${prefix}`,
|
|
239
|
+
components: groupRoutes.map(r => r.name),
|
|
240
|
+
routes: groupRoutes.map(r => r.path)
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// Enhance with stack-specific info
|
|
244
|
+
if (stack.frameworks?.includes('Next.js')) {
|
|
245
|
+
journey.framework = 'Next.js Pages/App Router';
|
|
246
|
+
} else if (stack.frameworks?.includes('React')) {
|
|
247
|
+
journey.framework = 'React Router';
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
journeys.push(journey);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return journeys;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Find entry points in the codebase
|
|
258
|
+
* @private
|
|
259
|
+
*/
|
|
260
|
+
_findEntryPoints(rootPath) {
|
|
261
|
+
const entryPoints = [];
|
|
262
|
+
const entryPatterns = [
|
|
263
|
+
'src/index.ts',
|
|
264
|
+
'src/index.tsx',
|
|
265
|
+
'src/index.js',
|
|
266
|
+
'src/main.ts',
|
|
267
|
+
'src/main.tsx',
|
|
268
|
+
'src/app.tsx',
|
|
269
|
+
'src/app.ts',
|
|
270
|
+
'index.ts',
|
|
271
|
+
'index.tsx',
|
|
272
|
+
'index.js',
|
|
273
|
+
'main.ts',
|
|
274
|
+
'app.tsx'
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
for (const pattern of entryPatterns) {
|
|
278
|
+
const entryPath = path.join(rootPath, pattern);
|
|
279
|
+
if (fs.existsSync(entryPath)) {
|
|
280
|
+
entryPoints.push(entryPath);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return entryPoints;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Trace data flow through source files
|
|
289
|
+
* @private
|
|
290
|
+
*/
|
|
291
|
+
_traceDataFlow(sourceFiles, rootPath) {
|
|
292
|
+
const flows = [];
|
|
293
|
+
|
|
294
|
+
for (const file of sourceFiles.slice(0, 50)) { // Limit to 50 files
|
|
295
|
+
try {
|
|
296
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
297
|
+
|
|
298
|
+
// Look for data flow patterns
|
|
299
|
+
const importMatches = content.match(/import\s+.*?\s+from\s+['"](.+?)['"]/g) || [];
|
|
300
|
+
const exportMatches = content.match(/export\s+(default\s+)?(function|class|const|let|var)/g) || [];
|
|
301
|
+
|
|
302
|
+
if (importMatches.length > 0 || exportMatches.length > 0) {
|
|
303
|
+
flows.push({
|
|
304
|
+
file,
|
|
305
|
+
imports: importMatches.length,
|
|
306
|
+
exports: exportMatches.length,
|
|
307
|
+
transformations: this._detectTransformations(content)
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
} catch (err) {
|
|
311
|
+
// Ignore read errors
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return flows;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Detect data transformations in code
|
|
320
|
+
* @private
|
|
321
|
+
*/
|
|
322
|
+
_detectTransformations(content) {
|
|
323
|
+
const transformations = [];
|
|
324
|
+
|
|
325
|
+
// Look for common transformation patterns
|
|
326
|
+
const patterns = {
|
|
327
|
+
map: /\.map\(/,
|
|
328
|
+
filter: /\.filter\(/,
|
|
329
|
+
reduce: /\.reduce\(/,
|
|
330
|
+
transform: /\.transform\(/,
|
|
331
|
+
parse: /\.parse\(|JSON\.parse/,
|
|
332
|
+
stringify: /\.stringify\(|JSON\.stringify/
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
for (const [name, regex] of Object.entries(patterns)) {
|
|
336
|
+
if (regex.test(content)) {
|
|
337
|
+
transformations.push(name);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return transformations;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Find data stores in the codebase
|
|
346
|
+
* @private
|
|
347
|
+
*/
|
|
348
|
+
_findDataStores(sourceFiles, rootPath) {
|
|
349
|
+
const dataStores = [];
|
|
350
|
+
|
|
351
|
+
// Look for store patterns
|
|
352
|
+
const storePatterns = [
|
|
353
|
+
'src/stores',
|
|
354
|
+
'src/store',
|
|
355
|
+
'stores',
|
|
356
|
+
'store',
|
|
357
|
+
'src/models',
|
|
358
|
+
'models',
|
|
359
|
+
'src/db',
|
|
360
|
+
'db',
|
|
361
|
+
'src/database',
|
|
362
|
+
'database'
|
|
363
|
+
];
|
|
364
|
+
|
|
365
|
+
for (const pattern of storePatterns) {
|
|
366
|
+
const storePath = path.join(rootPath, pattern);
|
|
367
|
+
if (fs.existsSync(storePath)) {
|
|
368
|
+
dataStores.push({
|
|
369
|
+
type: 'store',
|
|
370
|
+
path: storePath,
|
|
371
|
+
name: pattern.split('/').pop()
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return dataStores;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Get source files from directory
|
|
381
|
+
* @private
|
|
382
|
+
*/
|
|
383
|
+
_getSourceFiles(rootPath) {
|
|
384
|
+
const files = [];
|
|
385
|
+
const srcDir = path.join(rootPath, 'src');
|
|
386
|
+
|
|
387
|
+
if (fs.existsSync(srcDir)) {
|
|
388
|
+
this._collectSourceFiles(srcDir, files);
|
|
389
|
+
} else {
|
|
390
|
+
this._collectSourceFiles(rootPath, files);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return files;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Collect source files from directory
|
|
398
|
+
* @private
|
|
399
|
+
*/
|
|
400
|
+
_collectSourceFiles(dir, files, depth = 0) {
|
|
401
|
+
if (depth > 5 || dir.includes('node_modules') || dir.includes('dist') || dir.includes('build')) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
try {
|
|
406
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
407
|
+
for (const entry of entries) {
|
|
408
|
+
const fullPath = path.join(dir, entry.name);
|
|
409
|
+
if (entry.isDirectory()) {
|
|
410
|
+
this._collectSourceFiles(fullPath, files, depth + 1);
|
|
411
|
+
} else if (entry.isFile() && /\.(ts|tsx|js|jsx)$/.test(entry.name)) {
|
|
412
|
+
files.push(fullPath);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
} catch (err) {
|
|
416
|
+
// Ignore errors
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Capitalize first letter
|
|
422
|
+
* @private
|
|
423
|
+
*/
|
|
424
|
+
_capitalize(str) {
|
|
425
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
module.exports = { BusinessFlowMapper };
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* EZ Circuit Breaker — Prevent cascading failures
|
|
5
|
+
*
|
|
6
|
+
* Implements circuit breaker pattern:
|
|
7
|
+
* - CLOSED: Normal operation
|
|
8
|
+
* - OPEN: Failing, reject requests
|
|
9
|
+
* - HALF_OPEN: Testing if service recovered
|
|
10
|
+
*
|
|
11
|
+
* Persists state to .planning/circuit-breaker.json for recovery across restarts
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* const CircuitBreaker = require('./circuit-breaker.cjs');
|
|
15
|
+
* const breaker = new CircuitBreaker({ failureThreshold: 5, cwd });
|
|
16
|
+
* const result = await breaker.execute(() => riskyOperation());
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const { withLock } = require('./file-lock.cjs');
|
|
22
|
+
const Logger = require('./logger.cjs');
|
|
23
|
+
const logger = new Logger();
|
|
24
|
+
|
|
25
|
+
class CircuitBreaker {
|
|
26
|
+
/**
|
|
27
|
+
* Create circuit breaker
|
|
28
|
+
* @param {Object} options - Configuration
|
|
29
|
+
* @param {number} [options.failureThreshold=5] - Number of failures before opening circuit
|
|
30
|
+
* @param {number} [options.resetTimeout=60000] - Time in ms before trying to recover
|
|
31
|
+
* @param {boolean} [options.persistState=true] - Whether to persist state to disk
|
|
32
|
+
* @param {string} [options.cwd] - Working directory (defaults to process.cwd())
|
|
33
|
+
* @param {string} [options.agentType] - Agent type identifier for per-agent state
|
|
34
|
+
*/
|
|
35
|
+
constructor(options = {}) {
|
|
36
|
+
this.failureThreshold = options.failureThreshold || 5;
|
|
37
|
+
this.resetTimeout = options.resetTimeout || 60000;
|
|
38
|
+
this.persistState = options.persistState !== false;
|
|
39
|
+
this.cwd = options.cwd || process.cwd();
|
|
40
|
+
this.agentType = options.agentType || 'default';
|
|
41
|
+
this.state = 'CLOSED';
|
|
42
|
+
this.failures = 0;
|
|
43
|
+
this.lastFailureTime = null;
|
|
44
|
+
this.successes = 0;
|
|
45
|
+
this.lastStateChange = new Date().toISOString();
|
|
46
|
+
|
|
47
|
+
// Load persisted state if available
|
|
48
|
+
if (this.persistState) {
|
|
49
|
+
this.loadState();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get state file path
|
|
55
|
+
* @returns {string} - Path to circuit-breaker.json
|
|
56
|
+
*/
|
|
57
|
+
getStateFile() {
|
|
58
|
+
return path.join(this.cwd, '.planning', 'circuit-breaker.json');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Load state from disk
|
|
63
|
+
* @returns {void}
|
|
64
|
+
*/
|
|
65
|
+
loadState() {
|
|
66
|
+
const stateFile = this.getStateFile();
|
|
67
|
+
if (!fs.existsSync(stateFile)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const data = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
73
|
+
const agentState = data[this.agentType];
|
|
74
|
+
|
|
75
|
+
if (agentState) {
|
|
76
|
+
this.state = agentState.state || 'CLOSED';
|
|
77
|
+
this.failures = agentState.failures || 0;
|
|
78
|
+
this.successes = agentState.successes || 0;
|
|
79
|
+
this.lastFailureTime = agentState.lastFailureTime || null;
|
|
80
|
+
this.lastStateChange = agentState.lastStateChange || new Date().toISOString();
|
|
81
|
+
}
|
|
82
|
+
} catch (err) {
|
|
83
|
+
logger.warn('circuit-breaker: failed to load state', { error: err.message });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Save state to disk
|
|
89
|
+
* @returns {Promise<void>}
|
|
90
|
+
*/
|
|
91
|
+
async saveState() {
|
|
92
|
+
if (!this.persistState) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const stateFile = this.getStateFile();
|
|
97
|
+
const planningDir = path.join(this.cwd, '.planning');
|
|
98
|
+
|
|
99
|
+
// Ensure .planning directory exists
|
|
100
|
+
if (!fs.existsSync(planningDir)) {
|
|
101
|
+
fs.mkdirSync(planningDir, { recursive: true });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await withLock(stateFile, async () => {
|
|
105
|
+
let data = {};
|
|
106
|
+
if (fs.existsSync(stateFile)) {
|
|
107
|
+
try {
|
|
108
|
+
data = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
109
|
+
} catch (e) {
|
|
110
|
+
data = {};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
data[this.agentType] = {
|
|
115
|
+
state: this.state,
|
|
116
|
+
failures: this.failures,
|
|
117
|
+
successes: this.successes,
|
|
118
|
+
failureThreshold: this.failureThreshold,
|
|
119
|
+
resetTimeout: this.resetTimeout,
|
|
120
|
+
lastFailureTime: this.lastFailureTime,
|
|
121
|
+
lastStateChange: this.lastStateChange
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
fs.writeFileSync(stateFile, JSON.stringify(data, null, 2), 'utf8');
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Log state transition to metrics
|
|
130
|
+
* @param {string} fromState - Previous state
|
|
131
|
+
* @param {string} toState - New state
|
|
132
|
+
* @private
|
|
133
|
+
*/
|
|
134
|
+
_logStateTransition(fromState, toState) {
|
|
135
|
+
const metricsPath = path.join(this.cwd, '.planning', 'metrics.json');
|
|
136
|
+
const event = {
|
|
137
|
+
event: 'circuit_breaker_state_change',
|
|
138
|
+
timestamp: new Date().toISOString(),
|
|
139
|
+
agentType: this.agentType,
|
|
140
|
+
fromState,
|
|
141
|
+
toState
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
let metrics = { events: [] };
|
|
146
|
+
if (fs.existsSync(metricsPath)) {
|
|
147
|
+
try {
|
|
148
|
+
metrics = JSON.parse(fs.readFileSync(metricsPath, 'utf8'));
|
|
149
|
+
if (!Array.isArray(metrics.events)) metrics.events = [];
|
|
150
|
+
} catch (e) {
|
|
151
|
+
metrics = { events: [] };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
metrics.events.push(event);
|
|
156
|
+
fs.writeFileSync(metricsPath, JSON.stringify(metrics, null, 2), 'utf8');
|
|
157
|
+
} catch (err) {
|
|
158
|
+
logger.error('circuit-breaker: failed to log state transition', { error: err.message });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Change state and persist
|
|
164
|
+
* @param {string} newState - New state
|
|
165
|
+
* @private
|
|
166
|
+
*/
|
|
167
|
+
async _changeState(newState) {
|
|
168
|
+
if (this.state === newState) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const oldState = this.state;
|
|
173
|
+
this.state = newState;
|
|
174
|
+
this.lastStateChange = new Date().toISOString();
|
|
175
|
+
|
|
176
|
+
this._logStateTransition(oldState, newState);
|
|
177
|
+
await this.saveState();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Execute operation with circuit breaker protection
|
|
182
|
+
* @param {Function} operation - Async function to execute
|
|
183
|
+
* @returns {Promise<any>} - Result of operation
|
|
184
|
+
*/
|
|
185
|
+
async execute(operation) {
|
|
186
|
+
// Check if circuit is OPEN
|
|
187
|
+
if (this.state === 'OPEN') {
|
|
188
|
+
const timeSinceFailure = Date.now() - this.lastFailureTime;
|
|
189
|
+
|
|
190
|
+
if (timeSinceFailure > this.resetTimeout) {
|
|
191
|
+
// Try to recover
|
|
192
|
+
await this._changeState('HALF_OPEN');
|
|
193
|
+
this.failures = 0;
|
|
194
|
+
logger.info('Circuit breaker HALF_OPEN - testing recovery');
|
|
195
|
+
} else {
|
|
196
|
+
const waitTime = Math.round((this.resetTimeout - timeSinceFailure) / 1000);
|
|
197
|
+
logger.warn('Circuit breaker OPEN - rejecting request', { waitTime });
|
|
198
|
+
throw new Error(`Circuit breaker is OPEN. Try again in ${waitTime}s`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
const result = await operation();
|
|
204
|
+
|
|
205
|
+
// Success - reset counters if in HALF_OPEN
|
|
206
|
+
if (this.state === 'HALF_OPEN') {
|
|
207
|
+
await this._changeState('CLOSED');
|
|
208
|
+
this.failures = 0;
|
|
209
|
+
logger.info('Circuit breaker CLOSED - service recovered');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
this.successes++;
|
|
213
|
+
await this.saveState();
|
|
214
|
+
return result;
|
|
215
|
+
} catch (err) {
|
|
216
|
+
this.failures++;
|
|
217
|
+
this.lastFailureTime = Date.now();
|
|
218
|
+
|
|
219
|
+
// Open circuit if threshold reached
|
|
220
|
+
if (this.failures >= this.failureThreshold) {
|
|
221
|
+
await this._changeState('OPEN');
|
|
222
|
+
logger.error('Circuit breaker OPEN - failure threshold reached', {
|
|
223
|
+
failures: this.failures
|
|
224
|
+
});
|
|
225
|
+
} else {
|
|
226
|
+
await this.saveState();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
throw err;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Get current state
|
|
235
|
+
* @returns {string} - CLOSED, OPEN, or HALF_OPEN
|
|
236
|
+
*/
|
|
237
|
+
getState() {
|
|
238
|
+
return this.state;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Get stats
|
|
243
|
+
* @returns {Object} - Statistics
|
|
244
|
+
*/
|
|
245
|
+
getStats() {
|
|
246
|
+
return {
|
|
247
|
+
state: this.state,
|
|
248
|
+
failures: this.failures,
|
|
249
|
+
successes: this.successes,
|
|
250
|
+
failureThreshold: this.failureThreshold,
|
|
251
|
+
resetTimeout: this.resetTimeout,
|
|
252
|
+
lastFailureTime: this.lastFailureTime,
|
|
253
|
+
lastStateChange: this.lastStateChange,
|
|
254
|
+
agentType: this.agentType
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Reset circuit breaker
|
|
260
|
+
* @returns {Promise<void>}
|
|
261
|
+
*/
|
|
262
|
+
async reset() {
|
|
263
|
+
const oldState = this.state;
|
|
264
|
+
this.state = 'CLOSED';
|
|
265
|
+
this.failures = 0;
|
|
266
|
+
this.successes = 0;
|
|
267
|
+
this.lastFailureTime = null;
|
|
268
|
+
this.lastStateChange = new Date().toISOString();
|
|
269
|
+
|
|
270
|
+
this._logStateTransition(oldState, 'CLOSED');
|
|
271
|
+
await this.saveState();
|
|
272
|
+
logger.info('Circuit breaker reset');
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
module.exports = CircuitBreaker;
|