@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,617 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Release Validator — Automated release readiness validation
|
|
5
|
+
*
|
|
6
|
+
* Runs security gates, tier checklist validation, and produces
|
|
7
|
+
* a Production Readiness Score (0-100) for /ez:release.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const { execSync } = require('child_process');
|
|
15
|
+
const TierManager = require('./tier-manager.cjs');
|
|
16
|
+
|
|
17
|
+
// ─────────────────────────────────────────────
|
|
18
|
+
// Security Helpers
|
|
19
|
+
// ─────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Calculate Shannon entropy for a string — used to detect high-entropy secrets
|
|
23
|
+
* @param {string} str
|
|
24
|
+
* @param {number} threshold
|
|
25
|
+
* @returns {boolean}
|
|
26
|
+
*/
|
|
27
|
+
function hasHighEntropy(str, threshold = 4.5) {
|
|
28
|
+
const freq = {};
|
|
29
|
+
for (const c of str) freq[c] = (freq[c] || 0) + 1;
|
|
30
|
+
const len = str.length;
|
|
31
|
+
let entropy = 0;
|
|
32
|
+
for (const c in freq) {
|
|
33
|
+
const p = freq[c] / len;
|
|
34
|
+
entropy -= p * Math.log2(p);
|
|
35
|
+
}
|
|
36
|
+
return entropy > threshold;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─────────────────────────────────────────────
|
|
40
|
+
// Security Gates
|
|
41
|
+
// ─────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Run all security gates
|
|
45
|
+
* @param {string} cwd - Working directory
|
|
46
|
+
* @returns {{ passed: boolean, gates: object[] }}
|
|
47
|
+
*/
|
|
48
|
+
function runSecurityGates(cwd = process.cwd()) {
|
|
49
|
+
const gates = [];
|
|
50
|
+
|
|
51
|
+
// Gate 1: Multi-pattern secret detection
|
|
52
|
+
try {
|
|
53
|
+
const secretPatterns = [
|
|
54
|
+
// Original + variants
|
|
55
|
+
'(api[_-]?key|api[_-]?k[e3]y|password|passw[0o]rd|s[e3]cr[e3]t|auth[_-]?token)',
|
|
56
|
+
// High-value secret types
|
|
57
|
+
'(bearer|private[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret)',
|
|
58
|
+
// Known formats: AWS
|
|
59
|
+
'AKIA[0-9A-Z]{16}',
|
|
60
|
+
// GitHub PAT
|
|
61
|
+
'ghp_[a-zA-Z0-9]{36}',
|
|
62
|
+
// JWT pattern
|
|
63
|
+
'eyJ[a-zA-Z0-9-_=]+\\.[a-zA-Z0-9-_=]+\\.'
|
|
64
|
+
];
|
|
65
|
+
const secretPattern = secretPatterns.join('|');
|
|
66
|
+
const result = execSync(
|
|
67
|
+
`git grep -i -E "${secretPattern}" HEAD 2>/dev/null | grep -v "example\\|placeholder\\|your-key\\|process\\.env\\|env\\.\\|config\\." | wc -l`,
|
|
68
|
+
{ cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
69
|
+
).trim();
|
|
70
|
+
const count = parseInt(result) || 0;
|
|
71
|
+
gates.push({
|
|
72
|
+
name: 'no_secrets',
|
|
73
|
+
label: 'No secrets in committed files',
|
|
74
|
+
passed: count === 0,
|
|
75
|
+
blocking: true,
|
|
76
|
+
detail: count === 0 ? 'Clean' : `${count} potential secret(s) found`
|
|
77
|
+
});
|
|
78
|
+
} catch {
|
|
79
|
+
gates.push({ name: 'no_secrets', label: 'No secrets in committed files', passed: true, blocking: true, detail: 'Check skipped (git not available)' });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Gate 2: npm audit
|
|
83
|
+
try {
|
|
84
|
+
execSync('npm audit --audit-level=critical 2>/dev/null', { cwd, stdio: 'pipe' });
|
|
85
|
+
gates.push({ name: 'npm_audit', label: 'npm audit — no critical vulnerabilities', passed: true, blocking: true, detail: 'Clean' });
|
|
86
|
+
} catch (err) {
|
|
87
|
+
const output = err.stdout ? err.stdout.toString() : '';
|
|
88
|
+
const criticals = (output.match(/critical/gi) || []).length;
|
|
89
|
+
gates.push({
|
|
90
|
+
name: 'npm_audit',
|
|
91
|
+
label: 'npm audit — no critical vulnerabilities',
|
|
92
|
+
passed: false,
|
|
93
|
+
blocking: true,
|
|
94
|
+
detail: `${criticals} critical vulnerability issue(s). Run: npm audit fix`
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Gate 3: No production TODOs
|
|
99
|
+
try {
|
|
100
|
+
const srcDirs = ['src', 'lib', 'app', 'server'].filter(d => fs.existsSync(path.join(cwd, d)));
|
|
101
|
+
if (srcDirs.length > 0) {
|
|
102
|
+
const searchDirs = srcDirs.join(' ');
|
|
103
|
+
const result = execSync(
|
|
104
|
+
`grep -rn "TODO\\|FIXME\\|HACK\\|XXX" ${searchDirs} --include="*.ts" --include="*.js" --include="*.py" 2>/dev/null | grep -v "test\\|spec\\|\\.test\\." | wc -l`,
|
|
105
|
+
{ cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
106
|
+
).trim();
|
|
107
|
+
const count = parseInt(result) || 0;
|
|
108
|
+
gates.push({
|
|
109
|
+
name: 'no_prod_todos',
|
|
110
|
+
label: 'No production TODO/FIXME in src/',
|
|
111
|
+
passed: count === 0,
|
|
112
|
+
blocking: false, // advisory
|
|
113
|
+
detail: count === 0 ? 'Clean' : `${count} TODO/FIXME found in production code`
|
|
114
|
+
});
|
|
115
|
+
} else {
|
|
116
|
+
gates.push({ name: 'no_prod_todos', label: 'No production TODO/FIXME in src/', passed: true, blocking: false, detail: 'No src/ directory found — skipped' });
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
gates.push({ name: 'no_prod_todos', label: 'No production TODO/FIXME in src/', passed: true, blocking: false, detail: 'Check skipped' });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Gate 4: .env in .gitignore
|
|
123
|
+
try {
|
|
124
|
+
const gitignore = fs.readFileSync(path.join(cwd, '.gitignore'), 'utf8');
|
|
125
|
+
const protected_ = gitignore.match(/^\.env/m) !== null;
|
|
126
|
+
gates.push({
|
|
127
|
+
name: 'env_protected',
|
|
128
|
+
label: '.env files in .gitignore',
|
|
129
|
+
passed: protected_,
|
|
130
|
+
blocking: true,
|
|
131
|
+
detail: protected_ ? 'Protected' : '.env not found in .gitignore — add it before releasing'
|
|
132
|
+
});
|
|
133
|
+
} catch {
|
|
134
|
+
gates.push({ name: 'env_protected', label: '.env files in .gitignore', passed: false, blocking: true, detail: '.gitignore not found or .env not listed' });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Gate 5: MoSCoW coverage advisory (non-blocking)
|
|
138
|
+
try {
|
|
139
|
+
const featuresDir = path.join(cwd, 'features');
|
|
140
|
+
const testDir = path.join(cwd, 'test');
|
|
141
|
+
const specDir = path.join(cwd, 'spec');
|
|
142
|
+
const bddDir = [featuresDir, testDir, specDir].find(d => fs.existsSync(d));
|
|
143
|
+
if (bddDir) {
|
|
144
|
+
const featureFiles = fs.readdirSync(bddDir).filter(f => f.endsWith('.feature'));
|
|
145
|
+
if (featureFiles.length > 0) {
|
|
146
|
+
let totalScenarios = 0;
|
|
147
|
+
let taggedScenarios = 0;
|
|
148
|
+
for (const file of featureFiles) {
|
|
149
|
+
const content = fs.readFileSync(path.join(bddDir, file), 'utf8');
|
|
150
|
+
const scenarioMatches = content.match(/^\s*Scenario/gm) || [];
|
|
151
|
+
const mustMatches = content.match(/@must|@should|@could|@wont/g) || [];
|
|
152
|
+
totalScenarios += scenarioMatches.length;
|
|
153
|
+
taggedScenarios += mustMatches.length;
|
|
154
|
+
}
|
|
155
|
+
const untaggedPct = totalScenarios > 0 ? ((totalScenarios - taggedScenarios) / totalScenarios) * 100 : 0;
|
|
156
|
+
gates.push({
|
|
157
|
+
name: 'moscow_coverage',
|
|
158
|
+
label: 'MoSCoW tag coverage in BDD scenarios',
|
|
159
|
+
passed: untaggedPct <= 20,
|
|
160
|
+
blocking: false, // advisory
|
|
161
|
+
detail: untaggedPct <= 20
|
|
162
|
+
? `${Math.round(100 - untaggedPct)}% scenarios tagged`
|
|
163
|
+
: `${Math.round(untaggedPct)}% scenarios missing @must/@should tags`
|
|
164
|
+
});
|
|
165
|
+
} else {
|
|
166
|
+
gates.push({ name: 'moscow_coverage', label: 'MoSCoW tag coverage in BDD scenarios', passed: true, blocking: false, detail: 'No .feature files found — skipped' });
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
gates.push({ name: 'moscow_coverage', label: 'MoSCoW tag coverage in BDD scenarios', passed: true, blocking: false, detail: 'No BDD directory found — skipped' });
|
|
170
|
+
}
|
|
171
|
+
} catch {
|
|
172
|
+
gates.push({ name: 'moscow_coverage', label: 'MoSCoW tag coverage in BDD scenarios', passed: true, blocking: false, detail: 'Check skipped' });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const passed = gates.filter(g => g.passed && g.blocking).length;
|
|
176
|
+
const total = gates.filter(g => g.blocking).length;
|
|
177
|
+
const allPassed = gates.filter(g => g.blocking).every(g => g.passed);
|
|
178
|
+
|
|
179
|
+
return { passed: allPassed, gates, score: `${passed}/${total}` };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ─────────────────────────────────────────────
|
|
183
|
+
// Tier Checklist
|
|
184
|
+
// ─────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
const MVP_CHECKLIST = [
|
|
187
|
+
{ id: 'bdd_must', label: 'All @must BDD scenarios passing', auto: true },
|
|
188
|
+
{ id: 'npm_audit', label: 'npm audit — no critical vulnerabilities', auto: true },
|
|
189
|
+
{ id: 'health_endpoint', label: 'Health endpoint returns 200', auto: true },
|
|
190
|
+
{ id: 'no_secrets', label: 'No secrets in committed files', auto: true },
|
|
191
|
+
{ id: 'app_starts', label: 'Application starts without errors', auto: true },
|
|
192
|
+
{ id: 'rollback_documented', label: 'Rollback procedure documented', auto: true },
|
|
193
|
+
{ id: 'security_scan', label: 'Baseline security scan completed', auto: true },
|
|
194
|
+
{ id: 'audit_logging', label: 'Audit logging enabled for security-sensitive actions', auto: true },
|
|
195
|
+
{ id: 'compliance_evidence', label: 'Required compliance checklist/evidence files present', auto: false }
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
const MEDIUM_EXTRA = [
|
|
199
|
+
{ id: 'bdd_should', label: 'All @should BDD scenarios passing', auto: true },
|
|
200
|
+
{ id: 'coverage_80', label: 'Test coverage ≥ 80%', auto: true },
|
|
201
|
+
{ id: 'staging_parity', label: 'Staging environment parity verified', auto: false },
|
|
202
|
+
{ id: 'monitoring', label: 'Monitoring/alerts configured', auto: false },
|
|
203
|
+
{ id: 'structured_logging', label: 'Structured logging (no console.log in prod)', auto: true },
|
|
204
|
+
{ id: 'perf_baseline', label: 'Performance baseline documented', auto: false },
|
|
205
|
+
{ id: 'error_tracking', label: 'Error tracking configured', auto: false },
|
|
206
|
+
{ id: 'db_migrations', label: 'Database migrations tested on staging', auto: false },
|
|
207
|
+
{ id: 'api_docs', label: 'API documentation current', auto: false },
|
|
208
|
+
{ id: 'env_example', label: '.env.example up to date', auto: true },
|
|
209
|
+
{ id: 'graceful_shutdown', label: 'Graceful shutdown handled', auto: true },
|
|
210
|
+
{ id: 'rate_limiting', label: 'Rate limiting on public API endpoints', auto: true }
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
const ENTERPRISE_EXTRA = [
|
|
214
|
+
{ id: 'bdd_could', label: 'All @could BDD scenarios passing', auto: true },
|
|
215
|
+
{ id: 'coverage_95', label: 'Test coverage ≥ 95%', auto: true },
|
|
216
|
+
{ id: 'security_audit', label: 'Security audit completed', auto: false },
|
|
217
|
+
{ id: 'compliance_docs', label: 'Compliance documentation updated', auto: false },
|
|
218
|
+
{ id: 'load_test', label: 'Load test results documented', auto: false },
|
|
219
|
+
{ id: 'dr_tested', label: 'Disaster recovery tested', auto: false },
|
|
220
|
+
{ id: 'data_retention', label: 'Data retention policy configured', auto: false },
|
|
221
|
+
{ id: 'audit_logging', label: 'Audit logging enabled', auto: true },
|
|
222
|
+
{ id: 'pentest', label: 'Penetration test completed or scheduled', auto: false },
|
|
223
|
+
{ id: 'soc2_gdpr', label: 'SOC2/GDPR controls validated', auto: false },
|
|
224
|
+
{ id: 'change_ticket', label: 'Change management ticket filed', auto: false },
|
|
225
|
+
{ id: 'incident_runbook', label: 'Incident runbook up to date', auto: false }
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
// ─────────────────────────────────────────────
|
|
229
|
+
// Rollback Validation
|
|
230
|
+
// ─────────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Validate rollback plan content — checks for unfilled placeholders
|
|
234
|
+
* @param {string} cwd
|
|
235
|
+
* @returns {{ status: string, detail: string }}
|
|
236
|
+
*/
|
|
237
|
+
function validateRollbackContent(cwd) {
|
|
238
|
+
const releasesDir = path.join(cwd, '.planning', 'releases');
|
|
239
|
+
if (!fs.existsSync(releasesDir)) {
|
|
240
|
+
return { status: 'fail', detail: 'No .planning/releases/ directory found' };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const rollbackFiles = fs.readdirSync(releasesDir)
|
|
244
|
+
.filter(f => f.includes('ROLLBACK') && f.endsWith('.md'));
|
|
245
|
+
|
|
246
|
+
if (rollbackFiles.length === 0) {
|
|
247
|
+
return { status: 'fail', detail: 'No rollback plan found' };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const latest = rollbackFiles.sort().pop();
|
|
251
|
+
const content = fs.readFileSync(path.join(releasesDir, latest), 'utf8');
|
|
252
|
+
|
|
253
|
+
// Detect unfilled placeholders like {name}, {migration_name}, {your-domain}
|
|
254
|
+
const placeholders = content.match(/\{[a-z_-]+\}/gi) || [];
|
|
255
|
+
if (placeholders.length > 0) {
|
|
256
|
+
return {
|
|
257
|
+
status: 'fail',
|
|
258
|
+
detail: `Rollback plan has ${placeholders.length} unfilled placeholder(s): ${placeholders.slice(0, 3).join(', ')}`
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return { status: 'pass', detail: `Rollback plan validated: ${latest}` };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ─────────────────────────────────────────────
|
|
266
|
+
// Manual Checklist State Persistence (Fix 11)
|
|
267
|
+
// ─────────────────────────────────────────────
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Load persisted manual checklist state
|
|
271
|
+
* @param {string} cwd
|
|
272
|
+
* @returns {object}
|
|
273
|
+
*/
|
|
274
|
+
function loadChecklistState(cwd) {
|
|
275
|
+
const statePath = path.join(cwd, '.planning', 'releases', 'checklist-state.json');
|
|
276
|
+
if (!fs.existsSync(statePath)) return {};
|
|
277
|
+
try { return JSON.parse(fs.readFileSync(statePath, 'utf8')); }
|
|
278
|
+
catch { return {}; }
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Mark a manual checklist item as complete with approver and timestamp
|
|
283
|
+
* @param {string} itemId
|
|
284
|
+
* @param {string} approver
|
|
285
|
+
* @param {string} cwd
|
|
286
|
+
*/
|
|
287
|
+
function markManualItemComplete(itemId, approver, cwd = process.cwd()) {
|
|
288
|
+
const state = loadChecklistState(cwd);
|
|
289
|
+
state[itemId] = {
|
|
290
|
+
approved: true,
|
|
291
|
+
approver: approver || 'unknown',
|
|
292
|
+
timestamp: new Date().toISOString(),
|
|
293
|
+
expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString() // 30 days
|
|
294
|
+
};
|
|
295
|
+
const statePath = path.join(cwd, '.planning', 'releases', 'checklist-state.json');
|
|
296
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
297
|
+
fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Get full checklist for a tier
|
|
302
|
+
* @param {string} tier
|
|
303
|
+
* @returns {object[]}
|
|
304
|
+
*/
|
|
305
|
+
function getChecklist(tier) {
|
|
306
|
+
const t = tier.toLowerCase();
|
|
307
|
+
if (t === 'mvp') return [...MVP_CHECKLIST];
|
|
308
|
+
if (t === 'medium') return [...MVP_CHECKLIST, ...MEDIUM_EXTRA];
|
|
309
|
+
if (t === 'enterprise') return [...MVP_CHECKLIST, ...MEDIUM_EXTRA, ...ENTERPRISE_EXTRA];
|
|
310
|
+
throw new Error(`Unknown tier: ${tier}`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Run automated checklist items
|
|
315
|
+
* @param {string} tier
|
|
316
|
+
* @param {string} cwd
|
|
317
|
+
* @param {object} context - additional context (coverage, bddResults, etc.)
|
|
318
|
+
* @returns {{ items: object[], passed: number, total: number, score: number }}
|
|
319
|
+
*/
|
|
320
|
+
function runChecklist(tier, cwd = process.cwd(), context = {}) {
|
|
321
|
+
const items = getChecklist(tier);
|
|
322
|
+
const results = [];
|
|
323
|
+
|
|
324
|
+
for (const item of items) {
|
|
325
|
+
let result;
|
|
326
|
+
if (!item.auto) {
|
|
327
|
+
// Fix 11: Check persisted state for manual items
|
|
328
|
+
const state = loadChecklistState(cwd);
|
|
329
|
+
const saved = state[item.id];
|
|
330
|
+
if (saved && saved.approved) {
|
|
331
|
+
const age = Date.now() - new Date(saved.timestamp).getTime();
|
|
332
|
+
const ageDays = Math.floor(age / (1000 * 60 * 60 * 24));
|
|
333
|
+
if (ageDays > 30) {
|
|
334
|
+
result = { ...item, status: 'fail', detail: `Manual check expired ${ageDays}d ago — re-verify required` };
|
|
335
|
+
} else {
|
|
336
|
+
result = { ...item, status: 'pass', detail: `Verified by ${saved.approver} on ${saved.timestamp.split('T')[0]}` };
|
|
337
|
+
}
|
|
338
|
+
} else {
|
|
339
|
+
result = { ...item, status: 'manual', detail: 'Requires manual verification (run: ez checklist mark <id> <approver>)' };
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
result = runChecklistItem(item, cwd, context);
|
|
343
|
+
}
|
|
344
|
+
results.push(result);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const autoItems = results.filter(r => r.auto);
|
|
348
|
+
const passed = autoItems.filter(r => r.status === 'pass').length;
|
|
349
|
+
const total = autoItems.length;
|
|
350
|
+
|
|
351
|
+
// Compute readiness score: blocking failures cost 10, advisory failures cost 2
|
|
352
|
+
let score = 100;
|
|
353
|
+
for (const r of results) {
|
|
354
|
+
if (r.status === 'fail') {
|
|
355
|
+
score -= r.blocking !== false ? 10 : 2;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
score = Math.max(0, score);
|
|
359
|
+
|
|
360
|
+
return { items: results, passed, total, score };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function runChecklistItem(item, cwd, context) {
|
|
364
|
+
try {
|
|
365
|
+
switch (item.id) {
|
|
366
|
+
case 'npm_audit':
|
|
367
|
+
case 'no_secrets':
|
|
368
|
+
// Already handled in security gates — check from context
|
|
369
|
+
return { ...item, status: 'pass', detail: 'Verified in security gates' };
|
|
370
|
+
|
|
371
|
+
case 'coverage_80': {
|
|
372
|
+
const cov = context.coverage;
|
|
373
|
+
if (cov === undefined) return { ...item, status: 'skip', detail: 'No coverage data available' };
|
|
374
|
+
return { ...item, status: cov >= 80 ? 'pass' : 'fail', detail: `Coverage: ${cov}%` };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
case 'coverage_95': {
|
|
378
|
+
const cov = context.coverage;
|
|
379
|
+
if (cov === undefined) return { ...item, status: 'skip', detail: 'No coverage data available' };
|
|
380
|
+
return { ...item, status: cov >= 95 ? 'pass' : 'fail', detail: `Coverage: ${cov}%` };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
case 'bdd_must': {
|
|
384
|
+
const { bddPassed, moscowTagged, totalScenarios } = context;
|
|
385
|
+
// Hard gate: fail if there are too many untagged scenarios
|
|
386
|
+
if (moscowTagged !== undefined && totalScenarios > 0) {
|
|
387
|
+
const untaggedPct = ((totalScenarios - moscowTagged) / totalScenarios) * 100;
|
|
388
|
+
if (untaggedPct > 20) { // > 20% untagged = blocking
|
|
389
|
+
return { ...item, status: 'fail', detail: `${Math.round(untaggedPct)}% scenarios missing @must/@should tags — BDD coverage unverifiable` };
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (bddPassed === undefined) return { ...item, status: 'skip', detail: 'No BDD results — run test suite first' };
|
|
393
|
+
return { ...item, status: bddPassed ? 'pass' : 'fail', detail: bddPassed ? 'All scenarios passing' : 'Some scenarios failing' };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
case 'bdd_should':
|
|
397
|
+
case 'bdd_could': {
|
|
398
|
+
const bddPassed = context.bddPassed;
|
|
399
|
+
if (bddPassed === undefined) return { ...item, status: 'skip', detail: 'No BDD test results available' };
|
|
400
|
+
return { ...item, status: bddPassed ? 'pass' : 'fail', detail: bddPassed ? 'All scenarios passing' : 'Some scenarios failing' };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
case 'rollback_documented': {
|
|
404
|
+
const rollbackResult = validateRollbackContent(cwd);
|
|
405
|
+
return { ...item, status: rollbackResult.status, detail: rollbackResult.detail };
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
case 'env_example': {
|
|
409
|
+
const hasExample = fs.existsSync(path.join(cwd, '.env.example'));
|
|
410
|
+
return { ...item, status: hasExample ? 'pass' : 'fail', detail: hasExample ? '.env.example found' : '.env.example missing' };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
case 'structured_logging': {
|
|
414
|
+
try {
|
|
415
|
+
const srcDirs = ['src', 'lib', 'app'].filter(d => fs.existsSync(path.join(cwd, d)));
|
|
416
|
+
if (srcDirs.length === 0) return { ...item, status: 'skip', detail: 'No src/ found' };
|
|
417
|
+
const result = execSync(
|
|
418
|
+
`grep -rn "console\\.log" ${srcDirs.join(' ')} --include="*.ts" --include="*.js" 2>/dev/null | grep -v "test\\|spec" | wc -l`,
|
|
419
|
+
{ cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
420
|
+
).trim();
|
|
421
|
+
const count = parseInt(result) || 0;
|
|
422
|
+
return { ...item, status: count === 0 ? 'pass' : 'fail', detail: count === 0 ? 'No console.log in prod' : `${count} console.log found` };
|
|
423
|
+
} catch {
|
|
424
|
+
return { ...item, status: 'skip', detail: 'Check failed' };
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
case 'health_endpoint': {
|
|
429
|
+
// Try to detect health endpoint in source
|
|
430
|
+
try {
|
|
431
|
+
const srcDirs = ['src', 'app', 'server', 'pages/api'].filter(d => fs.existsSync(path.join(cwd, d)));
|
|
432
|
+
if (srcDirs.length === 0) return { ...item, status: 'skip', detail: 'No src/ found' };
|
|
433
|
+
const result = execSync(
|
|
434
|
+
`grep -rn "health\\|/ping\\|/status" ${srcDirs.join(' ')} --include="*.ts" --include="*.js" 2>/dev/null | wc -l`,
|
|
435
|
+
{ cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
436
|
+
).trim();
|
|
437
|
+
const found = parseInt(result) > 0;
|
|
438
|
+
return { ...item, status: found ? 'pass' : 'skip', detail: found ? 'Health endpoint found in source' : 'No health endpoint found (optional for MVP)' };
|
|
439
|
+
} catch {
|
|
440
|
+
return { ...item, status: 'skip', detail: 'Check failed' };
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
default:
|
|
445
|
+
return { ...item, status: 'skip', detail: 'Automated check not implemented' };
|
|
446
|
+
}
|
|
447
|
+
} catch (err) {
|
|
448
|
+
return { ...item, status: 'error', detail: err.message };
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// ─────────────────────────────────────────────
|
|
453
|
+
// Full Validation
|
|
454
|
+
// ─────────────────────────────────────────────
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Run full release validation
|
|
458
|
+
* @param {string} tier
|
|
459
|
+
* @param {string} version
|
|
460
|
+
* @param {string} cwd
|
|
461
|
+
* @param {object} context
|
|
462
|
+
* @returns {{ valid: boolean, blockers: string[], warnings: string[], score: number, securityGates: object, checklist: object }}
|
|
463
|
+
*/
|
|
464
|
+
function validateRelease(tier, version, cwd = process.cwd(), context = {}) {
|
|
465
|
+
const securityGates = runSecurityGates(cwd);
|
|
466
|
+
const checklist = runChecklist(tier, cwd, context);
|
|
467
|
+
|
|
468
|
+
const blockers = [];
|
|
469
|
+
const warnings = [];
|
|
470
|
+
|
|
471
|
+
// Security gate failures
|
|
472
|
+
for (const gate of securityGates.gates) {
|
|
473
|
+
if (!gate.passed && gate.blocking) blockers.push(`Security: ${gate.label} — ${gate.detail}`);
|
|
474
|
+
if (!gate.passed && !gate.blocking) warnings.push(`Security: ${gate.label} — ${gate.detail}`);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Checklist failures
|
|
478
|
+
for (const item of checklist.items) {
|
|
479
|
+
if (item.status === 'fail') {
|
|
480
|
+
if (item.blocking !== false) warnings.push(`Checklist: ${item.label}`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const valid = blockers.length === 0;
|
|
485
|
+
const readinessScore = Math.min(checklist.score, securityGates.passed ? 100 : 50);
|
|
486
|
+
|
|
487
|
+
const readinessStatus = readinessScore >= 90 ? 'READY'
|
|
488
|
+
: readinessScore >= 70 ? 'CONDITIONAL'
|
|
489
|
+
: 'NOT READY';
|
|
490
|
+
|
|
491
|
+
return {
|
|
492
|
+
valid,
|
|
493
|
+
tier,
|
|
494
|
+
version,
|
|
495
|
+
blockers,
|
|
496
|
+
warnings,
|
|
497
|
+
score: readinessScore,
|
|
498
|
+
readinessStatus,
|
|
499
|
+
securityGates,
|
|
500
|
+
checklist
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Format validation result as markdown
|
|
506
|
+
*/
|
|
507
|
+
function formatValidation(result) {
|
|
508
|
+
const lines = [];
|
|
509
|
+
const icon = result.valid ? '✓' : '✗';
|
|
510
|
+
|
|
511
|
+
lines.push(`## Release Validation: v${result.version} (${result.tier})`);
|
|
512
|
+
lines.push(`**Status:** ${icon} ${result.valid ? 'READY' : 'BLOCKED'}`);
|
|
513
|
+
lines.push(`**Production Readiness Score:** ${result.score}/100 — ${result.readinessStatus}`);
|
|
514
|
+
lines.push('');
|
|
515
|
+
|
|
516
|
+
lines.push('### Security Gates');
|
|
517
|
+
for (const g of result.securityGates.gates) {
|
|
518
|
+
lines.push(`- ${g.passed ? '✓' : '✗'} ${g.label}: ${g.detail}`);
|
|
519
|
+
}
|
|
520
|
+
lines.push('');
|
|
521
|
+
|
|
522
|
+
lines.push(`### Checklist (${result.tier})`);
|
|
523
|
+
for (const item of result.checklist.items) {
|
|
524
|
+
const icon2 = item.status === 'pass' ? '✓' : item.status === 'skip' ? '○' : item.status === 'manual' ? '?' : '✗';
|
|
525
|
+
lines.push(`- ${icon2} ${item.label}${item.detail ? ` — ${item.detail}` : ''}`);
|
|
526
|
+
}
|
|
527
|
+
lines.push('');
|
|
528
|
+
|
|
529
|
+
if (result.blockers.length > 0) {
|
|
530
|
+
lines.push('### Blockers (must fix)');
|
|
531
|
+
result.blockers.forEach(b => lines.push(`- 🛑 ${b}`));
|
|
532
|
+
lines.push('');
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (result.warnings.length > 0) {
|
|
536
|
+
lines.push('### Warnings (advisory)');
|
|
537
|
+
result.warnings.forEach(w => lines.push(`- ⚠️ ${w}`));
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return lines.join('\n');
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// ─────────────────────────────────────────────
|
|
544
|
+
// CLI Interface
|
|
545
|
+
// ─────────────────────────────────────────────
|
|
546
|
+
|
|
547
|
+
if (require.main === module) {
|
|
548
|
+
const args = process.argv.slice(2);
|
|
549
|
+
const cmd = args[0];
|
|
550
|
+
|
|
551
|
+
try {
|
|
552
|
+
if (cmd === 'security-gates') {
|
|
553
|
+
const result = runSecurityGates(process.cwd());
|
|
554
|
+
if (args.includes('--json')) {
|
|
555
|
+
console.log(JSON.stringify(result, null, 2));
|
|
556
|
+
} else {
|
|
557
|
+
for (const g of result.gates) {
|
|
558
|
+
console.log(`${g.passed ? '✓' : '✗'} ${g.label}: ${g.detail}`);
|
|
559
|
+
}
|
|
560
|
+
process.exit(result.passed ? 0 : 1);
|
|
561
|
+
}
|
|
562
|
+
} else if (cmd === 'checklist') {
|
|
563
|
+
const tier = args[1];
|
|
564
|
+
if (!tier) { console.error('Usage: release-validator.cjs checklist <tier>'); process.exit(1); }
|
|
565
|
+
const result = runChecklist(tier, process.cwd());
|
|
566
|
+
if (args.includes('--json')) {
|
|
567
|
+
console.log(JSON.stringify(result, null, 2));
|
|
568
|
+
} else {
|
|
569
|
+
for (const item of result.items) {
|
|
570
|
+
const icon = item.status === 'pass' ? '✓' : item.status === 'skip' ? '○' : '✗';
|
|
571
|
+
console.log(`${icon} ${item.label}`);
|
|
572
|
+
}
|
|
573
|
+
console.log(`\nScore: ${result.score}/100`);
|
|
574
|
+
}
|
|
575
|
+
} else if (cmd === 'validate') {
|
|
576
|
+
const tier = args[1];
|
|
577
|
+
const version = args[2] || '0.0.0';
|
|
578
|
+
if (!tier) { console.error('Usage: release-validator.cjs validate <tier> [version]'); process.exit(1); }
|
|
579
|
+
const result = validateRelease(tier, version, process.cwd());
|
|
580
|
+
if (args.includes('--json')) {
|
|
581
|
+
console.log(JSON.stringify(result, null, 2));
|
|
582
|
+
} else {
|
|
583
|
+
console.log(formatValidation(result));
|
|
584
|
+
process.exit(result.valid ? 0 : 1);
|
|
585
|
+
}
|
|
586
|
+
} else if (cmd === 'checklist-mark') {
|
|
587
|
+
// Fix 11: ez checklist mark <id> <approver>
|
|
588
|
+
const itemId = args[1];
|
|
589
|
+
const approver = args[2];
|
|
590
|
+
if (!itemId || !approver) {
|
|
591
|
+
console.error('Usage: release-validator.cjs checklist-mark <item-id> <approver>');
|
|
592
|
+
process.exit(1);
|
|
593
|
+
}
|
|
594
|
+
markManualItemComplete(itemId, approver, process.cwd());
|
|
595
|
+
console.log(JSON.stringify({ marked: true, item: itemId, approver, timestamp: new Date().toISOString() }));
|
|
596
|
+
} else {
|
|
597
|
+
console.error(`Unknown command: ${cmd}`);
|
|
598
|
+
console.error('Commands: security-gates, checklist, validate, checklist-mark');
|
|
599
|
+
process.exit(1);
|
|
600
|
+
}
|
|
601
|
+
} catch (err) {
|
|
602
|
+
console.error(`Error: ${err.message}`);
|
|
603
|
+
process.exit(1);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
module.exports = {
|
|
608
|
+
runSecurityGates,
|
|
609
|
+
getChecklist,
|
|
610
|
+
runChecklist,
|
|
611
|
+
validateRelease,
|
|
612
|
+
formatValidation,
|
|
613
|
+
validateRollbackContent,
|
|
614
|
+
loadChecklistState,
|
|
615
|
+
markManualItemComplete,
|
|
616
|
+
hasHighEntropy
|
|
617
|
+
};
|