@howlil/ez-agents 3.4.2 → 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 -462
- 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 -0
- package/agents/ez-requirements-agent.md +377 -0
- 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/install.js +3221 -3272
- 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 -41
- 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 -45
- package/commands/ez/progress.md +36 -24
- 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 +1692 -716
- 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 -0
- 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 -0
- package/ez-agents/bin/lib/context-cache.cjs +154 -0
- package/ez-agents/bin/lib/context-errors.cjs +71 -0
- package/ez-agents/bin/lib/context-manager.cjs +220 -0
- 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/discussion-synthesizer.cjs +458 -0
- package/ez-agents/bin/lib/file-access.cjs +207 -0
- 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 -0
- package/ez-agents/bin/lib/git-utils.cjs +118 -0
- package/ez-agents/bin/lib/git-workflow-engine.cjs +1157 -0
- package/ez-agents/bin/lib/health-check.cjs +162 -162
- package/ez-agents/bin/lib/index.cjs +40 -2
- package/ez-agents/bin/lib/init.cjs +0 -2
- package/ez-agents/bin/lib/lockfile-validator.cjs +227 -0
- package/ez-agents/bin/lib/log-rotation.cjs +71 -0
- package/ez-agents/bin/lib/logger.cjs +99 -154
- package/ez-agents/bin/lib/memory-compression.cjs +256 -0
- package/ez-agents/bin/lib/package-manager-detector.cjs +203 -0
- package/ez-agents/bin/lib/package-manager-executor.cjs +385 -0
- package/ez-agents/bin/lib/package-manager-service.cjs +216 -0
- 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 -0
- package/ez-agents/bin/lib/safe-exec.cjs +128 -214
- package/ez-agents/bin/lib/security-errors.cjs +62 -0
- package/ez-agents/bin/lib/session-chain.cjs +304 -0
- package/ez-agents/bin/lib/session-errors.cjs +81 -0
- package/ez-agents/bin/lib/session-export.cjs +251 -0
- package/ez-agents/bin/lib/session-import.cjs +262 -0
- package/ez-agents/bin/lib/session-manager.cjs +280 -0
- 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 -0
- package/ez-agents/bin/lib/tradeoff-analyzer.cjs +284 -0
- package/ez-agents/bin/lib/url-fetch.cjs +170 -0
- 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 -0
- 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 -200
- package/ez-agents/references/tier-strategy.md +103 -0
- 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 -0
- 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 -0
- package/ez-agents/templates/handoff-protocol.md +294 -0
- package/ez-agents/templates/incident-runbook.md +205 -0
- 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 -0
- package/ez-agents/templates/research.md +552 -552
- package/ez-agents/templates/rollback-plan.md +201 -0
- 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 +131 -30
- 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 +169 -3
- package/ez-agents/workflows/help.md +86 -133
- package/ez-agents/workflows/hotfix.md +291 -0
- package/ez-agents/workflows/new-milestone.md +340 -11
- package/ez-agents/workflows/new-project.md +294 -318
- package/ez-agents/workflows/plan-phase.md +22 -40
- package/ez-agents/workflows/progress.md +15 -25
- package/ez-agents/workflows/release.md +253 -0
- package/ez-agents/workflows/resume-session.md +215 -0
- 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 -64
- 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-plan-checker.md +0 -706
- package/agents/ez-research-synthesizer.md +0 -247
- 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/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/health.md +0 -22
- 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/pause-work.md +0 -38
- package/commands/ez/plan-milestone-gaps.md +0 -34
- package/commands/ez/reapply-patches.md +0 -124
- package/commands/ez/remove-phase.md +0 -31
- package/commands/ez/research-phase.md +0 -190
- package/commands/ez/set-profile.md +0 -34
- 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/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/research-phase.md +0 -74
- 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,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality Gate Coordinator
|
|
3
|
+
*
|
|
4
|
+
* Central registry and execution engine for quality gates.
|
|
5
|
+
* Supports gate registration, execution, bypass with audit trail, and status reporting.
|
|
6
|
+
* Uses Zod for schema validation of gate inputs/outputs.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { z } = require('zod');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get the audit trail file path (computed at runtime to support cwd changes)
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
function getAuditFilePath() {
|
|
18
|
+
return path.join(process.cwd(), '.planning', 'gate-audit.json');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {Object} GateDefinition
|
|
23
|
+
* @property {string} id - Gate identifier
|
|
24
|
+
* @property {z.ZodSchema} schema - Zod schema for context validation
|
|
25
|
+
* @property {Function} executor - Gate execution function
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {Object} GateStatus
|
|
30
|
+
* @property {'registered' | 'passed' | 'failed' | 'bypassed'} state - Current gate state
|
|
31
|
+
* @property {string} [id] - Gate identifier
|
|
32
|
+
* @property {Date} [registeredAt] - Registration timestamp
|
|
33
|
+
* @property {Date} [executedAt] - Execution timestamp
|
|
34
|
+
* @property {Date} [bypassedAt] - Bypass timestamp
|
|
35
|
+
* @property {string} [bypassReason] - Reason for bypass
|
|
36
|
+
* @property {Array} [errors] - Errors from last execution
|
|
37
|
+
* @property {Array} [warnings] - Warnings from last execution
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @typedef {Object} ExecutionResult
|
|
42
|
+
* @property {boolean} passed - Whether the gate passed
|
|
43
|
+
* @property {Array<{path: string, message: string}>} [errors] - Validation or execution errors
|
|
44
|
+
* @property {Array<string>} [warnings] - Warnings
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {Object} AuditEntry
|
|
49
|
+
* @property {string} gateId - Gate identifier
|
|
50
|
+
* @property {'bypass'} action - Action type
|
|
51
|
+
* @property {string} reason - Reason for bypass
|
|
52
|
+
* @property {string} timestamp - ISO timestamp
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
class QualityGate {
|
|
56
|
+
/**
|
|
57
|
+
* @type {Map<string, GateDefinition>}
|
|
58
|
+
*/
|
|
59
|
+
#gates;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @type {Map<string, GateStatus>}
|
|
63
|
+
*/
|
|
64
|
+
#status;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @type {Array<AuditEntry>}
|
|
68
|
+
*/
|
|
69
|
+
#auditTrail;
|
|
70
|
+
|
|
71
|
+
constructor() {
|
|
72
|
+
this.#gates = new Map();
|
|
73
|
+
this.#status = new Map();
|
|
74
|
+
this.#auditTrail = this.#loadAuditTrail();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Load audit trail from file
|
|
79
|
+
* @returns {Array<AuditEntry>}
|
|
80
|
+
*/
|
|
81
|
+
#loadAuditTrail() {
|
|
82
|
+
const auditFilePath = getAuditFilePath();
|
|
83
|
+
try {
|
|
84
|
+
if (fs.existsSync(auditFilePath)) {
|
|
85
|
+
const content = fs.readFileSync(auditFilePath, 'utf-8');
|
|
86
|
+
return JSON.parse(content);
|
|
87
|
+
}
|
|
88
|
+
} catch (err) {
|
|
89
|
+
// If file is corrupted or unreadable, start fresh
|
|
90
|
+
console.warn('Warning: Could not load gate audit trail, starting fresh');
|
|
91
|
+
}
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Save audit trail to file
|
|
97
|
+
*/
|
|
98
|
+
#saveAuditTrail() {
|
|
99
|
+
const auditFilePath = getAuditFilePath();
|
|
100
|
+
try {
|
|
101
|
+
const dir = path.dirname(auditFilePath);
|
|
102
|
+
if (!fs.existsSync(dir)) {
|
|
103
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
104
|
+
}
|
|
105
|
+
fs.writeFileSync(auditFilePath, JSON.stringify(this.#auditTrail, null, 2), 'utf-8');
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.error('Error saving gate audit trail:', err.message);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Format Zod validation errors into structured array with field paths
|
|
113
|
+
* @param {z.ZodError} zodError
|
|
114
|
+
* @returns {Array<{path: string, message: string}>}
|
|
115
|
+
*/
|
|
116
|
+
#formatValidationErrors(zodError) {
|
|
117
|
+
const errors = [];
|
|
118
|
+
for (const issue of zodError.errors) {
|
|
119
|
+
const fieldPath = issue.path.join('.');
|
|
120
|
+
errors.push({
|
|
121
|
+
path: fieldPath || 'root',
|
|
122
|
+
message: issue.message,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return errors;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Register a quality gate
|
|
130
|
+
* @param {string} id - Unique gate identifier
|
|
131
|
+
* @param {z.ZodSchema} schema - Zod schema for context validation
|
|
132
|
+
* @param {Function} executor - Async function that executes the gate logic
|
|
133
|
+
* @returns {void}
|
|
134
|
+
*/
|
|
135
|
+
registerGate(id, schema, executor) {
|
|
136
|
+
if (!id || typeof id !== 'string') {
|
|
137
|
+
throw new Error('Gate ID must be a non-empty string');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!schema || !schema.safeParse) {
|
|
141
|
+
throw new Error('Schema must be a valid Zod schema');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (typeof executor !== 'function') {
|
|
145
|
+
throw new Error('Executor must be a function');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this.#gates.set(id, { id, schema, executor });
|
|
149
|
+
this.#status.set(id, {
|
|
150
|
+
state: 'registered',
|
|
151
|
+
id,
|
|
152
|
+
registeredAt: new Date(),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Execute a quality gate
|
|
158
|
+
* @param {string} id - Gate identifier
|
|
159
|
+
* @param {any} context - Context data to validate and pass to executor
|
|
160
|
+
* @returns {Promise<ExecutionResult>}
|
|
161
|
+
*/
|
|
162
|
+
async executeGate(id, context) {
|
|
163
|
+
const gate = this.#gates.get(id);
|
|
164
|
+
|
|
165
|
+
if (!gate) {
|
|
166
|
+
throw new Error(`Gate not registered: ${id}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Validate context against schema
|
|
170
|
+
const parseResult = gate.schema.safeParse(context);
|
|
171
|
+
|
|
172
|
+
if (!parseResult.success) {
|
|
173
|
+
const errors = this.#formatValidationErrors(parseResult.error);
|
|
174
|
+
const status = {
|
|
175
|
+
...this.#status.get(id),
|
|
176
|
+
state: 'failed',
|
|
177
|
+
executedAt: new Date(),
|
|
178
|
+
errors,
|
|
179
|
+
};
|
|
180
|
+
this.#status.set(id, status);
|
|
181
|
+
return { passed: false, errors, warnings: [] };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Run executor with validated context
|
|
185
|
+
try {
|
|
186
|
+
const result = await gate.executor(parseResult.data);
|
|
187
|
+
|
|
188
|
+
// Handle executor result
|
|
189
|
+
if (result.passed) {
|
|
190
|
+
const status = {
|
|
191
|
+
...this.#status.get(id),
|
|
192
|
+
state: 'passed',
|
|
193
|
+
executedAt: new Date(),
|
|
194
|
+
errors: [],
|
|
195
|
+
warnings: result.warnings || [],
|
|
196
|
+
};
|
|
197
|
+
this.#status.set(id, status);
|
|
198
|
+
return { passed: true, errors: [], warnings: result.warnings || [] };
|
|
199
|
+
} else {
|
|
200
|
+
const errors = result.errors || [{ path: 'executor', message: 'Gate execution failed' }];
|
|
201
|
+
const status = {
|
|
202
|
+
...this.#status.get(id),
|
|
203
|
+
state: 'failed',
|
|
204
|
+
executedAt: new Date(),
|
|
205
|
+
errors,
|
|
206
|
+
warnings: result.warnings || [],
|
|
207
|
+
};
|
|
208
|
+
this.#status.set(id, status);
|
|
209
|
+
return { passed: false, errors, warnings: result.warnings || [] };
|
|
210
|
+
}
|
|
211
|
+
} catch (err) {
|
|
212
|
+
const errors = [{ path: 'executor', message: err.message || 'Executor threw an exception' }];
|
|
213
|
+
const status = {
|
|
214
|
+
...this.#status.get(id),
|
|
215
|
+
state: 'failed',
|
|
216
|
+
executedAt: new Date(),
|
|
217
|
+
errors,
|
|
218
|
+
};
|
|
219
|
+
this.#status.set(id, status);
|
|
220
|
+
return { passed: false, errors, warnings: [] };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Bypass a quality gate with mandatory audit reason
|
|
226
|
+
* @param {string} id - Gate identifier
|
|
227
|
+
* @param {string} reason - Reason for bypass (cannot be empty)
|
|
228
|
+
* @returns {void}
|
|
229
|
+
*/
|
|
230
|
+
bypassGate(id, reason) {
|
|
231
|
+
const gate = this.#gates.get(id);
|
|
232
|
+
|
|
233
|
+
if (!gate) {
|
|
234
|
+
throw new Error(`Gate not registered: ${id}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!reason || typeof reason !== 'string' || reason.trim() === '') {
|
|
238
|
+
throw new Error('Bypass reason must be a non-empty string');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const status = {
|
|
242
|
+
...this.#status.get(id),
|
|
243
|
+
state: 'bypassed',
|
|
244
|
+
bypassedAt: new Date(),
|
|
245
|
+
bypassReason: reason.trim(),
|
|
246
|
+
};
|
|
247
|
+
this.#status.set(id, status);
|
|
248
|
+
|
|
249
|
+
// Log to audit trail
|
|
250
|
+
const auditEntry = {
|
|
251
|
+
gateId: id,
|
|
252
|
+
action: 'bypass',
|
|
253
|
+
reason: reason.trim(),
|
|
254
|
+
timestamp: new Date().toISOString(),
|
|
255
|
+
};
|
|
256
|
+
this.#auditTrail.push(auditEntry);
|
|
257
|
+
this.#saveAuditTrail();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Get the current status of a gate
|
|
262
|
+
* @param {string} id - Gate identifier
|
|
263
|
+
* @returns {GateStatus}
|
|
264
|
+
*/
|
|
265
|
+
getGateStatus(id) {
|
|
266
|
+
const status = this.#status.get(id);
|
|
267
|
+
|
|
268
|
+
if (!status) {
|
|
269
|
+
throw new Error(`Gate not registered: ${id}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return { ...status };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Get all registered gate IDs
|
|
277
|
+
* @returns {Array<string>}
|
|
278
|
+
*/
|
|
279
|
+
getRegisteredGates() {
|
|
280
|
+
return Array.from(this.#gates.keys());
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get the audit trail
|
|
285
|
+
* @returns {Array<AuditEntry>}
|
|
286
|
+
*/
|
|
287
|
+
getAuditTrail() {
|
|
288
|
+
return [...this.#auditTrail];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Check if a gate is registered
|
|
293
|
+
* @param {string} id - Gate identifier
|
|
294
|
+
* @returns {boolean}
|
|
295
|
+
*/
|
|
296
|
+
isRegistered(id) {
|
|
297
|
+
return this.#gates.has(id);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Reset gate status (for testing)
|
|
302
|
+
* @param {string} id - Gate identifier
|
|
303
|
+
* @returns {void}
|
|
304
|
+
*/
|
|
305
|
+
resetGate(id) {
|
|
306
|
+
const gate = this.#gates.get(id);
|
|
307
|
+
if (gate) {
|
|
308
|
+
this.#status.set(id, {
|
|
309
|
+
state: 'registered',
|
|
310
|
+
id,
|
|
311
|
+
registeredAt: new Date(),
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Clear all gates and audit trail (for testing)
|
|
318
|
+
* @returns {void}
|
|
319
|
+
*/
|
|
320
|
+
clear() {
|
|
321
|
+
this.#gates.clear();
|
|
322
|
+
this.#status.clear();
|
|
323
|
+
this.#auditTrail = [];
|
|
324
|
+
// Also clear the audit file
|
|
325
|
+
const auditFilePath = getAuditFilePath();
|
|
326
|
+
if (fs.existsSync(auditFilePath)) {
|
|
327
|
+
fs.writeFileSync(auditFilePath, '[]', 'utf-8');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
module.exports = { QualityGate, z };
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Quality Metrics — Track quality gate metrics over time
|
|
5
|
+
*
|
|
6
|
+
* Provides:
|
|
7
|
+
* - Pass/fail rate tracking per gate
|
|
8
|
+
* - Trend analysis (improving, stable, degrading)
|
|
9
|
+
* - Quality reports per phase
|
|
10
|
+
* - Overall quality scoring
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* const QualityMetrics = require('./quality-metrics.cjs');
|
|
14
|
+
* const metrics = new QualityMetrics();
|
|
15
|
+
* metrics.recordGateResult('test', true, 5000);
|
|
16
|
+
* const report = metrics.getReport();
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const Logger = require('./logger.cjs');
|
|
22
|
+
const logger = new Logger();
|
|
23
|
+
|
|
24
|
+
const METRICS_PATH = path.join(process.cwd(), '.planning', 'quality-metrics.json');
|
|
25
|
+
const REPORTS_DIR = path.join(process.cwd(), '.planning', 'reports');
|
|
26
|
+
|
|
27
|
+
class QualityMetrics {
|
|
28
|
+
constructor() {
|
|
29
|
+
this.data = this.load();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Load metrics from disk
|
|
34
|
+
*/
|
|
35
|
+
load() {
|
|
36
|
+
try {
|
|
37
|
+
if (fs.existsSync(METRICS_PATH)) {
|
|
38
|
+
const data = JSON.parse(fs.readFileSync(METRICS_PATH, 'utf8'));
|
|
39
|
+
return data;
|
|
40
|
+
}
|
|
41
|
+
} catch (err) {
|
|
42
|
+
logger.warn('Failed to load quality metrics, starting fresh', { error: err.message });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
gates: {},
|
|
47
|
+
phases: {},
|
|
48
|
+
overall: {
|
|
49
|
+
peakScore: 0,
|
|
50
|
+
currentScore: 0
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Save metrics to disk
|
|
57
|
+
*/
|
|
58
|
+
save() {
|
|
59
|
+
try {
|
|
60
|
+
const dir = path.dirname(METRICS_PATH);
|
|
61
|
+
if (!fs.existsSync(dir)) {
|
|
62
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this.data.lastUpdated = new Date().toISOString();
|
|
66
|
+
fs.writeFileSync(METRICS_PATH, JSON.stringify(this.data, null, 2), 'utf8');
|
|
67
|
+
logger.debug('Quality metrics saved');
|
|
68
|
+
} catch (err) {
|
|
69
|
+
logger.error('Failed to save quality metrics', { error: err.message });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Record gate result
|
|
75
|
+
* @param {string} gateId - Gate identifier
|
|
76
|
+
* @param {boolean} passed - Whether gate passed
|
|
77
|
+
* @param {number} duration - Execution time in ms
|
|
78
|
+
*/
|
|
79
|
+
recordGateResult(gateId, passed, duration = 0) {
|
|
80
|
+
if (!this.data.gates[gateId]) {
|
|
81
|
+
this.data.gates[gateId] = { history: [] };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const entry = {
|
|
85
|
+
passed,
|
|
86
|
+
duration,
|
|
87
|
+
timestamp: new Date().toISOString()
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
this.data.gates[gateId].history.push(entry);
|
|
91
|
+
|
|
92
|
+
// Keep last 100 runs
|
|
93
|
+
this.data.gates[gateId].history = this.data.gates[gateId].history.slice(-100);
|
|
94
|
+
|
|
95
|
+
this.save();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get pass rate for a gate
|
|
100
|
+
* @param {string} gateId - Gate identifier
|
|
101
|
+
* @returns {number} Pass rate (0-1)
|
|
102
|
+
*/
|
|
103
|
+
getPassRate(gateId) {
|
|
104
|
+
const history = this.data.gates[gateId]?.history || [];
|
|
105
|
+
if (history.length === 0) return 1.0;
|
|
106
|
+
|
|
107
|
+
const passed = history.filter(h => h.passed).length;
|
|
108
|
+
return passed / history.length;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get trend for a gate
|
|
113
|
+
* @param {string} gateId - Gate identifier
|
|
114
|
+
* @returns {string} Trend: 'improving', 'stable', 'degrading', 'insufficient_data'
|
|
115
|
+
*/
|
|
116
|
+
getTrend(gateId) {
|
|
117
|
+
const history = this.data.gates[gateId]?.history || [];
|
|
118
|
+
if (history.length < 5) return 'insufficient_data';
|
|
119
|
+
|
|
120
|
+
const recent = history.slice(-5);
|
|
121
|
+
const older = history.slice(-10, -5);
|
|
122
|
+
|
|
123
|
+
const recentRate = recent.filter(h => h.passed).length / recent.length;
|
|
124
|
+
const olderRate = older.filter(h => h.passed).length / older.length;
|
|
125
|
+
|
|
126
|
+
if (recentRate > olderRate + 0.1) return 'improving';
|
|
127
|
+
if (recentRate < olderRate - 0.1) return 'degrading';
|
|
128
|
+
return 'stable';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Calculate overall quality score
|
|
133
|
+
* @returns {number} Score (0-100)
|
|
134
|
+
*/
|
|
135
|
+
calculateOverallScore() {
|
|
136
|
+
const gateIds = Object.keys(this.data.gates);
|
|
137
|
+
if (gateIds.length === 0) return 100;
|
|
138
|
+
|
|
139
|
+
let totalScore = 0;
|
|
140
|
+
for (const gateId of gateIds) {
|
|
141
|
+
totalScore += this.getPassRate(gateId) * 100;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const score = Math.round(totalScore / gateIds.length);
|
|
145
|
+
|
|
146
|
+
// Track peak and current
|
|
147
|
+
this.data.overall.currentScore = score;
|
|
148
|
+
if (score > this.data.overall.peakScore) {
|
|
149
|
+
this.data.overall.peakScore = score;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this.save();
|
|
153
|
+
return score;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get quality report
|
|
158
|
+
* @returns {Object} Quality report
|
|
159
|
+
*/
|
|
160
|
+
getReport() {
|
|
161
|
+
const report = {
|
|
162
|
+
gates: {},
|
|
163
|
+
overall: {
|
|
164
|
+
score: this.calculateOverallScore(),
|
|
165
|
+
peakScore: this.data.overall.peakScore,
|
|
166
|
+
trend: this.getOverallTrend()
|
|
167
|
+
},
|
|
168
|
+
generated: new Date().toISOString()
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
for (const [gateId, data] of Object.entries(this.data.gates)) {
|
|
172
|
+
report.gates[gateId] = {
|
|
173
|
+
passRate: this.getPassRate(gateId),
|
|
174
|
+
trend: this.getTrend(gateId),
|
|
175
|
+
totalRuns: data.history.length,
|
|
176
|
+
avgDuration: this.getAverageDuration(gateId)
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return report;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get overall trend
|
|
185
|
+
* @returns {string} Overall trend
|
|
186
|
+
*/
|
|
187
|
+
getOverallTrend() {
|
|
188
|
+
const gateIds = Object.keys(this.data.gates);
|
|
189
|
+
if (gateIds.length === 0) return 'stable';
|
|
190
|
+
|
|
191
|
+
const trends = gateIds.map(id => this.getTrend(id));
|
|
192
|
+
const degrading = trends.filter(t => t === 'degrading').length;
|
|
193
|
+
const improving = trends.filter(t => t === 'improving').length;
|
|
194
|
+
|
|
195
|
+
if (degrading > improving) return 'degrading';
|
|
196
|
+
if (improving > degrading) return 'improving';
|
|
197
|
+
return 'stable';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get average duration for a gate
|
|
202
|
+
* @param {string} gateId - Gate identifier
|
|
203
|
+
* @returns {number} Average duration in ms
|
|
204
|
+
*/
|
|
205
|
+
getAverageDuration(gateId) {
|
|
206
|
+
const history = this.data.gates[gateId]?.history || [];
|
|
207
|
+
if (history.length === 0) return 0;
|
|
208
|
+
|
|
209
|
+
const total = history.reduce((sum, h) => sum + (h.duration || 0), 0);
|
|
210
|
+
return Math.round(total / history.length);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Generate quality report for a phase
|
|
215
|
+
* @param {string} phaseId - Phase identifier
|
|
216
|
+
* @returns {string} Markdown report
|
|
217
|
+
*/
|
|
218
|
+
generatePhaseReport(phaseId) {
|
|
219
|
+
const report = this.getReport();
|
|
220
|
+
|
|
221
|
+
const markdown = `# Quality Report: Phase ${phaseId}
|
|
222
|
+
|
|
223
|
+
**Generated:** ${new Date().toISOString()}
|
|
224
|
+
|
|
225
|
+
## Summary
|
|
226
|
+
|
|
227
|
+
| Metric | Value |
|
|
228
|
+
|--------|-------|
|
|
229
|
+
| Overall Score | ${report.overall.score}/100 |
|
|
230
|
+
| Peak Score | ${report.overall.peakScore}/100 |
|
|
231
|
+
| Trend | ${report.overall.trend} |
|
|
232
|
+
|
|
233
|
+
## Gate Performance
|
|
234
|
+
|
|
235
|
+
| Gate | Pass Rate | Trend | Avg Duration |
|
|
236
|
+
|------|-----------|-------|--------------|
|
|
237
|
+
${Object.entries(report.gates).map(([gate, data]) =>
|
|
238
|
+
`| ${gate} | ${(data.passRate * 100).toFixed(1)}% | ${data.trend} | ${data.avgDuration}ms |`
|
|
239
|
+
).join('\n')}
|
|
240
|
+
|
|
241
|
+
## Recommendations
|
|
242
|
+
|
|
243
|
+
${this.generateRecommendations(report)}
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
*Report generated by EZ Agents Quality Metrics*
|
|
247
|
+
`;
|
|
248
|
+
|
|
249
|
+
// Save report
|
|
250
|
+
try {
|
|
251
|
+
const dir = REPORTS_DIR;
|
|
252
|
+
if (!fs.existsSync(dir)) {
|
|
253
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
254
|
+
}
|
|
255
|
+
const reportPath = path.join(dir, `quality-phase-${phaseId}.md`);
|
|
256
|
+
fs.writeFileSync(reportPath, markdown, 'utf8');
|
|
257
|
+
logger.info('Quality report generated', { path: reportPath });
|
|
258
|
+
} catch (err) {
|
|
259
|
+
logger.error('Failed to save quality report', { error: err.message });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return markdown;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Generate recommendations based on report
|
|
267
|
+
* @param {Object} report - Quality report
|
|
268
|
+
* @returns {string} Recommendations
|
|
269
|
+
*/
|
|
270
|
+
generateRecommendations(report) {
|
|
271
|
+
const recommendations = [];
|
|
272
|
+
|
|
273
|
+
for (const [gateId, data] of Object.entries(report.gates)) {
|
|
274
|
+
if (data.passRate < 0.8) {
|
|
275
|
+
recommendations.push(`- **${gateId}**: Low pass rate (${(data.passRate * 100).toFixed(1)}%). Review gate configuration and fix flaky tests.`);
|
|
276
|
+
}
|
|
277
|
+
if (data.trend === 'degrading') {
|
|
278
|
+
recommendations.push(`- **${gateId}**: Quality degrading. Investigate recent changes.`);
|
|
279
|
+
}
|
|
280
|
+
if (data.avgDuration > 60000) {
|
|
281
|
+
recommendations.push(`- **${gateId}**: Slow execution (${data.avgDuration}ms). Consider optimization.`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (report.overall.trend === 'degrading') {
|
|
286
|
+
recommendations.push('- **Overall**: Quality trend is degrading. Consider pausing feature work and addressing technical debt.');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (recommendations.length === 0) {
|
|
290
|
+
return 'No specific recommendations. Quality metrics are healthy.';
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return recommendations.join('\n');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Check if quality allows phase completion
|
|
298
|
+
* @returns {Object} Result with allowed flag and reasons
|
|
299
|
+
*/
|
|
300
|
+
canCompletePhase() {
|
|
301
|
+
const report = this.getReport();
|
|
302
|
+
const reasons = [];
|
|
303
|
+
|
|
304
|
+
// Check for degrading quality
|
|
305
|
+
const degradingGates = Object.entries(report.gates)
|
|
306
|
+
.filter(([_, data]) => data.trend === 'degrading');
|
|
307
|
+
|
|
308
|
+
if (degradingGates.length > 0) {
|
|
309
|
+
reasons.push(`Quality degrading on ${degradingGates.length} gate(s): ${degradingGates.map(([id, _]) => id).join(', ')}`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Check overall score
|
|
313
|
+
if (report.overall.score < 70) {
|
|
314
|
+
reasons.push(`Overall quality score too low: ${report.overall.score}/100 (minimum: 70)`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
allowed: reasons.length === 0,
|
|
319
|
+
reasons
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
module.exports = { QualityMetrics };
|