mcp-efficiency-engine 0.1.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/.github/CODEOWNERS +40 -0
- package/.github/GITHUB_MCP_INTEGRATION.md +366 -0
- package/.github/ISSUE_TEMPLATE/bug.md +51 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +45 -0
- package/.github/ISSUE_TEMPLATE/question.md +18 -0
- package/.github/agents/backend.agent.md +18 -0
- package/.github/agents/community-manager.agent.md +18 -0
- package/.github/agents/dba.agent.md +18 -0
- package/.github/agents/frontend-agent.agent.md +18 -0
- package/.github/agents/github-repository-manager.agent.md +338 -0
- package/.github/agents/iot.agent.md +20 -0
- package/.github/agents/legacy.agent.md +18 -0
- package/.github/agents/rag-azure.agent.md +18 -0
- package/.github/agents/rag-local.agent.md +18 -0
- package/.github/agents/snapshot.agent.md +18 -0
- package/.github/agents/ux-ui.agent.md +18 -0
- package/.github/agents/wiki-agent.agent.md +23 -0
- package/.github/copilot-instructions.md +44 -0
- package/.github/instructions/always-on-optimization.instructions.md +54 -0
- package/.github/prompts/auto-route.prompt.md +33 -0
- package/.github/prompts/azure-rag.query.prompt.md +19 -0
- package/.github/prompts/backend.fix-bug.prompt.md +13 -0
- package/.github/prompts/cavecrew.prompt.md +16 -0
- package/.github/prompts/caveman-commit.prompt.md +14 -0
- package/.github/prompts/caveman-compress.prompt.md +15 -0
- package/.github/prompts/caveman-debug.prompt.md +13 -0
- package/.github/prompts/caveman-help.prompt.md +11 -0
- package/.github/prompts/caveman-review.prompt.md +15 -0
- package/.github/prompts/caveman-stats.prompt.md +11 -0
- package/.github/prompts/caveman.prompt.md +11 -0
- package/.github/prompts/community.post.prompt.md +14 -0
- package/.github/prompts/dba.query-review.prompt.md +15 -0
- package/.github/prompts/dev.fix-bug.prompt.md +13 -0
- package/.github/prompts/frontend.code.prompt.md +13 -0
- package/.github/prompts/legacy.impact-analysis.prompt.md +14 -0
- package/.github/prompts/project.kickoff-analysis.prompt.md +51 -0
- package/.github/prompts/rag.knowledge-answer.prompt.md +13 -0
- package/.github/prompts/token-saver-review.prompt.md +14 -0
- package/.github/prompts/ux-ui.review.prompt.md +14 -0
- package/.github/pull_request_template.md +71 -0
- package/.github/skills/ahorro-tokens/SKILL.md +36 -0
- package/.github/skills/azure-rag-enterprise/SKILL.md +24 -0
- package/.github/skills/backend-coding/SKILL.md +24 -0
- package/.github/skills/cavecrew/SKILL.md +17 -0
- package/.github/skills/caveman/SKILL.md +25 -0
- package/.github/skills/caveman-commit/SKILL.md +15 -0
- package/.github/skills/caveman-compress/SKILL.md +14 -0
- package/.github/skills/caveman-help/SKILL.md +19 -0
- package/.github/skills/caveman-mode/SKILL.md +43 -0
- package/.github/skills/caveman-review/SKILL.md +16 -0
- package/.github/skills/caveman-stats/SKILL.md +13 -0
- package/.github/skills/community-content/SKILL.md +22 -0
- package/.github/skills/database-analysis/SKILL.md +23 -0
- package/.github/skills/dev-coding/SKILL.md +24 -0
- package/.github/skills/frontend-coding/SKILL.md +24 -0
- package/.github/skills/human-in-the-loop/SKILL.md +47 -0
- package/.github/skills/iot-architecture/SKILL.md +22 -0
- package/.github/skills/legacy-migration/SKILL.md +22 -0
- package/.github/skills/rag-knowledge/SKILL.md +21 -0
- package/.github/skills/token-saver/SKILL.md +36 -0
- package/.github/skills/ux-ui-governance/SKILL.md +24 -0
- package/.github/skills/wiki-tools.json +24 -0
- package/.github/workflows/auto-devlog.yml +56 -0
- package/.github/workflows/auto-manage-issues.yml +64 -0
- package/.github/workflows/auto-sync.yml +54 -0
- package/.github/workflows/autodocs-sync.yml +114 -0
- package/.github/workflows/ci.yml +68 -0
- package/.github/workflows/pages.yml +59 -0
- package/.github/workflows/routing-evals.yml +59 -0
- package/.github/workflows/techriders-coverage-effective.yml +91 -0
- package/.vscode/mcp.json +44 -0
- package/AGENTS.md +160 -0
- package/ARCHITECTURE.md +57 -0
- package/FINAL_USAGE_GUIDE.md +106 -0
- package/LICENSE +21 -0
- package/README.md +385 -0
- package/README_WIKI.md +130 -0
- package/autodocs/README.md +22 -0
- package/autodocs/schema/wiki-node.schema.json +56 -0
- package/autolearning/feedback-loop.md +90 -0
- package/bin/install-host.js +326 -0
- package/bin/mcpee.js +116 -0
- package/context/repomix/repomix.config.json +20 -0
- package/memory/cross-memory-reasoning.md +17 -0
- package/memory/mempry-registry.yml +16 -0
- package/observability/evals/chat-token-usage-report.json +64 -0
- package/observability/evals/chat-token-usage-report.md +33 -0
- package/observability/evals/iteration-value-report.json +76 -0
- package/observability/evals/iteration-value-report.md +39 -0
- package/observability/evals/learning-loop-report.json +268 -0
- package/observability/evals/learning-loop-report.md +37 -0
- package/observability/evals/repo-intake-eval-cases.md +23 -0
- package/observability/evals/routing-eval-cases.json +68 -0
- package/observability/evals/routing-eval-cases.md +25 -0
- package/observability/evals/routing-eval-report.json +72 -0
- package/observability/evaluation.md +34 -0
- package/observability/logs/copilot-usage-ingest-report.json +13 -0
- package/observability/logs/iteration-metrics.jsonl +75 -0
- package/observability/logs/learning-feedback.jsonl +152 -0
- package/observability/logs/routing-decisions.jsonl +333 -0
- package/observability/logs/session/agent-pipeline-preflight-20260701-004146.log +8 -0
- package/observability/logs/session/agent-pipeline-preflight-20260701-005554.log +8 -0
- package/observability/logs/session/agent-pipeline-preflight-20260701-010351.log +8 -0
- package/observability/logs/session/agent-pipeline-preflight-20260704-145829.log +8 -0
- package/observability/logs/session/agent-pipeline-preflight-20260704-150021.log +8 -0
- package/observability/logs/session/agent-pipeline-preflight-20260704-154328.log +8 -0
- package/observability/logs/session/bye-20260629-221513.json +39 -0
- package/observability/logs/session/bye-20260629-221545.json +59 -0
- package/observability/logs/session/bye-20260629-222007.json +52 -0
- package/observability/logs/session/bye-20260629-222133.json +59 -0
- package/observability/logs/session/bye-20260629-222412.json +45 -0
- package/observability/logs/session/bye-20260629-223224.json +52 -0
- package/observability/logs/session/bye-20260629-224050.json +59 -0
- package/observability/logs/session/bye-20260630-105732.json +24 -0
- package/observability/logs/session/bye-20260630-211536.json +87 -0
- package/observability/logs/session/bye-20260630-223423.json +87 -0
- package/observability/logs/session/bye-20260630-232534.json +87 -0
- package/observability/logs/session/bye-20260701-003638.json +126 -0
- package/observability/logs/session/bye-20260701-003849.json +126 -0
- package/observability/logs/session/bye-20260701-004315.json +138 -0
- package/observability/logs/session/bye-20260701-004559.json +76 -0
- package/observability/logs/session/bye-20260701-005913.json +138 -0
- package/observability/logs/session/bye-20260701-010753.json +138 -0
- package/observability/logs/session/bye-20260703-194106.json +24 -0
- package/observability/logs/session/bye-20260703-233443.json +76 -0
- package/observability/logs/session/bye-20260703-233653.json +76 -0
- package/observability/logs/session/bye-20260704-150317.json +152 -0
- package/observability/logs/session/bye-20260704-150929.json +75 -0
- package/observability/logs/session/bye-20260704-151050.json +75 -0
- package/observability/logs/session/bye-20260705-115458.json +165 -0
- package/observability/logs/session/codegraph-status-20260701-004228.log +39 -0
- package/observability/logs/session/codegraph-status-20260701-004310.log +39 -0
- package/observability/logs/session/codegraph-status-20260701-005637.log +39 -0
- package/observability/logs/session/codegraph-status-20260701-005908.log +39 -0
- package/observability/logs/session/codegraph-status-20260701-010444.log +39 -0
- package/observability/logs/session/codegraph-status-20260701-010747.log +39 -0
- package/observability/logs/session/codegraph-status-20260704-150137.log +26 -0
- package/observability/logs/session/codegraph-status-20260704-150313.log +28 -0
- package/observability/logs/session/codegraph-status-20260704-154445.log +26 -0
- package/observability/logs/session/codegraph-status-20260705-115454.log +27 -0
- package/observability/logs/session/codegraph-sync-20260701-004211.log +88 -0
- package/observability/logs/session/codegraph-sync-20260701-004241.log +82 -0
- package/observability/logs/session/codegraph-sync-20260701-005620.log +89 -0
- package/observability/logs/session/codegraph-sync-20260701-005840.log +88 -0
- package/observability/logs/session/codegraph-sync-20260701-010419.log +93 -0
- package/observability/logs/session/codegraph-sync-20260701-010609.log +90 -0
- package/observability/logs/session/codegraph-sync-20260704-145856.log +70 -0
- package/observability/logs/session/codegraph-sync-20260704-150047.log +53 -0
- package/observability/logs/session/codegraph-sync-20260704-150204.log +44 -0
- package/observability/logs/session/codegraph-sync-20260704-154353.log +26 -0
- package/observability/logs/session/codegraph-sync-20260705-115337.log +32 -0
- package/observability/logs/session/hi-20260629-221459.json +38 -0
- package/observability/logs/session/hi-20260629-221945.json +59 -0
- package/observability/logs/session/hi-20260629-222108.json +66 -0
- package/observability/logs/session/hi-20260629-222352.json +45 -0
- package/observability/logs/session/hi-20260629-225719.json +59 -0
- package/observability/logs/session/hi-20260629-233722.json +52 -0
- package/observability/logs/session/hi-20260629-233913.json +198 -0
- package/observability/logs/session/hi-20260630-110156.json +177 -0
- package/observability/logs/session/hi-20260630-191243.json +184 -0
- package/observability/logs/session/hi-20260630-230050.json +234 -0
- package/observability/logs/session/hi-20260630-230523.json +227 -0
- package/observability/logs/session/hi-20260630-230855.json +227 -0
- package/observability/logs/session/hi-20260630-231638.json +227 -0
- package/observability/logs/session/hi-20260630-231756.json +219 -0
- package/observability/logs/session/hi-20260701-002842.json +234 -0
- package/observability/logs/session/hi-20260701-003458.json +282 -0
- package/observability/logs/session/hi-20260701-004233.json +290 -0
- package/observability/logs/session/hi-20260701-004554.json +236 -0
- package/observability/logs/session/hi-20260701-005641.json +290 -0
- package/observability/logs/session/hi-20260701-010450.json +290 -0
- package/observability/logs/session/hi-20260703-233854.json +312 -0
- package/observability/logs/session/hi-20260703-234448.json +318 -0
- package/observability/logs/session/hi-20260704-145907.json +353 -0
- package/observability/logs/session/hi-20260704-150141.json +392 -0
- package/observability/logs/session/hi-20260704-154448.json +392 -0
- package/observability/logs/session/iteration-value-refresh-20260701-004309.log +3 -0
- package/observability/logs/session/iteration-value-refresh-20260701-005907.log +3 -0
- package/observability/logs/session/iteration-value-refresh-20260701-010747.log +3 -0
- package/observability/logs/session/iteration-value-refresh-20260704-150312.log +3 -0
- package/observability/logs/session/iteration-value-refresh-20260705-115453.log +3 -0
- package/observability/logs/session/learning-loop-refresh-20260701-004309.log +3 -0
- package/observability/logs/session/learning-loop-refresh-20260701-005906.log +3 -0
- package/observability/logs/session/learning-loop-refresh-20260701-010745.log +3 -0
- package/observability/logs/session/learning-loop-refresh-20260704-150309.log +3 -0
- package/observability/logs/session/learning-loop-refresh-20260705-115450.log +3 -0
- package/observability/logs/session/project-notes-refresh-20260701-004227.log +1 -0
- package/observability/logs/session/project-notes-refresh-20260701-004308.log +1 -0
- package/observability/logs/session/project-notes-refresh-20260701-005636.log +1 -0
- package/observability/logs/session/project-notes-refresh-20260701-005906.log +1 -0
- package/observability/logs/session/project-notes-refresh-20260701-010443.log +1 -0
- package/observability/logs/session/project-notes-refresh-20260701-010744.log +1 -0
- package/observability/logs/session/project-notes-refresh-20260704-150133.log +1 -0
- package/observability/logs/session/project-notes-refresh-20260704-150309.log +1 -0
- package/observability/logs/session/project-notes-refresh-20260704-154442.log +1 -0
- package/observability/logs/session/project-notes-refresh-20260705-115449.log +1 -0
- package/observability/logs/session/repo-intake-20260701-004218.log +6 -0
- package/observability/logs/session/repo-intake-20260701-005626.log +6 -0
- package/observability/logs/session/repo-intake-20260701-010433.log +6 -0
- package/observability/logs/session/repo-intake-20260704-150053.log +6 -0
- package/observability/logs/session/repo-intake-20260704-154404.log +6 -0
- package/observability/logs/session/repo-intake-refresh-20260701-004247.log +6 -0
- package/observability/logs/session/repo-intake-refresh-20260701-005847.log +6 -0
- package/observability/logs/session/repo-intake-refresh-20260701-010615.log +6 -0
- package/observability/logs/session/repo-intake-refresh-20260704-150209.log +6 -0
- package/observability/logs/session/repo-intake-refresh-20260705-115342.log +6 -0
- package/observability/logs/session/repo-registry-validation-20260701-004234.log +2 -0
- package/observability/logs/session/repo-registry-validation-20260701-004556.log +2 -0
- package/observability/logs/session/repo-registry-validation-20260701-005833.log +2 -0
- package/observability/logs/session/repo-registry-validation-20260701-010601.log +2 -0
- package/observability/logs/session/repo-registry-validation-20260704-150155.log +2 -0
- package/observability/logs/session/repo-registry-validation-20260705-115323.log +2 -0
- package/observability/logs/session/repomix-refresh-20260701-003823.log +4236 -0
- package/observability/logs/session/repomix-refresh-20260701-004251.log +4275 -0
- package/observability/logs/session/repomix-refresh-20260701-005850.log +4248 -0
- package/observability/logs/session/repomix-refresh-20260701-010619.log +6531 -0
- package/observability/logs/session/repomix-refresh-20260704-150239.log +652 -0
- package/observability/logs/session/repomix-refresh-20260705-115419.log +178 -0
- package/observability/logs/session/routing-evals-20260701-004221.log +2 -0
- package/observability/logs/session/routing-evals-20260701-004235.log +2 -0
- package/observability/logs/session/routing-evals-20260701-005629.log +2 -0
- package/observability/logs/session/routing-evals-20260701-005834.log +2 -0
- package/observability/logs/session/routing-evals-20260701-010437.log +2 -0
- package/observability/logs/session/routing-evals-20260701-010603.log +2 -0
- package/observability/logs/session/routing-evals-20260704-150125.log +2 -0
- package/observability/logs/session/routing-evals-20260704-150157.log +2 -0
- package/observability/logs/session/routing-evals-20260704-154434.log +2 -0
- package/observability/logs/session/routing-evals-20260705-115327.log +2 -0
- package/observability/logs/session/sibling-discovery-refresh-20260701-004250.log +3 -0
- package/observability/logs/session/sibling-discovery-refresh-20260701-005850.log +3 -0
- package/observability/logs/session/sibling-discovery-refresh-20260701-010618.log +3 -0
- package/observability/logs/session/sibling-discovery-refresh-20260704-150238.log +3 -0
- package/observability/logs/session/sibling-discovery-refresh-20260705-115418.log +3 -0
- package/observability/logs.schema.json +95 -0
- package/observability/logs.schema.md +26 -0
- package/observability/metrics.md +35 -0
- package/observability/routing-evaluator.md +37 -0
- package/observability/scoring-rubric.md +38 -0
- package/optimization/ALWAYS_ON_OPTIMIZATION.md +98 -0
- package/optimization/caveman-mode.md +62 -0
- package/optimization/examples/login-bug.md +25 -0
- package/optimization/examples/sla-query.md +25 -0
- package/optimization/optimization-routing.md +30 -0
- package/optimization/policies/response-style.policy.md +45 -0
- package/optimization/policies/token-budget.policy.md +28 -0
- package/optimization/profiles/always-on.profile.md +19 -0
- package/optimization/profiles/architecture.profile.md +20 -0
- package/optimization/profiles/debug.profile.md +20 -0
- package/optimization/profiles/enterprise-rag.profile.md +20 -0
- package/optimization/profiles/training.profile.md +20 -0
- package/optimization/token-saver.md +58 -0
- package/orchestrator/corporate-routing.md +51 -0
- package/orchestrator/decision-matrix.md +33 -0
- package/orchestrator/fallback.md +29 -0
- package/orchestrator/repo-routing.md +58 -0
- package/orchestrator/router.md +75 -0
- package/orchestrator/wiki/__init__.py +5 -0
- package/orchestrator/wiki/graph_consolidator.py +506 -0
- package/orchestrator/wiki/incremental_engine.py +57 -0
- package/orchestrator/wiki/plugin_manager.py +40 -0
- package/package.json +70 -0
- package/policies/context-policy.md +25 -0
- package/policies/cost-policy.md +25 -0
- package/policies/repo-intake-policy.md +36 -0
- package/policies/security-policy.md +25 -0
- package/repo-intake/README.md +81 -0
- package/repo-intake/templates/agent.template.md +4 -0
- package/repo-intake/templates/context-manifest.template.md +4 -0
- package/repo-intake/templates/skill.template.md +3 -0
- package/repo-registry/repos.schema.json +102 -0
- package/repo-registry/repos.template.json +26 -0
- package/requirements.txt +1 -0
- package/scripts/README.md +109 -0
- package/scripts/__init__.py +0 -0
- package/scripts/bootstrap-portable.cmd +4 -0
- package/scripts/bootstrap-portable.ps1 +90 -0
- package/scripts/context/azure-rag-mcp-adapter.py +1 -0
- package/scripts/context/build-graphify.ps1 +1 -0
- package/scripts/context/build-repomix.ps1 +1 -0
- package/scripts/discovery/discover-boost-repos.cmd +4 -0
- package/scripts/discovery/discover-boost-repos.py +186 -0
- package/scripts/discovery/refresh-project-notes.py +179 -0
- package/scripts/github/INSTALL.md +295 -0
- package/scripts/github/README.md +455 -0
- package/scripts/github/apply-boost-templates.ps1 +163 -0
- package/scripts/github/apply-single-repo.ps1 +85 -0
- package/scripts/github/create-devlog.ps1 +380 -0
- package/scripts/github/manage-issues.ps1 +332 -0
- package/scripts/github/sync-repo.ps1 +267 -0
- package/scripts/intake/agent-pipeline-preflight.py +151 -0
- package/scripts/intake/init-template-registry.cmd +4 -0
- package/scripts/intake/init-template-registry.ps1 +258 -0
- package/scripts/intake/repo-intake.py +354 -0
- package/scripts/intake/resolve-routing.py +620 -0
- package/scripts/intake/run-repo-intake.cmd +4 -0
- package/scripts/intake/run-repo-intake.ps1 +46 -0
- package/scripts/intake/run-routing-evals.py +147 -0
- package/scripts/intake/validate-database-routing.py +91 -0
- package/scripts/intake/validate-rag-routing.py +85 -0
- package/scripts/intake/validate-repo-registry.ps1 +20 -0
- package/scripts/intake/validate-repo-registry.py +286 -0
- package/scripts/intake/validate-security.py +170 -0
- package/scripts/learning/autolearning-gate.py +71 -0
- package/scripts/learning/chat-token-usage-report.py +326 -0
- package/scripts/learning/ingest-copilot-session-usage.py +421 -0
- package/scripts/learning/iteration-value-report.py +275 -0
- package/scripts/learning/learning-loop-report.py +272 -0
- package/scripts/learning/record-iteration-metrics.py +116 -0
- package/scripts/learning/record-learning-feedback.py +91 -0
- package/scripts/ops/bye.ps1 +620 -0
- package/scripts/ops/compare-demo-results.ps1 +826 -0
- package/scripts/ops/demo1.ps1 +11 -0
- package/scripts/ops/demo2.ps1 +11 -0
- package/scripts/ops/demo3.ps1 +11 -0
- package/scripts/ops/demo4.ps1 +11 -0
- package/scripts/ops/hi.ps1 +1045 -0
- package/scripts/ops/llm-token-probe.py +164 -0
- package/scripts/ops/run-all-demos.ps1 +39 -0
- package/scripts/ops/run-demo-block.ps1 +85 -0
- package/scripts/ops/run-demo-session.ps1 +532 -0
- package/scripts/setup/optimize-engines.ps1 +183 -0
- package/scripts/setup/setup-codegraph.ps1 +3 -0
- package/scripts/setup/setup-gitnexus.ps1 +3 -0
- package/scripts/setup/setup-graphify.ps1 +2 -0
- package/scripts/setup/setup-prerequisites.ps1 +205 -0
- package/scripts/setup/start-gitnexus.ps1 +71 -0
- package/scripts/setup/validate-context.ps1 +361 -0
- package/scripts/wiki/__init__.py +0 -0
- package/scripts/wiki/compiler_main.py +189 -0
- package/scripts/wiki/normalizer.py +206 -0
- package/scripts/wiki/providers/base_provider.py +25 -0
- package/scripts/wiki/providers/codegraph_provider.py +49 -0
- package/scripts/wiki/providers/graphify_provider.py +49 -0
- package/scripts/wiki/providers/repo_content_provider.py +408 -0
- package/scripts/wiki/query_autodocs.py +60 -0
- package/scripts/wiki/validator.py +183 -0
- package/scripts/wiki/wiki_compiler.py +5 -0
- package/specs/architecture.spec.md +32 -0
- package/specs/azure-rag.spec.md +28 -0
- package/specs/coding-standards.spec.md +24 -0
- package/specs/database.spec.md +29 -0
- package/specs/migration.spec.md +26 -0
- package/specs/observability.spec.md +24 -0
- package/specs/optimization.spec.md +31 -0
- package/specs/rag.spec.md +30 -0
- package/specs/repo-intake.spec.md +25 -0
- package/specs/routing.spec.md +24 -0
- package/specs/security.spec.md +30 -0
- package/specs/wiki/provider-contract.json +33 -0
- package/specs/wiki/unified-knowledge-graph.json +25 -0
- package/templates/BOOST_README_TEMPLATE.md +96 -0
- package/templates/CODEOWNERS_TEMPLATE.txt +29 -0
- package/templates/LICENSE_MIT_TEMPLATE.txt +21 -0
- package/templates/PERSONAL_README_TEMPLATE.md +90 -0
- package/templates/README_boost_RAG_Azure.md +46 -0
- package/templates/README_boost_azure_iot_edge.md +48 -0
- package/templates/README_boost_backend.md +47 -0
- package/templates/README_boost_dba.md +57 -0
- package/templates/ci.yml.template +73 -0
- package/tooling/tooling.manifest.json +72 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def utc_now() -> str:
|
|
13
|
+
return datetime.now(timezone.utc).isoformat()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def load_cases(path: Path) -> list[dict[str, Any]]:
|
|
17
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
18
|
+
cases = data.get("cases", [])
|
|
19
|
+
if not isinstance(cases, list):
|
|
20
|
+
return []
|
|
21
|
+
return [c for c in cases if isinstance(c, dict)]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def parse_event_ids(path: Path) -> set[str]:
|
|
25
|
+
if not path.exists():
|
|
26
|
+
return set()
|
|
27
|
+
ids: set[str] = set()
|
|
28
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
29
|
+
line = line.strip()
|
|
30
|
+
if not line:
|
|
31
|
+
continue
|
|
32
|
+
try:
|
|
33
|
+
obj = json.loads(line)
|
|
34
|
+
except Exception:
|
|
35
|
+
continue
|
|
36
|
+
if not isinstance(obj, dict):
|
|
37
|
+
continue
|
|
38
|
+
event_id = str(obj.get("event_id", "")).strip()
|
|
39
|
+
if event_id:
|
|
40
|
+
ids.add(event_id)
|
|
41
|
+
return ids
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def run_case(repo_root: Path, case: dict[str, Any], routing_log: Path) -> tuple[bool, str, str | None]:
|
|
45
|
+
before_ids = parse_event_ids(routing_log)
|
|
46
|
+
cmd = [
|
|
47
|
+
sys.executable,
|
|
48
|
+
str((repo_root / "scripts/intake/resolve-routing.py").resolve()),
|
|
49
|
+
"--input",
|
|
50
|
+
str(case.get("input", "")),
|
|
51
|
+
"--intent",
|
|
52
|
+
str(case.get("intent", "")),
|
|
53
|
+
"--domain",
|
|
54
|
+
str(case.get("domain", "")),
|
|
55
|
+
"--source-type",
|
|
56
|
+
str(case.get("source_type", "technical-docs")),
|
|
57
|
+
"--capability",
|
|
58
|
+
str(case.get("capability", "")),
|
|
59
|
+
]
|
|
60
|
+
proc = subprocess.run(cmd, cwd=str(repo_root), capture_output=True, text=True)
|
|
61
|
+
output = (proc.stdout or "") + (proc.stderr or "")
|
|
62
|
+
|
|
63
|
+
event_id: str | None = None
|
|
64
|
+
after_ids = parse_event_ids(routing_log)
|
|
65
|
+
new_ids = after_ids - before_ids
|
|
66
|
+
if new_ids:
|
|
67
|
+
event_id = sorted(new_ids)[-1]
|
|
68
|
+
|
|
69
|
+
return proc.returncode == 0, output, event_id
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def record_feedback(repo_root: Path, event_id: str, case_id: str) -> tuple[bool, str]:
|
|
73
|
+
cmd = [
|
|
74
|
+
sys.executable,
|
|
75
|
+
str((repo_root / "scripts/learning/record-learning-feedback.py").resolve()),
|
|
76
|
+
"--event-id",
|
|
77
|
+
event_id,
|
|
78
|
+
"--success",
|
|
79
|
+
"true",
|
|
80
|
+
"--confidence",
|
|
81
|
+
"0.95",
|
|
82
|
+
"--source",
|
|
83
|
+
"ci",
|
|
84
|
+
"--notes",
|
|
85
|
+
f"routing-eval-pass:{case_id}",
|
|
86
|
+
]
|
|
87
|
+
proc = subprocess.run(cmd, cwd=str(repo_root), capture_output=True, text=True)
|
|
88
|
+
output = (proc.stdout or "") + (proc.stderr or "")
|
|
89
|
+
return proc.returncode == 0, output
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def main() -> int:
|
|
93
|
+
parser = argparse.ArgumentParser(description="Run routing eval cases and write JSON report.")
|
|
94
|
+
parser.add_argument("--cases", default="observability/evals/routing-eval-cases.json")
|
|
95
|
+
parser.add_argument("--report", default="observability/evals/routing-eval-report.json")
|
|
96
|
+
args = parser.parse_args()
|
|
97
|
+
|
|
98
|
+
repo_root = Path(__file__).resolve().parents[2]
|
|
99
|
+
routing_log = (repo_root / "observability/logs/routing-decisions.jsonl").resolve()
|
|
100
|
+
cases_path = (repo_root / args.cases).resolve()
|
|
101
|
+
if not cases_path.exists():
|
|
102
|
+
print(f"Missing eval cases file: {cases_path}")
|
|
103
|
+
return 1
|
|
104
|
+
|
|
105
|
+
cases = load_cases(cases_path)
|
|
106
|
+
results: list[dict[str, Any]] = []
|
|
107
|
+
passed = 0
|
|
108
|
+
|
|
109
|
+
for case in cases:
|
|
110
|
+
ok, output, event_id = run_case(repo_root, case, routing_log)
|
|
111
|
+
feedback_ok = False
|
|
112
|
+
feedback_output = ""
|
|
113
|
+
case_id = str(case.get("id", "unknown"))
|
|
114
|
+
|
|
115
|
+
if ok and event_id:
|
|
116
|
+
feedback_ok, feedback_output = record_feedback(repo_root, event_id, case_id)
|
|
117
|
+
if ok:
|
|
118
|
+
passed += 1
|
|
119
|
+
results.append(
|
|
120
|
+
{
|
|
121
|
+
"id": case_id,
|
|
122
|
+
"ok": ok,
|
|
123
|
+
"event_id": event_id,
|
|
124
|
+
"feedback_recorded": feedback_ok,
|
|
125
|
+
"feedback_output": feedback_output,
|
|
126
|
+
"output": output,
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
report = {
|
|
131
|
+
"timestamp": utc_now(),
|
|
132
|
+
"cases_total": len(cases),
|
|
133
|
+
"cases_passed": passed,
|
|
134
|
+
"cases_failed": len(cases) - passed,
|
|
135
|
+
"results": results,
|
|
136
|
+
}
|
|
137
|
+
report_path = (repo_root / args.report).resolve()
|
|
138
|
+
report_path.parent.mkdir(parents=True, exist_ok=True)
|
|
139
|
+
report_path.write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
140
|
+
|
|
141
|
+
print(f"Routing evals completed: {passed}/{len(cases)} passed")
|
|
142
|
+
print(f"Report: {report_path}")
|
|
143
|
+
return 0 if passed == len(cases) else 1
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if __name__ == "__main__":
|
|
147
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""Validate database routing spec requirements.
|
|
4
|
+
|
|
5
|
+
Assertions:
|
|
6
|
+
1. intent=sql with domain=dba selects agent=dba.
|
|
7
|
+
2. An event with no resolvable source declares grounded=false.
|
|
8
|
+
|
|
9
|
+
Exit 0 = pass. Exit 1 = fail.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
18
|
+
RESOLVE_SCRIPT = REPO_ROOT / "scripts" / "intake" / "resolve-routing.py"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def run_routing(**kwargs: str) -> dict:
|
|
22
|
+
cmd = [sys.executable, str(RESOLVE_SCRIPT)]
|
|
23
|
+
for k, v in kwargs.items():
|
|
24
|
+
cmd += [f"--{k.replace('_', '-')}", v]
|
|
25
|
+
# Use utf-8 encoding and errors='replace' to handle non-ASCII chars
|
|
26
|
+
proc = subprocess.run(
|
|
27
|
+
cmd,
|
|
28
|
+
cwd=str(REPO_ROOT),
|
|
29
|
+
capture_output=True,
|
|
30
|
+
text=True,
|
|
31
|
+
encoding="utf-8",
|
|
32
|
+
errors="replace"
|
|
33
|
+
)
|
|
34
|
+
# Output contains a JSON block followed by a trailing log line; extract JSON.
|
|
35
|
+
stdout = proc.stdout or ""
|
|
36
|
+
brace = stdout.find("{")
|
|
37
|
+
last_brace = stdout.rfind("}")
|
|
38
|
+
if brace == -1 or last_brace == -1:
|
|
39
|
+
raise ValueError(f"No JSON in output: {stdout[:200]}")
|
|
40
|
+
return json.loads(stdout[brace : last_brace + 1])
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def main() -> int:
|
|
44
|
+
errors: list[str] = []
|
|
45
|
+
print("=== validate-database-routing ===")
|
|
46
|
+
|
|
47
|
+
# --- Assertion 1: intent=sql routes to dba ---
|
|
48
|
+
try:
|
|
49
|
+
event = run_routing(
|
|
50
|
+
input="analiza esquema sql",
|
|
51
|
+
intent="sql",
|
|
52
|
+
domain="dba",
|
|
53
|
+
source_type="technical-docs",
|
|
54
|
+
)
|
|
55
|
+
agent = event.get("agent", "")
|
|
56
|
+
if agent != "dba":
|
|
57
|
+
errors.append(f"[FAIL] intent=sql expected agent=dba, got agent={agent!r}")
|
|
58
|
+
else:
|
|
59
|
+
print(f"[OK] intent=sql → agent={agent}")
|
|
60
|
+
except Exception as exc:
|
|
61
|
+
errors.append(f"[FAIL] routing error for intent=sql: {exc}")
|
|
62
|
+
|
|
63
|
+
# --- Assertion 2: no resolvable source → grounded=false ---
|
|
64
|
+
try:
|
|
65
|
+
event = run_routing(
|
|
66
|
+
input="test grounding",
|
|
67
|
+
intent="info",
|
|
68
|
+
domain="unknown-domain",
|
|
69
|
+
source_type="code",
|
|
70
|
+
)
|
|
71
|
+
grounded = event.get("grounded")
|
|
72
|
+
if grounded is not False:
|
|
73
|
+
errors.append(f"[FAIL] no-source event expected grounded=false, got grounded={grounded!r}")
|
|
74
|
+
else:
|
|
75
|
+
print("[OK] no-source event → grounded=false")
|
|
76
|
+
except Exception as exc:
|
|
77
|
+
errors.append(f"[FAIL] routing error for grounded check: {exc}")
|
|
78
|
+
|
|
79
|
+
print()
|
|
80
|
+
if errors:
|
|
81
|
+
for e in errors:
|
|
82
|
+
print(e)
|
|
83
|
+
print(f"validate-database-routing FAILED: {len(errors)} issue(s)")
|
|
84
|
+
return 1
|
|
85
|
+
|
|
86
|
+
print("validate-database-routing PASSED")
|
|
87
|
+
return 0
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""Validate RAG local routing spec requirements.
|
|
4
|
+
|
|
5
|
+
Assertions:
|
|
6
|
+
1. domain=rag with source_type=technical-docs selects agent=rag-local.
|
|
7
|
+
2. The routing event declares grounded=true with explicit sources from the repo.
|
|
8
|
+
|
|
9
|
+
Exit 0 = pass. Exit 1 = fail.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
18
|
+
RESOLVE_SCRIPT = REPO_ROOT / "scripts" / "intake" / "resolve-routing.py"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def run_routing(**kwargs: str) -> dict:
|
|
22
|
+
cmd = [sys.executable, str(RESOLVE_SCRIPT)]
|
|
23
|
+
for k, v in kwargs.items():
|
|
24
|
+
cmd += [f"--{k.replace('_', '-')}", v]
|
|
25
|
+
# Use utf-8 encoding and errors='replace' to handle non-ASCII chars
|
|
26
|
+
proc = subprocess.run(
|
|
27
|
+
cmd,
|
|
28
|
+
cwd=str(REPO_ROOT),
|
|
29
|
+
capture_output=True,
|
|
30
|
+
text=True,
|
|
31
|
+
encoding="utf-8",
|
|
32
|
+
errors="replace"
|
|
33
|
+
)
|
|
34
|
+
stdout = proc.stdout or ""
|
|
35
|
+
brace = stdout.find("{")
|
|
36
|
+
last_brace = stdout.rfind("}")
|
|
37
|
+
if brace == -1 or last_brace == -1:
|
|
38
|
+
raise ValueError(f"No JSON in output: {stdout[:200]}")
|
|
39
|
+
return json.loads(stdout[brace : last_brace + 1])
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def main() -> int:
|
|
43
|
+
errors: list[str] = []
|
|
44
|
+
print("=== validate-rag-routing ===")
|
|
45
|
+
|
|
46
|
+
# --- Assertion 1: rag domain + technical-docs → rag-local ---
|
|
47
|
+
try:
|
|
48
|
+
event = run_routing(
|
|
49
|
+
input="explica guia tecnica local",
|
|
50
|
+
intent="info",
|
|
51
|
+
domain="rag",
|
|
52
|
+
source_type="technical-docs",
|
|
53
|
+
)
|
|
54
|
+
agent = event.get("agent", "")
|
|
55
|
+
if agent != "rag-local":
|
|
56
|
+
errors.append(f"[FAIL] expected agent=rag-local, got agent={agent!r}")
|
|
57
|
+
else:
|
|
58
|
+
print(f"[OK] domain=rag + technical-docs → agent={agent}")
|
|
59
|
+
|
|
60
|
+
# --- Assertion 2: grounded=true with explicit sources ---
|
|
61
|
+
grounded = event.get("grounded")
|
|
62
|
+
sources = event.get("sources", [])
|
|
63
|
+
if not grounded:
|
|
64
|
+
errors.append(f"[FAIL] rag-local event expected grounded=true, got grounded={grounded!r}")
|
|
65
|
+
elif not sources:
|
|
66
|
+
errors.append("[FAIL] rag-local event grounded=true but sources list is empty")
|
|
67
|
+
else:
|
|
68
|
+
print(f"[OK] grounded=true with sources: {sources}")
|
|
69
|
+
|
|
70
|
+
except Exception as exc:
|
|
71
|
+
errors.append(f"[FAIL] routing error: {exc}")
|
|
72
|
+
|
|
73
|
+
print()
|
|
74
|
+
if errors:
|
|
75
|
+
for e in errors:
|
|
76
|
+
print(e)
|
|
77
|
+
print(f"validate-rag-routing FAILED: {len(errors)} issue(s)")
|
|
78
|
+
return 1
|
|
79
|
+
|
|
80
|
+
print("validate-rag-routing PASSED")
|
|
81
|
+
return 0
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[string]$RegistryPath = "repo-registry/repos.yml",
|
|
3
|
+
[switch]$Strict
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
Set-StrictMode -Version Latest
|
|
7
|
+
$ErrorActionPreference = "Stop"
|
|
8
|
+
|
|
9
|
+
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
|
|
10
|
+
Set-Location $repoRoot
|
|
11
|
+
|
|
12
|
+
$args = @(".\scripts\intake\validate-repo-registry.py", "--registry", $RegistryPath)
|
|
13
|
+
if ($Strict) {
|
|
14
|
+
$args += "--strict"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
python @args
|
|
18
|
+
if ($LASTEXITCODE -ne 0) {
|
|
19
|
+
exit $LASTEXITCODE
|
|
20
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import subprocess
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import yaml # type: ignore
|
|
14
|
+
except Exception:
|
|
15
|
+
yaml = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def utc_now() -> str:
|
|
19
|
+
return datetime.now(timezone.utc).isoformat()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def parse_simple_yml(path: Path) -> dict[str, Any]:
|
|
23
|
+
repos: list[dict[str, Any]] = []
|
|
24
|
+
current: dict[str, Any] | None = None
|
|
25
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
26
|
+
st = line.strip()
|
|
27
|
+
if st.startswith("- name:"):
|
|
28
|
+
if current:
|
|
29
|
+
repos.append(current)
|
|
30
|
+
current = {"name": st.split(":", 1)[1].strip()}
|
|
31
|
+
elif current and ":" in st and not st.startswith("#"):
|
|
32
|
+
k, v = st.split(":", 1)
|
|
33
|
+
k = k.strip()
|
|
34
|
+
v = v.strip().strip('"')
|
|
35
|
+
if k in {"domain", "location", "type", "repo_url", "branch", "cache_location"}:
|
|
36
|
+
current[k] = v
|
|
37
|
+
if current:
|
|
38
|
+
repos.append(current)
|
|
39
|
+
return {"schema_version": "1.0", "repos": repos}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def load_registry(path: Path) -> dict[str, Any]:
|
|
43
|
+
content = path.read_text(encoding="utf-8")
|
|
44
|
+
|
|
45
|
+
# JSON-first support (JSON is valid YAML, but this path avoids extra deps).
|
|
46
|
+
if content.lstrip().startswith("{"):
|
|
47
|
+
return json.loads(content)
|
|
48
|
+
|
|
49
|
+
if yaml is not None:
|
|
50
|
+
data = yaml.safe_load(content) or {}
|
|
51
|
+
if isinstance(data, dict):
|
|
52
|
+
return data
|
|
53
|
+
|
|
54
|
+
return parse_simple_yml(path)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class ValidationResult:
|
|
59
|
+
errors: list[str]
|
|
60
|
+
warnings: list[str]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def command_exists(name: str) -> bool:
|
|
64
|
+
try:
|
|
65
|
+
completed = subprocess.run(
|
|
66
|
+
[name, "--version"],
|
|
67
|
+
check=False,
|
|
68
|
+
capture_output=True,
|
|
69
|
+
text=True,
|
|
70
|
+
)
|
|
71
|
+
except OSError:
|
|
72
|
+
return False
|
|
73
|
+
return completed.returncode == 0
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def validate(registry: dict[str, Any], strict: bool, repo_root: Path) -> ValidationResult:
|
|
77
|
+
errors: list[str] = []
|
|
78
|
+
warnings: list[str] = []
|
|
79
|
+
|
|
80
|
+
registry_mode = str(registry.get("registry_mode", "enterprise")).strip().lower() or "enterprise"
|
|
81
|
+
repos = registry.get("repos")
|
|
82
|
+
if not isinstance(repos, list):
|
|
83
|
+
return ValidationResult(errors=["Registry must define a repos list."], warnings=[])
|
|
84
|
+
|
|
85
|
+
if not repos:
|
|
86
|
+
if strict or registry_mode != "template":
|
|
87
|
+
return ValidationResult(errors=["Registry must define a non-empty repos list."], warnings=[])
|
|
88
|
+
warnings.append("Template registry has no repos yet.")
|
|
89
|
+
return ValidationResult(errors=[], warnings=warnings)
|
|
90
|
+
|
|
91
|
+
schema_version = str(registry.get("schema_version", "1.0"))
|
|
92
|
+
is_v2 = schema_version >= "2.0"
|
|
93
|
+
governance = registry.get("governance", {}) if isinstance(registry.get("governance", {}), dict) else {}
|
|
94
|
+
repo_name_prefix = str(governance.get("repo_name_prefix", "boost_")).strip() or "boost_"
|
|
95
|
+
|
|
96
|
+
names: set[str] = set()
|
|
97
|
+
graph: dict[str, list[str]] = {}
|
|
98
|
+
|
|
99
|
+
for repo in repos:
|
|
100
|
+
if not isinstance(repo, dict):
|
|
101
|
+
errors.append("Each repo entry must be an object.")
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
name = str(repo.get("name", "")).strip()
|
|
105
|
+
if not name:
|
|
106
|
+
errors.append("Repo is missing required field: name")
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
if strict or is_v2:
|
|
110
|
+
if not name.startswith(repo_name_prefix):
|
|
111
|
+
errors.append(
|
|
112
|
+
f"Repo '{name}' does not satisfy naming contract. Expected prefix '{repo_name_prefix}'."
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if name in names:
|
|
116
|
+
errors.append(f"Duplicate repo name: {name}")
|
|
117
|
+
continue
|
|
118
|
+
names.add(name)
|
|
119
|
+
|
|
120
|
+
for field in ("domain", "type"):
|
|
121
|
+
if not str(repo.get(field, "")).strip():
|
|
122
|
+
errors.append(f"Repo '{name}' is missing required field: {field}")
|
|
123
|
+
|
|
124
|
+
repo_type = str(repo.get("type", "")).strip().lower()
|
|
125
|
+
if repo_type not in {"local", "github"}:
|
|
126
|
+
errors.append(f"Repo '{name}' type must be 'local' or 'github'.")
|
|
127
|
+
|
|
128
|
+
location = str(repo.get("location", "")).strip()
|
|
129
|
+
if repo_type == "local":
|
|
130
|
+
if not location:
|
|
131
|
+
errors.append(f"Repo '{name}' is missing required field: location")
|
|
132
|
+
elif location:
|
|
133
|
+
resolved = (repo_root / location).resolve()
|
|
134
|
+
if not resolved.exists():
|
|
135
|
+
if bool(repo.get("optional", False)):
|
|
136
|
+
warnings.append(f"Optional repo '{name}' location does not exist: {location}")
|
|
137
|
+
else:
|
|
138
|
+
errors.append(f"Repo '{name}' location does not exist: {location}")
|
|
139
|
+
elif repo_type == "github":
|
|
140
|
+
repo_url = str(repo.get("repo_url", "")).strip()
|
|
141
|
+
if not repo_url:
|
|
142
|
+
errors.append(f"Repo '{name}' is missing required field: repo_url")
|
|
143
|
+
elif not re.match(r"^https://github\.com/[^/]+/[^/]+(?:\.git)?$", repo_url):
|
|
144
|
+
errors.append(f"Repo '{name}' repo_url must be a valid https GitHub repository URL.")
|
|
145
|
+
branch = str(repo.get("branch", "")).strip()
|
|
146
|
+
if branch and not re.match(r"^[A-Za-z0-9._\/-]+$", branch):
|
|
147
|
+
errors.append(f"Repo '{name}' branch contains invalid characters: {branch}")
|
|
148
|
+
cache_location = str(repo.get("cache_location", "")).strip()
|
|
149
|
+
if cache_location:
|
|
150
|
+
cache_resolved = (repo_root / cache_location).resolve()
|
|
151
|
+
cache_parent = cache_resolved.parent
|
|
152
|
+
if not cache_parent.exists():
|
|
153
|
+
warnings.append(f"GitHub repo '{name}' cache parent does not exist yet: {cache_parent}")
|
|
154
|
+
if strict and not command_exists("git"):
|
|
155
|
+
errors.append(f"Repo '{name}' requires git command for type 'github'.")
|
|
156
|
+
|
|
157
|
+
deps = repo.get("dependencies", [])
|
|
158
|
+
dep_refs: list[str] = []
|
|
159
|
+
if deps:
|
|
160
|
+
if not isinstance(deps, list):
|
|
161
|
+
errors.append(f"Repo '{name}' dependencies must be a list.")
|
|
162
|
+
else:
|
|
163
|
+
for dep in deps:
|
|
164
|
+
if not isinstance(dep, dict) or not str(dep.get("ref", "")).strip():
|
|
165
|
+
errors.append(f"Repo '{name}' has a dependency without ref.")
|
|
166
|
+
continue
|
|
167
|
+
dep_refs.append(str(dep["ref"]))
|
|
168
|
+
graph[name] = dep_refs
|
|
169
|
+
|
|
170
|
+
if strict or is_v2:
|
|
171
|
+
approval = repo.get("approval")
|
|
172
|
+
if not isinstance(approval, dict):
|
|
173
|
+
errors.append(f"Repo '{name}' requires approval block in v2/strict mode.")
|
|
174
|
+
else:
|
|
175
|
+
if approval.get("status") != "approved":
|
|
176
|
+
errors.append(f"Repo '{name}' is not approved (approval.status must be 'approved').")
|
|
177
|
+
if not str(approval.get("approved_by", "")).strip():
|
|
178
|
+
errors.append(f"Repo '{name}' missing approval.approved_by")
|
|
179
|
+
if not str(approval.get("approved_date", "")).strip():
|
|
180
|
+
errors.append(f"Repo '{name}' missing approval.approved_date")
|
|
181
|
+
if not str(approval.get("review_ticket", "")).strip():
|
|
182
|
+
warnings.append(f"Repo '{name}' missing approval.review_ticket")
|
|
183
|
+
|
|
184
|
+
for src, dep_refs in graph.items():
|
|
185
|
+
for dep in dep_refs:
|
|
186
|
+
if dep not in names:
|
|
187
|
+
errors.append(f"Repo '{src}' depends on unknown repo '{dep}'.")
|
|
188
|
+
|
|
189
|
+
# Cycle detection via DFS colors: 0=unseen, 1=visiting, 2=done
|
|
190
|
+
state: dict[str, int] = {name: 0 for name in names}
|
|
191
|
+
|
|
192
|
+
def visit(node: str) -> None:
|
|
193
|
+
if state[node] == 1:
|
|
194
|
+
errors.append(f"Dependency cycle detected at '{node}'.")
|
|
195
|
+
return
|
|
196
|
+
if state[node] == 2:
|
|
197
|
+
return
|
|
198
|
+
state[node] = 1
|
|
199
|
+
for nxt in graph.get(node, []):
|
|
200
|
+
if nxt in state:
|
|
201
|
+
visit(nxt)
|
|
202
|
+
state[node] = 2
|
|
203
|
+
|
|
204
|
+
for name in names:
|
|
205
|
+
visit(name)
|
|
206
|
+
|
|
207
|
+
return ValidationResult(errors=errors, warnings=warnings)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def write_report(
|
|
211
|
+
path: Path,
|
|
212
|
+
registry_path: Path,
|
|
213
|
+
registry_mode: str,
|
|
214
|
+
schema_version: str,
|
|
215
|
+
strict: bool,
|
|
216
|
+
result: ValidationResult,
|
|
217
|
+
repos_count: int,
|
|
218
|
+
) -> None:
|
|
219
|
+
report = {
|
|
220
|
+
"timestamp": utc_now(),
|
|
221
|
+
"registry_path": str(registry_path).replace("\\", "/"),
|
|
222
|
+
"registry_mode": str(registry_mode),
|
|
223
|
+
"schema_version": schema_version,
|
|
224
|
+
"strict_mode": strict,
|
|
225
|
+
"repos_count": repos_count,
|
|
226
|
+
"errors_count": len(result.errors),
|
|
227
|
+
"warnings_count": len(result.warnings),
|
|
228
|
+
"errors": result.errors,
|
|
229
|
+
"warnings": result.warnings,
|
|
230
|
+
}
|
|
231
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
232
|
+
path.write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def main() -> int:
|
|
236
|
+
parser = argparse.ArgumentParser(description="Validate repo-registry with strict governance checks.")
|
|
237
|
+
parser.add_argument("--registry", default="repo-registry/repos.yml", help="Registry file path")
|
|
238
|
+
parser.add_argument("--strict", action="store_true", help="Enable strict governance checks")
|
|
239
|
+
args = parser.parse_args()
|
|
240
|
+
|
|
241
|
+
repo_root = Path(__file__).resolve().parents[2]
|
|
242
|
+
registry_path = (repo_root / args.registry).resolve()
|
|
243
|
+
if not registry_path.exists():
|
|
244
|
+
print(f"Missing registry file: {registry_path}")
|
|
245
|
+
return 1
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
registry = load_registry(registry_path)
|
|
249
|
+
except Exception as exc:
|
|
250
|
+
print(f"Failed to parse registry file: {exc}")
|
|
251
|
+
return 1
|
|
252
|
+
|
|
253
|
+
registry_mode = str(registry.get("registry_mode", "enterprise")).strip().lower() or "enterprise"
|
|
254
|
+
schema_version = str(registry.get("schema_version", "1.0"))
|
|
255
|
+
repos_count = len(registry.get("repos", [])) if isinstance(registry.get("repos", []), list) else 0
|
|
256
|
+
result = validate(registry=registry, strict=args.strict, repo_root=repo_root)
|
|
257
|
+
|
|
258
|
+
report_path = repo_root / "repo-intake/generated/reports/repo-registry-validation.json"
|
|
259
|
+
write_report(
|
|
260
|
+
path=report_path,
|
|
261
|
+
registry_path=registry_path,
|
|
262
|
+
registry_mode=registry_mode,
|
|
263
|
+
schema_version=schema_version,
|
|
264
|
+
strict=args.strict,
|
|
265
|
+
result=result,
|
|
266
|
+
repos_count=repos_count,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
if result.errors:
|
|
270
|
+
print("Repo registry validation failed.")
|
|
271
|
+
for err in result.errors:
|
|
272
|
+
print(f"- {err}")
|
|
273
|
+
print(f"Report: {report_path}")
|
|
274
|
+
return 1
|
|
275
|
+
|
|
276
|
+
print("Repo registry validation OK.")
|
|
277
|
+
if result.warnings:
|
|
278
|
+
print("Warnings:")
|
|
279
|
+
for warn in result.warnings:
|
|
280
|
+
print(f"- {warn}")
|
|
281
|
+
print(f"Report: {report_path}")
|
|
282
|
+
return 0
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
if __name__ == "__main__":
|
|
286
|
+
raise SystemExit(main())
|