{
  "scope": ["master...origin/hooks-routing-llm"],
  "meta": {
    "master": "e953d358ab8da0ec6f461192b2c512b91a51cbd9",
    "branch": "origin/hooks-routing-llm (b1f3a31a79ad45004c794ede3a06f6489a07df8a)",
    "mergeBase": "5079ce17",
    "commits": [
      "44b7c91d Work package 1 — Contratti e refactoring",
      "f2560b06 Work package 2 — Metadata semantici",
      "06f8791d Work package 3 — MCP catalog",
      "c6b41ac0 Work package 4 — Ambiguity gate",
      "b1f3a31a Work package 5 — Parser e policy"
    ],
    "filesChanged": 25,
    "insertions": 6066,
    "deletions": 107
  },
  "files": [
    {
      "path": ".gitignore",
      "status": "M",
      "category": "build/install",
      "generated": false,
      "summary": "Adds scripts/hooks/mcp-catalog.json to the ignore list alongside the existing routing-catalog.json entry, marking it as a generated build artifact."
    },
    {
      "path": "docs/analisi-tecniche/TODO/analisi-tecnica-routing-valutatore-semantico-ministral-3b.md",
      "status": "A",
      "category": "doc",
      "generated": false,
      "summary": "3890-line implementation proposal for a FUTURE optional semantic routing evaluator (Ministral 3B via OpenAI-compatible endpoint: Ollama local/cloud, LM Studio). Purely descriptive/design doc; none of its described provider client, cache, circuit breaker, or telemetry schema is implemented in this diff's code."
    },
    {
      "path": "package.json",
      "status": "M",
      "category": "config/policy",
      "generated": false,
      "summary": "test:routing:unit now also runs the two new WP1/WP5 test files; test:routing:catalog now runs build-mcp-catalog.mjs before build-routing-catalog.mjs."
    },
    {
      "path": "scripts/build-mcp-catalog.mjs",
      "status": "A",
      "category": "catalogo",
      "generated": false,
      "summary": "New build script (source) that reads MCP_SERVER_REGISTRY from state-manager.js, validates it, and writes the generated scripts/hooks/mcp-catalog.json (availability always 'unknown' at build time) with a stable catalogFingerprint."
    },
    {
      "path": "scripts/build-routing-catalog.mjs",
      "status": "M",
      "category": "catalogo",
      "generated": false,
      "summary": "Adds --semantic-overrides CLI arg, loads and merges scripts/hooks/routing-semantic-overrides.json onto built skills via mergeSemanticOverrides (validated against skill IDs and MCP_SERVER_REGISTRY ids), stamps semanticMetadataVersion:1 and a catalogFingerprint on the generated catalog."
    },
    {
      "path": "scripts/hooks/catalog-fingerprint.mjs",
      "status": "A",
      "category": "routing-engine",
      "generated": false,
      "summary": "Small pure utility: stableJson() (order-independent JSON serialization) and catalogFingerprint() (sha256 over the catalog minus generatedAt/catalogFingerprint fields), used by both catalog builders."
    },
    {
      "path": "scripts/hooks/mcp-catalog-runtime.mjs",
      "status": "A",
      "category": "catalogo",
      "generated": false,
      "summary": "Loads the generated mcp-catalog.json and merges it with an on-disk ~/.mcp-servers/mcp-availability.json manifest to produce an 'effective' MCP catalog with real per-server availability (configured/unknown/unavailable). No network or provider calls."
    },
    {
      "path": "scripts/hooks/mcp-catalog-schema.mjs",
      "status": "A",
      "category": "schema",
      "generated": false,
      "summary": "Structural validator for the generated MCP catalog shape (version, servers[], allowed fields, riskClass enum, suggestOnly must be true, availability enum)."
    },
    {
      "path": "scripts/hooks/routing-ambiguity-gate.mjs",
      "status": "A",
      "category": "routing-engine",
      "generated": false,
      "summary": "Pure deterministic ambiguity gate (assessAmbiguity/buildAmbiguityCandidates). Decides, from existing deterministic scores/metadata only, whether a prompt is 'ambiguous enough' to warrant a semantic evaluation; explicitly documented and coded to never call an evaluator, MCP, or network service. DEFAULT_AMBIGUITY_CONFIG.enabled = false."
    },
    {
      "path": "scripts/hooks/routing-catalog-schema.mjs",
      "status": "M",
      "category": "schema",
      "generated": false,
      "summary": "Adds SEMANTIC_CATEGORIES/ROUTING_ROLES enums, validateSemanticMetadata, and validateSemanticOverrides; extends validateCatalogV2 to require the 6 semantic fields (semanticSummary, category, capabilities, notFor, compatibleWith, routingRole) per skill once semanticMetadataVersion:1 is set, otherwise metadata stays optional."
    },
    {
      "path": "scripts/hooks/routing-engine.mjs",
      "status": "M",
      "category": "routing-engine",
      "generated": false,
      "summary": "routePrompt() renamed/refactored into buildDeterministicRouting() (routePrompt kept as a backward-compatible alias). Adds a candidateEvidence parameter and emits diagnostics.candidateScores (rawScore, deterministicRank, hardExcluded, lockPriority, matchedSignals, matchedIntentIds) alongside the pre-existing rankedSkills output; scoring/threshold/exclusion/lock logic itself is unchanged."
    },
    {
      "path": "scripts/hooks/routing-evaluator-contract.mjs",
      "status": "A",
      "category": "routing-engine",
      "generated": false,
      "summary": "Pure, dependency-free contract module. Its own header states it 'deliberately has no runtime, provider, filesystem, or MCP dependency'. Provides buildEvaluatorConstraints() (allowlist builder) and parseRoutingEvaluation() (strict whole-payload JSON validator for a hypothetical future evaluator response). No code path actually invokes any evaluator/provider."
    },
    {
      "path": "scripts/hooks/routing-policy-resolver.mjs",
      "status": "A",
      "category": "routing-engine",
      "generated": false,
      "summary": "Pure policy module (resolveRoutingPolicy/resolveHybridRouting). Header states it 'does not parse model output, invoke providers, or render hints'. Given a deterministic result plus an already-parsed evaluation object, applies mode gating (off/shadow/observe/boost-only/hybrid), confidence/relevance thresholds, and protected-skill/lock preservation to optionally boost scores. Never called from the live hook path in this diff (no evaluation is ever produced there)."
    },
    {
      "path": "scripts/hooks/routing-semantic-overrides.json",
      "status": "A",
      "category": "config/policy",
      "generated": false,
      "summary": "SOURCE file (git-tracked, not gitignored), hand-authored per-skill semantic metadata (semanticSummary in Italian, category, capabilities, notFor, compatibleWith, routingRole) consumed by build-routing-catalog.mjs and merged into the generated (gitignored) routing-catalog.json."
    },
    {
      "path": "scripts/hooks/sophia-user-prompt-submit.mjs",
      "status": "M",
      "category": "hook runtime",
      "generated": false,
      "summary": "Refactors buildHints() into buildDeterministicRoutingContext() + renderRoutingHints(), threading catalog/candidateEvidence explicitly instead of via module-global loadCatalog(). Wires in assessAmbiguity()/buildAmbiguityCandidates() and attaches the result as routing.ambiguity, but the ambiguity result is not read anywhere else in this file, not used to alter hints, and not included in the logAnalyticsEventBestEffort() payload, which is byte-for-byte unchanged from master."
    },
    {
      "path": "scripts/install-user-runtime.js",
      "status": "M",
      "category": "build/install",
      "generated": false,
      "summary": "runLocalGenerators() now also runs scripts/build-mcp-catalog.mjs before scripts/build-routing-catalog.mjs during install/regeneration."
    },
    {
      "path": "scripts/runtime/state-manager.js",
      "status": "M",
      "category": "config/policy",
      "generated": false,
      "summary": "Replaces the two parallel MCP_SERVERS_TOOLS/SERVER_FOLDERS maps with a single MCP_SERVER_REGISTRY array (adds semanticSummary, capabilities, riskClass, suggestOnly per server; both old maps are now derived, so old export shape and values are backward-compatible). Adds new functions buildMcpAvailabilityManifest/saveMcpAvailabilityManifest and wires the latter into saveInstallationState(), which now also writes a new persistent state file ~/.mcp-servers/mcp-availability.json on every install/state save."
    },
    {
      "path": "scripts/smoke-codex-hooks.js",
      "status": "M",
      "category": "test",
      "generated": false,
      "summary": "Introduces a PROMPT_ROUTING_MODULES list (adds routing-ambiguity-gate.mjs, mcp-catalog-runtime.mjs, mcp-catalog-schema.mjs) and reuses it for both the portable-root copy step and the catalog-fallback verification step, so the new modules are covered by the existing portable/offline smoke checks."
    },
    {
      "path": "scripts/test-affected.mjs",
      "status": "M",
      "category": "build/install",
      "generated": false,
      "summary": "Behavioral change unrelated to LLM routing itself: package.json is removed from ROOT_TRANSVERSAL_FILES (no longer forces full 'npm test'); adds scripts/build-mcp-catalog.mjs and a tests/routing-*.test.(c|m)?js regex to the classifier so new routing test files map to 'npm run test:routing' instead of being uncovered/duplicated."
    },
    {
      "path": "scripts/test-routing-engine.mjs",
      "status": "M",
      "category": "test",
      "generated": false,
      "summary": "Updates the eval harness to call buildDeterministicRouting/new candidateScores shape consistent with the routing-engine.mjs refactor (not separately inspected line-by-line beyond confirming it targets the renamed API)."
    },
    {
      "path": "scripts/test-user-runtime.js",
      "status": "M",
      "category": "test",
      "generated": false,
      "summary": "Adds a 'Test 0' asserting MCP_SERVER_REGISTRY has 11 entries and exercising buildMcpAvailabilityManifest/saveMcpAvailabilityManifest against a fake $APPDATA/.codex/config.toml; also asserts the generated mcp-catalog.json has version 1 and 11 servers after runLocalGenerators()."
    },
    {
      "path": "tests/routing-evaluator-contract.test.mjs",
      "status": "A",
      "category": "test",
      "generated": false,
      "summary": "Unit tests for buildEvaluatorConstraints/parseRoutingEvaluation: allowlist derivation, acceptance of valid single/compose/none/uncertain payloads, rejection of malformed JSON/unknown fields/out-of-range values/disallowed target ids/oversized payloads. No provider/process is involved anywhere."
    },
    {
      "path": "tests/routing-hooks.test.mjs",
      "status": "M",
      "category": "test",
      "generated": false,
      "summary": "+~170 lines: adds tests for renderRoutingHints hint-limit/metadata behavior, mergeSemanticOverrides + catalogFingerprint stability, equivalence of buildHints vs buildDeterministicRoutingContext+renderRoutingHints, ambiguity-gate config validation and all reason signals (score gap, role overlap, category overlap, composite, soft-exclusion, intent mismatch, skill-mcp split), and buildAmbiguityCandidates MCP-linking/leak checks. Also updates existing scoreSkills() call sites for its new signature."
    },
    {
      "path": "tests/routing-policy-resolver.test.mjs",
      "status": "A",
      "category": "test",
      "generated": false,
      "summary": "Unit tests for resolveRoutingPolicy/resolveHybridRouting: off/shadow/observe stay deterministic; boost-only applies bounded score boosts and respects the minimumRelevance floor; invalid/missing/low-confidence/non-ambiguous/uncertain evaluations all fall back to the deterministic baseline with a specific fallbackReason; locked/explicit/hard-excluded skills cannot be displaced; MCP suggestions never leak tools/command/env fields and are dropped when the MCP isn't 'configured'."
    },
    {
      "path": "tests/test-affected.test.mjs",
      "status": "M",
      "category": "test",
      "generated": false,
      "summary": "Updates expectations to match the test-affected.mjs behavior change: 'root package manifest remains an explicit transversal gate' test is replaced by 'root package manifest is ignored by the affected-test selector'; adds a test that new tests/routing-*.test.mjs files map to npm run test:routing."
    }
  ],
  "pipeline": [
    {
      "stage": "normalizzazione (testo/hotword)",
      "implemented": true,
      "path": "scripts/hooks/prompt-routing-options.mjs (unchanged in this diff) + scripts/hooks/routing-engine.mjs",
      "lines": "routing-engine.mjs:15-38 (normalizeCharacters/normalizeText/tokenize)",
      "notes": "Pre-existing normalization/tokenize pipeline, untouched in substance."
    },
    {
      "stage": "catalogo (skill catalog build/merge)",
      "implemented": true,
      "path": "scripts/build-routing-catalog.mjs; scripts/hooks/routing-semantic-overrides.json; scripts/hooks/routing-catalog-schema.mjs",
      "lines": "build-routing-catalog.mjs:434-455 (loadSemanticOverrides/mergeSemanticOverrides); routing-catalog-schema.mjs:26-95 (validateSemanticOverrides)",
      "notes": "New: semantic overrides (source JSON) are merged into the generated, gitignored routing-catalog.json and validated. Also new: MCP catalog build (scripts/build-mcp-catalog.mjs) producing gitignored scripts/hooks/mcp-catalog.json from state-manager.js's MCP_SERVER_REGISTRY."
    },
    {
      "stage": "segnali (keyword/pattern/context scoring with evidence)",
      "implemented": true,
      "path": "scripts/hooks/sophia-user-prompt-submit.mjs",
      "lines": "scoreSkills(): 626-733; applyAntiPatterns(): 586-624",
      "notes": "scoreSkills now returns { scores, candidateEvidence } instead of a bare Map; every scoring rule (domain keyword, catalog keyword, prompt pattern, repo context, implicit signal, special-rule, anti-pattern) also calls recordEvidence(skillId, signal) to build a matchedSignals[] trail per candidate."
    },
    {
      "stage": "scoring/ranking deterministico",
      "implemented": true,
      "path": "scripts/hooks/routing-engine.mjs",
      "lines": "buildDeterministicRouting(): 178-273 (alias routePrompt(): 275-277)",
      "notes": "Same intent/lock/suppression/exclusion/ranking semantics as master; adds diagnostics.candidateScores (rawScore, deterministicRank, hardExcluded, lockPriority, matchedSignals, matchedIntentIds) as a new, richer output alongside the unchanged rankedSkills/threshold=3 cutoff."
    },
    {
      "stage": "anti-pattern",
      "implemented": true,
      "path": "scripts/hooks/sophia-user-prompt-submit.mjs",
      "lines": "applyAntiPatterns(): 586-624",
      "notes": "Unchanged suppress/deprioritize logic; now also records evidence signals ('anti-routing:suppress:...', 'anti-routing:deprioritize:...', 'anti-routing:handoff:...') consumed later by the ambiguity gate's soft-exclusion-conflict check."
    },
    {
      "stage": "candidate set construction",
      "implemented": true,
      "path": "scripts/hooks/routing-ambiguity-gate.mjs",
      "lines": "buildAmbiguityCandidates(): 157-179",
      "notes": "Builds a combined skill+MCP candidate list from routing.candidateScores and the generated catalog/effective MCP catalog, purely in-memory, no new scoring."
    },
    {
      "stage": "ambiguity gate",
      "implemented": true,
      "path": "scripts/hooks/routing-ambiguity-gate.mjs",
      "lines": "assessAmbiguity(): 123-155; DEFAULT_AMBIGUITY_CONFIG: 15-31",
      "notes": "Fully implemented, pure, deterministic. Computed on every prompt in sophia-user-prompt-submit.mjs (buildDeterministicRoutingContext) and attached to routing.ambiguity, but DEFAULT_AMBIGUITY_CONFIG.enabled=false so assessAmbiguity() short-circuits to {ambiguous:false, reasons:['disabled']} unless a caller explicitly passes an enabling config (only done in tests)."
    },
    {
      "stage": "valutatore LLM (evaluatore semantico esterno)",
      "implemented": false,
      "path": "scripts/hooks/routing-evaluator-contract.mjs (parser only); scripts/hooks/routing-policy-resolver.mjs (policy only)",
      "lines": "n/a — no provider client file exists in this diff",
      "notes": "No code in the diff opens a socket, spawns a process, or calls fetch()/http(s).request toward any LLM provider. Only a local JSON-shape parser/validator (routing-evaluator-contract.mjs) and a policy resolver that consumes an already-produced 'evaluation' object (routing-policy-resolver.mjs) exist. Neither is invoked from sophia-user-prompt-submit.mjs. The intended provider client (Ollama/LM Studio OpenAI-compatible) is described only in the docs/ TODO design doc, not implemented."
    },
    {
      "stage": "merge/override (policy resolution combining deterministic + evaluator)",
      "implemented": true,
      "path": "scripts/hooks/routing-policy-resolver.mjs",
      "lines": "resolveRoutingPolicy(): 155-236",
      "notes": "Implemented and unit-tested as a standalone pure function, but never called from the live hook (sophia-user-prompt-submit.mjs never imports routing-policy-resolver.mjs). Only reachable via tests/routing-policy-resolver.test.mjs."
    },
    {
      "stage": "hint rendering",
      "implemented": true,
      "path": "scripts/hooks/sophia-user-prompt-submit.mjs",
      "lines": "renderRoutingHints(): 903-949; buildExplicitHints(): 951-977",
      "notes": "Same 2-hint cap and rendering rules as master; only reorganized from a single buildHints() into explicit-hints + renderRoutingHints(routing, renderContext) so the (currently unused) hybrid/ambiguity path could later feed it. Output is asserted test-equivalent to the legacy buildHints() for a fixed prompt set."
    },
    {
      "stage": "telemetry",
      "implemented": "partial",
      "path": "scripts/hooks/sophia-user-prompt-submit.mjs",
      "lines": "logAnalyticsEventBestEffort call: ~1072-1080",
      "notes": "The logged 'UserPromptSubmit' event object is unchanged from master — it does not include routing.ambiguity, candidateScores, or any evaluator field. The rich RoutingEvaluator telemetry schema (provider/model/latency/tokens/cache_hit/etc.) is fully specified in the docs TODO file (section 22) but not implemented in scripts/hooks/analytics-hook-log-utils.mjs, which is untouched in this diff."
    }
  ],
  "questions": {
    "a": {
      "answer": "No real external provider/LLM call exists anywhere in the diff. routing-evaluator-contract.mjs's own header states it 'deliberately has no runtime, provider, filesystem, or MCP dependency' and only exports buildEvaluatorConstraints()/parseRoutingEvaluation() (pure JSON validation of a hypothetical response). routing-ambiguity-gate.mjs's own header states 'Pure deterministic ambiguity gate. It never invokes an evaluator, MCP, or network service.' A repo-wide grep of the new/changed files for fetch(/http.request/https.request/child_process/spawn/ollama/ministral finds zero matches; the only spawnSync usages in the branch are in pre-existing, unrelated adapter files (analytics-hook-log-utils.mjs, sophia-antigravity-adapter.mjs, sophia-cursor-adapter.mjs) not touched by this diff.",
      "evidence": [
        {"path": "scripts/hooks/routing-evaluator-contract.mjs", "lines": "1-4"},
        {"path": "scripts/hooks/routing-ambiguity-gate.mjs", "lines": "130 (docstring above assessAmbiguity)"}
      ]
    },
    "b": {
      "answer": "There is no live call path to a valutatore at all, so 'unavailable' cannot currently occur at runtime. But the code that WOULD consume an evaluator result is explicitly fail-closed/deterministic-fallback by design: resolveRoutingPolicy() returns the unmodified deterministic baseline (source:'fallback') whenever evaluation is null/invalid, confidence is below policy.minimumConfidence, decision is 'none'/'uncertain', or a locked/explicit skill would be displaced. Modes 'off'/'shadow'/'observe' always return the deterministic baseline outright. There is no throw/crash path — invalid policy config also falls back rather than erroring.",
      "evidence": [
        {"path": "scripts/hooks/routing-policy-resolver.mjs", "lines": "168-172 (mode off/shadow/observe -> baselineResult)"},
        {"path": "scripts/hooks/routing-policy-resolver.mjs", "lines": "173-179 (not-ambiguous / invalid-evaluation / low-confidence / no-semantic-decision fallbacks)"},
        {"path": "scripts/hooks/routing-policy-resolver.mjs", "lines": "215-219 (protected-skill-displaced fallback)"}
      ]
    },
    "c": {
      "answer": "Yes, two independent flags/config objects exist and both default OFF: (1) DEFAULT_AMBIGUITY_CONFIG.enabled = false and mode:'shadow' in routing-ambiguity-gate.mjs — assessAmbiguity() is invoked unconditionally per prompt in sophia-user-prompt-submit.mjs but immediately short-circuits to {ambiguous:false, reasons:['disabled']} unless a caller passes an overriding config (the live hook never does). (2) DEFAULT_ROUTING_POLICY.mode = 'off' in routing-policy-resolver.mjs, whose resolveRoutingPolicy() is not even wired into the hook in this diff. So both novel behaviors are off-by-default and, for the policy resolver, not yet reachable in production at all.",
      "evidence": [
        {"path": "scripts/hooks/routing-ambiguity-gate.mjs", "lines": "16-18 (enabled: false, mode: 'shadow')"},
        {"path": "scripts/hooks/routing-ambiguity-gate.mjs", "lines": "130-131 (if (!resolved.enabled || resolved.mode === 'off') return {ambiguous:false, reasons:['disabled'], ...})"},
        {"path": "scripts/hooks/routing-policy-resolver.mjs", "lines": "11-19 (mode: 'off')"}
      ]
    },
    "d": {
      "answer": "None implemented in code. No timeout/AbortController, no cache read/write, no circuit-breaker file or state exists anywhere in the diff's source files. The docs TODO file specifies all three in detail (providerTimeoutMs default 1500ms, ~/.mcp-servers/cache/routing-evaluator-circuit.json, cache keyed on prompt+candidates) but these are proposals only, not present in scripts/hooks/*.",
      "evidence": [
        {"path": "docs/analisi-tecniche/TODO/analisi-tecnica-routing-valutatore-semantico-ministral-3b.md", "lines": "227, 242-243 (host timeout 8s, providerTimeoutMs default/max)"},
        {"path": "docs/analisi-tecniche/TODO/analisi-tecnica-routing-valutatore-semantico-ministral-3b.md", "lines": "1977-2053 (circuit breaker + cache design, not code)"}
      ]
    },
    "e": {
      "answer": "Nothing is currently sent to any provider because no provider call exists. What WOULD be sent, per the strict local contract, is bounded: routing-evaluator-contract.mjs's buildEvaluatorConstraints() only ever exposes an allowlist of already-eligible candidate skill IDs (from the ambiguity gate's candidateIds, filtered to non-hardExcluded skills) and configured MCP IDs, plus fixed limits (maxResponseChars 8000, maxSkills 2, maxMcps 1) and the closed reasonCodes enum — no prompt text, file contents, tool schemas, or secrets are part of this object, and routing-policy-resolver.test.mjs explicitly asserts MCP suggestions never leak tools/command/env fields. The docs TODO (section 21, 'Data minimization') separately states the intended request payload for a real evaluator would be a minimized/redacted prompt plus top-K candidates and reason codes, explicitly excluding files/diffs/secrets/DB content — but this redaction/minimization logic is not implemented in code.",
      "evidence": [
        {"path": "scripts/hooks/routing-evaluator-contract.mjs", "lines": "60-79 (buildEvaluatorConstraints)"},
        {"path": "tests/routing-evaluator-contract.test.mjs", "lines": "38 (assert.equal(JSON.stringify(constraints).match(/tool|command|path|env|secret/i), null))"},
        {"path": "tests/routing-policy-resolver.test.mjs", "lines": "133-134 (no tool/command/env leakage assertion on suggestedMcps)"},
        {"path": "docs/analisi-tecniche/TODO/analisi-tecnica-routing-valutatore-semantico-ministral-3b.md", "lines": "2059-2107 (data minimization / redaction design, not implemented)"}
      ]
    },
    "f": {
      "answer": "The single telemetry event already emitted by the hook ('UserPromptSubmit' via logAnalyticsEventBestEffort, unchanged from master) is written best-effort to a local JSONL file under dirname(ANALYTICS_DB_PATH)/hooks/events.jsonl (per the pre-existing analytics-hook-log-utils.mjs path, not modified in this diff) and later scanned into local SQLite by analytics-node. It records matched/controlMatched/routingMatched flags, catalogStatus, the rendered hints (max 2), contextMode/subagentMode/detectedHotwords/reasons, and a minimal repo_context (classification + detected marker names, no paths). It does NOT record routing.ambiguity, candidateScores, or any evaluator/provider field — those fields are computed (buildDeterministicRoutingContext attaches ambiguity to routing.ambiguity) but never read again before the event is logged. A much richer 'RoutingEvaluator' event schema (provider/model/latency/tokens/cache_hit/decision/etc., plus a routing_evaluations SQL table) is fully specified in the docs TODO file but not implemented anywhere in the diff's code.",
      "evidence": [
        {"path": "scripts/hooks/sophia-user-prompt-submit.mjs", "lines": "1072-1080 (logAnalyticsEventBestEffort call, event fields)"},
        {"path": "scripts/hooks/sophia-user-prompt-submit.mjs", "lines": "996-1004 (ambiguity computed, attached to routing.ambiguity, not passed to the log call)"},
        {"path": "docs/analisi-tecniche/TODO/analisi-tecnica-routing-valutatore-semantico-ministral-3b.md", "lines": "2188-2350 (proposed RoutingEvaluator event + adapter + SQL migration, design only)"}
      ]
    },
    "g": {
      "answer": "scripts/hooks/routing-semantic-overrides.json is a SOURCE file: it is git-tracked (git show origin/hooks-routing-llm:... returns content; it is not listed in .gitignore, unlike routing-catalog.json and mcp-catalog.json which the diff explicitly adds/keeps ignored as generated). It contains, per skill id, a hand-authored object with: semanticSummary (Italian-language one-line description), category (enum, e.g. 'validation', 'testing'), capabilities (string array), notFor (string array of anti-scope hints), compatibleWith (array of {kind:'skill'|'mcp', id}), and routingRole (enum: orchestrator/specialist/sidecar/utility). It is read by scripts/build-routing-catalog.mjs at catalog build time and merged onto each skill definition (mergeSemanticOverrides), validated by validateSemanticOverrides() to require exactly the known skill set with no missing/unknown ids.",
      "evidence": [
        {"path": "scripts/hooks/routing-semantic-overrides.json", "lines": "1-90 (version:1, skills.<id>.{semanticSummary,category,capabilities,notFor,compatibleWith,routingRole})"},
        {"path": "scripts/build-routing-catalog.mjs", "lines": "434-455 (loadSemanticOverrides/mergeSemanticOverrides using this file as default --semantic-overrides path)"},
        {"path": ".gitignore", "lines": "37-38 (only routing-catalog.json and mcp-catalog.json are ignored, not routing-semantic-overrides.json)"}
      ]
    },
    "h": {
      "answer": "sophia-user-prompt-submit.mjs: scoreSkills() now takes an explicit `cat` parameter (was implicit via module-global loadCatalog()) and returns {scores, candidateEvidence} instead of a bare Map; matchComposite() likewise now takes `cat` explicitly; buildHints() is decomposed into buildDeterministicRoutingContext() + renderRoutingHints(), with a new buildDeterministicRoutingContext() that additionally builds ambiguity candidates and calls assessAmbiguity(). A dedicated test ('the explicit deterministic context renders equivalently to the backward-compatible wrapper') asserts buildHints() output is byte-identical to the new explicit path for 5 representative prompts, and another test asserts hints stay identical whether or not ambiguityConfig:{enabled:true} is passed. routing-engine.mjs: routePrompt() is renamed to buildDeterministicRouting() with routePrompt kept as a literal delegating alias ('Backward-compatible alias retained for existing hook and test consumers'); the same intents/locks/suppressions/exclusions/threshold=3/ranking logic is preserved, with the only behavior addition being the new diagnostics.candidateScores field. Net effect: both files are refactors for extensibility (explicit dependency injection, exposed candidate evidence) rather than behavior changes to the legacy hint output for prompts where the new ambiguity/evaluator path is not engaged (which is always, given the off-by-default flags).",
      "evidence": [
        {"path": "scripts/hooks/sophia-user-prompt-submit.mjs", "lines": "626 (scoreSkills signature), 903 (renderRoutingHints), 979 (buildDeterministicRoutingContext), 1015 (buildHints wrapper)"},
        {"path": "tests/routing-hooks.test.mjs", "lines": "~63-77 ('the explicit deterministic context renders equivalently...')"},
        {"path": "tests/routing-hooks.test.mjs", "lines": "~79-93 ('ambiguity diagnostics remain non-observable to hint rendering...')"},
        {"path": "scripts/hooks/routing-engine.mjs", "lines": "178 (buildDeterministicRouting), 275-277 (routePrompt alias + comment)"}
      ]
    },
    "i": {
      "answer": "New tests cover: (1) routing-evaluator-contract.test.mjs — constraints/allowlist derivation, strict acceptance/rejection of well-formed vs malformed/oversized/out-of-scope JSON evaluator payloads, decision-semantics checks (single/compose/none/uncertain). (2) routing-policy-resolver.test.mjs — mode gating (off/shadow/observe/boost-only), bounded score boosts, fallback reasons for missing/invalid/low-confidence/non-ambiguous/uncertain evaluation objects, protected-skill/lock/hard-exclusion preservation, MCP suggestion filtering and no-leak assertions. (3) routing-hooks.test.mjs additions — ambiguity gate config validation and every individual ambiguity reason code, MCP-linked candidate building, hint-rendering/legacy-equivalence checks, semantic-overrides merge + fingerprint stability. What is NOT covered anywhere in the diff: there is no test that exercises an actual provider call, a timeout, a network/HTTP error, a circuit-breaker open/closed transition, or a cache hit/miss — because no such code exists yet to test. All 'invalid evaluator output' tests operate on a manually-constructed JS object/string passed directly into parseRoutingEvaluation()/resolveRoutingPolicy(), never on output that came from a real subprocess or HTTP response.",
      "evidence": [
        {"path": "tests/routing-evaluator-contract.test.mjs", "lines": "1-80 (full file)"},
        {"path": "tests/routing-policy-resolver.test.mjs", "lines": "1-160 (full file)"},
        {"path": "tests/routing-hooks.test.mjs", "lines": "~95-197 (ambiguity gate tests)"}
      ]
    },
    "j": {
      "answer": "The doc frames itself as a 'proposta tecnica pronta per implementazione' (technical proposal ready for implementation), dated 17 luglio 2026, NOT as documentation of what WP1-5 already built. Its declared design/intent: the existing deterministic router stays authoritative and unchanged ('deterministic-first'); an optional semantic evaluator ('ranker vincolato', no operational authority) should activate ONLY when a deterministic ambiguity gate detects real ambiguity; the evaluator would receive a minimized prompt plus only the allowed candidate list; output is parsed/validated locally; a deterministic policy resolver applies allowlists/locks/exclusions/thresholds; the renderer still emits the same max-2-hint controlled output; any error at any stage falls back immediately to the deterministic result. Recommended first experiment: a single OpenAI-compatible client covering Ollama local, Ollama Cloud, and LM Studio, with Ministral 3 3B Instruct as the reference model, config/quotas kept external (never hardcoded). Explicit architectural requirements listed: deterministic-first, LLM-only-on-ambiguity, candidate allowlist, hard rules always win, no direct tool execution from the evaluator, no retry on the interactive path, short timeout + circuit breaker, full fallback to existing behavior, shadow mode as phase one, no API keys in generated hook files, mandatory local output validation, and a single OpenAI-compatible transport for all three providers. Sections 2.3 ('Invarianti da preservare') and the closing checklists (e.g. line ~3625: 'Con enabled=false, output e test equivalgono alla baseline') make explicit that WP1-5, as landed, are meant to be exactly this: the deterministic-preserving scaffolding phase, with the evaluator itself, its transport, cache, circuit breaker, and telemetry schema still to be built.",
      "evidence": [
        {"path": "docs/analisi-tecniche/TODO/analisi-tecnica-routing-valutatore-semantico-ministral-3b.md", "lines": "1-8 (title/stato/ambito/data)"},
        {"path": "docs/analisi-tecniche/TODO/analisi-tecnica-routing-valutatore-semantico-ministral-3b.md", "lines": "10-41 (executive summary, pipeline 1-7, decisione architetturale raccomandata)"},
        {"path": "docs/analisi-tecniche/TODO/analisi-tecnica-routing-valutatore-semantico-ministral-3b.md", "lines": "62-90 (invarianti da preservare)"},
        {"path": "docs/analisi-tecniche/TODO/analisi-tecnica-routing-valutatore-semantico-ministral-3b.md", "lines": "3625 ('Con enabled=false, output e test equivalgono alla baseline')"}
      ]
    }
  },
  "merge_risks": [
    "Textual/file overlap with master HEAD e953d358 is effectively zero: master has exactly one commit (e953d358, docs(gpts): add GPT instructions directory) ahead of the branch's merge-base (5079ce17), touching only gpts/*/istruzioni.md, none of which intersects the 25 files in this diff. `git diff --stat 5079ce17...master` confirms only those 5 doc files changed on master's side.",
    "package.json: the branch changes test:routing:unit and test:routing:catalog script strings. Any other concurrent branch touching those same npm script lines would conflict textually, though no such branch exists in current master history.",
    "scripts/runtime/state-manager.js is restructured from two parallel object literals (MCP_SERVERS_TOOLS, SERVER_FOLDERS) into a single MCP_SERVER_REGISTRY array with derived exports; the derived MCP_SERVERS_TOOLS/SERVER_FOLDERS keep identical keys/values (verified by test-user-runtime.js: 'assert.deepEqual(Object.keys(MCP_SERVERS_TOOLS).sort(), MCP_SERVER_REGISTRY.map(...).sort())'), so any other code reading those two exports is unaffected by shape, but a hand-edit to either map on another branch would need to be re-applied to the new registry array instead — a rebase/merge would not auto-resolve that shape change.",
    "New persistent state file: saveInstallationState() now unconditionally calls saveMcpAvailabilityManifest(), writing ~/.mcp-servers/mcp-availability.json on every install/state-save. This is a new on-disk artifact under the user's home directory (not under the repo), so it does not create a git migration risk, but any tooling/tests that assume the set of files under ~/.mcp-servers/ is fixed would need updating; scripts/test-user-runtime.js was updated accordingly in this diff.",
    "scripts/test-affected.mjs behavior change: package.json is removed from ROOT_TRANSVERSAL_FILES, so a bare package.json edit no longer forces a full 'npm test' gate under the affected-test selector — this is a policy change to CI/local test selection, not specific to the LLM routing feature, and could mask a scripts-only regression if another change to test-affected.mjs assumptions lands concurrently.",
    "scripts/hooks/routing-engine.mjs: routePrompt() is retained only as a thin delegating alias to buildDeterministicRouting(); any other branch that still calls routePrompt() directly continues to work, but a branch that patches internals of the old routePrompt() body directly (rather than through the alias) would silently apply to dead code.",
    ".gitignore adds scripts/hooks/mcp-catalog.json as ignored/generated; a branch that accidentally committed that file previously would need it removed from tracking to avoid a stale generated artifact shipping in git."
  ],
  "uncertainties": [
    "scripts/test-routing-engine.mjs (103-line diff) was confirmed to reference the renamed buildDeterministicRouting/candidateScores API but was not read function-by-function line-by-line in this audit; its full behavioral delta versus master is not itemized.",
    "The full 3890-line TODO doc was not read exhaustively line-by-line; sections beyond the ones directly relevant to questions a-j (architecture, invariants, data minimization, telemetry, timeout/circuit-breaker/cache, checklists) were sampled via targeted search rather than fully read.",
    "Whether any other still-open branch in the actual remote (beyond master) touches the same files was not checked — only master vs the branch's merge-base was compared, per the task's stated git context.",
    "It was not independently verified whether scripts/hooks/mcp-catalog.json or scripts/hooks/routing-catalog.json currently exist as stale generated artifacts anywhere in the working tree/other branches that could cause a first-build fingerprint mismatch."
  ],
  "recommended_followups": [
    "If/when a real evaluator client is implemented, verify it actually enforces the constraints object built by buildEvaluatorConstraints() (i.e., that the provider request body is built strictly from allowedSkillIds/allowedMcpIds/reasonCodes and never includes raw catalog/tool metadata), since no such enforcement exists yet to review.",
    "Add integration coverage once a real provider transport exists for: timeout expiry, non-200/HTTP error handling, malformed/oversized response bodies from an actual process boundary (not just hand-built JS objects), and circuit-breaker state transitions.",
    "Wire routing.ambiguity (and eventually evaluator/decision fields) into the logAnalyticsEventBestEffort() payload, or introduce the separate 'RoutingEvaluator' event described in the TODO doc, before enabling ambiguityConfig.enabled in any environment, since telemetry currently has no visibility into gate activity at all.",
    "Confirm resolveRoutingPolicy()/routing-policy-resolver.mjs is intentionally not yet imported by sophia-user-prompt-submit.mjs (i.e., WP5 is scaffolding only) versus a missed wiring step, since its own tests are the only thing currently exercising it."
  ]
}
