{
  "version" : "1.0",
  "compilerVersion" : "0.0.1-0",
  "categories" : {
    "enquiry" : {
      "description" : "Read-only tools. Always safe; no side effects. Free to call without confirmation prompts.",
      "categoryRules" : [ "Control response size with verbosity: \"full\" (default — the complete result), \"summary\" (trim per-item detail — graph/tree tools to depth 1, effective-api to member names), or \"none\" (collapse every list to a <key>Count total). Scan a wide result at summary or none, then re-issue full scoped to the one anchor you care about — cheaper than paging a huge full response into context." ],
      "tools" : [ {
        "name" : "ek9_version",
        "description" : "Returns the EK9 compiler toolchain version string, e.g. '0.0.1-0'.",
        "whenToUse" : "'Which EK9 compiler version is this' is one call with {} - the toolchain version string for a bug report, diagnostic bundle or compatibility check.",
        "whenNotToUse" : [ "The project's OWN package version (from the package directive) - use ek9_query_package_version.", "The full package directive (name, version, deps) - use ek9_query_package.", "Build state (last outcome, freshness) rather than a version string - use ek9_query_build_status." ],
        "examples" : [ "{} -> success with the version string, e.g. \"0.0.1-0\" (SemVer + feature-build suffix).", "{anything:true} -> error \"Unknown parameter for tool 'ek9_version': [anything]\" — strict schema, always call with {}." ],
        "details" : "Pure static read of the compiler-binary version baked in at build time — no workspace, compile phase or symbol tables involved, so no trustLevel is emitted. The value is constant for the lifetime of the binary: call once per session and reuse from conversation history rather than re-invoking. Same value as the CLI 'ek9 -V'.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : { },
          "type" : "object"
        }
      }, {
        "name" : "ek9_help",
        "description" : "Returns the reference page for one keyword, built-in type or topic - hover summary, the full type API for a built-in type, usage examples - or the keyword index when no keyword is given.",
        "whenToUse" : "'What is X' or 'what does String offer' is one call with keyword:'String' - the language reference for one keyword, built-in type or topic. Spelling and case are resolved for you ('stdout' -> 'Stdout'); omit keyword for the index of every page.",
        "whenNotToUse" : [ "How-to or why questions ('how do I write a switch', 'why no break?') - use ek9_ask.", "A construct in THE USER'S workspace (their own class/function/module) - use ek9_query_construct, or ek9_query_effective_api for its full API.", "Priming a session with the whole EK9 paradigm - use ek9_paradigm.", "One error code's meaning (E07235) - use ek9_explain_error." ],
        "examples" : [ "{keyword:'String'} -> the hover summary, then '--- TYPE API (org.ek9.lang) ---' with every constructor, method and operator, then '--- USAGE EXAMPLES ---'.", "{keyword:'stdout'} -> first line \"(Resolved 'stdout' -> 'Stdout')\", then the Stdout page. {keyword:'NoSuchThing'} -> error naming the related topics and the one next call (ek9_ask). {} -> the keyword index." ],
        "details" : "Plain text. Resolution order: exact page, EK9 error code (E + 5 digits, answered with the rich explanation), case-insensitive page ('list' -> List), then the closest page within four edits - a correction is announced on the first line. Operators are looked up by their symbol (keyword:'<=>' or '#?'). A true miss is an error (not an empty success) listing related topics from the concept registry. Static reference content, independent of workspace compile state; the type API is introspected from org.ek9.lang once per server.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "keyword" : {
              "description" : "The keyword, type name, operator symbol or topic (e.g. 'String', 'switch', '<=>', 'E05030'); case and near spellings are resolved. Omit for the index of every page.",
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_explain_error",
        "description" : "Returns the curated rich explanation for one EK9 error code - [DIAGNOSIS], [EXAMPLES] (as ek9_ask 'Q<id>' routes), the first example's source inlined as [WORKED EXAMPLE Q<id>], [ACTION], [SEE ALSO] and, when tooling is mapped, [REMEDIATION].",
        "whenToUse" : "'What does E07235 mean and how do I fix it' is one call with errorCode:'E07235' - the curated diagnosis, action steps and compilable examples. Take the code verbatim from ek9_compile / ek9_query_diagnostics; call once per distinct code before proposing a fix.",
        "whenNotToUse" : [ "What is currently broken in the workspace (the errors themselves) - use ek9_query_diagnostics.", "A keyword, type or topic with no error code involved - use ek9_help.", "Conceptual 'why does EK9 do X?' questions not tied to a code - use ek9_ask (also the fallback when a syntax error carries no code).", "Priming a session with broad EK9 design rationale - use ek9_paradigm." ],
        "examples" : [ "{errorCode:'E07235'} -> the E07235 rich message ([DIAGNOSIS]/[EXAMPLES]/[WORKED EXAMPLE Q<id>]/[ACTION]/[SEE ALSO], plus [REMEDIATION] when tooling is mapped); the first cited example's compilable source is inlined, the others are cited as ek9_ask 'Q<id>'.", "{errorCode:'e7235'} -> first line \"(Resolved 'e7235' -> 'E07235')\", then the page. {errorCode:'E1402'} -> error: 'E1402' is not an EK9 error code (codes are E + 5 digits) - take it verbatim from ek9_compile / ek9_query_diagnostics." ],
        "details" : "Exact lookup against the bundled rich-error corpus after normalisation: case, a missing 'E' and missing leading zeros are corrected and announced (e07235, E7235, 7235 all reach E07235); anything that is not digits after an optional E is a miss. A miss is an ERROR naming what a code looks like and where a real one comes from - syntax errors arrive with no code at all, so nothing invented for them will resolve. Sections beyond [DIAGNOSIS]/[ACTION]/[SEE ALSO] vary by code ([COMMON CAUSES]/[RATIONALE] are optional); [WORKED EXAMPLE Q<id>] inlines the first cited example's whole source when it is 60 lines or fewer (a longer one stays a pointer - ek9_ask 'Q<id>' returns it whole); [REMEDIATION] lists mapped MCP tools in recommended order, or a deliberate no-tool note. Pure static read - no workspace, no compile phase; identical output whether or not the workspace compiles.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "errorCode" : {
              "description" : "The error code, E + 5 digits (e.g. 'E01010', 'E05030'); 'e5030' and '5030' are resolved to 'E05030'",
              "type" : "string"
            }
          },
          "required" : [ "errorCode" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_paradigm",
        "description" : "Returns the constant EK9 paradigm summary (syntax that is NOT C/Java/Python, first-attempt traps, concepts with no analogue elsewhere) and working rules - the smallest priming context for an AI about to write EK9 code - and with withExamples:true the compiled inline examples after it.",
        "whenToUse" : "Prime a fresh AI session before it writes or reasons about EK9 code - one call with {} (withExamples:true adds the compiled examples). The content is constant, so never re-invoke once it is in history.",
        "whenNotToUse" : [ "A specific keyword, type or topic page - use ek9_help.", "How-to / why questions ('how do I write a switch', 'why no break') - use ek9_ask.", "A specific error-code explanation (E07235, ...) - use ek9_explain_error.", "The full forAI.json reference (~42K tokens) - CLI-only (ek9 -h -E5); withExamples:true here is the middle size, and ek9_help / ek9_ask give targeted depth." ],
        "examples" : [ "{} -> the constant paradigm summary and working rules as plain text (~9.7K chars, ~2.5K tokens).", "{withExamples:true} -> the same summary followed by '=== EK9 EXAMPLES (compiled) ===' - forAI.json's inlineExamples block (~55K chars) - the primer the harness measured as -E4x. Called a second time in one thread -> wasted tokens; reuse the first." ],
        "details" : "Returns AiReferenceGenerator.PARADIGM_SUMMARY then WORKING_RULES (~9.7K chars / ~2.5K tokens): the NOT-C/Java/Python NO/YES syntax list (no braces, return, break, null, new; the <-/:=/:=? assignment trio), the measured first-attempt traps (precedence, literals, named arguments, invented names, override, unset fields), the concepts with no analogue elsewhere (the fixed operator set and sameness, tri-state, pure, dispatcher, closed-by-default types, constructs, stream pipelines) and the WORKING RULES for driving the tools. The footer names ek9_help / ek9_ask / ek9_explain_error as the next calls. Independent of workspace compile state; always succeeds. CLI parity: ek9 -h -E4 (withExamples adds forAI.json's inlineExamples, the harness's -E4x).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "withExamples" : {
              "description" : "Append forAI.json's compiled inlineExamples after the summary (default false; ~55K chars more)",
              "type" : "boolean"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_ask",
        "description" : "Returns the best-matching curated Q&A entries: 'match: strong|weak|id' first, then per entry the id, question, category, Oracle tool, the answer and last the compiled EK9 example (capped by default).",
        "whenToUse" : "'How do I ... in EK9' or 'why does EK9 ...' is one call with query:'<the question>' - the curated, compiled Q&A corpus, no workspace needed. Pass the question as free text, or 'Q1043' to fetch one entry by id; the corpus covers idioms, rationale and migration.",
        "whenNotToUse" : [ "A type's methods and operators or one keyword's page ('what does String offer') - use ek9_help.", "A specific error code (E07235, E08100) - use ek9_explain_error.", "Priming a fresh AI session with the EK9 paradigm - use ek9_paradigm.", "A construct in THIS workspace rather than the corpus - use ek9_query_construct / ek9_query_workspace / ek9_query_effective_api; for a workspace error, ek9_query_diagnostics." ],
        "examples" : [ "{query:'how do I write a switch statement'} -> 'match: strong', then per entry 'Q<id>: <question>', 'Category:', 'Oracle tool:' (when one scaffolds it), the answer, then 'EK9 example' capped at 15 lines; entries separated by '---'. An entry with other angles on the same question lists them under 'Same question, other angles:' - one slot per question.", "{query:'Q1043'} -> that one entry, 'match: id', whole answer and whole example (an id fetch is never cut). {query:'quantum entanglement'} -> 'No matching entries for: ...' (a success, not an error) - rephrase around one EK9 concept or ask ek9_help for a type." ],
        "details" : "Plain text. verbosity: 'summary' (default for a free-text search) = the answer's opening paragraph and the first 15 lines of the example; 'full' (default for a Q<id> fetch) = the whole answer, the whole example and its companion sources; 'none' = only the headline lines (id, question, category, Oracle tool). includeMigration:true appends each entry's cross-language migration note where one exists (entries without one are not filtered out). match: 'strong' when at least half of the query's content words appear in the matched question, else 'weak' with a hint; 'id' for a Q<id> fetch. maxResults caps entries (default 3). Static corpus, independent of workspace compile state; a no-match is a successful empty result.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "includeMigration" : {
              "description" : "Append each entry's cross-language migration note where one exists (default false).",
              "type" : "boolean"
            },
            "maxResults" : {
              "description" : "Maximum entries returned (default 3)",
              "type" : "integer"
            },
            "mode" : {
              "description" : "Retained alias: 'summary' = verbosity:'summary', 'detailed' = verbosity:'full', 'migration' = includeMigration:true. Prefer the two parameters it expands to.",
              "enum" : [ "summary", "detailed", "migration" ],
              "type" : "string"
            },
            "query" : {
              "description" : "The question in natural language, or an entry id ('Q1043', '1043')",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Response size: 'summary' (default for a search) = opening answer paragraph + first 15 example lines; 'full' (default for a Q<id> fetch) = whole answer, whole example, companion sources; 'none' = headline lines only.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "query" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_tool_catalogue",
        "description" : "Returns the whole EK9 tool inventory grouped by category - toolCount and toolNames per category plus workflowNames by default; verbosity:'summary' adds each tool's description, rules and IDE precedent, 'full' the JSON schemas.",
        "whenToUse" : "'What tools are there' is one call with {} at session start - every tool's name by category (enquiry/editor/execution) and the workflow names in ~4K. Use verbosity:'summary' for descriptions, rules and IDE precedents; cache it, the catalogue is static within a session.",
        "whenNotToUse" : [ "ONE tool's parameters and schema - use ek9_query_tool_detail (cheaper; takes a name or an intent phrase).", "One named workflow's step sequence - use ek9_query_workflow.", "Re-fetching mid-session - it does not change; reuse the cached response." ],
        "examples" : [ "{} -> status:ok, toolCount, categories.{enquiry,editor,execution}.{toolCount, toolNames[]}, workflowNames[], nextStep.", "{verbosity:'summary', category:'editor'} -> the editor section only: description, categoryRules[], tools[]{name, description, idePrecedent?, rules?, workflows?}. {includeSchemas:true, category:'execution'} -> that section's tools with inputSchema; all 180 schemas at once are over the response ceiling and come back slim with truncated:true and truncationNote first." ],
        "details" : "Pure static read of the in-process McpToolRegistry - no workspace coupling. verbosity: 'none' (default) = names + counts; 'summary' = the slim catalogue (first-sentence descriptions, categoryRules, idePrecedent / rules / workflows when present - OMITTED when absent, so use property-presence); 'full' = with inputSchema. includeSchemas:true lifts any level to 'full'. category narrows to one of enquiry / editor / execution - the only way schemas fit under the 128 KB ceiling. The JSON field is 'rules'; toolCount is emitted at the top and per category. CLI parity: ek9 -h -E8 (slim) / ek9 -h -E7 (full).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "category" : {
              "description" : "Only this category's section",
              "enum" : [ "enquiry", "editor", "execution" ],
              "type" : "string"
            },
            "includeSchemas" : {
              "description" : "Include the JSON Schema for every tool (lifts verbosity to 'full'). Default false.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Response size: 'none' (default) = names + counts (~4K); 'summary' = the slim catalogue with descriptions, rules and workflows; 'full' = with every tool's JSON schema.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_tool_detail",
        "description" : "Returns one tool's entry - status, match (exact|single|ambiguous|none), then whenToUse, whenNotToUse, description, examples and inputSchema - by name, intent phrase or the error code it remediates.",
        "whenToUse" : "'How do I call ek9_x / which tool does Y' is one call with tool:'ek9_x' or an intent phrase ('find a class by name'). Pass errorCode:'E11066' instead for the tool that remediates a diagnostic; the answer is its when-to-use, parameters and schema before you invoke it.",
        "whenNotToUse" : [ "The whole tool inventory - use ek9_query_tool_catalogue.", "A named tool SEQUENCE (release, feature_branch_start) - use ek9_query_workflow.", "An error code's diagnosis rather than its remediating tool - use ek9_explain_error." ],
        "examples" : [ "{tool:'ek9_query_construct'} -> status:ok, match:'exact', tool:{name, whenToUse, whenNotToUse, description, examples, ..., inputSchema, details}.", "{tool:'edit file'} -> match:'exact' (normalised to ek9_edit_file); {tool:'find a class by name'} -> match:'single'; an unclear phrase -> match:'ambiguous' with ranked candidates[] (re-query by name); no hit -> status:not-found, match:'none', nextStep. {errorCode:'E11066'} -> the mapped tool, never a fuzzy guess." ],
        "details" : "Branch on match: 'exact' (a registered name, before or after normalisation - lower case, 'ek9 '/'ek9_' stripped, spaces to '_') and 'single' (fuzzy winner: top score >= 1.5x the runner-up, the sole candidate, or the one tool whose name tokens all appear in the phrase) carry the full tool object; 'ambiguous' carries ranked candidates[] without schemas; 'none' is status:not-found with nextStep. verbosity: 'full' (default) = the JSON schema; 'summary' = parameters[] (name, type, required, enum); 'none' = name, whenToUse, description only. idePrecedent / rules / workflows are omitted when empty. Static read of the in-process registry + BM25F index - works before any user code is parsed.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "errorCode" : {
              "description" : "An EK9 error code (E11066): answers with the tool mapped to remediate it, never a fuzzy guess",
              "type" : "string"
            },
            "tool" : {
              "description" : "Tool name ('ek9_query_construct', 'query construct') or an intent phrase ('find a class by name')",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Response size: 'full' (default) = the JSON schema; 'summary' = parameters[] with name/type/required/enum; 'none' = name, whenToUse, description.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_workflow",
        "description" : "Returns one named multi-tool workflow's ordered steps (tool + one-line reason each), or with no name every workflow with workflowNames first.",
        "whenToUse" : "'What are the steps of the release workflow' is one call with name:'release' - the ordered tool sequence with a reason per step. Omit name for every workflow (workflowNames first).",
        "whenNotToUse" : [ "The tool inventory rather than the named sequences - use ek9_query_tool_catalogue.", "ONE tool's parameters - use ek9_query_tool_detail." ],
        "examples" : [ "{name:'release'} -> status:ok, match:'exact', name, summary, sequence:[{tool, reason}, ...].", "{} -> status:ok, workflowCount, workflowNames[], workflows[] (each with its sequence). {name:'deploy_to_production'} -> status:not-found, match:'none', workflowNames[], nextStep." ],
        "details" : "Reads the in-process static workflow registry - no coupling to workspace compile state, works from any phase. Exact name match only (the names are in workflowNames). A workflow is a recipe, not an executable: walk sequence[] in order, calling each step's tool and handling its failure before the next; every step names a registered tool.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "name" : {
              "description" : "Workflow name (e.g. 'release'); omit for every workflow",
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_list_workflows",
        "description" : "Returns every named multi-tool workflow - workflowCount, workflowNames, then each workflow's summary and full step sequence.",
        "whenToUse" : "'Which workflows exist' is one call with {} - workflowNames first, then every workflow's full step sequence. The registry is static, so call once per session and cache.",
        "whenNotToUse" : [ "One workflow by name - use ek9_query_workflow name:'release' (the same registry, one entry).", "The tool inventory - use ek9_query_tool_catalogue." ],
        "examples" : [ "{} -> status:ok, workflowCount:3, workflowNames:['release', 'feature_branch_start', 'investigate_build_failure'], workflows:[{name, summary, sequence:[{tool, reason}, ...]}, ...].", "After scanning -> walk a chosen workflow's sequence[] directly (no re-fetch needed)." ],
        "details" : "Pure static read of the workflow registry: no compile-state coupling, deterministic at every phase, never fails (an empty registry returns workflowCount:0). Each workflow carries its complete ordered sequence[] of {tool, reason} steps; every step names a registered tool. Identical to ek9_query_workflow called with no name.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : { },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_metric_limits",
        "description" : "Returns EK9's ENFORCED quality-metric limits (coupling, cohesion, complexity, DI, size) - limitCount and limits[] with ruleCode, metricKey, limitKind, comparator, limit and usableMax - from the single source the checkers fire on.",
        "idePrecedent" : "EK9 IDE — quality-gate settings panel (the enforced quality-metric limits)",
        "whenToUse" : "'What limit does EK9 enforce on X, and at what value' is one call with {} - every quality gate with its rule code, comparator and usableMax. Coupling, cohesion, complexity, nesting, statements, DI and size, from the single source the checkers fire on; no workspace needed.",
        "whenNotToUse" : [ "Where a given construct or module SITS against a limit - use ek9_query_complexity / ek9_query_module_coupling / ek9_query_module_cohesion / ek9_query_dependency_injection.", "The prose MEANING of a code (E11016, ...) - use ek9_explain_error." ],
        "examples" : [ "{} -> status:ok, limitCount, note, limits[] one entry per gate: e.g. E11016 moduleCoupling limitKind:'scalar', E11014 lcom4 'per-genus' (byGenus keyed by class / trait / record ...), E11017 moduleCohesion 'compound', E11020 combinedComplexitySize 'ratio'.", "Read a scalar's usableMax -> the largest value that still compiles ('one more crosses')." ],
        "details" : "ENFORCED, not advisory: the compiler emits the entry's ruleCode the instant a value crosses its limit - no warning tier, no bypass. limitKind is one of scalar, per-genus, compound, ratio: scalar carries comparator/limit/usableMax ('>' means limit is the last legal value, '>=' means limit-1 is); per-genus adds a byGenus table keyed by the EK9 construct word (class, trait, record, enumeration, function, abstract-function, component, service), fallback and exempt kinds (LCOM4 exempts record/trait/function); compound fires only when disconnectedGroups>maxGroups AND ratio>maxRatio once minConstructs is reached; ratio fires when a fraction exceeds its threshold (each entry's condition says which). constructCoupling's limit is integer but its MEASURED value may be fractional (by-trait delegation discounts 0.5 each).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : { },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_read_construct",
        "description" : "Answers first: source.text with source.file/startLine/endLine from the compiled symbol-table boundaries (no line guessing), construct {name, fullyQualifiedName, kind}, members[{name, kind, signature, startLine, endLine}] and source.contentHash.",
        "idePrecedent" : "IntelliJ \"Show body of method\" / LSP textDocument/definition",
        "whenToUse" : "You want the source text of one construct or member - one call with symbol:'Shape', 'mod::Shape', 'Shape.computeTotal' or 'Shape.?'; no file or line needed. Anchor file + line only from an IDE cursor.",
        "whenNotToUse" : [ "The WHOLE file, not one construct - use ek9_read_file (path).", "Structure only, no bodies — use ek9_query_construct (declared members/signatures; cheaper) or ek9_query_effective_api (full visible set incl. inherited/trait/defaulted).", "Semantic info (type, set/unset state, mutability) at a cursor, not source text — use ek9_inspect_symbol_at_cursor.", "Keyword search across the workspace — use ek9_search_text." ],
        "examples" : [ "{symbol:'CustomerOrder.computeTotal'} → scope:member; source.text is just that method's body with source.startLine/endLine anchors and source.file.", "{file:'orders/customer_order.ek9', line:67} → scope:construct; resolver walks outward to the enclosing construct and returns its full body + members[]." ],
        "details" : "Ambiguity is keyed on the DEFINING module only — a construct reachable via a references block is not a second definition; when a name is declared in several modules the refusal lists them and the qualified form ('mod::Name') picks one. An overloaded member name returns EVERY overload (scope:'overloads', each with its signature and source); a dispatcher's name returns its entry point; 'Shape.render(Circle)' returns that overload alone. source.contentHash is the whole file's hash for expectedContentHash on ek9_edit_file. Requires the workspace compiled (ek9_compile) at least to symbols_defined; types_resolved gives reliable signatures.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "file" : {
              "description" : "IDE cursor form: the .ek9 file (workspace-relative or absolute inside the workspace). OPTIONAL - a symbol alone finds its file.",
              "type" : "string"
            },
            "includeBody" : {
              "default" : true,
              "description" : "When false, returns signatures only (declaration lines, no bodies).",
              "type" : "boolean"
            },
            "line" : {
              "description" : "1-based line anywhere inside the target with file - OMIT it and pass symbol:'<name>' instead (a model counting lines miscounts).",
              "type" : "integer"
            },
            "lineNumbers" : {
              "default" : false,
              "description" : "When true, each line of source.text is prefixed 'N| ' (the ek9_read_file form). Default false - source.startLine/endLine already anchor the slice.",
              "type" : "boolean"
            },
            "memberName" : {
              "description" : "Narrows a bare construct symbol to ONE member (method / operator / constructor / field name; operators by symbol: ?, <=>, $). 'Shape.member' says the same in one key.",
              "type" : "string"
            },
            "module" : {
              "description" : "Exact module name that picks one declaration when symbol is a bare name declared in several modules; 'mod::Shape' says the same in one key.",
              "type" : "string"
            },
            "symbol" : {
              "description" : "The construct or member: 'Shape' | 'mod::Shape' (when the name is declared in several modules) | 'Shape.computeTotal' | 'Shape.?' (an operator by its EK9 symbol). No file or line needed. With file, the identifier to anchor on there.",
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_read_file",
        "description" : "Answers first: content (numbered 'N| ' when lineNumbers:true), contentHash for expectedContentHash, and for .ek9 the structure outline {module, constructs[{kind,name,startLine,endLine,signature}]} - whole file up to 2,000 lines, truncated:true first when cut.",
        "idePrecedent" : "cat / Read tool (Claude Code) / fs.readFile",
        "whenToUse" : "You know the file path and want its text - one call with path:'orders.ek9'; add lineNumbers:true before any line-anchored edit, startLine/endLine for a slice. The .ek9 structure outline comes free, so you can plan navigation without reading the body.",
        "whenNotToUse" : [ "A SEMANTIC change - go straight to the editor with symbol:'<name>' (ek9_add_method, ek9_change_signature, ek9_rename_symbol, ...), no read first; a TEXT edit anchored on lines - read WITH lineNumbers:true first and quote those numbers into ek9_edit_file.", "You know the construct name but NOT the file path — use ek9_read_construct (symbol:'Name').", "You want a focused single-construct slice, not the whole file — use ek9_read_construct.", "Searching or browsing rather than reading a known file — ek9_search_text (keyword search), ek9_list_files (directory listing), ek9_query_workspace (structure overview without file content)." ],
        "examples" : [ "{path:'src/main/orders/order.ek9'} -> content plus structure {module, constructs:[{kind,name,startLine,endLine,signature}]} and contentHash.", "{path:'src/main/orders/large.ek9', startLine:200, endLine:280} -> content is only those lines; lineCount still reports the file's full count, plus rangeStart/rangeEnd." ],
        "details" : "structure.constructs[].kind is the EK9 word for the construct (class/trait/record/enumeration/constrained-type/function/abstract-function/program/component/service/application/constant/text; dispatchers are just class); non-.ek9 files report the detected language (markdown/json/java/yaml/xml/properties/plaintext) with no structure. The raw read ignores compile success, but the workspace must be initialized via ek9_compile, and the .ek9 overlay needs a compiled workspace (else a degraded-provider warning; raw content still returned). contentHash is the value to pass as expectedContentHash to ek9_write_file / ek9_edit_file. A file over 2,000 lines is cut at 2,000 with truncated:true first and the startLine/endLine call that restores the rest. Hard errors: no workspace roots, path outside workspace (security), not found, directory (use ek9_list_files), binary, invalid range (start>end).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "endLine" : {
              "description" : "1-based last line of a slice (inclusive); clamped to the file's lineCount with a range-clamped warning.",
              "type" : "integer"
            },
            "includeStructure" : {
              "default" : true,
              "description" : "When true (default), .ek9 files include the structure outline {module, constructs[{kind,name,startLine,endLine,signature}]}. Ignored for non-EK9 files.",
              "type" : "boolean"
            },
            "lineNumbers" : {
              "default" : false,
              "description" : "When true, 'content' is returned with each line prefixed by its 1-based number ('15| sayHello() as pure'). Use it before ANY line-anchored call (edit_file line-replace, remove_statements, a 'line' argument) - counting lines in raw text miscounts. For the symbol tools prefer symbol:'<name>' and no line at all.",
              "type" : "boolean"
            },
            "path" : {
              "description" : "File path: workspace-relative (preferred) or absolute inside the workspace. Must exist and be a text file.",
              "type" : "string"
            },
            "range" : {
              "description" : "IDE alias for the flat startLine/endLine pair: {\"startLine\": 100, \"endLine\": 200}.",
              "properties" : {
                "endLine" : {
                  "description" : "1-based end line (inclusive).",
                  "type" : "integer"
                },
                "startLine" : {
                  "description" : "1-based start line (inclusive).",
                  "type" : "integer"
                }
              },
              "required" : [ "startLine", "endLine" ],
              "type" : "object"
            },
            "startLine" : {
              "description" : "1-based first line of a slice (inclusive). With endLine; omit both for the whole file (capped at 2,000 lines).",
              "type" : "integer"
            }
          },
          "required" : [ "path" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_search_text",
        "description" : "Answers first: matchCount, files[], sites['file.ek9:LINE'] (truncated:true first when cut), then matches[{file,line,column,snippet}] - a literal or regex grep across the workspace with EK9-aware exclusions (build outputs, .ek9-cache/, dot-directories).",
        "idePrecedent" : "ripgrep / grep / Find in Files",
        "whenToUse" : "You want every place a piece of TEXT or a regex appears across workspace files - one call with query:'TODO'; add pathPattern:'**/*.ek9' or path to bound it. Comments, strings, README mentions, config keys - when no symbol name or file path is known.",
        "whenNotToUse" : [ "Uses of a NAMED construct or member - use ek9_query_references (symbol:'Type.member', semantic, exact); to find a construct by name use ek9_find_constructs_by_name.", "Searching for a FILE NAME rather than content — use ek9_list_files.", "Searching for compiler errors / warnings — use ek9_query_diagnostics.", "As a 'locate first' pre-step before mutating — editors (e.g. ek9_edit_localized_text, ek9_organize_localized_text) do their own discovery; dispatch straight to them." ],
        "examples" : [ "{query:'processOrder', pathPattern:'**/*.ek9'} → matchCount:7, files:[...], sites:['orders.ek9:12', ...] then matches[{file,line,column,snippet}].", "{query:'TODO[(:]', isRegex:true} → regex matches only; caseSensitive defaults to true for an identifier with an uppercase letter, false for prose." ],
        "details" : "Pure lexical grep — never consults symbol tables or the AST, so identical results regardless of compile state; requires watched roots (run ek9_compile first). Literal mode matches EK9 operators (<=> ? :=?) verbatim with no escaping; regex mode needs a valid java.util.regex.Pattern. caseSensitive defaults to true when the query is an identifier containing an uppercase letter (a symbol name) and false otherwise; pass it explicitly to override. path bounds the root (a directory, or a single file to search only it), pathPattern filters within it (intersection, cannot un-exclude); maxResults default 100 max 1000 - a cut result opens with truncated:true and matchTotal. column is 0-based, the base every EK9 tool uses.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "caseSensitive" : {
              "description" : "Omit it: an identifier with an uppercase letter (a symbol name) is searched case-sensitively, anything else case-insensitively. Set it to override.",
              "type" : "boolean"
            },
            "includeHidden" : {
              "default" : false,
              "description" : "When true, includes dot-files and otherwise-excluded build directories in the scan.",
              "type" : "boolean"
            },
            "isRegex" : {
              "default" : false,
              "description" : "When true, query is parsed as a java.util.regex.Pattern.",
              "type" : "boolean"
            },
            "maxResults" : {
              "default" : 100,
              "description" : "Cap on matches[] (default 100, max 1000). A cut result opens with truncated:true and matchTotal (the true count).",
              "type" : "integer"
            },
            "path" : {
              "description" : "Bounds the search: a workspace-relative directory, or ONE file to search just that file ('m04.ek9'). Defaults to the workspace root.",
              "type" : "string"
            },
            "pathPattern" : {
              "description" : "Optional glob restricting which files are searched, applied relative to path (e.g. \"**/*.ek9\", \"src/main/**\"). Intersects with default exclusions — cannot bring back excluded paths.",
              "type" : "string"
            },
            "query" : {
              "description" : "Literal text (default) or regex pattern (when isRegex:true). Literal mode matches special characters verbatim — no escaping needed for operators like <=> ? :=? * ( ) [ ] \\.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "full (default) returns matches[] records; summary keeps the headline only (files, sites, counts); none collapses the lists to counts.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "query" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_list_files",
        "description" : "Answers first: entryCount and paths[] (workspace-relative, the value ek9_read_file takes), then entries[{path, name, kind, size, lastModified}] - never content; direct children unless pathPattern recurses.",
        "idePrecedent" : "ls / fs.readdir / Glob (Claude Code)",
        "whenToUse" : "You want to know what files exist - one call with no arguments for the workspace root, path:'src' for a directory, pathPattern:'**/*.ek9' to recurse. The discovery primitive when you don't yet know exact filenames.",
        "whenNotToUse" : [ "You want a file's CONTENT, not just its existence/metadata — use ek9_read_file with an entry's path.", "You want keyword search across file CONTENTS — use ek9_search_text.", "You want the logical workspace structure (modules/packages) not a raw filesystem listing — use ek9_query_workspace.", "You want EK9-module-level structure (constructs, deps) not raw files — use ek9_query_module." ],
        "examples" : [ "{} → paths:['README.md', 'src', ...] then entries[{path, name, kind, size, lastModified}] for the direct children of the workspace root.", "{path:'src/test', pathPattern:'**/*Test.ek9'} → every matching test file recursively; chain to ek9_read_file with an entry's path." ],
        "details" : "Default (no pathPattern) lists DIRECT CHILDREN only — recurse via pathPattern:'**/*'; glob mode returns files only, while direct listings also include directories (kind 'directory', size:null). Every entry's path is workspace-relative — the exact value ek9_read_file / ek9_edit_file take. Entries are sorted by path. maxResults defaults 500 (clamped to 5000); a cut listing opens with truncated:true and entryTotal — narrow the glob or raise maxResults. Errors until ek9_compile has established workspace roots; beyond that, compile-state independent.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "includeHidden" : {
              "default" : false,
              "description" : "When true, includes dot-files and otherwise-excluded build directories.",
              "type" : "boolean"
            },
            "maxResults" : {
              "default" : 500,
              "description" : "Cap on entries[] (default 500, max 5000). A cut listing opens with truncated:true and entryTotal.",
              "type" : "integer"
            },
            "path" : {
              "description" : "Directory path: workspace-relative (preferred) or absolute inside the workspace. Defaults to the workspace root.",
              "type" : "string"
            },
            "pathPattern" : {
              "description" : "Optional glob (e.g. \"**/*.ek9\", \"test/**/*Test.ek9\"), applied recursively from path; files only.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "full (default) returns entries[] records; summary keeps paths[] and the counts; none collapses the lists to counts.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_check_requirements",
        "description" : "Answers status (healthy | violations | dangling-anchors) and healthy first, then requirementCount, developedCount, outstandingCount, violations[] (the store is broken), danglingAnchors[] (a @Test names a requirement that does not exist), outstanding[] (the backlog) and unanchoredTests[] (the authoring work-list) - the whole graph, nothing to verify.",
        "idePrecedent" : "Requirement navigator → health view (planned)",
        "whenToUse" : "'Is the requirement graph healthy, and what is still outstanding?' is one call: {} - no cursor, no id; the whole store plus the last compile's @Test anchors.",
        "whenNotToUse" : [ "Reading ONE requirement in depth (title, children, anchors) - use ek9_query_requirement (mode=detail).", "Anchoring a test to a requirement from the work-list — use ek9_anchor_test with the file + testName this tool reports.", "Confirming a requirement is actually FIT (passing/adequate tests) — developed means 'someone claimed it', not correct; use ek9_run_tests and the quality report." ],
        "examples" : [ "{} → status:'healthy' | 'violations' | 'dangling-anchors', healthy, requirementCount, developedCount/outstandingCount, violations[], danglingAnchors[], outstanding[], unanchoredTests[]{test,file,testName}; anchorsResolved:true when a compile was available.", "{includeDeveloped:true} → additionally developed[]{requirement,anchoringTests,ruleAnchored,viaChildren}; verbose on a large graph." ],
        "details" : "Four disjoint findings: violations[] (store broken, fix a file; kind is GraphViolation kebab-cased), danglingAnchors[] (a @Test names a non-existent requirement — most urgent), outstanding[] (nothing anchors it yet — backlog, carries title), unanchoredTests[] (NOT a fault — the authoring work-list, each entry ready for ek9_anchor_test). A requirement is DEVELOPED when a @Test names it, a rule anchor binds it, or every live child is developed (viaChildren separates the last case). anchorsResolved distinguishes '0 anchors' from 'nothing compiled'; errors when no workspace is configured (cannot locate .ek9-ai/requirements/).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "includeDeveloped" : {
              "default" : false,
              "description" : "List every developed requirement with its anchoring tests, not just the counts. Verbose on a large graph.",
              "type" : "boolean"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_requirement",
        "description" : "Answers status (ok | not-found) and requirementId first, then the node(s): each carries authored{title, body, retired, meta} and derived{developed, anchoringTests, parentChain, children} so a computed rollup is never mistaken for a person's assertion; list and outstanding return requirementCount + requirements[], footprint the anchoringTests and reachedSymbols.",
        "idePrecedent" : "Requirement navigator tree + detail pane (planned)",
        "whenToUse" : "'What does requirement X say, and what is under it?' is one call: {mode:'detail', requirementId:'REQ:fraud-screening'}; mode 'outstanding' with no id is the backlog.",
        "whenNotToUse" : [ "Mutating a requirement (title/body/retired) — use ek9_update_requirement (writes are bottom-up; you never set derived children/rollups).", "Creating new requirements — use ek9_create_requirement.", "Blast radius (which requirements reach a given SYMBOL) — starts from a symbol, so use ek9_query_construct_neighbourhood, whose requirements[] axis answers exactly that.", "Checking that requirement references resolve at compile time — that is ek9_check_requirements." ],
        "examples" : [ "{mode:'detail', requirementId:'REQ:fraud-screening'} → one node: authored title/body/retired plus derived developed/anchoringTests/parentChain/children, each labelled.", "{mode:'outstanding'} → live requirements not yet developed (the backlog); {mode:'footprint', requirementId:'REQ:x'} → the call-graph reach of X's anchoring tests." ],
        "details" : "Modes: detail (one node), hierarchy (subtree, optional depth), list (filter by developed/retired/prefix/assignee/titleLike), outstanding (live, not yet developed — the backlog), footprint (call-graph reach of the requirementId's anchoring tests - over-inclusive by construction, reachResolved:false when the call graph is unavailable). 'developed' is always derived and read-only - anchored by a @Test directly or via children, NOT that it is correct or adequately tested. Every response opens with status: 'ok', or 'not-found' from detail/hierarchy for an unknown requirementId; footprint just reports empty anchoringTests.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "assignee" : {
              "description" : "For list: match nodes whose meta.assignee equals this.",
              "type" : "string"
            },
            "depth" : {
              "description" : "For hierarchy: how far down to walk. Omit for the whole subtree; 1 gives the node and its direct children.",
              "type" : "integer"
            },
            "developed" : {
              "description" : "For list: match only developed (true) or only outstanding (false). Omit to match either.",
              "type" : "boolean"
            },
            "mode" : {
              "description" : "Which read to perform.",
              "enum" : [ "detail", "hierarchy", "list", "outstanding", "footprint" ],
              "type" : "string"
            },
            "prefix" : {
              "description" : "For list: match one node kind.",
              "enum" : [ "REQ", "CTRL", "CAP", "CON" ],
              "type" : "string"
            },
            "requirementId" : {
              "description" : "Required for detail, hierarchy and footprint, e.g. REQ:fraud-screening.",
              "type" : "string"
            },
            "retired" : {
              "description" : "For list: match only retired (true) or only live (false). Omit to match either.",
              "type" : "boolean"
            },
            "titleLike" : {
              "description" : "For list: case-insensitive substring of the title or body.",
              "type" : "string"
            }
          },
          "required" : [ "mode" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_render_specification",
        "description" : "Answers status (ok | not-found), requirementId and markdown - the specification document generated from the requirement graph with every section marked authored or derived - plus anchorsResolved saying whether a compile backed the derived sections.",
        "idePrecedent" : "Requirement navigator → Render Specification… preview (planned)",
        "whenToUse" : "'Write me the specification for X' is one call: {requirementId:'CAP:payments'} renders the subtree as markdown from the graph - a root for the exec summary, a leaf for acceptance criteria.",
        "whenNotToUse" : [ "You want the requirement graph structure / traceability, not a rendered doc — use ek9_query_requirement.", "Checking that requirement references resolve — use ek9_check_requirements.", "Authoring or confirming intent (the 'why' bodies this render displays) — use ek9_update_requirement (its confirm-intent mode stamps the 'last confirmed' date).", "PDF / Word / slides — this emits markdown only; pipe the output through pandoc / the docx/pptx/pdf conversion downstream." ],
        "examples" : [ "{requirementId:'CAP:payments'} → {status:'ok', requirementId, profile:'technical', markdown, anchorsResolved:true} - full subtree, derived sections populated because a compiled program was available.", "{requirementId:'CAP:payments', profile:'summary', depth:1} → node + direct children, intent and status only (no anchoring test names); an unknown id is status:'not-found'." ],
        "details" : "A view, never a stored source: the requirement nodes are the truth, the markdown is disposable and must not be persisted as source. Every render visibly separates derived (what the code provably does, always fresh) from authored (recorded intent) — that separation is the anti-staleness guarantee. Top-level anchorsResolved is true only when a compiled program was available; when false, derived sections are absent (not wrong) so 'nothing anchors this' is distinguishable from 'nothing compiled'.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "depth" : {
              "description" : "How far down to disclose. Omit for the whole subtree; 1 gives the node and its direct children.",
              "type" : "integer"
            },
            "profile" : {
              "default" : "technical",
              "description" : "technical names the anchoring tests; summary gives intent and status only.",
              "enum" : [ "technical", "summary" ],
              "type" : "string"
            },
            "requirementId" : {
              "description" : "Where to cut the tree, e.g. CAP:payments. Render from a root for the whole picture.",
              "type" : "string"
            }
          },
          "required" : [ "requirementId" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_find_unused_code",
        "description" : "Answers first: deadNames and deadByOwner (owner -> its dead members), complete workspace-wide - then each finding classified.",
        "idePrecedent" : "Code → Inspect Code → Dead Code (IntelliJ)",
        "whenToUse" : "'What can I delete' / 'which methods of X are never called' is one call, no arguments - every construct, method and constant nothing reaches, by owner.",
        "whenNotToUse" : [ "Checking usage of ONE specific symbol, not a whole-workspace scan - use ek9_query_references.", "Actually removing a construct once found unused - use ek9_safe_delete.", "You want compiler errors/warnings (incl. unused-parameter E08091/E11018), not the reachability scan - use ek9_query_diagnostics.", "You want the inventory of EXISTING constructs, not the unused subset - use ek9_query_workspace." ],
        "examples" : [ "{} → scanCount:3, findings classified production-dead/test-helper/dead-test, actionableCount:2; chain each 'delete'/'relocate-to-test' finding onwards.", "{excludePublic:true} on a library → drops intentional public API, leaving truly dead callables; empty findings ⇒ workspace is clean." ],
        "details" : "Covered kinds: 'construct' (dead top-level functions) + 'method' (dead methods/constructors); 'field'/'reference' return empty+note (not yet wired), 'parameter' is compiler-owned (E08091/E11018). Each finding carries reachability (dead|production-dead|test-helper|dead-test|untested-public-api), recommendation (delete|relocate-to-test|none|add-test), definedIn, reachedBy, reason and ONE nextStep (for 'delete' the literal ek9_safe_delete(symbols:[...]) call); a dead callable still called names its callers in calledOnlyFrom - each itself dead, deleted with it by that nextStep; library-aware - with no production roots (programs/service operations) the tests are the root set, while application mode adds ungroundedTests[] (@Test programs whose closure never touches production code a release runs). Requires the workspace to reach PRE_IR_CHECKS (where the call graph is built); below that the tool errors rather than guessing.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "among" : {
              "description" : "Names to answer FOR: 'which of these ...' - the name lists shrink to these and amongUnmatched names the ones that are not in the answer. Bare names, 'mod::Type' or 'Type.member'; a MODULE name stands for every construct in it ('which in scale.m07 do not ...' = amongUnmatched).",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "excludePublic" : {
              "description" : "When true, excludes public-visibility findings (intentional library API). Default false. Useful when scanning a library.",
              "type" : "boolean"
            },
            "fileFilter" : {
              "description" : "Optional source-file substring to scope the scan. Omit to scan all source.",
              "type" : "string"
            },
            "kind" : {
              "description" : "Filter by what kind of unused code to find. Default 'all'. 'construct' = dead top-level functions; 'method' = dead members (methods/constructors); 'constant' = a 'defines constant' entry nothing references (each finding carries the ek9_safe_delete call). 'field'/'reference' are not yet covered (empty + note); 'parameter' is owned by the compiler (E08091/E11018).",
              "enum" : [ "all", "construct", "method", "constant", "field", "reference", "parameter" ],
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control — see EK9_TOOL_SPECIFICATIONS.md §1.6. full (default) returns the complete result; summary trims per-item detail (call_graph to its headline names and sites, effective-api to member names); none collapses lists to counts. A very large response auto-degrades with an explicit `truncated` marker.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_find_inline_candidates",
        "description" : "Answers candidates[] (file, line, name, tier worth-inlining | safe, useCount) ranked slop-first, with counts{} - every local '<-' declaration in scope examined.",
        "idePrecedent" : "Code → Inspect Code → 'Local variable is redundant' (IntelliJ)",
        "whenToUse" : "'Which local temps add nothing over their initialiser' is one call, no arguments (fileFilter or module to scope it) - an empty list is a convergence signal.",
        "whenNotToUse" : [ "Applying the inline - use ek9_inline_variable to perform each transform.", "Finding callables to DELETE (dead code) - use ek9_find_unused_code (the delete mirror).", "A local with NO references - this tool skips those; use ek9_safe_delete instead." ],
        "examples" : [ "{fileFilter:'Calc.ek9'} → candidates ranked worth-inlining first, e.g. line 16 tag<-\"row\" tier:'worth-inlining' useCount:1.", "{module:'inline.scan'} → counts{worthInlining,safe,total,filesScanned,localsExamined}; empty candidates ⇒ nothing to inline." ],
        "details" : "Classified with VariableInliner.classify(), the SAME rules as the ek9_inline_variable transform, so every finding is actionable. Two tiers: 'worth-inlining' = a trivial initialiser (literal / bare name / dotted access, no call or operator) used EXACTLY once - a redundant temp; 'safe' = mechanically inlinable but the name may still document intent (non-trivial single use, a pure value reused, or trivial-but-multi-use, where inlining can trip magic-literal E11065). Never surfaced: control-flow guards, reassigned locals, multi-line initialisers, locals with no references, and call-bearing initialisers used more than once; needs only a PARSED workspace (run ek9_compile first), no call graph.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "fileFilter" : {
              "description" : "Optional case-insensitive substring on the source path to scope the scan (per-file or per-directory). Omit to scan all source.",
              "type" : "string"
            },
            "module" : {
              "description" : "Optional exact module name to scope the scan (per-module). Omit to scan all modules.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control — see EK9_TOOL_SPECIFICATIONS.md §1.6. full (default) returns the complete result; summary trims per-item detail (call_graph to its headline names and sites, effective-api to member names); none collapses lists to counts. A very large response auto-degrades with an explicit `truncated` marker.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_translation_completeness",
        "description" : "Answers status, translationDebtCount and translationDebt[] (every {textType, locale, method} still needing translation) first, then the per-(text type, locale, method) status grid (translated | stubbed | missing-error) and summary totals - the whole module, nothing to verify.",
        "idePrecedent" : "NEW IDE surface — translation completeness panel (Surface 3 of ek9_edit_localized_text); read-only grid showing translated / stubbed / missing per (text type, locale, method)",
        "whenToUse" : "'What still needs translating?' is one call: {} lists every {textType, locale, method} still placeholder-stubbed in the module's i18n text, no file needed.",
        "whenNotToUse" : [ "You just wrote via ek9_edit_localized_text and its response already carries translationDebt[] - re-querying is redundant.", "You want full method bodies, not just status — use ek9_query_construct on the text type.", "You want to grep the literal placeholder marker text — use ek9_search_text; this tool returns structured status, not raw matches.", "You need to CLEAR an orphaned //AI: marker whose body is already translated — feed the method's placeholderMarker line to ek9_remove_comments." ],
        "examples" : [ "{module:'com.acme.app', localeFilter:['ja_JP']} -> textTypes[] with per-method translated/stubbed status + summary.perLocale.ja_JP.percentTranslated + translationDebt[] of the outstanding ja_JP tuples.", "{} -> whole-module report; summary.stubbedCount==0 means the module is fully translated." ],
        "details" : "Method status: `translated`, `stubbed` (the marker comment above the body matches placeholderPattern — the body literal is NOT the discriminator, an empty \"\" body alone is not stubbed), or `missing-error` (E07150: method exists in other locales but not this one — transient mid-edit, 0 in a clean workspace). Text-type-per-locale status: complete / partial / stubbed / missing-incomplete / absent-by-design (a text type validly living in only some locales — excluded from percent denominators). translationDebt[] is {textType, locale, method, params} in that field order, LOCKED identical to ek9_edit_localized_text; unknown filter names surface in warnings[] and are skipped, not errored.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "includeMethodBodies" : {
              "default" : false,
              "description" : "When true, each method entry includes its current body (truncated to 200 chars).",
              "type" : "boolean"
            },
            "localeFilter" : {
              "description" : "Limit results to specific locales. Default: all locales discovered in the module.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "module" : {
              "description" : "The module to operate on. Default: the first module that defines any text type; supply it when several modules carry i18n.",
              "type" : "string"
            },
            "placeholderPattern" : {
              "description" : "Marker-detection regex applied to trimmed source lines. Default: literal match for `//AI: translate the message` (the marker emitted by LocalizedTextWriter).",
              "type" : "string"
            },
            "textTypeFilter" : {
              "description" : "Limit results to specific text type names. Default: all text types in the module.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_git_status",
        "description" : "Answers first: summary, branch, ahead/behind, isClean, then stagedFiles / modifiedFiles / untrackedFiles / deletedFiles / conflictFiles names and changedFileCount, then the per-file records with change words.",
        "idePrecedent" : "git status / Git Source Control panel",
        "whenToUse" : "'What have I changed, what is staged, what branch am I on?' - one call, no arguments; path narrows to a subtree. The pre-commit and pre-push check, and the first call when picking up a workspace.",
        "whenNotToUse" : [ "the content of the changes - use ek9_git_diff.", "commit history - use ek9_git_log.", "per-line authorship of a file - use ek9_git_blame.", "compiler errors and warnings - use ek9_query_diagnostics." ],
        "examples" : [ "{} -> summary:'main, 2 ahead; 1 staged, 1 modified, 1 untracked', branch:'main', isClean:false, stagedFiles/modifiedFiles/untrackedFiles names, then the records.", "{path:'src/main/orders', includeUntracked:false} -> the same, scoped to that subtree with untracked files omitted." ],
        "details" : "Headline first: summary, branch (null on a detached HEAD, with detachedAt), upstream with ahead/behind, isClean, changedFileCount and the name arrays stagedFiles / modifiedFiles / untrackedFiles / deletedFiles / conflictFiles. Then the records: staged / modified / deleted / conflicts entries are {file, change} with change one of modified / added / deleted / typechange / conflict; renamed / copied entries are {oldPath, newPath, change, similarity}. verbosity:'summary' keeps the names and drops the records.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "includeIgnored" : {
              "description" : "Include .gitignore-matched files. Default false.",
              "type" : "boolean"
            },
            "includeUntracked" : {
              "description" : "Include untracked files. Default true.",
              "type" : "boolean"
            },
            "path" : {
              "description" : "Workspace-relative file or directory to scope the status to. Default: the whole workspace.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control — see EK9_TOOL_SPECIFICATIONS.md §1.6. full (default) returns the complete result; summary trims per-item detail (call_graph to its headline names and sites, effective-api to member names); none collapses lists to counts. A very large response auto-degrades with an explicit `truncated` marker.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_git_diff",
        "description" : "Answers first: summary, fileCount, additions/deletions and changedFiles names, then per-file hunks with fromLine/toLine and the unified-diff text (dropped at verbosity:'summary').",
        "idePrecedent" : "git diff / Diff view",
        "whenToUse" : "'What did I change?' - one call, no arguments for the working tree; scope:'staged', ref:'<sha>' for one commit, or from/to for a range. Narrow to a file or directory with path.",
        "whenNotToUse" : [ "the list of changed files with their state - use ek9_git_status.", "commit messages, authors and dates - use ek9_git_log.", "per-line authorship of a file - use ek9_git_blame.", "a file's current content - use ek9_read_file." ],
        "examples" : [ "{} -> summary:'2 files changed, +7 -1 (working tree vs HEAD)', changedFiles, then files[] with hunks.", "{ref:'a3f2c1b8'} -> that one commit against its parent; {scope:'range', from:'v1.2.0', to:'HEAD'} -> the range; an empty scope is status:'empty', not an error." ],
        "details" : "Headline first: summary, scope, fromRef/toRef, fileCount, additions, deletions and the changedFiles names; then files[] as {file, change, additions, deletions, hunks[]} with change one of modified / added / deleted / renamed / copied and each hunk carrying fromLine/fromCount/toLine/toCount plus the unified-diff text. verbosity:'summary' keeps the per-file counts and drops the hunks. ref is the one-commit shorthand for scope:'commit' + from.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "contextLines" : {
              "description" : "Unchanged lines around each hunk. Default 3.",
              "type" : "integer"
            },
            "from" : {
              "description" : "Start ref for scope:'range' (with to), or the commit for scope:'commit'.",
              "type" : "string"
            },
            "path" : {
              "description" : "Workspace-relative file or directory to narrow the diff to. Default: everything.",
              "type" : "string"
            },
            "ref" : {
              "description" : "One commit to show (sha, tag, HEAD~1) - shorthand for scope:'commit' with from.",
              "type" : "string"
            },
            "scope" : {
              "description" : "What to compare. Default working (working tree vs HEAD); staged (index vs HEAD); commit (one commit vs its parent, needs ref or from); range (from..to).",
              "enum" : [ "working", "staged", "range", "commit" ],
              "type" : "string"
            },
            "to" : {
              "description" : "End ref for scope:'range'.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control — see EK9_TOOL_SPECIFICATIONS.md §1.6. full (default) returns the complete result; summary trims per-item detail (call_graph to its headline names and sites, effective-api to member names); none collapses lists to counts. A very large response auto-degrades with an explicit `truncated` marker.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_git_log",
        "description" : "Answers first: commitCount and commitTotal with a truncation note, then commits (sha, author, date, subject, body, parents; files with includeFileList:true).",
        "idePrecedent" : "git log / Git History view",
        "whenToUse" : "'What commits touched this file, since when, by whom?' - one call; path, author, since/until, from/to and maxResults are all optional. Pass allBranches:true to log every ref in topological order for a graph view.",
        "whenNotToUse" : [ "who wrote a particular line - use ek9_git_blame.", "the content of a change - use ek9_git_diff with ref:'<sha>'.", "the current working-tree state - use ek9_git_status." ],
        "examples" : [ "{maxResults:20} -> summary:'20 of 340 commits on HEAD (truncated)', commitCount:20, commitTotal:340, truncationNote, then commits[].", "{path:'src/main/orders/order.ek9', includeFileList:true} -> the commits touching that file, each with files[]; chain a sha into ek9_git_diff ref:'<sha>' for content." ],
        "details" : "A cut result opens with truncated:true and a truncationNote naming the call that restores it; then status, summary, branch (null with allBranches:true), commitCount (commitTotal only when truncated), filtersApplied echoing every filter sent, and commits[] as {sha, author, email, date, subject, body, parents} with 8-hex shas, plus files[] when includeFileList:true. from/to must be paired and AND-compose with since/until. verbosity:'summary' drops body and files from each commit.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "allBranches" : {
              "description" : "Log every ref in topological order (the full DAG) instead of one branch. Default false.",
              "type" : "boolean"
            },
            "author" : {
              "description" : "Author name or email substring.",
              "type" : "string"
            },
            "branch" : {
              "description" : "Branch or ref to log from. Default: the current HEAD.",
              "type" : "string"
            },
            "from" : {
              "description" : "Range start ref (pair with to).",
              "type" : "string"
            },
            "includeFileList" : {
              "description" : "Attach files[] to each commit. Default false.",
              "type" : "boolean"
            },
            "maxResults" : {
              "description" : "Most commits to return. Default 50, limit 1000.",
              "type" : "integer"
            },
            "path" : {
              "description" : "Workspace-relative file or directory - only commits touching it.",
              "type" : "string"
            },
            "since" : {
              "description" : "Commits on or after - a date (2026-09-01) or git's relative form ('2 weeks ago').",
              "type" : "string"
            },
            "to" : {
              "description" : "Range end ref (pair with from).",
              "type" : "string"
            },
            "until" : {
              "description" : "Commits on or before - a date (2026-09-01) or git's relative form ('yesterday').",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control — see EK9_TOOL_SPECIFICATIONS.md §1.6. full (default) returns the complete result; summary trims per-item detail (call_graph to its headline names and sites, effective-api to member names); none collapses lists to counts. A very large response auto-degrades with an explicit `truncated` marker.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_git_blame",
        "description" : "Answers first: lineCount and the range, then one record per line (line, sha, author, email, date, subject, text); verbosity:'summary' collapses them to same-commit runs.",
        "idePrecedent" : "git blame / Annotate",
        "whenToUse" : "'Who wrote this line, in which commit?' - one call with path; startLine/endLine narrow the slice, ref blames an older revision. The regression-hunting primitive.",
        "whenNotToUse" : [ "the commits that touched the file - use ek9_git_log.", "the content of one commit - use ek9_git_diff with ref:'<sha>'.", "a file's current content with no authorship - use ek9_read_file." ],
        "examples" : [ "{path:'src/main/orders/order.ek9'} -> summary:'42 lines (1-42) of ... from 3 commits', lineCount:42, then lines[] as {line, sha, author, email, date, subject, text}.", "{path:'order.ek9', startLine:80, endLine:95} -> just that slice; many rows sharing one sha are one commit - chain ek9_git_diff ref:'<sha>' for its content." ],
        "details" : "Headline first: status, summary, path, ref (HEAD unless given), lineCount, commitCount (distinct shas), rangeStart/rangeEnd; then lines[]. verbosity:'summary' collapses consecutive same-commit lines into runs[] {fromLine, toLine, sha, author, date, subject} without the text. Refusals: a file git does not track (file-not-tracked), a binary file, an unknown ref, an inverted range.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "endLine" : {
              "description" : "1-based last line of the slice, inclusive (with startLine).",
              "type" : "integer"
            },
            "path" : {
              "description" : "Workspace-relative path of a tracked file.",
              "type" : "string"
            },
            "range" : {
              "additionalProperties" : false,
              "description" : "IDE alias: the same slice as one object {startLine, endLine}.",
              "properties" : {
                "endLine" : {
                  "type" : "integer"
                },
                "startLine" : {
                  "type" : "integer"
                }
              },
              "type" : "object"
            },
            "ref" : {
              "description" : "Commit, tag or branch to blame at. Default HEAD.",
              "type" : "string"
            },
            "startLine" : {
              "description" : "1-based first line of the slice to blame (with endLine). Default: the whole file.",
              "type" : "integer"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control — see EK9_TOOL_SPECIFICATIONS.md §1.6. full (default) returns the complete result; summary trims per-item detail (call_graph to its headline names and sites, effective-api to member names); none collapses lists to counts. A very large response auto-degrades with an explicit `truncated` marker.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "path" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_git_branch",
        "description" : "Answers first: current, branchCount and branchNames, then per-branch records (name, current, upstream, ahead/behind, gone, sha).",
        "idePrecedent" : "git branch / Branches",
        "rules" : [ "Orient before ek9_git_switch: 'current' says where HEAD is, and branchNames says whether a branch exists (switch to it) or not (create:true)." ],
        "whenToUse" : "'Which branches exist, what am I on?' - one call, no arguments; pattern:'feature/*' filters, includeRemote:true adds remote-tracking branches.",
        "whenNotToUse" : [ "moving to or creating a branch - use ek9_git_switch.", "the working-tree state of the current branch - use ek9_git_status.", "commit history - use ek9_git_log." ],
        "examples" : [ "{} -> summary:'2 branches, on main', current:'main', branchNames:['develop','main'], branches:[{name:'main', current:true, upstream:'origin/main', ahead:0, behind:0, sha:'9a7b8e7b'}, ...].", "{includeRemote:true, pattern:'feature/*'} -> only the matching local and remote-tracking branches." ],
        "details" : "Headline first: status, summary, current, branchCount and branchNames; then branches[] as {name, current, upstream, ahead, behind, gone, sha} - ahead/behind only with an upstream, gone:true when that upstream no longer exists, sha the 8-hex short form shared with ek9_git_log and ek9_git_blame. verbosity:'summary' keeps the names and drops the records.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "includeRemote" : {
              "description" : "Also list remote-tracking branches (origin/...). Default false.",
              "type" : "boolean"
            },
            "pattern" : {
              "description" : "Glob (* and ?) matched against the short branch name, e.g. 'feature/*'. Default: every branch.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control — see EK9_TOOL_SPECIFICATIONS.md §1.6. full (default) returns the complete result; summary trims per-item detail (call_graph to its headline names and sites, effective-api to member names); none collapses lists to counts. A very large response auto-degrades with an explicit `truncated` marker.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_check_package_dependency_cycles",
        "description" : "Answers with hasCycles, cycleCount and the cycles (closed package paths with their edges) across every package declared in the workspace, plus packagesAnalysed and sccCount.",
        "idePrecedent" : "Package Dependency Cycle Detector",
        "whenToUse" : "'Do any packages in this workspace depend on each other in a loop' is one call with no arguments. The pre-release check: a cross-package cycle makes versioning incoherent and blocks independent release.",
        "whenNotToUse" : [ "The full dependency tree, who brings in what - use ek9_query_dependency_tree.", "Cycles between MODULES inside one package - use ek9_check_module_cycles.", "DI wiring cycles (compile error E08190) - use ek9_query_diagnostics." ],
        "examples" : [ "{} -> {status:'ok', hasCycles:false, cycleCount:0, packagesAnalysed:3, scope:'workspace-wide'} - release-eligible from a cycle perspective.", "{} -> {status:'ok', hasCycles:true, cycleCount:1, cycles:[{description:'com.acme.orders -> com.acme.shared -> com.acme.orders', edgeCount:2, ...}]} - break the loop before publishing." ],
        "details" : "Needs a compiled workspace: status:'not-compiled' with nextStep otherwise. Only edges whose target package is also in the workspace are walked (scope: 'workspace-wide'). cycles[].modules uses the closed-path convention - the first package is repeated at the end (['A','B','A'] means A->B->A), so edgeCount, not the array length, is the true edge count; cycles[].edges attribute each hop (from/to are package names, via the from-package's source files). sccCount deduplicates cycles by package set (one per strongly connected component) and is > 0 iff hasCycles.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : { },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_package",
        "description" : "Answers first: status, packageCount, then packages[] {name, version, description, license, tags, publishable, deps, devDeps, excludeDeps, capabilities, sourceLocation} - declared, not resolved.",
        "idePrecedent" : "View pom.xml / package.json (most IDEs)",
        "whenToUse" : "'What does the package directive say - name, version, deps, capabilities, publishable' is one call with no arguments.",
        "whenNotToUse" : [ "only the version string (use ek9_query_package_version, or ek9_version for the EK9 compiler binary's version)", "the RESOLVED transitive dependency tree, not the declared list (use ek9_query_dependency_tree)", "module-level (intra-package) imports, not package-level deps (use ek9_query_module_dependencies)", "MODIFYING deps (use ek9_add_dependency / ek9_remove_dependency)" ],
        "examples" : [ "{} → {status:'ok', packageCount:1, packages:[{name:'com.acme.orders', version:'2.1.0-3', deps:[...], capabilities:['network'], publishable:true, sourceLocation:{...}}]}", "workspace with no 'defines package' block → {status:'empty', packageCount:0, packages:[]} (not an error)." ],
        "details" : "Each entry carries name, version, description, license, tags, publishable, deps/devDeps/excludeDeps (declared, NOT resolved), capabilities (compare against actual use via ek9_query_capability_usage), and sourceLocation {file, line} for jump-to-edit. Works at any phase — the directive is read from source as soon as parsing completes; a module whose re-parse fails is silently skipped, not errored. The snapshot is static per session unless the directive is edited — cache and reuse.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : { },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_name_clashes",
        "description" : "Answers first: status, name, fullyQualifiedName, clashFree, donorLocals, renames (one globally-safe fresh name per clashing local), callSiteCount, then callSites[] with per-site clashes and the summary line.",
        "idePrecedent" : "Refactor → Inline (variable-capture pre-check)",
        "whenToUse" : "'Which of this callable's locals would collide if it were inlined at its call sites' is one call with symbol:'Type.method' - no file or line needed.",
        "whenNotToUse" : [ "performing the inline itself (use ek9_inline_function)", "applying a reported rename (use ek9_rename_symbol on each donor local's declaration, then inline)", "renaming a symbol and all its references generally, not a capture pre-check (use ek9_rename_symbol)" ],
        "examples" : [ "{symbol:'calc'} → clashFree:false, donorLocals:['doubled'], renames:[{name:'doubled', suggestedName:'doubled3'}] (safe at every clashing site).", "{symbol:'calc', callSiteFile:'main.ek9', callSiteLine:27} → clashes for just that one call (scope is inferred as single-call-site); no call graph needed." ],
        "details" : "Reports name/kind/fullyQualifiedName, donorLocals (names the body declares), per-site clashes each with a suggestedName, an aggregated 'renames' union whose one rename per donor local is safe at every clashing site at once, and a clashFree flag. Expression-bodied operations declare no locals and are always clashFree; recursive targets report self-call sites as skipped under 'notes'. scope is inferred: callSiteFile+callSiteLine present = single-call-site, else all-call-sites (via the Phase-5 call graph, across files). Shares the NameClashDetector primitive that raises ek9_inline_function's variable-clash conflict.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "callSiteColumn" : {
              "default" : 1,
              "description" : "single-call-site only — 1-based column on the call name (an IDE cursor); omit it and the first call on the line is taken.",
              "type" : "integer"
            },
            "callSiteFile" : {
              "description" : "single-call-site only — workspace-relative file of the target call.",
              "type" : "string"
            },
            "callSiteLine" : {
              "description" : "single-call-site only — line (1-based) of the target call.",
              "type" : "integer"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration header - OMIT it and pass symbol:'Type.method' to let the tool find the line (a model counting lines miscounts).",
              "type" : "integer"
            },
            "scope" : {
              "description" : "Optional - inferred from the arguments: callSiteFile+callSiteLine present = single-call-site (just that call, no call graph needed), else all-call-sites (every call site via the Phase-5 call graph, across files).",
              "enum" : [ "all-call-sites", "single-call-site" ],
              "type" : "string"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control — see EK9_TOOL_SPECIFICATIONS.md §1.6. full (default) returns the complete result; summary trims per-item detail (call_graph to its headline names and sites, effective-api to member names); none collapses lists to counts. A very large response auto-degrades with an explicit `truncated` marker.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_inspect_symbol_at_cursor",
        "description" : "Answers first: name, fullyQualifiedName, kind, type, state, declaredAt, then the modifiers, flags, scope, purityContext and the type's operators - one symbol, cursor-precise.",
        "idePrecedent" : "Quick Documentation (IntelliJ) / Hover (VSCode)",
        "whenToUse" : "What exactly is the identifier at this cursor - kind, type, tri-state, declaration, scope - one call with file + symbol:'<name>'. An IDE cursor passes file + line + column instead.",
        "whenNotToUse" : [ "The WHOLE construct containing the cursor (class/record/etc.), not the precise symbol at it — use ek9_query_construct.", "The FULL callable API of a type (methods + operators + constructors, inherited members) — use ek9_query_effective_api on the returned type.", "Enumerating ALL symbols in scope at a position — use ek9_list_visible_symbols.", "Language reference for a built-in type rather than a workspace symbol — use ek9_help." ],
        "examples" : [ "{file:'order.ek9', line:50, column:12} on a variable → kind:VARIABLE, type:Customer, state:set, mutable:true, declaredAt, scope, purityContext, operators[].", "Cursor on whitespace/comment → {symbol:'not_found', message:'...'} — treat as no symbol, do not fabricate." ],
        "details" : "Resolved symbols carry symbol:'found' with a uniform core (kind, type, state, mutable, purityContext, modifiers, declaredAt, scope, flags); kind is FIELD or PARAMETER where applicable, otherwise the symbol category (VARIABLE/METHOD/FUNCTION/TYPE/...), and METHOD adds methodDetails (parameters, returns, pure, abstract). state is the symbol's STATIC declared tri-state, not flow-sensitive (an assignment lines earlier still reads unset) — except state:'absent', which IS position-aware: a REAL variable out of lexical scope here (reason scope-closed or before-declaration) is recovered from its declaration with an absence block, while a name declared nowhere stays not_found. declaredAt is line+column only (no file yet); operators lists the type's operators only (?, $, <=>, ==, ...) — plain methods are deliberately excluded. displayName appears only when it differs from name (a parameterised or synthetic symbol).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "column" : {
              "description" : "0-based char offset of the target identifier on 'line' (an IDE cursor). OMIT it and pass symbol:'<name>' instead, or omit both and the first identifier on the line is taken (right for a declaration line). Reuse a column from ek9_inspect_symbol_at_cursor verbatim.",
              "type" : "integer"
            },
            "file" : {
              "description" : "The EK9 source file path",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "symbol" : {
              "description" : "The identifier you mean on 'line' (e.g. 'sayHello'); the tool finds its column. Prefer this to column - a column is a 0-based character offset an IDE supplies.",
              "type" : "string"
            }
          },
          "required" : [ "file" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_list_visible_symbols",
        "description" : "Answers first: scopeChain[] innermost to outermost, each scope with its symbols (name, kind, type, state, roles), then activeGuards and purityConstraint.",
        "idePrecedent" : "Variables Panel (debug) / Document Symbols (LSP)",
        "whenToUse" : "What names can I use at this position - every variable, field, method, type and constant in scope - one call with file + symbol:'<name on that line>'. Pass file + line instead from an IDE cursor.",
        "whenNotToUse" : [ "One specific symbol AT the cursor, not the whole list — use ek9_inspect_symbol_at_cursor (cursor-precise, single result, and the only path that reports 'absent').", "The members of a KNOWN type (e.g. all methods on Customer) — use ek9_query_effective_api.", "The structure of the enclosing construct (class/record) — use ek9_query_construct.", "Not anchored to a position — use ek9_query_workspace for a workspace-wide structure overview, or ek9_find_constructs_by_name to locate a known construct NAME." ],
        "examples" : [ "{file:'orders/customer_order.ek9', line:50, column:12} inside a method → scopeChain method→aggregate→module with each symbol's kind/type/state, activeGuards, purityConstraint:'non-pure'.", "Same in a pure method → purityConstraint:'pure' (innermost scope wins) — do NOT suggest mutations even on mutable fields." ],
        "details" : "scopeChain is ordered innermost→outermost with per-scope purityContext; purityConstraint is the effective (innermost) purity — check it before proposing mutations. Free top-level functions are kind FUNCTION, NOT METHOD; mutability is the string \"mutable\" inside roles[], not a top-level key; omitted fields are absent (presence-detection, not null-checks). A block scope carries scope:'block' and no name. The module rung spans the whole module — a constant declared in a sibling file appears with an extra 'file' key (its 'line' is a line in THAT file). verbosity:'summary' keeps each scope's symbolNames only.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "column" : {
              "description" : "0-based char offset of the target identifier on 'line' (an IDE cursor). OMIT it and pass symbol:'<name>' instead, or omit both and the first identifier on the line is taken (right for a declaration line). Reuse a column from ek9_inspect_symbol_at_cursor verbatim.",
              "type" : "integer"
            },
            "file" : {
              "description" : "The EK9 source file path",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "symbol" : {
              "description" : "The identifier you mean on 'line' (e.g. 'sayHello'); the tool finds its column. Prefer this to column - a column is a 0-based character offset an IDE supplies.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control — see EK9_TOOL_SPECIFICATIONS.md §1.6. full (default) returns the complete result; summary trims per-item detail (call_graph to its headline names and sites, effective-api to member names); none collapses lists to counts. A very large response auto-degrades with an explicit `truncated` marker.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_complexity",
        "description" : "Answers first: in view 'totals' the workspace-wide methodCount, averages, maxima and methodsAboveCyclomaticThreshold / methodsAboveCognitiveThreshold; in 'hotspots' hotspotNames[] then hotspots[]; in 'inputVariety' per-callable floor/ceiling (display-only).",
        "idePrecedent" : "Code Inspection / SonarQube Complexity",
        "whenToUse" : "Which methods are too complex, or how complex is the workspace overall - one call with view:'hotspots' or view:'totals'; no anchor needed. Tune the hotspots view with complexityThreshold and metric.",
        "whenNotToUse" : [ "Analysing ONE specific method's internal control-flow structure — use ek9_query_flow (complexity FINDS complex methods; flow ANALYSES one of them).", "Test coverage (line/branch/method) — use ek9_run_tests / ek9_run_quality_checks.", "Per-test pass/fail outcomes — use ek9_run_tests.", "Compile errors/warnings, including complexity-threshold violations as diagnostics — use ek9_query_diagnostics (this tool returns the metric VALUES, not the violations)." ],
        "examples" : [ "{} → view:'totals': methodCount, average/max cyclomatic+cognitive, methodsAboveCyclomaticThreshold / methodsAboveCognitiveThreshold with the thresholds (10 / 15) beside them.", "{view:'hotspots', complexityThreshold:15, metric:'cognitive'} → hotspotNames[] then hotspots[] of methods with cognitive >= 15, each with fullyQualifiedName/file/line/cyclomatic/cognitive, sorted by the chosen metric descending." ],
        "details" : "Three views: totals (fixed industry thresholds 10 cyclomatic / 15 cognitive, echoed as cyclomaticThreshold / cognitiveThreshold), hotspots (methods with the chosen metric >= complexityThreshold, default cyclomatic >= 10, sorted descending) and inputVariety (per-callable floor/ceiling input-variation figures + confidence, plus per-type rollups - DISPLAY ONLY, never a gate or a target). fileFilter (a path substring) is honoured in every view; complexityThreshold < 1 is clamped to 1. Metrics are squirrelled at PRE_IR_CHECKS, so compilation must reach that phase (queriesReliable:true on ek9_query_build_status) — no test run needed; an empty or earlier-stalled workspace returns methodCount 0 / empty arrays.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "complexityThreshold" : {
              "default" : 10,
              "description" : "Hotspots view: methods with metric >= complexityThreshold. Default 10.",
              "type" : "integer"
            },
            "fileFilter" : {
              "description" : "Optional: keep only methods whose source path contains this text.",
              "type" : "string"
            },
            "metric" : {
              "default" : "cyclomatic",
              "description" : "Which metric to threshold on in the hotspots view.",
              "enum" : [ "cyclomatic", "cognitive" ],
              "type" : "string"
            },
            "view" : {
              "default" : "totals",
              "description" : "Which view: totals (aggregate stats), hotspots (above-threshold methods), or inputVariety (per-construct input-variation figures, display-only).",
              "enum" : [ "totals", "hotspots", "inputVariety" ],
              "type" : "string"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_construct",
        "description" : "Answers first: fieldNames, methodNames, operatorNames, defaultedOperatorNames, superName, traitNames, then construct{name, fullyQualifiedName, module, genus, isOpen, extends, traits, modifiers} and the full fields/methods/operators/constructors/metrics.",
        "idePrecedent" : "Structure View (IntelliJ) / Outline (VSCode)",
        "whenToUse" : "Everything about one type or function in one call - fields, methods, operators, supertype, traits, metrics - with symbol:'Order', no file needed. An IDE cursor (file + line anywhere inside the construct) also works; defaulted operators are marked.",
        "whenNotToUse" : [ "JUST the effective member list (own + inherited + trait-provided), not the whole construct - use ek9_query_effective_api.", "You want the WHOLE module, not a single construct - use ek9_query_module.", "You have file + line + COLUMN and want the exact symbol at the cursor (parameter, local, …), finer than construct-level - use ek9_inspect_symbol_at_cursor.", "You want one method's call relationships, not the construct's structure - use ek9_query_call_graph." ],
        "examples" : [ "{symbol:'CustomerOrder'} -> fieldNames, methodNames, operatorNames, defaultedOperatorNames, superName, traitNames first, then construct{name,genus:'CLASS',extends,traits} + fields/methods/operators/constructors/metrics.", "{file:'orders/customer_order.ek9', line:42} -> the same, for the INNERMOST construct enclosing line 42; a line outside any construct -> {construct:'not_found', message:...}." ],
        "details" : "ONE uniform shape for every genus: the name lists first, then the header under 'construct{}' (name, fullyQualifiedName, module, genus, modifiers, declaration 'line', isOpen, flat 'extends' + 'traits', generic template/parameterised-type info, 'serviceUri' for services) with sibling fields/methods/operators/constructors/metrics. A FUNCTION reuses that shape — parameters appear as 'fields', the return as a single 'methods' entry, operators/constructors empty. Metrics carry complexity/cohesion/coupling/inheritanceDepth plus member counts; 'coupling' MAY be fractional (each by-trait delegation discounts 0.5).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line anywhere inside the target construct - OMIT it and pass symbol:'<name>' instead.",
              "type" : "integer"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_flow",
        "description" : "Answers first: status, branchCount, loopCount, guardCount, deadBranchCount, then method{name, fullyQualifiedName, signature, declaredAt}, isPure, metrics (cyclomatic/cognitive), flowNodes (the guard-tracked branch/loop tree) and deepAnalysis (provably dead branches, redundant tri-state checks).",
        "idePrecedent" : "Code Inspection / Control Flow Analyzer (IntelliJ)",
        "whenToUse" : "'How does this method or function flow inside - branches, loops, guards, dead code, complexity' is one call with symbol:'Type.method' - no file or line needed.",
        "whenNotToUse" : [ "method-to-method call relationships, who calls it or what it calls (use ek9_query_call_graph)", "a body that is a stream pipeline and you want stage-level analysis (use ek9_query_pipeline)", "just complexity numbers across MANY methods workspace-wide (use ek9_query_complexity)", "the structural outline of a class/record, not the flow of a body (use ek9_query_construct)" ],
        "examples" : [ "{symbol:'CustomerOrder.calculateTotal'} -> status:ok, branchCount/loopCount/guardCount/deadBranchCount, method{name, fullyQualifiedName, signature, declaredAt}, metrics{cyclomaticComplexity, cognitiveComplexity, ...}, flowNodes[] tree with per-node guards, deepAnalysis overlays.", "{file:'orders/customer_order.ek9', line:64} -> the same for the method enclosing line 64 (an IDE cursor anywhere in the body)." ],
        "details" : "Uses on-demand IR generation: flow is available after PRE_IR_CHECKS (Phase 5), no full IR_GENERATION compile needed — fast and usable mid-edit. metrics{cyclomaticComplexity, cognitiveComplexity, lineCount, maxNestingDepth, branchCount, loopCount, guardCount}; flowNodes[] (the IF_ELSE_IF/WHILE_LOOP/SWITCH/TRY_CATCH_FINALLY/FOR_RANGE/… tree with depth/children/per-node guard); guards[] {introducedAt, guarantee}; deepAnalysis {deadBranches — PROVABLY unreachable, redundantChecks — tri-state checks the compiler already proved, guaranteedVariables}. Missing source, no IR construct, or failed IR generation returns status:'not-found' with a note; a line not on a specific method falls back to the construct's best default operation, so anchor precisely (symbol:'Type.method' does); cyclomatic>10, cognitive>15 or non-empty overlays flag refactor candidates for ek9_extract_method / ek9_remove_statements / ek9_apply_guard_pattern.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line anywhere inside the method/function or on its declaration - OMIT it and pass symbol:'Type.method' to let the tool find the line (a model counting lines miscounts).",
              "type" : "integer"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_advise_loop",
        "description" : "Answers first: directions[] (each with summary such as 'candidate-stream-aggregation' or 'keep-as-loop', candidateShape, confidence), then the loop{} facts, outputs[], resources[] and unknowns[] - read-only.",
        "idePrecedent" : "IntelliJ 'Analyze Data Flow' + inspection hints (advisory, not an auto-fix)",
        "whenToUse" : "Should this for/while loop become a stream pipeline, and how - one call with file + line on the loop header (or file + symbol:'<loop variable>'). Any line inside the body also anchors it. Advisory only: the loop is never rewritten.",
        "whenNotToUse" : [ "The method's overall control-flow structure (branches, guards, dead code) rather than loop-to-stream advice - use ek9_query_flow.", "The body is already a stream pipeline and you want stage-level analysis - use ek9_query_pipeline.", "You just want a type's operator surface (operator |, <=>, #?) with no loop context - use ek9_query_effective_api.", "You have read the advice and are ready to synthesise a stage function / collector operator / record - use ek9_create_dynamic_function, ek9_add_operator, ek9_new_construct." ],
        "examples" : [ "{file:'AsStats.ek9', line:110} on a for-range accumulating count/total -> outputs interpreted (counter -> scalar reduction; accumulation -> 'collect as'), a 'candidate-stream-aggregation' direction with a `cat ... | collect as` candidateShape, suggestedTools [ek9_create_dynamic_function, ek9_add_operator, ek9_new_construct].", "{file:'Orders.ek9', line:12} inside a `repository.save(order)` loop -> sole direction 'keep-as-loop' (per-item side effect); a valid answer, not a failure." ],
        "details" : "Scope is always the single nearest enclosing loop: parse-tree mutation facts always work (PRE_IR_CHECKS, no symbols), while Tier-2 symbol facts (elementType, pipeEligible, bodyCallPurity, readAfterLoop) appear only when the workspace is resolved. pipeEligible mirrors the compiler's own `collect as` verdict — true means `collect as <type>` compiles with zero collector synthesis. directions[] are advisory candidateShapes, not committed edits; a line not on any loop returns an empty loop plus a single 'not-a-loop' direction (not an error); and quiet facts are not proven safety — always read unknowns[] (commutativity, aliasing, iterator single-consumption).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "file" : {
              "description" : "The EK9 source file path",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the for/while loop header or any line inside the loop body - OMIT it and pass symbol:'<loop variable>' to let the tool find the line.",
              "type" : "integer"
            },
            "symbol" : {
              "description" : "A name on the loop header (the loop variable, e.g. 'i') - the tool finds its line, so you never count lines.",
              "type" : "string"
            }
          },
          "required" : [ "file" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_verify_method",
        "description" : "Answers first: status, name, verified, then counterexampleCount, counterexamples[] {obligation, witness, suggestedTest, suggestedFix}, provedSafe, deadBranches and nextStep.",
        "idePrecedent" : "Design-by-contract / bounded-model verifier (Dafny, Frama-C, CBMC)",
        "whenToUse" : "'Can any input break this function's contract - a panic or an unset result' is one call with symbol:'name' - no file needed; the breaking input is returned.",
        "whenNotToUse" : [ "internal control flow or complexity of a body, not contract safety (use ek9_query_flow)", "materialising a returned counterexample as an actual failing test (use ek9_generate_edge_case_tests)", "discharging a returned counterexample by inserting the suggested require (use ek9_apply_symbolic_fix)", "the every-build census rollup across the whole workspace (use ek9_run_tests with generateReport:true - the quality report's Contract roundel)" ],
        "examples" : [ "{symbol:'safeAverage'} → status:ok, verified:false, counterexampleCount:1, counterexamples:[{obligation:DIV_BY_ZERO, witness:'count=0', suggestedTest}] — paste count=0 into a test.", "{file:'safe.ek9', line:12} on a proven-safe function → status:ok, verified:true, counterexamples:[]; nothing to do." ],
        "details" : "Runs the SymbolicEngine on demand against the already-resolved program (after PRE_IR_CHECKS — no -Xverify flag, no IR), resolving the ENCLOSING FUNCTION from the anchor; a type/record/bare-expression target returns status:'not-found' (re-anchor on the function) and a dynamic function returns status:'not-applicable' (no verification claim either way - its captures are constructor-injected fields, not free inputs). verified means verified TO A BOUND (finite value-class enumeration + affine boundaries from requires) — advisory, never fails a build. counterexamples[] carry {obligation, witness as name=value, line}, enriched with suggestedTest, suggestedFix (the discharging require — its presence gates ek9_apply_symbolic_fix) and alreadyTested/testProgram when an existing @Test already pins that boundary; obligation kinds today are DIV_BY_ZERO (silently-unset result), CALLEE_REQUIRE (a callee's require violated across a call) and CONSTRAINED_CONSTRUCTION (constructed value can leave the constrained type's window); provedSafe[] lists obligations proven unbreakable and deadBranches[] {reason} branches unreachable for every input.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line inside the function's body or on its declaration - OMIT it and pass symbol:'name' to let the tool find the line (a model counting lines miscounts).",
              "type" : "integer"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_module",
        "description" : "Answers first: status, module, constructCount, constructNames, then constructs[] with kind, member counts and quality metrics (complexity, coupling, cohesion).",
        "whenToUse" : "'What does this module declare - classes, functions, constants, their sizes' is one call with file:'any.ek9' in it; scope:'module' unions all its files.",
        "whenNotToUse" : [ "the whole workspace, or a module by NAME (use ek9_query_workspace with moduleFilter)", "ONE specific construct's details, not the whole module (use ek9_query_construct)", "the cross-module dependency graph or fan-in/fan-out (use ek9_query_module_dependencies; fanIn/fanOut are not emitted here)", "the exact symbol at a cursor (use ek9_inspect_symbol_at_cursor)" ],
        "examples" : [ "{file:'orders.ek9'} -> status:ok, module, constructCount, constructNames, constructs[] declared in that file (default scope:'file'), plus metrics{averageMembersPerConstruct}.", "{file:'orders.ek9', scope:'module', kinds:['constant']} -> only constants, unioned across every file in the module (a sibling file's `defines constant` block IS included)." ],
        "details" : "A multi-file module has one ModuleScope per file, so the default scope:'file' can report 'no constants' when they live in a sibling file — use scope:'module' when deciding local variable vs named constant. An unknown kind is rejected, not ignored. A path in no compiled module returns status:'not-found' rather than an error; per-construct 'coupling' may be fractional (by-trait-delegation discount); per-construct sourceFile, files[] and metrics.fanIn/fanOut are deferred.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "file" : {
              "description" : "Any .ek9 file inside the module of interest (workspace-relative; absolute inside the workspace accepted)",
              "type" : "string"
            },
            "kinds" : {
              "description" : "Optional filter, e.g. ['constant'] or ['class','component']. Omit for everything. EK9 has no module-scoped variables — only constants are per-module values, everything else is a type — so 'constant' is just one kind here.",
              "items" : {
                "enum" : [ "application", "class", "component", "constant", "enumeration", "function", "program", "record", "service", "text", "trait", "type" ],
                "type" : "string"
              },
              "type" : "array"
            },
            "scope" : {
              "description" : "'file' (default) lists what THIS source file declares; 'module' lists what the whole module declares across ALL its files, adding a 'file' field per construct. A multi-file module has one scope per file, so the two differ: a constant declared in a sibling file is absent from the 'file' view.",
              "enum" : [ "file", "module" ],
              "type" : "string"
            }
          },
          "required" : [ "file" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_pipeline",
        "description" : "Answers first: status, stageCount, then stages[] in order {operator, kind, consumes, produces}, sourceType, resultType and summary.transformations.",
        "whenToUse" : "'What does each stage of this stream pipeline consume and produce' is one call with symbol:'Type.method' (its first pipeline) or file+line on any line of it.",
        "whenNotToUse" : [ "an imperative body with branches, loops or guards, not a stream chain (use ek9_query_flow)", "method-level caller/callee relationships, not stage-level pipeline analysis (use ek9_query_call_graph)", "the structural outline of a class/record, not pipeline details (use ek9_query_construct)", "compile errors/warnings (incl. pipeline type mismatches), not the pipeline structure (use ek9_query_diagnostics)" ],
        "examples" : [ "{symbol:'OrderReport.build'} -> status:ok, stageCount, ordered stages[] and summary.transformations for the first `cat|filter|map|collect` chain in that body.", "{file:'orders/customer_order.ek9', line:25} -> the same for the chain spanning line 25 (an IDE cursor); a line on no pipeline -> status:'not-found' with a note (not an error)." ],
        "details" : "stages[] is ordered, each carrying operator, kind, context, consumes, produces (consumes omitted on source stages). kind enum: source (cat/for), filter (filter/reject/uniq), map (map/flatten/group/join/split), limit (head/tail/skipping), sort, tee, async, call, reduce, terminal (collect/`>`/`>>`), with kind-specific arg fields predicate/function/limit/collector (limit numeric for a plain int literal, else raw text). Flat sourceType/resultType and nested summary{stageCount, sourceType, terminalType, transformations[]} give the whole-pipeline type-evolution story. Where pipelines nest, the innermost chain spanning the line wins; a body holding several pipelines needs file+line for any but the first.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line the pipeline spans - any line of the chain; OMIT it and pass symbol:'Type.method' for the first pipeline in that body, or use ek9_read_file(lineNumbers:true) rather than counting.",
              "type" : "integer"
            },
            "symbol" : {
              "description" : "The method or function whose body holds the pipeline - 'Type.method' or a function name - no file or line needed; the FIRST pipeline in that body is decomposed.",
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_workspace",
        "description" : "Answers first: status, moduleCount, moduleNames, constructCount, then modules[] {name, constructCount, constructs[] {name, category, genus}}; verbosity:'none' lists modules only, 'full' adds per-construct detail.",
        "whenToUse" : "'What is in this workspace - every module and what each declares' is one call with no arguments; moduleFilter:['com.acme.*'] narrows it.",
        "whenNotToUse" : [ "one module's contents anchored by a file (use ek9_query_module)", "locating a single named construct, not enumerating the workspace (use ek9_find_constructs_by_name)", "the module dependency graph rather than the inventory (use ek9_query_module_dependencies)", "a built-in type's API (use ek9_help; includeBuiltins only lists org.ek9.lang constructs, it does not document them)" ],
        "examples" : [ "{} (no args) -> status:ok, moduleCount, moduleNames, constructCount and per-module constructs (builtins excluded).", "{verbosity:'none', moduleFilter:['com.acme.orders']} -> just that module as {name, sourceCount}; a filter matching nothing returns moduleCount:0, modules:[] (status:ok, not an error)." ],
        "details" : "verbosity selects the shape: none = module names + sourceCount only; summary (default) = adds each module's public constructs as {name, category, genus} plus constructCount; full = further adds line, memberCount and a modifiers array (open/abstract/extern) on aggregate constructs. moduleFilter is exact names, '*' suffix for a prefix; includeBuiltins defaults false (user code only).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "includeBuiltins" : {
              "description" : "Whether to include org.ek9.lang built-in modules (default false)",
              "type" : "boolean"
            },
            "moduleFilter" : {
              "description" : "Restrict to these modules - exact names; an entry ending in '*' is a prefix ('com.acme.*'). A single string is accepted. Omit for workspace-wide.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "none = module names + sourceCount; summary (default) = each module's constructs as {name, category, genus}; full = adds line, memberCount and modifiers per construct.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_classes",
        "description" : "Answers first: classNames[], then classes[] (name, module, file, line, extends, traits, isOpen, isAbstract, keyMethods, methodCount) and summary counts - workspace-wide or the filtered subset.",
        "idePrecedent" : "Classes outline panel (reuse-discovery)",
        "whenToUse" : "Is there already a class I can reuse, extend or compose - one call with nameFilter:'User'; no arguments lists every class. Narrow further with extendsFilter, traitFilter, methodNameFilter or moduleFilter.",
        "whenNotToUse" : [ "Looking for a pure data shape with no methods — use ek9_query_records.", "Looking for an interface contract — use ek9_query_traits.", "Looking for a DI-injectable component (a specialised class kind) — use ek9_query_components.", "You already have a class name — use ek9_query_type_hierarchy (full hierarchy graph), ek9_query_effective_api (complete method API), or ek9_read_construct (source)." ],
        "examples" : [ "{nameFilter:'User'} -> status:ok, classNames then every class with 'User' in the name + extends/traits/isOpen/keyMethods.", "{traitFilter:['Comparable']} -> status:ok, classes implementing that trait (transitive); a trait that names nothing yields status:empty with meta.unmatchedFilters:['trait:…'].", "{traitFilter:['Identifiable','Describable'], traitMatch:'all'} -> only the classes carrying BOTH, each with allTraits[] as the proof; traitMatch is echoed beside classNames." ],
        "details" : "EK9 classes are CLOSED by default — check isOpen before planning to extend; for a closed candidate the reuse path is composition (use as a field). status:empty means the walk succeeded but nothing matched — non-existent extends/trait/module targets surface in meta.unmatchedFilters[], NOT as an error; no compiled program is status:not-compiled (run ek9_compile first), and the only error is a traitMatch other than any/all. nameFilter/methodNameFilter are plain case-insensitive substrings, never regex; built-in org.ek9.* stdlib classes are excluded. traitFilter and extendsFilter name a trait/class EXACTLY (bare name, or module::Name for one module's) and match by symbol, transitively; traitMatch picks any-of (default) or all-of, and traitFilterResolvedTo lists the traits the filter matched. A value naming no trait/class is in meta.unmatchedFilters[], with meta.didYouMean naming those that contain it. traitFilter covers classes: records and components cannot carry traits, and the traits that extend a trait are in ek9_query_traits implementedBy.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "among" : {
              "description" : "Names to answer FOR: 'which of these ...' - the name lists shrink to these and amongUnmatched names the ones that are not in the answer. Bare names, 'mod::Type' or 'Type.member'; a MODULE name stands for every construct in it ('which in scale.m07 do not ...' = amongUnmatched).",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "extendsFilter" : {
              "description" : "Filter to classes whose extends-chain transitively includes this superclass name.",
              "type" : "string"
            },
            "includeAbstract" : {
              "description" : "Default true. When false, excludes classes marked 'as abstract'.",
              "type" : "boolean"
            },
            "includeMethodSummary" : {
              "description" : "Default true. When true each entry includes keyMethods[] (up to 5 public signatures). Set false for a lighter response.",
              "type" : "boolean"
            },
            "methodNameFilter" : {
              "description" : "Filter to classes declaring a method whose name matches (case-insensitive substring).",
              "type" : "string"
            },
            "moduleFilter" : {
              "description" : "Restrict to these exact module names. Default: workspace-wide.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "nameFilter" : {
              "description" : "Case-insensitive substring match on class names.",
              "type" : "string"
            },
            "traitFilter" : {
              "description" : "Filter to classes carrying the listed traits (transitive) - any one of them, or every one with traitMatch:'all'. Exact names, bare or module::Name.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "traitMatch" : {
              "description" : "How traitFilter combines: 'any' (default) - at least one; 'all' - every one.",
              "enum" : [ "any", "all" ],
              "type" : "string"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_records",
        "description" : "Answers first: status, recordCount, recordNames - complete for the workspace or the filters given - then records[] {name, module, file, line, fields, extends, traits, isOpen}.",
        "idePrecedent" : "Records outline panel (reuse-discovery)",
        "whenToUse" : "'Which records exist, and is there a data shape to reuse' is one call - no arguments, or nameFilter / fieldNameFilter / moduleFilter to narrow.",
        "whenNotToUse" : [ "constructs with methods/behaviour, not just data (use ek9_query_classes)", "interface contracts (use ek9_query_traits)", "constrained primitive types, EmailAddress as String (use ek9_query_constrained_types)", "a record you already know the name of (ek9_query_references for its usage sites, ek9_read_construct for its full source)" ],
        "examples" : [ "{} → status:ok, recordCount, recordNames, records[] workspace-wide (org.ek9.* stdlib excluded).", "{nameFilter:'Address'} → records named like Address; {fieldNameFilter:'customerId'} → records declaring a matching field; no match → status:empty (never an error)." ],
        "details" : "Each record carries name, module, file (workspace-relative), line, fields[] (name+type; unresolved types render <unresolved>), extends (null unless a super declared; implicit Any filtered), traits[], isOpen. Only directly-declared fields — supers not flattened; nameFilter and fieldNameFilter are plain case-insensitive substrings, never regex. status:empty (not an error) when nothing matched or moduleFilter named non-existent modules — then meta.unmatchedFilters[] carries module:<value>.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "fieldNameFilter" : {
              "description" : "Filter to records declaring a field whose name matches (case-insensitive substring).",
              "type" : "string"
            },
            "moduleFilter" : {
              "description" : "Restrict to specific modules (exact names). Default: workspace-wide.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "nameFilter" : {
              "description" : "Case-insensitive substring match on record names.",
              "type" : "string"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_traits",
        "description" : "Answers first: status, implementedByAll (when asked), traitCount, traitNames - complete for the workspace or the filters given - then traits[] {name, module, file, line, traitOf, implementedBy, implementedByCount, methods, methodCount}.",
        "idePrecedent" : "Traits outline panel (reuse-discovery)",
        "whenToUse" : "'Which traits exist, and is there a contract I can adopt' is one call - no arguments for the workspace, nameFilter / methodNameFilter / moduleFilter to narrow.",
        "whenNotToUse" : [ "concrete classes (use ek9_query_classes)", "data shapes (use ek9_query_records)", "a trait's depth-by-depth implementer graph (ek9_query_type_hierarchy with symbol:'TraitName'); the implementers themselves come back here as implementedBy", "a trait's full source (use ek9_read_construct)" ],
        "examples" : [ "{} -> status:ok, traitCount, traitNames, every workspace trait with its method contract.", "{methodNameFilter:'describe'} -> status:ok, only traits declaring a describe() method; no match -> status:empty.", "{implementedByAll:['Identifiable','Describable']} -> implementedByAll: every construct carrying BOTH traits (classes and sub-traits, direct or inherited), then the trait list." ],
        "details" : "nameFilter/methodNameFilter are plain case-insensitive substrings (not regex), moduleFilter an exact module-name allow-list; built-in org.ek9.* traits are excluded. status is ok (traits returned) / empty (no match — meta.unmatchedFilters[] lists any moduleFilter values naming non-existent modules as module:<value>) / not-compiled (no compiled program — run ek9_compile first). traitOf lists only directly-declared super-traits (with trait of ...); methods[] are EK9-syntax signatures capped at 8, including the synthesized operator ?; unresolved types render as <unresolved>. implementedBy is every construct carrying the trait workspace-wide - classes, traits that extend it, and types that get it through a sub-trait or a superclass - nothing to verify; records and components cannot carry traits, and a dynamic class inside a function body is not listed. implementedByAll names traits exactly (bare or module::Name) and intersects their implementedBy; a name that names no trait is in meta.unmatchedFilters[] as trait:<value>, with meta.didYouMean naming the traits that contain it.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "implementedByAll" : {
              "description" : "Trait names (exact, bare or module::Name). Answers implementedByAll: the constructs that carry EVERY one of them, transitively.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "includeMethodSummary" : {
              "description" : "Default true. When true each entry includes methods[] (the contract signatures). Set false for a lighter response.",
              "type" : "boolean"
            },
            "methodNameFilter" : {
              "description" : "Filter to traits declaring a method whose name matches (case-insensitive substring).",
              "type" : "string"
            },
            "moduleFilter" : {
              "description" : "Restrict to specific modules (exact names). Default: workspace-wide.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "nameFilter" : {
              "description" : "Case-insensitive substring match on trait names.",
              "type" : "string"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_components",
        "description" : "Answers first: componentNames[], then components[] (name, module, file, line, extends, isOpen, isAbstract, keyMethods, methodCount) and summary counts - the DI-injectable constructs, workspace-wide or the filtered subset.",
        "idePrecedent" : "Components outline panel (DI reuse-discovery)",
        "whenToUse" : "Is there already a DI component I can reuse or extend - one call with nameFilter:'Logger'; no arguments lists every component. Narrow with extendsFilter, moduleFilter or includeAbstract:false.",
        "whenNotToUse" : [ "The construct is a plain class, not a DI component — use ek9_query_classes instead.", "You want the actual DI wiring graph (who injects what) — use ek9_query_dependency_injection instead.", "You know a component's name and want its full inheritance chain — use ek9_query_type_hierarchy instead.", "You know the component's name and want its full source — use ek9_read_construct instead." ],
        "examples" : [ "{} -> status:ok, componentNames then every workspace component with extends + keyMethods + module/file/line", "{nameFilter:'Logger', includeAbstract:false} -> status:ok, concrete components whose name contains 'Logger' (or status:empty when none match)" ],
        "details" : "status ok = one or more matched; empty = filtered set empty (incl. a moduleFilter naming non-existent modules, surfaced in meta.unmatchedFilters[] as module:<value>) and is NOT an error; error = no compiled program (run ek9_compile first). nameFilter/methodNameFilter are plain case-insensitive substrings, not regex; built-in org.ek9.* components are excluded. extends is null unless a super-component is declared (implicit Any filtered out); keyMethods are EK9-syntax signatures, constructors-first, public-only, capped at 5; unresolved types render as <unresolved> without a partial status.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "extendsFilter" : {
              "description" : "Filter to components whose extends-chain transitively includes this super-component name.",
              "type" : "string"
            },
            "includeAbstract" : {
              "description" : "Default true. When false, excludes components marked 'as abstract'.",
              "type" : "boolean"
            },
            "includeMethodSummary" : {
              "description" : "Default true. When true each entry includes keyMethods[] (up to 5 public signatures). Set false for a lighter response.",
              "type" : "boolean"
            },
            "methodNameFilter" : {
              "description" : "Filter to components declaring a method whose name matches (case-insensitive substring).",
              "type" : "string"
            },
            "moduleFilter" : {
              "description" : "Restrict to these exact module names. Default: workspace-wide.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "nameFilter" : {
              "description" : "Case-insensitive substring match on component names.",
              "type" : "string"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_constrained_types",
        "description" : "Answers first: constrainedTypeNames[], then constrainedTypes[] (name, module, file, line, constrains, constraint, admissibleWindow, suffix) and summary counts.",
        "idePrecedent" : "Constrained-types outline panel (reuse-discovery)",
        "whenToUse" : "Is there already a constrained type over this base, or admitting this range - one call with baseTypeFilter:'String' or nameFilter:'Email'. Scope it with moduleFilter; no arguments lists every constrained type.",
        "whenNotToUse" : [ "Looking for multi-field data shapes — use ek9_query_records instead.", "Looking for a class with behaviour — use ek9_query_classes instead.", "You want the constrained type's full SOURCE (the declaration as written, comments and all) — use ek9_read_construct instead. This response carries the constraint predicate itself, so it answers 'what does it admit?' without reading the source." ],
        "examples" : [ "{} -> status:ok, constrainedTypeNames then constrainedTypes[] workspace-wide with name/module/file/line/constrains.", "{baseTypeFilter:'String'} -> status:ok listing EmailAddress, PostCode, … narrowing String; no match returns status:empty (not an error).", "{baseTypeFilter:'Integer'} -> each row also carries constraint:'>= 0 and <= 100' and admissibleWindow:'[0, 100]', so an existing Percentage is recognisable without opening the source." ],
        "details" : "constraint is the predicate as written ('>= 16 and <= 100'), present whenever the compiler modelled it. admissibleWindow is the inclusive interval it folds to, present only for a decidable Integer range. suffix is the shared currency or unit ('#GBP', 'mm') when the literals carry one — a Money type over GBP does not serve a USD need. constrains is the base type recovered from the synthesized base constructor (the super-aggregate is Any, not the base). nameFilter/baseTypeFilter are plain case-insensitive substrings, not regex; moduleFilter is an exact-name allow-list; built-in org.ek9.* types are excluded. status: ok (matches returned) / empty (none match, or moduleFilter named a non-existent module — meta.unmatchedFilters[] then carries module:<value>) / error (no compiled program — run ek9_compile first).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "baseTypeFilter" : {
              "description" : "Filter to constrained types whose base type matches (case-insensitive, e.g. 'String' finds EmailAddress, PostCode, ...).",
              "type" : "string"
            },
            "moduleFilter" : {
              "description" : "Restrict to these exact module names. Default: workspace-wide.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "nameFilter" : {
              "description" : "Case-insensitive substring match on constrained-type names.",
              "type" : "string"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_functions",
        "description" : "Answers first: status, functionCount, functionNames - complete for the workspace or the filters given - then functions[] {name, module, file, line, signature, parameters, returnType, isPure, isAbstract}.",
        "idePrecedent" : "Functions outline panel (reuse-discovery)",
        "whenToUse" : "'Which free-standing functions exist, and is there one to reuse' is one call - no arguments, or nameFilter / returnTypeFilter / moduleFilter to narrow.",
        "whenNotToUse" : [ "methods bound to a class or trait, not free-standing functions (use ek9_query_effective_api on the type, or ek9_query_classes)", "verbatim string match inside function bodies (use ek9_search_text)", "the full source of a function you already know the name of (use ek9_read_construct)" ],
        "examples" : [ "{} -> status:ok, functionCount, functionNames, functions[] workspace-wide with summary{moduleCount, pureCount}", "{returnTypeFilter:'Boolean'} -> status:ok, only predicate-shaped functions; {moduleFilter:['no.such.mod']} -> status:empty with meta.unmatchedFilters" ],
        "details" : "Top-level 'defines function' constructs only — anonymous dynamic-function closures are excluded (not name-addressable), and abstract function-typedefs (FUNCTION_TRAIT, higher-order parameter types) appear only with includeAbstract:true. nameFilter/returnTypeFilter are case-insensitive substrings, not regex; built-in org.ek9.* functions are excluded. A moduleFilter naming non-existent modules yields status:empty with meta.unmatchedFilters (informational, not an error); returnType is null for Void/no returning symbol, and unresolved types render <unresolved>.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "includeAbstract" : {
              "description" : "Default false. When true, also includes abstract function-typedefs (higher-order parameter signatures).",
              "type" : "boolean"
            },
            "includePure" : {
              "description" : "Default true. When false, excludes functions marked 'as pure'.",
              "type" : "boolean"
            },
            "moduleFilter" : {
              "description" : "Restrict to specific modules (exact names). Default: workspace-wide.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "nameFilter" : {
              "description" : "Case-insensitive substring match on function names.",
              "type" : "string"
            },
            "returnTypeFilter" : {
              "description" : "Filter to functions whose return type matches (case-insensitive substring, e.g. 'Boolean' finds predicates).",
              "type" : "string"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_programs",
        "description" : "Answers first: status, programCount, programNames - complete for the workspace or the filters given - then programs[] {name, module, file, line, signature, parameters}.",
        "idePrecedent" : "Constructs navigator (by-genus) — Programs group",
        "whenToUse" : "'What programs (entry points) does this app expose, with what arguments' is one call with no arguments, or nameFilter / argTypeFilter / moduleFilter to narrow.",
        "whenNotToUse" : [ "running a program you already know the name of (use ek9_run_program; this tool only DISCOVERS - query first, then run the chosen one)", "callable functions rather than executable entry points (use ek9_query_functions)", "the full source of a program you already know the name of (use ek9_read_construct)" ],
        "examples" : [ "{} -> status:ok, programCount, programNames; every program across the workspace with signature + parameters[].", "{argTypeFilter:['Date']} -> status:ok; programs whose FIRST argument is a Date (leading-prefix match) — likely daily/historical batch jobs." ],
        "details" : "status is ok (>=1 program) / empty (filtered set empty — NOT an error; carries meta.unmatchedFilters[] when a moduleFilter names no module) / not-compiled (nothing compiled yet — compile first). Each program projects name, module, file, line, a ready-to-read signature string, and parameters[] as {name,type} pairs read off the program's single entry method. argTypeFilter is a leading-prefix, in-order, case-insensitive exact-type match; unresolved arg types render as <unresolved> without degrading status.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "argTypeFilter" : {
              "description" : "Filter to programs whose argument list STARTS WITH these types, in order (case-insensitive, exact type-name match). Example: [\"Date\"] matches programs whose first argument is a Date.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "moduleFilter" : {
              "description" : "Restrict to specific modules (exact names). Default: workspace-wide.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "nameFilter" : {
              "description" : "Case-insensitive substring match on program names.",
              "type" : "string"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_enumerations",
        "description" : "Answers first: enumerationNames[], then enumerations[] (name, module, file, line, values in declaration order, valueCount) and summary counts.",
        "idePrecedent" : "Constructs navigator (by-genus) — Enumerations group",
        "whenToUse" : "Is there already an enumeration for this value set - one call with nameFilter:'Status' or valueFilter:'PENDING'; no arguments lists every enumeration. Both filters are case-insensitive substrings.",
        "whenNotToUse" : [ "Looking for a constrained primitive (PostCode as String, DrivingAge constrain ...) — those are not enums, use ek9_query_constrained_types.", "Looking for a data shape with named fields — use ek9_query_records.", "Hunting a specific enum VALUE literal across raw source — use ek9_search_text.", "You already know the enum name and want its full source — use ek9_read_construct." ],
        "examples" : [ "{nameFilter:'Status'} -> status:ok; enumerationNames then every enum with 'Status' in the name (consolidation target discovery).", "{valueFilter:'PENDING'} -> status:ok; every enum declaring a PENDING-like value — semantic-overlap candidates worth merging." ],
        "details" : "status ∈ ok (one or more matched) / empty (walk succeeded, filters matched nothing — not an error; adds meta.unmatchedFilters[] when a moduleFilter value names no module) / error (no compiled program — compile first). Each entry carries name, module, file, line, values[] and valueCount, with values[] in declaration order (never alphabetised — ordinal order is semantically meaningful) and casing preserved verbatim. nameFilter and valueFilter are case-insensitive substrings; moduleFilter is an exact module-name allow-list.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "moduleFilter" : {
              "description" : "Restrict to these exact module names. Default: workspace-wide.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "nameFilter" : {
              "description" : "Case-insensitive substring match on enumeration names.",
              "type" : "string"
            },
            "valueFilter" : {
              "description" : "Filter to enumerations declaring a value whose name matches (case-insensitive substring). Example: \"PENDING\" finds every enum with a PENDING-like value — useful for catching duplicate value sets.",
              "type" : "string"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_localisations",
        "description" : "Answers first: status, moduleCount, inconsistencyCount, workspaceLocales, then modules[] {name, locales, percentTranslated, methodCount} and inconsistencies[] {locale, supportedIn, missingIn}.",
        "idePrecedent" : "NEW IDE surface — Localisations Overview panel (locale × module completeness matrix + inconsistency flags)",
        "whenToUse" : "'What languages does this app support, and which modules are missing one' is one call with no arguments - the whole i18n surface at module granularity.",
        "whenNotToUse" : [ "per-method translation status (translated / stubbed / missing) for ONE module (use ek9_query_translation_completeness, the deep per-module dive - call it second, after this one finds the module of interest)", "finding a specific verbatim string in i18n source (use ek9_search_text)", "MUTATING the i18n surface (use ek9_edit_localized_text or ek9_organize_localized_text)" ],
        "examples" : [ "{} -> status:ok, moduleCount, inconsistencyCount, workspaceLocales + per-module summary + inconsistencies[] for the whole workspace.", "{localeFilter:['fr_FR'], includeFileDistribution:true} -> status:ok; per-module locales + metrics scoped to fr_FR (modules[] still lists every i18n module), each with i18nFiles[] (precursor to an ek9_organize_localized_text call)." ],
        "details" : "empty = zero 'defines text for' blocks anywhere (a legitimate greenfield state, not an error); an all-unmatched moduleFilter also yields empty, with meta.unmatchedFilters[] so a typo reads differently from greenfield. A module 'supports' a locale when it has at least one 'defines text for \"L\"' block; zero-block modules are auto-excluded from inconsistency analysis (excludeModules forces further exclusions). percentTranslated and methodCount (method × locale tuples) match ek9_query_translation_completeness byte-for-byte at module granularity; aggregates stop at module level (no per-method rows) to bound response size.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "excludeModules" : {
              "description" : "Modules to treat as intentionally non-i18n — removed from modules[] AND from inconsistencies[].missingIn. (Modules with zero text blocks are auto-excluded already.) Default: none.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "includeFileDistribution" : {
              "default" : false,
              "description" : "Include i18nFiles[] (the .ek9 files holding each module's text blocks). Default off to keep responses compact; turn on when planning a ek9_organize_localized_text call.",
              "type" : "boolean"
            },
            "includeInconsistencies" : {
              "default" : true,
              "description" : "Include the inconsistencies[] array flagging cross-module locale support gaps. Set false to suppress.",
              "type" : "boolean"
            },
            "localeFilter" : {
              "description" : "Restrict per-module metrics + inconsistencies to specific locales (e.g. [\"fr_FR\", \"de\"]). workspaceLocales still lists the full discovered set. Default: all discovered locales.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "moduleFilter" : {
              "description" : "Restrict the response to specific modules (exact names). Default: all i18n modules.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_env_vars",
        "description" : "Answers first: names[] (every env-var read), then envVars[] per call site (site, name, accessMethod, sanitization, inConstruct), dynamicAccesses[] and summary counts - type-resolved EnvVars calls, never text matches.",
        "idePrecedent" : "NEW IDE surface — \"Environment Variables\" panel listing every env-var read across the workspace (name + file + line + access method + sanitization), row-click navigate, right-click find-usages-of-name",
        "whenToUse" : "Which environment variables does this app read, where, and how safely - one call, no arguments. Pass envVarNames for an existence check, accessMethodFilter:['unsanitizedGet'] for a security audit.",
        "whenNotToUse" : [ "Raw text matches of a literal name anywhere (comments, docstrings, JSON, extern files) — use ek9_search_text; this returns only RESOLVED EnvVars calls.", "Auditing the broader sensitive-data surface beyond env vars (Sensitive construction / reveal points) — use ek9_query_sensitive_reveals.", "You already know the variable holding a name and want its call sites — use ek9_query_references (feed a dynamic access's nameExpression there).", "System properties (System.getProperty), .env files or config loaders — out of scope; this covers org.ek9.lang::EnvVars only." ],
        "examples" : [ "{} -> status:ok; names[] then envVars[] (one per call site with site, accessMethod, sanitization, inConstruct) + summary{staticAccessCount, uniqueNameCount, perAccessMethod}.", "{accessMethodFilter:[\"unsanitizedGet\"]} -> every sanitisation-bypassing read (should be near-empty at prod scale)." ],
        "details" : "Each entry carries a derived sanitization (get→sanitized, unsanitizedGet→unsanitized, sensitiveGet→sensitive-wrapped, contains→n/a) plus site ('file.ek9:LINE'), file/line/column and inConstruct/inConstructFqn — feed the FQN straight into ek9_query_call_graph to trace who reaches a secret read or where its value flows. One entry per CALL SITE, so staticAccessCount ≥ uniqueNameCount; runtime-computed names land in dynamicAccesses[]. Unmatched envVarNames/moduleFilter values are reported in meta.unmatchedFilters[] with status ok/empty, NOT errors — error only on an accessMethodFilter value outside the enum or no compiled program.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "accessMethodFilter" : {
              "description" : "Restrict to these access methods. Example: [\"unsanitizedGet\"] for a security audit. Default: all four.",
              "items" : {
                "enum" : [ "get", "unsanitizedGet", "sensitiveGet", "contains" ],
                "type" : "string"
              },
              "type" : "array"
            },
            "envVarNames" : {
              "description" : "Restrict to specific env-var names (case-sensitive EXACT match per name, e.g. [\"DATABASE_URL\", \"AUTH_SECRET\"]). Not a substring pattern — env-var names are a set of distinct identifiers. Default: all discovered.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "includeDynamic" : {
              "default" : true,
              "description" : "Include the dynamicAccesses[] array flagging accesses where the env-var name is a runtime expression rather than a literal. Set false to suppress when only the statically-resolvable surface matters.",
              "type" : "boolean"
            },
            "moduleFilter" : {
              "description" : "Restrict to these exact module names. Default: workspace-wide.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_capability_usage",
        "description" : "Answers first: consistent, underDeclared, overDeclared, usedCapabilities, declaredCapabilities and findings, then actualUsage with every call site - the workspace-wide gated-type audit against the package block.",
        "idePrecedent" : "EK9 IDE — \"Capabilities\" navigator (left dock): declared-vs-required gated-type audit; under-declared (compile-blocking) and over-declared (unnecessary attack surface) findings with Add/Remove capability quick-fixes; row-click navigate to each call site",
        "whenToUse" : "Which capabilities does the code actually need versus what the package block declares - one call, no arguments; underDeclared and overDeclared answer it. Capabilities are the gated types: network, filesystem, env-var, stdio, process, clock.",
        "whenNotToUse" : [ "JUST the declared capabilities, not the actual-vs-declared comparison — use ek9_query_package.", "Auditing secret-handling code (Sensitive.reveal callers), not capability usage — use ek9_query_sensitive_reveals. ('what env vars do I read?' is a CAPABILITY question, use THIS tool.)", "You want the LIST of capability errors (use ek9_query_diagnostics) or one E12010 explained (use ek9_explain_error), not the full usage analysis.", "You want method-level call relationships, not capability-level access — use ek9_query_call_graph." ],
        "examples" : [ "{} -> status:ok; consistent, underDeclared, overDeclared, usedCapabilities, declaredCapabilities, findings, then actualUsage keyed by FQ gated-type with its call sites.", "{capability:'org.ek9.lang::TCP'} -> same audit (workspace-wide) but actualUsage call sites narrowed to TCP; an unused value returns {used:false, callSites:[]}, not an error." ],
        "details" : "Capabilities are FQ gated-type names (org.ek9.lang::TCP / Stdout / File / EnvVars / OS / Clock …), NOT abstract kinds; the audit mirrors phase-5 CapabilityTypeReferenceOrError via the shared GatedTypeMapping, so underDeclared is EXACTLY the set that fires E12010 and overDeclared is declared-but-unused (leave-one-out, so alt-coverage like NetworkProperties covered by TCP/UDP is respected). The audit verdict applies ONLY when publishable:true (publicAccess + version + license + capabilities) — otherwise usage is informational, consistent is vacuously true and a note says so — and stays workspace-wide regardless of the filters, which narrow displayed call sites only. actualUsage is an OPEN map keyed by capability → {used, callSites[{site,file,line,column,type,module,inConstruct,inConstructFqn}]}; needs a compile reaching types_resolved (unresolved types are silently not counted); remediate via ek9_add_capability / ek9_remove_capability.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "capability" : {
              "description" : "Optional filter — restrict actualUsage to a single fully-qualified capability (e.g. \"org.ek9.lang::TCP\", \"org.ek9.lang::Stdout\"). An open string, NOT an enum — an unknown value simply matches nothing. Omit for all capabilities. Narrows the displayed call sites only; the audit remains workspace-wide.",
              "type" : "string"
            },
            "moduleFilter" : {
              "description" : "Optional: exact module names (an entry ending in * is a prefix) whose call sites to display; a bare string is accepted as a one-element list. Omit for workspace-wide. Narrows displayed call sites only; the audit remains workspace-wide.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_sensitive_reveals",
        "description" : "Answers first: status, revealCount, violationCount, privilegedCount, violationSites as file.ek9:LINE, then reveals[] {file, line, column, module, inConstruct, inConstructFqn, privilege}.",
        "idePrecedent" : "EK9 IDE — \"Sensitive Reveals\" navigator (left dock): every Sensitive.reveal() call grouped by legality (⚠ unprivileged / in-function violations = E11090 first, then ✓ privileged), row-click navigate + Show call graph",
        "whenToUse" : "'Where are secrets revealed, and which reveals are illegal (E11090)' is one call with no arguments - every Sensitive.reveal() site with its privilege verdict.",
        "whenNotToUse" : [ "env-var, network or filesystem capability audits, not the Sensitive type (use ek9_query_capability_usage)", "just the LIST of E11090 errors, not the full reveal-site audit (use ek9_query_diagnostics)", "E11090 explained rather than checked across the workspace (use ek9_explain_error)", "references to some arbitrary symbol (use ek9_query_references; this tool is specific to Sensitive.reveal())" ],
        "examples" : [ "{} -> status:ok, revealCount, violationCount, privilegedCount, violationSites ['audit.ek9:22', ...], then reveals[] with privilege per site; status:empty when no reveals exist.", "{moduleFilter:['com.acme.orders']} -> reveals only from that module ('com.acme.*' for a prefix)." ],
        "details" : "Type-resolution-driven (mirrors the compiler's PrivilegedRevealOrError check): only calls that RESOLVE to Sensitive.reveal are counted, so a workspace compiled below FULL_RESOLUTION silently under-reports — run ek9_compile first for an audit-grade result. Per-reveal privilege: privileged (legal) / unprivileged (caller class lacks Privileged, E11090) / in-function (reveal in a free function, which can never be Privileged, E11090); inConstruct is 'Class.method' or the function name, inConstructFqn its qualified identity. Revealing secrets into log strings is almost always a bug even when it passes the Privileged check.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "moduleFilter" : {
              "description" : "Restrict to these modules - exact names; an entry ending in '*' is a prefix ('com.acme.*'). A single string is accepted. Omit for workspace-wide.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_service_routes",
        "description" : "Answers first: status, programState, routeCount, byService, then routes[] {verb, uri, fullPath, parameters with binding + sanitized, securityGate, cors, auditWarnings} and summary counts.",
        "idePrecedent" : "EK9 IDE — \"Service Routes\" diagram (bottom pane): services → routes → params with per-node click-to-source, security gate / CORS shown as cross-file jump targets, unsanitized-input and unguarded-route findings highlighted",
        "whenToUse" : "'What is the HTTP attack surface - every route, its inputs, which are unsanitized or unguarded' is one call, no arguments; verb/service/uri/audit narrow it.",
        "whenNotToUse" : [ "how services get their components wired, DI, not the route list (use ek9_query_dependency_injection)", "whether services declare/require the network capability, not HTTP routes (use ek9_query_capability_usage)", "whether routes ever surface secrets, the secret-handling audit (use ek9_query_sensitive_reveals)", "the full structure of one specific service class, not the route catalogue (use ek9_query_construct)" ],
        "examples" : [ "{} → status:ok, full inventory: routeCount, byService, routes[] (verb/uri/fullPath/parameters/securityGate/cors), summary counts.", "{verb:'POST', audit:'unsanitized'} → only POST routes with >=1 un-sanitized input param — the XSS/injection candidates, each carrying auditWarnings[].fix." ],
        "details" : "status ok | empty (empty = no 'defines service' blocks, or filters matched nothing); needs a prior ek9_compile. parameters[].binding is UPPERCASE (PATH/QUERY/HEADER/CONTENT/REQUEST/CONTEXT); securityGate/cors are the declaring FUNCTION reference {name,fqn,file,line} (usually another file) — the compiler stores only the reference, null = unguarded / no CORS. auditWarnings flags unsanitized-{query,header,content} (PATH excluded, template-constrained) and unguarded-route; reuses the compiler's own ServiceMetadataExtractor, so the inventory never diverges from generated routing.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "audit" : {
              "description" : "Audit-level filter: all (default), unsanitized (routes with >=1 un-sanitized input parameter), unguarded (routes without a security gate).",
              "enum" : [ "all", "unsanitized", "unguarded" ],
              "type" : "string"
            },
            "service" : {
              "description" : "Optional service-name SUBSTRING filter ('Order' matches OrderService).",
              "type" : "string"
            },
            "uri" : {
              "description" : "Optional fullPath SUBSTRING filter ('/addresses' matches every route under it).",
              "type" : "string"
            },
            "verb" : {
              "description" : "Filter by HTTP verb (default all).",
              "enum" : [ "all", "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS" ],
              "type" : "string"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_diagnostics",
        "description" : "Answers first: diagnosticCount, errorCount, warningCount, errorCodes[] and sites[] ('file.ek9:LINE CODE message'), then diagnostics[] in full, byCode and byFile - the Problems View of the last compile.",
        "idePrecedent" : "Problems View (IntelliJ / VSCode)",
        "whenToUse" : "What is broken right now - every error and warning recorded by the last compile - one call, no arguments (fileFilter / severity / errorCode narrow it).",
        "whenNotToUse" : [ "One error code's WHY/QUICK FIX/EXAMPLE, not the current problem list — use ek9_explain_error.", "You want to TRIGGER a compile rather than read already-recorded diagnostics — use ek9_compile (it produces diagnostics; this tool reads them).", "You want build-state metadata (last outcome, freshness) rather than the diagnostics list — use ek9_query_build_status.", "You want TEST pass/fail results rather than COMPILE diagnostics — use ek9_run_tests." ],
        "examples" : [ "{} → all workspace diagnostics: {diagnosticCount, errorCount, warningCount, errorCodes[], sites['a.ek9:12 E05030 ...'], diagnostics[], byCode, byFile}.", "{severity:'errors', errorCode:'E08100'} → only that code's build-blocking errors; no match returns diagnostics:[] (success, not an error)." ],
        "details" : "Each diagnostic carries file (workspace-relative), line, column, severity, errorCode ('SYNTAX' for a parse error, which has no E-code), message (one line, no colour codes, no embedded explanation), typeOfError and classificationDescription, plus a richExplanation when the code has one; quickFixes and offendingSymbol are on by default while there are 20 diagnostics or fewer (pass the include* flag to force either way), fuzzySuggestions and tokenLength are opt-in, and codes with a mapped fix tool gain a remediation[] list. severity is all|errors|warnings, fileFilter is a path substring, and a filter matching nothing yields diagnostics:[] with zero counts — success, not an error. byCode and byFile count diagnostics (errors+warnings combined); no workspace returns {diagnostics:'unavailable', message:...}.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "errorCode" : {
              "description" : "Optional specific error code filter (e.g., 'E05030')",
              "type" : "string"
            },
            "fileFilter" : {
              "description" : "Optional: keep only diagnostics whose file path contains this text",
              "type" : "string"
            },
            "includeFuzzySuggestions" : {
              "description" : "Include 'Did you mean?' fuzzy match suggestions per diagnostic. Default false.",
              "type" : "boolean"
            },
            "includeOffendingSymbol" : {
              "description" : "Include the likely-offending symbol name per diagnostic. Default: on while there are 20 diagnostics or fewer.",
              "type" : "boolean"
            },
            "includeQuickFixes" : {
              "description" : "Include actionable replacement suggestions extracted from parse errors and fuzzy matches. Default: on while there are 20 diagnostics or fewer.",
              "type" : "boolean"
            },
            "includeTokenSpan" : {
              "description" : "Include tokenLength for highlight rendering. Default false.",
              "type" : "boolean"
            },
            "severity" : {
              "description" : "Severity filter: 'all' (default), 'errors', or 'warnings'",
              "enum" : [ "all", "errors", "warnings" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_effective_api",
        "description" : "Answers first: declaredMethodNames, inheritedMethodNames, overrides as 'method:Trait', operatorNames, traitNames - then every member in full with its source and file/line.",
        "idePrecedent" : "Show Members (IntelliJ) / Class View",
        "whenToUse" : "What can be called on a type - declared, inherited, which methods override a trait, its operators - one call with symbol:'X', no file needed.",
        "whenNotToUse" : [ "ONLY the type's declared structure (own bodies/fields, no merged inherited/trait/defaulted members) - use ek9_query_construct instead.", "You want the supertype/subtype graph ('what extends X?') not the member list - use ek9_query_type_hierarchy instead.", "You want ONE member at a cursor - use ek9_inspect_symbol_at_cursor; everything in scope at a position - use ek9_list_visible_symbols.", "You want the API of a BUILT-IN type (String, List) - use ek9_help instead." ],
        "examples" : [ "{symbol:'CustomerOrder'} -> {type, declaredMethodNames, inheritedMethodNames, overrides, operatorNames, traitNames, genus, superChain/traits, methods/operators/constructors arrays (+fields for records)}; each member carries source (declared|inherited|trait|defaulted) + a file/line jump anchor.", "Line on/inside a function -> a signature summary (genus:FUNCTION, parameters, returnType, pure); module-level or non-code line -> effectiveApi:not_found with a message." ],
        "details" : "Genus-dependent shape: a fields[] array appears only for records (record fields are public; class/component/service/application fields are private and excluded, traits have no fields), and only inherited record fields carry inheritedFrom. Per-member source is declared / inherited / trait / defaulted (compiler-synthesised operator), with the declaring site in each member's file/line; re-declared members add override:true plus overrides:{from,at:{file,line}} — follow recursively to trace override chains. Reliable merge needs FULL_RESOLUTION (Phase 7+); earlier phases mis-attribute inherited/trait/defaulted members with no PARTIAL_DATA warning to flag it.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "column" : {
              "description" : "0-based char offset of the target identifier on 'line' (an IDE cursor). OMIT it and pass symbol:'<name>' instead, or omit both and the first identifier on the line is taken (right for a declaration line). Reuse a column from ek9_inspect_symbol_at_cursor verbatim.",
              "type" : "integer"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control — see EK9_TOOL_SPECIFICATIONS.md §1.6. full (default) returns the complete result; summary trims per-item detail (call_graph to its headline names and sites, effective-api to member names); none collapses lists to counts. A very large response auto-degrades with an explicit `truncated` marker.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_type_hierarchy",
        "description" : "Answers first: subtypeNames (implementedBy on a trait), superNames, traitNames, allowedSubtypes for a sealed trait - complete workspace-wide - then the full graph.",
        "idePrecedent" : "Type Hierarchy (IntelliJ)",
        "whenToUse" : "'Who extends or implements X, and what does X extend' is one call with symbol:'X' - no file needed; file + line only from an IDE cursor.",
        "whenNotToUse" : [ "the methods/operators callable on the type, not its ancestry (use ek9_query_effective_api)", "the structure (own fields/methods) of ONE construct, not the inheritance chain (use ek9_query_construct)", "details of a single symbol under the cursor, not the whole tree (use ek9_inspect_symbol_at_cursor)", "the API of a BUILT-IN language type (String, List, …) (use ek9_help; this tool only walks workspace types)" ],
        "examples" : [ "{symbol:'Shape'} → status:ok, type, subtypeNames, superNames, traitNames, then name/fullyQualifiedName/declaredAt, superChain, traits, and all transitive subtypes with per-subtype depth (1=direct child).", "A leaf type → superChain populated, subtypes:[], subtypeCount:0. A line not on a type → status:'not-found' with a note." ],
        "details" : "Hierarchy queries are meaningful only for CLASS, TRAIT, RECORD, COMPONENT; SERVICE/APPLICATION/PROGRAM/ENUMERATION return basic info with empty superChain/subtypes, and a location resolving to no aggregate type returns status:'not-found'. Two depth metrics: root-level `depth` is distance from the inheritance root DOWN to the queried type; each subtype's `depth` is distance FROM the queried type (1=direct child). `subtypes` holds ALL transitive descendants (counted by subtypeCount/summary.totalDescendants; summary.directSubtypes counts depth 1 only), plus isOpen (always) and, when applicable, transitive allTraits, sealed/allowedSubtypes/exhaustive, and generic markers.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "among" : {
              "description" : "Names to answer FOR: 'which of these ...' - the name lists shrink to these and amongUnmatched names the ones that are not in the answer. Bare names, 'mod::Type' or 'Type.member'; a MODULE name stands for every construct in it ('which in scale.m07 do not ...' = amongUnmatched).",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "column" : {
              "description" : "0-based char offset of the target identifier on 'line' (an IDE cursor). OMIT it and pass symbol:'<name>' instead, or omit both and the first identifier on the line is taken (right for a declaration line). Reuse a column from ek9_inspect_symbol_at_cursor verbatim.",
              "type" : "integer"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control — see EK9_TOOL_SPECIFICATIONS.md §1.6. full (default) returns the complete result; summary trims per-item detail (call_graph to its headline names and sites, effective-api to member names); none collapses lists to counts. A very large response auto-degrades with an explicit `truncated` marker.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_references",
        "description" : "Answers first: useSiteCount, usedByConstructs, useSites as file.ek9:LINE - then each reference with its source line (omitted above 50 sites unless verbosity:'full').",
        "idePrecedent" : "Find Usages (IntelliJ) / Find All References (VSCode)",
        "whenToUse" : "'Who uses X, and from which constructs' is one call with symbol:'Type.member' - no file needed; the pre-flight for rename, safe-delete and change-signature.",
        "whenNotToUse" : [ "a method's incoming AND outgoing call structure in one query (use ek9_query_call_graph)", "what a type EXPOSES or how it RELATES (supertypes/subtypes/traits), not where it is used (use ek9_query_effective_api or ek9_query_type_hierarchy)", "raw text occurrences (comments, string literals) (use ek9_search_text; this tool resolves structural references via the type system)" ],
        "examples" : [ "{symbol:'Validator.validate'} → status:ok, name 'validate', useSiteCount:3, usedByConstructs, useSites ['orders.ek9:50', ...], then each reference with its kind and productionVsTest split.", "Zero-reference public symbol → useSiteCount:0, useSites:[] — a safe-delete candidate; follow with ek9_safe_delete or confirm it is intentional external API." ],
        "details" : "Anchor symbol may be any kind the compiler tracks: types, methods, functions, fields, constants, and locals / parameters / returning params inside a body. Two distinct enums on the response: top-level 'kind' is uppercase (METHOD, FIELD, TYPE…); per-reference 'kind' is lowercase snake_case — declaration (always first), structural (extends, trait_implementation, field_type, parameter_type, return_type…), calls (method_call, function_call, constructor_call) and identity-match body usages (variable_usage, parameter_usage, field_access, constant_usage…). A use of a VARIABLE (field, parameter, local, returning variable, constant) carries access:'read' or access:'write' (with writeOperator), and the headline gives writeSiteCount and writeSites - who CHANGES it. A write is an assignment (':', ':=', '=', ':=?') or a mutating operator ('+=', ':=:', ':~:', ':^:', '++', ...), so 'order.total += 5' is a write though it stores nothing to the field; a mutating METHOD call ('items.add(x)') is not. For a PROPERTY the writeSites are every line that can change its value on any instance (THE property write-site query): direct, through an alias ('t <- order.total; t += 5'), a view ('t <- order.getTotal()'), a method that is not pure on its object, or a user callee data touch says writes its parameter - writeSitesByRoute counts them and writes[] gives each with foundBy, operation, certain (false for view/method/callee: they MAY change it), through, fedBy and, for a callee, origin (where inside it the write is). A reference whose type was INFERRED (no text names the symbol: 'if product <- store.findProduct(id)' for Product) carries explicit:false - one per name that received the type - as does a method call on an instance for its type ('$product.getPriority()' does not spell Product); a site whose text names the symbol is explicit:true. When any are inferred the headline gives explicitUseSiteCount and inferredUseSiteCount; include:'explicit' keeps only the sites that name it. An overloaded 'Type.member' with no signature (a constructor 'Type.Type' included) answers for EVERY overload: useSites is their union and overloads[] holds each one's answer tagged with its signature, resendable as 'Type.m(P)'; when they form a dispatcher the name means its entry point. summary.productionVsTest is a PATH HEURISTIC (test = path contains a 'test' segment), not authoritative; a cursor on whitespace/comment/unresolvable returns status:'not-found', not an error.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "among" : {
              "description" : "Names to answer FOR: 'which of these ...' - the name lists shrink to these and amongUnmatched names the ones that are not in the answer. Bare names, 'mod::Type' or 'Type.member'; a MODULE name stands for every construct in it ('which in scale.m07 do not ...' = amongUnmatched).",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "column" : {
              "description" : "0-based char offset of the target identifier on 'line' (an IDE cursor). OMIT it and pass symbol:'<name>' instead, or omit both and the first identifier on the line is taken (right for a declaration line). Reuse a column from ek9_inspect_symbol_at_cursor verbatim.",
              "type" : "integer"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "include" : {
              "description" : "Which use sites: 'all' (default), 'explicit' (only sites whose text names the symbol) or 'inferred' (only names whose type was INFERRED as it, e.g. 'p <- find(id)' for Product - one per name). The headline counts both either way.",
              "enum" : [ "all", "explicit", "inferred" ],
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control — see EK9_TOOL_SPECIFICATIONS.md §1.6. full (default) returns the complete result; summary trims per-item detail (call_graph to its headline names and sites, effective-api to member names); none collapses lists to counts. A very large response auto-degrades with an explicit `truncated` marker.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_dispatchers",
        "description" : "Answers first: routing[] - per dispatcher method its dispatchOn type, typesWithOwnHandler, typesUsingInheritedHandler (served by a supertype's handler) and typesFallingBack (to the dispatchOn entry point's own body), with routingCovers when the lists are whole - then the full dispatcherMethods[] with resolutionTable; on a sealed type, allowedSubtypes[]. Do not read the class and simulate it.",
        "whenToUse" : "How does a dispatcher method route - which subtypes have their OWN handler and which fall back to the base handler - one call with symbol:'<dispatcher class>'. No file needed; on a sealed type, its allowed subtypes.",
        "whenNotToUse" : [ "General supertype/subtype inheritance graph (not dispatch-specific) - use ek9_query_type_hierarchy.", "Caller/callee relationships of a method rather than the dispatch table - use ek9_query_call_graph.", "Structural info (fields, methods, modifiers) or the callable member list of a type - use ek9_query_construct (structure) or ek9_query_effective_api (callable surface).", "Which dispatchers reference a sealed type (cross-reference not yet emitted here) - use ek9_query_references on the sealed type." ],
        "examples" : [ "Dispatcher anchor: {symbol:'Renderer'} -> routing[] first (per dispatcher method: dispatchOn, typesWithOwnHandler, typesUsingInheritedHandler {type: handler's type}, typesFallingBack, routingCovers), then kind:'dispatcher' with dispatcherMethods[], each carrying signature, dispatchType/dispatchTypeFqn, handlers[] (handlesType + routedTypes per handler) and resolutionTable[].", "Dispatch hole: a 'hierarchy' universe with unhandledTypes:['Triangle'] means Triangle reaches the entry point's own fallback body - add a handler or accept the fallback.", "Open base: an 'Any' dispatch type returns dispatchUniverse:'open' with a note and NO fallbackCount - absence of a gap count there is not absence of gaps.", "Sealed-type anchor: {symbol:'Animal'} -> kind:'sealed_type' with allowedSubtypes[] (simple names) and subtypeCount." ],
        "details" : "Auto-detected (no 'mode' parameter): routing[] is present for a dispatcher, allowedSubtypes[] for a sealed type, and a type that is neither returns dispatcher:'not_applicable' with a message (or 'not_found' when there is no enclosing type at the anchor). Dispatcher shape: dispatcherMethods[] + dispatcherMethodCount - drawn from the FULL inheritance chain, so an entry point or handler declared in a supertype is included - each method with signature, pure, dispatchType(+Fqn), allowedSubtypes[] when the dispatch parameter type is sealed, and handlers[] (handlesType and routedTypes per handler), plus top-level sealedTypeDispatched (the FQ sealed supertype handled). ROUTING (present when the workspace is compiled): resolutionTable[] has one row per concrete type that can arrive - typeName, typeGenus, category DIRECT (handler names this exact type) / INHERITED (won via a supertype, with resolvedVia and cost) / ENTRY (the dispatch type itself, when it is concrete - served by the entry point, not a gap) / FALLBACK (no handler; the entry point's own body runs) - plus totalTypes, directCount and inheritedCount. dispatchUniverse says how far to trust the gap analysis: 'sealed' (closed by the compiler, gaps provable), 'hierarchy' (a trait / abstract class / abstract function base - gaps real but a snapshot, since another implementor may be declared later), or 'open' (an Any base). For sealed and hierarchy you also get fallbackCount, unhandledTypes[] (the FALLBACK rows - routing[].typesFallingBack is the same list) and exhaustive, and routing[].routingCovers says the lists are whole. For 'open' those are DELIBERATELY ABSENT and a note explains why: an Any base admits every type in the program and every type not yet written, so 'types you did not handle' has no fixed answer and a fallbackCount of 0 would read as a false all-clear. Sealed shape: allowedSubtypes[] (simple-name strings) + subtypeCount. Note a class is reported as a dispatcher only when it has a NON-OPERATOR entry point: argument-taking operators such as '==' are marked as dispatchers internally so they resolve on the actual type, and that is not a user-level dispatcher.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "among" : {
              "description" : "Names to answer FOR: 'which of these ...' - the name lists shrink to these and amongUnmatched names the ones that are not in the answer. Bare names, 'mod::Type' or 'Type.member'; a MODULE name stands for every construct in it ('which in scale.m07 do not ...' = amongUnmatched).",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "column" : {
              "description" : "0-based char offset of the target identifier on 'line' (an IDE cursor). OMIT it and pass symbol:'<name>' instead, or omit both and the first identifier on the line is taken (right for a declaration line). Reuse a column from ek9_inspect_symbol_at_cursor verbatim.",
              "type" : "integer"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_build_status",
        "description" : "Answers first: programState, queriesReliable with its reason, buildOutcome, lastPhaseReached, then sourceCount/sourcesClean/sourcesWithErrors and sources[] with per-file error and warning counts.",
        "whenToUse" : "Has the workspace compiled, and can the query tools be trusted right now - one call, no arguments (fileFilter narrows the per-source list).",
        "whenNotToUse" : [ "The LIST of errors/warnings rather than build-state metadata - use ek9_query_diagnostics.", "You want to TRIGGER a compile, not query existing state - use ek9_compile.", "You want the structural inventory (constructs, modules) - use ek9_query_workspace.", "You want the compiler binary version, not workspace build state - use ek9_version." ],
        "examples" : [ "{} -> programState:'clean', queriesReliable:true, reason:'reached PRE_IR_CHECKS', buildOutcome:'success', lastPhaseReached, phaseNumber/totalPhases, sources[] per-file error flags.", "before compile -> {programState:'unavailable', queriesReliable:false, reason:'no compiled program', nextStep:'ek9_compile'}." ],
        "details" : "A gate-check that always succeeds at any phase — the result IS the report. queriesReliable is true once the compile reached PRE_IR_CHECKS (the phase that builds the call graph and the metrics the symbol-anchored query tools read); reason names the phase reached. buildOutcome is success (no source has errors), partial-success (some sources clean) or failure; programState is the shared clean / partial / unavailable envelope every architecture enquiry carries.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "fileFilter" : {
              "description" : "Optional: keep only sources whose path contains this text",
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_call_graph",
        "description" : "Answers first: callerNames / calleeNames and callerSites / calleeSites ('file.ek9:LINE') - then the chain to the requested depth.",
        "idePrecedent" : "Call Hierarchy (IntelliJ)",
        "whenToUse" : "Who calls a method or function, and what does it call, to a depth - one call with symbol:'Type.member', no file needed.",
        "whenNotToUse" : [ "Every reference to a symbol (types, fields, not just method calls) — use ek9_query_references (symbol-level usage, not call chains).", "Analysis WITHIN one body — use ek9_query_flow for control flow (branches, loops), or ek9_query_pipeline for stage-level stream-pipeline analysis.", "All callable members of a TYPE, not one method's calls — use ek9_query_effective_api.", "Details of one symbol at a cursor rather than call chains — use ek9_inspect_symbol_at_cursor." ],
        "examples" : [ "{symbol:'Order.total'} → both directions, depth 1: callerNames/calleeNames and callerSites/calleeSites first, then incomingCalls[] + outgoingCalls[] with the target's signature/complexity metrics.", "{symbol:'Order.total', direction:'incoming', depth:3} → callers only, 3 hops (refactor-impact: what breaks if I change this)." ],
        "details" : "direction: 'both' (default) | 'outgoing' (callees, what this depends on) | 'incoming' (callers, refactor-impact); depth 0-5 (default 1, capped at 5), with depth>1 recursing via a nested children[] per node — hop level is implicit in nesting. Response: the headline names and sites, the target's flat signature/complexity fields, then incomingCalls[]/outgoingCalls[]; each node carries caller or callee (display name), fullyQualifiedName, calledAt 'file.ek9:LINE' (the call expression; a caller calling more than once has its first here and the rest in alsoCalledAt[]), declaredAt 'file.ek9:LINE' (the callable's declaration), callKind (function-call | method-call), pure and context (the source line at the call site). The graph is built during PRE_IR_CHECKS — a workspace compiled to an earlier phase errors with the reached phase (the default ek9_compile phase suffices), and a line not on a method/function returns {callGraph:'not_found', message:...}.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "among" : {
              "description" : "Names to answer FOR: 'which of these ...' - the name lists shrink to these and amongUnmatched names the ones that are not in the answer. Bare names, 'mod::Type' or 'Type.member'; a MODULE name stands for every construct in it ('which in scale.m07 do not ...' = amongUnmatched).",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "depth" : {
              "description" : "Maximum traversal depth (0-5, default 1)",
              "type" : "integer"
            },
            "direction" : {
              "description" : "Direction: 'both' (default), 'outgoing', or 'incoming'",
              "enum" : [ "both", "outgoing", "incoming" ],
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control — see EK9_TOOL_SPECIFICATIONS.md §1.6. full (default) returns the complete result; summary trims per-item detail (call_graph to its headline names and sites, effective-api to member names); none collapses lists to counts. A very large response auto-degrades with an explicit `truncated` marker.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_find_constructs_by_name",
        "description" : "Answers first: matchCount, then matches[] (name, fullyQualifiedName, module, genus, file, line, column) - workspace-wide; matches:[] when no top-level construct has that name.",
        "idePrecedent" : "Search Everywhere (IntelliJ) / Go to Symbol in Workspace (VSCode)",
        "whenToUse" : "Where is a construct named X declared (file, line, module) - one call with name:'X', or name:'Cust*' for a case-insensitive prefix.",
        "whenNotToUse" : [ "The construct's detail rather than its location - ek9_query_construct, ek9_query_type_hierarchy, ek9_query_effective_api and ek9_query_call_graph all take symbol:'X' directly, no lookup first.", "A built-in language type (String, List, Optional, …) — those live in the language not the workspace; use ek9_help <Type> instead.", "You already have file+line+column — no name lookup needed; use ek9_inspect_symbol_at_cursor.", "Not a name lookup at all: free-text search over source content (comments, strings) is ek9_search_text; a whole-workspace overview is ek9_query_workspace." ],
        "examples" : [ "{name:'CustomerOrder'} -> mode:exact, matchCount:2 (a CLASS in com.acme.orders and a RECORD in com.acme.invoices) — disambiguate by module before chaining.", "{name:'Cust*'} -> mode:prefix, every construct whose name starts with Cust (case-insensitive) — the IDE search-as-you-type path." ],
        "details" : "Mode is selected purely by the value: exact case-sensitive match by default, case-insensitive prefix with a trailing `*` (a bare `*` is an error, not a list-all). Each match carries name, fullyQualifiedName (module `::` construct — preserve the `::` when chaining), module (always the DEFINING module; matches deduplicated by FQN), genus, file (workspace-relative), line and 0-based column. No matches is success with matchCount:0 and matches:[], never an error; requires the workspace to have reached SYMBOL_DEFINITION.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "name" : {
              "description" : "Unqualified name to search for. Append `*` for case-insensitive prefix matching (e.g. \"Cust*\"). Without `*`, exact case-sensitive match.",
              "type" : "string"
            }
          },
          "required" : [ "name" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_find_symbols",
        "description" : "Answers first: symbolCount, symbolNames, withinNames (Type.method each belongs to), siteCount, byKind, byUse, sites as file.ek9:LINE - then each symbol (kind, within, declaredAt, renameable, counts) with its declared/read/written sites.",
        "idePrecedent" : "Find in Project, symbols mode (EK9 IDE) - no direct IntelliJ / VS Code equivalent: Find Usages starts from ONE symbol",
        "whenToUse" : "Which symbols are NAMED like X, of which kinds, and where each is declared / read / written - one call: {name:'productId', kinds:['loop_variable']}. Pre-flight for a pattern rename (renameTo), a naming audit (kinds:['variables'], name:'tmp'), or 'is anything still reading X' after an edit.",
        "whenNotToUse" : [ "Every use of ONE symbol you can already name, with call structure, inferred-type uses and property writes by any route (alias, view, impure method) - ek9_query_references (symbol:'Type.member').", "Text in comments and Strings - ek9_search_text.", "Where a construct is declared, by name or prefix - ek9_find_constructs_by_name (lighter).", "Applying a rename - ek9_rename_symbol (this tool only prepares the request)." ],
        "examples" : [ "{name:'productId', kinds:['loop_variable']} → symbolCount:1, within 'OrderProcessor.processOrder', sites flow.ek9:91 declared, :94 and :116 read.", "{name:'Count', match:'contains', kinds:['variables'], renameTo:'Total'} → every variable with Count in its name and renameRequest - send it to ek9_rename_symbol (a dry run; apply from its preview)." ],
        "details" : "A name matches by 'match': exact (the whole name, default), contains, or regex (Java, tested against the name - anchor it yourself). caseSensitive defaults true; otherCaseNames lists what ignoring case would add. kinds take single kinds (class ... loop_variable) and the group words constructs / members / variables; default all. uses keeps only the declared / read / written sites and drops a symbol left with none. 'written' is the name assigned (':', ':=', '=', ':=?') or a mutating operator ('+=', ':=:', '++', ...) at that site - not the property write-site analysis (that is ek9_query_references). Scope: file, module (every file of it), or neither for the workspace; a symbol declared outside the scope but used in it is found with its in-scope sites. Built-ins are never returned. A construct carries fullyQualifiedName (module::Name); a member or variable carries within ('Order', 'OrderProcessor.processOrder'). Answers from the LAST compile: filesWithErrors lists the searched files that did not compile clean - names in their broken parts may be missing. Lines are 1-based, columns 0-based. renameTo returns renameRequest (the ek9_rename_symbol arguments, dryRun:true, the first symbol plus alsoRename) with each new name as the IDE gives it ('Count'->'Total' turns itemCount into itemTotal; regex may use $1); operators and constructors, and names left unchanged, are in renameSkipped.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "caseSensitive" : {
              "description" : "Default true.",
              "type" : "boolean"
            },
            "file" : {
              "description" : "Scope: this one file. Omit file and module for the whole workspace.",
              "type" : "string"
            },
            "kinds" : {
              "description" : "Kinds to find: single kinds and/or the groups constructs, members, variables. Default all.",
              "items" : {
                "enum" : [ "class", "record", "trait", "component", "function", "abstract_function", "enumeration", "type", "text", "service", "program", "application", "property", "method", "operator", "constructor", "parameter", "return", "local", "loop_variable", "guard", "capture", "constant", "constructs", "members", "variables" ],
                "type" : "string"
              },
              "type" : "array"
            },
            "match" : {
              "description" : "exact (whole name, default), contains (anywhere in the name), or regex (Java, against the name).",
              "enum" : [ "exact", "contains", "regex" ],
              "type" : "string"
            },
            "maxSymbols" : {
              "description" : "Default 200, max 2000. The answer is the first maxSymbols symbols; a cut one says truncated:true and moreSymbols (how many more).",
              "type" : "integer"
            },
            "module" : {
              "description" : "Scope: every file of this module.",
              "type" : "string"
            },
            "name" : {
              "description" : "The name pattern - matched against symbol NAMES, never comments or Strings.",
              "type" : "string"
            },
            "renameTo" : {
              "description" : "Also return renameRequest - the ek9_rename_symbol arguments renaming every symbol found. Nothing is applied.",
              "type" : "string"
            },
            "sourceLines" : {
              "description" : "Give each site its source line (text). Default false.",
              "type" : "boolean"
            },
            "uses" : {
              "description" : "Which sites: declared, read, written. Default all three.",
              "items" : {
                "enum" : [ "declared", "read", "written" ],
                "type" : "string"
              },
              "type" : "array"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "full (default): each symbol with its sites; summary: each symbol without its sites; none: the headline alone.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "name" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_dependency_injection",
        "description" : "Answers first: applicationCount and applications[] (registrations, injectionSites, boundPrograms, health incl. E11040 over-injection), programCount and programs[], then summary.",
        "idePrecedent" : "Spring Bean Graph (IntelliJ Ultimate)",
        "whenToUse" : "How is dependency injection wired - which application registers what, where it is injected, which programs bind - one call, no arguments. Narrow to one application with applicationFilter.",
        "whenNotToUse" : [ "Module-level imports (references) rather than component wiring — use ek9_query_module_dependencies.", "Method-level call relationships rather than component injection — use ek9_query_call_graph.", "Compile errors/warnings themselves (E11040 over-injection, E08190 DI cycle) rather than the wiring graph — use ek9_query_diagnostics.", "The structure of ONE specific component rather than the app-level graph — use ek9_query_construct." ],
        "examples" : [ "{} → full DI graph: applicationCount, each application's registrations/injectionSites/boundPrograms/health, plus summary.", "{applicationFilter:'OrderApp'} → only matching applications (substring); no match → applicationCount:0, applications:[] (empty, not an error)." ],
        "details" : "Registrations report implementation/implementationFqn, kind ('component' adds interface/interfaceFqn; 'service' does not), optional aspects[] (components are always singleton-scoped — no scope field); injectionSites carry site ('Owner.field'), owner, field, type, typeFqn, satisfied. health.verdict is HEALTHY / UNSATISFIED_INJECTIONS / NO_REGISTRATIONS (satisfaction-based only); health.overInjectedComponents[] is the E11040 >4-injected-fields gate — one entry per offending component (fields[], injectionCount, limit), it does NOT flip verdict, and non-empty entries feed the split_overinjected_component workflow. Requires a prior compile; no compiled program → error.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "applicationFilter" : {
              "description" : "Optional: keep only applications whose name contains this text",
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_module_dependencies",
        "description" : "Answers first: status, moduleCount, edgeCount, hasCycles, then modules[] {name, files, constructCount}, dependencies[] {from, to, referenceCount, kinds} and metrics (fan-in/out, cycles).",
        "idePrecedent" : "Module Dependencies Analyzer (IntelliJ)",
        "whenToUse" : "'Which modules depend on which, and are there cycles' is one call with no arguments - the whole-workspace module graph.",
        "whenNotToUse" : [ "the module inventory rather than the relationships (use ek9_query_workspace)", "PACKAGE-level cycles between published packages, not module cycles within a package (use ek9_check_package_dependency_cycles)", "the DI wiring graph (component injection), not module imports (use ek9_query_dependency_injection)", "method-level call relationships, not module-level coupling (use ek9_query_call_graph)" ],
        "examples" : [ "{} -> status:ok, moduleCount, edgeCount, hasCycles, then modules[], dependencies[] ({from,to,referenceCount,kinds}) and metrics.cycles:[] (empty = healthy).", "{includeBuiltins:true} -> graph additionally includes org.ek9.lang edges (noisy — only for auditing built-in usage)." ],
        "details" : "Empty workspace is status:ok (moduleCount:0, cycles:[]), not an error. dependencies[].kinds are STRUCTURAL (extends, implements, field_type, return_type, parameter_type), not import labels; referenceCount is coupling strength. metrics.cycles is the canonical cycle signal (empty array = none) — do NOT infer cycles from stronglyConnectedComponents, which equals moduleCount in an acyclic graph.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "includeBuiltins" : {
              "description" : "Whether to include org.ek9.lang built-in module dependencies (default false)",
              "type" : "boolean"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_module_landscape",
        "description" : "Answers first: status, programState, moduleCount, moduleNames, then modules[] {name, band, types, metrics as headroom {rule, limit, value, band, enforced}, cycles} ranked worst-first and summary.",
        "idePrecedent" : "EK9 IDE — \"Module Landscape\" pane (health-coloured module map)",
        "whenToUse" : "'How healthy is each module - size, coupling, cohesion, cycles - against the limits the compiler enforces' is one call with no arguments.",
        "whenNotToUse" : [ "the specific edges coupling two modules (use ek9_query_module_coupling)", "intra-module cohesion detail (use ek9_query_module_cohesion)", "the dependency graph / who-imports-whom (use ek9_query_module_dependencies)", "full cycle paths and how to break them (use ek9_check_module_cycles)" ],
        "examples" : [ "{} → status:ok, moduleCount, moduleNames; every user module ranked efferent-worst-first with types + metrics + overall band + cycle membership.", "{band:'over', metric:'afferent'} → only modules whose overall band is 'over' (a compiler gate would fire), ranked by afferent count — the stable-and-concrete 'zone of pain' candidates." ],
        "details" : "Per module: 'types' (counts by genus), 'metrics' — moduleCoupling (E11016, enforced:true, basis:'trueCoupling' — the tool's own type-argument-unwrapped walk, may exceed the FQN-blind E11016 checker), constructCoupling (E11015 rollup), moduleCohesion (E11017, enforced but available:false — value not persisted by the checker), afferent/instability (advisory, enforced:false, limit:null); for applications a registrations tuple (E11042). Enforced metrics carry a real comparator + band; overall 'band' is the worst ENFORCED band (advisory never raises it). Also isApplicationOrRoot (composition roots wire everything by design), cycle membership (pointer only), file/line, and programState — architecture facts need FULL_RESOLUTION; below that, unknown.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "band" : {
              "description" : "Restrict to modules whose overall health band is at/over this level. Default 'all'.",
              "enum" : [ "all", "ok", "approaching", "at-limit", "over" ],
              "type" : "string"
            },
            "metric" : {
              "description" : "Rank modules by this metric (worst first). Default 'efferent'.",
              "enum" : [ "efferent", "afferent" ],
              "type" : "string"
            },
            "moduleFilter" : {
              "description" : "Restrict to these modules - exact names; an entry ending in '*' is a prefix ('com.acme.*'). A single string is accepted. Omit for workspace-wide.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control — see EK9_TOOL_SPECIFICATIONS.md §1.6. full (default) returns the complete result; summary trims per-item detail (call_graph to its headline names and sites, effective-api to member names); none collapses lists to counts. A very large response auto-degrades with an explicit `truncated` marker.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_module_coupling",
        "description" : "Answers first: status, programState, edgeCount, then edges[] {from, to, types, weight, shape, sites, inCycle, proposals} ranked worst-first, audit (adHocCoupling, overDeclaredReferences candidates) and summary.",
        "idePrecedent" : "EK9 IDE — module coupling diagram (directed typed edges, ranked worst-first)",
        "whenToUse" : "'Which types leak across which module boundaries, and how heavily' is one call with no arguments - every cross-module edge ranked worst-first, with sites.",
        "whenNotToUse" : [ "the per-module enforced coupling number and band, the E11016 gate (use ek9_query_module_landscape; this is the edge breakdown behind it)", "confirming a declared 'references' entry is truly removable (use ek9_organize_references, a scratch compile; overDeclaredReferences here surfaces candidates only)", "per-construct coupling with the 0.5-per-'by'-delegation discount, E11015 (use ek9_query_construct; module edges are whole-number binary dependencies)" ],
        "examples" : [ "{} -> status:ok, edgeCount, all cross-module edges ranked worst-first by weight, each with types, shape, sites, proposals, plus the audit and summary blocks.", "{moduleFilter:['com.acme.orders'], direction:'outgoing', minWeight:3, targetAbstractness:'concrete'} -> only heavy (>=3) edges out of orders reaching concrete types — the worst leaks." ],
        "details" : "Read from RESOLVED symbol type-references (super/traits/field/param/return → edges by FQN); generics are unwrapped so List of Order couples to Order, not to org.ek9.lang. Each edge carries {from, to, types[], weight, declared, adHocFqn, shape{concentration, nearMutualityRatio, targetAbstractness}, sites[{file,line}], inCycle, proposals[]}; weight is a signature-reference count, and proposals (extract-kernel / narrow-with-trait / clarify-ownership) are advisory — pair with ek9_git_log co-change evidence before acting. audit.adHocCoupling (inline-FQN coupling with no 'references' entry) is signature-derived and safe; audit.overDeclaredReferences is candidates-only, since a body-local variable-declaration type escapes even its body-aware used-set.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "direction" : {
              "description" : "With moduleFilter: 'outgoing' = edges out of those modules, 'incoming' = edges into them, 'both' (default) = either side.",
              "enum" : [ "both", "outgoing", "incoming" ],
              "type" : "string"
            },
            "minWeight" : {
              "description" : "Only edges with at least this weight (signature reference count). Default 1.",
              "type" : "integer"
            },
            "moduleFilter" : {
              "description" : "Restrict to these modules - exact names; an entry ending in '*' is a prefix ('com.acme.*'). A single string is accepted. Omit for workspace-wide.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "targetAbstractness" : {
              "description" : "Only edges reaching this genus class (concrete = heaviest coupling risk).",
              "enum" : [ "value", "abstraction", "concrete" ],
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control — see EK9_TOOL_SPECIFICATIONS.md §1.6. full (default) returns the complete result; summary trims per-item detail (call_graph to its headline names and sites, effective-api to member names); none collapses lists to counts. A very large response auto-degrades with an explicit `truncated` marker.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_module_cohesion",
        "description" : "Answers first: status, programState, moduleCount, then modules[] {name, moduleCohesion (E11017 disconnected groups), cohesionRatio, lcom4 (E11014 over-limit constructs)}; symbol:'Type' drills to one construct's LCOM4.",
        "idePrecedent" : "EK9 IDE — module cohesion view (disconnected-group split signal + per-construct LCOM4)",
        "whenToUse" : "'Should this module be split, and which constructs do too many unrelated things' is one call with no arguments, or moduleFilter:['mod'] for one module.",
        "whenNotToUse" : [ "how modules depend on each other, the coupling side (use ek9_query_module_coupling)", "the workspace-wide module map to pick WHICH module to inspect (use ek9_query_module_landscape first, then drill here)", "COMPONENT cohesion — EK9 components hold only fields/methods, not nested constructs; the cohesion unit is the MODULE, so there is no component-cohesion tool" ],
        "examples" : [ "{moduleFilter:['com.acme.orders']} -> per-module moduleCohesion{disconnectedGroups, disconnectionRatio, band, rule:'E11017'} + advisory cohesionRatio + lcom4 rollup", "{symbol:'OrderService'} -> that construct's LCOM4 as a Headroom tuple (E11014); band:'exempt' for exempt genera, available:false when no stored value" ],
        "details" : "Per-construct LCOM4 (E11014) is a zero-recompute READ of the checker's own squirrelled COHESION value; module disconnected-groups (E11017) is RE-DERIVED (basis:'toolWalk') because the checker's value is not persisted, so it carries gate thresholds not enforced:true and can differ from the gate (this walk unwraps generics, the gate does not). E11017 fires only when groups>maxGroups AND ratio>maxRatio AND constructs>=minConstructs; band is over/approaching/ok/info. cohesionRatio (internal vs external edges) is advisory-only, never a gate; ratio:null when the module has no edges.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "moduleFilter" : {
              "description" : "Restrict to these modules - exact names; an entry ending in '*' is a prefix ('com.acme.*'). A single string is accepted. Omit for workspace-wide.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "symbol" : {
              "description" : "A construct NAME ('OrderService' or 'com.acme.orders::OrderService') — drill straight to its LCOM4 (E11014) instead of the per-module view.",
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_construct_neighbourhood",
        "description" : "Answers first: nodeCount, nodes[] (name, fullyQualifiedName, module, genus, file, line, distance), edges[] (from, to, kind) and edgesByKind - the N-hop typed ego graph merging inheritance, calls, composition, references and DI.",
        "idePrecedent" : "EK9 IDE — construct neighbourhood / blast-radius ego graph around the cursor",
        "whenToUse" : "If I change this construct, what is structurally affected - one call with symbol:'Order' and direction:'incoming' (the blast radius), no file needed.",
        "whenNotToUse" : [ "ONLY inheritance/trait ancestry — use ek9_query_type_hierarchy.", "You want ONLY call edges — use ek9_query_call_graph.", "You want the exhaustive per-site reference list (not the typed structural shape) — use ek9_query_references.", "You want module-altitude coupling, not one construct's neighbourhood — use ek9_query_module_coupling." ],
        "examples" : [ "{symbol:'Order', direction:'incoming'} → nodes[]/edges[] of everything that depends on Order (the change-impact set) plus edgesByKind.", "{file:'shapes.ek9', line:12, depth:2, edgeKinds:['calls','injects']} → the 2-hop call+DI neighbourhood of the construct at the cursor." ],
        "details" : "Edge kinds: extends/implements/composes(field types)/references(param/return types)/calls(phase-5 call graph)/injects(DI fields)/dispatches(dispatcher overload group) — all types generic-unwrapped, only user-construct↔user-construct edges kept, each node carries 'distance' from the target. A virtual call THROUGH an injected abstract field is a 'calls' edge to the abstract type, which has a 'calls' edge on to each implementation that is constructed somewhere (a register in the application counts) - one hop more, never none. A function's implicit generic super ('Consumer of Address') gives 'references', not 'extends', to its type argument. Also emits 'requirements[]' (whose anchoring tests can REACH this construct) over the FULL transitive closure — never limited by 'depth', over-inclusive by construction (reach is change-impact, NOT what this construct implements); reachResolved=false means no call graph to answer from; test nodes carry isTest/requirementRef.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "depth" : {
              "description" : "How many edges out from the target (default 1, max 5).",
              "type" : "integer"
            },
            "direction" : {
              "description" : "'outgoing' = what it depends on, 'incoming' = what depends on it (the blast radius), 'both' (default).",
              "enum" : [ "both", "incoming", "outgoing" ],
              "type" : "string"
            },
            "edgeKinds" : {
              "description" : "Restrict to these relationships. Omit for all.",
              "items" : {
                "enum" : [ "extends", "implements", "composes", "references", "calls", "injects", "dispatches" ],
                "type" : "string"
              },
              "type" : "array"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "symbol" : {
              "description" : "The construct: a bare name ('Order') or a qualified name ('com.acme.orders::Order') - no file needed, the tool finds it workspace-wide; 'Type.member' anchors on the member's construct.",
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_purity_impact",
        "description" : "Answers first: status, change, availableChanges, verdict (ready / would-fail / cascade-too-large / blocked-refuse-to-root), callerNames, calleeNames, then anchor, contract, afferent, efferent axes - the same closure ek9_fix_purity_contract applies.",
        "idePrecedent" : "EK9 IDE — blast-radius impact pane (purity change preview)",
        "whenToUse" : "'Who breaks if this stops being pure, or what must become pure if it does' is one call with symbol:'Type.method' on a callable flagged E08130/E05150/E05160.",
        "whenNotToUse" : [ "applying the purity fix, not previewing it (use ek9_fix_purity_contract)", "previewing a signature change (add/remove/reorder params, return type), not a purity change (use ek9_query_signature_impact)", "raw caller/callee chains without the purity verdict overlay (use ek9_query_call_graph)", "the list of purity errors first, to find a callable to anchor on (use ek9_query_diagnostics)" ],
        "examples" : [ "{symbol:'Derived.calc', change:'add-pure'} → status:ok with anchor + contract/afferent/efferent axes; verdict:'ready' means a safe one-liner fix.", "{file:'src/orders.ek9', line:45} → the same for the purity error on line 45 (the IDE cursor form); a line with no purity error → an error asking you to point at the flagged line." ],
        "details" : "Returns a BlastRadius (BlastRadiusJson): anchor callable + three axes — 'contract' (the override group; root is the ONLY legal edit anchor), 'afferent' (transitive pure-caller closure that breaks if pure is removed), 'efferent' (non-pure-callee closure that must become pure if pure is added) — each node with a verdict (root / fixable / breaks) and file:line. Overall verdict (ready / would-fail / cascade-too-large / blocked-refuse-to-root) is computed by the SAME rule ek9_fix_purity_contract applies, so picture and fix agree. change: 'remove-pure' = which CALLERS break if this stops being pure (the afferent axis); 'add-pure' = which CALLEES must become pure (the efferent axis); E08130 offers both, E05150 only add-pure, E05160 only remove-pure. Works under compile errors via an on-demand call graph; FULL_RESOLUTION (or default ek9_compile) suffices.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "change" : {
              "description" : "'remove-pure' = which CALLERS break if this stops being pure; 'add-pure' = which CALLEES must become pure. Omit to take the natural direction for the diagnosed code (E08130 offers both - see availableChanges).",
              "enum" : [ "add-pure", "remove-pure" ],
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line where the purity error (E08130 / E05150 / E05160) fires - OMIT it and pass symbol:'Type.method' to let the tool find the flagged line.",
              "type" : "integer"
            },
            "symbol" : {
              "description" : "The flagged method or function - 'Type.method' or a function name - no file or line needed; the tool finds its first purity error.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control — see EK9_TOOL_SPECIFICATIONS.md §1.6. full (default) returns the complete result; summary trims per-item detail (call_graph to its headline names and sites, effective-api to member names); none collapses lists to counts. A very large response auto-degrades with an explicit `truncated` marker.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_data_touch",
        "description" : "Answers first: anchor and verdicts (parameter → untouched | mutates | escapes | ... ), then touches[] with the mutation and escape axes and every contributing site - read-only.",
        "idePrecedent" : "EK9 IDE — construct-neighbourhood pane, data-touch lens (planned)",
        "whenToUse" : "Does this callable (or what it calls) write through the structure I pass, or keep a reference to it - one call with symbol:'Stash.keep' and parameter:'items'. No file needed; omit parameter for a verdict per parameter.",
        "whenNotToUse" : [ "Whether a callable is pure (no side effects at all), not whether it touches ONE structure you pass — use ek9_query_purity_impact / the purity diagnostics.", "You want raw caller/callee chains without the touch overlay — use ek9_query_call_graph.", "You want to CHANGE code — data-touch is a read-only enquiry, it has no fix." ],
        "examples" : [ "{symbol:'Service.apply', parameter:'address'} → mutation:'may-mutate', escape:'no-escape', verdict:'mutates' (a direct write through the parameter).", "{file:'service.ek9', line:54, parameter:'customers'} → mutation:'may-mutate' with a TRANSITIVE site (a for-in loop hands an element to a callee that writes it), verdict:'mutates-via'.", "{symbol:'Store.keep', parameter:'customer'} → mutation:'no-mutation', escape:'may-escape' (a field store — retained, not written), verdict:'escapes'." ],
        "details" : "Returns status:ok with 'anchor' (the resolved callable, display form) and 'verdicts' (parameter → verdict) first, then 'touches' — one entry per tracked parameter: {parameter, mutation, escape, verdict (untouched / mutates / mutates-via / escapes / escapes-via / mutates-and-escapes / mutates-and-escapes-via / unknown), reusable, sites[], unknowns[]}. Each mutation/escape site carries file:line, kind, axis, the source 'path' touched, and — when TRANSITIVE — the 'via' callee chain plus the origin file:line where the write/store really happens. Escape currently observes user-code whole-structure store/return only; container retain-into (list += arg) reads no-escape until the retention table lands (a documented trust residual). Runs on demand over FULL_RESOLUTION (or default ek9_compile).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "column" : {
              "description" : "Optional 0-based IDE column. Advisory — the anchor is the enclosing callable, resolved by line, so this may be omitted.",
              "type" : "integer"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line inside the callable whose parameter you are tracking (the enclosing method or function is resolved from it) - OMIT it and pass symbol instead.",
              "type" : "integer"
            },
            "parameter" : {
              "description" : "The parameter name to track (the data structure). Omit to get a verdict for EVERY parameter of the callable.",
              "type" : "string"
            },
            "symbol" : {
              "description" : "The callable: 'Stash.keep' (Type.member) or a bare function name - no file needed, the tool finds it workspace-wide; or, with file (+ line), the name you mean there.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control — see EK9_TOOL_SPECIFICATIONS.md §1.6. full (default) returns the complete result; summary trims per-item detail (call_graph to its headline names and sites, effective-api to member names); none collapses lists to counts. A very large response auto-degrades with an explicit `truncated` marker.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_mutex_payload",
        "description" : "Answers first: status, summary {bodies, contained, mayEscape, unknown}, findingCount, then findings[] {file, line, inConstruct, payloadType, locks, escape, sites[] with via chain and origin}.",
        "idePrecedent" : "EK9 IDE — concurrency lens (planned); closest external analogue is an escape-analysis or thread-safety inspection",
        "whenToUse" : "'Does any MutexLock payload escape its critical section' is one call with no arguments - every MutexKey body, with the chain to whoever keeps the reference.",
        "whenNotToUse" : [ "whether ONE named structure is touched by ONE callable (use ek9_query_data_touch, which anchors on a callable and tracks a named parameter)", "lock ORDER / deadlock risk rather than payload containment (that is E08252/E08255 and ek9_query_deadlock_chain)", "CHANGING code — this is a read-only enquiry with no fix" ],
        "examples" : [ "{} → status:ok, summary{bodies, contained, mayEscape, unknown}, findingCount, findings[] — each a MutexKey body whose payload may escape, with sites carrying the via chain to the callee that keeps the reference.", "{} on a workspace whose every MutexKey body only mutates lockedItem in place or copies it out with ':=:' → status:empty (nothing escapes).", "{includeUnknown:true} → also lists bodies whose escape is 'unknown' (an opaque callee, or the payload passed to a function value), which a human must judge." ],
        "details" : "summary {bodies, contained, mayEscape, unknown} counts EVERY MutexKey body, so 'N of M contained' can be stated; findings[] is the escaping subset. Each finding carries {file, line, column (the MutexKey body, 0-based column), inConstruct / inConstructFqn (the named method/function declaring it, in callable-identity form — joins ek9_query_call_graph), payloadType (as written: 'List of String'), locks[] ({stableId, displayName} of the MutexLock it runs under — the same ids ek9_query_lock_identity lists), escape (may-escape / contained / unknown), sites[], unknowns[]}. inConstruct is absent when phase 8 lock analysis did not run (the default compile reaches it); locks[] is absent when no enter() call was paired with the body. Each site has {file, line, kind (escape-store / returns-alias), path (where the reference lands), root {name, role: this/field/parameter/capture — the root of path in the WRITER's frame}, via[] (the callee chain, by RESOLVED signature) and origin {file, line} (where it is actually stored)}. A site with NO via and NO origin IS the write, in the body itself; capture means the body captured that variable, so the object is the caller's and the fix is in the body. hints{} explains the verdicts and fields. ':=:' / ':~:' / ':^:' copy CONTENT and are not reported — they are what E08258 tells you to use. ⚠️ The copy family is SHALLOW: the container is independent but its ELEMENTS are shared, so copying a list of mutable records out of a lock still shares those records.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "includeUnknown" : {
              "default" : false,
              "description" : "Also report MutexKey bodies whose escape verdict is 'unknown' — an opaque callee, or the payload handed to a function value. Off by default so the list stays actionable.",
              "type" : "boolean"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_signature_impact",
        "description" : "Answers first: status, direction (the mutation kind), verdict (ready / would-fail), callerSites, then anchor, contract (root + overrides the change cascades to) and afferent (the call sites ek9_change_signature would rewrite, each fixable or breaks).",
        "idePrecedent" : "EK9 IDE — blast-radius impact (signature change preview, detached window)",
        "whenToUse" : "'What breaks if I add, remove or reorder a parameter, or change the return type' is one call with symbol:'Type.method' plus the mutations - no file needed.",
        "whenNotToUse" : [ "applying the change, not previewing it (use ek9_change_signature with the same mutations)", "previewing a PURITY change derived from an existing diagnostic, not a signature edit (use ek9_query_purity_impact)", "raw caller/callee chains without the per-site update verdict (use ek9_query_call_graph)", "EVERY reference to a symbol (any kind), not the call sites a specific mutation would rewrite (use ek9_query_references)" ],
        "examples" : [ "{symbol:'Renderer.render', mutations:[{kind:'add-parameter', parameter:{name:'region', type:'String', valueForExistingCalls:'\"UK\"'}}]} → verdict:ready; contract root + overrides fixable, afferent call sites fixable (argument synthesised mechanically).", "Same anchor, add-parameter of a type with no accessible no-argument constructor and NO valueForExistingCalls → verdict:would-fail; afferent sites breaks (a human must supply the argument). A type that has one (String, Float) is passed its UNSET instance, as ek9_change_signature does → fixable." ],
        "details" : "Overall verdict is would-fail if ANY afferent site is breaks, else ready. Axes: 'contract' (⬆ override group — root has verdict 'root', overrides 'fixable'); 'afferent' (▲ one-hop call sites — each fqn a call-site file:line:col; 'fixable' rewritten mechanically, 'breaks' for an added param with no valueForExistingCalls and no unset instance, or a removed param without dropCallSiteArguments, 'consistent' when the argument list is unchanged (a return-type or purity change rewrites no site)); 'efferent' (▼ always empty — a signature change does not alter what the anchor calls). direction/availableDirections carry the mutation kind verbatim (add-parameter / remove-parameter / reorder-parameters / change-return-type / change-purity), not an axis; needs a FULL_RESOLUTION workspace (default ek9_compile suffices).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the method or function declaration - OMIT it and pass symbol:'Type.method' to let the tool find the line (a model counting lines miscounts).",
              "type" : "integer"
            },
            "mutations" : {
              "description" : "The proposed signature change (same shape as ek9_change_signature): one or more {kind: 'add-parameter' (+ parameter:{name,type,position?,valueForExistingCalls?}), 'remove-parameter' (+ removeParameterName, + optional dropCallSiteArguments:true), 'reorder-parameters' (+ newParameterOrder:[names]), 'change-return-type' (+ newReturnType), 'change-purity' (+ purityChange)}. Drives the direction and the per-site verdict.",
              "items" : {
                "anyOf" : [ {
                  "properties" : {
                    "kind" : {
                      "const" : "add-parameter"
                    },
                    "parameter" : {
                      "properties" : {
                        "name" : {
                          "type" : "string"
                        },
                        "position" : {
                          "description" : "0-based position among the parameters; omitted = append",
                          "type" : "integer"
                        },
                        "type" : {
                          "type" : "string"
                        },
                        "valueForExistingCalls" : {
                          "description" : "The EK9 expression injected at EVERY existing call site (EK9 has no default parameter values) - required when the callable has call sites, e.g. '0.0' or 'CurrencyCode(\"USD\")'",
                          "type" : "string"
                        }
                      },
                      "required" : [ "name", "type" ],
                      "type" : "object"
                    }
                  },
                  "required" : [ "kind", "parameter" ],
                  "type" : "object"
                }, {
                  "properties" : {
                    "dropCallSiteArguments" : {
                      "description" : "true to drop the argument at every call site; without it live call sites block the removal",
                      "type" : "boolean"
                    },
                    "kind" : {
                      "const" : "remove-parameter"
                    },
                    "removeParameterName" : {
                      "type" : "string"
                    }
                  },
                  "required" : [ "kind", "removeParameterName" ],
                  "type" : "object"
                }, {
                  "properties" : {
                    "kind" : {
                      "const" : "reorder-parameters"
                    },
                    "newParameterOrder" : {
                      "items" : {
                        "type" : "string"
                      },
                      "type" : "array"
                    }
                  },
                  "required" : [ "kind", "newParameterOrder" ],
                  "type" : "object"
                }, {
                  "properties" : {
                    "kind" : {
                      "const" : "change-return-type"
                    },
                    "newReturnType" : {
                      "description" : "the new return type; null removes the return clause",
                      "type" : [ "string", "null" ]
                    }
                  },
                  "required" : [ "kind", "newReturnType" ],
                  "type" : "object"
                }, {
                  "properties" : {
                    "kind" : {
                      "const" : "change-purity"
                    },
                    "pure" : {
                      "description" : "true adds 'as pure', false removes it",
                      "type" : "boolean"
                    }
                  },
                  "required" : [ "kind", "pure" ],
                  "type" : "object"
                } ]
              },
              "type" : "array"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control — see EK9_TOOL_SPECIFICATIONS.md §1.6. full (default) returns the complete result; summary trims per-item detail (call_graph to its headline names and sites, effective-api to member names); none collapses lists to counts. A very large response auto-degrades with an explicit `truncated` marker.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "mutations" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_check_module_cycles",
        "description" : "Answers first: hasCycles and cycleCount, then cycles[] (each a closed module path with edges[].via naming the source files) and moduleCount - the whole workspace module graph.",
        "idePrecedent" : "Module Dependency Cycle Detector",
        "whenToUse" : "Are there circular references between the modules of this package - one call, no arguments; hasCycles and cycleCount answer it.",
        "whenNotToUse" : [ "Cycles BETWEEN published packages (cross-package) - use ek9_check_package_dependency_cycles.", "The full module dependency graph, not just cycles - use ek9_query_module_dependencies (cycles are a subset of that data).", "Dependency-injection wiring cycles (compile error E08190) - use ek9_query_diagnostics." ],
        "examples" : [ "{} -> hasCycles:true, cycleCount:1, cycles[0].description 'com.acme.orders → com.acme.invoices → com.acme.orders' with edges[].via source files.", "{} on sound modules -> hasCycles:false, cycleCount:0, empty cycles (the goal)." ],
        "details" : "cycles[].modules uses the closed-path convention ([\"A\",\"B\",\"A\"] means A -> B -> A; the repeated first module marks closure, not a distinct node). moduleCount is how many modules the walk covered. edges[].via lists the source files whose imports create each cycle (jump-to-source); reliable detection and via-attribution need a compile that reached SYMBOL_DEFINITION or later — at syntax_only hasCycles is only a lower bound.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : { },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_dependency_tree",
        "description" : "Answers first: direction, rootPackage, rootVersion, then tree{} (dependsOn) or deduplicated[] with broughtInBy (broughtInBy), selections[] and summary - the transitive package dependency tree across the workspace.",
        "idePrecedent" : "mvn dependency:tree / npm ls",
        "whenToUse" : "What does this package pull in transitively, or why is module X pulled in - one call, no arguments for the whole tree. For the 'why', pass direction:'broughtInBy' and moduleFilter:'X'.",
        "whenNotToUse" : [ "YOUR DECLARED deps only (no transitive expansion) — use ek9_query_package.", "You want module-level imports WITHIN your package, not the external dep tree — use ek9_query_module_dependencies.", "You specifically want CYCLE DETECTION across the tree — use ek9_check_package_dependency_cycles.", "You want to TRIGGER a resolution / fetch rather than query an existing one — use ek9_resolve_dependencies." ],
        "examples" : [ "{} → direction:'dependsOn', nested tree with selections[], rejectedVersions[], summary {uniqueModules, totalDeclarations, maxDepth}.", "{direction:'broughtInBy', moduleFilter:'acme.core'} → deduplicated nodes with broughtInBy[] listing every parent package that declares that module (diamond detection); summary {uniqueModules, totalEdges}." ],
        "details" : "In-workspace scope: only packages whose source is in the compiled workspace are expanded; external deps appear as leaves with selectionNote 'external (not in workspace)'. direction:'dependsOn' is the nested DOWNWARD subtree ('what does X depend on?'), direction:'broughtInBy' the flat list with the UPWARD broughtInBy[] set ('what brings X in?'); format (nested | flat) is the same choice by shape, and giving both when they disagree is an error. moduleFilter is one exact module name (or a prefix with a trailing *): nested roots the subtree at the first match, flat keeps the matching modules; an unmatched filter still succeeds with a null/empty tree. selections[] lists each module's selected version; rejectedVersions[] is currently always empty (no remote version-conflict resolution), and revisited modules are pruned with selectionNote 'already present at shallower depth'.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "direction" : {
              "description" : "dependsOn (default): the downward tree from the root or the filtered module; broughtInBy: the flat list with, per module, the packages that pull it in.",
              "enum" : [ "dependsOn", "broughtInBy" ],
              "type" : "string"
            },
            "format" : {
              "description" : "Output shape: nested (hierarchical tree, = dependsOn) or flat (deduplicated list with broughtInBy, = broughtInBy). Optional when direction is given.",
              "enum" : [ "nested", "flat" ],
              "type" : "string"
            },
            "moduleFilter" : {
              "description" : "Optional: one exact module name, or a prefix with a trailing *. dependsOn: the subtree rooted at the first match; broughtInBy: the matching modules with their broughtInBy.",
              "type" : "string"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_lock_analysis",
        "description" : "Answers first: status, then deadlockCount, lockIdentityCount, enterSiteCount, keyBodyCount, crossThreadEdgeCount and nextStep (the one drill-down worth calling); lockIdentityCount 0 = no MutexLock anywhere, deadlock structurally impossible.",
        "idePrecedent" : "Lock Catalogue / Deadlock Detective",
        "whenToUse" : "'Does this workspace have any lock or deadlock risk at all' is one call with no arguments - the headline counts before any lock drill-down.",
        "whenNotToUse" : [ "the rationale chains behind a deadlock risk, deadlockCount > 0 (use ek9_query_deadlock_chain)", "the catalogue of distinct locks (origin, display name), not just the count (use ek9_query_lock_identity)", "the inventory of enter()/tryEnter() acquisition sites (use ek9_query_enter_sites)", "the raw E08252/E08253 as compiler diagnostics rather than the summary (use ek9_query_diagnostics)" ],
        "examples" : [ "{} -> status:ok with enterSiteCount/keyBodyCount/lockIdentityCount/deadlockCount/functionSummaryCount/crossThreadEdgeCount + nextStep.", "{} on a lock-free workspace -> status:ok, lockIdentityCount:0 = structural-deadlock-impossible; skip the drill-down tools entirely." ],
        "details" : "Zero parameters; whole-workspace only, no module scoping. status is 'ok' (analysis ran, counts live) or 'not-compiled' (no compiled program, or compilation failed before PRE_IR_CHECKS produced a LockAnalysis — nextStep ek9_compile). nextStep on 'ok': deadlockCount>0 -> ek9_query_deadlock_chain, lockIdentityCount>0 -> ek9_query_lock_identity, otherwise none — lockIdentityCount==0 means no MutexLock use anywhere, so structural deadlock is impossible.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : { },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_deadlock_chain",
        "description" : "Answers first: chains[] - per detected lock risk its errorCode, path, outerLock/innerLock and ordered steps[] with file/line/column - chains:[] when nothing is at risk.",
        "idePrecedent" : "Lock Catalogue / Deadlock Detective",
        "whenToUse" : "Why does the analyser report E08252 / E08253 - the ordered, navigable steps behind every detected lock risk - one call, no arguments.",
        "whenNotToUse" : [ "WHETHER a deadlock exists (the count only) — use ek9_query_lock_analysis.", "You want EVERY acquisition site, not just those in a detected chain — use ek9_query_enter_sites.", "You want the lock catalogue (identities), not the risk chains — use ek9_query_lock_identity.", "You want the raw E08252/E08253 compiler diagnostics rather than the analyser's rationale — use ek9_query_diagnostics." ],
        "examples" : [ "{} → status:available with chains[] — each has errorCode, path (verdict route), outerLock/innerLock identities, and ordered steps[] with file/line/col navigation.", "{} when nothing is at risk → status:available, chains:[] (the clean state); status:no-analysis means the PRE_IR_CHECKS lock analysis did not run — recompile and retry." ],
        "details" : "status discriminates available (analysis ran; chains[] may be empty=clean) vs no-analysis (LockAnalysis absent — recompile to PRE_IR_CHECKS). Each chain: errorCode (E08252 same-thread order cycle / E08253 cross-thread same-lock hold), path (analysis-path enum), outerLock/innerLock (lock-identity descriptors; correlate stableId with ek9_query_lock_identity; an unresolvable identity degrades to {stableId,status:'unresolved'}), and steps[] (kind, description, location{1-based line,0-based column}, contextFunction, optional stepTarget). Dynamic anonymous symbols render in the friendly fn/cls form (the volatile mangled hash is suppressed) — disambiguate by location.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : { },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_enter_sites",
        "description" : "Answers first: siteCount, then sites[] (stableId, kind, receiver, lockIdentityIds, location, enclosingFunction) - every lock acquisition, workspace-wide.",
        "idePrecedent" : "Lock Catalogue / Deadlock Detective",
        "whenToUse" : "Where does the program acquire locks - every MutexLock.enter() / tryEnter() site - one call, no arguments. Also the route from a lock identity to the sites that acquire it (match lockIdentityIds).",
        "whenNotToUse" : [ "The distinct locks themselves, not the sites that acquire them - use ek9_query_lock_identity.", "You want detected risk chains with rationale, not the raw acquisition inventory - use ek9_query_deadlock_chain.", "You only need the count (enterSiteCount), not the full list - use ek9_query_lock_analysis.", "You want general call relationships for a method, not lock-acquisition sites - use ek9_query_call_graph." ],
        "examples" : [ "{} -> status:available, siteCount:1, sites:[{stableId:'enter:<file>:13:19', kind:'ENTER', receiver:'accountLock', lockIdentityIds:['placeholder:com.example::accountLock'], location:{...}, enclosingFunction:{...}}].", "{} on an unanalysed workspace -> status:no-analysis, sites:[]." ],
        "details" : "status is available (siteCount + sites[]) | no-analysis (PRE_IR_CHECKS did not run — recompile past it and retry) | not-available (compile first). Each site: stableId, kind (ENTER/TRY_ENTER), receiver display form, lockIdentityIds[] (singleton in the common case; multi-element on branch-joined/polymorphic receivers — correlate with ek9_query_lock_identity), location {file, line(1-based), column(0-based)}, and enclosingFunction {fullyQualifiedName, name, displayName, category} or null. Synthetic inline MutexKey-body functions render their mangled name in friendly 'fn' form; no filtering yet.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : { },
          "type" : "object"
        }
      }, {
        "name" : "ek9_query_lock_identity",
        "description" : "Answers first: status, identityCount, then identities[] {stableId, origin, displayName, originSymbol, originLocation, payload}; stableId joins ek9_query_enter_sites and ek9_query_deadlock_chain.",
        "idePrecedent" : "Lock Catalogue / Deadlock Detective",
        "whenToUse" : "'What MutexLocks does this program have' is one call with no arguments - every distinct lock with its origin, declaration and stableId.",
        "whenNotToUse" : [ "the headline counts only, not the full list (use ek9_query_lock_analysis)", "the acquisition SITES (where locks are entered), not the identities (use ek9_query_enter_sites)", "the detected deadlock risk CHAINS, not the inventory (use ek9_query_deadlock_chain)", "details of one specific symbol under a cursor (use ek9_inspect_symbol_at_cursor)" ],
        "examples" : [ "{} → status:ok, identityCount:2, identities[] each with {stableId, origin, displayName, originSymbol, originLocation}.", "{} on a program with no locks → status:ok, identityCount:0 (structurally deadlock-impossible)." ],
        "details" : "status is ok | not-compiled (no compiled program, or PRE_IR_CHECKS did not produce a LockAnalysis — nextStep ek9_compile). On 'ok', identities[] carries per-lock {stableId, origin, displayName, originSymbol, originLocation}; origin is FIELD_ROOTED | ALLOCATION | PARAMETER | CAPTURE; stableId correlates with enter_sites.sites[].lockIdentityIds and deadlock_chain outerLock/innerLock. originSymbol and originLocation may be null; mangled _Function_/_Class_ names render friendly (fn/cls); no filtering yet. Each identity also carries payload {bodies, contained, mayEscape, unknown} — how many MutexKey bodies run under this lock and whether their lockedItem stays inside it (the same sweep as ek9_query_mutex_payload, which names the escaping bodies); absent when no enter call pairs a body with the lock.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : { },
          "type" : "object"
        }
      } ]
    },
    "editor" : {
      "description" : "Mutate source or package directive. Produce a reviewable changeset; default policy is confirm-on-write.",
      "categoryRules" : [ "After a sequence of editor calls, run ek9_compile (or ek9_run_tests) to verify.", "Call with dryRun:true first to preview the changeset — status is ready / would-fail / conflicts — then re-issue the SAME args with dryRun:false to apply.", "Control response size with verbosity: \"full\" (default — every edit's before/after bodies), \"summary\" (keep each edit's file/type/line range, drop the bodies), or \"none\" (just editCount + the risk block). On an apply after a dry-run already showed the render, verbosity:\"summary\" — or its retained alias terse:true — keeps what changed where without paying for the content twice.", "Pass terseErrors:true to collapse would-be diagnostics to one line each (code/file/line/message); recover the full explanation for any code with ek9_explain_error <code>." ],
      "tools" : [ {
        "name" : "ek9_write_file",
        "description" : "Answers first: status (applied | ready | would-fail | conflicts), written, edits[{file, type:create|overwrite, bytesWritten}] and contentHash; a .ek9 write carries wouldBeDiagnostics from the verifying compile, any other file is written as-is.",
        "idePrecedent" : "fs.writeFile / Write tool (Claude Code) / IDE write-back",
        "whenToUse" : "You are creating a file or replacing one whole - one call with path + content; a .ek9 path is compile-verified, any other file is written as-is. On create a .ek9 path gets the shebang / defines module / //EOF boilerplate when missing; a verified apply is rolled back on new errors.",
        "whenNotToUse" : [ "A few text-level changes inside an EXISTING file - use ek9_edit_file (find/replace, line-replace, regex) rather than rewriting the whole file.", "Reading existing file content — use ek9_read_file (the read counterpart).", "Reformatting or semantically mutating existing .ek9 source — use ek9_format, or the semantic editors (ek9_add_field, ek9_add_method, ek9_move_construct); don't read-modify-write through this tool.", "Renaming or deleting a file — use ek9_move_file / ek9_delete_file (metadata ops, not content writes)." ],
        "examples" : [ "New EK9 test, body-only, module path-derived: {path:'tests/com/acme/o_test.ek9', content:'  defines program...', mode:'create-only'} → status:applied, wouldBeDiagnostics:[] (verified), edits[0].boilerplateInjection {shebang:added, module:added, moduleSource:path-derived, eof:added}, contentHash.", "create-only on an existing path → status:conflicts (kind path-collision); re-issue with mode:overwrite-only to overwrite, or pick a new path." ],
        "details" : "fileKind is inferred from the extension (.ek9 → ek9-source); pass it only to override. Boilerplate handling is create-only and idempotent on all three markers; on overwrite of ek9-source, missing markers attach warnings rather than mutating edited content. A .ek9 dry run scratch-compiles the would-be file (ready / would-fail); a .ek9 apply is verified by the post-write compile and rolled back on new diagnostics (would-fail + apply-rolled-back advisory, written:false) - wouldBeDiagnostics is present only on a verified write. Non-.ek9 targets are written without compile checking (non-ek9-file-not-compile-checked advisory). Writes are workspace-bounded and UTF-8; expectedContentHash (the contentHash ek9_read_file returned) guards overwrites against lost-update races; every applied write returns the new contentHash. An explicit module that diverges from the path-derived one attaches a module-name-mismatches-path warning — the write still succeeds.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "content" : {
              "description" : "Content to write. For a .ek9 path on create, may be body-only — the writer prepends shebang + defines module and appends //EOF when missing.",
              "type" : "string"
            },
            "createIntermediateDirs" : {
              "default" : true,
              "description" : "When true (default), missing parent directories are created automatically.",
              "type" : "boolean"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedContentHash" : {
              "description" : "The contentHash ek9_read_file (or a prior write) returned for the file's CURRENT content; the write is refused when the file changed since (lost-update protection).",
              "type" : "string"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "fileKind" : {
              "description" : "Inferred from the extension (.ek9 → ek9-source, else text); pass it only to override. ek9-source: boilerplate injection on create + compile verification; env: KEY=VALUE; config: reserved.",
              "enum" : [ "text", "ek9-source", "env", "config" ],
              "type" : "string"
            },
            "mode" : {
              "default" : "auto",
              "description" : "auto (default): create or overwrite; create-only: conflict if the file exists; overwrite-only: error if it doesn't.",
              "enum" : [ "auto", "create-only", "overwrite-only" ],
              "type" : "string"
            },
            "module" : {
              "description" : "Fully-qualified EK9 module name for a .ek9 create. Optional — defaults to the path-derived module under the workspace source root; an explicit value that differs emits a 'module-name-mismatches-path' warning.",
              "type" : "string"
            },
            "path" : {
              "description" : "File path: workspace-relative (preferred) or absolute inside the workspace. Created when absent (mode auto).",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "path", "content" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_delete_file",
        "description" : "Answers first: status (applied | ready | conflicts | would-fail), written and, on a refusal, referenceCount + referencingFiles[] before the per-site conflicts[]; edits[{file, type:delete, disposition}].",
        "idePrecedent" : "IntelliJ Safe Delete (file-level) / shell rm",
        "whenToUse" : "You want a whole file gone - one call with path; a .ek9 file is refused while other files still reference its constructs, unless force:true. The refusal lists them as referencingFiles.",
        "whenNotToUse" : [ "Deleting ONE construct inside a file that has others you keep - use ek9_safe_delete (symbol:'Name', per-construct reference check).", "Relocating rather than removing the file — use ek9_move_file.", "Only wanting to KNOW the reference impact without deleting — use ek9_query_references.", "Deleting a whole directory — use ek9_delete_directory (this tool rejects directories)." ],
        "examples" : [ "{path:'src/main/orders/legacy_helper.ek9', dryRun:true} → status:ready (1 construct, 0 external references).", "{path:'src/main/orders/order.ek9'} where constructs are referenced elsewhere → status:conflicts, conflicts[].kind:'file-has-external-references' (one per site) — clear each site, or escalate to force:true." ],
        "details" : "Path must resolve inside the workspace root; directories are rejected — files only. For .ek9 files a per-construct reference sweep (same-file refs excluded) yields status:'conflicts' (kind 'file-has-external-references', with referenceCount and referencingFiles first) when external refs exist and force is false, or status:'would-fail' on a forced dry-run with wouldBeDiagnostics kind 'broken-reference-after-force-delete' (one per site); non-.ek9 files delete directly with no check. useTrash=true routes via the OS trash when supported, else falls back to permanent deletion with a 'trash-unsupported-fell-back-to-permanent' warning — otherwise deletion is irreversible unless version-controlled.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "force" : {
              "default" : false,
              "description" : "When false (default), the .ek9 reference check refuses deletion if workspace references point at constructs in the file. When true, the deletion proceeds REGARDLESS of references — the response includes warnings listing every reference that will fail to resolve.",
              "type" : "boolean"
            },
            "path" : {
              "description" : "File path: workspace-relative (preferred) or absolute inside the workspace. Must exist and be a file (directories: ek9_delete_directory).",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "useTrash" : {
              "default" : false,
              "description" : "When true AND the runtime supports trash semantics, the file is moved to the OS trash rather than permanently deleted. Falls back to permanent deletion with a warning otherwise.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "path" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_delete_directory",
        "description" : "Answers first: status (applied | ready | conflicts), written and edits[{file, type:delete, kind:directory}] - removes an EMPTY workspace directory, never a populated tree.",
        "idePrecedent" : "IntelliJ \"Delete\" on an empty directory / shell rmdir",
        "whenToUse" : "You want an EMPTY directory gone - one call with path; a directory with any entry is refused (directory-not-empty), there is no recursive delete.",
        "whenNotToUse" : [ "Removing a FILE, not a directory - use ek9_delete_file (path).", "Relocating the directory's contents rather than deleting the folder — use ek9_move_file.", "Recursively deleting a POPULATED tree — deliberately unsupported; empty the directory first (ek9_delete_file / ek9_move_file per entry) then retry, or use shell rm -r / git rm -r outside this tool." ],
        "examples" : [ "{path:'obsolete', dryRun:true} → status:ready, edits:[{type:delete, file:'obsolete', kind:directory}].", "{path:'populated'} on a non-empty directory → status:conflicts, conflicts:[{kind:directory-not-empty}] reporting the entry count, no disk mutation." ],
        "details" : "Empty-only is a HARD gate: any entry (including hidden/dot files) yields a directory-not-empty conflict (mechanical — empty the directory, then retry); there is no recursive/force mode. Workspace-bounded: the target must resolve inside a watched root and the root itself is refused. Errors: missing path, not-a-directory, outside-workspace, is-root, disk error.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dryRun" : {
              "default" : false,
              "description" : "When true nothing is deleted; the response shows the would-be deletion or the not-empty conflict.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "The workspaceHash a prior dry run returned; a mismatch adds a drift warning (the delete still proceeds).",
              "type" : "string"
            },
            "path" : {
              "description" : "Directory path: workspace-relative (preferred) or absolute inside the workspace. Must exist, be a directory, and not be the workspace root itself.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Retained alias of verbosity:summary - prefer verbosity.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "full (default) returns the complete response; summary drops each edit's before/after bodies; none collapses edits[] to editCount.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "path" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_move_file",
        "description" : "Answers first: status (applied | ready | conflicts), written and edits[{file, newFile, type:move}] - a pure file-system relocation of any workspace file, contents untouched.",
        "idePrecedent" : "IntelliJ \"Refactor → Move\" (file-level) / shell mv",
        "whenToUse" : "You want a file somewhere else or under another name - one call with oldPath + newPath; any file kind, contents untouched. A .ek9 file keeps its module identity (the 'defines module' line, not the directory).",
        "whenNotToUse" : [ "Moving a CONSTRUCT between files (FQ-name change + reference rewriting), not a whole file - use ek9_move_construct (symbol:'Name', targetFile).", "A .ek9-only rename that must refuse a non-.ek9 destination - ek9_rename_file is the same move with that extension check (it performs the rename).", "You want a copy + delete (distinct from a move — loses inode identity / VCS history) — use ek9_write_file + ek9_delete_file.", "The file should be REMOVED, not moved — use ek9_delete_file." ],
        "examples" : [ "{oldPath:'tests/fixtures/expected_old.txt', newPath:'tests/fixtures/legacy/expected_old.txt', dryRun:true} → status:ready, one 'move' edit (creates 1 dir).", "A dev/ .ek9 containing @Test/assert* moved to a production dir → status:conflicts, kind 'dev-exit-contains-tests' (no override — strip the markers first)." ],
        "details" : "For .ek9 files the 'defines module' line (not the directory) sets module identity, so a move never changes fully-qualified names or breaks references. HARD dev/ exit guard, no override: a .ek9 file leaving a dev/ directory for production is refused (conflict kind 'dev-exit-contains-tests') when it contains @Test or assert*; a marker-free dev/ file is promoted with a 'dev-exit-promotion' advisory. Directory moves are out of scope, and a destination collision (overwrite:false) or missing parent (createIntermediateDirs:false) returns status:error, not conflicts.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "createIntermediateDirs" : {
              "default" : true,
              "description" : "When true (default), missing parent directories at the destination are created (mkdir -p). When false, the tool errors if the destination's parent doesn't exist.",
              "type" : "boolean"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "newPath" : {
              "description" : "Destination: may be in a different directory; must not exist unless overwrite:true. Workspace-relative (preferred) or absolute inside the workspace.",
              "type" : "string"
            },
            "oldPath" : {
              "description" : "File path: workspace-relative (preferred) or absolute inside the workspace. The file as it is now; must exist.",
              "type" : "string"
            },
            "overwrite" : {
              "default" : false,
              "description" : "When false (default), the tool errors if a file already exists at newPath. When true, the existing file is replaced (its prior content is lost). Use cautiously.",
              "type" : "boolean"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "oldPath", "newPath" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_rename_file",
        "description" : "Answers first: status (applied | ready | conflicts), written and edits[{file, newFile, type:move}] - a pure file-system rename of a .ek9 source, contents and module identity untouched.",
        "idePrecedent" : "IntelliJ \"Refactor → Rename File\"",
        "whenToUse" : "You want a .ek9 file under another name or path, contents untouched - one call with oldPath + newPath (both .ek9). The directory does not affect module membership.",
        "whenNotToUse" : [ "Renaming a non-.ek9 file, or moving any file - use ek9_move_file (the same move without the .ek9 extension check).", "Changing the MODULE of the contained constructs — use ek9_move_construct(mode=cross-module) per construct; that edits the 'defines module' line and rewrites references.", "Renaming a CONSTRUCT (identifier) inside the file rather than the file — use ek9_rename_symbol.", "Relocating a construct between files — use ek9_move_construct." ],
        "examples" : [ "{oldPath:'src/main/orders/customer_order.ek9', newPath:'src/main/orders/customer_order_v2.ek9', dryRun:true} → status:ready, one rename edit, module unchanged.", "{oldPath:'dev/order_checks.ek9', newPath:'src/main/order_checks.ek9'} while the file still holds @Test/assert* → status:conflicts (dev-exit-contains-tests)." ],
        "details" : "The only structural refusal is the HARD dev/ exit guard (no override flag): a dev/ file containing @Test or assert* cannot be renamed into a production directory (conflict kind dev-exit-contains-tests) — strip the markers first or keep it in dev/; a markerless dev→production rename is permitted (dev-exit-promotion advisory). A destination collision is an error (no overwrite flag) — pick another name or ek9_delete_file first. Crossing a sourceRoot boundary is a source-root-crossed warning, not a failure; edits[] carries a single rename entry with no before/after content since nothing in the file changed.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "newPath" : {
              "description" : "Destination: must end in .ek9; may be in a different directory (the directory does NOT affect module membership). Workspace-relative (preferred) or absolute inside the workspace.",
              "type" : "string"
            },
            "oldPath" : {
              "description" : "File path: workspace-relative (preferred) or absolute inside the workspace. The .ek9 file as it is now; must exist.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "oldPath", "newPath" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_edit_file",
        "description" : "Answers first: status (applied | ready | would-fail | conflicts), written, edits[{file, startLine, endLine, before, after}], wouldBeDiagnostics from the verifying compile and contentHash on an applied edit - an atomic batch of text edits to one file, or to several as one apply.",
        "idePrecedent" : "Claude Code Edit tool / IntelliJ Find-and-Replace in File / sed -i",
        "whenToUse" : "You want a few exact text changes in an EXISTING file - one call with path + edits:[{mode:'find-replace', find:'old', replace:'new'}]. The line-replace and regex-replace modes mix freely in the same atomic batch, and files:[{path, edits}] lands several files as one apply. Cheaper than ek9_write_file for small surgical changes; untouched bytes are preserved exactly.",
        "whenNotToUse" : [ "You hold the FULL content of the file (a new file, or an editor tool returned modifiedSource) - use ek9_write_file.", "Renaming an EK9 identifier (class/method/field/local) — use ek9_rename_symbol; it rewrites the workspace-wide reference graph, this tool is text-only and single-file.", "Structural EK9 edits (changing a signature, adding a method, extracting a variable) — use the semantic editor (ek9_change_signature, ek9_add_method, ek9_extract_variable, …); it understands EK9 grammar, this tool does not.", "Removing the whole file — use ek9_delete_file." ],
        "examples" : [ "{path:'orders/constants.ek9', edits:[{mode:'find-replace', find:'MAX_RETRIES <- 5', replace:'MAX_RETRIES <- 10'}], dryRun:true} → status:ready (default expectedOccurrences=1 verifies exactly one match).", "{path:'notes.md', edits:[{mode:'regex-replace', pattern:'// TODO\\\\(.*?\\\\): ', replace:'// TODO: ', expectedOccurrences:'all'}]} → status:applied with a non-ek9-file-not-compile-checked advisory." ],
        "details" : "find-replace is literal + case-sensitive (whitespace/indentation must match exactly - it is NOT re-indented), guarded by expectedOccurrences (integer, default 1, or 'all' — regex-replace shares the guard); line-replace takes 1-based inclusive startLine/endLine + replace (empty deletes) and a replacement written flush-left is re-indented to the replaced range's indentation (relative indentation kept; a later line shallower than the range is kept where written when only that compiles, said in corrected[]); a 'default operator X' line for a construct that already declares X is left out, and a find-replace whose find is gone because what it adds is already in the file is not applied - both said in corrected[] (nothing else to write is status:'unchanged'); regex-replace takes a Java pattern + replace with $1 backreferences. Every mode reads find/replace (replacement, replaceWith, newText, search, oldText and kind are accepted spellings). Edits apply sequentially (edits[1] sees edits[0]'s output) - a batch whose line-replace ranges were all numbered against the file as read, and fails in order but compiles applied bottom-up, is applied that way and says so in corrected[]; validation failures return status:'conflicts' (NO edits applied) with per-edit, mechanically repairable kinds — edit-position-out-of-range / find-not-found (carries a whitespace near-miss hint) / expected-occurrences-mismatch / cascading-edit-conflict / regex-invalid. Non-.ek9 targets are written without compile checking (non-ek9-file-not-compile-checked advisory); expectedContentHash gives whole-file lost-update protection.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "edits" : {
              "description" : "Ordered edit operations, applied sequentially (edits[1] sees edits[0]'s output); one object per edit with mode + find/replace, or startLine/endLine + replace, or pattern + replace.",
              "items" : {
                "properties" : {
                  "endLine" : {
                    "description" : "line-replace: 1-based last line of the range (inclusive).",
                    "type" : "integer"
                  },
                  "expectedOccurrences" : {
                    "description" : "find-replace / regex-replace: how many matches there must be - an integer (default 1) or 'all'; a mismatch is a conflict and nothing is applied."
                  },
                  "find" : {
                    "description" : "find-replace: the exact text (whitespace included); regex-replace: the Java regex (alias of pattern).",
                    "type" : "string"
                  },
                  "mode" : {
                    "description" : "find-replace: literal find → replace; line-replace: startLine..endLine → replace; regex-replace: Java pattern → replace with $1 backreferences.",
                    "enum" : [ "find-replace", "line-replace", "regex-replace" ],
                    "type" : "string"
                  },
                  "pattern" : {
                    "description" : "regex-replace: the Java regex (find is accepted too).",
                    "type" : "string"
                  },
                  "replace" : {
                    "description" : "The replacement text in every mode (empty deletes). A flush-left line-replace is re-indented to the replaced range; find-replace is NOT re-indented.",
                    "type" : "string"
                  },
                  "startLine" : {
                    "description" : "line-replace: 1-based first line of the range (inclusive) - read ek9_read_file with lineNumbers:true first.",
                    "type" : "integer"
                  }
                },
                "required" : [ "mode" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "expectedContentHash" : {
              "description" : "The contentHash ek9_read_file returned for the file's CURRENT content; the call is refused when the on-disk content differs (lost-update protection, same semantics as ek9_write_file).",
              "type" : "string"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "files" : {
              "description" : "MULTI-FILE form: [{path, edits}] instead of path + edits - every file's edits are applied and the workspace compiled ONCE, so a change that only compiles when several files move together (a signature changed here, its callers there) lands as one atomic apply; any file failing rolls back all of them. Use it whenever a single-file edit would be rolled back for diagnostics in OTHER files.",
              "items" : {
                "properties" : {
                  "edits" : {
                    "description" : "That file's edits, as the top-level edits.",
                    "type" : "array"
                  },
                  "path" : {
                    "description" : "File path: workspace-relative (preferred) or absolute inside the workspace. Must exist; an .ek9 source in the multi-file form.",
                    "type" : "string"
                  }
                },
                "required" : [ "path", "edits" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "path" : {
              "description" : "File path: workspace-relative (preferred) or absolute inside the workspace. Must exist.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_new_construct",
        "description" : "Scaffolds one top-level construct (class/record/trait/component/function/type/constant/service) into an EXISTING module file, or a new package.ek9 for mode:'package'; answers status, edits[] (one INSERT, or one CREATE for a package), stubs[] to fill with ek9_insert_statements, added[] and nextStep.",
        "idePrecedent" : "File → New Package… / IntelliJ New… / Cargo new",
        "whenToUse" : "'Create a class Order with its operators and a program Main that prints it' is ONE call: targetFile plus constructs:[{mode:'class', name:'Order', fields, operators, statements...}, {mode:'program', name:'Main', statements:[...]}] - bodies included, compiled once; a single construct is mode + name at the top level. mode:'package' creates the file instead. Pick the mode by intent: class=state+behaviour, record=value+operators, trait=API contract, function=stateless computation, component=DI-wired, type=enum/constrained/alias, service=REST routes, constant, package=publishable metadata; every mode but package appends into the module file (creating the 'defines <genus>' block if absent).",
        "whenNotToUse" : [ "Authoring a program, application, or localized text block — those have their own dedicated tools: ek9_add_program (tests via isTest:true), ek9_add_application, ek9_edit_localized_text.", "Authoring an INLINE closure or dynamic construct inside a function/program body — use ek9_create_dynamic_function or ek9_create_dynamic_class (inline-only, not top-level).", "The construct already exists and you want to add MEMBERS — use ek9_add_method / ek9_add_field / ek9_add_constructor / ek9_add_operator.", "Adding a route to an EXISTING service — use ek9_add_route (symbol-table-bound), not a fresh service scaffold." ],
        "examples" : [ "{targetFile:'task.ek9', constructs:[{mode:'class', name:'Order', fields:['totalPrice as Float', 'customerName as String'], constructors:[{parameters:['totalPrice as Float', 'customerName as String']}], operators:[{symbol:'?', statements:['rtn: totalPrice? and customerName?']}, {symbol:'$', statements:['rtn: customerName']}]}, {mode:'program', name:'Main', statements:['stdout <- Stdout()', 'order <- Order(12.5, \"Alice\")', 'stdout.println($order)']}]} -> the class AND the program that uses it, compiled once, written once - the whole task in one call.", "{mode:'class', targetFile:'tally.ek9', name:'Tally', fields:['count as Integer: 0'], methods:[{name:'bump', parameters:['by as Integer'], statements:['count += by']}, {name:'total', returnType:'Integer', pure:true, statements:['rtn: count']}], operators:[{symbol:'$', statements:['rtn: `Tally(${count})`']}, '?']} -> the whole class, bodies included, compiled and written in ONE call - nothing left to fill.", "{mode:'class', targetFile:'orders/order.ek9', name:'CustomerOrder', extends:'FinancialEvent', traits:[{trait:'Validatable'}], modifiers:['as open']} -> appends an open class with trait composition into the 'defines class' block (created if absent).", "{mode:'type', targetFile:'cards/suit.ek9', name:'CardSuit', typeForm:'enumeration', enumValues:['Hearts','Diamonds','Clubs','Spades']} -> enumeration with auto compare/iterate operators.", "{mode:'function', targetFile:'labels.ek9', name:'findLabel', parameters:[{name:'code', type:'Integer'}], returnType:'String', pure:true} -> a pure function with ONE parameter. Signatures are DATA: no '->' or '<-' anywhere in them; the tool writes the arrows, the indentation and the order. ('code as Integer' is also accepted.)", "{mode:'record', targetFile:'orders.ek9', name:'OrderLine', fields:[{name:'price', type:'Float'}, {name:'quantity', type:'Integer'}], constructors:[{parameters:[{name:'price', type:'Float'}, {name:'quantity', type:'Integer'}]}], operators:['all']} -> record with unset fields, a full constructor, the compiler-required no-arg constructor added and reported under 'added', and every defaultable operator.", "{mode:'class', targetFile:'orders.ek9', name:'Order', fields:['totalPrice as Float', 'customerName as String'], constructors:[{parameters:['totalPrice as Float', 'customerName as String']}], operators:[{symbol:'?', custom:true, override:true, pure:true, returnType:'Boolean'}, {symbol:'$', custom:true, pure:true, returnType:'String'}]} -> class whose '?' and '$' have HAND-WRITTEN bodies (stubs listed under 'stubs'; fill with ek9_insert_statements(file, construct, method:'?', anchor, statements)): '?' needs override:true (inherited from the root), '$' must not have it.", "{mode:'class', targetFile:'names.ek9', name:'UniqueNames', fields:['names as List of String'], methods:[{name:'add', parameters:['name as String']}, {name:'count', returnType:'Integer', pure:true}], operators:['?','$']} -> class wrapping a collection; each method body is a stub listed under 'stubs' for ek9_insert_statements to fill." ],
        "details" : "name is required for every mode (for package it is the MODULE name, com.acme.orders; 'module' is accepted); genus rules are validated before render — record rejects named methods, trait rejects fields, component rejects trait composition. Classes and traits are closed-by-default ('is'/'extends' are synonyms via inheritanceKeyword; pass modifiers:['as open'] for an extensible base — an unresolved or non-open parent is caught by the compile gate); fields-bearing aggregates auto-get 'default operator ?' (E07235) and trait operators render as abstract contracts (Any-provided ?,==,$,$$,#?,<=> skipped + reported). service requires operations[] (>=1 — a route-less service does not compile) and defaults its security posture to bare 'open', not 'as open', when omitted (E12050); package capability enforcement turns on only when publicAccess + version + license + capabilities are ALL set.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "allowOnly" : {
              "description" : "Sealed-hierarchy implementors (class + trait) — renders 'allow only A, B' after the header, restricting who may extend/implement.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "applyStandardExcludes" : {
              "description" : "When true, EK9 standard excludes are merged with excludeFiles. Default false — must opt in. The directive is only emitted when this is explicitly true.",
              "type" : "boolean"
            },
            "applyStandardIncludes" : {
              "description" : "When true, EK9 standard includes (all .ek9 files in source tree) are merged with includeFiles. Default false — must opt in. The directive is only emitted when this is explicitly true.",
              "type" : "boolean"
            },
            "baseType" : {
              "description" : "type/constrained + type/alias: the underlying base type.",
              "type" : "string"
            },
            "body" : {
              "description" : "Raw EK9 source for the package-block body. When supplied, the structured fields below are ignored and the body is emitted verbatim inside the defines package wrapper. Indent each line with four spaces relative to column 0.",
              "type" : "string"
            },
            "capabilities" : {
              "description" : "Fully-qualified gated-type names (e.g. 'org.ek9.lang::Stdout', 'org.ek9.lang::TCP', 'org.ek9.lang::HTTPRequest'). Presence of this list — alongside publicAccess + version + license — turns on compile-time supply-chain capability enforcement for the package. EK9 forbids empty list literals; pass null/omit the parameter for a pure-computation library.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "constraint" : {
              "description" : "type/constrained: the expression after 'constrain as' (e.g. '>= 16 and <= 100').",
              "type" : "string"
            },
            "constructors" : {
              "description" : "Constructors [{parameters:['price as Float', ...], pure?, default?, visibility?}] — one parameter per field, each assigned to the property (':=?' when pure). default:true renders a synthesised 'default Name()' (no body); visibility 'private'/'protected' prefixes the declaration.",
              "items" : {
                "properties" : {
                  "default" : {
                    "type" : "boolean"
                  },
                  "parameters" : {
                    "items" : {
                      "anyOf" : [ {
                        "properties" : {
                          "name" : {
                            "description" : "The parameter's name.",
                            "type" : "string"
                          },
                          "type" : {
                            "description" : "Its type, e.g. 'Integer', 'List of String' - the TYPE only, no arrows.",
                            "type" : "string"
                          }
                        },
                        "required" : [ "name", "type" ],
                        "type" : "object"
                      }, {
                        "description" : "Also accepted: the EK9 text 'name as Type' - but send the object; the tool writes the '->' block, its indentation and its order.",
                        "type" : "string"
                      } ]
                    },
                    "type" : "array"
                  },
                  "params" : {
                    "description" : "Alias of 'parameters'.",
                    "items" : {
                      "anyOf" : [ {
                        "properties" : {
                          "name" : {
                            "description" : "The parameter's name.",
                            "type" : "string"
                          },
                          "type" : {
                            "description" : "Its type, e.g. 'Integer', 'List of String' - the TYPE only, no arrows.",
                            "type" : "string"
                          }
                        },
                        "required" : [ "name", "type" ],
                        "type" : "object"
                      }, {
                        "description" : "Also accepted: the EK9 text 'name as Type' - but send the object; the tool writes the '->' block, its indentation and its order.",
                        "type" : "string"
                      } ]
                    },
                    "type" : "array"
                  },
                  "pure" : {
                    "type" : "boolean"
                  },
                  "visibility" : {
                    "type" : "string"
                  }
                },
                "type" : "object"
              },
              "type" : "array"
            },
            "constructs" : {
              "description" : "SEVERAL constructs in ONE call - each item has the same keys as this tool's top level (mode, name, fields, constructors, methods, operators, traits, parameters, returnType, statements, value, ...), mode:'program' with statements is allowed here, and an item may name its own targetFile (default: the call's) - a file that does not exist yet is created around the item's (or the call's) module. Rendered in order into their 'defines' blocks, every body pre-flighted, every touched file compiled ONCE: 'a trait, the classes over it and the program that drives them' is one call. What compiles is written; a construct that does not is named in refused[] with its diagnostics mapped to the statements[] entry (partial:true) - resend only that one.",
              "items" : {
                "properties" : {
                  "mode" : {
                    "type" : "string"
                  },
                  "module" : {
                    "type" : "string"
                  },
                  "name" : {
                    "type" : "string"
                  },
                  "targetFile" : {
                    "type" : "string"
                  }
                },
                "required" : [ "mode", "name" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "deps" : {
              "additionalProperties" : {
                "type" : "string"
              },
              "description" : "Runtime dependencies as module-name → version-string mapping. Becomes an EK9 Dict literal. Example: { 'ekopen.network.support.utils': '1.6.1-9' }.",
              "type" : "object"
            },
            "description" : {
              "description" : "Package description (becomes a quoted EK9 String literal).",
              "type" : "string"
            },
            "devDeps" : {
              "additionalProperties" : {
                "type" : "string"
              },
              "description" : "Development-only dependencies as module-name → version-string mapping. Same format as deps. Used for build/test dependencies that don't ship with the package.",
              "type" : "object"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "enumValues" : {
              "description" : "type/enumeration: the enum value names.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "excludeDeps" : {
              "additionalProperties" : {
                "type" : "string"
              },
              "description" : "Transitive dependency exclusions: bad-module → context-module mapping. Example: { 'ekopen.some.bad.dependency.pack': 'ekopen.org.supertools.util' }.",
              "type" : "object"
            },
            "excludeFiles" : {
              "description" : "Glob patterns of files to exclude.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "extends" : {
              "description" : "Parent type for class/record/trait/component, OR the abstract function a function extends (function inheritance: 'f() extends abstractFunction'). Pair with inheritanceKeyword for is-vs-extends. Closed-by-default: an unresolved or non-'as open' parent surfaces as a would-fail on dry-run.",
              "type" : "string"
            },
            "fields" : {
              "description" : "Field declarations as DATA: {name, type, defaultValue?, injected?} with BOTH name and type (the EK9 text 'name as Type' / 'name as Type: default' / 'name as Type!' / 'name <- default' is also accepted). A field without a type or default is rejected before anything is rendered. Not valid on traits. A typed field with no default is declared unset ('as Type?') and the compiler-required no-arg constructor is added for you (reported under 'added').",
              "items" : {
                "anyOf" : [ {
                  "properties" : {
                    "defaultValue" : {
                      "description" : "Its initial value, e.g. '0.0', 'List() of String'; omitted renders the type's unset instance.",
                      "type" : "string"
                    },
                    "injected" : {
                      "description" : "true renders the DI form ('as Type!'); never with defaultValue.",
                      "type" : "boolean"
                    },
                    "name" : {
                      "description" : "The field's name.",
                      "type" : "string"
                    },
                    "type" : {
                      "description" : "Its type - the TYPE only, e.g. 'Float', 'List of String'.",
                      "type" : "string"
                    }
                  },
                  "required" : [ "name" ],
                  "type" : "object"
                }, {
                  "description" : "Also accepted: the EK9 text 'name as Type', 'name as Type: default', 'name as Type!' or 'name <- default' - but send the object; the tool writes the declaration and its indentation.",
                  "type" : "string"
                } ]
              },
              "type" : "array"
            },
            "fileMode" : {
              "description" : "mode:package - auto (default; conflict 'file-exists' when the file exists), create-file (alias for auto), overwrite (replace the existing file).",
              "enum" : [ "auto", "create-file", "overwrite" ],
              "type" : "string"
            },
            "includeFiles" : {
              "description" : "Glob patterns of files to include in the package (e.g. '**.{cal}', 'License.txt'). Use alongside or instead of applyStandardIncludes.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "inheritanceKeyword" : {
              "description" : "'extends' (default) or 'is' — synonyms; choice is stylistic (conventionally 'is' for components).",
              "enum" : [ "extends", "is" ],
              "type" : "string"
            },
            "license" : {
              "description" : "License identifier (e.g. 'MIT', 'Apache-2.0'). Becomes a quoted EK9 String literal. One of the four publishable conditions for capability enforcement.",
              "type" : "string"
            },
            "methods" : {
              "description" : "Methods [{name, parameters:[{name, type}, ...], returnType?, pure?, isAbstract?, override?, visibility?, dispatcher?, statements?}]. Each parameter is ONE item as DATA - {name:'code', type:'Integer'} - never a {name} and a {type} as two items, and never with '->' or '<-' in it: the tool writes the arrows and the layout. ('code as Integer' is also accepted.) Not valid on records (operators only). dispatcher:true renders 'as dispatcher' (double-dispatch root). Give the body as statements[] (each entry one statement or one block, assign 'rtn: <value>' on every path) and the member is complete in this call; without it the body is a stub listed under 'stubs' for ek9_insert_statements to fill.",
              "items" : {
                "properties" : {
                  "dispatcher" : {
                    "type" : "boolean"
                  },
                  "isAbstract" : {
                    "type" : "boolean"
                  },
                  "name" : {
                    "type" : "string"
                  },
                  "override" : {
                    "type" : "boolean"
                  },
                  "parameters" : {
                    "items" : {
                      "anyOf" : [ {
                        "properties" : {
                          "name" : {
                            "description" : "The parameter's name.",
                            "type" : "string"
                          },
                          "type" : {
                            "description" : "Its type, e.g. 'Integer', 'List of String' - the TYPE only, no arrows.",
                            "type" : "string"
                          }
                        },
                        "required" : [ "name", "type" ],
                        "type" : "object"
                      }, {
                        "description" : "Also accepted: the EK9 text 'name as Type' - but send the object; the tool writes the '->' block, its indentation and its order.",
                        "type" : "string"
                      } ]
                    },
                    "type" : "array"
                  },
                  "pure" : {
                    "type" : "boolean"
                  },
                  "returnType" : {
                    "description" : "Return TYPE only - the tool writes '<- rtn as <Type>?'; the body assigns 'rtn: ...'.",
                    "type" : "string"
                  },
                  "statements" : {
                    "description" : "The body, one statement or one block per entry (assign 'rtn: <value>' on every path when there is a return); the member is complete in this call, no stub to fill.",
                    "items" : {
                      "type" : "string"
                    },
                    "type" : "array"
                  },
                  "visibility" : {
                    "type" : "string"
                  }
                },
                "required" : [ "name" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "mode" : {
              "description" : "Construct kind. class/record/trait/component/function/type/constant/service/program append into an EXISTING .ek9 module file via the gated changeset path (omitted with parameters/returnType/statements and no fields = function); package creates a new file.",
              "enum" : [ "package", "class", "record", "trait", "component", "function", "type", "constant", "service", "program" ],
              "type" : "string"
            },
            "modifiers" : {
              "description" : "Trailing modifiers verbatim, e.g. ['as open'], ['as abstract']. All four aggregate genera (class/record/trait/component) accept open|abstract — closed by default.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "module" : {
              "description" : "mode:package - the EK9 module name (FQ format like com.acme.orders); the same value as name, accepted in either. With constructs[] - the module a targetFile that does not exist yet is created around (an item's own 'module' wins).",
              "type" : "string"
            },
            "name" : {
              "description" : "Construct name - PascalCase for type-shaped genera, camelCase for a function or constant; for mode:package the MODULE name (com.acme.orders; 'module' is accepted).",
              "type" : "string"
            },
            "operations" : {
              "description" : "service mode (required, >=1): the web operations/routes. Each: {name | operator, verb?, uri, security?, params?, response?}. Use 'name' (+ optional 'verb' GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS, default GET) for a named route, or 'operator' (+= POST, -= DELETE, :^: PUT, :~: PATCH, ? HEAD) for the operator form. 'uri' is the operation path (e.g. '/{id}'). params: [{name, type, in: PATH|QUERY|HEADER|CONTENT|REQUEST|CONTEXT, source?, sanitized?}] — named extractions (PATH/QUERY/HEADER) are auto-captured + referenced so they compile. response: {status?, contentType?, contentLanguage?, cacheControl? | cache:{maxAge, visibility:public|private, mustRevalidate?, noStore?}} — only the fields you set are overridden; HTTPResponse defaults the rest.",
              "items" : {
                "type" : "object"
              },
              "type" : "array"
            },
            "operators" : {
              "description" : "Operators to declare. RULE OF THUMB: a string ('?', '$', 'all') means 'let the compiler synthesise it' (default operator); if the task describes what the operator RETURNS or DOES, you need the object form with custom:true, which renders a stub body for ek9_insert_statements(method:'<sym>') to fill. A plain string (or {symbol}) renders the terse 'default operator <sym>'; 'all' / '*' renders the bare 'default operator' (all defaultable). A CUSTOM operator object {symbol, custom:true, override?, pure?, parameters:[{name,type}], returnType?} renders a CONCRETE operator with a //AI-marked body — for operators the compiler cannot synthesise (a hand-rolled '$', an overriding '?', a mutating '+='). On a trait, default operators render as ABSTRACT contracts ('operator <sym> [as pure] as abstract' with resolved param/return) — Any-provided operators (?,==,$,$$,#?,<=>) are skipped + reported. Valid on class/record/trait/component. A fields-bearing aggregate auto-gets 'default operator ?' if one isn't requested (E07235).",
              "items" : {
                "anyOf" : [ {
                  "description" : "The operator symbol ('?', '$', '==', '<=>' ...) or 'all' - rendered as 'default operator <sym>', synthesised by the compiler from the fields.",
                  "type" : "string"
                }, {
                  "properties" : {
                    "custom" : {
                      "description" : "true = render a CONCRETE operator with a stub body to fill; required whenever the operator's result is described by the task.",
                      "type" : "boolean"
                    },
                    "override" : {
                      "description" : "true for '?' on a class (every class inherits '?' from the root, E05120); false for '$', '+' etc. which have no parent version (E05110).",
                      "type" : "boolean"
                    },
                    "parameters" : {
                      "items" : {
                        "anyOf" : [ {
                          "properties" : {
                            "name" : {
                              "description" : "The parameter's name.",
                              "type" : "string"
                            },
                            "type" : {
                              "description" : "Its type, e.g. 'Integer', 'List of String' - the TYPE only, no arrows.",
                              "type" : "string"
                            }
                          },
                          "required" : [ "name", "type" ],
                          "type" : "object"
                        }, {
                          "description" : "Also accepted: the EK9 text 'name as Type' - but send the object; the tool writes the '->' block, its indentation and its order.",
                          "type" : "string"
                        } ]
                      },
                      "type" : "array"
                    },
                    "pure" : {
                      "type" : "boolean"
                    },
                    "returnType" : {
                      "type" : "string"
                    },
                    "statements" : {
                      "description" : "The body, one statement or one block per entry (assign 'rtn: <value>' on every path when there is a return); the member is complete in this call, no stub to fill.",
                      "items" : {
                        "type" : "string"
                      },
                      "type" : "array"
                    },
                    "symbol" : {
                      "type" : "string"
                    }
                  },
                  "required" : [ "symbol" ],
                  "type" : "object"
                } ]
              },
              "type" : "array"
            },
            "parameters" : {
              "description" : "function mode: input parameters, each ONE item - 'code as Integer' or {name:'code', type:'Integer'}. 'params' is accepted as an alias.",
              "items" : {
                "anyOf" : [ {
                  "properties" : {
                    "name" : {
                      "description" : "The parameter's name.",
                      "type" : "string"
                    },
                    "type" : {
                      "description" : "Its type, e.g. 'Integer', 'List of String' - the TYPE only, no arrows.",
                      "type" : "string"
                    }
                  },
                  "required" : [ "name", "type" ],
                  "type" : "object"
                }, {
                  "description" : "Also accepted: the EK9 text 'name as Type' - but send the object; the tool writes the '->' block, its indentation and its order.",
                  "type" : "string"
                } ]
              },
              "type" : "array"
            },
            "publicAccess" : {
              "description" : "When true, the package can be deployed to the EK9 repository (or a private repository when false-and-published). One of the four publishable conditions. Default false.",
              "type" : "boolean"
            },
            "pure" : {
              "description" : "Pure-construction mode (class/record/component). Renders every field unset ('as Type?', any defaultValue relocated), forces every constructor 'as pure' (assigning via ':=?'), and — when no no-arg constructor was supplied and the construct has assignable fields — adds a public no-arg default-setter, so the construct is constructible in a pure context. Default false.",
              "type" : "boolean"
            },
            "returnType" : {
              "description" : "Return TYPE only, e.g. 'String', 'List of Integer' - never '<- rtn as String', never a name: the tool writes the '<- rtn as <Type>?' declaration and the body assigns 'rtn: <expr>'. Omitted means no return.",
              "type" : "string"
            },
            "security" : {
              "description" : "service mode: the security posture rendered after the base URI — 'open' or 'constrain by Gate [and Cors]'. Mandatory at service or operation level (E12050); defaults to 'open' when neither is given.",
              "type" : "string"
            },
            "serviceUri" : {
              "description" : "service mode (required): the base URI, e.g. '/addresses' — renders 'Name for :/addresses <posture>'. Base URIs cannot contain path variables (E07680).",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "statements" : {
              "description" : "mode:function - the function's body, one statement or one block per entry (assign 'rtn: <value>' on every path when there is a return): the function is complete in this call, no stub to fill.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "tags" : {
              "description" : "Tag list — used for repository search.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "targetFile" : {
              "description" : "Destination: workspace-relative path of the .ek9 file - for package the NEW file to create (conflict 'file-exists' unless fileMode:'overwrite'); for every other mode the EXISTING module file the construct is appended to (its 'defines <genus>' block is created if absent). With constructs[] it is the default for items that name no targetFile of their own.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "traits" : {
              "description" : "'with trait of' composition (class/trait only). Each {trait, byField?}; byField delegates the trait to a field.",
              "items" : {
                "properties" : {
                  "byField" : {
                    "type" : "string"
                  },
                  "trait" : {
                    "type" : "string"
                  }
                },
                "type" : "object"
              },
              "type" : "array"
            },
            "typeForm" : {
              "description" : "type mode (required): 'enumeration' | 'constrained' | 'alias'.",
              "enum" : [ "enumeration", "constrained", "alias" ],
              "type" : "string"
            },
            "typeParameters" : {
              "description" : "Conceptual type parameters for a GENERIC class or function: [{name, constrainBy?}] → 'of type T' or 'of type (K, V)', each optionally 'constrain by <Type>'. Member signatures may reference the parameter names (T/K/V) as types. The parameterised header is grammar-incompatible with extends/with-trait-of/allow-only, so those are omitted when present.",
              "items" : {
                "properties" : {
                  "constrainBy" : {
                    "type" : "string"
                  },
                  "name" : {
                    "type" : "string"
                  }
                },
                "type" : "object"
              },
              "type" : "array"
            },
            "value" : {
              "description" : "constant mode: the literal verbatim (e.g. '\"localhost\"', '42', '3.14') — emitted as 'NAME <- value'.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            },
            "version" : {
              "description" : "Package version literal (Version type, not a String). Strict semver: MAJOR.MINOR.PATCH-BUILD or MAJOR.MINOR.PATCH-FEATURE-BUILD. Default '1.0.0-0'.",
              "type" : "string"
            }
          },
          "required" : [ "targetFile" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_remove_statements",
        "description" : "Deletes a contiguous statement range from one method/function/operator body; answers status and edits[] (one DELETE per line), or conflicts[] (range-straddles-method-boundary, range-not-on-statement-boundary, removed-statement-declares-used-local, removal-is-a-replacement, range-empties-body).",
        "idePrecedent" : "IntelliJ Cmd+Backspace delete-lines / LSP code action \"Remove statements\"",
        "whenToUse" : "'Delete these two statements inside the body' is one call with file, fromText and toText (the first and last line as written), or startLine:36, endLine:37 - the range must sit inside ONE method/function/operator body on statement boundaries. Use it to drop specific statements without a read + full-text rewrite (the second tool in a force-delete cleanup loop).",
        "whenNotToUse" : [ "Removing an unused entry from the references block (not body statements) - use ek9_organize_references (often run AFTER this tool).", "Removing the `<- rtn` return-value declaration (a signature change) - use ek9_change_signature; removing a class/record FIELD - use ek9_remove_field.", "Moving a block to a new method rather than dropping it - use ek9_extract_method; removing a whole function/method by inlining it at call sites - use ek9_inline_function.", "The range is NOT inside a body - use ek9_edit_file line-replace with empty replacement (references block, package metadata) or ek9_remove_comments (whole-line comment blocks); deleting the entire file - use ek9_delete_file." ],
        "examples" : [ "{file:'helloWorld.ek9', startLine:36, endLine:37} -> status:applied; the `isValid <-` declaration and its `require isValid` guard removed together.", "{file:'helloWorld.ek9', startLine:36, endLine:36, dryRun:true} -> status:conflicts (removed-statement-declares-used-local, details:'36-37') because `isValid` is read on line 37 - re-issue with the range named; a removal whose closure is the whole body or has gaps is removal-is-a-replacement, naming ek9_edit_file line-replace with its exact range." ],
        "details" : "Range MUST stay within one body and align with statement boundaries. Conflict kinds: range-straddles-method-boundary (mechanical - narrow to one body; a WHOLE member goes through ek9_safe_delete), range-not-on-statement-boundary (mechanical - response carries the boundary-snapped range to re-issue), range-includes-return-value-declaration (use ek9_change_signature). Before any compile the removal CLOSURE is computed by symbol (a kept statement using a removed local goes too; so does a kept local whose every read is removed): one range that compiles is removed-statement-declares-used-local / removal-leaves-unread-local with that exact range in details; gaps or the whole body is removal-is-a-replacement (no widening compiles - the new statements go in with ek9_edit_file line-replace or ek9_insert_statements anchor:'replace'); range-empties-body when every statement goes and the return is not initialised. Statement extents are ANTLR-derived so comments and string literals never false-match; indentation and unrelated statements are preserved.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "endLine" : {
              "description" : "Or the last line to remove (1-based, inclusive). Must be inside the SAME body as startLine, must align with a statement boundary, must be >= startLine.",
              "type" : "integer"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "Workspace-relative path to the .ek9 source file containing the target body.",
              "type" : "string"
            },
            "fromText" : {
              "description" : "The block's FIRST line exactly as written (indentation ignored) - instead of counting startLine. Must name one line; add more of it when it occurs twice.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "startLine" : {
              "description" : "Or the first line to remove (1-based, inclusive) - an IDE selection. Must be inside a method/function/operator body and must align with a statement boundary.",
              "type" : "integer"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "toText" : {
              "description" : "The block's LAST line as written - the first line matching it at or after fromText. Omit it for a one-line block.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_remove_comments",
        "description" : "Deletes one whole-line comment block (//, <!- -!>, <!-- --> or <?- -?>) in full; answers status and edits[] (one DELETE per line), or conflicts[] (kind 'comment-shares-line-with-code') when a boundary line also carries code.",
        "idePrecedent" : "IntelliJ Cmd+Shift+/ delete-block-comment / LSP code action \"Remove comment\"",
        "whenToUse" : "'Drop the //AI: comment in orders.ek9' is one call with file + symbol:'AI' (a word from the comment) or line (any line inside it) - the tool expands to the whole block. Use it on a stale whole-line comment: an acted-on instruction, or a <?- -?>/<!- -!> block whose target was removed.",
        "whenNotToUse" : [ "Removing CODE statements, not comments - use ek9_remove_statements (comments live on the HIDDEN ANTLR channel that remove_statements does not touch).", "Stripping a TRAILING comment from a code-bearing line (e.g. `x <- 10 //note`) — V1 refuses with comment-shares-line-with-code; use ek9_edit_file line-replace.", "Removing many comments at once or reshaping the file beyond comment removal — use ek9_write_file (overwrite) as the read+rewrite escape hatch." ],
        "examples" : [ "Drop an instruction comment: {file:'hello.ek9', line:3} → status:applied, one DELETE edit; summary 'Removed line-comment on line 3'.", "Mid-block hint on a doc comment: {file:'hello.ek9', line:4} where the <?- -?> spans 2-5 → status:applied with a DELETE per line 2-5; summary 'Removed doc-comment on lines 2-5'." ],
        "details" : "summary names the comment form removed: line-comment / block-comment / block-comment-html / doc-comment; edits are one DELETE per resolved line. A comment whose first or last line also carries code outside the delimiters returns status:'conflicts' (kind comment-shares-line-with-code).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "Workspace-relative path to the .ek9 source file containing the comment.",
              "type" : "string"
            },
            "line" : {
              "description" : "Any 1-based line inside the comment - the tool expands to the full extent of the block via the ANTLR token stream. OMIT it and pass symbol:'<word>' to let the tool find the line.",
              "type" : "integer"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "A word from the comment's text (e.g. 'AI' from '//AI: ...'); the first line carrying it as a whole word anchors the call. Pass line instead when the word also occurs in code.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_insert_statements",
        "description" : "Splices EK9 statement(s) into a named method/function/operator body at anchor top, bottom, replace or a body line, re-indented by the tool; answers status, edits[] (one INSERT), insertedLineRange and resultingBodySnippet, or error.problems[] naming the entry that does not parse.",
        "idePrecedent" : "IntelliJ \"Generate\" menu / LSP code action \"Insert at cursor\"",
        "whenToUse" : "'Add a statement to the end of Greeter.hello' is one call with file, symbol:'Greeter.hello', anchor:'bottom', statements:[...] - the tool owns indent and formatting. Use it to ADD statement(s) into an existing method/function/operator body (a log line, a guard at the head, a new field's first use) without a full-text rewrite.",
        "whenNotToUse" : [ "Adding a whole METHOD/FIELD/constructor declaration - use ek9_add_method / ek9_add_field / ek9_add_constructor; this tool only fills existing bodies.", "WRAPPING existing statements in try/if/for - use ek9_surround_with; REMOVING statements - use ek9_remove_statements (symmetric pair; replace = remove then insert).", "Adding a `<- rtn` return-value declaration - that is a signature change, use ek9_change_signature (conflicts here as inserted-content-includes-return-value-declaration).", "Body inside a `defines text for` block - use ek9_edit_localized_text; target line not inside any body (references block, package metadata, comment) - use ek9_edit_file." ],
        "examples" : [ "{file:'hello.ek9', symbol:'Main', anchor:'bottom', statements:['stdout.println(welcome.farewell())']} → line appended to the program body at the body indent.", "{file:'greeter.ek9', symbol:'Greeter.hello', anchor:'top', statements:['require welcome?']} → guard lands at the first executable line (after any `<- rtn` declaration).", "{file:'labels.ek9', symbol:'findLabel', anchor:'top', statements:[\"switch code\\n  case 1\\n    rtn: \\\"active\\\"\\n  case 2\\n    rtn: \\\"inactive\\\"\\n  default\\n    rtn: \\\"unknown\\\"\"]} → the whole switch as ONE multi-line entry; its nesting is kept and it replaces a scaffold's placeholder body (the 'require code?' guard survives)." ],
        "details" : "anchor: position top/bottom, or afterLine/beforeLine (a line inside the body). Statement text is re-indented to body level (common leading whitespace stripped, relative indent kept), one top-level statement per statements[] entry - an entry MAY be a multi-line block (switch/if/for/try/while, with '\\n' and two-space nesting). Every entry is parsed alone BEFORE the splice: a syntax error or a tab is status:error, written:false, error.problems[{path:'statements[i]', line, message}] with nothing written (the compile gate stays the semantic check). position:top resolves to the first valid statement position (after any `<- rtn` declaration) and REPLACES a scaffold's placeholder body (the require guards survive; appliedAnchor.replacedPlaceholder:true). Conflict kinds: dynamic-body-not-supported (dynamic function/class bodies rejected fail-fast) and inserted-content-includes-return-value-declaration; unknown construct/method surfaces as status:error, and success carries appliedIndent, appliedAnchor, insertedLineRange and resultingBodySnippet (±2-line window; includeBodySnippet:true swaps in the full post-insert body — check bodySnippetSource).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "anchor" : {
              "anyOf" : [ {
                "description" : "top: the first statement position (replaces a scaffold's placeholder; the tool resolves the rtn shape). bottom: after the last existing statement. replace: the WHOLE body is replaced by statements[] - the signature and any '<- rtn' declaration stay; use it to re-implement a member (a body 'rtn: <expr>' overrides an inline '<- rtn as T: value').",
                "enum" : [ "top", "bottom", "replace" ],
                "type" : "string"
              }, {
                "description" : "A 1-based line INSIDE the body: insert immediately AFTER it (the same as {afterLine:N}).",
                "type" : "integer"
              }, {
                "properties" : {
                  "afterLine" : {
                    "description" : "Insert immediately after this 1-based line of the body.",
                    "type" : "integer"
                  },
                  "beforeLine" : {
                    "description" : "Insert immediately before this 1-based line of the body.",
                    "type" : "integer"
                  },
                  "position" : {
                    "enum" : [ "top", "bottom", "replace" ],
                    "type" : "string"
                  }
                },
                "type" : "object"
              } ],
              "description" : "WHERE in the body: 'top' (first statement position; replaces a scaffold's placeholder), 'bottom' (after the last statement), 'replace' (the whole body - re-implement the member), a bare line number N (= {afterLine:N}), or {beforeLine:N} - a 1-based line INSIDE the body; with ek9_remove_statements(startLine, endLine) this replaces lines in the middle of a body."
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "Workspace-relative path to the .ek9 source file containing the target body (optional - found from symbol when omitted).",
              "type" : "string"
            },
            "includeBodySnippet" : {
              "default" : false,
              "description" : "When true, response.resultingBodySnippet carries the entire method/function body post-insert (delegated to ConstructSourceExtractor against the post-write program). Default false returns a ±2-line context window around the insert — usually enough to eyeball indent and position. Always check response.bodySnippetSource to confirm which path produced the snippet: \"extractor\" = full body delivered; \"contextWindowFallback\" = opt-in requested but the response carries the cheap window despite your request (typically because this call was dryRun:true — no file written, so no post-write extractor probe runs).",
              "type" : "boolean"
            },
            "memberName" : {
              "description" : "Method or operator name within symbol when symbol is the bare type ('Greeter' + memberName:'hello' is the same as symbol:'Greeter.hello'). OMIT for a program or a top-level function - do not send the string \"null\".",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "statements" : {
              "description" : "Ordered list of EK9 statements to insert. Each entry is ONE top-level statement and MAY span several lines with '\\n' - a switch, an if, a for, a try - with its relative indentation preserved (two spaces per level); the tool owns the absolute indent. Write a block as ONE entry: \"switch code\\n  case 1\\n    rtn: \\\"active\\\"\\n  default\\n    rtn: \\\"unknown\\\"\" (one line per entry also works when the nested lines are indented deeper than the first entry). Every entry is parsed alone before anything is written; a syntax error is rejected as error.problems[{path:'statements[i]', line}] with nothing written. Spaces only - a tab is rejected.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "symbol" : {
              "description" : "The body's owner: a program or top-level function by name ('Main', 'findLabel'), or a member as 'Type.member' ('Greeter.hello', an operator by its bare symbol: 'Order.$'). 'mod::Name' is understood.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "symbol", "anchor", "statements" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_create_dynamic_function",
        "description" : "Inserts `variableName <- (captures) is|extends Base as [pure] function` with its body (or an inline `(expr)`, or an //AI: stub) after the line; answers status, edits[] and summary, with a would-fail carrying the compiler's own diagnostics.",
        "idePrecedent" : "IntelliJ \"Introduce Lambda\" / anonymous-class generate action",
        "whenToUse" : "'A closure gt over the captured min implementing discriminator' is one call with file, line (the line it goes after), variableName, baseType and bodyExpression.",
        "whenNotToUse" : [ "You need a trait implementation (with trait of T) or a tuple-style class - use ek9_create_dynamic_class.", "You are authoring a NAMED, module-level reusable function - use ek9_new_construct mode:'function'.", "You are factoring an existing block of code into a function - use ek9_extract_method; this tool creates from scratch." ],
        "examples" : [ "{file:'filter.ek9', line:47, variableName:'isOverThreshold', baseType:'predicate', pure:true, captures:[{fieldName:'threshold', sourceExpression:'minOrderValue'}], bodyExpression:'rtn: number > threshold'} → status:ready, one INSERT `isOverThreshold <- (threshold: minOrderValue) is predicate as pure (rtn: number > threshold)`.", "{file:'p.ek9', line:18, variableName:'asString', baseType:'Function of (Integer, String)', captures:[]} (no body) → bodyless stub: block form with `r: String()` + `//AI: provide implementation`." ],
        "details" : "Single-mode - the syntactic variant is derived from the args: body -> multi-line block; bodyExpression -> inline `(expr)` for a named base, promoted to block for a parameterised base (grammar makes `function` mandatory there); both omitted -> `//AI:` scaffold stub. pure and inheritanceKeyword (is|extends) auto-detect from the base (is + non-pure for a user base; extends + pure for a pure built-in super), both overridable. Conflict kinds: target-not-function-body (escalation - anchor line not inside a callable body), dynamic-body-not-supported (nesting inside a dynamic body, V1 refusal), bodyless-stub-needs-body (a no-return base like Consumer/Acceptor needs a body); everything else - base unresolved, purity mismatch, capture syntax E06230/E06240/E02040 - surfaces as the scratch-compile's own diagnostics (the fresh binding's expected E08090 is tolerated).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "baseType" : {
              "description" : "The abstract function being implemented: a user-defined function name in scope, or a parameterised built-in (e.g. `Function of (Integer, String)`, `Predicate of Integer`, `Consumer of String`).",
              "type" : "string"
            },
            "body" : {
              "description" : "Multi-line body. Mutually exclusive with bodyExpression. Refers to the base function's parameter names and its return-variable name. Omit BOTH body and bodyExpression to scaffold a `//AI: provide implementation` stub (block form + default-initialised inherited return) — supported only for a base that RETURNS a value; a no-return base (Consumer/BiConsumer/Acceptor/BiAcceptor) requires a body and a bodyless request is refused.",
              "type" : "string"
            },
            "bodyExpression" : {
              "description" : "Single-expression inline body (e.g. `rtn: number > threshold`). Mutually exclusive with body. For a NAMED base emitted inline as `(<expr>)`; for a PARAMETERISED base it is promoted to the block form (EK9's grammar makes the `function` keyword mandatory there).",
              "type" : "string"
            },
            "captures" : {
              "description" : "Capture list (may be empty). Each entry: {fieldName?, sourceExpression}. Captures are by-pointer-value — mutating the captured object inside the closure is visible to the caller (copy via :=: before capture for isolation).",
              "items" : {
                "additionalProperties" : false,
                "properties" : {
                  "fieldName" : {
                    "description" : "Closure-field name (named capture). Omit for a simple identifier capture (field takes the same name). REQUIRED for literals/expressions, and all-or-none across the list.",
                    "type" : "string"
                  },
                  "sourceExpression" : {
                    "description" : "The value captured — a bare identifier, or any expression when fieldName is supplied.",
                    "type" : "string"
                  }
                },
                "required" : [ "sourceExpression" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The .ek9 source file containing the host body (workspace-relative; an absolute path inside the workspace is accepted).",
              "type" : "string"
            },
            "inheritanceKeyword" : {
              "description" : "`is` and `extends` are synonyms. Default: `extends` for a parameterised (generic) base, `is` otherwise.",
              "enum" : [ "is", "extends" ],
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line AFTER which the dynamic function is inserted (read it from ek9_read_file lineNumbers:true). Must be inside a function/program/method body and AFTER the captured variables are declared (a closure with captures cannot be hoisted above them).",
              "type" : "integer"
            },
            "pure" : {
              "description" : "Emit `as pure`. Default: auto — true for a pure built-in super (Predicate/Function/Consumer/...), false otherwise. Must match the base's purity or the scratch-compile reports a purity mismatch.",
              "type" : "boolean"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "variableName" : {
              "description" : "Local variable that receives the closure (e.g. gt, transformer, predicate1).",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file", "line", "variableName", "baseType" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_create_dynamic_class",
        "description" : "Inserts `variableName <- (captures) trait of T as class` (or the tuple / generic form) with its members after the line, auto-adding `default operator ?` for captures; answers status, edits[] and summary, with a would-fail carrying the compiler's own diagnostics (unimplemented abstract methods named).",
        "idePrecedent" : "IntelliJ \"Convert to anonymous class\" / anonymous-class generate action",
        "whenToUse" : "'An inline class implementing Describable over the captured prefix' is one call with file, line (the body line it goes after), variableName and traits:[{name}].",
        "whenNotToUse" : [ "You need a closure/lambda (an abstract-function impl), not a class - use ek9_create_dynamic_function.", "Authoring a NAMED top-level class reusable across the module, or one extending a non-generic concrete class (dynamic classes cannot) - use ek9_new_construct mode:'class'.", "Adding a trait to, or implementing methods on, an EXISTING declared type - use ek9_adopt_trait / ek9_implement_methods." ],
        "examples" : [ "Trait, inline + capture: {file:'report.ek9', line:12, variableName:'wrapper', traits:[{name:'Describable'}], captures:[{sourceExpression:'prefix'}], members:'override describe() as pure\\n  <- rtn as String: prefix'} → status:applied (auto `default operator ?` appended for the captured field).", "Generic, header-only + name-the-gaps: {file:'iter.ek9', line:12, variableName:'iter', baseType:'Iterator of Integer', dryRun:true} → status:would-fail naming the unimplemented abstract methods (set stubAbstractMethods:true to scaffold them as //AI: stubs → status:ready)." ],
        "details" : "Form is set by `form` or auto-detected (traits present -> trait; baseType present -> generic; else tuple); className is a module-level TYPE distinct from the variableName receiver — mandatory for tuple, optional (anonymous) for trait/generic. Scratch-compilation is the sole validator: would-fail carries the compiler's own diagnostics (unresolved trait/base, unimplemented abstract methods, name collision E01040, purity, capture syntax E06230/E06240/E02040), while the fresh binding's expected E08090 (scaffold not yet referenced) is tolerated so a clean insert stays ready. conflicts kinds: target-not-function-body (bad anchor), dynamic-body-not-supported (nesting into a dynamic body, v1 limit).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "baseType" : {
              "description" : "Generic-extension form: the parameterised base (e.g. `Iterator of Integer`, `Function of (String, Integer)`).",
              "type" : "string"
            },
            "captures" : {
              "description" : "Capture list (may be empty). Each entry: {fieldName?, sourceExpression}. Captures are by-pointer-value — mutating a captured object inside the class is visible to the caller (copy via :=: before capture for isolation). A non-empty list auto-adds `default operator ?`.",
              "items" : {
                "additionalProperties" : false,
                "properties" : {
                  "fieldName" : {
                    "description" : "Closure-field name (named capture). Omit for a simple identifier capture (field takes the same name). REQUIRED for literals/expressions, and all-or-none across the list. Tuple captures are usually named (they double as the type's fields).",
                    "type" : "string"
                  },
                  "sourceExpression" : {
                    "description" : "The value captured — a bare identifier, or any expression when fieldName is supplied.",
                    "type" : "string"
                  }
                },
                "required" : [ "sourceExpression" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "className" : {
              "description" : "The dynamic class's own name — the grammar identifier, a module-level TYPE, distinct from variableName. REQUIRED for the tuple form; optional (anonymous when omitted) for trait/generic forms.",
              "type" : "string"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The .ek9 source file containing the host body (workspace-relative; an absolute path inside the workspace is accepted).",
              "type" : "string"
            },
            "form" : {
              "description" : "Which grammar alternative to emit. Auto-detected when omitted: traits present → trait; baseType present → generic; otherwise tuple.",
              "enum" : [ "trait", "tuple", "generic" ],
              "type" : "string"
            },
            "inheritanceKeyword" : {
              "description" : "Generic-extension form only: `is` and `extends` are synonyms. Default: `is`.",
              "enum" : [ "is", "extends" ],
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line AFTER which the dynamic class is inserted (read it from ek9_read_file lineNumbers:true). Must be inside a function/program/method body and AFTER the captured variables are declared (a class with captures cannot be hoisted above them).",
              "type" : "integer"
            },
            "members" : {
              "description" : "Multi-line aggregateParts body: fields, then methods, then operators, in grammar order (the caller owns ordering). Required for the tuple form UNLESS at least one capture is supplied (captures become the tuple's fields + an auto `default operator ?`). Omit for trait/generic to scaffold header-only (the dryRun would-fail then names the abstract methods to implement). If you author an `operator ?`, the auto `default operator ?` is suppressed.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "stubAbstractMethods" : {
              "default" : false,
              "description" : "When true, scaffold the OUTSTANDING abstract methods the base owes, each as a compiling `override … //AI: complete method` stub (require-guarded params, guard-first return). Generic form: the base type's abstract methods with conceptual type parameters substituted (`Iterator of Integer` -> `next() as Integer`). Trait form: the traits' unimplemented abstract methods. Tuple form has no abstract obligations (no-op). Deduped against methods you already supply in `members` — only the missing ones are added. The result is a SCAFFOLD: bodies are placeholders behind `//AI:` markers, and the fresh variable's E08090 (not-yet-referenced) is tolerated so the dryRun is `ready`. Leave false to instead have the would-fail simply NAME the methods to add.",
              "type" : "boolean"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "traits" : {
              "description" : "Trait form: the traits to implement or delegate. Each {name, by?}. Order preserved.",
              "items" : {
                "additionalProperties" : false,
                "properties" : {
                  "by" : {
                    "description" : "Optional delegate: a captured field name. When given, emits `Name by field` — the whole trait is delegated to that captured field (no method stubs needed). Omit to implement the trait's abstract methods inline in `members`.",
                    "type" : "string"
                  },
                  "name" : {
                    "description" : "Trait name in scope (e.g. LenValidator).",
                    "type" : "string"
                  }
                },
                "required" : [ "name" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "variableName" : {
              "description" : "Local variable that receives the instance (e.g. wrapper, iterator1). Emitted as `variableName <- …`.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file", "line", "variableName" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_add_field",
        "description" : "Inserts the field declaration at the construct's canonical field position; answers status, edits[] and summary, a clashing name as conflicts[] (name-collision).",
        "idePrecedent" : "Generate Field (IntelliJ) / Quick Fix - Add Field",
        "whenToUse" : "'Add a field to CustomerOrder' is one call with symbol:'CustomerOrder', fieldName and defaultValue (or rawDeclaration:'logger as Logger!') - no file or line.",
        "whenNotToUse" : [ "Adding a METHOD or CONSTRUCTOR, not a field - use ek9_add_method / ek9_add_constructor.", "The target construct doesn't exist yet - create it with ek9_new_construct first.", "Renaming or removing an existing field - use ek9_rename_field / ek9_remove_field.", "Traits, functions, services, applications, programs, text - these grammatically have no fields; the tool returns a target-construct-is-not-fielded conflict." ],
        "examples" : [ "{symbol:'CustomerOrder', fieldName:'cachedTotal', defaultValue:'Money(\"0\")', dryRun:true} → status:ready, edits[] one INSERT `cachedTotal <- Money(\"0\")` (type inferred from the default).", "{symbol:'CustomerOrder', rawDeclaration:'logger as Logger!'} → status:applied; the `!` suffix marks the field for DI injection." ],
        "details" : "Two forms: structured (fieldName + defaultValue; defaultValue REQUIRED for records) or raw (rawDeclaration as typed, e.g. `name as String?` or `logger as Logger!`); mode is inferred from which is given and a contradicting mode is a conflict. Visibility is genus-fixed: class/component fields are always private, record fields always public - no access-modifier syntax exists. A pre-existing field name is a name-collision conflict (structured) or hard error (raw); an UNINITIALISED field can trip E07175/E07170 (public no-arg ctor would build a partial object) → would-fail - fix with an initialiser, a private no-arg ctor, or an explicit ctor.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "defaultValue" : {
              "description" : "Default value expression for the field, e.g. '0', '\"\"', 'String()'. Required for records in the structured form.",
              "type" : "string"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "fieldName" : {
              "description" : "The new field's name (structured form) - with 'type' or 'defaultValue'; a name alone renders a typeless field (E07110).",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "mode" : {
              "description" : "Optional - inferred: rawDeclaration → raw, fieldName → structured.",
              "enum" : [ "structured", "raw" ],
              "type" : "string"
            },
            "rawDeclaration" : {
              "description" : "The whole declaration line as you would type it (raw form), e.g. 'name as String?' or 'logger as Logger!' (DI).",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The class, record or component the field is added to - a bare name ('Order') finds its file workspace-wide; 'mod::Order' when the name is declared in two modules.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "type" : {
              "description" : "The field's type in the structured form, e.g. 'Float', 'List of String' - rendered 'name as Type: Type()' (present, unset until assigned) unless defaultValue is given.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            },
            "withConstructor" : {
              "description" : "Insert a NEW pure constructor taking this field, in the SAME change - the fields-mode rendering ek9_add_constructor mode:'fields' uses. Only applies when the field is left with no value ('name as Type?' / rawDeclaration ending '?', or the structured form with 'type' and no 'defaultValue') - that is the one shape a publicly-constructible class can leave uninitialised (E07170/E08070/E08060) until a second ek9_add_constructor call lands one; any other form already has what it needs and the option is a no-op. Default false.",
              "type" : "boolean"
            }
          },
          "required" : [ "symbol" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_rename_field",
        "description" : "Renames a private field and every use inside its owning construct body in one atomic edit; answers status and edits[] (one REPLACE per rewritten line).",
        "idePrecedent" : "Refactor → Rename (IntelliJ, field scope)",
        "whenToUse" : "'Rename the field farewell on Greeter to signoff' is one call with symbol:'Greeter.farewell', newName:'signoff' - no file or line needed. Use it for a class/record/component FIELD: the declaration plus every use inside the construct body, in one atomic edit.",
        "whenNotToUse" : [ "Renaming a method, type, function, local or any symbol whose uses cross files - use ek9_rename_symbol (the workspace-wide superset; it renames fields too).", "The field doesn't exist yet — use ek9_add_field, don't rename what isn't there.", "You want to DELETE the field, not rename it — use ek9_remove_field.", "The user supplied the exact modified source text — use ek9_edit_file; this tool is a semantic construct-scoped rename, not a raw edit." ],
        "examples" : [ "{symbol:'Class1.label', newName:'displayLabel'} → one REPLACE per rewritten line: the declaration plus every body use, including boolean chains and ${label} interpolation sites." ],
        "details" : "Every blocking case is an error, never conflicts[]: construct/field not found, name collision on the same construct, newName equals oldName, invalid/reserved identifier, or a synthesised member with no source occurrence. V1 find-references is a word-boundary textual scan over the construct's body byte range, so a same-named local that shadows the field is also renamed.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "newName" : {
              "description" : "New field name. Must be a valid EK9 identifier and not collide with an existing field on the same construct.",
              "type" : "string"
            },
            "oldName" : {
              "description" : "Current field name. Must exist on the construct (omit when symbol is 'Type.field').",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The construct that owns the field ('Greeter'), or the field itself as 'Greeter.farewell' (then oldName may be omitted) - found workspace-wide, no file needed.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "symbol", "newName" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_remove_field",
        "description" : "Deletes a field from a class/record/component; answers status and edits[] (one DELETE), or conflicts[] with every in-body reference classified by 'kind' and a suggestedRepair for the mechanical ones.",
        "idePrecedent" : "Refactor → Safe Delete (IntelliJ, field scope)",
        "whenToUse" : "'Remove the field spare from Greeter' is one call with symbol:'Greeter.spare' - no file or line needed. Use it when a class/record/component field is dead, redundant or being collapsed; the tool walks the owning construct's body and deletes it or reports the blocking reference sites.",
        "whenNotToUse" : [ "Deleting a method, type, function or any symbol whose uses cross files - use ek9_safe_delete (the workspace-wide superset; it deletes fields too).", "Keeping the field but changing its name — use ek9_rename_field instead." ],
        "examples" : [ "Refs exist: {symbol:'Class1.cachedTotal'} → status:conflicts with a boolean-chain-term site + suggestedRepair; apply the repair, then re-call.", "Clean: same args once refs are gone → one edit deleting the declaration line." ],
        "details" : "Each conflict carries a 'kind': boolean-chain-term / format-string-interpolation / constructor-param are mechanically repairable (constructor-param repairs cascade to callers in other files); expression-operand is the ESCALATION kind — semantics change, ask the human. The server re-walks the construct body on every call, so re-call after repairs rather than self-judging completion; there is no force flag — the conflicts list dominates. Default operators on the construct (<=>, ?, $, #?, ...) never block removal but surface a wouldAutoDerive[] note — derived identity/equality/string form shifts silently, so warn the human.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "memberName" : {
              "description" : "Field to remove. Must exist on the construct (omit when symbol is 'Type.field').",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The construct that owns the field ('Greeter'), or the field itself as 'Greeter.spare' (then memberName may be omitted) - found workspace-wide, no file needed.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "symbol" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_introduce_field",
        "description" : "Lifts a local and its initialiser to a same-named field of the enclosing class/record/component and deletes the local (adding `default operator ?` when it is the first field); answers status, edits[] and conflicts[] (no-promotable-declaration, not-inside-fielded-aggregate, field-name-collision).",
        "idePrecedent" : "Refactor → Introduce Field (from local variable)",
        "whenToUse" : "'Make the local counter a field' is one call with file + symbol:'counter' - no line needed. Use it when a local declared inside a method/operator/constructor should become per-instance state shared by the construct's other members; the field keeps the name.",
        "whenNotToUse" : [ "Lifting a `name <- literal` to a MODULE constant, not a field - use ek9_promote_to_constant.", "Scaffolding a brand-new field rather than lifting an existing local — use ek9_add_field.", "Wanting a DIFFERENT field name — introduce keeps the name; run ek9_rename_symbol afterwards.", "The local is not inside a class/record/component (e.g. a top-level function) — no fielded aggregate, so there is nowhere to lift to." ],
        "examples" : [ "First field: {file:'acc.ek9', symbol:'balance'} → status:applied; local removed, field inserted, AND `default operator ?` appended (EK9 tri-state requirement E07235).", "Preview an unsafe lift: {file:'acc.ek9', symbol:'balance', dryRun:true} where the initialiser reads a parameter → status:would-fail (E04050/E50001) — fix the local first, do not commit." ],
        "details" : "Name is kept verbatim, so in-construct references still resolve (to the field) and simple cases keep compiling; the two hard cases surface as would-fail via the scratch-compile gate: an initialiser referencing a parameter/local (E04050 for a complex expression, E50001 for a bare reference) and another method's same-named local now duplicating the field (E50050). When the aggregate has no own `?` operator (typically this is its first field), `default operator ?` is also appended (E07235 tri-state requirement). conflicts kinds: no-promotable-declaration (line is not a single-line initialised local), not-inside-fielded-aggregate (not inside a class/record/component), field-name-collision (aggregate already declares that field). Fields take the construct's natural visibility (class/component private, record public) — no visibility parameter.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "Workspace-relative path to the .ek9 source file holding the local declaration.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The identifier you mean (e.g. 'total'); the tool finds its declaration line in file. Pass line only to anchor one specific occurrence.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_introduce_parameter",
        "description" : "Appends a new parameter to the enclosing method/function, replaces the selected in-body expression with it and passes the ORIGINAL expression at every call site (Fowler \"Introduce Parameter\"); answers status, edits[] and conflicts[] (value-expression-not-a-single-expression, type-not-inferable, ...).",
        "idePrecedent" : "Refactor → Introduce Parameter (IntelliJ)",
        "whenToUse" : "'Give greet a parameter greeting and use it instead of the literal \"Hello \"' is one call with file, expression:'\"Hello \"' and name:'greeting' - the body uses the parameter and every existing call site is passed the original value. Use it (not ek9_change_signature, which adds the parameter but leaves the body unchanged) when a body hard-codes a value callers should be able to vary - in a statement or in the '<- rtn as T: ...' initialiser.",
        "whenNotToUse" : [ "The value should be a LOCAL variable in the same method - use ek9_extract_variable.", "The value should become a CLASS FIELD (per-instance state, not per-call) - use ek9_introduce_field.", "The value should be a module-level CONSTANT (immutable, shared) - use ek9_extract_constant.", "You need broader signature changes - explicit position, several parameters at once, return type, or purity - use ek9_change_signature; this tool only APPENDS one parameter." ],
        "examples" : [ "{file:'greeter.ek9', expression:'\"Hello \"', name:'greeting'} -> status:applied; greet gains greeting as String, its body reads 'greeting + name', both call sites pass \"Hello \".", "{file:'net/http_client.ek9', line:45, startColumn:18, endColumn:20, name:'maxRetries', dryRun:true} (an IDE selection) -> status:ready; signature gains maxRetries:Integer, body uses it, every call site passes the old literal 10.", "Span covers a WHOLE `name <- value` local decl -> auto-switches to promote-local: deletes the local, appends the parameter keeping the local's name, threads only the initialiser into call sites (kept-local-name advisory when your name differs)." ],
        "details" : "Parameter type is inferred from the expression unless parameterType is supplied; V1 always APPENDS (no position arg), and when the post-change parameter count reaches 4+ all call sites are rewritten to named-argument form per E11062. Promoting in a method that overrides a trait/supertype contract resolves to the root contract and cascades the parameter across the whole override family (resolved-to-contract advisory). would-fail (gate-caught): the expression references a method-local unresolvable at call sites (E50001) or hides impurity in a pure method (E08130); conflicts: value-expression-not-a-single-expression (bad/partial span), expression-not-in-body, type-not-inferable (supply parameterType), no-promotable-declaration; a name colliding with an existing parameter is an error.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "endColumn" : {
              "description" : "1-based EXCLUSIVE column of the END of the span (an IDE selection). Omit it - pass the text instead.",
              "type" : "integer"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "expression" : {
              "description" : "The expression to turn into the parameter, exactly as written (e.g. '\"Hello \"' - quotes included for a string). Its first occurrence in file, or on 'line' when given.",
              "type" : "string"
            },
            "file" : {
              "description" : "Workspace-relative path to the .ek9 source file holding the expression (and its enclosing method/function).",
              "type" : "string"
            },
            "line" : {
              "description" : "Optional 1-based line holding the expression - when it occurs more than once.",
              "type" : "integer"
            },
            "name" : {
              "description" : "Identifier for the new parameter (camelCase by convention); kept verbatim. Rename later with ek9_rename_symbol.",
              "type" : "string"
            },
            "parameterType" : {
              "description" : "Optional explicit parameter type. Inferred from the expression when omitted; supply when inference yields type-not-inferable or you want a wider type than the expression's.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "startColumn" : {
              "description" : "1-based column of the START of the span on 'line' (an IDE selection). Omit it - pass the text instead.",
              "type" : "integer"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file", "name" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_safe_delete",
        "description" : "Deletes a top-level construct or a class/record/component member - or several as one change - after a workspace-wide reference check; answers status and edits[] (one DELETE per line), or conflicts[] with one 'construct-still-referenced' entry per citing site.",
        "idePrecedent" : "Refactor → Safe Delete (IntelliJ)",
        "whenToUse" : "'Delete OrderProcessor if nothing uses it' is one call with symbol:'OrderProcessor' - no file or line needed (a member is 'Type.member'). Use it to DELETE a top-level construct or a class/record/component member with a workspace-wide reference check blocking it while anything still uses the target.",
        "whenNotToUse" : [ "You only want to KNOW the reference count, not delete - use ek9_query_references.", "You want to DISCOVER deletion candidates across the workspace — use ek9_find_unused_code.", "The construct should be RENAMED, not deleted — use ek9_rename_symbol.", "A method should keep existing but shed parameters — use ek9_change_signature." ],
        "examples" : [ "Preview: {symbol:'OrderProcessor', dryRun:true} → status:ready with the delete changeset when the target has 0 references.", "Blocked: {symbol:'OrderProcessor.audit'} on a still-used member → status:conflicts, one entry per citing site — clear the references first.", "Dead chain: {symbols:['reportTitles','Product.getTitle']} → one change, all or none, in any order - a reference inside another target goes with it (find_unused_code's nextStep is this call)." ],
        "details" : "Live references yield status:'conflicts', NOT an error — one conflicts[] entry per citing site (kind 'construct-still-referenced'; only the declaration site is excluded from the count); references are intentional, so surface them rather than silently retrying. Single-file V1: anchoring on a usage of a symbol declared elsewhere (or an external/built-in symbol) errors and names the declaring file; non-deletable kinds (enum values, individual references-block entries, statements) also error. A top-level deletion that empties its file attaches a 'file-emptied' advisory pointing at ek9_delete_file; override/sealed coupling is not pre-checked in V1 and surfaces as a post-apply compile failure.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "column" : {
              "description" : "0-based char offset of the target identifier on 'line' (an IDE cursor). OMIT it and pass symbol:'<name>' instead, or omit both and the first identifier on the line is taken (right for a declaration line). Reuse a column from ek9_inspect_symbol_at_cursor verbatim.",
              "type" : "integer"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "symbols" : {
              "description" : "Several to delete as ONE change - all or none (e.g. a dead method and its dead callers: ['reportTitles','Product.getTitle'], in any order). Instead of symbol.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_extract_method",
        "description" : "Inserts the new method (parameters and return inferred from variable data-flow) and replaces the block with a call to it; answers status, edits[] and summary, an ambiguous or boundary-crossing block as conflicts[].",
        "idePrecedent" : "Refactor → Extract → Method (IntelliJ) / LSP Extract Method",
        "whenToUse" : "'Pull this block of a method, function or program into computeSubtotal' is one call with file, fromText and toText (the block's first and last line as written - no line counting) and name; parameters and the return are inferred, and copies of the block in the same body become calls too.",
        "whenNotToUse" : [ "Extracting an EXPRESSION into a local - use ek9_extract_variable; a literal into a constants block - use ek9_extract_constant.", "Extracting MEMBERS into a new trait rather than a code block - use ek9_extract_trait.", "Writing a fresh method rather than lifting existing code - use ek9_add_method.", "Turning a loop body into a stream pipeline - use ek9_advise_loop." ],
        "examples" : [ "{file:'invoice.ek9', fromText:'total <- 0.0', toText:'total += line', name:'sumLines'} → status:applied; the block is now 'total <- sumLines()'. A name equal to a local of the body is kept: the local is renamed first (current<Name> - a local may not share a method's name), dry run and apply alike, and corrected[] says so.", "{file:'orders/customer_order.ek9', startLine:70, endLine:75, name:'computeSubtotal', dryRun:true} → status:ready, 2 edits (INSERT the new method, REPLACE the block with a call).", "Block with ≥2 values flowing out, no multiReturn → status:conflicts kind 'return-type-ambiguous' listing outflow vars; re-issue with multiReturn:'formal-record'." ],
        "details" : "Parameters = outer-scope vars read/written in the block; return = a var produced in the block and read after (or the enclosing return var written in the block); 'as pure' whenever the new member compiles pure - the compiler's purity rules decide (printing through Stdout is pure in EK9; mutation and non-pure calls are not) - and always from a pure source member. Conflict kinds (all mechanical): 'return-type-ambiguous' (≥2 outflows, supply multiReturn=formal-record|formal-class|dynamic-class, AI-chosen from downstream intent), 'block-spans-control-flow' (multi-exit selection - narrow to one scope), 'variable-flows-in-and-out' (in-out accumulator - EK9 forbids param reassignment; narrow so the var is only-consumed or only-produced), 'extracted-block-references-outer-state'. V1 scope: member methods + top-level functions; containerName defaults to <Name>Result. A selection ending in the enclosing member's return line returns that member's whole result - the summary says so with the range without it; returnType:'Integer' takes that range when it returns the type asked for (corrected[]), else refuses (return-type-not-produced).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "containerName" : {
              "description" : "Optional name for the synthesized container type (PascalCase). Defaults to <Name>Result.",
              "type" : "string"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "endLine" : {
              "description" : "Or the last line of the block (inclusive, 1-based). Same body as startLine.",
              "type" : "integer"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The .ek9 source file (workspace-relative; an absolute path inside the workspace is accepted).",
              "type" : "string"
            },
            "fromText" : {
              "description" : "The block's FIRST line exactly as written (indentation ignored) - instead of counting startLine. Must name one line; add more of it when it occurs twice.",
              "type" : "string"
            },
            "multiReturn" : {
              "description" : "When ≥2 values flow out of the block, the container shape that packages them into ONE return: 'formal-record' (top-level record, public fields, call site reads bundle.field), 'formal-class' (top-level class with accessor methods, bundle.field()), or 'dynamic-class' (inline captured class, no separate construct). Omit for a single value out; ≥2 out without it → return-type-ambiguous conflict listing these options. AI-directed: pick the shape from downstream intent (incidental bundle vs data carrier vs domain type).",
              "enum" : [ "formal-record", "formal-class", "dynamic-class" ],
              "type" : "string"
            },
            "name" : {
              "description" : "Name for the new method (camelCase).",
              "type" : "string"
            },
            "replaceDuplicates" : {
              "description" : "Default true: every other copy of the selected statements in the same body that is PROVEN the same computation (same shape, names mapped one-to-one, same types, only the same value flowing out) is replaced with the call too; near misses are listed in duplicates[] with the reason, never replaced.",
              "type" : "boolean"
            },
            "returnType" : {
              "description" : "Optional: the type the new member returns ('Integer'). A selection ending in the enclosing member's 'rtn: ...' line returns that member's whole result; when that is not the type asked for, the one other reading - those trailing return lines stay where they are - is taken if it returns the type asked for (said in corrected[]), else refused naming what each reading returns.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "startLine" : {
              "description" : "Or the first line of the block (inclusive, 1-based) - an IDE selection. Inside a method, function or program body.",
              "type" : "integer"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "toText" : {
              "description" : "The block's LAST line as written - the first line matching it at or after fromText. Omit it for a one-line block.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file", "name" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_extract_variable",
        "description" : "Inserts `name <- expression` before the first occurrence and replaces every matching occurrence in the enclosing block with the name; answers status, edits[] and summary, a side-effecting multi-occurrence expression as conflicts[].",
        "idePrecedent" : "Refactor → Extract → Variable (IntelliJ) / LSP Extract Variable",
        "whenToUse" : "'Name the repeated items.sum() * taxRate as subtotal' is one call with file, expression:'items.sum() * taxRate' and name:'subtotal' - no line or column.",
        "whenNotToUse" : [ "A LITERAL that belongs in the module `defines constant` block - use ek9_extract_constant.", "A multi-statement BLOCK of code, not a single expression - use ek9_extract_method.", "The value should outlive the local - a class FIELD (use ek9_introduce_field) or a PARAMETER propagated to call sites (use ek9_introduce_parameter).", "Collapsing an existing local back into its uses (the inverse) - use ek9_inline_variable." ],
        "examples" : [ "{file:'order.ek9', expression:'items.sum() * taxRate', name:'subtotal', dryRun:true} → status:ready; INSERT `subtotal <- items.sum() * taxRate` before the first occurrence + one REPLACE per occurrence.", "{..., replaceAll:false, explicitType:'Money'} → extracts only the located occurrence as `subtotal as Money: items.sum() * taxRate`." ],
        "details" : "Anchored by the outermost expression parse-node that EXACTLY spans the located text (its first occurrence outside a comment, on 'line' when given, or the IDE's 1-based startColumn..endColumn); a misaligned span returns status:'conflicts' (kind 'expression-spans-statement-boundaries'), a line outside any body is an error. Replacement is whitespace-insensitive and confined to the anchor's nearest enclosing instruction block (nested sub-blocks included). A call-bearing expression with multiple occurrences under replaceAll returns status:'conflicts' (kind 'expression-has-side-effects-and-multiple-occurrences' - the call would run once instead of N times); set replaceAll:false to take just the anchor; a name colliding in scope conflicts as 'name-collision'.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "endColumn" : {
              "description" : "1-based EXCLUSIVE column of the END of the span (an IDE selection). Omit it - pass the text instead.",
              "type" : "integer"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "explicitType" : {
              "description" : "Optional. Emits `name as <Type>: expression` instead of the inferred `name <- expression` form.",
              "type" : "string"
            },
            "expression" : {
              "description" : "The expression exactly as written in the source ('base * base'); its first occurrence is the anchor.",
              "type" : "string"
            },
            "file" : {
              "description" : "The .ek9 source file (workspace-relative; an absolute path inside the workspace is accepted).",
              "type" : "string"
            },
            "line" : {
              "description" : "Optional 1-based line of the occurrence to anchor on. Omit it - the expression text is enough.",
              "type" : "integer"
            },
            "name" : {
              "description" : "Identifier for the new local. camelCase by EK9 convention; placeholder names (temp/data/value/…) are rejected.",
              "type" : "string"
            },
            "replaceAll" : {
              "default" : true,
              "description" : "When true (default) replaces EVERY matching occurrence in the enclosing block; when false replaces only the located occurrence.",
              "type" : "boolean"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "startColumn" : {
              "description" : "1-based column of the START of the span on 'line' (an IDE selection). Omit it - pass the text instead.",
              "type" : "integer"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file", "expression", "name" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_extract_constant",
        "description" : "Appends `name <- literal` to the module's `defines constant` block (created if absent) and replaces every occurrence of the literal in the file with the name; answers status, edits[] and summary with the occurrence count, a clashing name as conflicts[] (constant-name-collision).",
        "idePrecedent" : "Refactor → Extract → Constant (IntelliJ) / LSP Extract Constant",
        "whenToUse" : "'Turn the magic \"Hello\" into a named constant' (the fix for E11065) is one call with file, literal:'\"Hello\"' and name:'greeting' - no line or column.",
        "whenNotToUse" : [ "The selection is NOT a single literal - a variable, call, or compound expression - use ek9_extract_variable.", "The selection is an interpolated backtick string (`x is ${y}`) - a runtime expression, not a compile-time literal - extract the embedded expression with ek9_extract_variable.", "You want to hoist an already-named local/expression to a constant rather than a raw literal - use ek9_promote_to_constant.", "You just want existing scattered constants consolidated (E11066/E11067), not a new one extracted - use ek9_organize_constants." ],
        "examples" : [ "{file:'order.ek9', literal:'0.0825', name:'usdTaxRate', dryRun:true} → status:ready; one INSERT for the `defines constant` entry + one REPLACE per occurrence in the file.", "name clashes with an existing module constant → status:conflicts (constant-name-collision) - pick a different name." ],
        "details" : "Only a literal may be extracted (EK9 grammar `constantInitialiser : literal`): the selection must be one of the 18 Ek9LiteralTypes (Integer, Float, String, Money, Date, Colour, Duration, RegEx, …) and its exact source syntax is preserved verbatim (12.50#USD, #FF0000, PT2H). The literal is found at its first occurrence outside a comment (on 'line' when given); an IDE may pass the 1-based startColumn/endColumn pair instead. Placement appends to an existing `defines constant` block even when it lives in ANOTHER file (avoiding E11066/E11067 scattering; with 2+ blocks it picks the richest and attaches a 'constants-scattered' advisory), creating a block only when the module has none - so a changeset can span two files, though occurrence replacement covers the literal's OWN file only (V1). Conflict kinds: selection-is-not-a-literal, interpolated-string-not-constant, selection-is-already-a-constant, constant-name-collision; banned/placeholder identifiers are hard errors.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "endColumn" : {
              "description" : "1-based EXCLUSIVE column of the END of the span (an IDE selection). Omit it - pass the text instead.",
              "type" : "integer"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The .ek9 source file holding the literal (workspace-relative; an absolute path inside the workspace is accepted).",
              "type" : "string"
            },
            "line" : {
              "description" : "Optional 1-based line of the occurrence to anchor on. Omit it - the literal text is enough.",
              "type" : "integer"
            },
            "literal" : {
              "description" : "The literal exactly as written in the source, quotes included for a string ('\"Hello\"', '42', '12.50#USD'); its first occurrence is extracted and every occurrence in the file replaced.",
              "type" : "string"
            },
            "name" : {
              "description" : "Identifier for the new constant. Must not clash with an existing constant; placeholder names (temp/data/value/…) are rejected.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "startColumn" : {
              "description" : "1-based column of the START of the span on 'line' (an IDE selection). Omit it - pass the text instead.",
              "type" : "integer"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file", "literal", "name" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_promote_to_constant",
        "description" : "Moves a `name <- literal` declaration into the module's `defines constant` block (same name, original deleted); answers status, edits[] and conflicts[] (no-promotable-declaration, initialiser-not-a-literal, constant-name-collision).",
        "idePrecedent" : "Refactor → Introduce Constant (from field) — declaration-level",
        "whenToUse" : "'Make temperature a module constant' is one call with file + symbol:'temperature' - no line needed. Use it when a whole `name <- literal` declaration (a property or a local) should become a shared module constant, keeping its name.",
        "whenNotToUse" : [ "Lifting a bare LITERAL and giving it a NEW name - use ek9_extract_constant (the literal-level sibling).", "Wanting a different constant name (e.g. UPPER_SNAKE) — promote first, then rename with ek9_rename_symbol (there is no convention/rename parameter here).", "The `<-` value is a compound expression, a call, or an interpolated backtick string — EK9 constants are literal-valued so it can never be promoted; lift a literal sub-expression with ek9_extract_constant instead." ],
        "examples" : [ "Promote a field: {file:'sim.ek9', symbol:'temperature'} on `temperature <- 20` → status:applied; the declaration is DELETEd and `temperature <- 20` appended to the module's `defines constant` block (possibly in another file).", "Conflict: {file:'sim.ek9', symbol:'banner'} where the initialiser is a call or interpolated backtick string → status:conflicts (initialiser-not-a-literal)." ],
        "details" : "conflicts kinds: 'no-promotable-declaration' (line is not a simple name <- value), 'initialiser-not-a-literal' (compound/call/interpolated value), 'constant-name-collision' (a module constant already has that name — rename the declaration first). Placement is shared with ek9_extract_constant via ConstantBlockPlacer: append to the module's existing `defines constant` block (even cross-file, to avoid E11066/E11067) or create one; a 'constants-scattered' advisory attaches when constants already span 2+ files. Only the `identifier <- literal` form is promoted (name AND type preserved; the `as Type:` form is deferred — constant grammar has no type annotation), and typical would-fail causes are a now-immutable value reassigned or an external member access that no longer resolves.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "Workspace-relative path to the .ek9 source file holding the `name <- literal` declaration.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The identifier you mean (e.g. 'total'); the tool finds its declaration line in file. Pass line only to anchor one specific occurrence.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_organize_constants",
        "description" : "Moves every `defines constant` declaration of one module into one block in one file (created when targetFile is new); answers status, edits[] (one INSERT + one DELETE per moved declaration) and conflicts[] (comments-in-block, module-ambiguous, target-file-ambiguous, pre-existing-duplicate-constant).",
        "idePrecedent" : "NEW IDE surface — Refactor menu → \"Organize Constants…\". Nearest analogues are IntelliJ \"Move Members…\" (but constant-block-aware) and \"Optimize Imports\" (a whole-module tidy with no per-site decisions).",
        "whenToUse" : "'Gather this module's constants into one file' is one call with no arguments (module:'x' when several modules declare constants - the tool refuses to guess; targetFile to pick or create the file). Use it when a module's constants have fragmented across files (E11066: 3+ files; E11067: 2+ files with 10+ constants) or drifted into the wrong file.",
        "whenNotToUse" : [ "Creating a NEW constant from a repeated literal in source (E11065) — use ek9_extract_constant. That authors a constant; this only relocates existing ones.", "Promoting a local `name <- literal` to a module constant — use ek9_promote_to_constant.", "Moving a class/record/trait/component/function between files — use ek9_move_construct (it handles those five genera only, not constants).", "Deleting a donor left holding only its module header — use ek9_prune_empty_sources; this tool never deletes files." ],
        "examples" : [ "{dryRun:true} → status:ready, edits[] INSERT into richest block + one DELETE per moved declaration (idempotent no-op when already consolidated).", "{dryRun:true} returns conflicts[{kind:'comments-in-block'}] → re-issue {mode:'hoist-comments'} then {} to consolidate." ],
        "details" : "Semantics-preserving by construction: constants live in ModuleScope and codegen merges all module constants into one `_Constants` class, so no call site, `references` entry or generated byte changes. ONE module per call; a constant never crosses a module boundary; donor files are retained with their block excised and declarations keep donor source order. Comments in a block's declaration area are conflict 'comments-in-block' — mode:'hoist-comments' lifts them to the block header so consolidate then proceeds (a trailing same-line comment rides its line and never blocks); other conflict kinds are 'pre-existing-duplicate-constant', 'module-ambiguous' (several modules declare constants - supply module), 'target-file-ambiguous' (supply targetFile) and 'directive-bearing-declaration', while no-constants and already-consolidated are graceful no-ops, not errors.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "mode" : {
              "default" : "consolidate",
              "description" : "consolidate (default) gathers every constant into one block; hoist-comments lifts comments out of each block's declaration area up to its header — the one-time mechanical repair that unblocks consolidate.",
              "enum" : [ "consolidate", "hoist-comments" ],
              "type" : "string"
            },
            "module" : {
              "description" : "The exact module to organize. Default: the one module declaring constants; when several do, the call is conflicts[{kind:'module-ambiguous', modules:[...]}] until you name one.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "targetFile" : {
              "description" : "Workspace-relative path of the file that will hold the consolidated block. Three cases, all supported: (1) a source file of the resolved module that already has a `defines constant` block; (2) one with no such block yet — a block is created at the canonical position; (3) a path that DOES NOT EXIST — the file is created as a source file of this module (shebang + `defines module` + //EOF) with the consolidated block in it. Case (3) is the dedicated-constants-file outcome E11066's guidance recommends, so name e.g. 'myModuleConstants.ek9' and this one call does it; no separate ek9_write_file step is needed. Nothing is written on dryRun. Default: the file holding the block with the most declarations. Rejected for hoist-comments.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_create_requirement",
        "description" : "Writes the requirement nodes as flat markdown files under .ek9-ai/requirements/ (one atomic batch, each keyed by its frozen slug) and answers the editor envelope: status (applied | ready | conflicts), written, edits[] and summary naming the ids created.",
        "idePrecedent" : "Requirement navigator → New Requirement… (planned)",
        "whenToUse" : "'Record a new requirement' is one call: {nodes:[{slug, title, body}]} for a root, add parentId:'REQ:x' for a child; several nodes naming each other make a whole subtree.",
        "whenNotToUse" : [ "Editing an existing node's body/title/position, or retiring or deleting it - use ek9_update_requirement (it cannot rename a slug: create a replacement here, then retire the old node with supersededBy).", "Anchoring an EXISTING @Test program to a requirement - use ek9_anchor_test (ek9_add_program's requirementId covers only a freshly-scaffolded test)." ],
        "examples" : [ "{nodes:[{prefix:'REQ', slug:'fraud-screening', title:'Screen inbound payments', body:'# Intent...'}]} → one insert of REQ-fraud-screening.md.", "{nodes:[{slug:'payments', title:'...', body:'...'}, {slug:'settle', parentId:'REQ:payments', title:'...', body:'...'}]} → a two-node subtree in one atomic call." ],
        "details" : "Slug is FROZEN at creation ([a-z0-9-]+) and is the key every @Test and parentId joins on; a collision against the derived id-set is REFUSED, never auto-suffixed — pick a distinctive slug and re-call. prefix is the closed set REQ/CTRL/CAP/CON, immutable once set; timestamps, version and changelog are server-stamped, never caller-supplied. Atomic: one bad node (invalid-slug, slug-collision, duplicate-in-batch, parent-not-found, parent-retired, batch-cycle) blocks the whole batch, and only the child file is ever written — parents are never edited.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dryRun" : {
              "default" : false,
              "description" : "When true nothing is written; the response shows the would-be edits with status:'ready'.",
              "type" : "boolean"
            },
            "nodes" : {
              "description" : "The requirements to create, in intended order. A member's parentId may name an existing requirement OR another member of this same batch, which is what lets a whole subtree be created in one call.",
              "items" : {
                "additionalProperties" : false,
                "properties" : {
                  "authorKind" : {
                    "description" : "Who is authoring - recorded in the changelog, the same attribution ek9_update_requirement records on every change. A child may omit authorKind and authorName to inherit its parent's; a root should state them.",
                    "enum" : [ "human", "persona" ],
                    "type" : "string"
                  },
                  "authorName" : {
                    "description" : "The authoring identity's name; paired with authorKind (both or neither).",
                    "type" : "string"
                  },
                  "body" : {
                    "description" : "Authored markdown — the WHY: intent, rationale, regulation, what success means. Not derivable from code.",
                    "type" : "string"
                  },
                  "meta" : {
                    "additionalProperties" : {
                      "type" : "string"
                    },
                    "description" : "Free attributes: priority, assignee, tags.",
                    "type" : "object"
                  },
                  "parentId" : {
                    "description" : "Parent's requirement id, e.g. REQ:payments. Omit for a root. May name another member of this batch.",
                    "type" : "string"
                  },
                  "prefix" : {
                    "default" : "REQ",
                    "description" : "Node kind. Closed registry. Immutable once set.",
                    "enum" : [ "REQ", "CTRL", "CAP", "CON" ],
                    "type" : "string"
                  },
                  "rankKey" : {
                    "description" : "Explicit sibling position (base-62 fractional index). Omit to append after existing siblings.",
                    "type" : "string"
                  },
                  "ruleAnchors" : {
                    "description" : "Named constraint rules for a structural requirement — one developed by a compiler rule rather than a @Test. Checked against a closed registry.",
                    "items" : {
                      "type" : "string"
                    },
                    "type" : "array"
                  },
                  "slug" : {
                    "description" : "Frozen at creation, [a-z0-9-]+. Combined with prefix to form the requirement id. Pick something distinctive: a collision is refused, never auto-suffixed.",
                    "type" : "string"
                  },
                  "title" : {
                    "description" : "Human label. Floats freely and is NOT the id.",
                    "type" : "string"
                  }
                },
                "required" : [ "slug", "title", "body" ],
                "type" : "object"
              },
              "minItems" : 1,
              "type" : "array"
            },
            "terse" : {
              "default" : false,
              "description" : "Retained boolean alias for verbosity:'summary'.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Response size: full (default) carries each edit's full content; summary drops the edit bodies; none collapses edits[] to a count.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "nodes" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_update_requirement",
        "description" : "Rewrites one existing requirement node's markdown file (body, title, meta, position, retirement) or deletes it, and answers the editor envelope: status (applied | ready | conflicts), written, edits[] and summary naming the mode applied.",
        "idePrecedent" : "Requirement navigator → right-click Edit / Retire / Reparent (planned)",
        "whenToUse" : "'Change requirement X' is one call: {mode:'edit-body-text', requirementId:'REQ:x', text, authorKind, authorName}; modes cover title, meta, reparent, retire and delete.",
        "whenNotToUse" : [ "Adding a NEW requirement node - use ek9_create_requirement.", "Anchoring or retargeting which @Test proves a requirement - use ek9_anchor_test (there is no set-status here: 'developed' is derived from anchors).", "Reading a requirement or the graph - use ek9_query_requirement." ],
        "examples" : [ "Gated delete preview: {mode:'delete', requirementId:'REQ:new-account-threshold', dryRun:true} → status:conflicts (still-referenced) naming the children/@Tests still pointing at it - reparent or remove them first.", "Edit intent: {mode:'edit-body-text', requirementId:'REQ:fraud-screening', text:'…', authorKind:'persona', authorName:'…'} → status:applied; this node rewritten plus a descendant rewrite per node whose intent confirmation was cleared." ],
        "details" : "Modes: edit-body-text (authored why; a vertical-drift trigger — clears intent confirmation on confirmed descendants, advisory only, nothing becomes undeveloped), edit-title, edit-meta (empty value removes the key; unnamed attrs left alone), retire / un-retire (retire in place, optional supersededBy successor that must already exist), reparent (own parentId + optional rankKey; refused with kind would-create-cycle if it would make the node its own ancestor), confirm-intent (re-stamp confirmed_at), delete (the only gated mode — kind still-referenced while children/anchors remain). Other conflict kinds: target-not-found, target-retired (un-retire first), missing-value, parent-not-found, parent-retired. rename-slug is refused (a partial rename leaves dangling @Test anchors) - create the replacement and retire this node with supersededBy instead; each edit is a whole-file rewrite (no line range).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "authorKind" : {
              "description" : "Who is making the change - recorded in the changelog, the same attribution ek9_create_requirement records. Required here: an edit is never attributed by inheritance (only a created child inherits its parent's author).",
              "enum" : [ "human", "persona" ],
              "type" : "string"
            },
            "authorName" : {
              "description" : "The changing identity's name; paired with authorKind.",
              "type" : "string"
            },
            "dryRun" : {
              "default" : false,
              "description" : "When true nothing is written; the response shows the would-be edits with status:'ready'.",
              "type" : "boolean"
            },
            "meta" : {
              "additionalProperties" : {
                "type" : "string"
              },
              "description" : "For edit-meta: attributes to set. An empty value removes the key. Attributes not named are left alone.",
              "type" : "object"
            },
            "mode" : {
              "description" : "What to change.",
              "enum" : [ "edit-body-text", "edit-title", "edit-meta", "retire", "un-retire", "reparent", "confirm-intent", "delete" ],
              "type" : "string"
            },
            "parentId" : {
              "description" : "For reparent: the new parent's requirement id. Omit to promote the requirement to a root.",
              "type" : "string"
            },
            "rankKey" : {
              "description" : "For reparent: position among the new siblings.",
              "type" : "string"
            },
            "requirementId" : {
              "description" : "The requirement to change, e.g. REQ:fraud-screening.",
              "type" : "string"
            },
            "supersededBy" : {
              "description" : "For retire: the successor requirement that replaces this one. It must already exist.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Retained boolean alias for verbosity:'summary'.",
              "type" : "boolean"
            },
            "text" : {
              "description" : "The new body (edit-body-text) or the new title (edit-title).",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Response size: full (default) carries each edit's full content; summary drops the edit bodies; none collapses edits[] to a count.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "mode", "requirementId", "authorKind", "authorName" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_inline_variable",
        "description" : "Deletes a single-assignment local's declaration and substitutes its initialiser at every use site (including `${...}` holes); answers status, edits[] and conflicts[] (variable-reassigned, impure-initialiser-multi-use, ...).",
        "idePrecedent" : "Refactor → Inline Variable (IntelliJ) / LSP Inline Variable",
        "whenToUse" : "'Inline the local subtotal' is one call with file + symbol:'subtotal' - no line needed; 'inline both locals' is one call with symbols:['prefix','message']. Use it on a LOCAL whose name adds no clarity over its short initialiser, or one an earlier refactor extracted and no longer needs.",
        "whenNotToUse" : [ "Variable has ZERO references - nothing to inline; use ek9_safe_delete to remove it.", "You want the OPPOSITE (lift an expression INTO a new local) — use ek9_extract_variable.", "Target is a FUNCTION, not a variable — use ek9_inline_function.", "Target is a field, parameter, module constant or `<- rtn` return variable — locals only; no inline tool exists for those." ],
        "examples" : [ "{file:'orders/customer_order.ek9', symbol:'subtotal', dryRun:true} → status:ready; would inline 'subtotal' at 3 references and delete the declaration (4 edits).", "{file:'orders/customer_order.ek9', symbol:'subtotal'} → status:applied. A reassigned local instead returns status:conflicts (variable-reassigned)." ],
        "details" : "A call-free initialiser inlines at any number of uses; a call-bearing one only at a SINGLE use site (V1 refuses multi-use: impure-initialiser-multi-use). Other conflict kinds: variable-reassigned, multi-line-initialiser, variable-is-guard, variable-has-no-references; fields, parameters, return variables and lines not on a local declaration return status:error. Pairs with ek9_extract_method to collapse the destructuring locals a multiReturn container leaves at the call site.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "Workspace-relative path to the .ek9 source file holding the local declaration.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The identifier you mean (e.g. 'total'); the tool finds its declaration line in file. Pass line only to anchor one specific occurrence.",
              "type" : "string"
            },
            "symbols" : {
              "description" : "Several locals to inline as ONE change, in this order (e.g. ['prefix','message']) - all or none. Instead of symbol.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_inline_function",
        "description" : "Replaces every call to a FUNCTION or METHOD with its parameter-substituted body and deletes the declaration; answers status, edits[] (one REPLACE per call site + the DELETE) and conflicts[] (kind 'variable-clash' with {name, suggestedName}).",
        "idePrecedent" : "Refactor → Inline Function (IntelliJ) / LSP Inline Function",
        "whenToUse" : "'Inline the function formatLabel everywhere' is one call with symbol:'formatLabel' - no file or line needed. Use it on a trivial FUNCTION or METHOD ('Type.method') whose name no longer earns its abstraction.",
        "whenNotToUse" : [ "The function has ZERO call sites - just delete it with ek9_safe_delete (inline returns conflict no-call-sites).", "You want the OPPOSITE — factor a block OUT into a new function — use ek9_extract_method.", "The target is a LOCAL VARIABLE, not a function — use ek9_inline_variable.", "Operator/constructor/abstract (error), recursive (direct or mutual), or dispatcher/override/trait-dispatch targets — no inlinable expansion; refactor or keep the abstraction.", "A METHOD called on another object (obj.method()) or whose body reads 'this' or a field — conflicts receiver-qualified-callsite / this-or-field-capture; it needs its object." ],
        "examples" : [ "{symbol:'formatLabel', dryRun:true} → status:ready, edits[] = one REPLACE per call site + a DELETE of the declaration.", "{symbol:'formatLabel', scope:'single-call-site', callSite:{file:'orders.ek9', line:30}} → inline just that one call, declaration stays." ],
        "details" : "scope: all-call-sites (default; inline everywhere then delete decl) | single-call-site (requires callSiteFile/Line, callSiteColumn defaults to 1; decl kept). An expression-bodied function (name() <- rtn as T: EXPR) inlines at any call context; a multi-statement body inlines only where the call is the whole statement (bare, or RHS of <- / :=), its rtn mapping to the assignment target. A donor-local clash returns conflicts kind 'variable-clash' with clashes[] of {name, suggestedName} — rename via ek9_rename_symbol then re-run (the tool never auto-renames); recursion, dispatcher/override, this/field capture, named arguments and discarded return refuse cleanly as other conflict kinds, and anything subtler (e.g. an E08130 purity violation) surfaces as would-fail — never silent broken source.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "callSite" : {
              "description" : "single-call-site only: {file, line} of the ONE call to inline - the function's name on that line is found for you; column (0-based, an IDE cursor) is optional. The flat callSiteFile / callSiteLine / callSiteColumn (1-based) are the IDE form of the same thing.",
              "properties" : {
                "column" : {
                  "type" : "integer"
                },
                "file" : {
                  "type" : "string"
                },
                "line" : {
                  "type" : "integer"
                }
              },
              "required" : [ "file", "line" ],
              "type" : "object"
            },
            "callSiteColumn" : {
              "default" : 1,
              "description" : "IDE form - column (1-based) on the call name; the name is found on the line when omitted.",
              "type" : "integer"
            },
            "callSiteFile" : {
              "description" : "IDE form of callSite.file - workspace-relative file of the target call.",
              "type" : "string"
            },
            "callSiteLine" : {
              "description" : "IDE form of callSite.line - line (1-based) of the target call.",
              "type" : "integer"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "scope" : {
              "default" : "all-call-sites",
              "description" : "all-call-sites (default): inline every call (via the Phase-5 call graph, across files) and remove the declaration. single-call-site: inline just the call at callSite:{file, line} and keep the declaration.",
              "enum" : [ "all-call-sites", "single-call-site" ],
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_rename_symbol",
        "description" : "Renames a symbol and every workspace reference to it atomically across all files (references blocks and `${...}` holes included); answers status, edits[] per site and conflicts[] (name-collision-in-target-scope, convention-mismatch, symbol-not-renameable).",
        "idePrecedent" : "Refactor → Rename (IntelliJ) / LSP Rename Symbol",
        "whenToUse" : "'Rename Shape to Polygon everywhere' is one call with symbol:'Shape', newName:'Polygon' - no file or line needed (a member is 'Shape.area'; a local or parameter needs the file). Use it for a top-level construct, a member, a parameter or a local plus EVERY workspace reference, atomically.",
        "whenNotToUse" : [ "Operators (==, <=>, $, ...) - fixed contracts, cannot be renamed; use ek9_safe_delete + ek9_add_operator.", "Changing a construct's MODULE rather than its name — use ek9_move_construct (mode=cross-module).", "Renaming the .ek9 FILE, not a symbol — use ek9_rename_file (module identity is the 'defines module' line, not the path).", "Renaming a record/component FIELD specifically — ek9_rename_field is the field-scoped anchor; this tool handles fields too, but rename_field is the precise path." ],
        "examples" : [ "Rename a local: {file:'calc.ek9', symbol:'total', newName:'grandTotal'} → status:applied; the declaration and every usage (incl. `${grandTotal}`) rewritten.", "Preview first: same args + dryRun:true → status:ready with the per-site changeset and no disk write; a colliding newName returns status:conflicts (name-collision-in-target-scope) — inspect, don't blindly retry. Re-issue without dryRun to commit." ],
        "details" : "Collision, casing-convention and banned-identifier checks run before any rewrite; a colliding newName returns status:'conflicts' (kind 'name-collision-in-target-scope') — inspect, don't silently retry. The dry-run runs a scratch-workspace compile, so a semantically unsafe rename surfaces as status:'would-fail' before any disk write. Casing convention is enforced per symbol kind: PascalCase for types, camelCase for members/locals. alsoRename renames further symbols in the SAME changeset (the IDE's Find and Replace in symbols mode): any conflict refuses them all, and two on one line are rewritten together.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "alsoRename" : {
              "description" : "OPTIONAL further symbols renamed in the same changeset, each {file, line, column, newName}: a place the symbol is written (its declaration or a reference; column 0-based) and its new name. Any conflict refuses them all.",
              "items" : {
                "type" : "object"
              },
              "type" : "array"
            },
            "column" : {
              "description" : "0-based char offset of the target identifier on 'line' (an IDE cursor). OMIT it and pass symbol:'<name>' instead, or omit both and the first identifier on the line is taken (right for a declaration line). Reuse a column from ek9_inspect_symbol_at_cursor verbatim.",
              "type" : "integer"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The .ek9 file containing the symbol's declaration (or a reference to it) - OPTIONAL when symbol is a construct name or 'Type.member'; required for a local or a parameter.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "newName" : {
              "description" : "The new name. Must be a valid EK9 identifier honouring the symbol kind's casing convention (PascalCase for types, camelCase for members/locals) and not in EK9's banned non-descriptive set (temp/flag/data/value/...).",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "symbol", "newName" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_change_signature",
        "description" : "Rewrites the declaration and EVERY call site atomically (add / remove / reorder parameters, change the return type, toggle 'as pure'); answers status, edits[] and summary with the call-site count, a blocking call site as conflicts[] (callsite-incompatible-with-new-signature).",
        "idePrecedent" : "Refactor → Change Signature (IntelliJ)",
        "whenToUse" : "'Add a parameter to Order.total and fix every caller' is one call with symbol:'Order.total' and mutations:[{kind:'add-parameter', parameter:{...}}]. A constructor is 'Order.Order' - its 'Order(...)' calls are updated the same way.",
        "whenNotToUse" : [ "Renaming the method - use ek9_rename_symbol; removing it entirely - use ek9_safe_delete; adding a brand-new method - use ek9_add_method.", "Extracting a code block into a NEW method - use ek9_extract_method.", "Promoting an in-body expression to a parameter - use ek9_introduce_parameter.", "Operators (fixed contracts) - use ek9_safe_delete + ek9_add_operator; a brand-new constructor beside the existing ones - use ek9_add_constructor; a purity COMPILE ERROR to reconcile - use ek9_fix_purity_contract." ],
        "examples" : [ "Add a param: {symbol:'Order.total', mutations:[{kind:'add-parameter', parameter:{name:'currency', type:'CurrencyCode', position:0, valueForExistingCalls:'CurrencyCode(\"USD\")'}}], dryRun:true} → status:ready; decl block rewritten + the expression injected at all 12 call sites.", "Remove a param: {..., mutations:[{kind:'remove-parameter', removeParameterName:'flag', dropCallSiteArguments:true}]} → status:applied (without the flag live call sites return status:conflicts, kind callsite-incompatible-with-new-signature)." ],
        "details" : "EK9 has NO default parameter values, so add-parameter REQUIRES valueForExistingCalls (an EK9 expression injected at every existing call site) and injects a 'require <name>?' guard + //AI: block (E08091 bans unused params) unless the target is an override. When the post-change count reaches 4+, all positional call sites auto-rewrite to named-argument form (name: value) per E11062. Targeting a leaf override resolves UP to its root contract and cascades the whole family (resolved-to-contract advisory); semantic cascades (purity E05150/E05160, return-type incompatibility, override cascades) surface as would-fail from the scratch compile in V1, not structured conflict kinds.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "column" : {
              "description" : "IDE cursor offset (0-based) on 'line'. Omit it - pass symbol instead.",
              "type" : "integer"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "mutations" : {
              "description" : "One or more mutations applied atomically, in order. Each: {kind: 'add-parameter' (+ parameter: {name, type, position?, valueForExistingCalls: the expression every existing call passes - REQUIRED whenever anything calls it, EK9 has no default parameter values}), 'remove-parameter' (+ removeParameterName, + optional dropCallSiteArguments: true to drop the argument expression at every call site — without it live call sites block the removal), 'reorder-parameters' (+ newParameterOrder: [names]), 'change-return-type' (+ newReturnType — null removes the return clause; OMIT the mutation to keep the return type), 'change-purity' (+ pure: true | false)}. position omitted = append.",
              "items" : {
                "anyOf" : [ {
                  "properties" : {
                    "kind" : {
                      "const" : "add-parameter"
                    },
                    "parameter" : {
                      "properties" : {
                        "name" : {
                          "type" : "string"
                        },
                        "position" : {
                          "description" : "0-based position among the parameters; omitted = append",
                          "type" : "integer"
                        },
                        "type" : {
                          "type" : "string"
                        },
                        "valueForExistingCalls" : {
                          "description" : "The EK9 expression injected at EVERY existing call site (EK9 has no default parameter values) - required when the callable has call sites, e.g. '0.0' or 'CurrencyCode(\"USD\")'",
                          "type" : "string"
                        }
                      },
                      "required" : [ "name", "type" ],
                      "type" : "object"
                    }
                  },
                  "required" : [ "kind", "parameter" ],
                  "type" : "object"
                }, {
                  "properties" : {
                    "dropCallSiteArguments" : {
                      "description" : "true to drop the argument at every call site; without it live call sites block the removal",
                      "type" : "boolean"
                    },
                    "kind" : {
                      "const" : "remove-parameter"
                    },
                    "removeParameterName" : {
                      "type" : "string"
                    }
                  },
                  "required" : [ "kind", "removeParameterName" ],
                  "type" : "object"
                }, {
                  "properties" : {
                    "kind" : {
                      "const" : "reorder-parameters"
                    },
                    "newParameterOrder" : {
                      "items" : {
                        "type" : "string"
                      },
                      "type" : "array"
                    }
                  },
                  "required" : [ "kind", "newParameterOrder" ],
                  "type" : "object"
                }, {
                  "properties" : {
                    "kind" : {
                      "const" : "change-return-type"
                    },
                    "newReturnType" : {
                      "description" : "the new return type; null removes the return clause",
                      "type" : [ "string", "null" ]
                    }
                  },
                  "required" : [ "kind", "newReturnType" ],
                  "type" : "object"
                }, {
                  "properties" : {
                    "kind" : {
                      "const" : "change-purity"
                    },
                    "pure" : {
                      "description" : "true adds 'as pure', false removes it",
                      "type" : "boolean"
                    }
                  },
                  "required" : [ "kind", "pure" ],
                  "type" : "object"
                } ]
              },
              "type" : "array"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The method or function - 'Order.total' or a bare function name finds its file and declaration workspace-wide; with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "symbol", "mutations" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_fix_purity_contract",
        "description" : "Adds or removes 'as pure' on the callable the diagnostic names, with the override cascade applied; answers status, edits[] and summary, the horizontal cascade as advisories[] and a mode/diagnosis mismatch as conflicts[].",
        "idePrecedent" : "IDE quick-fix: reconcile purity contract (design-by-contract)",
        "whenToUse" : "The compiler reported E05150 / E05160 / E08130 - 'fix the purity contract' is one call with file and the diagnostic's line; mode is inferred except for E08130.",
        "whenNotToUse" : [ "Proactively deciding a function should be pure (no compile error yet) - use ek9_change_signature's change-purity mutation.", "Purity change is part of a broader signature edit (params / return type / purity together) - use ek9_change_signature.", "The real fix is to factor pure logic out from impure logic - use ek9_extract_method, then re-run this on the residual error." ],
        "examples" : [ "{file:'orders.ek9', line:42, mode:'remove-pure-from-caller', dryRun:true} → status:ready; edit drops `as pure` from the caller, a purity-cascade advisory lists the pure callers that will now break.", "{file:'shapes.ek9', line:28} on an E05150 override → mode add-pure-to-override inferred; status:applied. A mode that contradicts the diagnosis returns status:conflicts (mode-mismatch-with-diagnosis) with validModes[]." ],
        "details" : "Modes: make-callee-pure / remove-pure-from-caller (E08130, opposite intents - pick by whether the callee body is genuinely pure-eligible; omitting mode there is a mode-required conflict listing both); add-pure-to-override (E05150) and remove-pure-from-override (E05160) are inferred when omitted. Conflicts gate the fix: no-purity-error-at-line, target-not-method-or-function, callee-not-resolvable; for E08130 modes an override anchor is refused (override-cascade-blocks-fix, pointing at the contract root) and an oversized transitive blast radius as cascade-too-large. The vertical override cascade is applied automatically; sub-threshold horizontal cascade sites come back as a purity-cascade advisory (chain this tool per site), and cascade-too-large is a design call - extract a pure core or pick the opposite mode.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "column" : {
              "description" : "IDE cursor offset (0-based) on 'line'. Omit it - pass symbol instead.",
              "type" : "integer"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The .ek9 source file with the purity diagnostic (workspace-relative; an absolute path inside the workspace is accepted). Optional when symbol names the callable.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line the compiler flagged (the diagnostic's own line). Omit it and pass symbol for an override (E05150/E05160), whose declaration IS the flagged line.",
              "type" : "integer"
            },
            "mode" : {
              "description" : "The fix direction. Optional for E05150 (add-pure-to-override) and E05160 (remove-pure-from-override) - inferred from the diagnostic; required for E08130: make-callee-pure or remove-pure-from-caller.",
              "enum" : [ "make-callee-pure", "remove-pure-from-caller", "add-pure-to-override", "remove-pure-from-override" ],
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The flagged callable - 'Shape.area' or a bare function name finds its file and declaration workspace-wide; with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_change_visibility",
        "description" : "Rewrites the method's declaration line with the new access modifier (public = none); answers status, edits[] and summary, an unchanged method as conflicts[] (already-<visibility>). Method-only: fields, operators and trait methods have fixed visibility.",
        "idePrecedent" : "Refactor → Change Signature (visibility) (IntelliJ) / Change Method Visibility (Eclipse)",
        "whenToUse" : "'Make Order.decorate private' is one call with symbol:'Order.decorate' and visibility:'private' | 'protected' | 'public' - no file, line or column needed.",
        "whenNotToUse" : [ "The visibility change is part of a wider signature change (params / return type) - use ek9_change_signature.", "The method shouldn't exist at all - use ek9_safe_delete rather than restricting access.", "Factoring part of a method into a private helper - use ek9_extract_method (extracted methods are private by default).", "The target is a field, an operator, or a trait method - their visibility is fixed in EK9 (class/component fields private, record fields public, operators and trait methods public); this tool returns status:'error'." ],
        "examples" : [ "Restrict a helper: {symbol:'CustomerOrder.decorate', visibility:'private', dryRun:true} → status:ready with the one-line declaration REPLACE edit.", "Open to subclasses: {symbol:'Shape.area', visibility:'protected'} → status:applied (protected is subclass-scoped only; EK9 has no within-package tier)." ],
        "details" : "visibility:'public' REMOVES the modifier (public is EK9's default; writing it explicitly is E07280). A method already at the requested visibility is conflicts[] kind already-public / already-private / already-protected (nothing written). Structurally-impossible changes - an override (visibility locked to its parent), protected on a component/service method, protected on a non-open class method (E07260), any non-public modifier on a trait method (E07270) - are refused up-front as status:'error' by the shared VisibilityChangePredicate, the same rule core the compiler enforces. Narrowing (to private / protected) that leaves a call site unable to reach the member is NOT an up-front error - the scratch compile surfaces it as status:'would-fail' with the E06180 NOT_ACCESSIBLE diagnostics.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "column" : {
              "description" : "IDE cursor offset (0-based) on 'line'. Omit it - pass symbol instead.",
              "type" : "integer"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The method - 'Order.total' finds its file and declaration workspace-wide; with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            },
            "visibility" : {
              "description" : "The new access modifier.",
              "enum" : [ "public", "protected", "private" ],
              "type" : "string"
            }
          },
          "required" : [ "symbol", "visibility" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_make_open",
        "description" : "Appends 'as open' to the declaration so the construct can be extended; answers status and edits[] (one REPLACE on the declaration line), or conflicts[] when it is already open.",
        "idePrecedent" : "Refactor → Make Class Open",
        "whenToUse" : "'Open Animal for extension' is one call with symbol:'Animal' - no file or line needed. Use it when a closed (EK9's default) class/record/component/function must permit subclassing - a widening change that never breaks existing compile and resolves E05030.",
        "whenNotToUse" : [ "Removing 'as open' to lock a class down - use ek9_make_closed.", "Wanting 'as abstract' (implicitly open AND forces subclasses to supply bodies) — use ek9_make_abstract.", "Wanting a SEALED class ('as open' + 'allow only A, B, C', open to a fixed subclass list) — use ek9_seal_type, which applies both modifications atomically.", "Traits — always open already; rejected as an unsupported construct." ],
        "examples" : [ "Preview: {symbol:'Animal', dryRun:true} → status:ready, edit REPLACE on the declaration line 'Animal' → 'Animal as open'.", "Already open: {symbol:'Animal'} → status:conflicts, conflicts[].kind:'already-open' (an abstract target answers 'already-abstract' - abstract is implicitly open)." ],
        "details" : "Accepts class, record, component and function (both concrete and abstract functions). When the target is sealed ('allow only') but missing 'as open' (the E05270 contradiction), adding the modifier fixes it. An already-open or already-abstract (implicitly open) target is status:conflicts with kind 'already-open' / 'already-abstract'; an unsupported genus is status:error.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_make_closed",
        "description" : "Strips 'as open' from the declaration so the construct can no longer be extended; answers status, edits[] and conflicts[] naming every subtype (kind 'subtype') that would break with E05030.",
        "idePrecedent" : "Refactor → Make Class Closed / Final",
        "whenToUse" : "'Close Animal to extension' is one call with symbol:'Animal' - no file or line needed. Use it on an 'as open' class/record/component/function with NO subtypes to return it to EK9's closed-by-default.",
        "whenNotToUse" : [ "Opting INTO subclassability (the opposite) - use ek9_make_open.", "Removing 'as abstract' from an abstract class — use ek9_make_concrete (abstract is implicitly open and cannot be closed directly).", "The construct is sealed ('allow only …') — use ek9_unseal_type first (E05270 requires a sealed class stay open).", "Subtypes exist and block closure — remove/refactor them first with ek9_safe_delete (or convert to composition via 'with trait of')." ],
        "examples" : [ "Preview: {symbol:'Animal', dryRun:true} → status:ready, edit strips 'as open' from 'Animal as open' → 'Animal'.", "Blocked: {symbol:'Animal'} when Cat extends Animal → status:conflicts, conflicts[].kind:'subtype' listing each subclass name/fqn/file/line." ],
        "details" : "Supports class/record/component/function (all carry the optional 'as open'); traits are always open and rejected as an unsupported construct. status:'conflicts' with kind:'subtype' (an escalation — human must decide) when any workspace subtype would hit E05030 (NOT_OPEN_TO_EXTENSION), and kind:'already-closed' when there is nothing to do; status:'error' when the target is abstract (hint: ek9_make_concrete) or sealed (hint: ek9_unseal_type). Closed is the ABSENCE of 'as open' — there is no 'as closed' keyword.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_make_abstract",
        "description" : "Rewrites the declaration to 'as abstract' (replacing any 'as open') so the construct can no longer be instantiated; answers status, edits[] and wouldBeDiagnostics with every direct construction site (E10030).",
        "idePrecedent" : "Refactor → Make Class Abstract",
        "whenToUse" : "'Make Shape abstract' is one call with symbol:'Shape' - no file or line needed. Use it when a class/record/component/function should be a subclassing base that is never instantiated bare; abstract is implicitly open, so no separate 'as open' is needed.",
        "whenNotToUse" : [ "Removing 'as abstract' so the class can be instantiated again - use ek9_make_concrete.", "Wanting subclassability but STILL allowing direct instantiation — use ek9_make_open ('as open' alone, not abstract).", "Wanting a fixed allow-only list of subtypes — use ek9_seal_type (it keeps an existing 'as abstract' but never adds one; run this tool first for abstract+sealed).", "Filling in abstract methods rather than locking them as abstract — use ek9_implement_methods (opposite intent)." ],
        "examples" : [ "{symbol:'Animal'} → single REPLACE on the declaration line: 'Animal as open' becomes 'Animal as abstract'.", "Same call with a direct construction site Animal() elsewhere → status:would-fail, E10030 at each construction site." ],
        "details" : "Rewrites only the declaration line: strips any existing 'as open' (the two modifiers are mutually-exclusive grammar alternatives) and appends 'as abstract'; function style handled too ('myFunc() as pure' → 'myFunc() as pure abstract', reclassifying the genus to an abstract function). Breakage surfaces as would-fail — direct construction sites become E10030 CONSTRUCTOR_USED_ON_ABSTRACT_TYPE, and concrete-subclass cascades appear as new diagnostics. An already-abstract target is status:conflicts with kind 'already-abstract'; no supported declaration at the anchor, or a trait target (always abstract), is status:error.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_make_concrete",
        "description" : "Removes 'as abstract' from the declaration (keeping 'as open' unless keepOpen:false) so the construct can be instantiated; answers status, edits[] and conflicts[] listing every remaining abstract member (kind 'abstract-method').",
        "idePrecedent" : "Refactor → Make Class Concrete",
        "whenToUse" : "'Make Shape concrete' is one call with symbol:'Shape' - no file or line needed. Use it on an 'as abstract' class/record/component/function whose abstract members are all implemented, to allow direct instantiation.",
        "whenNotToUse" : [ "Opting INTO forced-subclassing (the opposite) - use ek9_make_abstract.", "Removing subclassability (closing) — that's the 'as open' modifier; use ek9_make_closed on a concrete class, or keepOpen:false here to go concrete-and-closed in one step.", "The class still HAS abstract methods to implement first — use ek9_implement_methods for trait-derived abstracts, or ek9_add_method per member, then re-call.", "Unsealing a sealed ('allow only') type — the sealed constraint stays; use ek9_unseal_type (and keep keepOpen:true — sealed needs 'as open' per E05270)." ],
        "examples" : [ "Preview: {symbol:'Animal', dryRun:true} → status:ready; edit replaces 'Animal as abstract' with 'Animal as open' (0 abstract members remain).", "Blocked: {symbol:'Animal'} while an abstract method remains → status:conflicts, conflicts[].kind:'abstract-method' with name/signature/line; implement the body then re-call." ],
        "details" : "keepOpen (default true) emits 'as open' to preserve the extensibility abstract implied per QA0102 — removing abstract alone would silently CLOSE the class; keepOpen:false makes it concrete-and-closed in one step (avoid on sealed types, which need 'as open' per E05270). Traits are always abstract and are rejected as an unsupported construct. Conflicts carry kind 'abstract-method' with name/signature/line (mechanical: implement each body, then re-call) or 'already-concrete'; would-fail flags subclass cascade errors.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "keepOpen" : {
              "default" : true,
              "description" : "Whether the resulting concrete class should be 'as open' (default true) or closed (false).",
              "type" : "boolean"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_seal_type",
        "description" : "Adds 'allow only A, B, C' to a class or trait declaration (plus 'as open' on a class that has no extensibility modifier); answers status, edits[] and conflicts[] naming any entry that does not extend the target or any existing subtype left off the list.",
        "idePrecedent" : "Kotlin \"Make sealed\" (loose analogue)",
        "rules" : [ "allowOnly must name EVERY subtype in the target's hierarchy at ANY depth — a grandchild that extends a permitted subtype must still be listed, because EK9 rejects an unlisted concrete transitive descendant with E05240. Query the full subtype set first (e.g. ek9_query_type_hierarchy); any you omit come back as an 'existing-subtypes-not-in-allowlist' conflict." ],
        "whenToUse" : "'Seal Shape to Circle and Square' is one call with symbol:'Shape', allowOnly:['Circle','Square'] - no file or line needed. Use it to INITIALLY seal a class or trait to a known, fixed subtype list for exhaustive dispatch or API lock-down.",
        "whenNotToUse" : [ "The type is ALREADY sealed and you just want to add entries - use ek9_add_to_sealed_allowlist.", "You want the OPPOSITE, removing an existing 'allow only' clause — use ek9_unseal_type.", "You want extensibility WITHOUT a fixed subtype list (just 'as open') — use ek9_make_open.", "You want forced subclassing WITHOUT a fixed list — use ek9_make_abstract." ],
        "examples" : [ "Preview: {symbol:'Shape', allowOnly:['Circle','Square','Triangle'], dryRun:true} → status:ready; edit rewrites `Shape` → `Shape allow only Circle, Square, Triangle as open`.", "A subtype extends the target but is absent from allowOnly → status:conflicts (existing-subtypes-not-in-allowlist) — add it or ek9_safe_delete it, then re-run." ],
        "details" : "Class/trait targets only — records/components/functions error. Every allowOnly entry must ALREADY extend/implement the target (conflicts: entry-does-not-extend-target), and the language requires concrete entries — an abstract entry fails the dry-run compile per E05250. Sealed CLASSES need 'as open' or 'as abstract' per E05270 (existing modifier kept, else 'as open' auto-emitted; traits need none); an already-sealed target returns conflicts (target-already-sealed).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "allowOnly" : {
              "description" : "The 'allow only' list: every concrete class that may extend/implement the target, at any depth.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "allowOnly" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_unseal_type",
        "description" : "Removes the 'allow only A, B, C' clause from a sealed class or trait, keeping its 'as open' / 'as abstract'; answers status and edits[] (one REPLACE on the declaration line), or conflicts[] (kind 'already-unsealed') when there is no clause.",
        "idePrecedent" : "Remove sealed modifier (no direct IDE precedent)",
        "whenToUse" : "'Unseal Shape' is one call with symbol:'Shape' - no file or line needed. Use it when a sealed class or trait's `allow only ...` subtype set is no longer warranted and the type should be openly extensible again.",
        "whenNotToUse" : [ "ADDING an `allow only` clause (the opposite direction) - use ek9_seal_type.", "The type stays sealed but you want MORE subtypes in the existing allowlist — use ek9_add_to_sealed_allowlist.", "After unsealing, locking the (now-extensible) class down entirely — chain ek9_make_closed (as open → closed) or ek9_make_concrete (as abstract → concrete)." ],
        "examples" : [ "Class: {symbol:'Shape'} rewrites `Shape allow only Circle, Square, Triangle as open` → `Shape as open`.", "Trait: {symbol:'Renderable'} rewrites `Renderable allow only Sprite, Mesh` → `Renderable`." ],
        "details" : "Class or trait targets only (a record/component/function target errors); a target that is not sealed is status:conflicts with kind 'already-unsealed'. The extensibility modifier is always preserved (sealed classes require `as open` OR `as abstract` per E05270; traits carry none). Unsealing is a WIDENING change — existing subtypes were all in the removed allowlist so they keep compiling, and prior E05240 blocks on new extensions are resolved. The design cost is that dispatchers over the target silently lose compile-time exhaustiveness per E05260 (new subtypes then hit the default fallback) — check ek9_query_dispatchers before committing.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_add_to_sealed_allowlist",
        "description" : "Rewrites the sealed type's 'allow only' line with the new subtypes appended; answers status, edits[] and summary, each bad entry as conflicts[] (target-not-sealed, entry-does-not-extend-target, entry-already-in-allowlist).",
        "idePrecedent" : "Kotlin sealed-hierarchy extension (loose analogue)",
        "rules" : [ "Use only on an ALREADY-sealed target (has an 'allow only' clause) — for initial sealing use ek9_seal_type. The entry must already extend (class) / implement (trait) the target; a not-yet-extending entry returns 'entry-does-not-extend-target'. Adding a subtype to a type dispatched over (as dispatcher) may require a new handler (E05260) — surfaced as a would-fail; follow up with ek9_generate_dispatcher / ek9_add_method." ],
        "whenToUse" : "'Let Pentagon and Hexagon into sealed Shape' is one call with symbol:'Shape' and allowOnly:['Pentagon','Hexagon'] - every unlisted subtype at once, no line.",
        "whenNotToUse" : [ "Target is NOT yet sealed (no 'allow only' clause) - use ek9_seal_type for the initial seal; it writes the whole list.", "Removing the seal entirely rather than extending the list - use ek9_unseal_type.", "The entry does not yet extend/implement the target - add the extends / with trait of clause to the entry's class first (else 'entry-does-not-extend-target'), then re-call.", "The entry is abstract, or a dispatcher over the target lacks a handler for it - ek9_make_concrete / ek9_generate_dispatcher / ek9_add_method first (else post-apply E05250 / E05260 would-fail)." ],
        "examples" : [ "{symbol:'Shape', allowOnly:['Pentagon'], dryRun:true} → status:ready; one replace on the 'allow only' line: 'Shape allow only Circle, Square, Triangle' → '…, Pentagon'.", "Two unlisted subtypes: {symbol:'Shape', allowOnly:['Pentagon','Hexagon']} → clears both E05240s in one edit; adding them one at a time never reaches a clean workspace." ],
        "details" : "conflicts kinds (all bad entries reported at once, not just the first): 'target-not-sealed' (mechanical - switch to ek9_seal_type), 'entry-does-not-extend-target' (escalation - add extends / with trait of first), 'entry-already-in-allowlist' (mechanical no-op - drop it from allowOnly). Abstract entry (E05250) and dispatcher-non-exhaustive (E05260) are NOT pre-checked - they surface as would-fail. Entries de-duplicated, appended in the order given; the typical baseline is already E05240-errored, and since apply verifies only NEW diagnostics, clearing it yields 'applied'.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "allowOnly" : {
              "description" : "Concrete subtype names to add to the target's 'allow only' list, appended in the order given. Pass every unlisted subtype you intend to permit.",
              "items" : {
                "type" : "string"
              },
              "minItems" : 1,
              "type" : "array"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The sealed class or trait - a bare name ('Shape') finds its file and declaration workspace-wide; 'mod::Shape' when the name is declared in two modules.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "symbol", "allowOnly" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_add_method",
        "description" : "Inserts the method (signature, a require-guarded stub body and the return) at the construct's canonical member position; answers status, edits[] and summary, a same-signature method as conflicts[] (signature-collision). Records reject named methods.",
        "idePrecedent" : "Generate Method (IntelliJ) / Override/Implement Method",
        "whenToUse" : "'Add a method to CustomerOrder' is one call with symbol:'CustomerOrder', name, parameters:['code as Integer'], returnType and statements:['rtn: ...'] (the body) - no file, no line, nothing left to fill.",
        "whenNotToUse" : [ "The member is an OPERATOR (==, <=>, ?, $, …) - use ek9_add_operator; a CONSTRUCTOR (the type's own name) - use ek9_add_constructor.", "You want ALL trait methods added at once — use ek9_implement_methods (batch).", "You want a FIELD, not a method — use ek9_add_field.", "The target construct does not exist yet — create it with ek9_new_construct first." ],
        "examples" : [ "{symbol:'CustomerOrder', name:'calculateTotal', returnType:'Money', pure:true, dryRun:true} → status:ready with one INSERT edit.", "{symbol:'Processable', name:'process', parameters:['input as OrderItem'], returnType:'Result', isAbstract:true} → abstract method added to the trait; the parameter is written in EK9 form, {name:'input', type:'OrderItem'} is the equivalent object.", "{symbol:'UniqueNames', name:'add', parameters:['name as String']} → a void method with ONE parameter and a stub body for ek9_insert_statements to fill." ],
        "details" : "Inserts exactly ONE method per call; a same-signature second call yields status:conflicts (kind 'signature-collision' — rename, change the parameter shape, or add override:true if overriding an inherited method). status:error when the target genus rejects methods (record/program/text/enumeration …) or isAbstract targets a non-abstract class/component. No override-inheritance analysis is performed — a bad override surfaces only on the apply-path verify compile as would-fail.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "emptyBody" : {
              "default" : false,
              "description" : "When true, generate ONLY the signature (declaration + parameters + return-with-default) with no placeholder body - no 'require param?' preconditions, no '//AI' lead, for a body you then supply with ek9_insert_statements. NOTE: EK9 flags unused parameters, so a shell WITH parameters will would-fail on apply until a body uses them; only the no-parameter-with-return shape compiles standalone.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "isAbstract" : {
              "description" : "Add 'as abstract'. Only valid on traits and classes already marked 'as abstract'.",
              "type" : "boolean"
            },
            "name" : {
              "description" : "The new method's name (camelCase).",
              "type" : "string"
            },
            "override" : {
              "description" : "Add 'override' prefix when overriding a parent or trait method.",
              "type" : "boolean"
            },
            "parameters" : {
              "description" : "Parameter list, each ONE item as DATA - {name:'code', type:'Integer'} - never a {name} and a {type} as two items, and never with '->' or '<-' in it; the tool writes the arrows and the layout. ('code as Integer' is also accepted.) A malformed item is rejected before anything is rendered. Empty / omitted means a no-arg method.",
              "items" : {
                "anyOf" : [ {
                  "properties" : {
                    "name" : {
                      "description" : "The parameter's name.",
                      "type" : "string"
                    },
                    "type" : {
                      "description" : "Its type, e.g. 'Integer', 'List of String' - the TYPE only, no arrows.",
                      "type" : "string"
                    }
                  },
                  "required" : [ "name", "type" ],
                  "type" : "object"
                }, {
                  "description" : "Also accepted: the EK9 text 'name as Type' - but send the object; the tool writes the '->' block, its indentation and its order.",
                  "type" : "string"
                } ]
              },
              "type" : "array"
            },
            "pure" : {
              "description" : "Add 'as pure' modifier. Default false.",
              "type" : "boolean"
            },
            "returnType" : {
              "description" : "Return TYPE only, e.g. 'String', 'List of Integer' - never '<- rtn as String', never a name: the tool writes the '<- rtn as <Type>?' declaration, and the body assigns 'rtn: <expr>'. Omitted means no return.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "statements" : {
              "description" : "The body, one statement or one block per entry (assign 'rtn: <value>' on every path when there is a return): the method is complete in this call - no stub, no second ek9_insert_statements call. Parsed alone first; a problem names the entry and its fix.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "symbol" : {
              "description" : "The class, trait, component or service the method is added to - a bare name ('Order') finds its file workspace-wide; 'mod::Order' when the name is declared in two modules.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            },
            "visibility" : {
              "description" : "The method's access. Default public.",
              "enum" : [ "public", "private", "protected" ],
              "type" : "string"
            }
          },
          "required" : [ "symbol", "name" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_add_route",
        "description" : "Inserts the web operation (`byId() as GET for :/{id}` or `operator += for :/`) with a compiling stub body into the service; answers status, edits[] and summary, a duplicate route as conflicts[] (route-collision).",
        "idePrecedent" : "Add Endpoint / Generate REST Method",
        "whenToUse" : "'Add GET /{id} to OrdersService' is one call with symbol:'OrdersService', name:'byId', uri:'/{id}' and parameters:[{name:'id', in:'PATH'}] - no file or line.",
        "whenNotToUse" : [ "Target is a class, trait, or component (a normal method, not a web operation) - use ek9_add_method.", "The service does not exist yet - create it with ek9_new_construct (service mode) first, then add routes here.", "You want a value-type operator (==, <=>, ?, …) on a class/record, not an operator-form HTTP route - use ek9_add_operator." ],
        "examples" : [ "Named GET route with a PATH var: {symbol:'OrdersService', name:'byId', verb:'GET', uri:'/{id}', parameters:[{name:'id',type:'String',in:'PATH'}]} → one INSERT rendering `byId() as GET for :/{id}`.", "Operator POST mutator: {symbol:'OrdersService', operator:'+=', uri:'/', parameters:[{name:'order',type:'Order',in:'CONTENT'}]} → renders `operator += for :/`." ],
        "details" : "Renders via the SAME operation spec + renderer as ek9_new_construct service mode (ServiceOperationParser + ConstructScaffolder.renderServiceOperation), so a route added here is byte-identical to one created with the service. ONE route per call: an obvious duplicate (same operator or same named handler) is a conflicts kind 'route-collision', but this is not a full overload analyser — pre-check with ek9_query_effective_api. An unbound PATH var, unresolved param type, or unresolved 'constrain by' gate surfaces as would-fail; a non-service target is an error naming its actual genus.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "name" : {
              "description" : "Named-route handler name (camelCase), e.g. 'byId'. Provide this OR 'operator'.",
              "type" : "string"
            },
            "operator" : {
              "description" : "Operator form of the route: += (POST), -= (DELETE), :^: (PUT), :~: (PATCH), ? (HEAD). Provide this OR 'name'.",
              "type" : "string"
            },
            "parameters" : {
              "description" : "Operation parameters: [{name, type, in:PATH|QUERY|HEADER|CONTENT|REQUEST|CONTEXT, source?, sanitized?}] - each ONE object (this list's items carry 'in', so the bare 'name as Type' form does not apply); 'type' defaults to String when omitted. PATH/QUERY/HEADER are auto-captured into the response and referenced in content() so they compile.",
              "items" : {
                "properties" : {
                  "in" : {
                    "enum" : [ "PATH", "QUERY", "HEADER", "CONTENT", "REQUEST", "CONTEXT" ],
                    "type" : "string"
                  },
                  "name" : {
                    "type" : "string"
                  },
                  "sanitized" : {
                    "type" : "boolean"
                  },
                  "source" : {
                    "type" : "string"
                  },
                  "type" : {
                    "type" : "string"
                  }
                },
                "required" : [ "name" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "response" : {
              "description" : "Response customisations: {status?, contentType?, contentLanguage?, cacheControl? | cache:{maxAge, visibility:public|private, mustRevalidate?, noStore?}}. Only set fields are overridden; HTTPResponse defaults the rest.",
              "type" : "object"
            },
            "security" : {
              "description" : "Per-operation security posture ('open' or 'constrain by <Gate>'). Omit to inherit the service posture.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The service the route is added to - a bare name ('OrdersService') finds its file workspace-wide; 'mod::OrdersService' when the name is declared in two modules.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "uri" : {
              "description" : "Operation path, e.g. '/{id}' or '/'. Path variables must be bound by a PATH param of the same name.",
              "type" : "string"
            },
            "verb" : {
              "description" : "HTTP verb for the named form. Default GET. Ignored for the operator form.",
              "enum" : [ "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS" ],
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "symbol" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_add_application",
        "description" : "Appends the application to the file's 'defines application' block (created if absent), one 'register Component() as Interface' line per registration; answers status, edits[] and summary, with a would-fail carrying the compiler's own diagnostics.",
        "idePrecedent" : "New Application / DI Container",
        "whenToUse" : "'Add a DI application DemoApp wiring V8 as Engine' is one call with file, name and registrations:[{component, as}] - no line needed. The same construct is production wiring (DemoApp) and test substitution (TestApp); there is no mocking framework.",
        "whenNotToUse" : [ "Defining the COMPONENT itself (the concrete or abstract base) - use ek9_new_construct mode:'component'; applications register components, they do not define them.", "Authoring the program or @Test that binds via 'with application of <App>' - use ek9_add_program (isTest:true, application:'TestApp' for tests); author the application here first." ],
        "examples" : [ "{file:'orders.ek9', name:'DemoApp', registrations:[{component:'SimpleHRSystem', as:'HRSystem'},{component:'StdoutInterface', as:'OutputInterface'}], dryRun:true} → status:ready, insert edit creating the 'defines application' block with 2 register lines.", "Test substitution: second call same file, name:'TestApp', registrations wiring test doubles → appends TestApp under the existing 'defines application' keyword alongside DemoApp." ],
        "details" : "Registrations (>=1 required — a bare shell does not compile) take two forms: supply 'as' for 'register Component() as Interface' (bind concrete to trait/component interface), or omit 'as' for a bare 'register Component()' (service-genus / HTTP-routing targets); optional 'aspects' flat list renders 'with aspect of A, B' for AOP, and components may be fully-qualified. Multiple applications share one 'defines application' block — the tool appends under an existing block or creates one. A duplicate application name in the SAME module (different modules are fine) or one interface bound twice in one app (E08230 DI_DUPLICATE_REGISTRATION) surfaces as would-fail from the dry-run gate.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "Workspace-relative path to the .ek9 module file the application is appended to (the 'defines application' block is created if absent).",
              "type" : "string"
            },
            "name" : {
              "description" : "Application name (PascalCase), e.g. 'DemoApp'.",
              "type" : "string"
            },
            "registrations" : {
              "description" : "DI registrations (>=1): [{component, as?, aspects?}]. Two forms: omit 'as' for a bare 'register Component()' (inject the concrete type), or supply 'as' for 'register Component() as Interface' (inject via the interface); 'aspects' add 'with aspect of A[, B]' for AOP.",
              "items" : {
                "properties" : {
                  "as" : {
                    "type" : "string"
                  },
                  "aspects" : {
                    "items" : {
                      "type" : "string"
                    },
                    "type" : "array"
                  },
                  "component" : {
                    "type" : "string"
                  }
                },
                "type" : "object"
              },
              "type" : "array"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file", "name" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_add_program",
        "description" : "Appends the program (or @Test program) to the file's 'defines program' block, created if absent, with a stub body that references every parameter and injection; answers status, edits[] and summary.",
        "idePrecedent" : "New Program / Main / Test",
        "whenToUse" : "'Add a program Main that prints X' is one call with file, name and statements:['stdout <- Stdout()', 'stdout.println(...)'] - the body included, nothing left to fill; isTest:true makes it a @Test program.",
        "whenNotToUse" : [ "Authoring any OTHER top-level construct (class/record/trait/function/component/type/constant/service) - use ek9_new_construct.", "Authoring the DI application a program binds to via 'application' - use ek9_add_application first, then reference it here by name.", "Anchoring a @Test program that ALREADY exists to a requirement - use ek9_anchor_test (requirementId here is for a freshly-scaffolded test)." ],
        "examples" : [ "{file:'greeter.ek9', name:'Greet', parameters:['message as String', 'when as Date']} -> runtime auto-parses each arg into its declared type; parameters are written in EK9 form ({name:'message', type:'String'} is the equivalent object).", "{file:'cli/data.ek9', name:'DataCorrelation', parameters:[{name:'argv',type:'List of String'}]} -> the single-List-of-String escape hatch for variable-arity/flag-style args." ],
        "details" : "parameters[].type must be one of the 18 whitelisted CLI arg types (String/Integer/Float/Boolean/Character/Bits/Date/Time/DateTime/Duration/Millisecond/Money/Colour/Resolution/Dimension/RegEx/GUID/List of String) — an invalid type (E07600), a typed primitive mixed with 'List of String' (E07610), or a program name already taken in the module all surface as would-fail from the dry-run gate. Programs may return only Integer (process exit code — E07590).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "application" : {
              "description" : "Application to bind for DI — renders 'with application of <App>'. The application must resolve (define it with ek9_add_application first).",
              "type" : "string"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "Workspace-relative path to the .ek9 module file the program is appended to (the 'defines program' block is created if absent). For isTest:true this must be a dev source (dev/ subdirectory or QA####_dev_ file).",
              "type" : "string"
            },
            "injections" : {
              "description" : "Components pulled from the application by injection, each ONE item as DATA - {name:'code', type:'Integer'} - never a {name} and a {type} as two items, and never with '->' or '<-' in it; the tool writes the arrows and the layout. ('code as Integer' is also accepted.) A malformed item is rejected before anything is rendered. ('logger as Logger' or 'logger as Logger!' both render 'logger as Logger!'). Each is referenced in the stub (an unused injection is E11018).",
              "items" : {
                "anyOf" : [ {
                  "properties" : {
                    "name" : {
                      "description" : "The parameter's name.",
                      "type" : "string"
                    },
                    "type" : {
                      "description" : "Its type, e.g. 'Integer', 'List of String' - the TYPE only, no arrows.",
                      "type" : "string"
                    }
                  },
                  "required" : [ "name", "type" ],
                  "type" : "object"
                }, {
                  "description" : "Also accepted: the EK9 text 'name as Type' - but send the object; the tool writes the '->' block, its indentation and its order.",
                  "type" : "string"
                } ]
              },
              "type" : "array"
            },
            "isTest" : {
              "default" : false,
              "description" : "When true, prepend the @Test directive and emit an assert-based stub. Only legal in a dev source (E81015).",
              "type" : "boolean"
            },
            "name" : {
              "description" : "Program name (PascalCase), e.g. 'ShowInjection' or 'GreetingTest'.",
              "type" : "string"
            },
            "parameters" : {
              "description" : "Typed CLI argument parameters, each ONE item as DATA - {name:'code', type:'Integer'} - never a {name} and a {type} as two items, and never with '->' or '<-' in it; the tool writes the arrows and the layout. ('code as Integer' is also accepted.) A malformed item is rejected before anything is rendered. Each is referenced in the stub so it compiles (an unused arg is E08091).",
              "items" : {
                "anyOf" : [ {
                  "properties" : {
                    "name" : {
                      "description" : "The parameter's name.",
                      "type" : "string"
                    },
                    "type" : {
                      "description" : "Its type, e.g. 'Integer', 'List of String' - the TYPE only, no arrows.",
                      "type" : "string"
                    }
                  },
                  "required" : [ "name", "type" ],
                  "type" : "object"
                }, {
                  "description" : "Also accepted: the EK9 text 'name as Type' - but send the object; the tool writes the '->' block, its indentation and its order.",
                  "type" : "string"
                } ]
              },
              "type" : "array"
            },
            "requirementId" : {
              "description" : "Requirement id this test anchors, e.g. 'REQ:velocity-limit' - renders '@Test: \"REQ:velocity-limit\"'. Requires isTest:true; supplying it for a non-test program is rejected rather than ignored. The requirement need not exist yet (writing the test first is legitimate); ek9_check_requirements reports an id that names nothing as a dangling anchor.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "statements" : {
              "description" : "The program's body, one statement or one block per entry ('stdout <- Stdout()', 'stdout.println(...)'): the program is complete in this call - no stub, no ek9_insert_statements after it. Parsed alone first; a problem names the entry and its fix.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file", "name" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_anchor_test",
        "description" : "Rewrites the @Test directive line above the program to '@Test: \"REQ:x\"' (the execution group kept); answers status, edits[] and summary, an unchanged directive as conflicts[] (already-anchored).",
        "idePrecedent" : "Anchor Test to Requirement",
        "whenToUse" : "'Anchor test VelocityBreachesAboveFive to REQ:velocity-limit' is one call with file, symbol (the @Test program) and requirementId - omit it to clear.",
        "whenNotToUse" : [ "The test does not exist yet - use ek9_add_program (isTest:true, requirementId:…) to create it already anchored, rather than create-then-anchor.", "You want to change the requirement itself, not which test proves it - use ek9_update_requirement (anchors live on the @Test; requirement nodes never list their tests).", "The requirement node does not exist - use ek9_create_requirement (you may anchor first; it reports as a dangling anchor until created)." ],
        "examples" : [ "{file:'dev/tests.ek9', symbol:'VelocityBreachesAboveFive', requirementId:'REQ:velocity-limit'} → status:applied; @Test rewritten to @Test: \"REQ:velocity-limit\".", "Clear: same args without requirementId → @Test: \"REQ:already-anchored\" returns to a bare @Test." ],
        "details" : "Cursor-free: symbol is resolved through the symbol table, not by searching source text. The execution group survives — @Test: \"database\" becomes @Test: \"REQ:x\": \"database\" (id first, group second), read from the compiler's parsed TEST_DIRECTIVE so a serialised test is never silently rescheduled to the parallel pool. The requirement need not exist yet (dangling anchor, not faulted), but a mistyped prefix like RREQ:foo is rejected — it would otherwise pass as a legal execution GROUP name and silently drop the anchor; status:'error' when no such @Test in the file or no directive line above it (recompile); a directive that already reads as requested is conflicts[] kind 'already-anchored'.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The dev source holding the @Test program (workspace-relative; an absolute path inside the workspace is accepted).",
              "type" : "string"
            },
            "requirementId" : {
              "description" : "Requirement id to anchor to, e.g. 'REQ:velocity-limit'. Omit to CLEAR the anchor, leaving a plain @Test.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The @Test program's name, e.g. 'VelocityBreachesAboveFive' - a @Test program declared in this file.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file", "symbol" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_apply_symbolic_fix",
        "description" : "Inserts the 'require <divisor> <> 0' line guard-first (an inline-initialised return becomes '<- rtn as T?' + ':=?'), re-finding the counterexample server-side, and migrates a pinning @Test to assertPanic in the same changeset; answers status, edits[] and summary.",
        "idePrecedent" : "IDE quick-fix: add precondition (design-by-contract guard)",
        "whenToUse" : "After ek9_verify_method reports DIV_BY_ZERO, 'add the guard' is one call with symbol:'safeAverage' - it inserts the 'require <divisor> <> 0' precondition. V1: DIV_BY_ZERO only.",
        "whenNotToUse" : [ "You want to FIND whether a function is safe, not change it - use ek9_verify_method (the read-only dual).", "You want to PIN the failing boundary as a test rather than fix it - use ek9_generate_edge_case_tests (mode:'verify' writes a directed test per counterexample).", "You want to add a PARAMETER, not a precondition - use ek9_change_signature (shares the same guard-first return machinery)." ],
        "examples" : [ "{symbol:'safeAverage', dryRun:true} → status:ready; inline-initialised return rewritten guard-first to `<- rtn as Integer?` + `require count <> 0` + `rtn :=? total / count`.", "alreadyTested boundary: same args → status:ready with a TWO-FILE changeset — the source require plus migrating the pinning @Test off `…UnsetWhen…` to `…PanicsWhen…` with assertPanic, so one apply leaves the workspace green." ],
        "details" : "V1 scope: DIV_BY_ZERO only (single named-parameter divisor); other obligations (CALLEE_REQUIRE, CONSTRAINED_CONSTRUCTION) are rejected with a message, not a guessed edit. The finding is re-found by re-running the engine at apply time and matched by obligation+witness on the function at line — a drifted or already-discharged line errors, several fixable divisions demand a witness ('<param>=<value>') to pick one, and the require line is derived server-side (no free-text guard param). Pinned-test migration is off-when-unsure: a hand-edited test it does not recognise is left untouched and named for manual migration.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "obligation" : {
              "description" : "Optional counterexample selector when the function has several findings. V1 accepts only 'DIV_BY_ZERO'.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The function (or 'Type.method') to fix - a bare name finds its file workspace-wide; 'mod::name' when the name is declared in two modules.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            },
            "witness" : {
              "description" : "Optional counterexample selector ('<param>=<value>', e.g. 'quantity=0') when the function has several fixable divisions.",
              "type" : "string"
            }
          },
          "required" : [ "symbol" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_apply_guard_pattern",
        "description" : "Rewrites the construct's head to 'if x <- f()' (or ':=?', or ':=' with an explicit 'then x?', as the source assignment dictates) and deletes the standalone assignment and the redundant 'x?' check; answers status, edits[] and summary, an unsafe or unsupported shape as conflicts[].",
        "idePrecedent" : "IDE quick-fix: convert check-then-use into a guard (design-by-contract null safety)",
        "whenToUse" : "Turn 'x <- f()' followed by 'if x?' into the EK9 guard 'if x <- f()' - one call with file and symbol:'x' (the variable), or line on the control line.",
        "whenNotToUse" : [ "There is no surrounding control construct yet - use ek9_surround_with to wrap first.", "You just want an expression pulled into a local (no gating intent) - use ek9_extract_variable.", "The lifted local must survive past the construct as per-instance state - use ek9_introduce_field." ],
        "examples" : [ "{file:'order.ek9', symbol:'customer', dryRun:true} where 'customer <- find(id)' precedes 'if customer?' -> status:ready, edit rewrites the head to 'if customer <- find(id)' and drops the 'customer?' check.", "Fresh '<-' local read after the construct -> status:conflicts kind variable-used-outside-control-scope (lifting would narrow its scope)." ],
        "details" : "No mode parameter - the head operator is DERIVED from the source: '<-' stays '<-', ':=?' stays ':=?' (both gate the body per PreFlowCanSkipBody), a blind ':='/'='/':' stays ':=' with the gate explicit ('if x := f() then x?', 'while x := f() then x? and (cond)') and is refused before switch/try/for/do (kind blind-assignment-cannot-gate). Per-construct fold: switch guards on the MATCH SUBJECT ('v <- getV()'+'switch v' -> 'switch v <- getV()'); try/function has no control so folds bare ('try r <- open()'); loops keep their control/iteration as a 'then' clause ('while x <- src() then cond', 'for x <- src() then item in coll') and the guard is evaluated ONCE at loop entry, not per iteration. A compound 'if x? and cond' (isSet on either side of a two-operand 'and') lifts with the other operand preserved as a 'then' residual; an 'or', a three-way 'and', or nested 'x?' stays a compound-control-not-supported conflict (other kinds: switch-subject-not-the-guard-var, already-a-guard).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file (workspace-relative; an absolute path inside the workspace is accepted).",
              "type" : "string"
            },
            "line" : {
              "description" : "IDE refinement: the 1-based line of the control construct's opening line (if/while/do-while/for/switch/try). Omit it and pass symbol.",
              "type" : "integer"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The variable to lift into the guard ('customer'); the tool finds the control construct that tests it. Prefer this to line.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_add_constructor",
        "description" : "Inserts one constructor at the construct's canonical member position; answers status, edits[] and summary, a same-shape constructor as conflicts[] (constructor-already-exists, with its line).",
        "idePrecedent" : "Generate Constructor (IntelliJ)",
        "whenToUse" : "'Add a constructor to Order' is one call with symbol:'Order' and mode:'no-arg' | 'all-fields' | 'fields' (+ fields:[...]) - no file or line needed.",
        "whenNotToUse" : [ "Adding a regular METHOD or an operator like == / <=> - use ek9_add_method / ek9_add_operator.", "Implementing trait methods on a class - use ek9_implement_methods.", "The TYPE doesn't exist yet - create it with ek9_new_construct first, constructors second.", "Target must be a class, record, or component - functions, traits, services, and enumerations have no constructors." ],
        "examples" : [ "{symbol:'CustomerOrder', mode:'all-fields'} → one INSERT: a constructor taking every field as a parameter and assigning each.", "{symbol:'Order', fields:['id','customerName']} → mode 'fields' inferred; a matching parameter shape already present returns status:conflicts (constructor-already-exists, naming its line - nothing is written)." ],
        "details" : "Modes: no-arg (emits a bodyless 'default' constructor; visibility default private, honours pure), all-fields (honours pure), fields (requires non-empty fields[]; purity auto-detected - greenfield defaults pure). In the field modes purity is auto-forced when any existing constructor is pure (mixed purity won't compile); assignment is ':' non-pure, ':=?' when pure (never the copy operator ':=:'). A duplicate parameter shape returns status:'conflicts' kind 'constructor-already-exists' naming the existing constructor's line and the insert_statements / change_signature / safe_delete routes (nothing written); a pure constructor over a field DECLARED with a value returns status:'error' (E08095) - declare those fields unset (String()/Integer()) or request non-pure.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "fields" : {
              "description" : "fields mode - names of the fields to take as constructor parameters, in order.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "mode" : {
              "description" : "no-arg: a bodyless 'default' constructor blocking default construction; all-fields: every field as a parameter; fields: the named subset (inferred when fields[] is given).",
              "enum" : [ "no-arg", "all-fields", "fields" ],
              "type" : "string"
            },
            "pure" : {
              "description" : "Add 'as pure'. Default false.",
              "type" : "boolean"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The class, record or component the constructor is added to - a bare name ('Order') finds its file workspace-wide; 'mod::Order' when the name is declared in two modules.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            },
            "visibility" : {
              "description" : "no-arg only - the constructor's access. Default 'private' (the typical force-a-factory use).",
              "enum" : [ "public", "private", "protected" ],
              "type" : "string"
            }
          },
          "required" : [ "symbol" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_convert_constructor",
        "description" : "Replaces the no-arg constructor with its other spelling - 'default Name()' ↔ an explicit 'this.field :=? Type()' default-setter body - purity and visibility preserved; answers status, edits[] and summary.",
        "idePrecedent" : "Convert Constructor (collapse/expand)",
        "whenToUse" : "'Expand Person's default constructor so I can customise it' (or collapse it back) is one call with symbol:'Person' - mode is inferred from the constructor.",
        "whenNotToUse" : [ "The constructor doesn't exist yet - use ek9_add_constructor to create one.", "You want to round-trip an OPERATOR (==, <=>, $, …), not a constructor - use ek9_expand_default_operator / ek9_collapse_to_default_operator.", "The constructor takes parameters and its signature is wrong - use ek9_change_signature (this is a no-arg form swap, not a structural change).", "The constructor should be removed entirely - use ek9_safe_delete." ],
        "examples" : [ "{symbol:'Person', dryRun:true} on 'default private Person() as pure' → mode expand inferred; status:ready replacing it with 'private Person() as pure' + one 'this.field :=? Type()' per assignable field.", "{symbol:'Person', mode:'collapse'} → status:applied; the explicit default-setter body collapses back to 'default private Person() as pure' (byte-identical round-trip of expand)." ],
        "details" : "Class/record only; the no-arg constructor is found by name (parser-authoritative) and mode is inferred from its form - a 'default' constructor expands, an explicit one collapses; an explicit mode that contradicts the form is status:error. expand emits the guarded ':=?' default-setter, skipping injected ('name as Type!') fields and any field whose declaration already supplies a value (its ':=?' would be dead code, E08095), and errors when no field is left to set (keep 'default'). collapse returns would-fail when losing the explicit body would break the compile - e.g. uninitialised 'as Type?' fields with no co-initialising value ctor (E08070/E07175).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "IDE refinement: the 1-based line of the constructor declaration. Omit it - the no-arg constructor is found by symbol; needed only when two exist.",
              "type" : "integer"
            },
            "mode" : {
              "description" : "Optional - inferred from the constructor found: a 'default' constructor expands (→ explicit 'this.field :=? Type()' body), an explicit no-arg one collapses (→ 'default').",
              "enum" : [ "expand", "collapse" ],
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The class or record owning the no-arg constructor - a bare name ('Person') finds its file workspace-wide; 'mod::Person' when the name is declared in two modules.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "symbol" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_add_operator",
        "description" : "Inserts the operator declaration (or the bare 'default operator' block) at the construct's canonical operator position; answers status, edits[] and summary - 'unchanged', naming where it is, for a default operator the construct already declares; statements[] for an operator already written by hand replace its body, and body:'explicit' over one names the insert route in conflicts[].",
        "idePrecedent" : "Generate Operator (no direct equivalent — EK9-specific)",
        "whenToUse" : "'Add operator $ to Point' is one call with symbol:'Point', operator:'$' and statements:['rtn: ...'] (its body); body:'default' synthesises a defaultable one (==, <=>, ?).",
        "whenNotToUse" : [ "The member is a regular METHOD with an ordinary identifier name - use ek9_add_method; a CONSTRUCTOR (the type's own name) - use ek9_add_constructor.", "You want to CHECK whether an existing operator is contract-compliant - the compiler already reports it; use ek9_query_diagnostics / ek9_explain_error. (A NEW operator's purity is derived here, so E07500/E07510 cannot reach you from this tool.)", "You want to implement TRAIT methods in bulk - use ek9_implement_methods.", "The target type does not exist yet - use ek9_new_construct." ],
        "examples" : [ "{symbol:'OrderItem', operator:'==', dryRun:true} → status:ready, one INSERT 'default operator =='.", "{symbol:'CustomerOrder'} → inserts bare 'default operator' so the compiler synthesises every missing defaultable op (the sameness - <=> where every field can be ordered, else == - from which the other comparisons are generated, and ?, $, $$, #?, :=:).", "{symbol:'Money', operator:'+', body:'explicit'} → a field-based 'operator + as pure' stub with an //AI: marker to complete (then ek9_insert_statements method:'+')." ],
        "details" : "body:'default' with an operator emits 'default operator <sym>' for that one op; with no operator it emits the bare 'default operator' (compiler synthesises every missing defaultable op; errors if a default block already exists). body:'explicit' generates a field-based implementation stub you fill in (templates for the defaultable set plus the arithmetic/word-form ops; needs at least one field) and is the only form also allowed on traits. The operator is given in EK9 source form ('==' not '_eq'); pipeline keywords (sort, filter, …) are rejected and the deprecated '!=' is applied as '<>' (E07640) with the summary saying so. A duplicate operator symbol returns status:'conflicts' (kind 'signature-collision') naming the ek9_expand_default_operator route - operator semantics are load-bearing; surface to the human rather than retry (pre-check via ek9_query_construct → operators[]).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "body" : {
              "description" : "default (the default): a 'default operator' declaration the compiler synthesises from the fields; explicit: a field-based stub for you to complete.",
              "enum" : [ "default", "explicit" ],
              "type" : "string"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "operator" : {
              "description" : "The operator in EK9 source form: '==', '<=>', '<>', '?', '$', '#?', ':=:', '+', 'mod', … Omit it with body:'default' to default every defaultable operator at once.",
              "type" : "string"
            },
            "pure" : {
              "description" : "IGNORED for a known operator and not worth sending - purity is not a choice. The language fixes it: every non-mutating operator must be 'as pure' (E07500) and the ten mutators ':~: :^: :=: | += -= *= /= ++ --' must not be (E07510). It is derived and the response says so in 'corrected' when your value differed.",
              "type" : "boolean"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "statements" : {
              "description" : "The operator's body, one statement or one block per entry (assign 'rtn: <value>' on every path): the operator is complete in this call - override, pure and the language-fixed signature are filled in. Needs 'operator'; 'body' is then ignored. For an operator the construct already implements by hand, its body is REPLACED (the signature stays).",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "symbol" : {
              "description" : "The class, record, component, enumeration (or trait, for an explicit body) the operator is added to - a bare name ('Point') finds its file workspace-wide; 'mod::Point' when the name is declared in two modules.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "symbol" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_collapse_to_default_operator",
        "description" : "Replaces the operator's declaration and body with the one-line 'default operator <sym>' form (the inverse of ek9_expand_default_operator); answers status, edits[] and summary.",
        "idePrecedent" : "Code Cleanup → Use Default Implementation (no direct equivalent)",
        "whenToUse" : "'Collapse Order's hand-written == back to default operator ==' is one call with symbol:'Order' and operator:'==' - the body must be default-equivalent.",
        "whenNotToUse" : [ "You want the OPPOSITE - turn 'default operator <sym>' into an explicit body so you can customise it - use ek9_expand_default_operator.", "The operator should be REMOVED entirely, not collapsed - use ek9_safe_delete.", "The operator's signature/purity is wrong and needs structural change - use ek9_change_signature.", "The body is genuinely customised - the tool does NOT diff it against the synthesis and will silently discard the custom behaviour; review with ek9_read_construct first." ],
        "examples" : [ "Preview: {symbol:'CustomerOrder', operator:'<=>', dryRun:true} → status:ready, edits[] replace the declaration + body with 'default operator <=>'.", "Non-defaultable: same args with operator:'+' → error \"Operator '+' cannot be defaulted\"." ],
        "details" : "Class/record/component only; the defaultable set is <=> == ? $ $$ #? :=: - <> < <= > >= are generated from the sameness and never written or requested (E07181; E07180 where the type has no <=> to generate them from), and any other operator errors ('cannot be defaulted'). The operator's declaration is found by name; two explicit declarations of the same symbol (overloads) are a conflict listing their lines - pass line to pick one. The whole declaration + body is replaced without being diffed against the compiler's synthesis, so a customised body is silently lost. The verify compile only catches breakage the collapse itself introduces (e.g. E07030 default-on-trait, E07190 super missing '<=>', a field type lacking the operator).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "IDE refinement: the 1-based line of the operator declaration. Omit it - the declaration is found by symbol + operator; needed only when two overloads declare the same operator.",
              "type" : "integer"
            },
            "operator" : {
              "description" : "The operator to collapse, in EK9 source form ('==', '<=>', '$'); must be in the defaultable set.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The class, record or component holding the operator - a bare name ('Order') finds its file workspace-wide; 'mod::Order' when the name is declared in two modules.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "symbol", "operator" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_expand_default_operator",
        "description" : "Replaces the 'default operator' declaration with the explicit body the compiler would synthesise from the fields, ready to customise; answers status, edits[] and summary, a missing default line as conflicts[] (target-not-a-default-operator).",
        "idePrecedent" : "Generate Default Implementation (no direct equivalent)",
        "whenToUse" : "'Expand Order's default operator <=> so I can customise it' is one call with symbol:'Order' and operator:'<=>' - omit operator to expand the bare block.",
        "whenNotToUse" : [ "Going the OTHER way - replacing an explicit operator body with the 'default operator' form (cleanup) - use ek9_collapse_to_default_operator.", "The operator doesn't exist on the target yet (default OR explicit) - use ek9_add_operator to author from scratch.", "The operator should be REMOVED entirely, not expanded - use ek9_safe_delete.", "Operator outside the defaultable set (==, <>, <=>, <, >, <=, >=, $, $$, ?, #?, :=:) - no default form exists; author it with ek9_add_operator body:'explicit'." ],
        "examples" : [ "{symbol:'Order', operator:'<=>'} → the 'default operator <=>' line is REPLACED with the explicit pure body cascading each property's <=>.", "{symbol:'Order'} → the bare 'default operator' block expands into individual explicit implementations of every defaultable operator not already declared." ],
        "details" : "Class / record / component targets with at least one field - traits and field-less constructs are refused (no properties to derive a body from). mode is inferred (operator → one, none → all); a contradicting mode is a mode-contradicts-arguments conflict. The 'after' body mirrors the compiler's synthesis: == ANDs each property's ==, <=> cascades to the first non-zero in declaration order (< <= > >= delegate to <=>), ? ANDs each _isSet, #? XORs each _hashcode, $ interpolates the fields, :=: copies each field; all emit 'as pure' except :=:, chaining via super.<op>(...) first when a real superclass declares the operator. Conflict kinds: target-not-a-default-operator (no such 'default operator' line - a hand-written body is changed with ek9_insert_statements), target-operator-not-supported-for-default; the all form skips operators already explicitly declared and errors when none remain.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "IDE refinement: the 1-based line of the 'default operator' declaration. Omit it - the declaration is found by symbol + operator.",
              "type" : "integer"
            },
            "mode" : {
              "description" : "Optional - inferred: operator given → one, none → all.",
              "enum" : [ "one", "all" ],
              "type" : "string"
            },
            "operator" : {
              "description" : "The one operator to expand, in EK9 source form ('==', '<=>', '$'). Omit it to expand the bare 'default operator' block into every defaultable operator.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The class, record or component holding the default operator - a bare name ('Order') finds its file workspace-wide; 'mod::Order' when the name is declared in two modules.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "symbol" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_adopt_trait",
        "description" : "Appends 'with trait of <Trait>' (or '… by <field>') to the construct's declaration and, when implementing, one override stub per abstract trait method; answers status, edits[] and summary, an already-adopted trait as conflicts[] (trait-already-adopted).",
        "idePrecedent" : "Implement Interface (IntelliJ — Cmd+I) / Generate Delegate",
        "whenToUse" : "'Make Greeter implement Speakable' is one call with symbol:'Greeter' and traitName:'Speakable' - stubs to fill in, or delegateFieldName to forward to a field.",
        "whenNotToUse" : [ "The class ALREADY declares the trait and only the 'by <field>' forwarding is missing - use ek9_generate_delegation.", "Renaming or restructuring a method rather than adopting a trait - use ek9_rename_symbol or ek9_change_signature.", "The target is a record / function / enumeration - trait composition is classes/components only.", "Defining the trait itself, or pulling members out of a class into a new trait - use ek9_new_construct / ek9_extract_trait.", "Adding a plain method or field (no trait contract) - use ek9_add_method / ek9_add_field." ],
        "examples" : [ "Implement: {symbol:'MyClass', traitName:'Validator', dryRun:true} → REPLACE appends 'with trait of Validator' on the decl line + INSERT one override stub per missing abstract method.", "Delegate: {symbol:'MyClass', traitName:'Logger', delegateFieldName:'log'} → 'with trait of Logger by log'; the compiler auto-generates forwarders, no bodies to fill." ],
        "details" : "mode is inferred: delegateFieldName present → delegate, else implement; a contradicting mode is a mode-contradicts-arguments conflict. delegate errors if delegateFieldName names no existing field; a sealed ('allow only') trait that excludes the construct also errors. conflicts kinds: 'trait-already-adopted' and 'target-cannot-adopt-trait' (record/enumeration); a concrete-method clash across traits does NOT block - implement emits an override stub with '//AI: Resolve conflict' guidance instead. A trait from another module gets a references entry added automatically; a delegate field whose type doesn't satisfy the trait surfaces as would-fail from the scratch compile.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "delegateFieldName" : {
              "description" : "To DELEGATE instead of implementing: the existing field on the construct to forward every trait method to (its type must satisfy the trait).",
              "type" : "string"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "mode" : {
              "description" : "Optional - inferred: delegateFieldName present → delegate, else implement (override stubs).",
              "enum" : [ "implement", "delegate" ],
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The class or component that adopts the trait - a bare name ('Greeter') finds its file workspace-wide; 'mod::Greeter' when the name is declared in two modules.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "traitName" : {
              "description" : "The EXISTING trait to adopt (must resolve in the workspace); 'mod::Trait' when the name is declared in two modules.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "symbol", "traitName" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_implement_methods",
        "description" : "Inserts an 'override' stub for every abstract trait method the class has not implemented; answers status and edits[] (one INSERT block), or an error carrying the resolutions[] a concrete-diamond collision needs.",
        "idePrecedent" : "Implement Methods (IntelliJ)",
        "whenToUse" : "'Implement every missing trait method on Order' is one call with symbol:'Order' - no file, line or mode needed. Use it when a CLASS declares traits (`with trait of X, Y`) and its unimplemented abstract trait methods should be stubbed in one batch.",
        "whenNotToUse" : [ "Adding ONE specific method - use ek9_add_method; a trait-declared OPERATOR - use ek9_add_operator (this tool batches methods only).", "You only want to ENUMERATE what is missing without inserting — use ek9_query_effective_api (read-only).", "You want CLASS-LEVEL trait delegation (`with trait of X by field`) — use ek9_generate_delegation; `by` is class-header only, there is no per-method form.", "The target class does not exist yet — use ek9_new_construct." ],
        "examples" : [ "{symbol:'CustomerOrder', dryRun:true} → status:ready, edits[] with one INSERT block of `override` stubs (each `<- rtn as T: <default>` + `//AI:` lead).", "Concrete diamond: the default stubs mode returns status:error whose message carries a ready-to-edit resolutions[] payload → retry {symbol:'CustomerOrder', resolutions:[{methodName:'process', source:'trait:Validatable'}, {methodName:'audit', source:'override'}]} (mode is inferred as conflict-resolution)." ],
        "details" : "Every generated stub carries `override` (trait methods are never bare, per E05120); an all-abstract multi-trait collision auto-resolves to a single override, while a concrete diamond (at least one trait supplies a BODY) makes stubs mode return status:error with a ready-to-edit resolutions[] payload — retry with resolutions[], each picking source 'trait:<Name>' (delegate to that body; rejected if that trait declares it abstract) or 'override' (your own stub, whose //AI: lead names the trait bodies to combine). An already-implemented method is a no-op with EMPTY edits[] — never a conflicts status. would-fail = a generated stub references an unresolvable type; errors on non-class targets (record/trait/component/enum).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "members" : {
              "description" : "Optional - only implement these methods (each must be an unimplemented trait method). A name gets a placeholder body; {name, statements:[...]} is written WITH that body, so one call implements the trait. Omit to implement all outstanding with placeholders.",
              "items" : {
                "anyOf" : [ {
                  "type" : "string"
                }, {
                  "additionalProperties" : false,
                  "properties" : {
                    "name" : {
                      "type" : "string"
                    },
                    "statements" : {
                      "items" : {
                        "type" : "string"
                      },
                      "type" : "array"
                    }
                  },
                  "required" : [ "name" ],
                  "type" : "object"
                } ]
              },
              "type" : "array"
            },
            "mode" : {
              "description" : "Optional: stubs (the default - placeholder bodies for every outstanding method) or conflict-resolution (inferred whenever resolutions[] is sent).",
              "enum" : [ "stubs", "conflict-resolution" ],
              "type" : "string"
            },
            "resolutions" : {
              "description" : "One {methodName, source} per conflicting method; source is 'trait:<TraitName>' or 'override'. Sending it selects conflict-resolution mode.",
              "items" : {
                "additionalProperties" : false,
                "properties" : {
                  "methodName" : {
                    "type" : "string"
                  },
                  "source" : {
                    "type" : "string"
                  }
                },
                "required" : [ "methodName", "source" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_generate_delegation",
        "description" : "Rewrites the class header's 'with trait of <Trait>' clause to '... by <field>' so the compiler forwards every trait method (no bodies generated); answers status, edits[] and summary.",
        "idePrecedent" : "Generate Delegate (IntelliJ)",
        "whenToUse" : "'Forward every Processor method of DelegatingProcessor to its inner field' is one call with symbol, traitName:'Processor' and delegate:'inner'.",
        "whenNotToUse" : [ "The class does NOT yet declare the trait at all - use ek9_adopt_trait with delegateFieldName, which appends 'with trait of <Trait> by <field>' in one step.", "The delegate field doesn't exist yet - add it first with ek9_add_field, then call this.", "You want stub bodies to fill in rather than compiler-generated forwarding - use ek9_implement_methods (mode=stubs).", "You want to override ONE trait method's behaviour - wire delegation here first, then ek9_add_method for the explicit override (it shadows the forwarded method)." ],
        "examples" : [ "{symbol:'DelegatingProcessor', traitName:'Processor', delegate:'delegate', dryRun:true} → status:ready; one REPLACE turning 'DelegatingProcessor with trait of Processor' into '... by delegate'.", "{symbol:'MultiTraitClass', traitName:'com.acme.shared::Validatable', delegate:'validator'} → status:applied (fully-qualified trait name disambiguates)." ],
        "details" : "Modifies ONE line - the class declaration - because 'by' attaches to the trait reference at class-declaration time, not per-method (per-method 'name by field' syntax does not exist). status:'error' for validation failures: target not a class, class declares no traits, trait not declared on the class, ambiguous trait name, delegate field missing, trait already delegated, or a malformed class-header line (the grammar keeps the whole header on one physical line, so a multi-line clause cannot occur). A non-conforming delegate field type is NOT blocked - the tool proceeds and notes it; a genuine mismatch surfaces as status:'would-fail' from the whole-workspace verify (never 'conflicts'; no banned-identifier check since the field already exists).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "delegate" : {
              "description" : "The name of an existing field on the class to delegate to. Its declared type should implement the trait (the compile gate confirms).",
              "type" : "string"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The class that declares the trait - a bare name ('DelegatingProcessor') finds its file and declaration workspace-wide; 'mod::Name' when the name is declared in two modules.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "traitName" : {
              "description" : "The trait already declared on the class via 'with trait of'. Simple name (Processor) when unambiguous; fully-qualified (com.acme::Processor) when needed.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "symbol", "traitName", "delegate" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_generate_dispatcher",
        "description" : "Inserts the '<name>() as dispatcher' entry with its fallback body plus one same-named handler overload per subtype of the base type; answers status, edits[] and summary naming any existing handlers kept.",
        "idePrecedent" : "Generate (IntelliJ) — visitor/dispatch scaffolding",
        "whenToUse" : "'Give ShapeProcessor a describe() dispatcher over Shape' is one call with symbol:'ShapeProcessor', name:'describe' and baseType:'Shape'.",
        "whenNotToUse" : [ "Adding ONE method rather than a dispatcher + handler set - use ek9_add_method.", "Registering a NEW subtype on the sealed base (not generating handlers) - use ek9_add_to_sealed_allowlist.", "INSPECTING existing dispatcher tables, not generating new ones - use ek9_query_dispatchers.", "The dispatcher CLASS does not exist yet - create it with ek9_new_construct first, then run this on it." ],
        "examples" : [ "{symbol:'AnimalDispatcher', name:'sound', baseType:'com.acme.zoo::Animal', returnType:'String', dryRun:true} → status:ready; entry sound() as dispatcher + private sound() overloads for Dog/Cat/Bird.", "{symbol:'AnimalDispatcher', name:'sound', baseType:'Animal'} → applied; Dog already had a sound(Dog) handler, so it is skipped and the summary notes 'existing handler(s) kept for Dog'." ],
        "details" : "Class targets only (errors on record/trait/component); handlers REUSE the entry's name - overload-by-parameter-type IS the dispatch mechanism (E05260), there is no '<name>For<Subtype>' convention. The entry is concrete: its guard-first fallback body ('require' guard, '//AI:' marker, and for a returning dispatcher '<- rtn as T?' assigned only after the guard) is also the runtime default when no handler matches; sealed base → one handler per 'allow only' subtype (compiler enforces exhaustiveness), non-sealed base → known concrete subtypes only with the fallback covering the rest (errors if none known). Never returns 'conflicts': an existing dispatcher on the class (E07820) or a same-named non-dispatcher method taking the base type is an error; an existing per-subtype handler is silently skipped and named in the summary.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "additionalParameters" : {
              "description" : "Optional extra parameters added to BOTH the entry and every handler, after the auto-added subtype parameter; each ONE item as DATA - {name:'code', type:'Integer'} - never a {name} and a {type} as two items, and never with '->' or '<-' in it; the tool writes the arrows and the layout. ('code as Integer' is also accepted.) A malformed item is rejected before anything is rendered.",
              "items" : {
                "anyOf" : [ {
                  "properties" : {
                    "name" : {
                      "description" : "The parameter's name.",
                      "type" : "string"
                    },
                    "type" : {
                      "description" : "Its type, e.g. 'Integer', 'List of String' - the TYPE only, no arrows.",
                      "type" : "string"
                    }
                  },
                  "required" : [ "name", "type" ],
                  "type" : "object"
                }, {
                  "description" : "Also accepted: the EK9 text 'name as Type' - but send the object; the tool writes the '->' block, its indentation and its order.",
                  "type" : "string"
                } ]
              },
              "type" : "array"
            },
            "baseType" : {
              "description" : "The type the dispatcher dispatches on. If SEALED, its 'allow only' subtypes drive handlers (exhaustive); if NON-SEALED, its known concrete subtypes do (entry fallback covers the rest). Fully-qualified (com.acme::Animal) when needed; simple name when unambiguous.",
              "type" : "string"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "name" : {
              "description" : "The dispatcher method name (e.g. describe, sound, route). ALL generated handlers share this exact name.",
              "type" : "string"
            },
            "returnType" : {
              "description" : "Optional shared return type for the entry + every handler. Omit for a void dispatcher.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The class the dispatcher is scaffolded on - a bare name ('ShapeProcessor') finds its file and declaration workspace-wide; 'mod::Name' when the name is declared in two modules.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            },
            "visibility" : {
              "description" : "The handlers' access (default 'private' - handlers are implementation details). The entry is always public.",
              "enum" : [ "private", "public", "protected" ],
              "type" : "string"
            }
          },
          "required" : [ "symbol", "name", "baseType" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_prune_empty_sources",
        "description" : "Deletes every .ek9 file that is a bare zero-construct module stub (shebang + 'defines module' + //EOF, line comments allowed); answers status, edits[] (one DELETE per file) and skipped[] with a reason per retained candidate.",
        "idePrecedent" : "NEW IDE surface — Build menu → \"Prune Empty Sources…\" wizard (root picker, module filter, preview pane with two columns: files to delete + retained candidates with reasons). Also wired as a receipts-pane follow-up action on every pure-relocation tool (ek9_organize_localized_text, ek9_extract_to_localized_text, ek9_move_construct).",
        "whenToUse" : "'Delete every empty module stub left after the relocation' is one call with no arguments (narrow with roots and/or moduleFilter:['com.example.i18n']). Use it AFTER a pure-relocation tool has left donor files as bare 'defines module' shells; it needs a prior ek9_compile.",
        "whenNotToUse" : [ "You know the exact single file to delete - use ek9_delete_file; this tool is bulk-discovery + filter.", "The stub still has block/doc comments you want pruned anyway — run ek9_remove_comments first to make it eligible, then this tool.", "The empty stubs are intentional placeholders (TODO scaffolds, planned module structure) — leave them; do not run this tool." ],
        "examples" : [ "Module-scoped: {moduleFilter:['com.example.i18n'], dryRun:true} → only stubs in that module in edits[]; files in other modules appear in skipped[] with reason 'module-filter-mismatch'.", "Clean workspace: {} → status:applied, empty edits[] — idempotent no-op." ],
        "details" : "Two-stage detection: symbol-table walk for zero-construct module scopes, then a lex-only confirmation of the bare `#!ek9 / defines module / //EOF` shape — line `//` comments count as whitespace (pruned); block (`<!- -!>`) and doc (`<?- -?>`) comments are preserving. Pure deletion (only `delete` edits, never rewrites); non-deleted candidates go in `skipped[]` (reason ∈ has-comments | has-unrecognised-content | not-a-stub | module-filter-mismatch | outside-roots) with a `retainedContent` line-slice, while status:conflicts is reserved for a locked/unreadable file (kind:file-locked-or-unreadable). `dirtyOverlay` is V1 schema-accepted but not consumed; when supplied it appears in the response's `deferredParameters[]`.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "moduleFilter" : {
              "anyOf" : [ {
                "items" : {
                  "type" : "string"
                },
                "maxItems" : 1,
                "type" : "array"
              }, {
                "type" : "string"
              } ],
              "description" : "Restrict candidates to files declaring this exact `defines module <name>` (an array of one name, or the bare name). When omitted, every module in the scanned roots is considered."
            },
            "roots" : {
              "description" : "Workspace-relative directories to scan. When omitted, defaults to the watched workspace roots.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_rewrite_string_concat",
        "description" : "Replaces a selected `\"a\" + var + \"b\"` concat with the equivalent backtick interpolation, escaping backtick and `$` in literal segments; answers status and edits[] (one REPLACE), conflicts[] (selection-not-a-string-expression, operand-has-no-string-operator with a suggestedRepair), or status:unchanged when the selection is already a backtick literal.",
        "idePrecedent" : "IntelliJ \"Convert to template string\" / VS Code \"Convert to template literal\"",
        "whenToUse" : "'Turn this \"a\" + var + \"b\" concat into a backtick literal' is one call with file and selection:{startLine, startColumn, endLine, endColumn} (1-based, inclusive) covering exactly the concat expression. Use it when a concat reads better interpolated, or before ek9_extract_to_localized_text.",
        "whenNotToUse" : [ "The goal is to move the string into the i18n surface, not just modernise syntax - use ek9_extract_to_localized_text (it runs this first when the source is a concat).", "An operand is a conditional/lambda/collection/side-effecting/non-stringable expression - the tool refuses with an escalation conflict; extract that operand to a local via ek9_extract_variable first, then re-run.", "The `+` on the selection is not string concat (e.g. numeric 1 + 2) - not a string expression; there is nothing to interpolate." ],
        "examples" : [ "{file:'Welcome.ek9', selection:{startLine:7,startColumn:13,endLine:7,endColumn:51}, dryRun:true} on `\"Hello \" + person.firstName + \", welcome\"` -> status:ready, after `` `Hello ${person.firstName}, welcome` ``.", "Selection already a backtick literal -> status:unchanged, written:false, empty edits (idempotent no-op, disk untouched)." ],
        "details" : "Transformation is syntactic; the admission gate is semantic - a compiled program is required and every operand's type must define `_string()`, else a conflict carrying a suggestedRepair. A selection already in target form is status:unchanged (nothing written). Escaping: backtick and `$` in literal segments are escaped; redundant \\\" is reduced to \"; \\n \\t \\r \\\\ preserved verbatim.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "Workspace-relative path to the .ek9 source file containing the selection.",
              "type" : "string"
            },
            "selection" : {
              "description" : "The IDE selection covering exactly the concat expression (or one string literal): startLine/endLine and startColumn/endColumn, all 1-based, end INCLUSIVE - read them off ek9_read_file(lineNumbers:true). startCol/endCol are accepted.",
              "properties" : {
                "endColumn" : {
                  "type" : "integer"
                },
                "endLine" : {
                  "type" : "integer"
                },
                "startColumn" : {
                  "type" : "integer"
                },
                "startLine" : {
                  "type" : "integer"
                }
              },
              "required" : [ "startLine", "endLine" ],
              "type" : "object"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file", "selection" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_insert_coalescing",
        "description" : "Splices `LEFT <op> RIGHT` in place of a selected single-line left operand using one of the EK9 coalescing / elvis operators; answers status, edits[] (one REPLACE) and conflicts[] (kind 'selection-not-an-expression'), with a type-incompatible pairing surfacing as would-fail.",
        "idePrecedent" : "no direct precedent — EK9-specific coalescing / elvis operators",
        "whenToUse" : "'Default name to \"Anonymous\" when unset' is one call with file, the LEFT operand as written (operand:'name'), operator:'?:' and rightOperand:'\"Anonymous\"'. Use it to wrap an existing single-line expression in one of EK9's null-safe operators (?: <? >?) without hand-writing tokens.",
        "whenNotToUse" : [ "Inserting a whole new statement/expression rather than wrapping an existing operand - use ek9_insert_statements.", "Adding a set-check guard to an if/while/for/switch/try head (not an inline expression) — use ek9_apply_guard_pattern.", "Lifting an expression into a local rather than coalescing it — use ek9_extract_variable." ],
        "examples" : [ "Elvis default: {file:'greet.ek9', line:8, operand:'name', operator:'?:', rightOperand:'\"Anonymous\"'} → status:applied; `displayName <- name` becomes `displayName <- name ?: \"Anonymous\"`.", "Null-safe min: {file:'bounds.ek9', operand:'alpha', operator:'<?', rightOperand:'beta'} → `smaller <- alpha` becomes `smaller <- alpha <? beta`." ],
        "details" : "The splice is syntactic but gated by a semantic scratch-compile: `?:` works on any tri-state type, while `<?`/`>?` need the operand's type to define `<=>` (`_cmp`), so a bad pairing or type-incompatible rightOperand surfaces as status:'would-fail' with the compiler diagnostic, not a broken edit. A compound left operand (e.g. `a + b`) is parenthesised for unambiguous binding, as is a compound rightOperand (`count ?: (base + 1)`) and the whole new expression when the operand sits inside another operator (`x + (a <? c)`), so the value never silently changes. The operand text names its occurrence; when it is an operand on several lines and no line is given, the call is refused naming them. A declaration's LHS is an identifier, not an expression (status:'conflicts', kind 'selection-not-an-expression') — and V1 supports a single-line left operand only.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "endColumn" : {
              "description" : "1-based EXCLUSIVE column of the END of the span (an IDE selection). Omit it - pass the text instead.",
              "type" : "integer"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "Workspace-relative path to the .ek9 source file containing the expression.",
              "type" : "string"
            },
            "line" : {
              "description" : "Optional 1-based line of the operand. Needed only when the operand text is an operand on more than one line.",
              "type" : "integer"
            },
            "operand" : {
              "description" : "The LEFT operand exactly as written in the source ('name', 'a + b').",
              "type" : "string"
            },
            "operator" : {
              "description" : "The coalescing operator to insert: `?:` first SET - the one that defaults an unset value ('name ?: \"Anonymous\"'); `<?` min, `>?` max (the LEFT operand on a tie).",
              "enum" : [ "?:", "<?", ">?" ],
              "type" : "string"
            },
            "rightOperand" : {
              "description" : "The RIGHT operand expression as source text — e.g. a fallback literal (`\"Anonymous\"`), another variable (`y`), or a further chained expression (`b ?: c`). Spliced verbatim.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "startColumn" : {
              "description" : "1-based column of the START of the span on 'line' (an IDE selection). Omit it - pass the text instead.",
              "type" : "integer"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file", "operator", "rightOperand" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_surround_with",
        "description" : "Folds a contiguous line range under an if / if-else / try-catch / try-catch-finally / for-range / while head, re-indenting the block per EK9's brace-free rules; answers status and edits[] (one REPLACE carrying the head + the re-indented lines, one DELETE per absorbed line).",
        "idePrecedent" : "Surround With… (IntelliJ — Cmd+Alt+T)",
        "whenToUse" : "'Wrap those lines in an if on count > 0' is one call with file, fromText (the block's first line as written), toText (its last), surroundType:'if', condition:'count > 0' - the tool owns the re-indent. Use it on a contiguous range of WHOLE statements at one indent level that should run conditionally, loop, or be exception-handled.",
        "whenNotToUse" : [ "You want EK9's idiomatic guard form (<-, :=?) for a null/unset check, not a generic if wrap — use ek9_apply_guard_pattern.", "You want to factor the block into a named method rather than wrap it — use ek9_extract_method.", "You want to convert an imperative loop into a stream pipeline — use ek9_advise_loop.", "You want to wrap a single expression in a coalescing/elvis operator — use ek9_insert_coalescing." ],
        "examples" : [ "{file:'orders.ek9', fromText:'total <- price * qty', toText:'stdout.println($total)', surroundType:'if', condition:'count > 0', dryRun:true} → status:ready; one multi-line REPLACE folding the range under `if count > 0` + re-indented lines, plus a DELETE per absorbed line.", "{file:'io.ek9', startLine:12, endLine:18, surroundType:'try-catch-finally', exceptionType:'IOException'} → status:applied; try/catch/finally scaffold whose handler is `-> ex as IOException`." ],
        "details" : "Head text you pass (condition for if/if-else/while, rangeExpression for for-range, exceptionType/exceptionVariable for try-catch) is parsed alone first - a text that does not parse is status:error with nothing written - then emitted verbatim. An omitted head is an opaque placeholder for the caller to edit: `if` inserts `surroundGuard <- Boolean(true)` then `if surroundGuard? //AI:`, `while` inserts `loopGuard <- Boolean(false)` then `while loopGuard //AI:`, `for-range` inserts `for i in 1 ... 10 //AI:` (a bare `true` would trip E07390 pointless-condition). Wrapped lines are re-indented +2 spaces; the head line(s) sit at the block's current indent; a line range outside the file or endLine < startLine is a hard error.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "condition" : {
              "description" : "if / if-else / while: the condition source text (e.g. 'count > 0', 'name?'), emitted verbatim. Omitted: an opaque placeholder guard with an //AI: marker to replace.",
              "type" : "string"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "endLine" : {
              "description" : "Or the last line of the selection (1-based, inclusive).",
              "type" : "integer"
            },
            "exceptionType" : {
              "description" : "try-catch / try-catch-finally: the caught type (default Exception).",
              "type" : "string"
            },
            "exceptionVariable" : {
              "description" : "try-catch / try-catch-finally: the handler's variable name (default ex).",
              "type" : "string"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "Workspace-relative path to the .ek9 source file containing the lines to wrap.",
              "type" : "string"
            },
            "fromText" : {
              "description" : "The block's FIRST line exactly as written (indentation ignored) - instead of counting startLine. Must name one line; add more of it when it occurs twice.",
              "type" : "string"
            },
            "rangeExpression" : {
              "description" : "for-range: the text after 'for i in' (e.g. '1 ... 10', 'items'), or a whole 'item in items' clause. Omitted: 'for i in 1 ... 10 //AI: Replace range'.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "startLine" : {
              "description" : "Or the first line of the selection (1-based) - an IDE selection.",
              "type" : "integer"
            },
            "surroundType" : {
              "description" : "The wrapper: if, if-else, try-catch, try-catch-finally, for-range or while.",
              "enum" : [ "if", "if-else", "try-catch", "try-catch-finally", "for-range", "while" ],
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "toText" : {
              "description" : "The block's LAST line as written - the first line matching it at or after fromText. Omit it for a one-line block.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file", "surroundType" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_move_construct",
        "description" : "Relocates one top-level construct to another .ek9 file, rewriting every workspace 'references' entry when the move changes module; answers status, edits[] (source DELETE, target INSERT, one rewrite per referring file) and conflicts[].",
        "idePrecedent" : "Move Class (IntelliJ)",
        "whenToUse" : "'Move CustomerOrder into shared/order.ek9' is one call with symbol:'CustomerOrder', targetFile:'shared/order.ek9' - no source file, line or mode needed (the mode is derived from the two files' 'defines module' lines). Use it to relocate ONE top-level class/record/trait/function/component to another .ek9 file.",
        "whenNotToUse" : [ "Renaming the construct (name unchanged by a move) - use ek9_rename_symbol; combine by moving first, then renaming.", "Renaming the .ek9 FILE while keeping its constructs in place — use ek9_rename_file.", "Only wanting to KNOW where the construct is referenced (not move it) — use ek9_query_references; run it first to gauge cross-module blast radius.", "Changing a construct's visibility/openness rather than its file — use ek9_change_visibility / ek9_make_open." ],
        "examples" : [ "same module: {symbol:'CustomerOrder', targetFile:'orders/order_audit.ek9'} → status:applied; exactly two edits (source removal + target insertion), no reference rewrites (FQN unchanged).", "cross-module preview: {symbol:'CustomerOrder', targetFile:'shared/order.ek9', dryRun:true} → status:ready with source + target + one `references ...::CustomerOrder` rewrite per referring file." ],
        "details" : "Module membership is set by the target file's 'defines module' line, NOT by directory layout; the tool reads both files' declarations to derive same-module vs cross-module, and rejects a sent mode that mismatches the actual pair. Same module keeps the FQN and yields two edits only; cross-module changes the FQN and atomically rewrites every `references oldModule::Thing` edge workspace-wide. Conflict kinds: 'name-collision-in-module' (mechanical — pick another target or rename first); 'construct-references-not-resolvable-in-new-module' and 'circular-dependency-introduced' (escalation — restructure required); anything the up-front checks miss is caught as would-fail by the scratch-compile gate.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The .ek9 file currently containing the construct - OPTIONAL when symbol is a construct name (the tool finds it workspace-wide).",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "mode" : {
              "description" : "Optional - derived from the two files' 'defines module' lines: same-module (relocation) or cross-module (relocation + workspace-wide reference rewriting). A sent value that contradicts the files is rejected.",
              "enum" : [ "same-module", "cross-module" ],
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "sourceFile" : {
              "description" : "Alias of file (the file currently containing the construct).",
              "type" : "string"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "targetFile" : {
              "description" : "Destination: workspace-relative path the construct should move to. Must already exist; its 'defines module' declaration determines the target module.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "targetFile" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_pull_up",
        "description" : "Moves the named methods/operators from a subclass into its superclass, dropping 'override' on the moved declarations; answers status and edits[] (one DELETE per member + one INSERT on the superclass).",
        "idePrecedent" : "Pull Members Up (IntelliJ)",
        "whenToUse" : "'Move describe() from Circle up to its superclass' is one call with symbol:'Circle', members:['describe'] - no file or line needed; the superclass comes from the 'extends' clause. Use it when a subclass member belongs on the parent (siblings duplicate it, or the abstraction was authored one level too low).",
        "whenNotToUse" : [ "The OPPOSITE direction - demote a member from the parent into subclasses - use ek9_push_down.", "The target trait/superclass does not exist yet and you want it created + members moved in one shot — use ek9_extract_trait.", "Adding an implementation for an abstract trait method onto a class (opposite direction) — use ek9_implement_methods.", "Renaming a member rather than relocating it — use ek9_rename_symbol." ],
        "examples" : [ "{symbol:'Circle', members:['describe'], dryRun:true} → status:ready; a DELETE edit on the subclass declaration + an INSERT on the superclass, override dropped.", "{symbol:'Circle', members:['describe', '<=>']} → operators named by symbol form; one DELETE per member, a single combined INSERT on the superclass." ],
        "details" : "V1: to the superclass only, with subclass and superclass declared in the SAME file — a trait target, cross-file targets and fields are follow-ups and return status:error today. The superclass must already be 'as open' / 'as abstract'; this tool does not add it — chain ek9_make_open first. An unopened superclass (E05030), a member referencing subclass-only state, a name collision on the superclass, or a sibling now shadowing without override (E05120) are caught by the scratch-compile would-fail gate.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "members" : {
              "description" : "Names of methods/operators on the subclass to pull up. Operators use their symbol form (e.g. '==', '<=>').",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "superclass" : {
              "description" : "Optional — the superclass name. When omitted it is resolved from the subclass's 'extends' clause; when supplied it is validated against that superclass.",
              "type" : "string"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "members" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_push_down",
        "description" : "Removes the named methods/operators from a superclass and copies them into each named direct subclass; answers status, edits[] (one DELETE + one INSERT per subclass) and conflicts[] (subclass-not-direct, target-is-only-source-class).",
        "idePrecedent" : "Push Members Down (IntelliJ)",
        "whenToUse" : "'Move fly() from Shape down into Eagle and Sparrow' is one call with symbol:'Shape', members:['fly'], subclasses:['Eagle','Sparrow'] - no file or line needed. Use it when a superclass member is only needed by some direct subclasses (the abstraction was over-promoted).",
        "whenNotToUse" : [ "Promoting a member UP from subclasses to the superclass (the opposite) - use ek9_pull_up.", "Removing the member from the hierarchy entirely, with no subclass needing it - use ek9_safe_delete (also the fix when the source class has no subclasses).", "The fix is access-level, not relocation (e.g. make it private/internal) - use ek9_change_visibility.", "Traits, not classes - traits are behaviour contracts with implementors, not subclasses; edit the trait directly and use ek9_query_references to find consumers." ],
        "examples" : [ "{symbol:'Shape', members:['fly','wingSpan'], subclasses:['Eagle','Sparrow'], dryRun:true} -> status:ready; REMOVE on Shape + INSERT on Eagle & Sparrow (override dropped).", "subclasses:[] -> status:error (empty); a non-direct/unknown subclass -> status:conflicts (subclass-not-direct)." ],
        "details" : "Class hierarchies ONLY, methods/operators ONLY (fields not yet supported); each named DIRECT subclass gets an identical copy of every member (no per-subclass body customisation). 'override' is DROPPED from copies by default (no parent left to override, else E05100); set keepAsOverride:true ONLY for multi-level hierarchies where an ancestor above the source still declares the member. status:conflicts kinds: subclass-not-direct, target-is-only-source-class; external callers reaching the member via the superclass type surface as status:would-fail from the scratch compile.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "keepAsOverride" : {
              "default" : false,
              "description" : "Keep the 'override' modifier on the subclass copies (only for multi-level hierarchies where an ancestor above the superclass still declares the member).",
              "type" : "boolean"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "members" : {
              "description" : "Names of methods/operators on the superclass to push down. Operators use their symbol form (e.g. '==', '<=>').",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "subclasses" : {
              "description" : "Names of DIRECT subclasses to receive the pushed-down members. Empty errors.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "symbol" : {
              "description" : "A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "members", "subclasses" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_extract_trait",
        "description" : "Appends a new 'defines trait' block declaring the chosen methods as abstract, adds 'with trait of <Name>' to the class header and 'override' to each method; answers status, edits[] and summary, a non-extractable member as conflicts[] (member-not-extractable-to-trait).",
        "idePrecedent" : "Extract Interface (IntelliJ / Eclipse)",
        "whenToUse" : "'Pull validate out of CustomerOrder into a Validatable trait' is one call with symbol:'CustomerOrder', members:['validate'] and name:'Validatable'.",
        "whenNotToUse" : [ "Extracting a CODE BLOCK into a method on the SAME class, not a method into a trait - use ek9_extract_method.", "The trait already EXISTS and you want to add its implementation on a class that declares it - use ek9_implement_methods.", "The class should DELEGATE to a trait via a field ('with trait of T by field'), not BE the impl - use ek9_generate_delegation.", "Promoting a method to a SUPERCLASS rather than a separate trait - use ek9_pull_up.", "The member is private/protected - traits are public-only (E07270); raise visibility first with ek9_change_visibility, or keep it on the class." ],
        "examples" : [ "{symbol:'CustomerOrder', members:['validate','validateBatch'], name:'Validatable', dryRun:true} → status:ready; new 'defines trait' block + 'with trait of Validatable' on the header + 'override' on each method.", "{symbol:'CustomerOrder', members:['validate'], name:'Pricing'} where a construct named Pricing already exists in the file → status:conflicts (name-collision-with-existing-trait)." ],
        "details" : "Class targets only - records/components/functions error. A named method that is not a public, non-operator method of the class (not found, private/protected, an operator, or a field per E07075) returns status:'conflicts' (kind 'member-not-extractable-to-trait'), not an error. 'override' is always prepended on the class per E05120 - no opt-out; V1 appends the new 'defines trait' block to the SAME file, declaring the methods 'as abstract' (the class keeps its bodies).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site.",
              "type" : "string"
            },
            "line" : {
              "description" : "1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true).",
              "type" : "integer"
            },
            "members" : {
              "description" : "Names of PUBLIC methods on the class to extract into the trait.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "name" : {
              "description" : "The new trait's identifier (PascalCase by convention). Must not already exist in the file.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "symbol" : {
              "description" : "The class whose methods move to the trait - a bare name ('CustomerOrder') finds its file and declaration workspace-wide; 'mod::CustomerOrder' when the name is declared in two modules.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "symbol", "members", "name" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_toggle_block_comment",
        "description" : "Wraps a whole-line range in EK9 block-comment markers (<!- ... -!>) or removes them when the range already carries them, never nesting; answers status and edits[] (two INSERTs or two DELETEs).",
        "idePrecedent" : "Comment with Block Comment (IntelliJ)",
        "whenToUse" : "'Comment out lines 42-50 of orders.ek9' is one call with file, startLine:42, endLine:50; re-select INCLUDING the marker lines to uncomment. Use it to TEMPORARILY exclude whole lines from compilation - debug bisection, staged rollout, scratch code.",
        "whenNotToUse" : [ "PERMANENTLY removing a construct - use ek9_safe_delete (commenting-out is wrong unless truly temporary).", "Commenting a single line or a partial line — use a // line comment via ek9_edit_file; this tool wraps WHOLE lines only." ],
        "examples" : [ "Comment lines 42-50: {file:'orders/customer_order.ek9', startLine:42, endLine:50} → two INSERT edits: <!- above line 42, -!> below line 50, both indented to match line 42.", "Uncomment: re-select INCLUDING the marker lines — after the above, {startLine:42, endLine:52} → two DELETE edits removing the <!- and -!> lines." ],
        "details" : "Wraps whole lines startLine..endLine in the EK9-distinctive <!- … -!> form (not the HTML <!-- … --> nor the reserved documentation <?- … -?>), each marker on its own line indented to match the first line of the range. Refuses to nest: if any line in the range begins with a marker from any of the three block-comment forms it returns status:error ('Cannot comment: line N contains a block comment marker'); // line comments inside the range are fine. Invalid range (endLine<startLine, startLine<1, or endLine past EOF) returns status:error 'Invalid line range: S-E'.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "endLine" : {
              "description" : "Last line of the range to toggle (1-based, inclusive). Must be >= startLine.",
              "type" : "integer"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "Workspace-relative path to the .ek9 source file.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "startLine" : {
              "description" : "First line of the range to toggle (1-based, inclusive).",
              "type" : "integer"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file", "startLine", "endLine" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_format",
        "description" : "Rewrites the file in canonical EK9 style (indentation, spacing, blank-line and line-break conventions); answers status, edits[] (one per changed line) and summary, an unparseable file as conflicts[] (source-not-parseable).",
        "idePrecedent" : "Reformat Code (IntelliJ Ctrl+Alt+L)",
        "whenToUse" : "'Reformat customer.ek9 to canonical style' is one call with file - the whole file is re-rendered, never a region, and nothing the code does changes.",
        "whenNotToUse" : [ "Checking whether the code is VALID (types/references/errors), not just styled - use ek9_compile; format is structure only.", "Asking what is wrong (compile errors / warnings) - use ek9_query_diagnostics.", "Targeted find/replace edits to specific lines rather than a whole-file re-render - use ek9_edit_file." ],
        "examples" : [ "{file:'src/main/com/acme/customer.ek9'} → one edit per changed line; an already-canonical file returns edits:[] with an 'already canonical' summary.", "A tab-indented file, or '-> a as Integer, b as String': repaired first (corrected[] names each repair), then formatted.", "A syntax error with more than one reading: {file:'broken.ek9'} → status:conflicts, kind 'source-not-parseable' and no write - chain ek9_compile for the formal diagnostic." ],
        "details" : "Deterministic and idempotent: format-twice yields byte-identical output. edits[] carries one REPLACE per changed line, INSERT per added line, DELETE per removed line, and doubles as a training signal for canonical EK9 style. A file that does not parse is repaired first where each fault has exactly one reading (tabs, indent width, shebang, comma-separated parameters, ...), each repair in corrected[]. The only conflict kind is 'source-not-parseable' (a syntax error with no one-reading repair - resolve via ek9_compile first).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The .ek9 source file to format (workspace-relative; an absolute path inside the workspace is accepted).",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_edit_localized_text",
        "description" : "Rewrites the `defines text for \"<locale>\"` blocks of one text type in every locale that defines it (one atomic edit, so E07150 locale-completeness holds) and answers the editor envelope: status (applied | ready | would-fail | conflicts | error), written, edits[], summary, and translationDebt[] naming each {textType, locale, method} left placeholder-stubbed.",
        "idePrecedent" : "NEW IDE surface — JavaFX dialog for create-text-type, right-click context menu inside `defines text for` blocks",
        "whenToUse" : "'Add / change / remove a translatable text method or locale' is one call: pick mode (create-text-type, add-text-method, add-locale, update-text-method, remove-text-method, remove-locale) and name the textTypeName.",
        "whenNotToUse" : [ "Promoting a hard-coded string literal in EK9 source into a text method - use ek9_extract_to_localized_text (it rewrites the call site too).", "Authoring a `defines class`/`record`/etc. - text is a distinct construct kind; use ek9_new_construct instead.", "A regular method on a class/trait/component — text methods are localized-string bodies, structurally different; use ek9_add_method instead.", "Changing an existing translated method's SIGNATURE — update-text-method rejects it (signature-change-not-allowed); do remove-text-method + add-text-method instead (ek9_change_signature cascades over type hierarchies, not locale groups).", "Non-uniform locale coverage (one locale needs different params) — EK9 rejects this via E07150; author two separate text types by hand instead." ],
        "examples" : [ "{mode:'add-text-method', textTypeName:'WelcomePageText', methodName:'farewell', parameters:['name as String'], methodBodies:{en_GB:'`Goodbye ${name}`', de:'`Auf Wiedersehen ${name}`'}} → inserts the method atomically into both locale blocks.", "{mode:'create-text-type', targetFile:'i18n/welcome.ek9', textTypeName:'WelcomePageText', primaryLocale:'en_GB', supportedLocales:['en_GB','de'], methods:[...]} → de gets placeholder-stubbed bodies listed in translationDebt[]." ],
        "details" : "Enforcement of per-text-type locale completeness is the distinguishing trait: adding a method to one locale without the others fires E07150, so create / add-text-method / add-locale / remove-text-method succeed across every locale defining the text type or block entirely; update-text-method is the only per-locale-subset mode (signature immutable); remove-locale is module-wide, blocking on literal-locale constructor calls and on would-orphan-text-type (escalation — last defining locale). Text methods can overload - when methodName matches >1 overload you MUST supply parameters or it blocks with method-ambiguous-supply-params (candidateOverloads[] lists signatures); locale identifiers must match [a-z]+(_[A-Z]+)?. Auto-stubbed bodies emit a two-line placeholder (`//AI: translate the message` + empty `\"\"`) surfaced in translationDebt[], and update-text-method rewrites the whole method block, discarding in-body comments.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "includeTextTypes" : {
              "description" : "For add-locale: limit the clone to these text types; omit for every text type referenceLocale defines.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "locale" : {
              "description" : "For remove-locale: the locale code to discontinue module-wide (e.g. fr, fr_FR, ja_JP) - every `defines text for \"<locale>\"` block matching it goes.",
              "type" : "string"
            },
            "methodBodies" : {
              "additionalProperties" : {
                "type" : "string"
              },
              "description" : "For add-text-method and update-text-method: locale -> body, e.g. {en_GB:'`Goodbye ${name}`', de:'`Auf Wiedersehen ${name}`'}; add-text-method stubs any locale left out, update-text-method leaves it alone.",
              "type" : "object"
            },
            "methodName" : {
              "description" : "For add-text-method: the new method's name; for update-text-method and remove-text-method: the existing method (supply parameters when the name is overloaded).",
              "type" : "string"
            },
            "methods" : {
              "description" : "For create-text-type: the text methods to author, each {name, parameters, body}.",
              "items" : {
                "properties" : {
                  "body" : {
                    "description" : "The primary locale's body, e.g. '`Hello ${name}`'.",
                    "type" : "string"
                  },
                  "name" : {
                    "description" : "The text method's name.",
                    "type" : "string"
                  },
                  "parameters" : {
                    "description" : "Its parameters, 'name as Type' each; omit for none.",
                    "items" : {
                      "anyOf" : [ {
                        "properties" : {
                          "name" : {
                            "description" : "The parameter's name.",
                            "type" : "string"
                          },
                          "type" : {
                            "description" : "Its type, e.g. 'Integer', 'List of String' - the TYPE only, no arrows.",
                            "type" : "string"
                          }
                        },
                        "required" : [ "name", "type" ],
                        "type" : "object"
                      }, {
                        "description" : "Also accepted: the EK9 text 'name as Type' - but send the object; the tool writes the '->' block, its indentation and its order.",
                        "type" : "string"
                      } ]
                    },
                    "type" : "array"
                  }
                },
                "required" : [ "name", "body" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "mode" : {
              "description" : "Which operation to perform.",
              "enum" : [ "create-text-type", "add-text-method", "add-locale", "update-text-method", "remove-text-method", "remove-locale" ],
              "type" : "string"
            },
            "module" : {
              "description" : "The module to operate on. Default: the first module that defines any text type; supply it when several modules carry i18n. Derived from targetFile when that is given.",
              "type" : "string"
            },
            "newLocale" : {
              "description" : "For add-locale: the locale to bring up (e.g. fr_FR) - every text type in referenceLocale gets a block, placeholder-stubbed.",
              "type" : "string"
            },
            "parameters" : {
              "description" : "For add-text-method: the new method's parameters, 'name as Type' each; for update/remove-text-method: the overload to pick when methodName is ambiguous.",
              "items" : {
                "anyOf" : [ {
                  "properties" : {
                    "name" : {
                      "description" : "The parameter's name.",
                      "type" : "string"
                    },
                    "type" : {
                      "description" : "Its type, e.g. 'Integer', 'List of String' - the TYPE only, no arrows.",
                      "type" : "string"
                    }
                  },
                  "required" : [ "name", "type" ],
                  "type" : "object"
                }, {
                  "description" : "Also accepted: the EK9 text 'name as Type' - but send the object; the tool writes the '->' block, its indentation and its order.",
                  "type" : "string"
                } ]
              },
              "type" : "array"
            },
            "primaryLocale" : {
              "description" : "For create-text-type: the locale whose bodies are authored (e.g. en_GB); every other supportedLocale gets a placeholder-stubbed body listed in translationDebt[].",
              "type" : "string"
            },
            "referenceLocale" : {
              "description" : "For add-locale: the existing locale whose text types and method signatures the new locale clones.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "supportedLocales" : {
              "description" : "For create-text-type: every locale to define the text type in, primaryLocale included (e.g. ['en_GB','de']).",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "targetFile" : {
              "description" : "Destination: the workspace-relative file the new text type lands in - required for create-text-type when no locale block exists yet. Optional for every other mode (they are workspace-wide and find the existing blocks through the module catalogue); supply it, or `module`, when the workspace has more than one module.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "textTypeName" : {
              "description" : "The text type (PascalCase) - the one to create for create-text-type, the existing one for add/update/remove-text-method; for add-locale and remove-locale see includeTextTypes / locale.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "mode" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_extract_to_localized_text",
        "description" : "Replaces the selected string literal with `<localeAccessor>.<name>(args)` and adds the text method to every locale of the i18n file in one atomic changeset, answering the editor envelope: status (applied | ready | would-fail | conflicts | error), written, edits[], summary and translationDebt[] naming the locales left placeholder-stubbed.",
        "idePrecedent" : "NEW IDE surface — Right-click on a selected string literal (double-quoted or backtick-interpolation) inside an EK9 source file → \"Extract to Localized Text…\"",
        "whenToUse" : "'Move this hard-coded user-facing string into the i18n text type' is one call: {file, startLine, startColumn, endLine, endColumn (1-based) over the literal, textTypeName, name, localeAccessor}.",
        "whenNotToUse" : [ "The string is already in the i18n surface and you want to change a body - use ek9_edit_localized_text (update-text-method).", "Greenfield i18n authoring with no source call-site to rewrite — use ek9_edit_localized_text (add-text-method / create-text-type).", "The selection is a `+`-style concatenation (\"a\" + var + \"b\"), not a single literal — run ek9_rewrite_string_concat first to make it a backtick interpolation, THEN re-run this tool on the rewritten range.", "The string is not user-facing (debug log, internal diagnostic, assert message) — leave it." ],
        "examples" : [ "New text type, primary locale: {file:'app/Main.ek9', startLine:4, startColumn:20, endLine:4, endColumn:47, textTypeName:'AppText', name:'mainWelcome', localeAccessor:'appText', i18nFile:'i18n/text.ek9', primaryLocale:'en_GB', dryRun:true} → status:ready; source rewrite + create-text-type edit.", "Existing 3-locale type, interpolation: the span over `Hello ${person.firstName}, you have ${items.size()} items` + name + localeAccessor → status:applied; parameters inferred (person, count) and the method added atomically across all 3 locales, non-primary stubbed into translationDebt[]." ],
        "details" : "Parameter inference from `${...}`: simple ref → symbol name+type; property access (${p.x}) → receiver name+type; method call (${items.size()}) → return-type heuristic ('count'), receiver-name fallback - a parameters override must match the inferred COUNT and ORDER (types can widen, names can differ). textTypeName discovery is cross-module — resolved through the module's references block, so a centralized text type is found and extended, not duplicated. Conflicts are all mechanical and caller-repairable — notably selection-is-concat-not-literal (run ek9_rewrite_string_concat first), no-text-type-instance-in-scope, and i18n-file-supplied-for-existing-text-type (drop i18nFile — the existing block locations govern); when the type already spans multiple locales an omitted primaryLocale silently defaults to the first locale, so supply it. translationDebt[] lists the non-primary locales stubbed with the primary body plus an //AI: translate directive.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "endColumn" : {
              "description" : "1-based column of the literal's closing quote or backtick, inclusive.",
              "type" : "integer"
            },
            "endLine" : {
              "description" : "1-based line the literal ends on (the same line for a one-line literal).",
              "type" : "integer"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The .ek9 source file holding the literal - workspace-relative (absolute inside the workspace accepted).",
              "type" : "string"
            },
            "i18nFile" : {
              "description" : "Destination: required when textTypeName does NOT yet exist in the module. Workspace-relative path where the new text type lands.",
              "type" : "string"
            },
            "localeAccessor" : {
              "description" : "Name of an existing variable in scope at the selection site whose type is the target text type. The rewritten call site becomes `<localeAccessor>.<name>(<args>)`.",
              "type" : "string"
            },
            "name" : {
              "description" : "Name for the new text method.",
              "type" : "string"
            },
            "parameters" : {
              "description" : "Optional override of the auto-inferred parameter signature. Supplied parameters must match the inferred count AND order; names can differ; types can widen.",
              "items" : {
                "anyOf" : [ {
                  "properties" : {
                    "name" : {
                      "description" : "The parameter's name.",
                      "type" : "string"
                    },
                    "type" : {
                      "description" : "Its type, e.g. 'Integer', 'List of String' - the TYPE only, no arrows.",
                      "type" : "string"
                    }
                  },
                  "required" : [ "name", "type" ],
                  "type" : "object"
                }, {
                  "description" : "Also accepted: the EK9 text 'name as Type' - but send the object; the tool writes the '->' block, its indentation and its order.",
                  "type" : "string"
                } ]
              },
              "type" : "array"
            },
            "primaryLocale" : {
              "description" : "Required when textTypeName does NOT yet exist. Strongly recommended whenever the text type already exists in multiple locales.",
              "type" : "string"
            },
            "selection" : {
              "description" : "IDE alias for the four flat keys: {startLine, startCol, endLine, endCol}, 1-based inclusive.",
              "properties" : {
                "endCol" : {
                  "type" : "integer"
                },
                "endLine" : {
                  "type" : "integer"
                },
                "startCol" : {
                  "type" : "integer"
                },
                "startLine" : {
                  "type" : "integer"
                }
              },
              "type" : "object"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "startColumn" : {
              "description" : "1-based column of the literal's opening quote or backtick, inclusive.",
              "type" : "integer"
            },
            "startLine" : {
              "description" : "1-based line the string literal starts on.",
              "type" : "integer"
            },
            "supportedLocales" : {
              "description" : "Optional for new text type creation.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "textTypeName" : {
              "description" : "Target text type (PascalCase). If it exists in the module, the method is added via add-text-method semantics internally. If it does not exist, the tool creates it.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file", "textTypeName", "name", "localeAccessor" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_organize_localized_text",
        "description" : "Moves the `defines text for \"<locale>\"` blocks between files to the chosen layout - block content preserved verbatim, donors kept as residual stubs, never deleted - and answers the editor envelope: status (applied | ready | conflicts | error), written, edits[] and summary; an already-canonical layout is applied with empty edits[].",
        "idePrecedent" : "NEW IDE surface — Build menu → \"Organize Localized Text…\" wizard (mode picker, preview pane, conflict resolver). Pairs with the Translation Completeness panel.",
        "whenToUse" : "'Re-lay the module's i18n files' is one call: mode split-by-locale (one file per language), split-by-text-type (one per text type) or consolidate (targetFile) - content untouched.",
        "whenNotToUse" : [ "Adding / removing / editing text types, methods or locale bodies - this only MOVES existing blocks; use ek9_edit_localized_text instead.", "Promoting a hard-coded string literal in EK9 source into a text method (source -> i18n) — use ek9_extract_to_localized_text instead.", "Feature-area or per-subdirectory layouts beyond the three fixed modes — organise by hand; the modes are deliberately limited.", "Deleting a residual donor stub after relocation — this tool never deletes donors; use ek9_delete_file." ],
        "examples" : [ "{mode:'split-by-locale', dryRun:true} → status:ready; edits create en_GB.ek9 / de.ek9 / fr_FR.ek9 and excise the moved blocks from the donor, which stays on disk.", "{mode:'consolidate', targetFile:'src/main/i18n/text.ek9'} → status:applied; all blocks collapse into text.ek9, donors left as residual stubs. Re-running same mode on a canonical layout → status:applied with empty edits (idempotent no-op)." ],
        "details" : "Semantics-preserving by construction: EK9 resolves text types by module-scoped class name, not file path, so no call sites break and no reference sync is needed. Every apply canonicalises ordering (locales then text types, alphabetical), so the FIRST apply on a correctly-shaped but unordered layout is a normalisation pass (non-empty edits); subsequent applies are true no-ops. Donor files are RETAINED with block ranges excised (comments, commented-out code and live constructs survive in the residual); conflict kinds are target-file-exists (mechanical: rename clash or pick different targetFile), module-name-mismatch (fix source typo or scope via `module`), discovery-spans-multiple-directories (caller picks a directory) and no-localized-text-found.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "mode" : {
              "description" : "Layout target. split-by-locale -> <locale>.ek9 per language; split-by-text-type -> <TextTypeName>.ek9 per text type; consolidate -> the single targetFile.",
              "enum" : [ "split-by-locale", "split-by-text-type", "consolidate" ],
              "type" : "string"
            },
            "module" : {
              "description" : "The module to operate on. Default: the first module that defines any text type; supply it when several modules carry i18n.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "targetFile" : {
              "description" : "Destination: the single output file (workspace-relative), required for consolidate; REJECTED for the split modes, whose outputs are deterministically named.",
              "type" : "string"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "mode" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_organize_references",
        "description" : "Deletes the unused entries of one file's 'references' block (usage judged by a scratch compile, so functions, types and name-collisions are all right) and answers the editor envelope: status (applied | ready | error), written, edits[] - one delete per unused line - and summary; an already-clean file is applied with empty edits[].",
        "idePrecedent" : "Optimize Imports (IntelliJ) / Remove Unused Imports",
        "whenToUse" : "'Remove the references this file no longer uses' is one call: {file:'x.ek9'} - the remove-only half of ek9_sync_references (its addMissing:false).",
        "whenNotToUse" : [ "Adding the MISSING references too, or repairing an unresolved symbol - use ek9_sync_references (this tool is ek9_sync_references with addMissing:false).", "Sorting / reordering the references block - use ek9_format (the formatter owns layout).", "Renaming a referenced symbol everywhere - use ek9_rename_symbol." ],
        "examples" : [ "{file:'src/main/com/example/main.ek9'} → edits[] one DELETE per unused reference line (e.g. org.unused::Foo).", "Already clean (no block / empty block / all in use): {file:'main.ek9'} → empty edits[] — a graceful no-op, not an error." ],
        "details" : "Detection is compiler-driven and cascade-safe: a scratch compile with the whole block deleted pre-filters the definitely-used names, then each remaining candidate is confirmed by deleting only its line and checking the error count doesn't rise (a cascade-masked-but-used reference is kept). Immune to case conventions and name collisions; degrades to a lexer name-heuristic (over-retains, never wrongly deletes) when no scratch compile can run. Never returns status:'conflicts'; interleaved comments and kept entries are preserved exactly (targeted per-line delete).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The .ek9 source file whose references block to trim - workspace-relative (absolute inside the workspace accepted).",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_sync_references",
        "description" : "Rewrites one file's 'references' block - adding the module::Name entry for each cross-module symbol the file uses but has not declared (E50001/E50010) and deleting each entry nothing uses - and answers the editor envelope: status (applied | ready | conflicts | error), written, edits[] and summary ('Added N and removed M reference(s)').",
        "idePrecedent" : "Optimize Imports (IntelliJ) / Organize Imports (VS Code)",
        "whenToUse" : "'Fix this file's references block' (the EK9 Organize Imports) is one call: {file:'x.ek9'} adds every missing cross-module reference and removes every unused one.",
        "whenNotToUse" : [ "You only want to KNOW which symbols are unresolved without rewriting - use ek9_query_diagnostics.", "You want to just reformat whitespace/indent, not change references semantics — use ek9_format.", "You're moving a construct between modules (it rewrites references as part of the move) — use ek9_move_construct (mode=cross-module).", "You want to consolidate a shared reference set across many files into one shared file — that is workspace-wide, not this single-file tool." ],
        "examples" : [ "{file:'orders/order_processor.ek9', dryRun:true} → status:ready; edits add com.acme.orders.events::OrderEvent and remove the unused com.acme.legacy::DeprecatedHelper.", "{file:'x.ek9', addMissing:false, removeUnused:false} → status:error (nothing to sync)." ],
        "details" : "Entries are symbol-level (module.path::Identifier) — no module wildcards; disable addMissing or removeUnused for a surgical one-sided sync. Add-detection is compiler-driven (file-scoped scratch compile, iterated to a fixpoint since EK9 surfaces unresolved names across several phases). A name resolving to more than one module is surfaced as status:conflicts (kind auto-added-reference-creates-ambiguity — fully-qualify the use site, then retry) rather than guessed; a stale entry whose symbol no longer exists is simply removed as unused.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "addMissing" : {
              "default" : true,
              "description" : "Add references for cross-module symbols the file uses but doesn't declare. Default true.",
              "type" : "boolean"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The .ek9 source file whose references block to sync - workspace-relative (absolute inside the workspace accepted). The file must have compiled to FULL_RESOLUTION or beyond.",
              "type" : "string"
            },
            "removeUnused" : {
              "default" : true,
              "description" : "Remove references nothing in the file uses. Default true.",
              "type" : "boolean"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_add_dependency",
        "description" : "Answers with the edit that appends one runtime or dev dependency to the deps/devDeps Dict of a 'defines package' block - source only, nothing is fetched.",
        "idePrecedent" : "npm install / cargo add / mvn add-dependency",
        "whenToUse" : "'Add a dependency on ekopen.net.handy.tools 3.2.1-0' is one call with file:'package.ek9', bucket:'deps', module:'ekopen.net.handy.tools', version:'3.2.1-0'. Use bucket:'devDeps' for a test-only dependency.",
        "whenNotToUse" : [ "Removing a dependency - use ek9_remove_dependency.", "Blocking a TRANSITIVE dependency that a parent pulls in - use ek9_exclude_transitive_dependency.", "Declaring a security capability (a permission, not a library) - use ek9_add_capability.", "Fetching the declared dependencies afterwards - ek9_resolve_dependencies (this tool edits the source only).", "The 'defines package' block does not exist yet - ek9_new_construct with mode:'package' first." ],
        "examples" : [ "{file:'package.ek9', bucket:'deps', module:'ekopen.network.support.utils', version:'1.6.1-9', dryRun:true} -> status:ready with the insert edit.", "{file:'package.ek9', bucket:'devDeps', module:'ekopen.testing.mockery', version:'0.3.2-0'} -> status:applied, appended to devDeps (created if absent)." ],
        "details" : "bucket selects deps or devDeps and the Dict is created when absent; only the multi-line Dict form is edited (a single-line bucket is error 'single-line-bucket-unsupported'). conflicts[].kind: 'dependency-already-pinned' (same module, same version - a no-op) or 'version-conflict' (same module, different version - ask the human). A file with no 'defines package' block is error 'package-block-not-found'; a version that is not major.minor.patch-build adds an advisories[] 'non-standard-version-format' but is written.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "bucket" : {
              "description" : "Which Dict to extend: 'deps' (runtime, deployed) or 'devDeps' (dev/test only, not deployed).",
              "enum" : [ "deps", "devDeps" ],
              "type" : "string"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The .ek9 file holding the 'defines package' block, workspace-relative (typically 'package.ek9'); an absolute path inside the workspace is accepted.",
              "type" : "string"
            },
            "module" : {
              "description" : "The dependency's module name, e.g. 'ekopen.network.support.utils' - the Dict key.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            },
            "version" : {
              "description" : "The version to pin, e.g. '1.6.1-9' (major.minor.patch-build; a feature-tagged major.minor.patch-feature-build is also accepted) - the Dict value.",
              "type" : "string"
            }
          },
          "required" : [ "file", "bucket", "module", "version" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_remove_dependency",
        "description" : "Answers with the edit that deletes one entry from the deps or devDeps Dict of a 'defines package' block (the whole Dict when it was the last entry).",
        "idePrecedent" : "npm uninstall / cargo remove / mvn dependency:remove",
        "whenToUse" : "'Drop the dependency on ekopen.net.handy.tools' is one call with file:'package.ek9', bucket:'deps' (or 'devDeps') and module:'ekopen.net.handy.tools'.",
        "whenNotToUse" : [ "Adding a dependency - use ek9_add_dependency.", "Lifting a transitive EXCLUSION - use ek9_remove_transitive_exclusion.", "Removing a capability declaration - use ek9_remove_capability.", "The module is a transitive, not a direct deps/devDeps entry - use ek9_exclude_transitive_dependency to stop the pull-in." ],
        "examples" : [ "{file:'package.ek9', bucket:'deps', module:'ekopen.network.support.utils'} -> status:applied; the entry's line is deleted (and the previous entry loses its now-dangling comma).", "{file:'package.ek9', bucket:'devDeps', module:'ekopen.testing.mockery'} where it is the sole entry -> status:applied, summary '... removed now-empty devDeps declaration'." ],
        "details" : "An emptied bucket's whole 'bucket <- { ... }' declaration is removed (EK9 forbids an empty Dict) and the summary says so - there is no flag either way. Only the multi-line Dict form is edited (a single-line bucket is error 'single-line-bucket-unsupported'); an unknown module conflicts with 'dependency-not-declared' listing the bucket's keys. Edits the source only - chain ek9_resolve_dependencies to clean the local cache; the dry run performs no scratch compile, so a reference-breaking removal is caught by the post-apply verify.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "bucket" : {
              "description" : "Which Dict to remove from: 'deps' (runtime, deployed) or 'devDeps' (dev/test only, not deployed).",
              "enum" : [ "deps", "devDeps" ],
              "type" : "string"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The .ek9 file holding the 'defines package' block, workspace-relative (typically 'package.ek9'); an absolute path inside the workspace is accepted.",
              "type" : "string"
            },
            "module" : {
              "description" : "The dependency's module name to remove, e.g. 'ekopen.network.support.utils' - matched against the Dict key.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file", "bucket", "module" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_exclude_transitive_dependency",
        "description" : "Answers with the edit that adds an excludeDeps entry (module -> viaModule) to a 'defines package' block, blocking a transitive dependency a parent pulls in.",
        "idePrecedent" : "maven exclusions / npm overrides",
        "whenToUse" : "'Block the transitive ekopen.bad.pack that ekopen.util pulls in' is one call with file:'package.ek9', module:'ekopen.bad.pack' and viaModule:'ekopen.util'.",
        "whenNotToUse" : [ "Declaring a direct runtime or dev dependency - use ek9_add_dependency.", "Lifting an existing exclusion - use ek9_remove_transitive_exclusion.", "The module is a DIRECT deps/devDeps entry, not a transitive - drop it with ek9_remove_dependency (this tool conflicts with 'direct-dependency-cannot-be-excluded-as-transitive').", "The 'defines package' block does not exist yet - ek9_new_construct with mode:'package' first." ],
        "examples" : [ "{file:'package.ek9', module:'ekopen.some.bad.dependency.pack', viaModule:'ekopen.org.supertools.util', dryRun:true} -> status:ready with the single excludeDeps insert.", "the same module already keyed in excludeDeps -> status:conflicts, kind 'exclusion-already-declared' - a no-op, do not retry." ],
        "details" : "The excludeDeps Dict is created when absent; module is the KEY and viaModule the VALUE (the parent it arrives through, never a version), so there is at most one exclusion per transitive - to re-route via a different parent, ek9_remove_transitive_exclusion then re-add. Never returns would-fail: the dry run skips the scratch compile because exclusion validity is a resolver concern. Edits the source only - chain ek9_resolve_dependencies (it cleans the cache) to purge the cached transitive.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The .ek9 file holding the 'defines package' block, workspace-relative (typically 'package.ek9'); an absolute path inside the workspace is accepted.",
              "type" : "string"
            },
            "module" : {
              "description" : "The transitive module to block, e.g. 'ekopen.some.bad.dependency.pack' - the excludeDeps Dict key.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            },
            "viaModule" : {
              "description" : "The parent module whose dependency on 'module' is blocked, e.g. 'ekopen.org.supertools.util' - the excludeDeps Dict value (a module name, not a version).",
              "type" : "string"
            }
          },
          "required" : [ "file", "module", "viaModule" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_remove_transitive_exclusion",
        "description" : "Answers with the edit that deletes one excludeDeps entry from a 'defines package' block, re-allowing a previously-blocked transitive dependency.",
        "idePrecedent" : "maven exclusion removal",
        "whenToUse" : "'Stop excluding ekopen.some.bad.pack' is one call with file:'package.ek9' and module:'ekopen.some.bad.pack'. For when the parent upgraded, or the transitive is wanted again.",
        "whenNotToUse" : [ "Adding an exclusion - use ek9_exclude_transitive_dependency.", "Removing a direct dependency from deps/devDeps - use ek9_remove_dependency." ],
        "examples" : [ "{file:'package.ek9', module:'ekopen.some.bad.dependency.pack', dryRun:true} -> status:ready with a one-line delete from the excludeDeps Dict.", "removing the LAST excludeDeps entry -> status:applied, summary '... removed now-empty excludeDeps declaration' (EK9 forbids an empty Dict literal)." ],
        "details" : "conflicts[].kind: 'exclusion-not-declared' (the description lists the existing keys) and 'dependency-is-direct-not-transitive' (use ek9_remove_dependency). error.message: 'package-block-not-found', 'excludeDeps-not-found' or 'single-line-bucket-unsupported' (only the multi-line Dict form is edited). An emptied Dict's whole declaration is removed and the summary says so. Removal affects future resolutions only - pair with ek9_resolve_dependencies.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The .ek9 file holding the 'defines package' block, workspace-relative (typically 'package.ek9'); an absolute path inside the workspace is accepted.",
              "type" : "string"
            },
            "module" : {
              "description" : "The previously-excluded transitive module, e.g. 'ekopen.some.bad.dependency.pack' - matched against the excludeDeps Dict key.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file", "module" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_add_capability",
        "description" : "Answers with the edit that declares one gated-type capability in the 'capabilities <- [...]' List of a 'defines package' block, plus the resolved capability name.",
        "idePrecedent" : "EK9 IDE - package-block right-click \"Add Capability...\" + the E12010 quick-fix. Concept: Android manifest permissions / iOS entitlements.",
        "whenToUse" : "'Declare that this package may use Stdout' is one call with file:'package.ek9' and capability:'Stdout'. The full 'org.ek9.lang::Stdout' is accepted too; this is the fix for E12010 UNDECLARED_CAPABILITY on a publishable package.",
        "whenNotToUse" : [ "Adding an external LIBRARY dependency (a library, not a permission) - use ek9_add_dependency.", "Revoking a capability - use ek9_remove_capability.", "The 'defines package' block does not exist yet - ek9_new_construct with mode:'package' first." ],
        "examples" : [ "{file:'package.ek9', capability:'Stdout', dryRun:true} -> status:ready, capability:'org.ek9.lang::Stdout', note:\"Resolved 'Stdout' -> 'org.ek9.lang::Stdout'\", one insert into the capabilities List.", "{file:'package.ek9', capability:'org.ek9.lang::TCP'} where TCP is already listed -> status:conflicts, kind 'capability-already-declared' (already permitted)." ],
        "details" : "capability is a gated type: the bare name ('TCP', 'Stdout', 'File') resolves to org.ek9.lang::<Name> and an associated type resolves to the capability that covers it (TCPConnection -> org.ek9.lang::TCP); the response's capability key is the spelling written. A bare name that is not a gated type is error 'unknown-capability' listing the pickable set; a missing package block is 'package-block-not-found'. The List is created in compact single-line form when absent - never as an empty '[]', which the grammar rejects. Enforcement (E12010, PRE_IR_CHECKS) applies only when publicAccess, version, license and capabilities are ALL declared; the dry run validates the identifier, it does not compile.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "capability" : {
              "description" : "The gated type to permit: a bare name such as 'TCP' or 'Stdout' (resolved to org.ek9.lang::<Name>) or the fully-qualified 'org.ek9.lang::TCP'. A primary capability covers its group (TCP covers TCPConnection/TCPHandler/NetworkProperties), so the related types need no entry of their own.",
              "type" : "string"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The .ek9 file holding the 'defines package' block, workspace-relative (typically 'package.ek9'); an absolute path inside the workspace is accepted.",
              "type" : "string"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file", "capability" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_remove_capability",
        "description" : "Answers with the edit that revokes one gated-type capability from the 'capabilities <- [...]' List of a 'defines package' block, plus the resolved capability name.",
        "idePrecedent" : "EK9 IDE - package-block right-click \"Remove Capability...\". Concept: Android manifest permission removal.",
        "whenToUse" : "'This package no longer needs TCP' is one call with file:'package.ek9' and capability:'TCP'. The full 'org.ek9.lang::TCP' is accepted too; revoke a declared capability the code stopped using.",
        "whenNotToUse" : [ "Declaring a capability - use ek9_add_capability.", "Checking whether the gated type is still referenced before revoking - use ek9_query_capability_usage (this tool performs no usage scan).", "Removing a library dependency - use ek9_remove_dependency." ],
        "examples" : [ "{file:'package.ek9', capability:'TCP', dryRun:true} -> status:ready, capability:'org.ek9.lang::TCP', with the single-line delete edit.", "removing the sole entry without removeListIfEmpty -> status:conflicts (would-empty-capabilities-list); re-call with removeListIfEmpty:true to drop the whole 'capabilities <- [...]' declaration." ],
        "details" : "Preserves the List's single-/multi-line form. Removing the LAST entry is a real decision: an empty List literal is a parse error, so the whole declaration must go, and without a capabilities declaration the package is no longer publishable-enforced - hence the removeListIfEmpty gate (default false -> conflict 'would-empty-capabilities-list'). conflicts[].kind 'capability-not-declared' lists the existing entries. If the gated type is still used the next compile fires E12010 (no usage scan here); enforcement applies only to publishable packages (publicAccess + version + license + capabilities all declared).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "capability" : {
              "description" : "The gated type to revoke: a bare name such as 'TCP' (resolved to org.ek9.lang::TCP) or the fully-qualified name - matched against the capabilities List entries.",
              "type" : "string"
            },
            "dirtyOverlay" : {
              "description" : "Universal — see §1.1. Override on-disk content for files with unsaved buffers. Entries are applied at read time; the apply path still writes the (post-edit) result to disk.",
              "items" : {
                "properties" : {
                  "content" : {
                    "type" : "string"
                  },
                  "file" : {
                    "type" : "string"
                  }
                },
                "required" : [ "file", "content" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "dryRun" : {
              "default" : false,
              "description" : "Universal — see EK9_TOOL_SPECIFICATIONS.md §1.1. When true, no disk mutation; response shows the would-be edit.",
              "type" : "boolean"
            },
            "expectedWorkspaceHash" : {
              "description" : "Universal — see §1.1. Drift-detection hash from a prior dry-run. Mismatch emits a WORKSPACE_DRIFTED warning.",
              "type" : "string"
            },
            "file" : {
              "description" : "The .ek9 file holding the 'defines package' block, workspace-relative (typically 'package.ek9'); an absolute path inside the workspace is accepted.",
              "type" : "string"
            },
            "removeListIfEmpty" : {
              "default" : false,
              "description" : "True allows removing the LAST entry, which drops the whole 'capabilities <- [...]' declaration (an empty List literal is a parse error) and switches capability enforcement off for the package. False (default) refuses that case with conflict 'would-empty-capabilities-list'.",
              "type" : "boolean"
            },
            "showExamples" : {
              "default" : false,
              "description" : "Universal — the inverse of terseErrors. When true each wouldBeDiagnostics entry is enriched with a 'richExplanation' field inlining that error code's rich explanation (diagnosis + correct-pattern action + 'ek9 -q N' worked-example pointers — the same text ek9_explain_error returns), so a would-fail dry-run carries the corrective pattern without a second call. terseErrors takes precedence if both set.",
              "type" : "boolean"
            },
            "terse" : {
              "default" : false,
              "description" : "Universal — token-saver. When true the response omits each edit's before/after bodies (keeping skeletal file/type/line range). Use on an apply after a dryRun already showed the full render — you keep WHAT changed WHERE without re-sending the content.",
              "type" : "boolean"
            },
            "terseErrors" : {
              "default" : false,
              "description" : "Universal — token-saver. When true each wouldBeDiagnostics entry collapses to its one-line message + errorCode/file/line (the multi-paragraph explanation is dropped). Recover full detail on demand with ek9_explain_error <errorCode>.",
              "type" : "boolean"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Universal response-size control (smaller / less detail / summary) — see §1.6. full (default) reproduces the complete response; summary drops each edit's before/after bodies (keeping file/type/line range); none collapses the edits array to a count. The `terse` boolean is the retained alias for summary.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ "file", "capability" ],
          "type" : "object"
        }
      } ]
    },
    "execution" : {
      "description" : "Run code or tooling. Most expensive, most side-effecting; default policy is confirm-on-execute.",
      "categoryRules" : [ ],
      "tools" : [ {
        "name" : "ek9_compile",
        "description" : "Compiles the workspace and answers status (ok | failed | found-no-sources | blocked), sourceCount, errorCount, warningCount and diagnostics[] - each with file, line, column, severity, errorCode and message, the ek9_query_diagnostics record - for every discovered .ek9 file.",
        "idePrecedent" : "Build Project (IntelliJ)",
        "workflows" : [ "release", "feature_branch_start", "investigate_build_failure" ],
        "whenToUse" : "'Does the workspace compile, and what are the errors?' is one call: {} recompiles the initialised workspace, {projectDir:'.'} points it at a project root first.",
        "whenNotToUse" : [ "Reading the last compile's cached diagnostics without recompiling - use ek9_query_diagnostics.", "Running a program - use ek9_run_program (it compiles first).", "Running tests - use ek9_run_tests (it compiles first).", "Canonical source formatting - use ek9_format." ],
        "examples" : [ "{projectDir:'src/main/orders', mode:'full'} -> {status:'ok', sourceCount, errorCount:0, warningCount, diagnostics:[], summary:'Compilation succeeded (...)'}", "{phaseUpTo:'FULL_RESOLUTION'} -> stops after the type-checker for pipeline-stage isolation; status:'failed' lists each diagnostic with file, line, errorCode, message." ],
        "details" : "Two orthogonal dimensions: `mode` = cache strategy (default full; in V1 incremental/full/clean share the full-workspace compile path and only check-only differs - no artefacts) and artifact-intent flags (includeDev, withDebug, withProfiling, noLink, optimisation o0/o2-default/o3, phaseUpTo - any CompilationPhase name, default PRE_IR_CHECKS). Guards: includeDev implies withDebug; withProfiling implies withDebug and requires explicit optimisation:o0 (the o2 default blocks it); withDebug+o3 and withProfiling+noLink are blocked (status:'blocked' with a kebab-case reason). A success with zero discovered sources is status:'found-no-sources' (misconfigured projectDir); the .ek9 cache is not flag-keyed (use mode:full after switching debug/profile/opt). A failed compile with E07235 (fields but no '?') gets 'default operator ?' applied through the verified write and is compiled again - corrected[] and edits[] say where; repair:false only compiles.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "errorLevel" : {
              "default" : 3,
              "description" : "Error detail level: 0 minimal, 1 visual, 2 + fuzzy suggestions, 3 + rich explanations.",
              "maximum" : 3,
              "minimum" : 0,
              "type" : "integer"
            },
            "includeDev" : {
              "description" : "Include dev-only code (test sources, fixtures). Implies withDebug per the CLI -cd family bundling rule.",
              "type" : "boolean"
            },
            "mode" : {
              "default" : "full",
              "description" : "Cache strategy: incremental (recompile only changed files), full (re-emit all bytecode), check-only (compile + analyse without emitting artefacts), clean (wipe artefacts + dependencies before compile).",
              "enum" : [ "incremental", "full", "check-only", "clean" ],
              "type" : "string"
            },
            "noLink" : {
              "description" : "Compile + analyse without producing an executable artefact. Equivalent to mode: check-only when paired with mode: incremental or full. Rejected with withProfiling.",
              "type" : "boolean"
            },
            "optimisation" : {
              "default" : "o2",
              "description" : "o0 (none - fastest compile, max debug fidelity), o2 (balanced), o3 (aggressive - inlining, DCE, loop opts; breaks debug attributes and profiling probes). Auto-set to o0 when withProfiling: true.",
              "enum" : [ "o0", "o2", "o3" ],
              "type" : "string"
            },
            "phaseUpTo" : {
              "default" : "PRE_IR_CHECKS",
              "description" : "Stop after the named compilation phase (e.g. FULL_RESOLUTION, IR_GENERATION) for pipeline-stage diagnostics.",
              "enum" : [ "READING", "PARSING", "SYMBOL_DEFINITION", "DUPLICATION_CHECK", "REFERENCE_CHECKS", "EXPLICIT_TYPE_SYMBOL_DEFINITION", "TYPE_HIERARCHY_CHECKS", "FULL_RESOLUTION", "POST_RESOLUTION_CHECKS", "PRE_IR_CHECKS", "PLUGIN_RESOLUTION", "SYMBOLIC_ANALYSIS", "IR_GENERATION", "IR_ANALYSIS", "IR_OPTIMISATION", "CODE_GENERATION_PREPARATION", "CODE_GENERATION_CONSTANTS", "CODE_GENERATION_APPLICATIONS", "CODE_GENERATION_AGGREGATES", "CODE_OPTIMISATION", "PLUGIN_LINKAGE", "APPLICATION_PACKAGING", "PACKAGING_POST_PROCESSING" ],
              "type" : "string"
            },
            "projectDir" : {
              "description" : "Project root to discover .ek9 files from - absolute, or relative to the workspace root; (re)initialises the workspace before compiling. Omit to reuse the initialised workspace. Shorthand for roots:[projectDir].",
              "type" : "string"
            },
            "repair" : {
              "default" : true,
              "description" : "Apply the fixes the language leaves no choice about, then compile again: a slip with one reading (e.g. '-> a as Integer, b as String', 'Type?: value', a stray ')'), and 'default operator ?' for a class, record or component with fields and no '?' (E07235) - each listed in corrected[] and edits[]. false compiles only, and changes nothing.",
              "type" : "boolean"
            },
            "roots" : {
              "description" : "Several project roots at once, each as projectDir. Omit for one root (use projectDir) or to reuse the initialised workspace.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "traceCompiler" : {
              "default" : false,
              "description" : "Emit verbose compile-pipeline tracing to the build log.",
              "type" : "boolean"
            },
            "withDebug" : {
              "description" : "Emit debug info / SMAP for breakpoint + variable inspection. Compatible with optimisation o0 (max fidelity) or o2 (safe). Rejected with o3 (aggressive optimisation strips debug attributes).",
              "type" : "boolean"
            },
            "withProfiling" : {
              "description" : "Emit per-function profiling probes for hot-path / call-graph data. Forces optimisation: o0. Rejected with o2 / o3 (probes get inlined away). Rejected with noLink (no artefact to profile). Implies withDebug for source-line attribution.",
              "type" : "boolean"
            }
          },
          "type" : "object"
        }
      }, {
        "name" : "ek9_clean",
        "description" : "Wipes the on-disk .ek9/ artefacts the scope names and resets the server JVM's in-memory coverage/profiling runtimes; answers status:ok with cleanedSources[] listing what was wiped or reset, in order.",
        "idePrecedent" : "Build → Clean (IntelliJ) / mvn clean / cargo clean",
        "workflows" : [ "release" ],
        "whenToUse" : "'Start this measurement from a pristine project' is one call: {scope:'coverage'} (or profiling / release / full) on the workspace root - projectDir only for another root.",
        "whenNotToUse" : [ "Running tests - ek9_run_tests already cleans with scope coverage first, so a preceding ek9_clean is redundant.", "Just wanting a fresh recompile without deleting lib/, the target executable, or resetting runtimes - use ek9_compile (mode=full), which re-emits all bytecode." ],
        "examples" : [ "{scope:'coverage'} → status:ok, cleanedSources:[generated, coverageRuntime].", "{projectDir:'orders', scope:'full'} → status:ok, cleanedSources:[generated, lib, target, props, coverageRuntime, profilingRuntime]." ],
        "details" : "Scope maps to state sources: coverage = wipe generated/ + reset CoverageRuntime; profiling = also reset ProfilingRuntime (profiling registers probes via CoverageRuntime, so both reset); release/full = also wipe lib/ + target executable + props (identical on disk today, kept distinct so future caches can join full). Disk wipe + runtime reset are one coordinated step so they never drift (historically a registry-only reset let orphaned .class files re-register stale probes and pollute coverage). The response's cleanedSources lists what was wiped/reset in execution order.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "file" : {
              "description" : "The entry-point .ek9 source file (absolute, or relative to projectDir). Omit to auto-detect the shallowest non-dev .ek9 file - enough to locate the .ek9/ directory.",
              "type" : "string"
            },
            "projectDir" : {
              "description" : "Project root holding the .ek9 sources - absolute, or relative to the workspace root. Omit to use the workspace root itself (the first root ek9_compile was given).",
              "type" : "string"
            },
            "scope" : {
              "default" : "full",
              "description" : "What to make pristine: coverage (generated/ + coverage runtime), profiling (coverage + profiling runtime), release and full (also lib/, the target executable and props).",
              "enum" : [ "coverage", "profiling", "release", "full" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_run_tests",
        "description" : "Compiles and runs the project's @Test programs (auto-forcing -Cd -O0 so coverage probes map to source) and answers status (ok | compile-failed | no-tests-found | tests-failed | coverage-low), tests:{passed, failed}, overallCoverage, results[] per test, plus the inputVariety and hex change-risk blocks.",
        "idePrecedent" : "Run All Tests (IntelliJ) / mvn test / cargo test",
        "workflows" : [ "release", "quality_check" ],
        "whenToUse" : "'Do the tests pass?' is one call: {} runs every @Test program in the workspace; testNameFilter:'SmokeTest' (bare or qualified) runs one.",
        "whenNotToUse" : [ "Running a NORMAL entry-point program (no @Test directive) - use ek9_run_program.", "Compiling tests WITHOUT running them - use ek9_compile with includeDev:true.", "Wanting the full quality snapshot (coverage + complexity + readability + DI) in one call - use ek9_run_quality_checks.", "Authoring a @Test program rather than running one - use ek9_add_program with isTest:true." ],
        "examples" : [ "{} → status:'ok' when every test passes AND coverage meets the threshold, else 'tests-failed' / 'coverage-low' / 'no-tests-found' / 'compile-failed'; tests:{passed,failed}.", "{testNameFilter:'PaymentValidationTest'} → runs that one test (a bare name resolves when unique, else status:'error' naming the candidates); results[] carries fullyQualifiedName/passed/errorMessage." ],
        "details" : "In-process CompilationPipeline (bootstrap → discover → compile → runTests), the tests themselves in a fresh child JVM per run, killed at the test time limit (E82001); no pre-compiled workspace needed. status ∈ {ok, compile-failed, no-tests-found, tests-failed, coverage-low}; 'ok' requires all tests green AND coverage at or above the 80% threshold (coverageSufficient). Every run with results carries 'inputVariety' and a 'hex' change-risk block (hazard x exposure, worst construct named, caMax blast radius) - read hex before editing; verbosity:'full' adds per-method/branch/statement uncovered items with file+line (CLI -t4/-t5 data) and the full ranked hex construct list; the invariant interpretation prose only with notes:true.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "coverageDetail" : {
              "description" : "Retained boolean alias for verbosity:'full'.",
              "type" : "boolean"
            },
            "file" : {
              "description" : "The entry-point .ek9 source file (absolute, or relative to projectDir). Omit to auto-detect the shallowest non-dev .ek9 file.",
              "type" : "string"
            },
            "generateReport" : {
              "description" : "Default false. When true, additionally produces an HTML coverage report at .ek9/coverage/index.html.",
              "type" : "boolean"
            },
            "notes" : {
              "description" : "Default false. When true, include the full interpretation prose beside the metric blocks (how to read hex/hexMax/bands/cohorts, why Ca and purity sit outside the score, what asserted variety means). It is invariant text and ~1,300 tokens, so it is omitted by default and every block instead carries a one-line pointer. The same guidance is permanently available via ek9_help with keyword 'hex' - prefer that once, over paying for it on every run.",
              "type" : "boolean"
            },
            "projectDir" : {
              "description" : "Project root holding the .ek9 sources - absolute, or relative to the workspace root. Omit to use the workspace root itself (the first root ek9_compile was given).",
              "type" : "string"
            },
            "testGroupFilter" : {
              "description" : "Run only tests in the named group. Tests without the group are skipped.",
              "type" : "string"
            },
            "testNameFilter" : {
              "description" : "Run only this test: a fully qualified name (com.acme::SmokeTest) or a bare program name (SmokeTest) when it is unique across the workspace - an ambiguous bare name is refused naming the candidates.",
              "type" : "string"
            },
            "verbosity" : {
              "default" : "full",
              "description" : "Response size. summary (default): counts, per-test results, the hex and inputVariety headlines. full: also a 'coverage' block of every UNCOVERED method / branch / statement with file + line (the CLI -t4/-t5 data) and hex.constructs[], the full ranked per-construct list with raw signals. none: status and counts only.",
              "enum" : [ "full", "summary", "none" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_run_quality_checks",
        "description" : "Compiles, runs the tests and answers status (ok | compile-failed | no-tests-found | tests-failed | coverage-low) with tests:{passed, failed}, coverage (overall, methods, lines, branches, byModule), complexity.hotspots[], readability, injections, uncoveredMethods[] and the advisory lockPayloads - every quality metric in one call, extracted in-process.",
        "idePrecedent" : "SonarQube dashboard / cargo-tarpaulin / istanbul coverage",
        "workflows" : [ "release", "quality_check" ],
        "whenToUse" : "'Is the project's quality posture OK?' is one call: {} compiles, runs the tests and reports coverage, complexity hotspots, readability, DI and uncovered methods together.",
        "whenNotToUse" : [ "Per-test pass/fail detail, test filtering, or an HTML report - use ek9_run_tests.", "Complexity from an already-compiled workspace with no re-compile - use ek9_query_complexity.", "Compile error/warning detail, not metrics - use ek9_query_diagnostics.", "Compiling without running tests - use ek9_compile." ],
        "examples" : [ "{} → status:ok with coverage.overall, complexity.hotspots, readability, injections, uncoveredMethods and lockPayloads sections.", "{complexityThreshold:15} → same snapshot but only methods with cyclomatic >= 15 appear in complexity.hotspots." ],
        "details" : "status is one of: ok (tests pass + coverage meets threshold) / tests-failed / coverage-low (tests pass but coverage below threshold) / compile-failed / no-tests-found - the same words ek9_run_tests uses. lockPayloads reports MutexKey bodies whose protected payload may leave the lock — each naming the enclosing method, the MutexLock it runs under and the via chain to the callee that keeps the reference — beside a census {bodies, contained, mayEscape, unknown} of EVERY body; it carries gates:false and is NOT part of status — advisory until it proves reliable enough to stop a publish (ek9_query_mutex_payload is the same sweep, workspace-scoped). All metrics are extracted in-process from CoverageRuntime after execution (no disk reads/writes for metrics); complexityThreshold (default 10) gates the cyclomatic hotspots list, uncoveredMethods is capped at 50 entries sorted by file then start line, and the entry-point file auto-detects when omitted. RELEASE chain: ek9_increment_version -> ek9_run_tests -> ek9_run_quality_checks -> ek9_package.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "complexityThreshold" : {
              "default" : 10,
              "description" : "Methods with cyclomatic complexity >= this appear in complexity.hotspots.",
              "type" : "integer"
            },
            "file" : {
              "description" : "The entry-point .ek9 source file (absolute, or relative to projectDir). Omit to auto-detect the shallowest non-dev .ek9 file.",
              "type" : "string"
            },
            "projectDir" : {
              "description" : "Project root holding the .ek9 sources - absolute, or relative to the workspace root. Omit to use the workspace root itself (the first root ek9_compile was given).",
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_generate_edge_case_tests",
        "description" : "Writes a compile-checked edge-case @Test program file for one .ek9 source and answers status (applied | ready | unchanged | blocked | error), written, testCount, the echoed seed and modifiedFiles[] - every harvested constructor, method, operator and function gets the tri-state UNSET value among its inputs.",
        "idePrecedent" : "Hypothesis (Python) / fast-check (JS) / QuickCheck (Haskell)",
        "rules" : [ "Generated tests are liveness / edge-case SMOKE tests — they exercise edge + always-unset inputs and assert set-ness / no-crash, NOT business correctness. Read a green result as 'survives edge inputs', not 'behaviour verified'.", "Compile-surviving does NOT mean passing: some generated tests legitimately fail (e.g. asserting on an unset-input result). The flow is generate -> review / curate -> ek9_run_tests, not generate -> trust.", "The output is SCAFFOLDING. Every //AI: assertion is an expectation YOU must set from the REQUIREMENT the code is meant to satisfy — never from what the code currently returns. Do NOT run the tests and keep whichever assertion is green: that codes the test to the implementation and cements existing bugs as passing. A RED test is this tool working — decide whether the expectation or the code is wrong. Where the project tracks requirements, ek9_query_requirement / ek9_check_requirements is where the expectation should come from.", "Capture the echoed seed to regenerate the exact same survivor set after a source edit.", "If status is no-testable-symbols the source compiled but exposes nothing public to test — move on or widen the API surface." ],
        "whenToUse" : "'Give this file systematic edge-case tests' is one call: {file:'account.ek9', outputFile:'dev/account_tests.ek9'} sprays type-aware edge values (always UNSET) over every public surface.",
        "whenNotToUse" : [ "ONE specific test you already know how to write - use ek9_add_program(isTest:true).", "You already have tests and just want to run them - use ek9_run_tests.", "The counterexample + a paste-ready directed test for ONE function at a cursor - use ek9_verify_method (this tool's mode:'verify' is its whole-file dual).", "Business-logic / contract assertions the generator can't infer - hand-author the test." ],
        "examples" : [ "{file:'/abs/src/main/account.ek9', outputFile:'/abs/dev/account_fuzz_tests.ek9'} -> status:applied, written:true, testCount, harvest/candidates + echoed seed.", "{file:'/abs/src/main/validation.ek9', outputFile:'/abs/dev/validation_verify_tests.ek9', mode:'verify'} -> status:applied mode:verify, one assertPanic/assert-not-set test per counterexamplesFound (or status:unchanged reason:no-counterexamples when verified to bound)." ],
        "details" : "Two modes share one compile-filter/assemble/write engine: 'edge' is type-aware fuzz (seed echoed - pass it back to reproduce a survivor set; maxResults defaults 200), 'verify' is deterministic (seed/maxResults ignored). The editor envelope: applied (written:true) | ready (dryRun, written:false) | unchanged (reason no-candidates or no-counterexamples - nothing to write) | blocked (output-exists / output-not-dev-source / no-testable-symbols / source-compile-failed / invalid-test-module) | error (write-failed, or assembled-compile-failed where the file IS written for inspection, written:true). Intent-dependent set-state asserts carry an //AI-REVIEW marker. Workspace-independent: each call compiles the source against a fresh builtins-only program, so no prior ek9_compile is needed; paths are absolute, or relative to the server's working directory.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "dryRun" : {
              "description" : "Default false. Preview only: harvest, generate, compile-filter and assemble exactly as a real run, but write NOTHING. Returns status 'ready' with testCount and the full fileContent that a committing call would write. Re-invoke without dryRun to commit. Use this to check testCount > 0 before writing.",
              "type" : "boolean"
            },
            "file" : {
              "description" : "The .ek9 source file to generate tests for - absolute, or relative to the MCP server's working directory (this tool runs against a fresh builtins-only program, not the workspace).",
              "type" : "string"
            },
            "includeGeneratedTests" : {
              "description" : "Default false. When true, include a compact [{name, description}] index of the surviving tests for inline curation (full bodies are written to outputFile).",
              "type" : "boolean"
            },
            "maxResults" : {
              "default" : 200,
              "description" : "Max candidate tests to generate before compile-filtering; testCount will be <= this.",
              "type" : "integer"
            },
            "mode" : {
              "description" : "Default 'edge' — spray type-aware edge values (fuzz). 'verify' — run bounded-model verification per function and synthesise a directed test for each counterexample (seed/count are ignored in this mode).",
              "enum" : [ "edge", "verify" ],
              "type" : "string"
            },
            "outputFile" : {
              "description" : "Destination: the generated test file. MUST be a dev source — under a 'dev/' subdirectory or a QA####_dev_ filename — because the generated @Test/assert constructs only compile there (E81015); a non-dev path is blocked. Also blocked if it already exists unless overwrite:true.",
              "type" : "string"
            },
            "overwrite" : {
              "description" : "Default false. Replace outputFile if it exists.",
              "type" : "boolean"
            },
            "seed" : {
              "description" : "Reproducibility seed. Default: random (always echoed in the response). Pass the echoed seed back to regenerate the exact same survivor set after a source edit.",
              "type" : "integer"
            },
            "targets" : {
              "description" : "Optional. Restrict generation to these construct names (types and/or functions, e.g. [\"Location\"]). Tests are generated ONLY for the named constructs, but nested arguments still use the full source for fidelity. Omit or empty = all testable constructs.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "testModule" : {
              "description" : "Module to declare the generated @Test programs in. Omit to DETECT: whichever module already holds tests referencing the source's module wins, else a conventional '<sourceModule>.tests' if the project has one, else the source's own module. When it differs from the source's module the file gains an alphabetically-ordered references block (E11026) and its edge-value constants are de-duplicated against that module, not the source's. Validated when supplied: an illegal module name is rejected up front with status 'blocked' / reason 'invalid-test-module'. Note a segment may not be a reserved keyword EK9 excludes from moduleSegment — 'some.other.mod' is illegal ('mod' is an operator), 'some.other.place' is fine.",
              "type" : "string"
            },
            "verbose" : {
              "description" : "Default false. Verbose compiler reporting.",
              "type" : "boolean"
            }
          },
          "required" : [ "file", "outputFile" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_git_init",
        "description" : "Reports branch and gitignoreCreated (a .gitignore for the generated .ek9/ directory is scaffolded when absent); refuses when already a repository.",
        "idePrecedent" : "git init / Enable Version Control Integration",
        "rules" : [ "The only git tool valid OUTSIDE a repo. Refuses when already one. After init there are no commits yet - ek9_git_add all:true then ek9_git_commit." ],
        "whenToUse" : "'Put this project under git' - one call, no arguments; the only ek9_git_* tool valid outside a repository (the answer to not-a-repository). The optional branch names the initial branch, default main.",
        "whenNotToUse" : [ "a project already under git - use ek9_git_status.", "the first commit after init - use ek9_git_add then ek9_git_commit.", "cloning an existing remote - not supported (there is no clone tool)." ],
        "examples" : [ "{} -> status:'initialized', branch:'main', gitignoreCreated:true (a .gitignore for the generated .ek9/ directory is scaffolded when absent; *.ek9 sources stay tracked).", "{branch:'trunk'} on a repository -> status:'refused', error.kind:'already-a-repository' (a second init never silently re-initialises)." ],
        "details" : "written:true with branch, gitignoreCreated (an existing .gitignore is never clobbered) and a nextStep for the first commit. The only parameter is the optional initial branch (default main).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "branch" : {
              "description" : "Initial branch name. Default main.",
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_git_add",
        "description" : "Reports stagedPaths and stagedPathCount (what this call staged), then indexAfter and indexAfterCount (everything now staged) and a summary line.",
        "idePrecedent" : "git add / Stage Changes",
        "rules" : [ "Stage before ek9_git_commit, or let ek9_git_commit paths:[...] do both. indexAfter is what the next commit will record." ],
        "whenToUse" : "'Stage these files for the next commit' - one call with paths:[...] or all:true; the first step of stage, commit, push. Staging copies the CURRENT content, so edits made afterwards need staging again.",
        "whenNotToUse" : [ "recording the staged changes - use ek9_git_commit (its paths:[...] stages and commits in one call).", "seeing what is staged, modified or untracked - use ek9_git_status.", "undoing a mis-stage - use ek9_git_restore mode:'unstage'." ],
        "examples" : [ "{paths:['src/main/orders/order.ek9']} -> status:'staged', stagedPaths:[that path], stagedPathCount:1, indexAfter (everything now staged), indexAfterCount.", "{all:true} -> status:'staged'; stagedPaths and indexAfter are the same set, deletions included." ],
        "details" : "stagedPaths / stagedPathCount are what THIS call staged; indexAfter / indexAfterCount are the whole index afterwards, so one call can never be read as having staged the earlier entries. At least one of paths / all is required; a path outside the workspace, a path git cannot find, or a non-git workspace is refused with the route in nextStep.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "all" : {
              "description" : "Stage every change including deletions (git add -A). Default false.",
              "type" : "boolean"
            },
            "paths" : {
              "description" : "Workspace-relative paths to stage.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_git_commit",
        "description" : "Reports newSha and previousSha, branch, subject, changedFiles and changedFileCount; local history only, nothing is published until ek9_git_push.",
        "idePrecedent" : "git commit / Commit",
        "rules" : [ "Stage first (ek9_git_add) or pass paths / all:true. Local history only - run ek9_git_push to publish. nothing-to-commit means the index is clean." ],
        "whenToUse" : "'Commit this' - one call with message; paths:[...] stages and commits in one step, all:true stages tracked edits, amend:true rewrites the last commit. The middle step of stage, commit, push.",
        "whenNotToUse" : [ "publishing to the remote - use ek9_git_push.", "staging without recording - use ek9_git_add.", "checking what would be committed - use ek9_git_status." ],
        "examples" : [ "{message:'add second file'} -> status:'committed', newSha:'a3f2c1b8', previousSha, branch:'main', subject, changedFiles, changedFileCount.", "{message:'fix order total', paths:['src/order.ek9']} -> stages that file and commits it; {amend:true} alone folds newly-staged changes into the last commit, keeping its message." ],
        "details" : "message is required unless amend:true (then omitted = keep the previous message). all:true stages tracked modifications only - a new file needs paths or ek9_git_add. Shas are the 8-hex short form shared across the family. A clean index is refused as nothing-to-commit with the route; a missing user.name/user.email is git's own refusal, surfaced under error.message.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "all" : {
              "description" : "Stage every tracked modification first (git commit -a); untracked files are not included. Default false.",
              "type" : "boolean"
            },
            "amend" : {
              "description" : "Rewrite the previous commit instead of creating a new one. Default false.",
              "type" : "boolean"
            },
            "message" : {
              "description" : "Commit message. Required unless amend:true, which then keeps the previous commit's message.",
              "type" : "string"
            },
            "paths" : {
              "description" : "Workspace-relative paths to stage before committing (stage + commit in one call).",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_git_push",
        "description" : "Reports remote, branch, upstream, upToDate and a summary line, with git's own report under gitReport; outward-facing and not trivially reversible, so confirm intent first.",
        "idePrecedent" : "git push / Push",
        "rules" : [ "Push PUBLISHES and is not trivially reversible - confirm intent before calling. setUpstream:true on the first push of a new branch." ],
        "whenToUse" : "'Publish my commits' - one call, no arguments for the current branch to origin; setUpstream:true on a new branch's first push. The last step of stage, commit, push and outward-facing: confirm intent before calling.",
        "whenNotToUse" : [ "changes not yet committed - use ek9_git_commit first.", "checking ahead/behind before publishing - use ek9_git_status or ek9_git_fetch.", "undoing an already-pushed commit - use ek9_git_revert, never amend plus force." ],
        "examples" : [ "{} -> status:'pushed', summary:'Pushed main to origin', remote, branch, upToDate:false, gitReport (git's own report).", "{branch:'feature-x', setUpstream:true} -> status:'pushed', upstream:'origin/feature-x'; nothing new -> upToDate:true." ],
        "details" : "Defaults: remote origin, the current branch. force:true is --force-with-lease (refuses to clobber un-fetched upstream work); a bare --force is deliberately not exposed. Refusals name the route: no-remote (add one), rejected (ek9_git_fetch then ek9_git_pull, then push again), timed-out.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "branch" : {
              "description" : "Branch to push. Default: the current branch.",
              "type" : "string"
            },
            "force" : {
              "description" : "Replace the remote branch with --force-with-lease (refuses when the remote moved since the last fetch). Default false.",
              "type" : "boolean"
            },
            "remote" : {
              "description" : "Remote name. Default origin.",
              "type" : "string"
            },
            "setUpstream" : {
              "description" : "Set the upstream tracking ref (-u) - the first push of a new branch. Default false.",
              "type" : "boolean"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_git_switch",
        "description" : "Reports status (switched or created), branch and previousBranch; a dirty tree that would be overwritten is refused with the route (commit, stash or restore first), never auto-stashed.",
        "idePrecedent" : "git switch / Checkout Branch",
        "rules" : [ "Switch to an existing branch, or create:true to branch from HEAD (or ref) and switch. Branch BEFORE committing onto the default branch. A refusal names its route in nextStep - this tool never auto-stashes." ],
        "whenToUse" : "'Switch to branch X' or 'start a branch for this work' - one call with branch; create:true creates it from HEAD (or ref) and switches. Branch here BEFORE committing onto the default branch.",
        "whenNotToUse" : [ "listing the branches that exist - use ek9_git_branch.", "unstaging or discarding file changes - use ek9_git_restore.", "parking uncommitted changes so a switch can proceed - use ek9_git_stash.", "recording changes on the current branch - use ek9_git_commit." ],
        "examples" : [ "{branch:'feature/x', create:true} -> status:'created', branch, previousBranch:'main', created:true.", "{branch:'main'} -> status:'switched', previousBranch:'feature/x'; an unknown branch -> status:'refused', error.kind:'branch-not-found', nextStep." ],
        "details" : "Uses git switch (never checkout, which can silently discard working-tree changes). ref is only meaningful with create:true. No auto-stash: a dirty tree that would be overwritten is refused as dirty-tree with the route (commit, stash or restore first); branch-not-found and already-exists are refused with theirs.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "branch" : {
              "description" : "Branch to switch to (or to create with create:true).",
              "type" : "string"
            },
            "create" : {
              "description" : "Create the branch first (git switch -c). Default false.",
              "type" : "boolean"
            },
            "ref" : {
              "description" : "Commit, tag or branch to create the new branch from (with create:true). Default: the current HEAD.",
              "type" : "string"
            }
          },
          "required" : [ "branch" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_git_merge",
        "description" : "Reports status and outcome (already-up-to-date, fast-forward, clean-merge or conflict), newSha/previousSha, commitsMerged, changedFiles and conflictFiles; a conflicting merge aborts with the tree restored.",
        "idePrecedent" : "git merge / Merge Branch",
        "rules" : [ "Merges a LOCAL branch (the remote is ek9_git_pull mode:'merge'). Requires a clean tree; a conflicting merge auto-aborts and restores it, so run dryRun:true first and resolve conflicts on a branch." ],
        "whenToUse" : "'Merge feature-x into this branch' - one call with branch; dryRun:true predicts the outcome and the conflicting files without merging. Use mode:'merge-commit' to keep the branch visible in the graph, mode:'ff-only' to refuse a real merge.",
        "whenNotToUse" : [ "integrating the REMOTE upstream - use ek9_git_pull mode:'merge'.", "just moving to the branch - use ek9_git_switch.", "undoing a commit - use ek9_git_revert." ],
        "examples" : [ "{branch:'feature-x'} -> status:'merged' (or fast-forward / already-up-to-date), outcome, newSha/previousSha, commitsMerged, changedFiles, changedFileCount.", "{branch:'feature-a', dryRun:true} -> status:'conflicts', outcome:'conflict', conflictFiles:['aCheck.ek9'], willAutoAbort:true, written:false - nothing touched." ],
        "details" : "outcome shares one vocabulary between preview and apply: already-up-to-date / fast-forward / clean-merge / conflict (conflict only in a preview - the real merge aborts it). Apply status: merged / fast-forward / already-up-to-date with dryRun:false; preview status: ready / conflicts / unchanged with dryRun:true (an in-memory merge-tree, git 2.38+). A dirty tree is refused (dirty-tree); a conflicting merge is aborted with treeRestored:true. noFastForward:true is the IDE alias of mode:'merge-commit'.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "branch" : {
              "description" : "The local branch to merge into the current branch.",
              "type" : "string"
            },
            "dryRun" : {
              "description" : "Predict the outcome, the changed and the conflicting files without merging. Default false.",
              "type" : "boolean"
            },
            "message" : {
              "description" : "Merge-commit message. Default: git's own.",
              "type" : "string"
            },
            "mode" : {
              "description" : "Default auto (fast-forward when possible, else a merge commit); merge-commit always records one (--no-ff); ff-only refuses when a merge commit would be needed.",
              "enum" : [ "auto", "merge-commit", "ff-only" ],
              "type" : "string"
            },
            "noFastForward" : {
              "description" : "IDE alias of mode:'merge-commit'.",
              "type" : "boolean"
            }
          },
          "required" : [ "branch" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_git_restore",
        "description" : "Reports mode, paths and pathCount and a summary line; ref restores content from that commit instead of the index or HEAD.",
        "idePrecedent" : "git restore / Revert / Unstage",
        "rules" : [ "Unstage with mode:'unstage' (safe - working tree untouched). Discard working-tree edits with mode:'discard' (DESTRUCTIVE, uncommitted changes are lost). The inverse of ek9_git_add." ],
        "whenToUse" : "'Unstage this' or 'throw away my edits' - one call with paths:[...]; mode:'unstage' (default, safe) or mode:'discard' (destructive). Pass ref to restore the content from that commit instead.",
        "whenNotToUse" : [ "undoing a commit that is already made - use ek9_git_revert.", "staging changes - use ek9_git_add (this tool is its inverse).", "moving between branches - use ek9_git_switch.", "seeing what is staged or modified first - use ek9_git_status." ],
        "examples" : [ "{paths:['src/order.ek9']} -> status:'restored', mode:'unstage', summary:'Unstaged 1 path (working-tree edits kept)', paths, pathCount.", "{paths:['src/order.ek9'], mode:'discard'} -> status:'restored', mode:'discard' - the uncommitted edits are gone (irreversible)." ],
        "details" : "mode:'unstage' takes the paths out of the index and leaves the working tree alone; mode:'discard' overwrites the working tree from the index (or from ref); mode:'both' does both from HEAD. discard is a one-way door for edits made outside the workspace tools. The IDE aliases staged:true / worktree:true map to unstage / discard; sending them alongside a contradicting mode is refused. An untracked path is refused as path-not-tracked with the route.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "mode" : {
              "description" : "Default unstage (take out of the index, keep the working-tree edits); discard (overwrite the working-tree edits - DESTRUCTIVE); both.",
              "enum" : [ "unstage", "discard", "both" ],
              "type" : "string"
            },
            "paths" : {
              "description" : "Workspace-relative paths to restore.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "ref" : {
              "description" : "Commit to restore content from. Default: the index for discard, HEAD for unstage.",
              "type" : "string"
            },
            "staged" : {
              "description" : "IDE alias: true means mode:'unstage'.",
              "type" : "boolean"
            },
            "worktree" : {
              "description" : "IDE alias: true means mode:'discard' (with staged:true, mode:'both').",
              "type" : "boolean"
            }
          },
          "required" : [ "paths" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_git_revert",
        "description" : "Reports newSha, revertedSha, previousSha, branch, subject, changedFiles and changedFileCount; a conflicting revert aborts with the tree restored.",
        "idePrecedent" : "git revert / Revert Commit",
        "rules" : [ "Revert is the push-safe undo: a NEW inverse commit, no history rewrite. On a conflict it auto-aborts and restores the tree - resolve by hand. To revert a merge commit pass mainline." ],
        "whenToUse" : "'Undo that commit safely' - one call with ref (a sha, HEAD, a tag); records a NEW inverse commit, so it is safe on already-pushed history. For a merge commit add mainline (the parent to keep, usually 1).",
        "whenNotToUse" : [ "changes not yet committed - use ek9_git_restore.", "fixing the LAST commit when it is not pushed - use ek9_git_commit amend:true.", "moving branches - use ek9_git_switch." ],
        "examples" : [ "{ref:'9a7b8e7b'} -> status:'reverted', newSha, previousSha, revertedSha:'9a7b8e7b', branch, subject:'Revert ...', changedFiles, changedFileCount.", "{ref:'HEAD', mainline:1} -> reverts a merge commit keeping parent 1." ],
        "details" : "ref is verified first (rev-parse) so an unknown ref is refused before any mutation (ref-not-found, with ek9_git_log as the route); a merge commit without mainline is refused (mainline-required), mainline on a non-merge likewise. A revert that hits conflicts is aborted with treeRestored:true - never a half-done state. Shas are the 8-hex short form shared across the family.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "mainline" : {
              "description" : "For a merge commit only: the parent number to keep (usually 1). Omit for an ordinary commit.",
              "type" : "integer"
            },
            "ref" : {
              "description" : "The commit to revert - a short or full sha, HEAD, a tag or any ref.",
              "type" : "string"
            }
          },
          "required" : [ "ref" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_git_fetch",
        "description" : "Reports updated, upstream, behind and ahead, a summary line and a nextStep when behind; git's own report under gitReport.",
        "idePrecedent" : "git fetch / Fetch",
        "rules" : [ "Fetch never touches the working tree - it is the 'how far behind am I?' check. behind > 0 means ek9_git_pull has work to do." ],
        "whenToUse" : "'Is there anything new upstream?' - one call, no arguments; updates remote-tracking refs only and reports behind/ahead without touching the working tree. The safe pre-flight before ek9_git_pull.",
        "whenNotToUse" : [ "integrating the upstream commits into the branch - use ek9_git_pull.", "the local working-tree state (no network) - use ek9_git_status.", "history already present locally - use ek9_git_log." ],
        "examples" : [ "{} -> status:'fetched', summary:'Fetched origin: 2 behind, 0 ahead of origin/main', updated:true, behind:2, ahead:0, nextStep naming ek9_git_pull.", "{remote:'upstream', prune:true} -> fetches that remote and drops remote-tracking refs deleted there; {allRemotes:true} -> remote:null, allRemotes:true." ],
        "details" : "Safe by construction: fetch only advances remote-tracking refs (origin/main, ...) so it cannot conflict or leave a half-done tree. updated is true when git reported ref updates; upstream/behind/ahead appear only when the current branch has an upstream and match ek9_git_status exactly; gitReport is git's own report. Refusals: no-remote (add one first), timed-out.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "allRemotes" : {
              "description" : "Fetch every remote (--all) instead of one. Default false.",
              "type" : "boolean"
            },
            "prune" : {
              "description" : "Drop remote-tracking refs deleted on the remote (--prune). Default false.",
              "type" : "boolean"
            },
            "remote" : {
              "description" : "Remote name. Default origin.",
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_git_pull",
        "description" : "Reports commitsPulled, newSha and previousSha, changedFiles and changedFileCount; a dirty tree is refused and a rebase/merge conflict is aborted with the tree restored.",
        "idePrecedent" : "git pull / Update Project",
        "rules" : [ "Default mode:'ff-only' cannot conflict; on divergence it is refused and you choose mode:'rebase' or mode:'merge'. Requires a clean tree; conflicts auto-abort and restore it." ],
        "whenToUse" : "'Get the latest from upstream' - one call, no arguments; the default mode:'ff-only' never conflicts, mode:'rebase' or 'merge' when the branch has diverged. Operates on the current branch.",
        "whenNotToUse" : [ "checking what is upstream without changing the branch - use ek9_git_fetch.", "a dirty working tree - commit (ek9_git_commit) or stash (ek9_git_stash) first; pull refuses it and never auto-stashes.", "publishing local commits - use ek9_git_push." ],
        "examples" : [ "{} -> status:'fast-forward', summary, commitsPulled:2, newSha/previousSha, changedFiles, changedFileCount (ff-only default).", "diverged with {} -> status:'refused', error.kind:'diverged', nextStep naming mode:'rebase'; {mode:'rebase'} -> status:'rebased' (linear, local shas rewritten)." ],
        "details" : "status is one of up-to-date / fast-forward / rebased / merged; commitsPulled and changedFiles cover previousSha..HEAD; shas are the 8-hex short form; default remote origin. mode:'ff-only' only advances the pointer, and on divergence is refused so you choose mode:'rebase' (linear, rewrites local shas - never on already-pushed commits) or mode:'merge' (records a merge commit). A dirty tree is refused (dirty-tree); a rebase/merge conflict is aborted with treeRestored:true, never a half-done state.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "mode" : {
              "description" : "How to integrate. Default ff-only (advance the pointer only, never conflicts); rebase (linear, rewrites local shas); merge (records a merge commit).",
              "enum" : [ "ff-only", "rebase", "merge" ],
              "type" : "string"
            },
            "remote" : {
              "description" : "Remote name. Default origin.",
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_git_stash",
        "description" : "Reports status (stashed, popped, applied, dropped or nothing-to-stash) and the stash ref; mode:'list' returns stashCount and stashes; a conflicting pop keeps the entry (stashPreserved:true).",
        "idePrecedent" : "git stash / Stash Changes",
        "rules" : [ "Explicit stash only, never auto-stash. A conflicting pop/apply PRESERVES the entry (stashPreserved:true) - resolve by hand and retry." ],
        "whenToUse" : "'Set my changes aside for now' - one call with mode:'push' (or pop, apply, list, drop); never runs automatically, every stash move is explicit. The answer when ek9_git_switch or ek9_git_pull refused a dirty tree.",
        "whenNotToUse" : [ "work that is finished - record it with ek9_git_commit.", "throwing the changes away - use ek9_git_restore mode:'discard'.", "seeing what is dirty - use ek9_git_status." ],
        "examples" : [ "{mode:'push', message:'wip'} -> status:'stashed', ref:'stash@{0}' (a clean tree -> status:'nothing-to-stash').", "{mode:'pop'} -> status:'popped', ref, conflicts:false; a conflicting pop -> status:'conflicts', stashPreserved:true, nextStep - the entry is kept for a retry." ],
        "details" : "mode:'push' parks the changes (message and includeUntracked apply); 'pop' restores and removes the entry; 'apply' restores and keeps it; 'list' returns stashCount and stashes[] {index, ref, message, branch}; 'drop' deletes an entry (destructive). index (default 0, the most recent) targets pop/apply/drop as stash@{N}; a missing entry is refused as no-stash-entry with the route.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "includeUntracked" : {
              "description" : "Also stash untracked files (push only). Default false.",
              "type" : "boolean"
            },
            "index" : {
              "description" : "Entry for pop/apply/drop, as in stash@{N}. Default 0 (the most recent).",
              "type" : "integer"
            },
            "message" : {
              "description" : "Label for the entry (push only).",
              "type" : "string"
            },
            "mode" : {
              "description" : "push (park the changes), pop (restore and remove), apply (restore and keep), list, drop (delete an entry).",
              "enum" : [ "push", "pop", "apply", "list", "drop" ],
              "type" : "string"
            }
          },
          "required" : [ "mode" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_debug_session",
        "description" : "Runs a program under the debugger - several inputs at once with a change journal (sweep), one run to its end, or step by step - and reports, waits on or ends THE debug session (one per server); answers status, runState, sequence, the stops, breakpoints and nextStep.",
        "idePrecedent" : "Debug (IntelliJ) / VS Code launch.json / gdb run",
        "workflows" : [ "investigate_runtime_defect" ],
        "whenToUse" : "'Why does this input fail when that one works?' is ONE call: {action:'sweep', inputs:[[<failing args>], [<passing args>]], journal:{property:'Type.field', hypothesis}} - every change to the value, on every object, recorded in each run, and the runs compared. 'Run it and tell me what happened' is ONE call too: {action:'launch', run:'toEnd', journal:{property, hypothesis}}. Neither stops the program; ek9_debug_inspect then reads the journals (leadingTo, compare, report). 'Debug this program' step by step starts with {action:'launch'} (breakpoints optional); the debuggee waits at its start until ek9_debug_control continues it or ek9_debug_observe runs it, and {action:'wait', since:N} returns when the session changes.",
        "whenNotToUse" : [ "Just running a program for its output - use ek9_run_program.", "Running @Test programs - use ek9_run_tests." ],
        "examples" : [ "{action:'launch', breakpoints:[{file:'main.ek9', line:12}]} -> status:ok, runState:'running', started:false, sequence:3, breakpoints:[{id:7, line:12, bound:false}].", "{action:'wait', since:3} -> status:ok, runState:'stopped', stops:[{thread:'main', file:'main.ek9', line:12, topLocals:[...]}].", "{action:'launch', args:['3'], run:'toEnd', journal:{property:'Order.total', hypothesis:'...'}} -> runState:'terminated', exitCode:1, journal:{changes:3, symptoms:1, ...}, leadingTo:{account:['t1 input arg 0 = 3', ..., 't7 SYMPTOM Panic ...']}.", "{action:'sweep', inputs:[['1'], ['2'], ['3'], ['4']], journal:{property:'Order.total', hypothesis:'...'}} -> summary:'4 run(s): 1 failing, 3 passing', runs:[{input:1, args:['1'], runId:'...', outcome:'pass', exitCode:0, changes:2, output:['invoice for 100']}, ..., {input:3, outcome:'FAIL', symptom:'t7 Panic ..., uncaught at Orders.ek9:40'}], compare:{differences:['order ... BEFORE ... - in 1/1 failing, 0/3 passing ...']}.", "{action:'status'} after an edit to main.ek9 -> status:stale, staleReason:'sources changed since launch ...; relaunch'." ],
        "details" : "One current session (no ids). launch builds the project with debug information in-process and starts the debuggee with a debug thread cap (maxThreads, default 64). wait blocks until the session's sequence moves past 'since' (by default the last sequence a debug tool answered), or its time limit (default 30 s, at most 100 s); the dispatcher is serial, so nothing else is served while it waits. Every time limit counts from when the call reached the server - a build, a launch and any wait behind another call included; an answer that queued says so (queued). An edit to any source of the jar ends the session (stale); 15 minutes without a debug call ends it too. sweep builds the project ONCE and runs it to its end once per input set, one after another, each with its own journal, within one time limit (timeoutMs, default and at most 100 s, for the whole sweep); sets it had no time for are listed as notRun, to sweep again. Every call is recorded in .ek9/debug-questions.jsonl.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "action" : {
              "default" : "status",
              "description" : "launch a new session, report it, wait for it to change, or end it; sweep runs the program once per input set and compares the runs.",
              "enum" : [ "launch", "status", "wait", "terminate", "sweep" ],
              "type" : "string"
            },
            "args" : {
              "description" : "launch: the program's arguments, as strings, one per parameter; a count that does not fit is refused, naming the parameters.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "breakpoints" : {
              "description" : "EK9 source lines, each {file:'main.ek9', line:12}; the file is its path from projectDir ('orders/main.ek9'), or just its name when only one source has it.",
              "items" : {
                "properties" : {
                  "file" : {
                    "description" : "the .ek9 file: its path from projectDir, or its name when unique",
                    "type" : "string"
                  },
                  "line" : {
                    "description" : "the 1-based line",
                    "type" : "integer"
                  }
                },
                "required" : [ "file", "line" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "env" : {
              "additionalProperties" : {
                "type" : "string"
              },
              "description" : "launch: environment variables for the debuggee only.",
              "type" : "object"
            },
            "failing" : {
              "description" : "sweep: what makes a run a failing one (default uncaught: an Exception or Panic the program did not catch; changeCheck: its journal.changeCheck broke) - as compare judges it.",
              "enum" : [ "uncaught", "anySymptom", "exitCode", "changeCheck" ],
              "type" : "string"
            },
            "inputs" : {
              "description" : "sweep: the input sets, each the program's arguments as strings, one per parameter - every set is checked against the program before any run starts.",
              "items" : {
                "items" : {
                  "type" : "string"
                },
                "type" : "array"
              },
              "type" : "array"
            },
            "journal" : {
              "additionalProperties" : false,
              "description" : "launch, sweep: record every change to this property, on every instance, from the start of the run - before, after, the line and the EK9 stack that made it - into .ek9/debug-journal/<runId>.jsonl, never stopping the program. Caught at every line the compiler says can change it (ek9_query_references' writeSites); a store from any other line is recorded too, marked gap (gapNet). A line stops being journalled after siteCap hits (a capped row; capped in the answers), so a hot loop cannot use up the run's time. The answers carry journal:{file, changes, gaps, bySite, byRoute, missed}. Every row has a tick: cite the ticks of the rows that show each claim you make, and never infer a value from the code alone.",
              "properties" : {
                "calls" : {
                  "description" : "functions or Type.methods whose EVERY call is logged: a call row when it starts (its arguments, who called it) and a return row naming that call's tick (what it returned); a call that throws has no return row. calls counts each, 0 if never run.",
                  "items" : {
                    "type" : "string"
                  },
                  "type" : "array"
                },
                "changeCheck" : {
                  "description" : "a condition that should hold after every change, as firstWhere takes it: 'total >= 0', 'total <= before.total + 100'. A change that breaks it is a symptom AT that change (cause changeCheck, atChange its tick), once per break for each object; changes while it stays broken are counted (stillBroken). Never a stop.",
                  "type" : "string"
                },
                "gapNet" : {
                  "default" : "on",
                  "description" : "on: a store to the property from a line the analysis did not list is caught too, marked gap - a breakpoint on each such store, costing nothing until one runs. off: only the listed lines; every answer then says a store elsewhere is not seen.",
                  "enum" : [ "on", "off" ],
                  "type" : "string"
                },
                "hypothesis" : {
                  "description" : "REQUIRED: what this run is to confirm or refute, in a sentence - it is written into the run row, so the evidence and the reason for gathering it stay together.",
                  "type" : "string"
                },
                "properties" : {
                  "description" : "several properties to journal in one run (at most 5), each Type.property; every change row names its property. Use it when the failure may come from how two values move together.",
                  "items" : {
                    "type" : "string"
                  },
                  "maxItems" : 5,
                  "type" : "array"
                },
                "property" : {
                  "description" : "Type.property (or module::Type.property) to journal.",
                  "type" : "string"
                },
                "siteCap" : {
                  "description" : "how many times one line is journalled before it stops being (default 1000; 0 for no cap) - each hit costs about a millisecond, changed or not. Lower it for long runs and sweeps; raise it, or 0, to catch a change late in a long loop. A capped row and capped in the answers say where and when.",
                  "minimum" : 0,
                  "type" : "integer"
                },
                "stackArgs" : {
                  "additionalProperties" : false,
                  "description" : "argument values on each row's stack.",
                  "properties" : {
                    "frames" : {
                      "description" : "how many of the top frames carry their argument values (default 3; 0 for callables and lines only).",
                      "type" : "integer"
                    }
                  },
                  "type" : "object"
                },
                "stackDepth" : {
                  "description" : "how many of the innermost EK9 frames each row keeps (default 5): what got to the change, not the way in from the entry; a row cut short says outerFrames and its route starts '… ->'.",
                  "type" : "integer"
                },
                "symptoms" : {
                  "description" : "lines where the failure shows: reaching one writes a symptom row with the watched values its frame can see and the last change to each; never a stop. Every EK9 Exception and Panic is a symptom too.",
                  "items" : {
                    "properties" : {
                      "file" : {
                        "type" : "string"
                      },
                      "line" : {
                        "type" : "integer"
                      }
                    },
                    "required" : [ "file", "line" ],
                    "type" : "object"
                  },
                  "type" : "array"
                }
              },
              "required" : [ "hypothesis" ],
              "type" : "object"
            },
            "maxThreads" : {
              "description" : "launch: the debug thread cap - at most this many task threads alive (default 64).",
              "type" : "integer"
            },
            "program" : {
              "description" : "launch: module::Program (or a bare name only one program has) when the project declares several; a program that is not there is refused, listing the programs.",
              "type" : "string"
            },
            "projectDir" : {
              "description" : "Project root holding the .ek9 sources - absolute, or relative to the workspace root. Omit to use the workspace root itself (the first root ek9_compile was given).",
              "type" : "string"
            },
            "run" : {
              "description" : "launch: 'toEnd' starts it and answers once - when the program has ended (with the journal's counts and, if something failed, its leadingTo account), when a breakpoint of yours stops it, or at timeoutMs. Exceptions do not stop it unless stopOnExceptions is given; a journal records them as symptoms.",
              "enum" : [ "toEnd" ],
              "type" : "string"
            },
            "seed" : {
              "description" : "launch, sweep: the random seed the program's SystemRandom and OS.random draw from, so a run can be repeated exactly (a sweep gives every set the same one). Left out, one is chosen; every answer says which (seed), and a journal records it as an input row. To check a cause, launch again with the same seed and the suspected input changed.",
              "type" : "integer"
            },
            "since" : {
              "description" : "wait: the sequence to wait past (default: the last one a debug tool answered, so a stop that landed before the wait arrived is answered at once).",
              "type" : "integer"
            },
            "stopOnExceptions" : {
              "default" : "uncaught",
              "description" : "launch: stop on an EK9 Exception or Panic where it is thrown - only when nothing in the program catches it, every one, or none.",
              "enum" : [ "none", "uncaught", "all" ],
              "type" : "string"
            },
            "timeoutMs" : {
              "description" : "wait: how long to wait (default 30000, at most 100000); launch with run: and sweep, for every run together (default and at most 100000). Counted from when the call reached the server.",
              "type" : "integer"
            },
            "until" : {
              "default" : "all",
              "description" : "sweep: run every set, or stop after the first failing run.",
              "enum" : [ "all", "firstFailure" ],
              "type" : "string"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_debug_control",
        "description" : "Continues, pauses, steps or sets breakpoints in the current debug session; answers its new runState, sequence and stops.",
        "idePrecedent" : "Resume / Pause / Step Over / Step Into / Step Out / Toggle Breakpoint (IntelliJ)",
        "whenToUse" : "'Run on', 'pause' or 'step' the current debug session is one call: {action:'continue'} (also starts a debuggee waiting at its start), {action:'pause'}, {action:'stepOver'}, or {action:'setBreakpoints', file, lines}. Stop only when i == 6, on the 3rd hit, or print and run on: {action:'setBreakpoints', file, breakpoints:[{line, condition, hitCondition, logMessage}]}. Stop when a value changes: {action:'setDataBreakpoint', path}. Feed a program reading stdin: {action:'sendInput', text}, then {action:'endInput'} (Ctrl-D). Run on to a line: {action:'runTo', file, line}. A crash stops where it is thrown (uncaught, the default); {action:'setExceptionBreakpoints', stopOn:'all'|'none'}.",
        "whenNotToUse" : [ "Starting a session - use ek9_debug_session {action:'launch'}.", "Running to a condition while collecting values - use ek9_debug_observe." ],
        "examples" : [ "{action:'continue'} -> status:ok, runState:'running', nextStep:'ek9_debug_session {action:'wait', ...}'.", "{action:'stepOver'} -> status:ok, runState:'stopped', stops:[{reason:'step', line:13, ...}].", "{action:'pause'} -> status:ok, runState:'stopped', stops:[{reason:'pause', thread:'main', line:11, ...}].", "{action:'setBreakpoints', file:'main.ek9', lines:[12, 20]} -> breakpoints:[{id:7, line:12, bound:true}, ...].", "{action:'setBreakpoints', file:'main.ek9', breakpoints:[{line:12, condition:{kind:'comparison', path:'i', op:'==', literal:6}}]} -> breakpoints:[{id:7, line:12, condition:'i == 6', hits:0, ...}].", "{action:'setDataBreakpoint', path:'total'} -> dataBreakpoints:[{id:9, path:'total', identity:'#3', hits:0}]; it stops just after each change, and the output says 'total changes: 10 -> 15 (by main.ek9:12)'." ],
        "details" : "threadId picks the stopped thread to act on (default: the most recent stop). pause stops every thread running EK9 code where it is, each with a stop of its own (the main thread is the default after it); a thread already stopped keeps its stop; it ends an observation in progress. setBreakpoints replaces that file's breakpoints (removedBreakpoints names any it took away); a line still set keeps its id. Each of 'breakpoints' may have a condition (the typed condition ek9_debug_observe takes: comparison, setCheck, all, any), judged by field at each hit - never by running the program; a hitCondition {op:'==' | '>=' | '%', count} counting the hits where the condition held; a logMessage, printed with {path} replaced, which never stops. A condition that cannot be judged stops and says why. setDataBreakpoint watches the OBJECT the path reads in the stopped frame (frameId, default the top frame) through any variable, stopping once just after each change; a variable later pointed at another object is not followed, and every write to that field type slows while it is set. A step waits up to about 2 s for its landing stop; if the step runs on past that, wait with ek9_debug_session.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "action" : {
              "description" : "what to do.",
              "enum" : [ "continue", "pause", "stepOver", "stepInto", "stepOut", "setBreakpoints", "setDataBreakpoint", "clearDataBreakpoint", "sendInput", "endInput", "runTo", "setExceptionBreakpoints" ],
              "type" : "string"
            },
            "breakpoints" : {
              "description" : "setBreakpoints: breakpoints that say when they stop, with lines: {line, condition?, hitCondition?: {op, count}, logMessage?}.",
              "items" : {
                "type" : "object"
              },
              "type" : "array"
            },
            "file" : {
              "description" : "setBreakpoints: the .ek9 file - its path from projectDir, or its name when unique.",
              "type" : "string"
            },
            "frameId" : {
              "description" : "setDataBreakpoint: the frame the path is read in (default: the top frame).",
              "type" : "integer"
            },
            "id" : {
              "description" : "clearDataBreakpoint: the data breakpoint's id.",
              "type" : "integer"
            },
            "line" : {
              "description" : "runTo: the line to run on to (with file).",
              "type" : "integer"
            },
            "lines" : {
              "description" : "setBreakpoints: every line to break on in that file (empty clears them).",
              "items" : {
                "type" : "integer"
              },
              "type" : "array"
            },
            "path" : {
              "description" : "setDataBreakpoint: the value to watch, read in the stopped frame: total, rex, names.",
              "type" : "string"
            },
            "stopOn" : {
              "description" : "setExceptionBreakpoints: stop on an EK9 Exception or Panic where it is thrown - only when nothing in the program catches it (the launch default), every one, or none.",
              "enum" : [ "none", "uncaught", "all" ],
              "type" : "string"
            },
            "text" : {
              "description" : "sendInput: what the program reads from stdin; a line ends with \\n, added when missing.",
              "type" : "string"
            },
            "threadId" : {
              "description" : "the stopped thread to act on (default: the most recent stop).",
              "type" : "integer"
            }
          },
          "required" : [ "action" ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_debug_inspect",
        "description" : "Reads what runs recorded - a failure's account, where a value first went wrong, failing and passing runs compared, a report to share - and the current debug session by field (the recorded stop, a frame's locals, path values, the threads), never running the debuggee.",
        "idePrecedent" : "Variables / Watches / Frames / Threads panes (IntelliJ)",
        "workflows" : [ "investigate_runtime_defect" ],
        "whenToUse" : "After a journalled run or sweep: 'what led to the failure?' is {action:'leadingTo', hypothesis:'...'}; 'when did it first go wrong?' is {action:'firstWhere', condition:'total < 0', hypothesis:'...'}; 'what do the failing runs have that the passing ones do not?' is {action:'compare', hypothesis:'...'}; 'write it up for the team' is {action:'report', hypothesis:'...', finding:'the cause and fix, citing t5 and Orders.ek9:36'} - one page to share. At a stop: 'what is happening where it stopped?' is {action:'stop'}; {action:'evaluate', frameId, paths:['rex.legs', 'names[0]', 'total?']} reads values; {action:'locals', frameId} lists a frame's locals.",
        "whenNotToUse" : [ "Collecting values at every hit of a line - use ek9_debug_observe.", "Static questions about the code - use ek9_query_construct or ek9_query_flow." ],
        "examples" : [ "{action:'stop'} -> stops:[{thread:'main', reason:'breakpoint', line:12, frames:[{frameId:9, ...}], topLocals:[{name:'total', type:'Integer', value:'1'}]}].", "{action:'evaluate', frameId:9, paths:['rex.legs', 'names[5]']} -> values:[{path:'rex.legs', value:'4'}, {path:'names[5]', error:'index 5 is out of range: ...'}].", "{action:'leadingTo', hypothesis:'the refund makes it negative'} -> symptom:{tick:6, at:'Orders.ek9:40'}, account:['t1 input arg 0 = 3', 't2 change #4.total 0 -> 100 at Orders.ek9:27 (+=, direct) by Orders -> Order.add(amount=100)', ..., 't6 SYMPTOM Panic ... at Orders.ek9:40 in Orders -> Order.invoice'], envelope:{complete:true, ...}." ],
        "details" : "stop answers from the record made AT the stop, so it is exact even after the thread resumes (lastStop). locals and evaluate read the live frame and need the thread still stopped; frame ids are valid until it resumes. A path is name ('.' field | '[' index ']')* ('?')?; one that cannot be followed gives its reason, never a guess. leadingTo reads the run's change journal (the current session's run, or runId from the same project), never the live program, so it answers after the run has ended: the account is every change to the objects the symptom read - their owners' changes and where the value itself came from - with the inputs, the logged calls and earlier symptoms, oldest first; each line opens with its tick. Cite those ticks for every claim. envelope says what the account cannot hold (missed hits, gaps, steps past limit).",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "action" : {
              "default" : "stop",
              "description" : "what to read; leadingTo, firstWhere, history, routes and slice reduce the run's change journal (history: every change to ONE object, named by its #N), compare (and routes with runs) several runs' journals; report writes those runs as a page to hand a person (index.html, and report.json with the same facts).",
              "enum" : [ "stop", "locals", "evaluate", "threads", "leadingTo", "firstWhere", "history", "routes", "slice", "compare", "report" ],
              "type" : "string"
            },
            "condition" : {
              "description" : "firstWhere: when the value is wrong, as a breakpoint condition: 'total < 0', 'not total?', 'total > 100 and before.total <= 100'; the property's name is its value after a change, before.<name> before it.",
              "type" : "string"
            },
            "failing" : {
              "description" : "compare, report: what makes a run a failing one (default uncaught: an Exception or Panic the program did not catch; changeCheck: its journal.changeCheck broke).",
              "enum" : [ "uncaught", "anySymptom", "exitCode", "changeCheck" ],
              "type" : "string"
            },
            "finding" : {
              "description" : "report: your conclusion - the cause and the fix - citing ticks (t5) and lines (Orders.ek9:36); shown as yours in the report's Finding, which the report never writes itself (default: none).",
              "type" : "string"
            },
            "frameId" : {
              "description" : "locals / evaluate: a frame id from a stop's frames; locals also takes a value's 'reference' and lists what it holds (a List's items, a Dict's values, an object's fields).",
              "type" : "integer"
            },
            "from" : {
              "description" : "history, slice: the first tick to include.",
              "type" : "integer"
            },
            "hypothesis" : {
              "description" : "leadingTo, firstWhere, history, routes, slice, compare: REQUIRED - what you expect the answer to confirm or refute, in a sentence; it is kept with the question.",
              "type" : "string"
            },
            "limit" : {
              "description" : "leadingTo, history (compare: how many differences; routes: how many routes; slice: how many rows, the first): how many change steps to show, the most recent (default 40); the rest are counted in envelope.",
              "type" : "integer"
            },
            "object" : {
              "description" : "history (required): whose changes to list - an object's identity #N as an earlier answer shows it, e.g. #4 from leadingTo's 't2 change #4.total 0 -> 100' (the owner) or a value's identity; the values one property took are its owner's changes to it.",
              "type" : "string"
            },
            "paths" : {
              "description" : "evaluate: paths such as 'total', 'rex.legs', 'names[0]', 'scores[\"bob\"]', 'total?'.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "projectDir" : {
              "description" : "leadingTo, firstWhere, history, routes, slice, compare: the project whose journals to read (default: the session's) - absolute, or relative to the workspace root. With it they answer after terminate, and in a server that never ran the session.",
              "type" : "string"
            },
            "property" : {
              "description" : "routes: one of the watched properties, by field ('total') or 'Type.field' (default: every one, each route tagged with its own).",
              "type" : "string"
            },
            "runId" : {
              "description" : "leadingTo, firstWhere, history, routes, slice: another run's journal in the same project (default: the current session's run, or with projectDir and no session there, the most recent run).",
              "type" : "string"
            },
            "runs" : {
              "description" : "compare, report: the run ids (default: the most recent 20 runs in this project journalling the same property of the same program); routes: the runs to count each route across (default: the one run).",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "symptom" : {
              "description" : "leadingTo: the tick of the symptom to explain (default: the first uncaught exception, else the last symptom); the answer lists the others.",
              "type" : "integer"
            },
            "to" : {
              "description" : "history: the last tick to include; slice: the tick to stop BEFORE (required).",
              "type" : "integer"
            }
          },
          "required" : [ ],
          "type" : "object"
        }
      }, {
        "name" : "ek9_debug_observe",
        "description" : "Runs the current debug session until a condition holds or the budget bites, reading the watched paths at every hit of the given lines; answers stoppedBecause, exact hits and the rows.",
        "idePrecedent" : "Logpoints / tracepoints (IntelliJ, VS Code) with a stop condition",
        "whenToUse" : "'Run until total goes unset and show me how it got there' is one call: {points:[{file, line}], watch:['total','i'], until:{kind:'setCheck', path:'total', negated:true}, budget:{maxHits, maxWallClockMs}}. 'How did total become wrong?' is one call too, while stopped: {changesOf:['total'], budget} answers every change with before, after and the line that made it.",
        "whenNotToUse" : [ "Stopping at a line to look around - set a breakpoint with ek9_debug_session or ek9_debug_control.", "Recording every write in a hot loop - that is the compiled-in probes' job, not a JDWP observation." ],
        "examples" : [ "{points:[{file:'loop.ek9', line:23}], watch:['total','i'], until:{kind:'setCheck', path:'total', negated:true}, budget:{maxHits:10000, maxWallClockMs:30000}} -> stoppedBecause:'until', hits:7313, watch:['total','i'], rows:[...{tick:7313, values:['unset', '7313']}].", "{..., until:{kind:'hits', count:500}} -> stoppedBecause:'until', hits:500.", "{..., until:{kind:'comparison', path:'total', op:'==', literal:'x'}} -> stoppedBecause:'conditionUnjudged', detail:'...cannot be compared...'." ],
        "details" : "Starts the debuggee if it waits at its start, and resumes any stopped thread. until is optional (without it the observation runs to the budget or the program's end) and is {kind:'hits', count} or a v1 condition: {kind:'comparison', path, op (== <> < <= > >=), literal} or {kind:'setCheck', path, negated}. The literal's JSON type matters: a number compares with an Integer or Float, a string with a String or Character, true/false with a Boolean (3 and '3' differ). A comparison with an unset value is not met; a condition that cannot be judged ends the observation with the reason. budget is required: maxHits and maxWallClockMs (at most 95000). It ends on until, the budget, a user breakpoint (which still stops) or the program ending - stoppedBecause 'ended' when it finished with exit code 0, 'died' when it failed; ending at a hit leaves that thread stopped there. A point on a line with no code is refused, with the nearest lines that have code. hits is exact; rows keep the values of the first and last 10 hits unless keep says otherwise (at most 100 each), in the compact form unless format is 'records'.",
        "inputSchema" : {
          "additionalProperties" : false,
          "properties" : {
            "budget" : {
              "description" : "required: {maxHits, maxWallClockMs} (maxWallClockMs at most 95000).",
              "properties" : {
                "maxHits" : {
                  "type" : "integer"
                },
                "maxWallClockMs" : {
                  "type" : "integer"
                }
              },
              "required" : [ "maxHits", "maxWallClockMs" ],
              "type" : "object"
            },
            "changesOf" : {
              "description" : "paths read where the program is stopped now ('total', 'counter'): every change to each is recorded - before, after, the line that made it, the thread - and never stops. With changesOf, points may be empty; the observation then ends with the budget or the program.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            },
            "format" : {
              "default" : "compact",
              "description" : "compact: each row is {tick, values:[...]} in watch order, with what every row shares said once (a path unreadable at every hit is in 'unreadable' and 'absent' in the rows, one unreadable at some hits is {error}); records: each row is the full runtime-journal record (SPEC 3.2) with isSet and the object identity #N of every value, or {path, error} for a path it could not read.",
              "enum" : [ "compact", "records" ],
              "type" : "string"
            },
            "keep" : {
              "description" : "how many of the first and last hits keep their values (default {first:10, last:10}, each at most 100); hits stays exact whatever is kept.",
              "properties" : {
                "first" : {
                  "type" : "integer"
                },
                "last" : {
                  "type" : "integer"
                }
              },
              "type" : "object"
            },
            "points" : {
              "description" : "EK9 source lines, each {file:'main.ek9', line:12}; the file is its path from projectDir ('orders/main.ek9'), or just its name when only one source has it.",
              "items" : {
                "properties" : {
                  "file" : {
                    "description" : "the .ek9 file: its path from projectDir, or its name when unique",
                    "type" : "string"
                  },
                  "line" : {
                    "description" : "the 1-based line",
                    "type" : "integer"
                  }
                },
                "required" : [ "file", "line" ],
                "type" : "object"
              },
              "type" : "array"
            },
            "until" : {
              "description" : "optional - without it the observation runs until the budget bites or the program ends. {kind:'hits', count} or a condition: {kind:'comparison', path, op, literal} or {kind:'setCheck', path, negated}. literal is typed JSON: a number (100) for an Integer or Float, a string ('x') for a String or Character, true/false for a Boolean.",
              "type" : "object"
            },
            "watch" : {
              "description" : "paths to read at every hit: 'total', 'rex.legs', 'names[0]', 'scores[\"bob\"]', 'total?'.",
              "items" : {
                "type" : "string"
              },
              "type" : "array"
            }
          },
          "required" : [ "budget" ],
          "type" : "object"
        }
      } ]
    }
  },
  "workflows" : [ {
    "name" : "release",
    "summary" : "Bump version + run tests with coverage + build artefact + install locally + verify.",
    "sequence" : [ {
      "tool" : "ek9_increment_version",
      "reason" : "Bump the appropriate version part (typically patch after a fix)."
    }, {
      "tool" : "ek9_run_tests",
      "reason" : "Verify nothing regressed; produces the coverage report as a side effect."
    }, {
      "tool" : "ek9_run_quality_checks",
      "reason" : "Run full quality checks — coverage, complexity, readability, DI — to confirm nothing has degraded since the last release."
    }, {
      "tool" : "ek9_package",
      "reason" : "Build the deployable artefact at the new version."
    }, {
      "tool" : "ek9_install_package",
      "reason" : "Install into ~/.ek9/lib so sibling workspaces can pick it up."
    }, {
      "tool" : "ek9_query_package_version",
      "reason" : "Verify the installed version matches what was bumped."
    } ]
  }, {
    "name" : "feature_branch_start",
    "summary" : "Set feature-branch version + add new dependencies + resolve + verify.",
    "sequence" : [ {
      "tool" : "ek9_set_feature_version",
      "reason" : "Mark the package as on a feature branch (e.g. 2.1.0-PAYINTEG-0)."
    }, {
      "tool" : "ek9_add_dependency",
      "reason" : "Declare each new package dependency the feature requires."
    }, {
      "tool" : "ek9_resolve_dependencies",
      "reason" : "Pull the new dependencies into ~/.ek9/lib."
    }, {
      "tool" : "ek9_compile",
      "reason" : "Verify the package still compiles with the new deps."
    }, {
      "tool" : "ek9_run_tests",
      "reason" : "Verify existing tests still pass before any feature work begins."
    } ]
  }, {
    "name" : "investigate_build_failure",
    "summary" : "Diagnose dependency-resolution or compilation failure: diagnostics → tree → cycles → fix → re-resolve.",
    "sequence" : [ {
      "tool" : "ek9_query_diagnostics",
      "reason" : "Read the specific compiler/resolver error messages."
    }, {
      "tool" : "ek9_query_dependency_tree",
      "reason" : "See the resolved tree with version selections and any rejection reasons."
    }, {
      "tool" : "ek9_check_package_dependency_cycles",
      "reason" : "Rule out a transitive cycle in the published-package graph."
    }, {
      "tool" : "ek9_resolve_dependencies",
      "reason" : "Re-resolve after any fix to the package directive."
    }, {
      "tool" : "ek9_compile",
      "reason" : "Confirm the build now succeeds."
    } ]
  }, {
    "name" : "investigate_runtime_defect",
    "summary" : "A runtime failure whose cause is earlier: journal the value over failing AND passing runs -> compare -> leadingTo -> firstWhere -> confirm with one input changed -> report with a finding.",
    "sequence" : [ {
      "tool" : "ek9_debug_session",
      "reason" : "{action:'launch', run:'toEnd', args:<the failing input>}: reproduce the failure once - its message usually names the value that went wrong (a Panic on 'require total >= NOTHING' in Order.invoice -> watch Order.total)."
    }, {
      "tool" : "ek9_debug_session",
      "reason" : "{action:'sweep', inputs:[...], journal:{property:'Type.property', hypothesis}}: one run per input set, all with ONE seed. Include inputs you expect to PASS as well as the failing one - compare needs both; every change to the value, on every object, is recorded. Its answer already carries compare's top differences, and the investigation id."
    }, {
      "tool" : "ek9_debug_inspect",
      "reason" : "{action:'compare', runs, hypothesis}: what only the failing runs did - a step they took, a step they LEFT OUT (onlyInPassing), or none (then the values differ). A tie or thin evidence means more inputs, not a verdict. Cite each difference by its run and ticks."
    }, {
      "tool" : "ek9_debug_inspect",
      "reason" : "{action:'leadingTo', runId:<a failing run>, hypothesis}: the account - the changes that led to its failure, each with the line, the values and the calls that made it. Cite ticks for every claim."
    }, {
      "tool" : "ek9_debug_inspect",
      "reason" : "{action:'firstWhere', runId, condition:'total < 0', hypothesis}: the step where the value first went wrong - usually the line to fix."
    }, {
      "tool" : "ek9_debug_session",
      "reason" : "{action:'launch', run:'toEnd', seed:<the sweep's>, args:<one input changed>, journal}: confirm the suspect - the same run with only the suspect's input changed should pass. Without this, a difference is a suspect, not a cause."
    }, {
      "tool" : "ek9_debug_inspect",
      "reason" : "{action:'report', runs, hypothesis, finding:'cause and fix, citing t5 and Orders.ek9:36'}: write the investigation up for the person (index.html, report.json); the finding is shown as yours."
    } ]
  } ]
}