@yasserkhanorg/impact-gate 2.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/LICENSE +168 -0
- package/README.md +520 -0
- package/dist/adapters/cypress.d.ts +10 -0
- package/dist/adapters/cypress.d.ts.map +1 -0
- package/dist/adapters/cypress.js +86 -0
- package/dist/adapters/framework_adapter.d.ts +41 -0
- package/dist/adapters/framework_adapter.d.ts.map +1 -0
- package/dist/adapters/framework_adapter.js +152 -0
- package/dist/adapters/playwright.d.ts +10 -0
- package/dist/adapters/playwright.d.ts.map +1 -0
- package/dist/adapters/playwright.js +86 -0
- package/dist/adapters/pytest.d.ts +10 -0
- package/dist/adapters/pytest.d.ts.map +1 -0
- package/dist/adapters/pytest.js +96 -0
- package/dist/adapters/supertest.d.ts +12 -0
- package/dist/adapters/supertest.d.ts.map +1 -0
- package/dist/adapters/supertest.js +85 -0
- package/dist/agent/api_catalog.d.ts +11 -0
- package/dist/agent/api_catalog.d.ts.map +1 -0
- package/dist/agent/api_catalog.js +210 -0
- package/dist/agent/config.d.ts +193 -0
- package/dist/agent/config.d.ts.map +1 -0
- package/dist/agent/config.js +875 -0
- package/dist/agent/feedback.d.ts +91 -0
- package/dist/agent/feedback.d.ts.map +1 -0
- package/dist/agent/feedback.js +323 -0
- package/dist/agent/git.d.ts +19 -0
- package/dist/agent/git.d.ts.map +1 -0
- package/dist/agent/git.js +257 -0
- package/dist/agent/handoff.d.ts +22 -0
- package/dist/agent/handoff.d.ts.map +1 -0
- package/dist/agent/handoff.js +180 -0
- package/dist/agent/llm_agents_flow.d.ts +15 -0
- package/dist/agent/llm_agents_flow.d.ts.map +1 -0
- package/dist/agent/llm_agents_flow.js +434 -0
- package/dist/agent/native_flow.d.ts +6 -0
- package/dist/agent/native_flow.d.ts.map +1 -0
- package/dist/agent/native_flow.js +179 -0
- package/dist/agent/pipeline.d.ts +7 -0
- package/dist/agent/pipeline.d.ts.map +1 -0
- package/dist/agent/pipeline.js +260 -0
- package/dist/agent/pipeline_types.d.ts +54 -0
- package/dist/agent/pipeline_types.d.ts.map +1 -0
- package/dist/agent/pipeline_types.js +4 -0
- package/dist/agent/pipeline_utils.d.ts +12 -0
- package/dist/agent/pipeline_utils.d.ts.map +1 -0
- package/dist/agent/pipeline_utils.js +156 -0
- package/dist/agent/plan.d.ts +170 -0
- package/dist/agent/plan.d.ts.map +1 -0
- package/dist/agent/plan.js +86 -0
- package/dist/agent/playwright_report.d.ts +8 -0
- package/dist/agent/playwright_report.d.ts.map +1 -0
- package/dist/agent/playwright_report.js +126 -0
- package/dist/agent/process_runner.d.ts +10 -0
- package/dist/agent/process_runner.d.ts.map +1 -0
- package/dist/agent/process_runner.js +92 -0
- package/dist/agent/spec_generator.d.ts +5 -0
- package/dist/agent/spec_generator.d.ts.map +1 -0
- package/dist/agent/spec_generator.js +253 -0
- package/dist/agent/test_path.d.ts +2 -0
- package/dist/agent/test_path.d.ts.map +1 -0
- package/dist/agent/test_path.js +23 -0
- package/dist/agent/traceability_capture.d.ts +18 -0
- package/dist/agent/traceability_capture.d.ts.map +1 -0
- package/dist/agent/traceability_capture.js +313 -0
- package/dist/agent/traceability_ingest.d.ts +21 -0
- package/dist/agent/traceability_ingest.d.ts.map +1 -0
- package/dist/agent/traceability_ingest.js +237 -0
- package/dist/agent/types.d.ts +42 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +4 -0
- package/dist/agent/utils.d.ts +13 -0
- package/dist/agent/utils.d.ts.map +1 -0
- package/dist/agent/utils.js +152 -0
- package/dist/agent/validation_runner.d.ts +5 -0
- package/dist/agent/validation_runner.d.ts.map +1 -0
- package/dist/agent/validation_runner.js +77 -0
- package/dist/agentic/fix_loop.d.ts +26 -0
- package/dist/agentic/fix_loop.d.ts.map +1 -0
- package/dist/agentic/fix_loop.js +96 -0
- package/dist/agentic/playwright_runner.d.ts +43 -0
- package/dist/agentic/playwright_runner.d.ts.map +1 -0
- package/dist/agentic/playwright_runner.js +165 -0
- package/dist/agentic/runner.d.ts +27 -0
- package/dist/agentic/runner.d.ts.map +1 -0
- package/dist/agentic/runner.js +210 -0
- package/dist/agentic/types.d.ts +62 -0
- package/dist/agentic/types.d.ts.map +1 -0
- package/dist/agentic/types.js +4 -0
- package/dist/agents/coverage-evaluator.d.ts +8 -0
- package/dist/agents/coverage-evaluator.d.ts.map +1 -0
- package/dist/agents/coverage-evaluator.js +41 -0
- package/dist/agents/cross-impact.d.ts +13 -0
- package/dist/agents/cross-impact.d.ts.map +1 -0
- package/dist/agents/cross-impact.js +140 -0
- package/dist/agents/executor.d.ts +8 -0
- package/dist/agents/executor.d.ts.map +1 -0
- package/dist/agents/executor.js +75 -0
- package/dist/agents/explorer.d.ts +12 -0
- package/dist/agents/explorer.d.ts.map +1 -0
- package/dist/agents/explorer.js +43 -0
- package/dist/agents/generator.d.ts +8 -0
- package/dist/agents/generator.d.ts.map +1 -0
- package/dist/agents/generator.js +77 -0
- package/dist/agents/healer.d.ts +8 -0
- package/dist/agents/healer.d.ts.map +1 -0
- package/dist/agents/healer.js +31 -0
- package/dist/agents/impact-analyst.d.ts +8 -0
- package/dist/agents/impact-analyst.d.ts.map +1 -0
- package/dist/agents/impact-analyst.js +38 -0
- package/dist/agents/regression-advisor.d.ts +8 -0
- package/dist/agents/regression-advisor.d.ts.map +1 -0
- package/dist/agents/regression-advisor.js +116 -0
- package/dist/agents/strategist.d.ts +9 -0
- package/dist/agents/strategist.d.ts.map +1 -0
- package/dist/agents/strategist.js +92 -0
- package/dist/agents/test-designer.d.ts +8 -0
- package/dist/agents/test-designer.d.ts.map +1 -0
- package/dist/agents/test-designer.js +111 -0
- package/dist/anthropic_provider.d.ts +65 -0
- package/dist/anthropic_provider.d.ts.map +1 -0
- package/dist/anthropic_provider.js +334 -0
- package/dist/api.d.ts +48 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +151 -0
- package/dist/base_provider.d.ts +109 -0
- package/dist/base_provider.d.ts.map +1 -0
- package/dist/base_provider.js +203 -0
- package/dist/budget_ledger.d.ts +28 -0
- package/dist/budget_ledger.d.ts.map +1 -0
- package/dist/budget_ledger.js +62 -0
- package/dist/cache/cached_provider.d.ts +49 -0
- package/dist/cache/cached_provider.d.ts.map +1 -0
- package/dist/cache/cached_provider.js +91 -0
- package/dist/cache/response_cache.d.ts +79 -0
- package/dist/cache/response_cache.d.ts.map +1 -0
- package/dist/cache/response_cache.js +177 -0
- package/dist/cli/commands/analyze.d.ts +3 -0
- package/dist/cli/commands/analyze.d.ts.map +1 -0
- package/dist/cli/commands/analyze.js +77 -0
- package/dist/cli/commands/bootstrap.d.ts +3 -0
- package/dist/cli/commands/bootstrap.d.ts.map +1 -0
- package/dist/cli/commands/bootstrap.js +109 -0
- package/dist/cli/commands/cost_report.d.ts +3 -0
- package/dist/cli/commands/cost_report.d.ts.map +1 -0
- package/dist/cli/commands/cost_report.js +115 -0
- package/dist/cli/commands/crew.d.ts +3 -0
- package/dist/cli/commands/crew.d.ts.map +1 -0
- package/dist/cli/commands/crew.js +255 -0
- package/dist/cli/commands/feedback.d.ts +3 -0
- package/dist/cli/commands/feedback.d.ts.map +1 -0
- package/dist/cli/commands/feedback.js +39 -0
- package/dist/cli/commands/finalize.d.ts +3 -0
- package/dist/cli/commands/finalize.d.ts.map +1 -0
- package/dist/cli/commands/finalize.js +41 -0
- package/dist/cli/commands/gate.d.ts +3 -0
- package/dist/cli/commands/gate.d.ts.map +1 -0
- package/dist/cli/commands/gate.js +89 -0
- package/dist/cli/commands/generate.d.ts +4 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/generate.js +108 -0
- package/dist/cli/commands/heal.d.ts +3 -0
- package/dist/cli/commands/heal.d.ts.map +1 -0
- package/dist/cli/commands/heal.js +60 -0
- package/dist/cli/commands/impact.d.ts +4 -0
- package/dist/cli/commands/impact.d.ts.map +1 -0
- package/dist/cli/commands/impact.js +33 -0
- package/dist/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +169 -0
- package/dist/cli/commands/llm_health.d.ts +2 -0
- package/dist/cli/commands/llm_health.d.ts.map +1 -0
- package/dist/cli/commands/llm_health.js +22 -0
- package/dist/cli/commands/plan.d.ts +4 -0
- package/dist/cli/commands/plan.d.ts.map +1 -0
- package/dist/cli/commands/plan.js +120 -0
- package/dist/cli/commands/plan_crew.d.ts +17 -0
- package/dist/cli/commands/plan_crew.d.ts.map +1 -0
- package/dist/cli/commands/plan_crew.js +316 -0
- package/dist/cli/commands/traceability.d.ts +4 -0
- package/dist/cli/commands/traceability.d.ts.map +1 -0
- package/dist/cli/commands/traceability.js +77 -0
- package/dist/cli/commands/train.d.ts +3 -0
- package/dist/cli/commands/train.d.ts.map +1 -0
- package/dist/cli/commands/train.js +391 -0
- package/dist/cli/defaults.d.ts +35 -0
- package/dist/cli/defaults.d.ts.map +1 -0
- package/dist/cli/defaults.js +172 -0
- package/dist/cli/errors.d.ts +27 -0
- package/dist/cli/errors.d.ts.map +1 -0
- package/dist/cli/errors.js +57 -0
- package/dist/cli/parse_args.d.ts +6 -0
- package/dist/cli/parse_args.d.ts.map +1 -0
- package/dist/cli/parse_args.js +257 -0
- package/dist/cli/types.d.ts +87 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +4 -0
- package/dist/cli/usage.d.ts +2 -0
- package/dist/cli/usage.d.ts.map +1 -0
- package/dist/cli/usage.js +109 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +194 -0
- package/dist/crew/context.d.ts +55 -0
- package/dist/crew/context.d.ts.map +1 -0
- package/dist/crew/context.js +36 -0
- package/dist/crew/orchestrator.d.ts +50 -0
- package/dist/crew/orchestrator.d.ts.map +1 -0
- package/dist/crew/orchestrator.js +329 -0
- package/dist/crew/protocol.d.ts +46 -0
- package/dist/crew/protocol.d.ts.map +1 -0
- package/dist/crew/protocol.js +4 -0
- package/dist/crew/provider.d.ts +17 -0
- package/dist/crew/provider.d.ts.map +1 -0
- package/dist/crew/provider.js +36 -0
- package/dist/crew/sanitize.d.ts +3 -0
- package/dist/crew/sanitize.d.ts.map +1 -0
- package/dist/crew/sanitize.js +31 -0
- package/dist/crew/types.d.ts +52 -0
- package/dist/crew/types.d.ts.map +1 -0
- package/dist/crew/types.js +4 -0
- package/dist/crew/workflows.d.ts +52 -0
- package/dist/crew/workflows.d.ts.map +1 -0
- package/dist/crew/workflows.js +36 -0
- package/dist/custom_provider.d.ts +20 -0
- package/dist/custom_provider.d.ts.map +1 -0
- package/dist/custom_provider.js +277 -0
- package/dist/engine/ai_enrichment.d.ts +44 -0
- package/dist/engine/ai_enrichment.d.ts.map +1 -0
- package/dist/engine/ai_enrichment.js +267 -0
- package/dist/engine/diff_loader.d.ts +11 -0
- package/dist/engine/diff_loader.d.ts.map +1 -0
- package/dist/engine/diff_loader.js +63 -0
- package/dist/engine/impact_engine.d.ts +72 -0
- package/dist/engine/impact_engine.d.ts.map +1 -0
- package/dist/engine/impact_engine.js +298 -0
- package/dist/engine/plan_builder.d.ts +11 -0
- package/dist/engine/plan_builder.d.ts.map +1 -0
- package/dist/engine/plan_builder.js +599 -0
- package/dist/esm/adapters/cypress.js +49 -0
- package/dist/esm/adapters/framework_adapter.js +114 -0
- package/dist/esm/adapters/playwright.js +49 -0
- package/dist/esm/adapters/pytest.js +59 -0
- package/dist/esm/adapters/supertest.js +48 -0
- package/dist/esm/agent/api_catalog.js +199 -0
- package/dist/esm/agent/config.js +872 -0
- package/dist/esm/agent/feedback.js +317 -0
- package/dist/esm/agent/git.js +252 -0
- package/dist/esm/agent/handoff.js +177 -0
- package/dist/esm/agent/llm_agents_flow.js +421 -0
- package/dist/esm/agent/native_flow.js +175 -0
- package/dist/esm/agent/pipeline.js +256 -0
- package/dist/esm/agent/pipeline_types.js +3 -0
- package/dist/esm/agent/pipeline_utils.js +146 -0
- package/dist/esm/agent/plan.js +83 -0
- package/dist/esm/agent/playwright_report.js +123 -0
- package/dist/esm/agent/process_runner.js +83 -0
- package/dist/esm/agent/spec_generator.js +249 -0
- package/dist/esm/agent/test_path.js +20 -0
- package/dist/esm/agent/traceability_capture.js +310 -0
- package/dist/esm/agent/traceability_ingest.js +234 -0
- package/dist/esm/agent/types.js +3 -0
- package/dist/esm/agent/utils.js +138 -0
- package/dist/esm/agent/validation_runner.js +73 -0
- package/dist/esm/agentic/fix_loop.js +91 -0
- package/dist/esm/agentic/playwright_runner.js +161 -0
- package/dist/esm/agentic/runner.js +207 -0
- package/dist/esm/agentic/types.js +3 -0
- package/dist/esm/agents/coverage-evaluator.js +37 -0
- package/dist/esm/agents/cross-impact.js +136 -0
- package/dist/esm/agents/executor.js +71 -0
- package/dist/esm/agents/explorer.js +39 -0
- package/dist/esm/agents/generator.js +73 -0
- package/dist/esm/agents/healer.js +27 -0
- package/dist/esm/agents/impact-analyst.js +34 -0
- package/dist/esm/agents/regression-advisor.js +112 -0
- package/dist/esm/agents/strategist.js +88 -0
- package/dist/esm/agents/test-designer.js +107 -0
- package/dist/esm/anthropic_provider.js +326 -0
- package/dist/esm/api.js +143 -0
- package/dist/esm/base_provider.js +198 -0
- package/dist/esm/budget_ledger.js +58 -0
- package/dist/esm/cache/cached_provider.js +85 -0
- package/dist/esm/cache/response_cache.js +140 -0
- package/dist/esm/cli/commands/analyze.js +74 -0
- package/dist/esm/cli/commands/bootstrap.js +106 -0
- package/dist/esm/cli/commands/cost_report.js +112 -0
- package/dist/esm/cli/commands/crew.js +252 -0
- package/dist/esm/cli/commands/feedback.js +36 -0
- package/dist/esm/cli/commands/finalize.js +38 -0
- package/dist/esm/cli/commands/gate.js +86 -0
- package/dist/esm/cli/commands/generate.js +105 -0
- package/dist/esm/cli/commands/heal.js +57 -0
- package/dist/esm/cli/commands/impact.js +30 -0
- package/dist/esm/cli/commands/init.js +133 -0
- package/dist/esm/cli/commands/llm_health.js +19 -0
- package/dist/esm/cli/commands/plan.js +117 -0
- package/dist/esm/cli/commands/plan_crew.js +309 -0
- package/dist/esm/cli/commands/traceability.js +73 -0
- package/dist/esm/cli/commands/train.js +355 -0
- package/dist/esm/cli/defaults.js +165 -0
- package/dist/esm/cli/errors.js +52 -0
- package/dist/esm/cli/parse_args.js +251 -0
- package/dist/esm/cli/types.js +3 -0
- package/dist/esm/cli/usage.js +106 -0
- package/dist/esm/cli.js +192 -0
- package/dist/esm/crew/context.js +32 -0
- package/dist/esm/crew/orchestrator.js +325 -0
- package/dist/esm/crew/protocol.js +3 -0
- package/dist/esm/crew/provider.js +33 -0
- package/dist/esm/crew/sanitize.js +27 -0
- package/dist/esm/crew/types.js +3 -0
- package/dist/esm/crew/workflows.js +33 -0
- package/dist/esm/custom_provider.js +273 -0
- package/dist/esm/engine/ai_enrichment.js +264 -0
- package/dist/esm/engine/diff_loader.js +59 -0
- package/dist/esm/engine/impact_engine.js +291 -0
- package/dist/esm/engine/plan_builder.js +593 -0
- package/dist/esm/index.js +72 -0
- package/dist/esm/knowledge/api_surface.js +408 -0
- package/dist/esm/knowledge/cluster_utils.js +60 -0
- package/dist/esm/knowledge/context_loader.js +85 -0
- package/dist/esm/knowledge/failure_history.js +121 -0
- package/dist/esm/knowledge/kg_bridge.js +381 -0
- package/dist/esm/knowledge/kg_types.js +3 -0
- package/dist/esm/knowledge/route_families.js +393 -0
- package/dist/esm/knowledge/spec_index.js +122 -0
- package/dist/esm/logger.js +115 -0
- package/dist/esm/mcp-server.js +621 -0
- package/dist/esm/metrics/prometheus.js +149 -0
- package/dist/esm/model_router.js +59 -0
- package/dist/esm/ollama_provider.js +301 -0
- package/dist/esm/openai_provider.js +243 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/pipeline/orchestrator.js +228 -0
- package/dist/esm/pipeline/spec_verifier.js +75 -0
- package/dist/esm/pipeline/stage0_preprocess.js +102 -0
- package/dist/esm/pipeline/stage1_impact.js +140 -0
- package/dist/esm/pipeline/stage2_coverage.js +153 -0
- package/dist/esm/pipeline/stage3_generation.js +284 -0
- package/dist/esm/pipeline/stage4_heal.js +288 -0
- package/dist/esm/progress.js +112 -0
- package/dist/esm/prompts/coverage.js +57 -0
- package/dist/esm/prompts/cross-impact.js +53 -0
- package/dist/esm/prompts/generation.js +297 -0
- package/dist/esm/prompts/generation_profile.js +147 -0
- package/dist/esm/prompts/heal.js +91 -0
- package/dist/esm/prompts/impact.js +63 -0
- package/dist/esm/prompts/json_extract.js +36 -0
- package/dist/esm/prompts/strategist.js +61 -0
- package/dist/esm/prompts/test-designer.js +92 -0
- package/dist/esm/provider_factory.js +366 -0
- package/dist/esm/provider_interface.js +23 -0
- package/dist/esm/provider_utils.js +96 -0
- package/dist/esm/qa-agent/cli.js +205 -0
- package/dist/esm/qa-agent/orchestrator.js +120 -0
- package/dist/esm/qa-agent/phase1/runner.js +139 -0
- package/dist/esm/qa-agent/phase1/scope.js +126 -0
- package/dist/esm/qa-agent/phase2/agent_browser.js +95 -0
- package/dist/esm/qa-agent/phase2/agent_loop.js +351 -0
- package/dist/esm/qa-agent/phase2/exploration_state.js +97 -0
- package/dist/esm/qa-agent/phase2/tools.js +386 -0
- package/dist/esm/qa-agent/phase2/vision.js +75 -0
- package/dist/esm/qa-agent/phase3/feedback.js +34 -0
- package/dist/esm/qa-agent/phase3/reporter.js +145 -0
- package/dist/esm/qa-agent/phase3/spec_generator.js +62 -0
- package/dist/esm/qa-agent/phase3/verdict.js +66 -0
- package/dist/esm/qa-agent/safe_env.js +23 -0
- package/dist/esm/qa-agent/types.js +3 -0
- package/dist/esm/reporters/junit.js +86 -0
- package/dist/esm/reporters/reporter.js +3 -0
- package/dist/esm/reporters/sarif.js +132 -0
- package/dist/esm/resilience/circuit_breaker.js +78 -0
- package/dist/esm/resilience/retry.js +56 -0
- package/dist/esm/sanitize.js +66 -0
- package/dist/esm/training/enricher.js +345 -0
- package/dist/esm/training/kg_scanner.js +115 -0
- package/dist/esm/training/merger.js +204 -0
- package/dist/esm/training/scanner.js +923 -0
- package/dist/esm/training/types.js +6 -0
- package/dist/esm/training/validator.js +254 -0
- package/dist/esm/validation/guardrails.js +101 -0
- package/dist/esm/validation/output_schema.js +80 -0
- package/dist/esm/version.js +33 -0
- package/dist/index.d.ts +99 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +169 -0
- package/dist/knowledge/api_surface.d.ts +37 -0
- package/dist/knowledge/api_surface.d.ts.map +1 -0
- package/dist/knowledge/api_surface.js +418 -0
- package/dist/knowledge/cluster_utils.d.ts +28 -0
- package/dist/knowledge/cluster_utils.d.ts.map +1 -0
- package/dist/knowledge/cluster_utils.js +67 -0
- package/dist/knowledge/context_loader.d.ts +13 -0
- package/dist/knowledge/context_loader.d.ts.map +1 -0
- package/dist/knowledge/context_loader.js +90 -0
- package/dist/knowledge/failure_history.d.ts +39 -0
- package/dist/knowledge/failure_history.d.ts.map +1 -0
- package/dist/knowledge/failure_history.js +128 -0
- package/dist/knowledge/kg_bridge.d.ts +31 -0
- package/dist/knowledge/kg_bridge.d.ts.map +1 -0
- package/dist/knowledge/kg_bridge.js +388 -0
- package/dist/knowledge/kg_types.d.ts +75 -0
- package/dist/knowledge/kg_types.d.ts.map +1 -0
- package/dist/knowledge/kg_types.js +4 -0
- package/dist/knowledge/route_families.d.ts +98 -0
- package/dist/knowledge/route_families.d.ts.map +1 -0
- package/dist/knowledge/route_families.js +410 -0
- package/dist/knowledge/spec_index.d.ts +18 -0
- package/dist/knowledge/spec_index.d.ts.map +1 -0
- package/dist/knowledge/spec_index.js +128 -0
- package/dist/logger.d.ts +31 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +119 -0
- package/dist/mcp-server.d.ts +68 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +629 -0
- package/dist/metrics/prometheus.d.ts +37 -0
- package/dist/metrics/prometheus.d.ts.map +1 -0
- package/dist/metrics/prometheus.js +153 -0
- package/dist/model_router.d.ts +28 -0
- package/dist/model_router.d.ts.map +1 -0
- package/dist/model_router.js +63 -0
- package/dist/ollama_provider.d.ts +65 -0
- package/dist/ollama_provider.d.ts.map +1 -0
- package/dist/ollama_provider.js +309 -0
- package/dist/openai_provider.d.ts +23 -0
- package/dist/openai_provider.d.ts.map +1 -0
- package/dist/openai_provider.js +251 -0
- package/dist/pipeline/orchestrator.d.ts +33 -0
- package/dist/pipeline/orchestrator.d.ts.map +1 -0
- package/dist/pipeline/orchestrator.js +231 -0
- package/dist/pipeline/spec_verifier.d.ts +20 -0
- package/dist/pipeline/spec_verifier.d.ts.map +1 -0
- package/dist/pipeline/spec_verifier.js +79 -0
- package/dist/pipeline/stage0_preprocess.d.ts +31 -0
- package/dist/pipeline/stage0_preprocess.d.ts.map +1 -0
- package/dist/pipeline/stage0_preprocess.js +105 -0
- package/dist/pipeline/stage1_impact.d.ts +19 -0
- package/dist/pipeline/stage1_impact.d.ts.map +1 -0
- package/dist/pipeline/stage1_impact.js +143 -0
- package/dist/pipeline/stage2_coverage.d.ts +19 -0
- package/dist/pipeline/stage2_coverage.d.ts.map +1 -0
- package/dist/pipeline/stage2_coverage.js +156 -0
- package/dist/pipeline/stage3_generation.d.ts +43 -0
- package/dist/pipeline/stage3_generation.d.ts.map +1 -0
- package/dist/pipeline/stage3_generation.js +287 -0
- package/dist/pipeline/stage4_heal.d.ts +62 -0
- package/dist/pipeline/stage4_heal.d.ts.map +1 -0
- package/dist/pipeline/stage4_heal.js +294 -0
- package/dist/progress.d.ts +22 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +116 -0
- package/dist/prompts/coverage.d.ts +39 -0
- package/dist/prompts/coverage.d.ts.map +1 -0
- package/dist/prompts/coverage.js +61 -0
- package/dist/prompts/cross-impact.d.ts +23 -0
- package/dist/prompts/cross-impact.d.ts.map +1 -0
- package/dist/prompts/cross-impact.js +57 -0
- package/dist/prompts/generation.d.ts +25 -0
- package/dist/prompts/generation.d.ts.map +1 -0
- package/dist/prompts/generation.js +302 -0
- package/dist/prompts/generation_profile.d.ts +29 -0
- package/dist/prompts/generation_profile.d.ts.map +1 -0
- package/dist/prompts/generation_profile.js +151 -0
- package/dist/prompts/heal.d.ts +23 -0
- package/dist/prompts/heal.d.ts.map +1 -0
- package/dist/prompts/heal.js +95 -0
- package/dist/prompts/impact.d.ts +31 -0
- package/dist/prompts/impact.d.ts.map +1 -0
- package/dist/prompts/impact.js +67 -0
- package/dist/prompts/json_extract.d.ts +14 -0
- package/dist/prompts/json_extract.d.ts.map +1 -0
- package/dist/prompts/json_extract.js +39 -0
- package/dist/prompts/strategist.d.ts +25 -0
- package/dist/prompts/strategist.d.ts.map +1 -0
- package/dist/prompts/strategist.js +65 -0
- package/dist/prompts/test-designer.d.ts +35 -0
- package/dist/prompts/test-designer.d.ts.map +1 -0
- package/dist/prompts/test-designer.js +96 -0
- package/dist/provider_factory.d.ts +104 -0
- package/dist/provider_factory.d.ts.map +1 -0
- package/dist/provider_factory.js +371 -0
- package/dist/provider_interface.d.ts +365 -0
- package/dist/provider_interface.d.ts.map +1 -0
- package/dist/provider_interface.js +28 -0
- package/dist/provider_utils.d.ts +39 -0
- package/dist/provider_utils.d.ts.map +1 -0
- package/dist/provider_utils.js +103 -0
- package/dist/qa-agent/cli.d.ts +3 -0
- package/dist/qa-agent/cli.d.ts.map +1 -0
- package/dist/qa-agent/cli.js +207 -0
- package/dist/qa-agent/orchestrator.d.ts +3 -0
- package/dist/qa-agent/orchestrator.d.ts.map +1 -0
- package/dist/qa-agent/orchestrator.js +123 -0
- package/dist/qa-agent/phase1/runner.d.ts +3 -0
- package/dist/qa-agent/phase1/runner.d.ts.map +1 -0
- package/dist/qa-agent/phase1/runner.js +142 -0
- package/dist/qa-agent/phase1/scope.d.ts +6 -0
- package/dist/qa-agent/phase1/scope.d.ts.map +1 -0
- package/dist/qa-agent/phase1/scope.js +129 -0
- package/dist/qa-agent/phase2/agent_browser.d.ts +35 -0
- package/dist/qa-agent/phase2/agent_browser.d.ts.map +1 -0
- package/dist/qa-agent/phase2/agent_browser.js +99 -0
- package/dist/qa-agent/phase2/agent_loop.d.ts +3 -0
- package/dist/qa-agent/phase2/agent_loop.d.ts.map +1 -0
- package/dist/qa-agent/phase2/agent_loop.js +357 -0
- package/dist/qa-agent/phase2/exploration_state.d.ts +12 -0
- package/dist/qa-agent/phase2/exploration_state.d.ts.map +1 -0
- package/dist/qa-agent/phase2/exploration_state.js +109 -0
- package/dist/qa-agent/phase2/tools.d.ts +28 -0
- package/dist/qa-agent/phase2/tools.d.ts.map +1 -0
- package/dist/qa-agent/phase2/tools.js +390 -0
- package/dist/qa-agent/phase2/vision.d.ts +3 -0
- package/dist/qa-agent/phase2/vision.d.ts.map +1 -0
- package/dist/qa-agent/phase2/vision.js +78 -0
- package/dist/qa-agent/phase3/feedback.d.ts +3 -0
- package/dist/qa-agent/phase3/feedback.d.ts.map +1 -0
- package/dist/qa-agent/phase3/feedback.js +37 -0
- package/dist/qa-agent/phase3/reporter.d.ts +3 -0
- package/dist/qa-agent/phase3/reporter.d.ts.map +1 -0
- package/dist/qa-agent/phase3/reporter.js +148 -0
- package/dist/qa-agent/phase3/spec_generator.d.ts +3 -0
- package/dist/qa-agent/phase3/spec_generator.d.ts.map +1 -0
- package/dist/qa-agent/phase3/spec_generator.js +65 -0
- package/dist/qa-agent/phase3/verdict.d.ts +3 -0
- package/dist/qa-agent/phase3/verdict.d.ts.map +1 -0
- package/dist/qa-agent/phase3/verdict.js +69 -0
- package/dist/qa-agent/safe_env.d.ts +3 -0
- package/dist/qa-agent/safe_env.d.ts.map +1 -0
- package/dist/qa-agent/safe_env.js +26 -0
- package/dist/qa-agent/types.d.ts +130 -0
- package/dist/qa-agent/types.d.ts.map +1 -0
- package/dist/qa-agent/types.js +4 -0
- package/dist/reporters/junit.d.ts +6 -0
- package/dist/reporters/junit.d.ts.map +1 -0
- package/dist/reporters/junit.js +89 -0
- package/dist/reporters/reporter.d.ts +42 -0
- package/dist/reporters/reporter.d.ts.map +1 -0
- package/dist/reporters/reporter.js +4 -0
- package/dist/reporters/sarif.d.ts +7 -0
- package/dist/reporters/sarif.d.ts.map +1 -0
- package/dist/reporters/sarif.js +135 -0
- package/dist/resilience/circuit_breaker.d.ts +36 -0
- package/dist/resilience/circuit_breaker.d.ts.map +1 -0
- package/dist/resilience/circuit_breaker.js +82 -0
- package/dist/resilience/retry.d.ts +11 -0
- package/dist/resilience/retry.d.ts.map +1 -0
- package/dist/resilience/retry.js +59 -0
- package/dist/sanitize.d.ts +15 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/sanitize.js +71 -0
- package/dist/training/enricher.d.ts +17 -0
- package/dist/training/enricher.d.ts.map +1 -0
- package/dist/training/enricher.js +350 -0
- package/dist/training/kg_scanner.d.ts +13 -0
- package/dist/training/kg_scanner.d.ts.map +1 -0
- package/dist/training/kg_scanner.js +118 -0
- package/dist/training/merger.d.ts +15 -0
- package/dist/training/merger.d.ts.map +1 -0
- package/dist/training/merger.js +208 -0
- package/dist/training/scanner.d.ts +36 -0
- package/dist/training/scanner.d.ts.map +1 -0
- package/dist/training/scanner.js +932 -0
- package/dist/training/types.d.ts +117 -0
- package/dist/training/types.d.ts.map +1 -0
- package/dist/training/types.js +9 -0
- package/dist/training/validator.d.ts +21 -0
- package/dist/training/validator.d.ts.map +1 -0
- package/dist/training/validator.js +262 -0
- package/dist/validation/guardrails.d.ts +31 -0
- package/dist/validation/guardrails.d.ts.map +1 -0
- package/dist/validation/guardrails.js +112 -0
- package/dist/validation/output_schema.d.ts +67 -0
- package/dist/validation/output_schema.d.ts.map +1 -0
- package/dist/validation/output_schema.js +84 -0
- package/dist/version.d.ts +6 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +36 -0
- package/package.json +126 -0
- package/schemas/flow-decision.schema.json +83 -0
- package/schemas/gap.schema.json +18 -0
- package/schemas/impact.schema.json +455 -0
- package/schemas/plan.schema.json +491 -0
- package/schemas/route-families.schema.json +137 -0
- package/schemas/subsystem-risk-map.schema.json +62 -0
- package/schemas/traceability-input.schema.json +122 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
2
|
+
// See LICENSE.txt for license information.
|
|
3
|
+
import { lstatSync, readdirSync, readFileSync } from 'fs';
|
|
4
|
+
import { join, relative, resolve } from 'path';
|
|
5
|
+
import { logger } from '../logger.js';
|
|
6
|
+
import { isGuessedRoute } from './types.js';
|
|
7
|
+
const MAX_FILES_PER_FAMILY = 20;
|
|
8
|
+
const MAX_LINES_PER_FILE = 50;
|
|
9
|
+
const LLM_TIMEOUT_MS = 60000;
|
|
10
|
+
const MAX_PROMPT_CHARS = 100000;
|
|
11
|
+
const SENSITIVE_PATTERNS = [
|
|
12
|
+
/[._]env/, /secret/i, /credential/i, /\.pem$/, /\.key$/, /password/i,
|
|
13
|
+
/config\/secrets/, /fixtures\/.*auth/i, /\.npmrc/, /\.netrc/,
|
|
14
|
+
/id_rsa/, /id_ed25519/, /\.p12$/, /\.pfx$/, /tokens?\.json/i,
|
|
15
|
+
];
|
|
16
|
+
const SKIP_DIRS = new Set([
|
|
17
|
+
'node_modules', '.git', 'dist', 'build', '.next', '.nuxt', 'coverage',
|
|
18
|
+
]);
|
|
19
|
+
function sampleFiles(dir, maxFiles) {
|
|
20
|
+
const files = [];
|
|
21
|
+
function walk(d, depth = 0, maxDepth = 10) {
|
|
22
|
+
if (files.length >= maxFiles)
|
|
23
|
+
return;
|
|
24
|
+
if (depth > maxDepth)
|
|
25
|
+
return;
|
|
26
|
+
try {
|
|
27
|
+
for (const entry of readdirSync(d)) {
|
|
28
|
+
if (files.length >= maxFiles)
|
|
29
|
+
return;
|
|
30
|
+
// Skip dot-dirs and known heavy directories
|
|
31
|
+
if (entry.startsWith('.') || SKIP_DIRS.has(entry))
|
|
32
|
+
continue;
|
|
33
|
+
const full = join(d, entry);
|
|
34
|
+
try {
|
|
35
|
+
// Skip symlinks
|
|
36
|
+
const lstat = lstatSync(full);
|
|
37
|
+
if (lstat.isSymbolicLink())
|
|
38
|
+
continue;
|
|
39
|
+
// Skip sensitive files (test against relative path from scan root)
|
|
40
|
+
const relPath = relative(dir, full);
|
|
41
|
+
if (SENSITIVE_PATTERNS.some((p) => p.test(relPath) || p.test(entry)))
|
|
42
|
+
continue;
|
|
43
|
+
if (lstat.isDirectory()) {
|
|
44
|
+
walk(full, depth + 1, maxDepth);
|
|
45
|
+
}
|
|
46
|
+
else if (lstat.isFile() && lstat.size < 50000) {
|
|
47
|
+
const ext = entry.slice(entry.lastIndexOf('.'));
|
|
48
|
+
if (['.ts', '.tsx', '.js', '.jsx', '.go', '.py', '.rs'].includes(ext)) {
|
|
49
|
+
const content = readFileSync(full, 'utf-8');
|
|
50
|
+
const lines = content.split('\n').slice(0, MAX_LINES_PER_FILE).join('\n');
|
|
51
|
+
files.push({ path: full, content: lines });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch { /* skip */ }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch { /* skip */ }
|
|
59
|
+
}
|
|
60
|
+
walk(dir);
|
|
61
|
+
return files;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Build a shallow directory listing of the source tree (depth 2-3) so the LLM
|
|
65
|
+
* can suggest accurate webappPaths / serverPaths for test-derived families.
|
|
66
|
+
*/
|
|
67
|
+
function getSourceTreeListing(projectRoot, maxDepth = 3) {
|
|
68
|
+
const lines = [];
|
|
69
|
+
function walk(dir, depth, prefix) {
|
|
70
|
+
if (depth > maxDepth || lines.length > 200)
|
|
71
|
+
return;
|
|
72
|
+
let entries;
|
|
73
|
+
try {
|
|
74
|
+
entries = readdirSync(dir).sort();
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const dirs = entries.filter((e) => {
|
|
80
|
+
if (e.startsWith('.') || SKIP_DIRS.has(e))
|
|
81
|
+
return false;
|
|
82
|
+
try {
|
|
83
|
+
const stat = lstatSync(join(dir, e));
|
|
84
|
+
return !stat.isSymbolicLink() && stat.isDirectory();
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
for (const d of dirs) {
|
|
91
|
+
lines.push(`${prefix}${d}/`);
|
|
92
|
+
walk(join(dir, d), depth + 1, prefix + ' ');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
walk(resolve(projectRoot), 0, '');
|
|
96
|
+
return lines.join('\n');
|
|
97
|
+
}
|
|
98
|
+
function buildEnrichPrompt(families, projectRoot, testsRoot) {
|
|
99
|
+
const sections = [];
|
|
100
|
+
const hasTestOnlyFamilies = families.some((f) => f.webappPaths.length === 0 && f.serverPaths.length === 0);
|
|
101
|
+
const resolvedTestsRoot = testsRoot ? resolve(testsRoot) : resolve(projectRoot);
|
|
102
|
+
for (const family of families) {
|
|
103
|
+
const isTestOnly = family.webappPaths.length === 0 && family.serverPaths.length === 0;
|
|
104
|
+
const allDirs = [
|
|
105
|
+
...family.webappPaths.map((p) => p.replace(/\/?\*.*$/, '')),
|
|
106
|
+
...family.serverPaths.map((p) => p.replace(/\/?\*.*$/, '')),
|
|
107
|
+
];
|
|
108
|
+
const samples = [];
|
|
109
|
+
for (const dir of allDirs) {
|
|
110
|
+
if (!dir)
|
|
111
|
+
continue;
|
|
112
|
+
const fullDir = join(resolve(projectRoot), dir);
|
|
113
|
+
samples.push(...sampleFiles(fullDir, MAX_FILES_PER_FAMILY - samples.length));
|
|
114
|
+
if (samples.length >= MAX_FILES_PER_FAMILY)
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
// For test-only families, sample the test files themselves for richer context
|
|
118
|
+
if (isTestOnly) {
|
|
119
|
+
for (const specDir of family.specDirs) {
|
|
120
|
+
if (samples.length >= MAX_FILES_PER_FAMILY)
|
|
121
|
+
break;
|
|
122
|
+
const fullDir = join(resolvedTestsRoot, specDir);
|
|
123
|
+
samples.push(...sampleFiles(fullDir, MAX_FILES_PER_FAMILY - samples.length));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Sample spec descriptions
|
|
127
|
+
const specSamples = [];
|
|
128
|
+
for (const specDir of family.specDirs) {
|
|
129
|
+
const fullDir = join(resolvedTestsRoot, specDir);
|
|
130
|
+
const specFiles = sampleFiles(fullDir, 5);
|
|
131
|
+
for (const sf of specFiles) {
|
|
132
|
+
const matches = sf.content.match(/(?:test|it|describe)\s*\(\s*['"`]([^'"`]+)/g);
|
|
133
|
+
if (matches) {
|
|
134
|
+
specSamples.push(...matches.map((m) => m.replace(/(?:test|it|describe)\s*\(\s*['"`]/, '')));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
sections.push(`## Family: ${family.id}${isTestOnly ? ' [TEST-ONLY — needs webappPaths/serverPaths]' : ''}
|
|
139
|
+
Routes (guessed): ${JSON.stringify(family.routes)}
|
|
140
|
+
Webapp paths: ${JSON.stringify(family.webappPaths)}
|
|
141
|
+
Server paths: ${JSON.stringify(family.serverPaths)}
|
|
142
|
+
Spec dirs: ${JSON.stringify(family.specDirs)}
|
|
143
|
+
Tags: ${JSON.stringify(family.tags)}
|
|
144
|
+
Features: ${family.features.map((f) => f.id).join(', ') || 'none'}
|
|
145
|
+
|
|
146
|
+
Sample files (${samples.length}):
|
|
147
|
+
${samples.map((s) => `### ${relative(projectRoot, s.path)}\n\`\`\`\n${s.content}\n\`\`\``).join('\n')}
|
|
148
|
+
|
|
149
|
+
Test descriptions:
|
|
150
|
+
${specSamples.length > 0 ? specSamples.map((d) => `- ${d}`).join('\n') : '(none found)'}
|
|
151
|
+
`);
|
|
152
|
+
}
|
|
153
|
+
// Include source tree listing when we have test-only families
|
|
154
|
+
const sourceTreeSection = hasTestOnlyFamilies
|
|
155
|
+
? `\n## Source Directory Structure\nUse this to suggest accurate webappPaths and serverPaths for test-only families:\n\`\`\`\n${getSourceTreeListing(projectRoot)}\n\`\`\`\n`
|
|
156
|
+
: '';
|
|
157
|
+
return `You are analyzing a codebase to enrich route-family definitions for an E2E test impact analysis tool.
|
|
158
|
+
|
|
159
|
+
For each family below, provide:
|
|
160
|
+
1. **priority**: P0 (critical user flow), P1 (important), or P2 (nice-to-have)
|
|
161
|
+
2. **userFlows**: Array of human-readable flow names (e.g., "Create channel", "Search messages")
|
|
162
|
+
3. **routes**: Improved URL patterns (e.g., "/{team}/channels/{channel}" instead of "/channels")
|
|
163
|
+
4. **pageObjects**: Array of page object class names found in the code
|
|
164
|
+
5. **components**: Array of UI component names relevant to this family
|
|
165
|
+
6. **webappPaths**: Array of glob patterns for frontend source directories (e.g., "src/components/drafts/**"). REQUIRED for families marked [TEST-ONLY].
|
|
166
|
+
7. **serverPaths**: Array of glob patterns for backend source directories. REQUIRED for families marked [TEST-ONLY].
|
|
167
|
+
|
|
168
|
+
Respond in JSON format:
|
|
169
|
+
\`\`\`json
|
|
170
|
+
[
|
|
171
|
+
{
|
|
172
|
+
"id": "family_id",
|
|
173
|
+
"priority": "P0",
|
|
174
|
+
"userFlows": ["Flow name 1", "Flow name 2"],
|
|
175
|
+
"routes": ["/improved/route/{param}"],
|
|
176
|
+
"pageObjects": ["PageName"],
|
|
177
|
+
"components": ["ComponentName"],
|
|
178
|
+
"webappPaths": ["src/components/feature_name/**"],
|
|
179
|
+
"serverPaths": ["server/channels/api4/feature.go"]
|
|
180
|
+
}
|
|
181
|
+
]
|
|
182
|
+
\`\`\`
|
|
183
|
+
${sourceTreeSection}
|
|
184
|
+
${sections.join('\n---\n')}`;
|
|
185
|
+
}
|
|
186
|
+
export function validateEntries(parsed) {
|
|
187
|
+
const filterStrings = (arr, maxLen) => {
|
|
188
|
+
if (!Array.isArray(arr))
|
|
189
|
+
return undefined;
|
|
190
|
+
const filtered = arr.filter((v) => typeof v === 'string' && v.length < maxLen);
|
|
191
|
+
return filtered.length > 0 ? filtered : undefined;
|
|
192
|
+
};
|
|
193
|
+
return parsed
|
|
194
|
+
.filter((e) => !!e && typeof e.id === 'string')
|
|
195
|
+
.map((entry) => ({
|
|
196
|
+
id: entry.id,
|
|
197
|
+
priority: ['P0', 'P1', 'P2'].includes(entry.priority) ? entry.priority : undefined,
|
|
198
|
+
routes: filterStrings(entry.routes, 200),
|
|
199
|
+
userFlows: filterStrings(entry.userFlows, 500),
|
|
200
|
+
pageObjects: filterStrings(entry.pageObjects, 200),
|
|
201
|
+
components: filterStrings(entry.components, 200),
|
|
202
|
+
webappPaths: filterStrings(entry.webappPaths, 300),
|
|
203
|
+
serverPaths: filterStrings(entry.serverPaths, 300),
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
export function parseEnrichResponse(response) {
|
|
207
|
+
// Extract JSON from response (may be wrapped in markdown code block)
|
|
208
|
+
const jsonMatch = response.match(/```(?:json)?\s*([\s\S]*?)```/) || [null, response];
|
|
209
|
+
const jsonStr = jsonMatch[1]?.trim() || response.trim();
|
|
210
|
+
try {
|
|
211
|
+
const parsed = JSON.parse(jsonStr);
|
|
212
|
+
if (Array.isArray(parsed)) {
|
|
213
|
+
return validateEntries(parsed);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// Try to find any JSON array in the response
|
|
218
|
+
const arrayMatch = response.match(/\[[\s\S]*\]/);
|
|
219
|
+
if (arrayMatch) {
|
|
220
|
+
try {
|
|
221
|
+
const parsed = JSON.parse(arrayMatch[0]);
|
|
222
|
+
if (Array.isArray(parsed)) {
|
|
223
|
+
return validateEntries(parsed);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
// give up
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
function applyEnrichment(family, enriched) {
|
|
234
|
+
const result = { ...family };
|
|
235
|
+
if (enriched.priority && !family.priority) {
|
|
236
|
+
result.priority = enriched.priority;
|
|
237
|
+
}
|
|
238
|
+
if (enriched.userFlows && (!family.userFlows || family.userFlows.length === 0)) {
|
|
239
|
+
result.userFlows = enriched.userFlows;
|
|
240
|
+
}
|
|
241
|
+
if (enriched.routes && enriched.routes.length > 0) {
|
|
242
|
+
// Only replace if current routes look like guesses
|
|
243
|
+
if (isGuessedRoute(family.routes)) {
|
|
244
|
+
result.routes = enriched.routes;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (enriched.pageObjects && (!family.pageObjects || family.pageObjects.length === 0)) {
|
|
248
|
+
result.pageObjects = enriched.pageObjects;
|
|
249
|
+
}
|
|
250
|
+
if (enriched.components && (!family.components || family.components.length === 0)) {
|
|
251
|
+
result.components = enriched.components;
|
|
252
|
+
}
|
|
253
|
+
// Only fill source paths when the family has none (test-derived families)
|
|
254
|
+
if (enriched.webappPaths && (!family.webappPaths || family.webappPaths.length === 0)) {
|
|
255
|
+
result.webappPaths = enriched.webappPaths;
|
|
256
|
+
}
|
|
257
|
+
if (enriched.serverPaths && (!family.serverPaths || family.serverPaths.length === 0)) {
|
|
258
|
+
result.serverPaths = enriched.serverPaths;
|
|
259
|
+
}
|
|
260
|
+
return result;
|
|
261
|
+
}
|
|
262
|
+
export async function enrichFamilies(families, scanned, projectRoot, provider, budgetUSD, testsRoot) {
|
|
263
|
+
const scannedMap = new Map(scanned.map((s) => [s.id, s]));
|
|
264
|
+
const enriched = [];
|
|
265
|
+
let totalTokens = 0;
|
|
266
|
+
let totalCost = 0;
|
|
267
|
+
let requestCount = 0;
|
|
268
|
+
let totalResponseMs = 0;
|
|
269
|
+
const skipped = [];
|
|
270
|
+
// Process in chunks of 4 families
|
|
271
|
+
const chunkSize = 4;
|
|
272
|
+
for (let i = 0; i < families.length; i += chunkSize) {
|
|
273
|
+
if (totalCost >= budgetUSD) {
|
|
274
|
+
for (let j = i; j < families.length; j++) {
|
|
275
|
+
skipped.push(families[j].id);
|
|
276
|
+
enriched.push(families[j]);
|
|
277
|
+
}
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
const chunk = families.slice(i, i + chunkSize);
|
|
281
|
+
const scannedChunk = chunk
|
|
282
|
+
.map((f) => scannedMap.get(f.id))
|
|
283
|
+
.filter((s) => s !== undefined);
|
|
284
|
+
if (scannedChunk.length === 0) {
|
|
285
|
+
enriched.push(...chunk);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
let prompt = buildEnrichPrompt(scannedChunk, projectRoot, testsRoot);
|
|
289
|
+
if (prompt.length > MAX_PROMPT_CHARS) {
|
|
290
|
+
// Truncate at the last complete section boundary to avoid malformed input
|
|
291
|
+
const lastSectionEnd = prompt.lastIndexOf('\n---\n', MAX_PROMPT_CHARS);
|
|
292
|
+
if (lastSectionEnd > 0) {
|
|
293
|
+
logger.warn(`[train] Prompt truncated from ${prompt.length} chars at section boundary`);
|
|
294
|
+
prompt = prompt.slice(0, lastSectionEnd);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
logger.warn(`[train] Prompt truncated from ${prompt.length} to ${MAX_PROMPT_CHARS} chars`);
|
|
298
|
+
prompt = prompt.slice(0, MAX_PROMPT_CHARS);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
let timeoutTimer;
|
|
302
|
+
try {
|
|
303
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
304
|
+
timeoutTimer = setTimeout(() => reject(new Error('LLM request timed out')), LLM_TIMEOUT_MS);
|
|
305
|
+
});
|
|
306
|
+
const reqStart = performance.now();
|
|
307
|
+
const response = await Promise.race([
|
|
308
|
+
provider.generateText(prompt, { maxTokens: 4096, temperature: 0.3 }),
|
|
309
|
+
timeoutPromise,
|
|
310
|
+
]);
|
|
311
|
+
totalResponseMs += performance.now() - reqStart;
|
|
312
|
+
requestCount++;
|
|
313
|
+
totalTokens += (response.usage?.inputTokens ?? 0) + (response.usage?.outputTokens ?? 0);
|
|
314
|
+
totalCost += response.cost ?? 0;
|
|
315
|
+
const entries = parseEnrichResponse(response.text);
|
|
316
|
+
const entryMap = new Map(entries.map((e) => [e.id, e]));
|
|
317
|
+
for (const family of chunk) {
|
|
318
|
+
const entry = entryMap.get(family.id);
|
|
319
|
+
if (entry) {
|
|
320
|
+
enriched.push(applyEnrichment(family, entry));
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
enriched.push(family);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
// On LLM failure, keep families unchanged
|
|
329
|
+
logger.warn(`[train] LLM enrichment failed for chunk: ${error instanceof Error ? error.message : String(error)}`);
|
|
330
|
+
enriched.push(...chunk);
|
|
331
|
+
}
|
|
332
|
+
finally {
|
|
333
|
+
if (timeoutTimer)
|
|
334
|
+
clearTimeout(timeoutTimer);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
enrichedFamilies: enriched,
|
|
339
|
+
tokensUsed: totalTokens,
|
|
340
|
+
costUSD: Math.round(totalCost * 100) / 100,
|
|
341
|
+
skippedFamilies: skipped,
|
|
342
|
+
requestCount,
|
|
343
|
+
avgResponseMs: requestCount > 0 ? Math.round(totalResponseMs / requestCount) : 0,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
2
|
+
// See LICENSE.txt for license information.
|
|
3
|
+
import { deriveClusterId, SKIP_DIRS_WITH_TESTS } from '../knowledge/cluster_utils.js';
|
|
4
|
+
/**
|
|
5
|
+
* Converts KG nodes/edges into a ScanResult compatible with the filesystem scanner output.
|
|
6
|
+
* Groups nodes by their containing module/directory to form families.
|
|
7
|
+
*/
|
|
8
|
+
export function scanFromKnowledgeGraph(kg) {
|
|
9
|
+
const clusters = new Map();
|
|
10
|
+
// Group nodes into clusters by directory/module
|
|
11
|
+
for (const node of kg.nodes) {
|
|
12
|
+
if (node.layer === 'infra')
|
|
13
|
+
continue; // skip infrastructure nodes
|
|
14
|
+
const clusterId = deriveClusterId(node, SKIP_DIRS_WITH_TESTS);
|
|
15
|
+
if (!clusterId)
|
|
16
|
+
continue;
|
|
17
|
+
if (!clusters.has(clusterId)) {
|
|
18
|
+
clusters.set(clusterId, []);
|
|
19
|
+
}
|
|
20
|
+
clusters.get(clusterId).push(node);
|
|
21
|
+
}
|
|
22
|
+
let totalSourceFiles = 0;
|
|
23
|
+
let totalTestFiles = 0;
|
|
24
|
+
const families = [];
|
|
25
|
+
for (const [id, nodes] of clusters) {
|
|
26
|
+
const webappPaths = [];
|
|
27
|
+
const serverPaths = [];
|
|
28
|
+
const specDirs = [];
|
|
29
|
+
const tags = [];
|
|
30
|
+
const seenDirs = new Set();
|
|
31
|
+
for (const node of nodes) {
|
|
32
|
+
if (!node.filePath)
|
|
33
|
+
continue;
|
|
34
|
+
const normalized = node.filePath.replace(/\\/g, '/');
|
|
35
|
+
if (node.layer === 'test') {
|
|
36
|
+
totalTestFiles++;
|
|
37
|
+
const dir = normalized.split('/').slice(0, -1).join('/');
|
|
38
|
+
if (dir && !seenDirs.has(dir)) {
|
|
39
|
+
seenDirs.add(dir);
|
|
40
|
+
specDirs.push(dir + '/');
|
|
41
|
+
}
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
totalSourceFiles++;
|
|
45
|
+
const glob = buildGlobFromPath(normalized);
|
|
46
|
+
if (node.layer === 'api' || node.layer === 'service' || node.layer === 'data') {
|
|
47
|
+
serverPaths.push(glob);
|
|
48
|
+
}
|
|
49
|
+
else if (node.layer === 'ui') {
|
|
50
|
+
webappPaths.push(glob);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
// Default assignment based on file path heuristics
|
|
54
|
+
if (isLikelyServerPath(normalized)) {
|
|
55
|
+
serverPaths.push(glob);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
webappPaths.push(glob);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Extract tags from node metadata
|
|
62
|
+
if (node.tags) {
|
|
63
|
+
tags.push(...node.tags);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (webappPaths.length === 0 && serverPaths.length === 0 && specDirs.length === 0) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
families.push({
|
|
70
|
+
id,
|
|
71
|
+
routes: [`/${id}`],
|
|
72
|
+
webappPaths: [...new Set(webappPaths)],
|
|
73
|
+
serverPaths: [...new Set(serverPaths)],
|
|
74
|
+
specDirs: [...new Set(specDirs)],
|
|
75
|
+
cypressSpecDirs: [],
|
|
76
|
+
tags: [...new Set(tags)],
|
|
77
|
+
features: [],
|
|
78
|
+
routesGuessed: true,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
families,
|
|
83
|
+
unmatchedSourceDirs: [],
|
|
84
|
+
unmatchedTestDirs: [],
|
|
85
|
+
stats: {
|
|
86
|
+
totalSourceFiles,
|
|
87
|
+
totalTestFiles,
|
|
88
|
+
familyCount: families.length,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Internal helpers
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// deriveClusterId and deriveClusterIdFromPath imported from cluster_utils.ts
|
|
96
|
+
function buildGlobFromPath(filePath) {
|
|
97
|
+
// Reject paths with traversal or null bytes
|
|
98
|
+
if (filePath.includes('..') || filePath.includes('\0')) {
|
|
99
|
+
return '';
|
|
100
|
+
}
|
|
101
|
+
// Convert a file path to a glob pattern matching the directory
|
|
102
|
+
const dir = filePath.split('/').slice(0, -1).join('/');
|
|
103
|
+
return dir ? `${dir}/*` : `${filePath}*`;
|
|
104
|
+
}
|
|
105
|
+
function isLikelyServerPath(filePath) {
|
|
106
|
+
const lower = filePath.toLowerCase();
|
|
107
|
+
return lower.includes('/server/') ||
|
|
108
|
+
lower.includes('/api/') ||
|
|
109
|
+
lower.includes('/routes/') ||
|
|
110
|
+
lower.includes('/controllers/') ||
|
|
111
|
+
lower.includes('/services/') ||
|
|
112
|
+
lower.includes('/models/') ||
|
|
113
|
+
lower.endsWith('.go') ||
|
|
114
|
+
lower.endsWith('.py');
|
|
115
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
2
|
+
// See LICENSE.txt for license information.
|
|
3
|
+
import { execFileSync } from 'child_process';
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
import { join, resolve } from 'path';
|
|
6
|
+
import { isGuessedRoute } from './types.js';
|
|
7
|
+
function unionArrays(existing, incoming) {
|
|
8
|
+
const set = new Set(existing || []);
|
|
9
|
+
for (const item of incoming) {
|
|
10
|
+
set.add(item);
|
|
11
|
+
}
|
|
12
|
+
return Array.from(set);
|
|
13
|
+
}
|
|
14
|
+
function mergeFamily(existing, scanned) {
|
|
15
|
+
const merged = { ...existing };
|
|
16
|
+
// Structural fields: union arrays
|
|
17
|
+
merged.webappPaths = unionArrays(existing.webappPaths, scanned.webappPaths);
|
|
18
|
+
merged.serverPaths = unionArrays(existing.serverPaths, scanned.serverPaths);
|
|
19
|
+
merged.specDirs = unionArrays(existing.specDirs, scanned.specDirs);
|
|
20
|
+
merged.cypressSpecDirs = unionArrays(existing.cypressSpecDirs, scanned.cypressSpecDirs);
|
|
21
|
+
merged.tags = unionArrays(existing.tags, scanned.tags);
|
|
22
|
+
// Routes: only update if existing looks like a guess
|
|
23
|
+
if (isGuessedRoute(existing.routes) && !isGuessedRoute(scanned.routes)) {
|
|
24
|
+
merged.routes = scanned.routes;
|
|
25
|
+
}
|
|
26
|
+
// Human-curated fields: never overwrite (priority, userFlows, pageObjects, components)
|
|
27
|
+
// Merge features
|
|
28
|
+
if (scanned.features.length > 0) {
|
|
29
|
+
const existingFeatures = existing.features || [];
|
|
30
|
+
const existingIds = new Set(existingFeatures.map((f) => f.id));
|
|
31
|
+
const newFeatures = scanned.features.filter((f) => !existingIds.has(f.id));
|
|
32
|
+
if (newFeatures.length > 0) {
|
|
33
|
+
merged.features = [
|
|
34
|
+
...existingFeatures,
|
|
35
|
+
...newFeatures.map((f) => ({
|
|
36
|
+
id: f.id,
|
|
37
|
+
webappPaths: f.webappPaths,
|
|
38
|
+
serverPaths: f.serverPaths,
|
|
39
|
+
specDirs: f.specDirs,
|
|
40
|
+
})),
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return merged;
|
|
45
|
+
}
|
|
46
|
+
function scannedToRouteFamily(scanned) {
|
|
47
|
+
const family = {
|
|
48
|
+
id: scanned.id,
|
|
49
|
+
routes: scanned.routes,
|
|
50
|
+
};
|
|
51
|
+
if (scanned.webappPaths.length > 0)
|
|
52
|
+
family.webappPaths = scanned.webappPaths;
|
|
53
|
+
if (scanned.serverPaths.length > 0)
|
|
54
|
+
family.serverPaths = scanned.serverPaths;
|
|
55
|
+
if (scanned.specDirs.length > 0)
|
|
56
|
+
family.specDirs = scanned.specDirs;
|
|
57
|
+
if (scanned.cypressSpecDirs.length > 0)
|
|
58
|
+
family.cypressSpecDirs = scanned.cypressSpecDirs;
|
|
59
|
+
if (scanned.tags.length > 0)
|
|
60
|
+
family.tags = scanned.tags;
|
|
61
|
+
if (scanned.features.length > 0) {
|
|
62
|
+
family.features = scanned.features.map((f) => ({
|
|
63
|
+
id: f.id,
|
|
64
|
+
webappPaths: f.webappPaths.length > 0 ? f.webappPaths : undefined,
|
|
65
|
+
serverPaths: f.serverPaths.length > 0 ? f.serverPaths : undefined,
|
|
66
|
+
specDirs: f.specDirs.length > 0 ? f.specDirs : undefined,
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
return family;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Try to find a matching family ID with singular/plural normalization.
|
|
73
|
+
* "team" matches "teams", "emoji" matches "emoji", etc.
|
|
74
|
+
*/
|
|
75
|
+
function findFuzzyMatch(id, idMap) {
|
|
76
|
+
if (idMap.has(id))
|
|
77
|
+
return id;
|
|
78
|
+
// Try adding 's'
|
|
79
|
+
if (!id.endsWith('s') && idMap.has(id + 's'))
|
|
80
|
+
return id + 's';
|
|
81
|
+
// Try removing 's'
|
|
82
|
+
if (id.endsWith('s') && idMap.has(id.slice(0, -1)))
|
|
83
|
+
return id.slice(0, -1);
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
export function mergeFamilies(existing, scanned) {
|
|
87
|
+
const existingFamilies = existing?.families || [];
|
|
88
|
+
const existingMap = new Map(existingFamilies.map((f) => [f.id, f]));
|
|
89
|
+
const scannedMap = new Map(scanned.map((f) => [f.id, f]));
|
|
90
|
+
const newFamilies = [];
|
|
91
|
+
const updatedFamilies = [];
|
|
92
|
+
const mergedFamilies = [];
|
|
93
|
+
// Process existing families — match scanned by exact or fuzzy ID
|
|
94
|
+
for (const ef of existingFamilies) {
|
|
95
|
+
let sf = scannedMap.get(ef.id);
|
|
96
|
+
// Try singular/plural match if exact match failed
|
|
97
|
+
if (!sf) {
|
|
98
|
+
const fuzzyId = findFuzzyMatch(ef.id, scannedMap);
|
|
99
|
+
if (fuzzyId)
|
|
100
|
+
sf = scannedMap.get(fuzzyId);
|
|
101
|
+
}
|
|
102
|
+
if (sf) {
|
|
103
|
+
mergedFamilies.push(mergeFamily(ef, sf));
|
|
104
|
+
updatedFamilies.push(ef.id);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// Keep untouched
|
|
108
|
+
mergedFamilies.push({ ...ef });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Add new families from scanner (if no existing family matched)
|
|
112
|
+
for (const sf of scanned) {
|
|
113
|
+
const matchedExisting = findFuzzyMatch(sf.id, existingMap);
|
|
114
|
+
if (!matchedExisting) {
|
|
115
|
+
mergedFamilies.push(scannedToRouteFamily(sf));
|
|
116
|
+
newFamilies.push(sf.id);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const parts = [];
|
|
120
|
+
if (updatedFamilies.length > 0)
|
|
121
|
+
parts.push(`${updatedFamilies.length} families updated`);
|
|
122
|
+
if (newFamilies.length > 0)
|
|
123
|
+
parts.push(`${newFamilies.length} new families added`);
|
|
124
|
+
if (parts.length === 0)
|
|
125
|
+
parts.push('no changes');
|
|
126
|
+
return {
|
|
127
|
+
manifest: { families: mergedFamilies, source: existing?.source || 'train-scan' },
|
|
128
|
+
newFamilies,
|
|
129
|
+
updatedFamilies,
|
|
130
|
+
staleFamilies: [],
|
|
131
|
+
summary: parts.join(', '),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Detect families whose paths no longer exist on disk.
|
|
136
|
+
*
|
|
137
|
+
* Paths in the manifest may be relative to different roots:
|
|
138
|
+
* - webappPaths / serverPaths are typically relative to the repo root
|
|
139
|
+
* - specDirs may be relative to the tests root
|
|
140
|
+
*
|
|
141
|
+
* We try each pattern against all provided roots (and the git repo root
|
|
142
|
+
* if discoverable) to avoid false positives from path-prefix mismatches.
|
|
143
|
+
*/
|
|
144
|
+
export function detectStaleFamilies(manifest, projectRoot, testsRoot) {
|
|
145
|
+
const roots = new Set([resolve(projectRoot)]);
|
|
146
|
+
if (testsRoot)
|
|
147
|
+
roots.add(resolve(testsRoot));
|
|
148
|
+
// Also try to discover the git repo root — manifest paths may be repo-relative
|
|
149
|
+
try {
|
|
150
|
+
const gitRoot = execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
151
|
+
cwd: projectRoot,
|
|
152
|
+
encoding: 'utf-8',
|
|
153
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
154
|
+
}).trim();
|
|
155
|
+
if (gitRoot)
|
|
156
|
+
roots.add(resolve(gitRoot));
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Not a git repo or git not available — that's fine
|
|
160
|
+
}
|
|
161
|
+
const stale = [];
|
|
162
|
+
for (const family of manifest.families) {
|
|
163
|
+
const allPatterns = [
|
|
164
|
+
...(family.webappPaths || []),
|
|
165
|
+
...(family.serverPaths || []),
|
|
166
|
+
...(family.specDirs || []),
|
|
167
|
+
];
|
|
168
|
+
if (allPatterns.length === 0)
|
|
169
|
+
continue;
|
|
170
|
+
// Check if any pattern resolves to existing files/dirs in any root
|
|
171
|
+
let hasAny = false;
|
|
172
|
+
for (const pattern of allPatterns) {
|
|
173
|
+
// Strip trailing glob (* or **) to get the directory
|
|
174
|
+
const dirPart = pattern.replace(/\/?\*.*$/, '');
|
|
175
|
+
if (!dirPart)
|
|
176
|
+
continue;
|
|
177
|
+
// For file-level patterns like "server/channels/api4/draft*.go",
|
|
178
|
+
// dirPart is "server/channels/api4/draft" — check the parent dir instead
|
|
179
|
+
const isFileGlob = /\.\w+$/.test(pattern);
|
|
180
|
+
const pathsToCheck = [dirPart];
|
|
181
|
+
if (isFileGlob) {
|
|
182
|
+
const parentDir = dirPart.split('/').slice(0, -1).join('/');
|
|
183
|
+
if (parentDir)
|
|
184
|
+
pathsToCheck.push(parentDir);
|
|
185
|
+
}
|
|
186
|
+
for (const checkPath of pathsToCheck) {
|
|
187
|
+
for (const root of roots) {
|
|
188
|
+
if (existsSync(join(root, checkPath))) {
|
|
189
|
+
hasAny = true;
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (hasAny)
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
if (hasAny)
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
if (!hasAny) {
|
|
200
|
+
stale.push(family.id);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return stale;
|
|
204
|
+
}
|