@mutagent/helix 0.1.0-alpha.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/mutagent-helix/SKILL.md +113 -0
- package/.claude/skills/mutagent-helix/scripts/cli/codex-transcode.ts +143 -0
- package/.claude/skills/mutagent-helix/scripts/cli/doctor.ts +111 -0
- package/.claude/skills/mutagent-helix/scripts/cli/init.ts +382 -0
- package/.claude/skills/mutagent-helix/scripts/cli/run.sh +61 -0
- package/CLAUDE.md +193 -0
- package/LICENSE +201 -0
- package/NOTICE +22 -0
- package/README.md +209 -0
- package/assets/adl-loop.svg +162 -0
- package/assets/system-status.svg +422 -0
- package/bin/mutagent-cli.mjs +9950 -0
- package/bin/mutagent-helix.mjs +78 -0
- package/mutagent-agentspec/.claude/skills/mutagent-agentspec/CLAUDE.md +28 -0
- package/mutagent-agentspec/.claude/skills/mutagent-agentspec/SKILL.md +138 -0
- package/mutagent-agentspec/.claude/skills/mutagent-agentspec/assets/templates/agentspec.yaml.tpl +247 -0
- package/mutagent-agentspec/.claude/skills/mutagent-agentspec/references/frameworks/doc-pins.md +53 -0
- package/mutagent-agentspec/.claude/skills/mutagent-agentspec/references/principles.md +91 -0
- package/mutagent-agentspec/.claude/skills/mutagent-agentspec/references/requirements.yaml +295 -0
- package/mutagent-agentspec/.claude/skills/mutagent-agentspec/references/workflows/orchestrator-protocol.md +302 -0
- package/mutagent-agentspec/.claude/skills/mutagent-agentspec/scripts/cli/doctor.ts +116 -0
- package/mutagent-agentspec/.claude/skills/mutagent-agentspec/scripts/cli/init.ts +230 -0
- package/mutagent-agentspec/.claude/skills/mutagent-agentspec/scripts/cli/run.sh +60 -0
- package/mutagent-agentspec/.claude/skills/mutagent-agentspec/scripts/config/resolve-spec-dir.ts +199 -0
- package/mutagent-agentspec/.claude/skills/mutagent-agentspec/scripts/contract/agentspec.schema.ts +455 -0
- package/mutagent-agentspec/.claude/skills/mutagent-agentspec/scripts/setup/detect.ts +67 -0
- package/mutagent-agentspec/.claude/skills/mutagent-agentspec/scripts/validate/validate-spec.ts +93 -0
- package/mutagent-agentspec/LICENSE +201 -0
- package/mutagent-agentspec/NOTICE +19 -0
- package/mutagent-agentspec/README.md +44 -0
- package/mutagent-builder/.claude/skills/mutagent-builder/SKILL.md +100 -0
- package/mutagent-builder/.claude/skills/mutagent-builder/assets/agents/ai-architect.md +312 -0
- package/mutagent-builder/.claude/skills/mutagent-builder/assets/agents/ai-engineer.md +357 -0
- package/mutagent-builder/.claude/skills/mutagent-builder/assets/templates/build-report.md.tpl +39 -0
- package/mutagent-builder/.claude/skills/mutagent-builder/references/principles.md +31 -0
- package/mutagent-builder/.claude/skills/mutagent-builder/references/workflows/build-protocol.md +61 -0
- package/mutagent-builder/.claude/skills/mutagent-builder/references/workflows/improve-handoff.md +23 -0
- package/mutagent-builder/.claude/skills/mutagent-builder/scripts/cli/run.sh +60 -0
- package/mutagent-builder/.claude/skills/mutagent-builder/scripts/handoff/validate-build-input.ts +72 -0
- package/mutagent-builder/.claude/skills/mutagent-builder/scripts/sync-spec/check-sync-spec.ts +499 -0
- package/mutagent-builder/.claude/skills/mutagent-builder/scripts/verify/spec-impl-coverage.ts +182 -0
- package/mutagent-builder/CLAUDE.md +25 -0
- package/mutagent-builder/README.md +26 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/README.md +192 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/SKILL.md +352 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/agents/ai-architect.md +256 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/agents/ai-engineer.md +344 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/agents/diagnostics-analyzer.md +576 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/agents/discovery.md +425 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/audit.json.tpl +24 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/audit.md.tpl +49 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/config.yaml.tpl +100 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/pr-body.md.tpl +56 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/report.html.tpl +327 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/shared/README.md +27 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/shared/iter-N-handover.md.tpl +125 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/shared/self-diagnosis-contract.v0.1.0.yaml.tpl +234 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/shared/spec.yaml.tpl +152 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/shared/team.yaml.tpl +127 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/shared/wave-N-dashboard.html.tpl +1712 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/diagnostics/WF-2.1-nl-query.md +59 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/diagnostics/WF-2.2-tier0-progress.md +58 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/diagnostics/WF-2.3-analysis-progress.md +44 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/diagnostics/WF-2.4-html-report.md +61 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/diagnostics/WF-2.5-rca-detail.md +69 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/diagnostics/WF-2.6-stale-warning.md +53 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/onboarding/WF-1.1-welcome.md +60 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/onboarding/WF-1.2-source-platform-picker.md +41 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/onboarding/WF-1.3-source-auth.md +75 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/onboarding/WF-1.4-target-picker.md +48 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/onboarding/WF-1.5-ask-tool.md +43 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/onboarding/WF-1.6-config-review.md +57 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/onboarding/WF-1.7-filter-defaults.md +52 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/onboarding/WF-1.8-first-run.md +59 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/examples/sample-findings.json +92 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/examples/sample-meta.json +47 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/adapter-strategy.md +75 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/config-migration.md +84 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/config.md +159 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/filter-search-matrix.md +66 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/harness-knowledge.md +22 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/memory-format.md +78 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/onboarding-decisions.yaml +187 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/operation-inventory.md +59 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/overview.md +167 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/reference.md +119 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/script-index.md +101 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/source-adapter-contract.md +93 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/source-platforms/claude-code-transcripts.md +66 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/source-platforms/codex-transcripts.md +78 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/source-platforms/codex.md +145 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/source-platforms/install-paths.md +57 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/source-platforms/langfuse.md +178 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/source-platforms/local-jsonl.md +55 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/source-platforms/openobserve.md +102 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/source-platforms/otel.md +63 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/target-platforms/cloud-rest.md +84 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/target-platforms/local-claude.md +65 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/target-platforms/local-cloud-agent-sdk.md +92 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/target-platforms/local-codex.md +72 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/target-platforms/local-cursor.md +108 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/target-platforms/local-mastra.md +77 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/target-platforms/local-opencode.md +70 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/workflows/apply-pr-comment-format.md +82 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/workflows/diagnostics.md +35 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/workflows/handover-contract.md +247 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/workflows/onboarding.md +300 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/workflows/orchestrator-protocol.md +1861 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/workflows/rca.md +331 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/workflows/rendering-anatomy.md +314 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/workflows/schedule-prep.md +100 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/workflows/scope-model.md +128 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/workflows/verification-methodology.md +174 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/aggregate/sort-findings.ts +0 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/awareness/blind-spots.ts +167 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/awareness/llm-sample.ts +681 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/cli/doctor.ts +215 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/cli/init.ts +1013 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/cli/install-agents.ts +350 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/cli/run.sh +61 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/cli/shared-templates.ts +79 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/config/load.ts +392 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/config/resolve-credential.ts +120 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/config/schema.ts +641 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/config/validate.ts +134 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/context/build-diagnosis-context.ts +417 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/context/load-context.ts +105 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/contract/types.ts +166 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/enrich/build-render-input.ts +1964 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/enrich/corpus-severity.ts +172 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/enrich/dismissal-match.ts +270 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/enrich/rank-remedies.ts +75 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/hooks/install-hooks.sh +46 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/hooks/pre-push.sh +78 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/invocation/parse-brief.ts +188 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/library/match.ts +114 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/library/paths.ts +95 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/library/store.ts +535 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/library/types.ts +181 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/lint/template-inline-js.ts +267 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/memory/append.ts +188 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/memory/read.ts +218 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/normalize/platforms/entity-context.ts +686 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/normalize/read-unitf.ts +267 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/normalize/trace.ts +1078 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/normalize/unitf-adapter.ts +0 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/normalize/unitf-types.ts +213 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/report/persist-selections.ts +174 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/report/render.ts +3254 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/run/diagnose.ts +64 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/run/session.ts +167 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/sample/caps.ts +345 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/sample/deep-read-gate.ts +424 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/sample/representative.ts +584 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/scan/objection.ts +224 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/scan/trajectory.ts +489 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/self-diagnostics/dispatch.ts +135 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/self-diagnostics/probe.ts +170 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/setup/detect.ts +307 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/setup/ensure-cli.ts +315 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/setup/reconfigure.ts +77 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/setup/verify-agents.ts +207 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/slicer.ts +166 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/source/adapters/claude-code.ts +17 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/source/adapters/codex.ts +11 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/source/adapters/langfuse.ts +17 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/source/adapters/local-jsonl.ts +12 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/source/adapters/otel.ts +12 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/source/index.ts +54 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/source/registry.ts +45 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/source/types.ts +86 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/stale-detector.ts +74 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/test/render-playwright.cli.ts +477 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/tier0/claude-code.ts +133 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/tier0/langfuse.ts +143 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/tier0-scan.ts +423 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/validate/completeness-check.ts +436 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/validate/doc-refs.ts +190 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/validate/finalize-gate.ts +417 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/validate/findings-contract.ts +628 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/validate/render-js-syntax.ts +188 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/validate/report-checklist.yaml +217 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/validate/trace-shape.ts +262 -0
- package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/validate/wave6-checklist.ts +326 -0
- package/mutagent-diagnostics/CLAUDE.md +41 -0
- package/mutagent-diagnostics/LICENSE +201 -0
- package/mutagent-diagnostics/NOTICE +18 -0
- package/mutagent-diagnostics/README.md +343 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/SKILL.md +548 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/assets/agents/audit-executor.md +169 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/assets/agents/dataset-builder.md +160 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/assets/agents/discovery.md +425 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/assets/agents/evaluator.md +1108 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/assets/brand/theme.css +213 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/assets/brand/wordmark.html +9 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/lenses/context-flow-lens.md +85 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/lenses/data-lens.md +47 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/lenses/decision-lens.md +48 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/lenses/methodology-critic-lens.md +53 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/lenses/trajectory-lens.md +44 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/build-review-interface.md +83 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/edd-loop.md +134 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/error-analysis.md +113 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/eval-audit.md +154 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/eval-stage.md +168 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/generate-synthetic-data.md +81 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/grounded-adjudication.md +221 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/memory-format.md +65 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/methodology.md +201 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/operation-inventory.md +197 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/validate-evaluator.md +125 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/workflows/orchestrator-protocol.md +300 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/write-judge-prompt.md +123 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/schemas/behavior-tree.schema.yaml +73 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/schemas/dataset.schema.yaml +66 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/schemas/edd-change-request.schema.yaml +114 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/schemas/eval-matrix.schema.yaml +74 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/schemas/flow-graph.schema.yaml +69 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/schemas/flow-profile.schema.yaml +49 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/schemas/methodology-review.schema.yaml +40 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/schemas/scorecard.schema.yaml +85 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/agent-dispatch.ts +0 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/aggregate-discover.ts +543 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/artifact-paths.ts +115 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/assemble-scorecard.ts +172 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/build-dataset.ts +186 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/build-evals.ts +93 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/build-review-ui.ts +393 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/check-method-router.ts +170 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cli/aggregate.ts +112 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cli/audit-run.ts +182 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cli/doctor.ts +211 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cli/dogfood.ts +133 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cli/init.ts +601 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cli/methodology-review.ts +122 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cli/prep.ts +295 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cli/profile-subject.ts +165 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cli/run.sh +56 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cli/variance-check.ts +105 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/code-eval.ts +248 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/code-quality-verdict.ts +285 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/codegen-evals.ts +173 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cold-start-project.ts +151 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cold-start-sampler.ts +267 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/config/load.ts +343 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/config/schema.ts +380 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/contracts/agentspec-evals.ts +85 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/contracts/dataset.ts +149 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/contracts/eval-engine.ts +118 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/contracts/eval-matrix.ts +577 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/contracts/eval-types.ts +1074 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/contracts/flow-graph.ts +194 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/contracts/types.ts +303 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/contracts/validation.ts +193 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/derive-dataset.ts +152 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/determine-outcome.ts +219 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/diff-discriminate.ts +160 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/discover-criteria.ts +348 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/edd/change-request.ts +232 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/edd/edd-types.ts +210 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/edd/variance-gate.ts +186 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/emit-completeness.ts +111 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/eval-engine.ts +160 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/evaluate.ts +333 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/flow-graph.ts +230 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/judge-prompt-template.ts +253 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/judge-provider.ts +135 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/lint-grounding.ts +229 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/lint-uniformity.ts +168 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/living-suite.ts +109 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/load-bundle.ts +203 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/load-profile-vocab.ts +64 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/load-profile.ts +106 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/mask.ts +138 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/materialize-dataset.ts +113 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/matrix-judge.ts +750 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/memory/append.ts +215 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/memory/read.ts +168 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/persist-eval-criteria.ts +232 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/prep-tasks.ts +125 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/profile-subject.ts +310 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/publish-report.ts +131 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/read-unitf-traces.ts +81 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/render-build-cards.ts +195 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/render-discover-report.ts +1640 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/render-eval-report.ts +3838 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/render-report.ts +212 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/resolve-credential.ts +110 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/resolve-ref.ts +98 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/result-verify.ts +129 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/route-failures.ts +330 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/run-deterministic.ts +367 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/run-evaluate.ts +719 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/run-judge.ts +155 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/run-pipeline.ts +175 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/sample-traces.ts +210 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/self-audit.ts +387 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/source-map.ts +106 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/subject-profile.ts +134 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/substrate.ts +162 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/sync-eval-criteria.ts +244 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/ui-slots.ts +119 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/unitf-to-evaltrace.ts +284 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/validate-judge.ts +358 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/variance-compare.ts +177 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/subjects/mutagent-diagnostics/behavior-tree.yaml +140 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/subjects/mutagent-diagnostics/eval-matrix.yaml +1270 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/subjects/mutagent-diagnostics/methodology-review.yaml +105 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/workflows/audit.workflow.js +82 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/workflows/data-leak.workflow.js +236 -0
- package/mutagent-evaluator/.claude/skills/mutagent-evaluator/workflows/variance.workflow.js +163 -0
- package/mutagent-evaluator/CLAUDE.md +45 -0
- package/mutagent-evaluator/LICENSE +201 -0
- package/mutagent-evaluator/NOTICE +23 -0
- package/mutagent-evaluator/README.md +88 -0
- package/mutagent-optimize/.claude/skills/mutagent-optimize/SKILL.md +287 -0
- package/mutagent-optimize/.claude/skills/mutagent-optimize/scripts/contracts/amend-request.ts +503 -0
- package/mutagent-optimize/.claude/skills/mutagent-optimize/scripts/improve-loop-run.ts +304 -0
- package/mutagent-optimize/.claude/skills/mutagent-optimize/scripts/loop-state-cli.ts +300 -0
- package/mutagent-optimize/.claude/skills/mutagent-optimize/scripts/loop-state.ts +440 -0
- package/mutagent-optimize/CLAUDE.md +49 -0
- package/mutagent-optimize/README.md +26 -0
- package/mutagent-orchestrator/CLAUDE.md +102 -0
- package/mutagent-orchestrator/README.md +20 -0
- package/mutagent-orchestrator/assets/agents/discovery.md +425 -0
- package/mutagent-orchestrator/assets/agents/dogfood-monitor.md +266 -0
- package/mutagent-orchestrator/assets/agents/monitor.md +258 -0
- package/mutagent-orchestrator/orchestrator.md +782 -0
- package/mutagent-orchestrator/references/MONITORING.md +209 -0
- package/mutagent-orchestrator/references/config-fields.md +132 -0
- package/mutagent-orchestrator/references/config-migration.md +132 -0
- package/mutagent-orchestrator/references/index-registry.md +92 -0
- package/mutagent-orchestrator/routing.yaml +391 -0
- package/mutagent-orchestrator/scripts/config-schema.ts +1010 -0
- package/mutagent-orchestrator/scripts/dispatch.ts +439 -0
- package/mutagent-orchestrator/scripts/dogfood/extract-feedback.ts +203 -0
- package/mutagent-orchestrator/scripts/dogfood/reconstruct-trajectory.ts +327 -0
- package/mutagent-orchestrator/scripts/dogfood/render-dogfood-report.ts +347 -0
- package/mutagent-orchestrator/scripts/dogfood/resolve-subagents.ts +160 -0
- package/mutagent-orchestrator/scripts/dogfood/run-report.ts +264 -0
- package/mutagent-orchestrator/scripts/dogfood/slack-thread.ts +106 -0
- package/mutagent-orchestrator/scripts/dogfood/types.ts +257 -0
- package/mutagent-orchestrator/scripts/eval-routing.ts +753 -0
- package/mutagent-orchestrator/scripts/gate.ts +219 -0
- package/mutagent-orchestrator/scripts/handover-contract.ts +362 -0
- package/mutagent-orchestrator/scripts/index/build-index.ts +351 -0
- package/mutagent-orchestrator/scripts/index/render-index.ts +110 -0
- package/mutagent-orchestrator/scripts/monitor/slack-notify.ts +111 -0
- package/mutagent-orchestrator/scripts/monitor/triggers.ts +162 -0
- package/mutagent-orchestrator/scripts/onboarding-check.ts +369 -0
- package/mutagent-orchestrator/scripts/render-roster.ts +308 -0
- package/mutagent-orchestrator/scripts/resolve-credential.ts +185 -0
- package/mutagent-orchestrator/scripts/resolve-paths.ts +114 -0
- package/mutagent-orchestrator/scripts/slack/format.ts +63 -0
- package/mutagent-orchestrator/scripts/slack/post.ts +115 -0
- package/mutagent-orchestrator/scripts/sync-index.ts +311 -0
- package/package.json +64 -0
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { Type } from "@sinclair/typebox";
|
|
4
|
+
import { TypeCompiler } from "@sinclair/typebox/compiler";
|
|
5
|
+
import { parse as parseYaml } from "yaml";
|
|
6
|
+
|
|
7
|
+
import { loadConfig } from "./config-schema.ts";
|
|
8
|
+
import { resolveConfigPath } from "./resolve-paths.ts";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// LLM-driven routing eval — closes spec pred3 ("a routing map/table + tests map
|
|
12
|
+
// utterances -> command") the OPERATOR'S way.
|
|
13
|
+
//
|
|
14
|
+
// pred3 asked for tests that resolve an utterance -> *command. The operator
|
|
15
|
+
// REJECTED a deterministic resolver (testing-theater for an LLM router) AND a
|
|
16
|
+
// spec amendment. The directive (verbatim): "do a LLM driven test, like a
|
|
17
|
+
// re-usable command to evaluate also routing (of orchestrator itself etc.)."
|
|
18
|
+
//
|
|
19
|
+
// So this module is a REUSABLE LLM-DRIVEN routing eval: feed utterances through
|
|
20
|
+
// the REAL LLM router (the orchestrator's ACTUAL routing behavior — same prompt
|
|
21
|
+
// it would use) and score the chosen *command against a labeled dataset.
|
|
22
|
+
//
|
|
23
|
+
// THE DI SPLIT (so the CI gate stays deterministic while the eval is real):
|
|
24
|
+
// - DETERMINISTIC HARNESS (in `bun test`): the dataset loader + the categorical
|
|
25
|
+
// exact-match grader + the aggregation. The router is an INJECTED function
|
|
26
|
+
// (RouterFn). Tests inject a STUB → assert the pass/fail math. NO live LLM.
|
|
27
|
+
// - REAL LLM ROUTER (on-demand, the CLI): `routeViaLLM` builds the routing
|
|
28
|
+
// prompt from a RoutingContext (mirroring orchestrator.md's NL-routing +
|
|
29
|
+
// routing.yaml intents), calls the model, and parses the answer to
|
|
30
|
+
// one-of-the-closed-set-or-null. Non-deterministic → it produces a SCORED
|
|
31
|
+
// REPORT, never a gate assertion.
|
|
32
|
+
//
|
|
33
|
+
// REUSABLE: every public function is parameterized on a RoutingContext (the
|
|
34
|
+
// command roster + utterances) and a closed command set — so the SAME harness
|
|
35
|
+
// evaluates the orchestrator's own routing today and any other routing table
|
|
36
|
+
// tomorrow (the evaluator skill absorbs/generalizes this in Loop-2 per the EQ
|
|
37
|
+
// goal). `routeViaLLM(utterance, ctx, …)` takes the routing table as an argument.
|
|
38
|
+
//
|
|
39
|
+
// MODEL INTENT IS SACRED (feedback_model_intent_sacred): the model is
|
|
40
|
+
// `config.models.default` OR an explicit `--model`. There is NO silent swap and
|
|
41
|
+
// NO retry-on-failure alternate-model fallback. If the chosen model/creds can't
|
|
42
|
+
// run, `routeViaLLM` THROWS — it never substitutes a different model. The CLI
|
|
43
|
+
// refuses to pick a model for you.
|
|
44
|
+
//
|
|
45
|
+
// GRADER: categorical EXACT-MATCH (got === expected). The command is a CLOSED set
|
|
46
|
+
// (10 for the orchestrator); a fuzzy LLM judge would be the wrong tool. A router
|
|
47
|
+
// that returns anything outside the set is normalized/rejected to null — never a
|
|
48
|
+
// new class.
|
|
49
|
+
//
|
|
50
|
+
// Design invariants (mirror scripts/handover-contract.ts + scripts/config-schema.ts):
|
|
51
|
+
// - Pure functions + a thin CLI wrapper. The pure core has NO clock, NO random,
|
|
52
|
+
// NO network. The ONLY network call is `routeViaLLM`, reached solely from the
|
|
53
|
+
// CLI (never from the deterministic harness). Any report timestamp is a CLI
|
|
54
|
+
// `--stamp` param, never a self-read clock — so the harness stays deterministic.
|
|
55
|
+
// - Loaders read an INJECTED path (resolved LOCAL, never `~/.mutagent`); `~` expansion
|
|
56
|
+
// happens only in the thin CLI.
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
/** The orchestrator's CLOSED command set (normalized `*name`). Mirrors routing.yaml. */
|
|
60
|
+
export const ORCHESTRATOR_COMMANDS: readonly string[] = [
|
|
61
|
+
"*spec",
|
|
62
|
+
"*build",
|
|
63
|
+
"*sync-spec",
|
|
64
|
+
"*sync",
|
|
65
|
+
"*evaluate",
|
|
66
|
+
"*audit",
|
|
67
|
+
"*diagnose",
|
|
68
|
+
"*improve",
|
|
69
|
+
"*status",
|
|
70
|
+
"*onboard",
|
|
71
|
+
"*help",
|
|
72
|
+
] as const;
|
|
73
|
+
|
|
74
|
+
/** The per-command bucket key used for out-of-domain (null-expected) cases. */
|
|
75
|
+
export const NULL_BUCKET = "(out-of-domain)" as const;
|
|
76
|
+
|
|
77
|
+
// ── Dataset types + schema ───────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
/** One labeled eval case: a free-text utterance and its expected *command (or null). */
|
|
80
|
+
export interface EvalCase {
|
|
81
|
+
utterance: string;
|
|
82
|
+
/** The expected command (normalized `*name`) OR null for out-of-domain. */
|
|
83
|
+
expected_command: string | null;
|
|
84
|
+
/** true ⇒ a NOVEL paraphrase NOT in the routing table (held-out cohort). */
|
|
85
|
+
held_out?: boolean;
|
|
86
|
+
/** Optional human note (kept for auditability; ignored by the grader). */
|
|
87
|
+
note?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** A labeled routing-eval dataset. */
|
|
91
|
+
export interface Dataset {
|
|
92
|
+
version?: string;
|
|
93
|
+
cases: EvalCase[];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// STRUCTURAL TypeBox schema (closed objects). expected_command is string|null at
|
|
97
|
+
// the structural layer; membership in the CLOSED command set is enforced
|
|
98
|
+
// separately in parseDataset (so the harness is reusable on any command set).
|
|
99
|
+
const EvalCaseSchema = Type.Object(
|
|
100
|
+
{
|
|
101
|
+
utterance: Type.String({ minLength: 1 }),
|
|
102
|
+
expected_command: Type.Union([Type.Null(), Type.String({ minLength: 1 })]),
|
|
103
|
+
held_out: Type.Optional(Type.Boolean()),
|
|
104
|
+
note: Type.Optional(Type.String()),
|
|
105
|
+
},
|
|
106
|
+
{ additionalProperties: false },
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const DatasetSchema = Type.Object(
|
|
110
|
+
{
|
|
111
|
+
version: Type.Optional(Type.String()),
|
|
112
|
+
cases: Type.Array(EvalCaseSchema),
|
|
113
|
+
},
|
|
114
|
+
{ additionalProperties: false },
|
|
115
|
+
);
|
|
116
|
+
const DatasetChecker = TypeCompiler.Compile(DatasetSchema);
|
|
117
|
+
|
|
118
|
+
// ── RoutingContext types ─────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
/** One command row of a routing table (mirrors routing.yaml `commands`). */
|
|
121
|
+
export interface RoutingCommand {
|
|
122
|
+
/** Normalized `*name`. */
|
|
123
|
+
command: string;
|
|
124
|
+
stage?: string;
|
|
125
|
+
description?: string;
|
|
126
|
+
/** The NL utterances/intents that route to this command. */
|
|
127
|
+
utterances: string[];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** A routing table — the closed roster the router chooses from. Parameterizable. */
|
|
131
|
+
export interface RoutingContext {
|
|
132
|
+
commands: RoutingCommand[];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The router's answer for one utterance. `command` is null ⇒ "route nowhere". */
|
|
136
|
+
export interface RouteResult {
|
|
137
|
+
command: string | null;
|
|
138
|
+
/** The model's raw text (audit trail); optional for stubs. */
|
|
139
|
+
raw?: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The INJECTED router. The deterministic harness calls this for every case; tests
|
|
144
|
+
* inject a stub, the CLI injects the real `routeViaLLM`. Parameterized on the
|
|
145
|
+
* RoutingContext so the SAME harness evaluates any routing table.
|
|
146
|
+
*/
|
|
147
|
+
export type RouterFn = (
|
|
148
|
+
utterance: string,
|
|
149
|
+
ctx: RoutingContext,
|
|
150
|
+
) => Promise<RouteResult>;
|
|
151
|
+
|
|
152
|
+
// ── Report types ─────────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
/** A cohort's aggregate accuracy. */
|
|
155
|
+
export interface CohortStat {
|
|
156
|
+
n: number;
|
|
157
|
+
passed: number;
|
|
158
|
+
accuracy: number;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** The scored routing-eval report. Pure aggregation — no clock/random. */
|
|
162
|
+
export interface RoutingReport {
|
|
163
|
+
total: number;
|
|
164
|
+
passed: number;
|
|
165
|
+
failed: number;
|
|
166
|
+
accuracy: number;
|
|
167
|
+
/** Per-EXPECTED-command rates (keyed by the closed set + NULL_BUCKET). */
|
|
168
|
+
per_command: Record<string, { n: number; passed: number }>;
|
|
169
|
+
/** In-distribution cohort (held_out !== true). */
|
|
170
|
+
in_distribution: CohortStat;
|
|
171
|
+
/** Held-out cohort (held_out === true) — the generalization measure. */
|
|
172
|
+
held_out: CohortStat;
|
|
173
|
+
/** Every mis-route, with the exact (utterance, expected, got) triple. */
|
|
174
|
+
failures: Array<{ utterance: string; expected: string | null; got: string | null }>;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ── Dataset loading (pure) ────────────────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Parse + validate a routing-eval dataset from YAML text. STRUCTURAL validation
|
|
181
|
+
* (closed objects, required fields) via TypeBox, THEN a closed-set check on every
|
|
182
|
+
* `expected_command` (null OR a member of `opts.validCommands`, default the
|
|
183
|
+
* orchestrator's 10). THROWS on any violation — never silently coerces (a typo'd
|
|
184
|
+
* label is a contract bug, not a 0-score case). Pure: no I/O, no clock.
|
|
185
|
+
*/
|
|
186
|
+
export function parseDataset(
|
|
187
|
+
raw: string,
|
|
188
|
+
opts: { validCommands?: readonly string[] } = {},
|
|
189
|
+
): Dataset {
|
|
190
|
+
const validCommands = opts.validCommands ?? ORCHESTRATOR_COMMANDS;
|
|
191
|
+
|
|
192
|
+
let parsed: unknown;
|
|
193
|
+
try {
|
|
194
|
+
parsed = parseYaml(raw);
|
|
195
|
+
} catch (err) {
|
|
196
|
+
throw new Error(`routing-eval dataset: malformed YAML — ${String(err)}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!DatasetChecker.Check(parsed)) {
|
|
200
|
+
const errs = [...DatasetChecker.Errors(parsed)]
|
|
201
|
+
.map((e) => `${e.path === "" ? "/" : e.path}: ${e.message}`)
|
|
202
|
+
.join("; ");
|
|
203
|
+
throw new Error(`routing-eval dataset: structural validation failed — ${errs}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const ds = parsed as Dataset;
|
|
207
|
+
for (const c of ds.cases) {
|
|
208
|
+
if (c.expected_command !== null && !validCommands.includes(c.expected_command)) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
`routing-eval dataset: case '${c.utterance}' has expected_command ` +
|
|
211
|
+
`'${c.expected_command}' which is not in the closed command set ` +
|
|
212
|
+
`[${validCommands.join(", ")}] (and is not null)`,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return ds;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Read + parse + validate a dataset from an INJECTED file path. The path is taken
|
|
221
|
+
* verbatim (no `~` expansion — the CLI does that). Pure aside from the single file
|
|
222
|
+
* read. Throws (via parseDataset) on a malformed/invalid dataset.
|
|
223
|
+
*/
|
|
224
|
+
export function loadDataset(
|
|
225
|
+
datasetPath: string,
|
|
226
|
+
opts: { validCommands?: readonly string[] } = {},
|
|
227
|
+
): Dataset {
|
|
228
|
+
const raw = fs.readFileSync(datasetPath, "utf-8");
|
|
229
|
+
return parseDataset(raw, opts);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ── RoutingContext loading (pure) ─────────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
// The shape of routing.yaml's `commands` block (only the fields we read).
|
|
235
|
+
const RoutingYamlSchema = Type.Object(
|
|
236
|
+
{
|
|
237
|
+
commands: Type.Record(
|
|
238
|
+
Type.String(),
|
|
239
|
+
Type.Object(
|
|
240
|
+
{
|
|
241
|
+
stage: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
242
|
+
utterances: Type.Optional(Type.Array(Type.String())),
|
|
243
|
+
},
|
|
244
|
+
{ additionalProperties: true },
|
|
245
|
+
),
|
|
246
|
+
),
|
|
247
|
+
},
|
|
248
|
+
{ additionalProperties: true },
|
|
249
|
+
);
|
|
250
|
+
const RoutingYamlChecker = TypeCompiler.Compile(RoutingYamlSchema);
|
|
251
|
+
|
|
252
|
+
/** Normalize a command name to its canonical `*name` form. */
|
|
253
|
+
function starCommand(name: string): string {
|
|
254
|
+
const t = name.trim();
|
|
255
|
+
return t.startsWith("*") ? t : `*${t}`;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Read routing.yaml at an INJECTED path into a RoutingContext (the closed roster
|
|
260
|
+
* + each command's utterances). This is the SAME map the orchestrator routes on,
|
|
261
|
+
* so the eval measures ACTUAL routing behavior — not a parallel prompt. Pure
|
|
262
|
+
* aside from the file read. Throws on a malformed routing file.
|
|
263
|
+
*/
|
|
264
|
+
export function loadRoutingContext(routingPath: string): RoutingContext {
|
|
265
|
+
const raw = fs.readFileSync(routingPath, "utf-8");
|
|
266
|
+
let parsed: unknown;
|
|
267
|
+
try {
|
|
268
|
+
parsed = parseYaml(raw);
|
|
269
|
+
} catch (err) {
|
|
270
|
+
throw new Error(`routing map: malformed YAML in ${routingPath} — ${String(err)}`);
|
|
271
|
+
}
|
|
272
|
+
if (!RoutingYamlChecker.Check(parsed)) {
|
|
273
|
+
throw new Error(`routing map: ${routingPath} is missing a valid 'commands' block`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const block = (parsed as { commands: Record<string, { stage?: string | number; utterances?: string[] }> })
|
|
277
|
+
.commands;
|
|
278
|
+
const commands: RoutingCommand[] = Object.entries(block).map(([name, body]) => ({
|
|
279
|
+
command: starCommand(name),
|
|
280
|
+
stage: body.stage === undefined ? undefined : String(body.stage),
|
|
281
|
+
utterances: body.utterances ?? [],
|
|
282
|
+
}));
|
|
283
|
+
return { commands };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** The closed command set (normalized `*name`) for a routing context. */
|
|
287
|
+
export function validCommandsFromContext(ctx: RoutingContext): string[] {
|
|
288
|
+
return ctx.commands.map((c) => c.command);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ── Prompt construction (pure) ────────────────────────────────────────────────
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Build the routing prompt the REAL router uses. It MIRRORS orchestrator.md's
|
|
295
|
+
* NL-routing instruction + routing.yaml's per-command utterances/intents so the
|
|
296
|
+
* eval scores the orchestrator's ACTUAL routing behavior (highest-confidence
|
|
297
|
+
* match; on no match → route nowhere, never guess a stage). Pure + deterministic:
|
|
298
|
+
* the same ctx yields the identical string (commands in ctx order). Reusable —
|
|
299
|
+
* the roster is entirely derived from the passed RoutingContext.
|
|
300
|
+
*/
|
|
301
|
+
export function buildRoutingPrompt(ctx: RoutingContext): string {
|
|
302
|
+
const lines: string[] = [];
|
|
303
|
+
lines.push(
|
|
304
|
+
"You are the MutagenT ADL orchestrator's natural-language intent ROUTER.",
|
|
305
|
+
"Map the user's free-text request to EXACTLY ONE command from the closed set",
|
|
306
|
+
"below — the command whose listed utterances/intents best match the request",
|
|
307
|
+
"(highest-confidence match). You are a router only; never perform the work.",
|
|
308
|
+
"",
|
|
309
|
+
"Commands (the ONLY valid answers):",
|
|
310
|
+
);
|
|
311
|
+
for (const c of ctx.commands) {
|
|
312
|
+
const stage = c.stage ? ` [stage ${c.stage}]` : "";
|
|
313
|
+
const desc = c.description ? ` — ${c.description}` : "";
|
|
314
|
+
lines.push(`- ${c.command}${stage}${desc}`);
|
|
315
|
+
if (c.utterances.length > 0) {
|
|
316
|
+
const sample = c.utterances.map((u) => `"${u}"`).join(", ");
|
|
317
|
+
lines.push(` utterances: ${sample}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
lines.push(
|
|
321
|
+
"",
|
|
322
|
+
"Rules:",
|
|
323
|
+
"1. Choose exactly one command from the list above, by its token (e.g. *evaluate).",
|
|
324
|
+
"2. If the request is OUT-OF-DOMAIN (it does not match any command's purpose),",
|
|
325
|
+
" answer exactly: none. Never guess a stage for an off-topic request.",
|
|
326
|
+
"3. Respond with ONLY the chosen command token, or the single word none.",
|
|
327
|
+
" No explanation, no punctuation, no extra text.",
|
|
328
|
+
);
|
|
329
|
+
return lines.join("\n");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ── Answer parsing (pure) ─────────────────────────────────────────────────────
|
|
333
|
+
|
|
334
|
+
/** Escape a string for safe inclusion in a RegExp. */
|
|
335
|
+
function escapeRegExp(s: string): string {
|
|
336
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Normalize a router's raw answer to one of `validCommands` (normalized `*name`)
|
|
341
|
+
* or null. Pure. Handles: leading `*`, surrounding whitespace/quotes/backticks,
|
|
342
|
+
* mixed case, the words none/null/n-a/empty (⇒ null), and a single command token
|
|
343
|
+
* embedded in a short sentence. ANYTHING outside the closed set (a hallucinated
|
|
344
|
+
* command) ⇒ null — REJECTED, never coerced into a new class. An answer naming
|
|
345
|
+
* TWO valid commands is ambiguous ⇒ null. Word-boundary aware so e.g. "helpful"
|
|
346
|
+
* does not match "*help".
|
|
347
|
+
*/
|
|
348
|
+
export function parseRoutedCommand(
|
|
349
|
+
raw: string | null,
|
|
350
|
+
validCommands: readonly string[],
|
|
351
|
+
): string | null {
|
|
352
|
+
if (raw === null) return null;
|
|
353
|
+
const text = raw
|
|
354
|
+
.trim()
|
|
355
|
+
.toLowerCase()
|
|
356
|
+
.replace(/^["'`]+|["'`]+$/g, "")
|
|
357
|
+
.trim();
|
|
358
|
+
if (text === "" || text === "none" || text === "null" || text === "n/a") return null;
|
|
359
|
+
|
|
360
|
+
const bare = validCommands.map((c) => c.replace(/^\*/, "").toLowerCase());
|
|
361
|
+
|
|
362
|
+
// 1. exact match (with or without a leading `*`).
|
|
363
|
+
const stripped = text.replace(/^\*/, "");
|
|
364
|
+
const exactIdx = bare.indexOf(stripped);
|
|
365
|
+
if (exactIdx >= 0) return validCommands[exactIdx];
|
|
366
|
+
|
|
367
|
+
// 2. token scan — which valid commands appear as a `*token` or whole word.
|
|
368
|
+
const found = new Set<string>();
|
|
369
|
+
for (let i = 0; i < bare.length; i++) {
|
|
370
|
+
const re = new RegExp(
|
|
371
|
+
`(^|[^a-z0-9_*])\\*?${escapeRegExp(bare[i])}([^a-z0-9_]|$)`,
|
|
372
|
+
"i",
|
|
373
|
+
);
|
|
374
|
+
if (re.test(text)) found.add(validCommands[i]);
|
|
375
|
+
}
|
|
376
|
+
if (found.size === 1) {
|
|
377
|
+
const only = [...found][0];
|
|
378
|
+
return only ?? null;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// zero matches OR ambiguous (>1) ⇒ reject.
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// ── The deterministic eval (pure given routerFn) ──────────────────────────────
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Run a routing eval: for every case, call the INJECTED router, normalize its
|
|
389
|
+
* answer to the closed set (rejecting hallucinated commands to null), and grade
|
|
390
|
+
* by categorical EXACT-MATCH (got === expected; null===null is a correct refusal).
|
|
391
|
+
* Aggregates overall + per-command + in-distribution + held-out cohorts + the
|
|
392
|
+
* exact failure triples.
|
|
393
|
+
*
|
|
394
|
+
* PURE GIVEN routerFn: no clock, no random, no network here. Cases run
|
|
395
|
+
* sequentially in dataset order, so the report is deterministic for a
|
|
396
|
+
* deterministic router (same dataset + same stub ⇒ deep-equal report).
|
|
397
|
+
*/
|
|
398
|
+
export async function runRoutingEval(
|
|
399
|
+
dataset: Dataset,
|
|
400
|
+
routerFn: RouterFn,
|
|
401
|
+
ctx: RoutingContext,
|
|
402
|
+
): Promise<RoutingReport> {
|
|
403
|
+
const validCommands = validCommandsFromContext(ctx);
|
|
404
|
+
const per_command: Record<string, { n: number; passed: number }> = {};
|
|
405
|
+
const failures: RoutingReport["failures"] = [];
|
|
406
|
+
let passed = 0;
|
|
407
|
+
let heldN = 0;
|
|
408
|
+
let heldPassed = 0;
|
|
409
|
+
let inN = 0;
|
|
410
|
+
let inPassed = 0;
|
|
411
|
+
|
|
412
|
+
for (const c of dataset.cases) {
|
|
413
|
+
const result = await routerFn(c.utterance, ctx);
|
|
414
|
+
const got = parseRoutedCommand(result.command, validCommands);
|
|
415
|
+
const expected = c.expected_command;
|
|
416
|
+
const ok = got === expected;
|
|
417
|
+
|
|
418
|
+
const key = expected === null ? NULL_BUCKET : expected;
|
|
419
|
+
let bucket = per_command[key];
|
|
420
|
+
if (bucket === undefined) {
|
|
421
|
+
bucket = { n: 0, passed: 0 };
|
|
422
|
+
per_command[key] = bucket;
|
|
423
|
+
}
|
|
424
|
+
bucket.n += 1;
|
|
425
|
+
if (ok) {
|
|
426
|
+
bucket.passed += 1;
|
|
427
|
+
passed += 1;
|
|
428
|
+
} else {
|
|
429
|
+
failures.push({ utterance: c.utterance, expected, got });
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (c.held_out === true) {
|
|
433
|
+
heldN += 1;
|
|
434
|
+
if (ok) heldPassed += 1;
|
|
435
|
+
} else {
|
|
436
|
+
inN += 1;
|
|
437
|
+
if (ok) inPassed += 1;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const total = dataset.cases.length;
|
|
442
|
+
return {
|
|
443
|
+
total,
|
|
444
|
+
passed,
|
|
445
|
+
failed: total - passed,
|
|
446
|
+
accuracy: total === 0 ? 0 : passed / total,
|
|
447
|
+
per_command,
|
|
448
|
+
in_distribution: {
|
|
449
|
+
n: inN,
|
|
450
|
+
passed: inPassed,
|
|
451
|
+
accuracy: inN === 0 ? 0 : inPassed / inN,
|
|
452
|
+
},
|
|
453
|
+
held_out: {
|
|
454
|
+
n: heldN,
|
|
455
|
+
passed: heldPassed,
|
|
456
|
+
accuracy: heldN === 0 ? 0 : heldPassed / heldN,
|
|
457
|
+
},
|
|
458
|
+
failures,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// ── The REAL LLM router (on-demand — NOT in the deterministic gate) ───────────
|
|
463
|
+
|
|
464
|
+
/** A minimal structural view of a LangChain chat model (mirrors the repo's call shape). */
|
|
465
|
+
interface ChatModelLike {
|
|
466
|
+
invoke(input: unknown): Promise<{ content: unknown }>;
|
|
467
|
+
}
|
|
468
|
+
interface ChatGoogleCtor {
|
|
469
|
+
new (opts: {
|
|
470
|
+
model: string;
|
|
471
|
+
temperature: number;
|
|
472
|
+
maxOutputTokens?: number;
|
|
473
|
+
apiKey?: string;
|
|
474
|
+
}): ChatModelLike;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** Options for the real router. `invoke` is an optional seam for focused testing. */
|
|
478
|
+
export interface RouteViaLLMOptions {
|
|
479
|
+
/** The model id — `config.models.default` or an explicit `--model`. SACRED: no swap. */
|
|
480
|
+
model: string;
|
|
481
|
+
/** Provider credential (defaults to the matching env var). */
|
|
482
|
+
apiKey?: string;
|
|
483
|
+
/** Optional max output tokens. */
|
|
484
|
+
maxOutputTokens?: number;
|
|
485
|
+
/**
|
|
486
|
+
* Optional injected transport — `(systemPrompt, utterance) => raw text`. When
|
|
487
|
+
* absent, the real provider client is constructed. The harness never uses this;
|
|
488
|
+
* it exists so the prompt→parse wiring can be exercised without a live call.
|
|
489
|
+
*/
|
|
490
|
+
invoke?: (systemPrompt: string, utterance: string) => Promise<string>;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/** Coerce a LangChain message `content` (string | content-blocks) to plain text. */
|
|
494
|
+
function extractContentText(content: unknown): string {
|
|
495
|
+
if (typeof content === "string") return content;
|
|
496
|
+
if (Array.isArray(content)) {
|
|
497
|
+
return content
|
|
498
|
+
.map((part) => {
|
|
499
|
+
if (typeof part === "string") return part;
|
|
500
|
+
if (
|
|
501
|
+
part !== null &&
|
|
502
|
+
typeof part === "object" &&
|
|
503
|
+
"text" in part &&
|
|
504
|
+
typeof (part as { text: unknown }).text === "string"
|
|
505
|
+
) {
|
|
506
|
+
return (part as { text: string }).text;
|
|
507
|
+
}
|
|
508
|
+
return "";
|
|
509
|
+
})
|
|
510
|
+
.join("");
|
|
511
|
+
}
|
|
512
|
+
return content === undefined || content === null ? "" : String(content);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/** Detect the provider implied by a model id. Closed + explicit (no fuzzy guessing). */
|
|
516
|
+
function detectProvider(model: string): "google" | "unsupported" {
|
|
517
|
+
return /^gemini/i.test(model) ? "google" : "unsupported";
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* The REAL router: build the routing prompt from `ctx` (mirroring orchestrator.md
|
|
522
|
+
* + routing.yaml), call the chosen model, and parse the answer to the closed set
|
|
523
|
+
* or null. This is the operator's "LLM driven test" — only reached from the CLI,
|
|
524
|
+
* never from the deterministic harness.
|
|
525
|
+
*
|
|
526
|
+
* MODEL INTENT IS SACRED: the model is exactly `opts.model`. If its provider is
|
|
527
|
+
* not wired here, or its credentials are absent, this THROWS — it does NOT swap
|
|
528
|
+
* to another model or provider, and it does NOT retry on a different model. The
|
|
529
|
+
* repo's Google call shape (`@langchain/google-genai` ChatGoogleGenerativeAI,
|
|
530
|
+
* temperature 0) is MIRRORED, not reinvented. The SDK is imported lazily so the
|
|
531
|
+
* deterministic harness never loads a provider.
|
|
532
|
+
*/
|
|
533
|
+
export async function routeViaLLM(
|
|
534
|
+
utterance: string,
|
|
535
|
+
ctx: RoutingContext,
|
|
536
|
+
opts: RouteViaLLMOptions,
|
|
537
|
+
): Promise<RouteResult> {
|
|
538
|
+
const validCommands = validCommandsFromContext(ctx);
|
|
539
|
+
const systemPrompt = buildRoutingPrompt(ctx);
|
|
540
|
+
|
|
541
|
+
// Test/automation seam: an injected transport bypasses the live provider.
|
|
542
|
+
if (opts.invoke) {
|
|
543
|
+
const raw = await opts.invoke(systemPrompt, utterance);
|
|
544
|
+
return { command: parseRoutedCommand(raw, validCommands), raw };
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const provider = detectProvider(opts.model);
|
|
548
|
+
if (provider === "unsupported") {
|
|
549
|
+
throw new Error(
|
|
550
|
+
`routeViaLLM: model '${opts.model}' has no provider wired in this harness ` +
|
|
551
|
+
`(only Google/gemini-* is wired — the creds available for the routing ` +
|
|
552
|
+
`eval). MODEL INTENT IS SACRED: this harness will NOT silently swap to a ` +
|
|
553
|
+
`different model. Pass a supported --model, or wire its provider (mirror ` +
|
|
554
|
+
`the repo's call shape) — never substitute.`,
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const apiKey = opts.apiKey ?? process.env.GOOGLE_API_KEY;
|
|
559
|
+
if (apiKey === undefined || apiKey === "") {
|
|
560
|
+
throw new Error(
|
|
561
|
+
`routeViaLLM: GOOGLE_API_KEY is not set for model '${opts.model}'. Source ` +
|
|
562
|
+
`the env first (set -a && source mutagent-core/.env && source mutagent/.env ` +
|
|
563
|
+
`&& set +a). NOT substituting another provider (model intent is sacred).`,
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Lazy import — the deterministic harness never loads the provider SDK.
|
|
568
|
+
const mod = (await import("@langchain/google-genai")) as unknown as {
|
|
569
|
+
ChatGoogleGenerativeAI: ChatGoogleCtor;
|
|
570
|
+
};
|
|
571
|
+
const client = new mod.ChatGoogleGenerativeAI({
|
|
572
|
+
model: opts.model,
|
|
573
|
+
temperature: 0, // routing is a classification — deterministic decoding
|
|
574
|
+
...(opts.maxOutputTokens !== undefined && { maxOutputTokens: opts.maxOutputTokens }),
|
|
575
|
+
apiKey,
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
const response = await client.invoke([
|
|
579
|
+
["system", systemPrompt],
|
|
580
|
+
["human", utterance],
|
|
581
|
+
]);
|
|
582
|
+
const raw = extractContentText(response.content);
|
|
583
|
+
return { command: parseRoutedCommand(raw, validCommands), raw };
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// ---------------------------------------------------------------------------
|
|
587
|
+
// CLI — the reusable "command to evaluate routing". On-demand, runs the REAL
|
|
588
|
+
// router and prints/writes a SCORED report. NOT part of the `bun test` gate.
|
|
589
|
+
//
|
|
590
|
+
// bun run scripts/eval-routing.ts \
|
|
591
|
+
// [--dataset <path>] [--routing <path>] [--model <id>] \
|
|
592
|
+
// [--report <path>] [--stamp <iso>]
|
|
593
|
+
//
|
|
594
|
+
// Model resolution (model intent sacred): --model wins; else config.models.default
|
|
595
|
+
// from the LOCAL .mutagent/config.yaml; else REFUSE (the harness never picks a model).
|
|
596
|
+
// ---------------------------------------------------------------------------
|
|
597
|
+
|
|
598
|
+
function expandHome(p: string): string {
|
|
599
|
+
if (p === "~" || p.startsWith("~/")) {
|
|
600
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
601
|
+
return path.join(home, p.slice(1));
|
|
602
|
+
}
|
|
603
|
+
return p;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function getFlag(argv: string[], name: string): string | undefined {
|
|
607
|
+
const i = argv.indexOf(`--${name}`);
|
|
608
|
+
if (i >= 0 && i + 1 < argv.length) return argv[i + 1];
|
|
609
|
+
return undefined;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/** Best-effort read of config.models.default for DISPLAY (never throws; null if absent). */
|
|
613
|
+
function readConfigDefault(argv: string[]): string | null {
|
|
614
|
+
const flag = getFlag(argv, "config");
|
|
615
|
+
const configPath = flag ? expandHome(flag) : resolveConfigPath();
|
|
616
|
+
const cfg = loadConfig(path.resolve(configPath));
|
|
617
|
+
if (cfg.ok && cfg.config.global?.models?.default) {
|
|
618
|
+
return cfg.config.global.models.default;
|
|
619
|
+
}
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/** The transparent model decision: WHAT ran, WHERE it came from, and the config default. */
|
|
624
|
+
interface ModelDecision {
|
|
625
|
+
/** The model actually used (model intent is sacred — exactly this, no swap). */
|
|
626
|
+
model: string;
|
|
627
|
+
/** Where `model` came from. */
|
|
628
|
+
source: "--model" | "config.models.default";
|
|
629
|
+
/** config.models.default for transparency (may differ from `model` on an explicit override). */
|
|
630
|
+
configDefault: string | null;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Resolve the model: explicit --model wins (an intentional override, NOT a silent
|
|
635
|
+
* swap), else config.models.default, else REFUSE (the harness never picks a model
|
|
636
|
+
* for you). Always surfaces the config default too, so the report prints BOTH and
|
|
637
|
+
* the model intent is fully transparent.
|
|
638
|
+
*/
|
|
639
|
+
function resolveModel(argv: string[]): ModelDecision {
|
|
640
|
+
const configDefault = readConfigDefault(argv);
|
|
641
|
+
const explicit = getFlag(argv, "model");
|
|
642
|
+
if (explicit !== undefined && explicit !== "") {
|
|
643
|
+
return { model: explicit, source: "--model", configDefault };
|
|
644
|
+
}
|
|
645
|
+
if (configDefault !== null) {
|
|
646
|
+
return { model: configDefault, source: "config.models.default", configDefault };
|
|
647
|
+
}
|
|
648
|
+
throw new Error(
|
|
649
|
+
"eval-routing: no model resolved. Pass --model <id>, or set " +
|
|
650
|
+
"shared.models.default in .mutagent/config.yaml. MODEL INTENT IS SACRED: " +
|
|
651
|
+
"the harness will not pick a model for you.",
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function formatReport(
|
|
656
|
+
report: RoutingReport,
|
|
657
|
+
decision: ModelDecision,
|
|
658
|
+
datasetPath: string,
|
|
659
|
+
): string {
|
|
660
|
+
const pct = (x: number): string => `${(x * 100).toFixed(1)}%`;
|
|
661
|
+
const out: string[] = [];
|
|
662
|
+
out.push("─".repeat(72));
|
|
663
|
+
out.push("MutagenT LLM-driven routing eval — scored report");
|
|
664
|
+
out.push("─".repeat(72));
|
|
665
|
+
// Model intent is SACRED — print BOTH what ran AND the config default, so an
|
|
666
|
+
// explicit --model override is fully transparent (never a silent swap).
|
|
667
|
+
out.push(`model (ran): ${decision.model} [via ${decision.source}]`);
|
|
668
|
+
out.push(`config.models.default: ${decision.configDefault ?? "(none / no .mutagent/config.yaml)"}`);
|
|
669
|
+
if (
|
|
670
|
+
decision.source === "--model" &&
|
|
671
|
+
decision.configDefault !== null &&
|
|
672
|
+
decision.configDefault !== decision.model
|
|
673
|
+
) {
|
|
674
|
+
out.push(
|
|
675
|
+
` note: --model EXPLICITLY overrode the config default ` +
|
|
676
|
+
`'${decision.configDefault}' (intentional, not a silent swap).`,
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
out.push(`dataset: ${datasetPath}`);
|
|
680
|
+
out.push("");
|
|
681
|
+
out.push(`OVERALL ${report.passed}/${report.total} accuracy ${pct(report.accuracy)}`);
|
|
682
|
+
out.push(
|
|
683
|
+
` in-distribution ${report.in_distribution.passed}/${report.in_distribution.n} ${pct(report.in_distribution.accuracy)}`,
|
|
684
|
+
);
|
|
685
|
+
out.push(
|
|
686
|
+
` held-out ${report.held_out.passed}/${report.held_out.n} ${pct(report.held_out.accuracy)} (generalization)`,
|
|
687
|
+
);
|
|
688
|
+
out.push("");
|
|
689
|
+
out.push("per-command:");
|
|
690
|
+
for (const [cmd, s] of Object.entries(report.per_command)) {
|
|
691
|
+
out.push(` ${cmd.padEnd(18)} ${s.passed}/${s.n} ${pct(s.n === 0 ? 0 : s.passed / s.n)}`);
|
|
692
|
+
}
|
|
693
|
+
if (report.failures.length > 0) {
|
|
694
|
+
out.push("");
|
|
695
|
+
out.push(`failures (${report.failures.length}):`);
|
|
696
|
+
for (const f of report.failures) {
|
|
697
|
+
out.push(` "${f.utterance}" expected=${f.expected ?? "null"} got=${f.got ?? "null"}`);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
out.push("─".repeat(72));
|
|
701
|
+
return out.join("\n");
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
async function runCli(argv: string[]): Promise<number> {
|
|
705
|
+
const args = argv.slice(2);
|
|
706
|
+
const datasetPath = path.resolve(
|
|
707
|
+
expandHome(getFlag(args, "dataset") ?? "tests/fixtures/routing-eval/dataset.yaml"),
|
|
708
|
+
);
|
|
709
|
+
const routingPath = path.resolve(expandHome(getFlag(args, "routing") ?? "routing.yaml"));
|
|
710
|
+
const reportPath = getFlag(args, "report");
|
|
711
|
+
const stamp = getFlag(args, "stamp"); // optional injected timestamp (no clock)
|
|
712
|
+
|
|
713
|
+
const decision = resolveModel(args);
|
|
714
|
+
const ctx = loadRoutingContext(routingPath);
|
|
715
|
+
const dataset = loadDataset(datasetPath, { validCommands: validCommandsFromContext(ctx) });
|
|
716
|
+
|
|
717
|
+
const report = await runRoutingEval(
|
|
718
|
+
dataset,
|
|
719
|
+
(utterance, c) => routeViaLLM(utterance, c, { model: decision.model }),
|
|
720
|
+
ctx,
|
|
721
|
+
);
|
|
722
|
+
|
|
723
|
+
console.info(formatReport(report, decision, datasetPath));
|
|
724
|
+
|
|
725
|
+
if (reportPath !== undefined) {
|
|
726
|
+
const payload = {
|
|
727
|
+
...(stamp !== undefined && { stamp }),
|
|
728
|
+
model: decision.model,
|
|
729
|
+
model_source: decision.source,
|
|
730
|
+
config_default: decision.configDefault,
|
|
731
|
+
dataset: datasetPath,
|
|
732
|
+
routing: routingPath,
|
|
733
|
+
report,
|
|
734
|
+
};
|
|
735
|
+
fs.writeFileSync(path.resolve(reportPath), `${JSON.stringify(payload, null, 2)}\n`);
|
|
736
|
+
console.info(`[eval-routing] wrote JSON report → ${reportPath}`);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
return 0;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const isMain =
|
|
743
|
+
typeof import.meta !== "undefined" &&
|
|
744
|
+
(import.meta as unknown as { main?: boolean }).main === true;
|
|
745
|
+
if (isMain) {
|
|
746
|
+
const argv = typeof Bun !== "undefined" ? Bun.argv : process.argv;
|
|
747
|
+
runCli(argv)
|
|
748
|
+
.then((code) => process.exit(code))
|
|
749
|
+
.catch((err: unknown) => {
|
|
750
|
+
process.stderr.write(`[eval-routing] FAIL — ${String(err)}\n`);
|
|
751
|
+
process.exit(1);
|
|
752
|
+
});
|
|
753
|
+
}
|