agentic-qe 3.8.0 → 3.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/v3/qe-message-broker-tester.md +3 -3
- package/.claude/agents/v3/qe-middleware-validator.md +3 -3
- package/.claude/agents/v3/qe-odata-contract-tester.md +3 -3
- package/.claude/agents/v3/qe-qx-partner.md +1 -1
- package/.claude/agents/v3/qe-sap-idoc-tester.md +3 -3
- package/.claude/agents/v3/qe-sap-rfc-tester.md +3 -3
- package/.claude/agents/v3/qe-security-scanner.md +2 -2
- package/.claude/agents/v3/qe-soap-tester.md +3 -3
- package/.claude/agents/v3/qe-sod-analyzer.md +3 -3
- package/.claude/commands/claude-flow-help.md +1 -1
- package/.claude/helpers/github-setup.sh +4 -4
- package/.claude/helpers/post-commit +1 -1
- package/.claude/helpers/pre-commit +1 -1
- package/.claude/helpers/quick-start.sh +4 -4
- package/.claude/helpers/setup-mcp.sh +3 -3
- package/.claude/helpers/statusline-v3.cjs +1 -1
- package/.claude/helpers/validation-pipeline.cjs +625 -0
- package/.claude/skills/a11y-ally/SKILL.md +0 -1
- package/.claude/skills/accessibility-testing/SKILL.md +0 -1
- package/.claude/skills/agentic-quality-engineering/SKILL.md +0 -1
- package/.claude/skills/aqe-v2-v3-migration/skill.md +0 -1
- package/.claude/skills/brutal-honesty-review/SKILL.md +0 -1
- package/.claude/skills/bug-reporting-excellence/SKILL.md +0 -1
- package/.claude/skills/cicd-pipeline-qe-orchestrator/SKILL.md +0 -1
- package/.claude/skills/code-review-quality/SKILL.md +0 -1
- package/.claude/skills/compliance-testing/SKILL.md +0 -1
- package/.claude/skills/consultancy-practices/SKILL.md +0 -1
- package/.claude/skills/context-driven-testing/SKILL.md +0 -1
- package/.claude/skills/exploratory-testing-advanced/SKILL.md +0 -1
- package/.claude/skills/holistic-testing-pact/SKILL.md +0 -1
- package/.claude/skills/iterative-loop/SKILL.md +6 -6
- package/.claude/skills/localization-testing/SKILL.md +0 -1
- package/.claude/skills/mobile-testing/SKILL.md +0 -1
- package/.claude/skills/mutation-testing/SKILL.md +0 -1
- package/.claude/skills/n8n-expression-testing/SKILL.md +0 -1
- package/.claude/skills/n8n-integration-testing-patterns/SKILL.md +0 -1
- package/.claude/skills/n8n-security-testing/SKILL.md +0 -1
- package/.claude/skills/n8n-trigger-testing-strategies/SKILL.md +0 -1
- package/.claude/skills/n8n-workflow-testing-fundamentals/SKILL.md +0 -1
- package/.claude/skills/qcsd-cicd-swarm/SKILL.md +0 -1
- package/.claude/skills/qcsd-development-swarm/SKILL.md +0 -1
- package/.claude/skills/qcsd-ideation-swarm/SKILL.md +0 -1
- package/.claude/skills/qcsd-production-swarm/SKILL.md +0 -1
- package/.claude/skills/qcsd-production-swarm/steps/01-flag-detection.md +1 -1
- package/.claude/skills/qcsd-production-swarm/steps/07-learning-persistence.md +2 -2
- package/.claude/skills/qcsd-refinement-swarm/SKILL.md +0 -1
- package/.claude/skills/qcsd-refinement-swarm/steps/01-flag-detection.md +1 -1
- package/.claude/skills/qe-chaos-resilience/SKILL.md +0 -1
- package/.claude/skills/qe-code-intelligence/SKILL.md +0 -1
- package/.claude/skills/qe-contract-testing/SKILL.md +0 -1
- package/.claude/skills/qe-coverage-analysis/SKILL.md +0 -1
- package/.claude/skills/qe-defect-intelligence/SKILL.md +0 -1
- package/.claude/skills/qe-iterative-loop/SKILL.md +0 -1
- package/.claude/skills/qe-learning-optimization/SKILL.md +0 -1
- package/.claude/skills/qe-quality-assessment/SKILL.md +0 -1
- package/.claude/skills/qe-requirements-validation/SKILL.md +0 -1
- package/.claude/skills/qe-security-compliance/SKILL.md +0 -1
- package/.claude/skills/qe-test-execution/SKILL.md +0 -1
- package/.claude/skills/qe-test-generation/SKILL.md +0 -1
- package/.claude/skills/qe-visual-accessibility/SKILL.md +0 -1
- package/.claude/skills/quality-metrics/SKILL.md +0 -1
- package/.claude/skills/refactoring-patterns/SKILL.md +0 -1
- package/.claude/skills/regression-testing/SKILL.md +0 -1
- package/.claude/skills/risk-based-testing/SKILL.md +0 -1
- package/.claude/skills/security-visual-testing/SKILL.md +0 -1
- package/.claude/skills/sherlock-review/SKILL.md +0 -1
- package/.claude/skills/shift-left-testing/SKILL.md +0 -1
- package/.claude/skills/shift-right-testing/SKILL.md +0 -1
- package/.claude/skills/six-thinking-hats/SKILL.md +0 -1
- package/.claude/skills/skills-manifest.json +1 -1
- package/.claude/skills/tdd-london-chicago/SKILL.md +0 -1
- package/.claude/skills/technical-writing/SKILL.md +0 -1
- package/.claude/skills/test-automation-strategy/SKILL.md +0 -1
- package/.claude/skills/test-data-management/SKILL.md +0 -1
- package/.claude/skills/test-design-techniques/SKILL.md +0 -1
- package/.claude/skills/test-environment-management/SKILL.md +0 -1
- package/.claude/skills/test-reporting-analytics/SKILL.md +0 -1
- package/.claude/skills/validation-pipeline/SKILL.md +4 -5
- package/.claude/skills/visual-testing-advanced/SKILL.md +0 -1
- package/.claude/skills/xp-practices/SKILL.md +0 -1
- package/.opencode/agents/collective-intelligence-coordinator.yaml +52 -0
- package/.opencode/agents/ddd-domain-expert.yaml +49 -0
- package/.opencode/agents/memory-specialist.yaml +49 -0
- package/.opencode/agents/performance-engineer.yaml +53 -0
- package/.opencode/agents/qe-accessibility-auditor.yaml +118 -0
- package/.opencode/agents/qe-api-contract-validator.yaml +85 -0
- package/.opencode/agents/qe-bdd-generator.yaml +83 -0
- package/.opencode/agents/qe-chaos-engineer.yaml +114 -0
- package/.opencode/agents/qe-code-complexity.yaml +82 -0
- package/.opencode/agents/qe-code-intelligence.yaml +80 -0
- package/.opencode/agents/qe-coverage-analyzer.yaml +75 -0
- package/.opencode/agents/qe-defect-predictor.yaml +81 -0
- package/.opencode/agents/qe-dependency-mapper.yaml +81 -0
- package/.opencode/agents/qe-deployment-advisor.yaml +82 -0
- package/.opencode/agents/qe-devils-advocate.yaml +63 -0
- package/.opencode/agents/qe-flaky-hunter.yaml +116 -0
- package/.opencode/agents/qe-fleet-commander.yaml +83 -0
- package/.opencode/agents/qe-gap-detector.yaml +81 -0
- package/.opencode/agents/qe-graphql-tester.yaml +84 -0
- package/.opencode/agents/qe-impact-analyzer.yaml +81 -0
- package/.opencode/agents/qe-integration-architect.yaml +46 -0
- package/.opencode/agents/qe-integration-tester.yaml +84 -0
- package/.opencode/agents/qe-kg-builder.yaml +75 -0
- package/.opencode/agents/qe-learning-coordinator.yaml +82 -0
- package/.opencode/agents/qe-load-tester.yaml +84 -0
- package/.opencode/agents/qe-message-broker-tester.yaml +94 -0
- package/.opencode/agents/qe-metrics-optimizer.yaml +81 -0
- package/.opencode/agents/qe-middleware-validator.yaml +92 -0
- package/.opencode/agents/qe-mutation-tester.yaml +84 -0
- package/.opencode/agents/qe-odata-contract-tester.yaml +98 -0
- package/.opencode/agents/qe-parallel-executor.yaml +79 -0
- package/.opencode/agents/qe-pattern-learner.yaml +80 -0
- package/.opencode/agents/qe-pentest-validator.yaml +137 -0
- package/.opencode/agents/qe-performance-tester.yaml +83 -0
- package/.opencode/agents/qe-product-factors-assessor.yaml +116 -0
- package/.opencode/agents/qe-property-tester.yaml +82 -0
- package/.opencode/agents/qe-quality-criteria-recommender.yaml +111 -0
- package/.opencode/agents/qe-quality-gate.yaml +80 -0
- package/.opencode/agents/qe-queen-coordinator.yaml +59 -0
- package/.opencode/agents/qe-qx-partner.yaml +75 -0
- package/.opencode/agents/qe-regression-analyzer.yaml +90 -0
- package/.opencode/agents/qe-requirements-validator.yaml +111 -0
- package/.opencode/agents/qe-responsive-tester.yaml +85 -0
- package/.opencode/agents/qe-retry-handler.yaml +82 -0
- package/.opencode/agents/qe-risk-assessor.yaml +81 -0
- package/.opencode/agents/qe-root-cause-analyzer.yaml +82 -0
- package/.opencode/agents/qe-sap-idoc-tester.yaml +104 -0
- package/.opencode/agents/qe-sap-rfc-tester.yaml +94 -0
- package/.opencode/agents/qe-security-auditor.yaml +90 -0
- package/.opencode/agents/qe-security-scanner.yaml +80 -0
- package/.opencode/agents/qe-soap-tester.yaml +93 -0
- package/.opencode/agents/qe-sod-analyzer.yaml +96 -0
- package/.opencode/agents/qe-tdd-specialist.yaml +84 -0
- package/.opencode/agents/qe-test-generator.yaml +78 -0
- package/.opencode/agents/qe-test-idea-rewriter.yaml +88 -0
- package/.opencode/agents/qe-transfer-specialist.yaml +81 -0
- package/.opencode/agents/qe-visual-tester.yaml +82 -0
- package/.opencode/agents/security-architect.yaml +51 -0
- package/.opencode/agents/security-auditor.yaml +50 -0
- package/.opencode/permissions.yaml +74 -0
- package/.opencode/skills/qcsd-cicd-swarm.yaml +45 -0
- package/.opencode/skills/qcsd-development-swarm.yaml +45 -0
- package/.opencode/skills/qcsd-ideation-swarm.yaml +45 -0
- package/.opencode/skills/qcsd-production-swarm.yaml +45 -0
- package/.opencode/skills/qcsd-refinement-swarm.yaml +45 -0
- package/.opencode/skills/qe-a11y-ally.yaml +45 -0
- package/.opencode/skills/qe-accessibility-testing.yaml +45 -0
- package/.opencode/skills/qe-agentic-jujutsu.yaml +45 -0
- package/.opencode/skills/qe-agentic-quality-engineering.yaml +45 -0
- package/.opencode/skills/qe-api-testing-patterns.yaml +45 -0
- package/.opencode/skills/qe-aqe-v2-v3-migration.yaml +45 -0
- package/.opencode/skills/qe-brutal-honesty-review.yaml +45 -0
- package/.opencode/skills/qe-bug-reporting-excellence.yaml +45 -0
- package/.opencode/skills/qe-chaos-engineering-resilience.yaml +30 -0
- package/.opencode/skills/qe-chaos-resilience.yaml +45 -0
- package/.opencode/skills/qe-cicd-pipeline-qe-orchestrator.yaml +45 -0
- package/.opencode/skills/qe-code-intelligence.yaml +45 -0
- package/.opencode/skills/qe-code-review-quality.yaml +45 -0
- package/.opencode/skills/qe-compatibility-testing.yaml +35 -0
- package/.opencode/skills/qe-compliance-testing.yaml +35 -0
- package/.opencode/skills/qe-consultancy-practices.yaml +45 -0
- package/.opencode/skills/qe-context-driven-testing.yaml +45 -0
- package/.opencode/skills/qe-contract-testing.yaml +45 -0
- package/.opencode/skills/qe-coverage-analysis.yaml +45 -0
- package/.opencode/skills/qe-database-testing.yaml +40 -0
- package/.opencode/skills/qe-debug-loop.yaml +45 -0
- package/.opencode/skills/qe-defect-intelligence.yaml +45 -0
- package/.opencode/skills/qe-enterprise-integration-testing.yaml +45 -0
- package/.opencode/skills/qe-exploratory-testing-advanced.yaml +45 -0
- package/.opencode/skills/qe-github-code-review.yaml +45 -0
- package/.opencode/skills/qe-github-multi-repo.yaml +45 -0
- package/.opencode/skills/qe-github-project-management.yaml +45 -0
- package/.opencode/skills/qe-github-release-management.yaml +45 -0
- package/.opencode/skills/qe-github-workflow-automation.yaml +45 -0
- package/.opencode/skills/qe-holistic-testing-pact.yaml +45 -0
- package/.opencode/skills/qe-iterative-loop.yaml +45 -0
- package/.opencode/skills/qe-learning-optimization.yaml +45 -0
- package/.opencode/skills/qe-localization-testing.yaml +40 -0
- package/.opencode/skills/qe-middleware-testing-patterns.yaml +45 -0
- package/.opencode/skills/qe-mobile-testing.yaml +35 -0
- package/.opencode/skills/qe-mutation-testing.yaml +35 -0
- package/.opencode/skills/qe-n8n-expression-testing.yaml +45 -0
- package/.opencode/skills/qe-n8n-integration-testing-patterns.yaml +45 -0
- package/.opencode/skills/qe-n8n-security-testing.yaml +45 -0
- package/.opencode/skills/qe-n8n-trigger-testing-strategies.yaml +45 -0
- package/.opencode/skills/qe-n8n-workflow-testing-fundamentals.yaml +45 -0
- package/.opencode/skills/qe-observability-testing-patterns.yaml +45 -0
- package/.opencode/skills/qe-pair-programming.yaml +45 -0
- package/.opencode/skills/qe-pentest-validation.yaml +45 -0
- package/.opencode/skills/qe-performance-analysis.yaml +45 -0
- package/.opencode/skills/qe-performance-testing.yaml +45 -0
- package/.opencode/skills/qe-pr-review.yaml +45 -0
- package/.opencode/skills/qe-quality-assessment.yaml +45 -0
- package/.opencode/skills/qe-quality-metrics.yaml +45 -0
- package/.opencode/skills/qe-refactoring-patterns.yaml +40 -0
- package/.opencode/skills/qe-regression-testing.yaml +40 -0
- package/.opencode/skills/qe-release.yaml +45 -0
- package/.opencode/skills/qe-requirements-validation.yaml +45 -0
- package/.opencode/skills/qe-risk-based-testing.yaml +45 -0
- package/.opencode/skills/qe-security-compliance.yaml +45 -0
- package/.opencode/skills/qe-security-testing.yaml +45 -0
- package/.opencode/skills/qe-security-visual-testing.yaml +45 -0
- package/.opencode/skills/qe-sfdipot-product-factors.yaml +45 -0
- package/.opencode/skills/qe-sherlock-review.yaml +45 -0
- package/.opencode/skills/qe-shift-left-testing.yaml +45 -0
- package/.opencode/skills/qe-shift-right-testing.yaml +45 -0
- package/.opencode/skills/qe-six-thinking-hats.yaml +45 -0
- package/.opencode/skills/qe-skill-builder.yaml +45 -0
- package/.opencode/skills/qe-sparc-methodology.yaml +45 -0
- package/.opencode/skills/qe-stream-chain.yaml +45 -0
- package/.opencode/skills/qe-tdd-london-chicago.yaml +45 -0
- package/.opencode/skills/qe-technical-writing.yaml +45 -0
- package/.opencode/skills/qe-test-automation-strategy.yaml +35 -0
- package/.opencode/skills/qe-test-data-management.yaml +45 -0
- package/.opencode/skills/qe-test-design-techniques.yaml +40 -0
- package/.opencode/skills/qe-test-environment-management.yaml +40 -0
- package/.opencode/skills/qe-test-execution.yaml +45 -0
- package/.opencode/skills/qe-test-generation.yaml +45 -0
- package/.opencode/skills/qe-test-idea-rewriting.yaml +45 -0
- package/.opencode/skills/qe-test-reporting-analytics.yaml +45 -0
- package/.opencode/skills/qe-testability-scoring.yaml +45 -0
- package/.opencode/skills/qe-verification-quality.yaml +45 -0
- package/.opencode/skills/qe-visual-accessibility.yaml +45 -0
- package/.opencode/skills/qe-visual-testing-advanced.yaml +40 -0
- package/.opencode/skills/qe-wms-testing-patterns.yaml +45 -0
- package/.opencode/skills/qe-xp-practices.yaml +45 -0
- package/.opencode/tools/qe-defect-scan.ts +79 -0
- package/.opencode/tools/qe-fleet-status.ts +59 -0
- package/.opencode/tools/qe-full-audit.ts +81 -0
- package/.opencode/tools/qe-learning-report.ts +74 -0
- package/.opencode/tools/qe-test-and-verify.ts +97 -0
- package/CHANGELOG.md +32 -0
- package/assets/agents/v3/qe-message-broker-tester.md +3 -3
- package/assets/agents/v3/qe-middleware-validator.md +3 -3
- package/assets/agents/v3/qe-odata-contract-tester.md +3 -3
- package/assets/agents/v3/qe-qx-partner.md +1 -1
- package/assets/agents/v3/qe-sap-idoc-tester.md +3 -3
- package/assets/agents/v3/qe-sap-rfc-tester.md +3 -3
- package/assets/agents/v3/qe-security-scanner.md +2 -2
- package/assets/agents/v3/qe-soap-tester.md +3 -3
- package/assets/agents/v3/qe-sod-analyzer.md +3 -3
- package/assets/helpers/statusline-v3.cjs +1 -1
- package/assets/helpers/validation-pipeline.cjs +625 -0
- package/assets/skills/a11y-ally/SKILL.md +0 -1
- package/assets/skills/accessibility-testing/SKILL.md +0 -1
- package/assets/skills/agentic-quality-engineering/SKILL.md +0 -1
- package/assets/skills/aqe-v2-v3-migration/skill.md +0 -1
- package/assets/skills/brutal-honesty-review/SKILL.md +0 -1
- package/assets/skills/bug-reporting-excellence/SKILL.md +0 -1
- package/assets/skills/cicd-pipeline-qe-orchestrator/SKILL.md +0 -1
- package/assets/skills/code-review-quality/SKILL.md +0 -1
- package/assets/skills/compliance-testing/SKILL.md +0 -1
- package/assets/skills/consultancy-practices/SKILL.md +0 -1
- package/assets/skills/context-driven-testing/SKILL.md +0 -1
- package/assets/skills/exploratory-testing-advanced/SKILL.md +0 -1
- package/assets/skills/holistic-testing-pact/SKILL.md +0 -1
- package/assets/skills/localization-testing/SKILL.md +0 -1
- package/assets/skills/mobile-testing/SKILL.md +0 -1
- package/assets/skills/mutation-testing/SKILL.md +0 -1
- package/assets/skills/n8n-expression-testing/SKILL.md +0 -1
- package/assets/skills/n8n-integration-testing-patterns/SKILL.md +0 -1
- package/assets/skills/n8n-security-testing/SKILL.md +0 -1
- package/assets/skills/n8n-trigger-testing-strategies/SKILL.md +0 -1
- package/assets/skills/n8n-workflow-testing-fundamentals/SKILL.md +0 -1
- package/assets/skills/qcsd-cicd-swarm/SKILL.md +0 -1
- package/assets/skills/qcsd-development-swarm/SKILL.md +0 -1
- package/assets/skills/qcsd-ideation-swarm/SKILL.md +0 -1
- package/assets/skills/qcsd-production-swarm/SKILL.md +0 -1
- package/assets/skills/qcsd-production-swarm/steps/01-flag-detection.md +1 -1
- package/assets/skills/qcsd-production-swarm/steps/07-learning-persistence.md +2 -2
- package/assets/skills/qcsd-refinement-swarm/SKILL.md +0 -1
- package/assets/skills/qcsd-refinement-swarm/steps/01-flag-detection.md +1 -1
- package/assets/skills/qe-chaos-resilience/SKILL.md +0 -1
- package/assets/skills/qe-code-intelligence/SKILL.md +0 -1
- package/assets/skills/qe-contract-testing/SKILL.md +0 -1
- package/assets/skills/qe-coverage-analysis/SKILL.md +0 -1
- package/assets/skills/qe-defect-intelligence/SKILL.md +0 -1
- package/assets/skills/qe-iterative-loop/SKILL.md +0 -1
- package/assets/skills/qe-learning-optimization/SKILL.md +0 -1
- package/assets/skills/qe-quality-assessment/SKILL.md +0 -1
- package/assets/skills/qe-requirements-validation/SKILL.md +0 -1
- package/assets/skills/qe-security-compliance/SKILL.md +0 -1
- package/assets/skills/qe-test-execution/SKILL.md +0 -1
- package/assets/skills/qe-test-generation/SKILL.md +0 -1
- package/assets/skills/qe-visual-accessibility/SKILL.md +0 -1
- package/assets/skills/quality-metrics/SKILL.md +0 -1
- package/assets/skills/refactoring-patterns/SKILL.md +0 -1
- package/assets/skills/regression-testing/SKILL.md +0 -1
- package/assets/skills/risk-based-testing/SKILL.md +0 -1
- package/assets/skills/security-visual-testing/SKILL.md +0 -1
- package/assets/skills/sherlock-review/SKILL.md +0 -1
- package/assets/skills/shift-left-testing/SKILL.md +0 -1
- package/assets/skills/shift-right-testing/SKILL.md +0 -1
- package/assets/skills/six-thinking-hats/SKILL.md +0 -1
- package/assets/skills/tdd-london-chicago/SKILL.md +0 -1
- package/assets/skills/technical-writing/SKILL.md +0 -1
- package/assets/skills/test-automation-strategy/SKILL.md +0 -1
- package/assets/skills/test-data-management/SKILL.md +0 -1
- package/assets/skills/test-design-techniques/SKILL.md +0 -1
- package/assets/skills/test-environment-management/SKILL.md +0 -1
- package/assets/skills/test-reporting-analytics/SKILL.md +0 -1
- package/assets/skills/validation-pipeline/SKILL.md +4 -5
- package/assets/skills/visual-testing-advanced/SKILL.md +0 -1
- package/assets/skills/xp-practices/SKILL.md +0 -1
- package/dist/adapters/claude-flow/detect.d.ts +5 -0
- package/dist/adapters/claude-flow/detect.js +38 -18
- package/dist/adapters/claude-flow/model-router-bridge.js +4 -3
- package/dist/adapters/claude-flow/pretrain-bridge.js +5 -4
- package/dist/adapters/claude-flow/trajectory-bridge.js +5 -4
- package/dist/cli/bundle.js +159 -62
- package/dist/cli/commands/claude-flow-setup.js +4 -4
- package/dist/domains/learning-optimization/coordinator.d.ts +6 -1
- package/dist/domains/learning-optimization/coordinator.js +26 -4
- package/dist/domains/learning-optimization/plugin.d.ts +2 -0
- package/dist/domains/learning-optimization/plugin.js +2 -0
- package/dist/governance/adversarial-defense-integration.js +1 -0
- package/dist/governance/continue-gate-integration.d.ts +1 -0
- package/dist/governance/continue-gate-integration.js +1 -0
- package/dist/governance/deterministic-gateway-integration.d.ts +1 -0
- package/dist/governance/deterministic-gateway-integration.js +1 -0
- package/dist/governance/evolution-pipeline-integration.d.ts +1 -0
- package/dist/governance/evolution-pipeline-integration.js +1 -0
- package/dist/governance/memory-write-gate-integration.d.ts +1 -0
- package/dist/governance/memory-write-gate-integration.js +1 -0
- package/dist/governance/proof-envelope-integration.d.ts +1 -0
- package/dist/governance/proof-envelope-integration.js +1 -0
- package/dist/governance/shard-retriever-integration.d.ts +1 -0
- package/dist/governance/shard-retriever-integration.js +1 -0
- package/dist/governance/trust-accumulator-integration.d.ts +1 -0
- package/dist/governance/trust-accumulator-integration.js +1 -0
- package/dist/init/agents-installer.js +28 -1
- package/dist/init/enhancements/claude-flow-adapter.js +15 -2
- package/dist/init/init-wizard-hooks.js +3 -3
- package/dist/init/kiro-installer.js +10 -4
- package/dist/init/opencode-installer.d.ts +13 -1
- package/dist/init/opencode-installer.js +50 -11
- package/dist/init/phases/07-hooks.js +21 -7
- package/dist/init/settings-merge.d.ts +1 -1
- package/dist/init/settings-merge.js +13 -13
- package/dist/mcp/bundle.js +70 -30
- package/package.json +8 -3
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Validation Pipeline Helper
|
|
4
|
+
*
|
|
5
|
+
* Runs structured validation pipelines against documents with sequential
|
|
6
|
+
* step execution, gate enforcement, per-step scoring, and weighted rollup.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node validation-pipeline.cjs <pipeline> <file> [options]
|
|
10
|
+
* node validation-pipeline.cjs requirements docs/requirements.md
|
|
11
|
+
* node validation-pipeline.cjs requirements docs/requirements.md --steps format-check,completeness-check
|
|
12
|
+
* node validation-pipeline.cjs requirements docs/requirements.md --continue-on-failure --json
|
|
13
|
+
*/
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Types (documented in SKILL.md output schema)
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {'pass'|'fail'|'warn'} Status
|
|
25
|
+
* @typedef {'blocking'|'warning'|'info'} Severity
|
|
26
|
+
* @typedef {'format'|'content'|'quality'|'traceability'|'compliance'} Category
|
|
27
|
+
*
|
|
28
|
+
* @typedef {Object} Finding
|
|
29
|
+
* @property {'HIGH'|'MEDIUM'|'LOW'|'INFO'} level
|
|
30
|
+
* @property {string} message
|
|
31
|
+
* @property {string} [evidence]
|
|
32
|
+
* @property {string} stepId
|
|
33
|
+
*
|
|
34
|
+
* @typedef {Object} StepResult
|
|
35
|
+
* @property {string} id
|
|
36
|
+
* @property {string} name
|
|
37
|
+
* @property {Category} category
|
|
38
|
+
* @property {Severity} severity
|
|
39
|
+
* @property {Status} status
|
|
40
|
+
* @property {number} score
|
|
41
|
+
* @property {Finding[]} findings
|
|
42
|
+
* @property {number} durationMs
|
|
43
|
+
*
|
|
44
|
+
* @typedef {Object} PipelineResult
|
|
45
|
+
* @property {string} pipelineId
|
|
46
|
+
* @property {string} pipelineName
|
|
47
|
+
* @property {Status} overall
|
|
48
|
+
* @property {number} score
|
|
49
|
+
* @property {StepResult[]} steps
|
|
50
|
+
* @property {Finding[]} blockers
|
|
51
|
+
* @property {boolean} halted
|
|
52
|
+
* @property {string} [haltedAt]
|
|
53
|
+
* @property {number} totalDuration
|
|
54
|
+
* @property {string} timestamp
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Category weights (from SKILL.md)
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
const CATEGORY_WEIGHTS = {
|
|
62
|
+
format: 0.10,
|
|
63
|
+
content: 0.30,
|
|
64
|
+
quality: 0.25,
|
|
65
|
+
traceability: 0.20,
|
|
66
|
+
compliance: 0.15,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Requirements pipeline — 13 steps
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
/** @type {Array<{id: string, name: string, category: Category, severity: Severity, check: (content: string, prior: StepResult[]) => {score: number, findings: Finding[]}}>} */
|
|
74
|
+
const REQUIREMENTS_STEPS = [
|
|
75
|
+
{
|
|
76
|
+
id: 'format-check',
|
|
77
|
+
name: 'Format Check',
|
|
78
|
+
category: 'format',
|
|
79
|
+
severity: 'blocking',
|
|
80
|
+
check(content, _prior) {
|
|
81
|
+
const findings = [];
|
|
82
|
+
const lines = content.split('\n');
|
|
83
|
+
const headings = lines.filter(l => /^#{1,3}\s/.test(l));
|
|
84
|
+
|
|
85
|
+
if (lines.length < 10) {
|
|
86
|
+
findings.push({ level: 'HIGH', message: 'Document too short (< 10 lines)', stepId: 'format-check' });
|
|
87
|
+
}
|
|
88
|
+
if (headings.length === 0) {
|
|
89
|
+
findings.push({ level: 'HIGH', message: 'No headings found — missing required sections', stepId: 'format-check' });
|
|
90
|
+
}
|
|
91
|
+
const score = findings.length === 0 ? 100 : Math.max(0, 100 - findings.length * 40);
|
|
92
|
+
return { score, findings };
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'completeness-check',
|
|
97
|
+
name: 'Completeness Check',
|
|
98
|
+
category: 'content',
|
|
99
|
+
severity: 'blocking',
|
|
100
|
+
check(content, _prior) {
|
|
101
|
+
const findings = [];
|
|
102
|
+
const lower = content.toLowerCase();
|
|
103
|
+
|
|
104
|
+
const requiredSections = ['acceptance criteria', 'requirements', 'user stor'];
|
|
105
|
+
const found = requiredSections.filter(s => lower.includes(s));
|
|
106
|
+
if (found.length === 0) {
|
|
107
|
+
findings.push({ level: 'HIGH', message: 'No acceptance criteria or requirements section found', stepId: 'completeness-check' });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Check for empty sections (heading followed immediately by another heading)
|
|
111
|
+
const lines = content.split('\n');
|
|
112
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
113
|
+
if (/^#{1,3}\s/.test(lines[i]) && /^#{1,3}\s/.test(lines[i + 1])) {
|
|
114
|
+
findings.push({ level: 'MEDIUM', message: `Empty section: ${lines[i].trim()}`, stepId: 'completeness-check' });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const score = findings.length === 0 ? 100 : Math.max(0, 100 - findings.filter(f => f.level === 'HIGH').length * 40 - findings.filter(f => f.level === 'MEDIUM').length * 10);
|
|
119
|
+
return { score, findings };
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: 'invest-criteria',
|
|
124
|
+
name: 'INVEST Criteria',
|
|
125
|
+
category: 'quality',
|
|
126
|
+
severity: 'warning',
|
|
127
|
+
check(content, _prior) {
|
|
128
|
+
const findings = [];
|
|
129
|
+
const lower = content.toLowerCase();
|
|
130
|
+
|
|
131
|
+
// Check for signs of INVEST qualities
|
|
132
|
+
const checks = [
|
|
133
|
+
{ name: 'Independent', pattern: /depend(s|ent|ency)/i, inverse: true, msg: 'Requirements may have undeclared dependencies' },
|
|
134
|
+
{ name: 'Testable', pattern: /test|verif|assert|expect/i, inverse: false, msg: 'No testability indicators found' },
|
|
135
|
+
{ name: 'Small', heuristic: () => content.length > 20000, msg: 'Document is very large — requirements may not be small enough' },
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
let issues = 0;
|
|
139
|
+
for (const c of checks) {
|
|
140
|
+
if (c.heuristic) {
|
|
141
|
+
if (c.heuristic()) { findings.push({ level: 'MEDIUM', message: c.msg, stepId: 'invest-criteria' }); issues++; }
|
|
142
|
+
} else if (c.inverse ? c.pattern.test(lower) : !c.pattern.test(lower)) {
|
|
143
|
+
findings.push({ level: 'MEDIUM', message: c.msg, stepId: 'invest-criteria' }); issues++;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const score = Math.max(0, 100 - issues * 20);
|
|
148
|
+
return { score, findings };
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: 'smart-acceptance',
|
|
153
|
+
name: 'SMART Acceptance Criteria',
|
|
154
|
+
category: 'quality',
|
|
155
|
+
severity: 'warning',
|
|
156
|
+
check(content, _prior) {
|
|
157
|
+
const findings = [];
|
|
158
|
+
|
|
159
|
+
// Look for acceptance criteria sections
|
|
160
|
+
const acMatch = content.match(/acceptance criteria[\s\S]*?(?=\n#{1,3}\s|$)/i);
|
|
161
|
+
if (!acMatch) {
|
|
162
|
+
findings.push({ level: 'MEDIUM', message: 'No acceptance criteria section found for SMART evaluation', stepId: 'smart-acceptance' });
|
|
163
|
+
return { score: 50, findings };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const ac = acMatch[0];
|
|
167
|
+
if (!/\d/.test(ac)) {
|
|
168
|
+
findings.push({ level: 'MEDIUM', message: 'Acceptance criteria lack measurable values (no numbers found)', stepId: 'smart-acceptance' });
|
|
169
|
+
}
|
|
170
|
+
if (!/when|given|then|if/i.test(ac)) {
|
|
171
|
+
findings.push({ level: 'LOW', message: 'Acceptance criteria lack specific conditions (no when/given/then)', stepId: 'smart-acceptance' });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const score = Math.max(0, 100 - findings.length * 25);
|
|
175
|
+
return { score, findings };
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: 'testability-score',
|
|
180
|
+
name: 'Testability Score',
|
|
181
|
+
category: 'quality',
|
|
182
|
+
severity: 'warning',
|
|
183
|
+
check(content, _prior) {
|
|
184
|
+
const findings = [];
|
|
185
|
+
|
|
186
|
+
// Count requirements-like statements
|
|
187
|
+
const reqs = content.match(/shall|must|should|will/gi) || [];
|
|
188
|
+
const testable = content.match(/test|verify|assert|expect|check|validate|confirm/gi) || [];
|
|
189
|
+
|
|
190
|
+
if (reqs.length > 0 && testable.length === 0) {
|
|
191
|
+
findings.push({ level: 'MEDIUM', message: `${reqs.length} requirement statements but no testability language found`, stepId: 'testability-score' });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const ratio = reqs.length > 0 ? testable.length / reqs.length : 1;
|
|
195
|
+
const score = Math.min(100, Math.round(ratio * 100));
|
|
196
|
+
return { score, findings };
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: 'vague-term-detection',
|
|
201
|
+
name: 'Vague Term Detection',
|
|
202
|
+
category: 'content',
|
|
203
|
+
severity: 'info',
|
|
204
|
+
check(content, _prior) {
|
|
205
|
+
const findings = [];
|
|
206
|
+
const vagueTerms = ['should', 'might', 'various', 'etc', 'some', 'many', 'few', 'often', 'usually', 'approximately', 'fairly', 'quite'];
|
|
207
|
+
|
|
208
|
+
for (const term of vagueTerms) {
|
|
209
|
+
const regex = new RegExp(`\\b${term}\\b`, 'gi');
|
|
210
|
+
const matches = content.match(regex);
|
|
211
|
+
if (matches && matches.length > 2) {
|
|
212
|
+
findings.push({ level: 'LOW', message: `Vague term "${term}" used ${matches.length} times`, stepId: 'vague-term-detection' });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const score = Math.max(0, 100 - findings.length * 10);
|
|
217
|
+
return { score, findings };
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: 'information-density',
|
|
222
|
+
name: 'Information Density',
|
|
223
|
+
category: 'content',
|
|
224
|
+
severity: 'info',
|
|
225
|
+
check(content, _prior) {
|
|
226
|
+
const findings = [];
|
|
227
|
+
const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 0);
|
|
228
|
+
|
|
229
|
+
// Detect filler sentences (very short or very generic)
|
|
230
|
+
let fillerCount = 0;
|
|
231
|
+
for (const s of sentences) {
|
|
232
|
+
const words = s.trim().split(/\s+/);
|
|
233
|
+
if (words.length <= 3 && !/^#{1,3}/.test(s.trim())) {
|
|
234
|
+
fillerCount++;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (fillerCount > sentences.length * 0.2) {
|
|
239
|
+
findings.push({ level: 'LOW', message: `${fillerCount}/${sentences.length} sentences appear to be filler (≤3 words)`, stepId: 'information-density' });
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const ratio = sentences.length > 0 ? 1 - fillerCount / sentences.length : 1;
|
|
243
|
+
const score = Math.round(ratio * 100);
|
|
244
|
+
return { score, findings };
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
id: 'traceability-check',
|
|
249
|
+
name: 'Traceability Check',
|
|
250
|
+
category: 'traceability',
|
|
251
|
+
severity: 'warning',
|
|
252
|
+
check(content, _prior) {
|
|
253
|
+
const findings = [];
|
|
254
|
+
|
|
255
|
+
// Look for requirement IDs (REQ-xxx, US-xxx, FR-xxx, etc.)
|
|
256
|
+
const reqIds = content.match(/\b(REQ|US|FR|NFR|UC|TC|TS)-\d+/g) || [];
|
|
257
|
+
if (reqIds.length === 0) {
|
|
258
|
+
findings.push({ level: 'MEDIUM', message: 'No requirement IDs found (e.g., REQ-001, US-101) — traceability not possible', stepId: 'traceability-check' });
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Check for test references
|
|
262
|
+
const testRefs = content.match(/\b(TC|TS|TEST)-\d+/g) || [];
|
|
263
|
+
if (reqIds.length > 0 && testRefs.length === 0) {
|
|
264
|
+
findings.push({ level: 'LOW', message: `${reqIds.length} requirement IDs found but no test case references`, stepId: 'traceability-check' });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const score = reqIds.length > 0 ? (testRefs.length > 0 ? 100 : 60) : 30;
|
|
268
|
+
return { score, findings };
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
id: 'implementation-leakage',
|
|
273
|
+
name: 'Implementation Leakage',
|
|
274
|
+
category: 'quality',
|
|
275
|
+
severity: 'warning',
|
|
276
|
+
check(content, _prior) {
|
|
277
|
+
const findings = [];
|
|
278
|
+
const implTerms = [
|
|
279
|
+
{ term: 'database', pattern: /\b(MySQL|PostgreSQL|MongoDB|Redis|SQLite|DynamoDB)\b/gi },
|
|
280
|
+
{ term: 'framework', pattern: /\b(React|Angular|Vue|Express|Django|Rails|Spring)\b/gi },
|
|
281
|
+
{ term: 'language', pattern: /\b(use Java|use Python|use TypeScript|implement in|code in)\b/gi },
|
|
282
|
+
{ term: 'API detail', pattern: /\b(REST endpoint|GraphQL mutation|POST \/api\/|GET \/api\/)\b/gi },
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
for (const { term, pattern } of implTerms) {
|
|
286
|
+
const matches = content.match(pattern);
|
|
287
|
+
if (matches) {
|
|
288
|
+
findings.push({ level: 'MEDIUM', message: `Implementation leakage (${term}): "${matches[0]}" — requirements should not prescribe implementation`, stepId: 'implementation-leakage' });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const score = Math.max(0, 100 - findings.length * 20);
|
|
293
|
+
return { score, findings };
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
id: 'domain-compliance',
|
|
298
|
+
name: 'Domain Compliance',
|
|
299
|
+
category: 'compliance',
|
|
300
|
+
severity: 'info',
|
|
301
|
+
check(content, _prior) {
|
|
302
|
+
const findings = [];
|
|
303
|
+
|
|
304
|
+
// Basic check: document uses consistent terminology
|
|
305
|
+
const terms = new Map();
|
|
306
|
+
const words = content.match(/\b[A-Z][a-z]+(?:\s[A-Z][a-z]+)*\b/g) || [];
|
|
307
|
+
for (const w of words) {
|
|
308
|
+
terms.set(w, (terms.get(w) || 0) + 1);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Flag terms that appear only once (potential inconsistency)
|
|
312
|
+
const singleUse = [...terms.entries()].filter(([, c]) => c === 1 && terms.size > 10);
|
|
313
|
+
if (singleUse.length > terms.size * 0.5) {
|
|
314
|
+
findings.push({ level: 'INFO', message: `Many terms used only once (${singleUse.length}/${terms.size}) — potential terminology inconsistency`, stepId: 'domain-compliance' });
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const score = findings.length === 0 ? 100 : 70;
|
|
318
|
+
return { score, findings };
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
id: 'dependency-analysis',
|
|
323
|
+
name: 'Dependency Analysis',
|
|
324
|
+
category: 'traceability',
|
|
325
|
+
severity: 'info',
|
|
326
|
+
check(content, _prior) {
|
|
327
|
+
const findings = [];
|
|
328
|
+
|
|
329
|
+
const reqIds = content.match(/\b(REQ|US|FR|NFR|UC)-\d+/g) || [];
|
|
330
|
+
const depKeywords = content.match(/\b(depends on|requires|blocks|blocked by|prerequisite|after)\b/gi) || [];
|
|
331
|
+
|
|
332
|
+
if (reqIds.length > 3 && depKeywords.length === 0) {
|
|
333
|
+
findings.push({ level: 'LOW', message: `${reqIds.length} requirements found but no dependency language — cross-requirement dependencies may be undeclared`, stepId: 'dependency-analysis' });
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const score = reqIds.length <= 3 ? 100 : (depKeywords.length > 0 ? 100 : 60);
|
|
337
|
+
return { score, findings };
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
id: 'bdd-scenario-generation',
|
|
342
|
+
name: 'BDD Scenario Generation',
|
|
343
|
+
category: 'quality',
|
|
344
|
+
severity: 'warning',
|
|
345
|
+
check(content, _prior) {
|
|
346
|
+
const findings = [];
|
|
347
|
+
|
|
348
|
+
// Check if Given/When/Then already present
|
|
349
|
+
const bddPatterns = content.match(/\b(Given|When|Then|And|But)\b/g) || [];
|
|
350
|
+
if (bddPatterns.length > 0) {
|
|
351
|
+
return { score: 100, findings: [] };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Check if requirements are specific enough to derive BDD scenarios
|
|
355
|
+
const actionVerbs = content.match(/\b(click|submit|enter|select|navigate|view|display|send|receive|create|update|delete)\b/gi) || [];
|
|
356
|
+
if (actionVerbs.length === 0) {
|
|
357
|
+
findings.push({ level: 'MEDIUM', message: 'No action verbs found — requirements may be too abstract to generate BDD scenarios', stepId: 'bdd-scenario-generation' });
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const score = actionVerbs.length > 0 ? 80 : 40;
|
|
361
|
+
return { score, findings };
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
id: 'holistic-quality',
|
|
366
|
+
name: 'Holistic Quality',
|
|
367
|
+
category: 'compliance',
|
|
368
|
+
severity: 'blocking',
|
|
369
|
+
check(content, prior) {
|
|
370
|
+
const findings = [];
|
|
371
|
+
|
|
372
|
+
// Aggregate signals from prior steps
|
|
373
|
+
const failedSteps = prior.filter(s => s.status === 'fail');
|
|
374
|
+
const avgScore = prior.length > 0 ? prior.reduce((sum, s) => sum + s.score, 0) / prior.length : 0;
|
|
375
|
+
|
|
376
|
+
if (failedSteps.length > 3) {
|
|
377
|
+
findings.push({ level: 'HIGH', message: `${failedSteps.length} steps failed — document has systemic quality issues`, stepId: 'holistic-quality' });
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (avgScore < 50) {
|
|
381
|
+
findings.push({ level: 'HIGH', message: `Average step score is ${avgScore.toFixed(0)}/100 — document needs significant revision`, stepId: 'holistic-quality' });
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Check for contradictions (very basic: negation near requirement terms)
|
|
385
|
+
const contradictions = content.match(/\bnot\b.{0,30}\b(shall|must|will)\b/gi) || [];
|
|
386
|
+
if (contradictions.length > 2) {
|
|
387
|
+
findings.push({ level: 'MEDIUM', message: `${contradictions.length} potential contradictions found (negation near requirement terms)`, stepId: 'holistic-quality' });
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const score = Math.max(0, 100 - failedSteps.length * 15 - findings.length * 10);
|
|
391
|
+
return { score, findings };
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
];
|
|
395
|
+
|
|
396
|
+
// ---------------------------------------------------------------------------
|
|
397
|
+
// Pipeline registry
|
|
398
|
+
// ---------------------------------------------------------------------------
|
|
399
|
+
|
|
400
|
+
const PIPELINES = {
|
|
401
|
+
requirements: { name: 'Requirements Pipeline', steps: REQUIREMENTS_STEPS },
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
// ---------------------------------------------------------------------------
|
|
405
|
+
// Pipeline runner
|
|
406
|
+
// ---------------------------------------------------------------------------
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* @param {string} pipelineName
|
|
410
|
+
* @param {string} content
|
|
411
|
+
* @param {Object} options
|
|
412
|
+
* @param {string[]} [options.steps]
|
|
413
|
+
* @param {boolean} [options.continueOnFailure]
|
|
414
|
+
* @returns {PipelineResult}
|
|
415
|
+
*/
|
|
416
|
+
function runPipeline(pipelineName, content, options = {}) {
|
|
417
|
+
const pipeline = PIPELINES[pipelineName];
|
|
418
|
+
if (!pipeline) {
|
|
419
|
+
return {
|
|
420
|
+
pipelineId: `${pipelineName}-${Date.now()}`,
|
|
421
|
+
pipelineName: pipelineName,
|
|
422
|
+
overall: 'fail',
|
|
423
|
+
score: 0,
|
|
424
|
+
steps: [],
|
|
425
|
+
blockers: [{ level: 'HIGH', message: `Unknown pipeline: ${pipelineName}. Available: ${Object.keys(PIPELINES).join(', ')}`, stepId: 'pipeline-runner' }],
|
|
426
|
+
halted: true,
|
|
427
|
+
haltedAt: 'pipeline-runner',
|
|
428
|
+
totalDuration: 0,
|
|
429
|
+
timestamp: new Date().toISOString(),
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
let steps = pipeline.steps;
|
|
434
|
+
if (options.steps && options.steps.length > 0) {
|
|
435
|
+
steps = steps.filter(s => options.steps.includes(s.id));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const startTime = Date.now();
|
|
439
|
+
/** @type {StepResult[]} */
|
|
440
|
+
const results = [];
|
|
441
|
+
let halted = false;
|
|
442
|
+
let haltedAt = undefined;
|
|
443
|
+
|
|
444
|
+
for (const step of steps) {
|
|
445
|
+
const stepStart = Date.now();
|
|
446
|
+
let score = 0;
|
|
447
|
+
let findings = [];
|
|
448
|
+
let status = 'pass';
|
|
449
|
+
|
|
450
|
+
try {
|
|
451
|
+
const result = step.check(content, results);
|
|
452
|
+
score = result.score;
|
|
453
|
+
findings = result.findings;
|
|
454
|
+
|
|
455
|
+
if (score < 50) status = 'fail';
|
|
456
|
+
else if (score < 80) status = 'warn';
|
|
457
|
+
else status = 'pass';
|
|
458
|
+
} catch (err) {
|
|
459
|
+
status = 'fail';
|
|
460
|
+
score = 0;
|
|
461
|
+
findings = [{ level: 'HIGH', message: `Step threw exception: ${err.message}`, stepId: step.id }];
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
results.push({
|
|
465
|
+
id: step.id,
|
|
466
|
+
name: step.name,
|
|
467
|
+
category: step.category,
|
|
468
|
+
severity: step.severity,
|
|
469
|
+
status,
|
|
470
|
+
score,
|
|
471
|
+
findings,
|
|
472
|
+
durationMs: Date.now() - stepStart,
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// Gate enforcement
|
|
476
|
+
if (status === 'fail' && step.severity === 'blocking' && !options.continueOnFailure) {
|
|
477
|
+
halted = true;
|
|
478
|
+
haltedAt = step.id;
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Weighted rollup by category
|
|
484
|
+
const categoryScores = {};
|
|
485
|
+
const categoryCounts = {};
|
|
486
|
+
for (const r of results) {
|
|
487
|
+
categoryScores[r.category] = (categoryScores[r.category] || 0) + r.score;
|
|
488
|
+
categoryCounts[r.category] = (categoryCounts[r.category] || 0) + 1;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
let weightedScore = 0;
|
|
492
|
+
let totalWeight = 0;
|
|
493
|
+
for (const [cat, weight] of Object.entries(CATEGORY_WEIGHTS)) {
|
|
494
|
+
if (categoryCounts[cat]) {
|
|
495
|
+
weightedScore += weight * (categoryScores[cat] / categoryCounts[cat]);
|
|
496
|
+
totalWeight += weight;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
const finalScore = totalWeight > 0 ? Math.round(weightedScore / totalWeight * 100) / 100 : 0;
|
|
500
|
+
|
|
501
|
+
const blockers = results.flatMap(r => r.findings.filter(f => f.level === 'HIGH'));
|
|
502
|
+
const hasBlocker = results.some(r => r.status === 'fail' && r.severity === 'blocking');
|
|
503
|
+
|
|
504
|
+
let overall = 'pass';
|
|
505
|
+
if (hasBlocker || halted) overall = 'fail';
|
|
506
|
+
else if (finalScore < 80) overall = 'warn';
|
|
507
|
+
|
|
508
|
+
return {
|
|
509
|
+
pipelineId: `${pipelineName}-${Date.now()}`,
|
|
510
|
+
pipelineName: pipeline.name,
|
|
511
|
+
overall,
|
|
512
|
+
score: Math.round(finalScore),
|
|
513
|
+
steps: results,
|
|
514
|
+
blockers,
|
|
515
|
+
halted,
|
|
516
|
+
haltedAt,
|
|
517
|
+
totalDuration: Date.now() - startTime,
|
|
518
|
+
timestamp: new Date().toISOString(),
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// ---------------------------------------------------------------------------
|
|
523
|
+
// Output formatters
|
|
524
|
+
// ---------------------------------------------------------------------------
|
|
525
|
+
|
|
526
|
+
function formatMarkdown(result) {
|
|
527
|
+
const lines = [];
|
|
528
|
+
lines.push(`# Validation Report: ${result.pipelineName}`);
|
|
529
|
+
lines.push('');
|
|
530
|
+
lines.push(`**Overall**: ${result.overall.toUpperCase()} | **Score**: ${result.score}/100 | **Duration**: ${result.totalDuration}ms`);
|
|
531
|
+
if (result.halted) {
|
|
532
|
+
lines.push(`**HALTED** at step: ${result.haltedAt}`);
|
|
533
|
+
}
|
|
534
|
+
lines.push('');
|
|
535
|
+
lines.push('## Step Results');
|
|
536
|
+
lines.push('| # | Step | Status | Score | Findings | Duration |');
|
|
537
|
+
lines.push('|---|------|--------|-------|----------|----------|');
|
|
538
|
+
|
|
539
|
+
result.steps.forEach((s, i) => {
|
|
540
|
+
const statusIcon = s.status === 'pass' ? 'PASS' : s.status === 'warn' ? 'WARN' : 'FAIL';
|
|
541
|
+
lines.push(`| ${i + 1} | ${s.name} | ${statusIcon} | ${s.score} | ${s.findings.length} | ${s.durationMs}ms |`);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
if (result.blockers.length > 0) {
|
|
545
|
+
lines.push('');
|
|
546
|
+
lines.push('## Blockers');
|
|
547
|
+
for (const b of result.blockers) {
|
|
548
|
+
lines.push(`- [${b.level}] ${b.message}`);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const allFindings = result.steps.flatMap(s => s.findings);
|
|
553
|
+
if (allFindings.length > 0) {
|
|
554
|
+
lines.push('');
|
|
555
|
+
lines.push('## All Findings');
|
|
556
|
+
for (const f of allFindings) {
|
|
557
|
+
lines.push(`- [${f.level}] ${f.message}`);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return lines.join('\n');
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// ---------------------------------------------------------------------------
|
|
565
|
+
// CLI
|
|
566
|
+
// ---------------------------------------------------------------------------
|
|
567
|
+
|
|
568
|
+
function main() {
|
|
569
|
+
const args = process.argv.slice(2);
|
|
570
|
+
|
|
571
|
+
if (args.length < 2 || args.includes('--help') || args.includes('-h')) {
|
|
572
|
+
console.log('Usage: node validation-pipeline.cjs <pipeline> <file> [options]');
|
|
573
|
+
console.log('');
|
|
574
|
+
console.log('Pipelines: ' + Object.keys(PIPELINES).join(', '));
|
|
575
|
+
console.log('');
|
|
576
|
+
console.log('Options:');
|
|
577
|
+
console.log(' --steps <id,id,...> Run specific steps only');
|
|
578
|
+
console.log(' --continue-on-failure Skip blocking gates');
|
|
579
|
+
console.log(' --json Output as JSON');
|
|
580
|
+
console.log(' --help Show this help');
|
|
581
|
+
process.exit(0);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const pipelineName = args[0];
|
|
585
|
+
const filePath = args[1];
|
|
586
|
+
|
|
587
|
+
// Parse options
|
|
588
|
+
const stepFilter = args.includes('--steps') ? args[args.indexOf('--steps') + 1].split(',') : null;
|
|
589
|
+
const continueOnFailure = args.includes('--continue-on-failure');
|
|
590
|
+
const jsonOutput = args.includes('--json');
|
|
591
|
+
|
|
592
|
+
// Read file
|
|
593
|
+
const resolvedPath = path.resolve(filePath);
|
|
594
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
595
|
+
const err = { error: `File not found: ${resolvedPath}` };
|
|
596
|
+
if (jsonOutput) { console.log(JSON.stringify(err)); } else { console.error(err.error); }
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const content = fs.readFileSync(resolvedPath, 'utf-8');
|
|
601
|
+
if (content.trim().length === 0) {
|
|
602
|
+
const err = { error: 'File is empty' };
|
|
603
|
+
if (jsonOutput) { console.log(JSON.stringify(err)); } else { console.error(err.error); }
|
|
604
|
+
process.exit(1);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Run pipeline
|
|
608
|
+
const options = { continueOnFailure };
|
|
609
|
+
if (stepFilter) options.steps = stepFilter;
|
|
610
|
+
|
|
611
|
+
const result = runPipeline(pipelineName, content, options);
|
|
612
|
+
|
|
613
|
+
if (jsonOutput) {
|
|
614
|
+
console.log(JSON.stringify(result, null, 2));
|
|
615
|
+
} else {
|
|
616
|
+
console.log(formatMarkdown(result));
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Exit code: 0=pass, 1=fail, 2=warn
|
|
620
|
+
if (result.overall === 'fail') process.exit(1);
|
|
621
|
+
if (result.overall === 'warn') process.exit(2);
|
|
622
|
+
process.exit(0);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
main();
|