{
  "schema": "sonny.docs.tool-metadata/v1",
  "qualification": {
    "version": "0.12.7",
    "source_commit": "192e590345c6a4bf1ef8f72b043b318084ea84d6",
    "binary_sha256": "45a7ae62c66c15693790de1905b72502b75e7889682e4e58cbb4e88756ba6879",
    "command": "sonny tools list --json",
    "exit": 0,
    "scope": "Installed Linux declaration metadata in a private environment; not execution or service/client/platform qualification",
    "raw_stdout_sha256": "bf827709385511d57368c6124426c4d96045307a210b77526857979cac52e813"
  },
  "metadata": {
    "pack_file": "",
    "source": "sonny tools list --json",
    "tool_count": 72,
    "tools": [
      {
        "category": "canary",
        "description": "Run Go tests; report evidence origin and eligibility. Canary's run-go-test producer persists the result: canary launches its own resolved `go test` with your test args, retains the raw artifact, and the emitted evidence is ingested into .canary/evidence.json. Records are origin=executed (the only kind canary_verify counts) ONLY when the workspace's .canary/project.yaml names the toolchain (evidence.toolchain_path); otherwise they are recorded as imported. exit_code reports producer/ingest completion, not the test result; run_exit_status reports the observed Go test status even with no matching records, or null if unobserved. Neither establishes verification. run identifies this observed source/project/commit/artifact/toolchain independently of record samples; producer_identity is empty unless evidence was signed. run_truncated explicitly reports an identity omitted for the response budget, which fails the wrapper. Record samples and eligibility counts reflect this invocation after successful ingest, including policy demotions, not prior history. Set preflight=true to inspect configured availability here without running tests or writing evidence; trust_qualified remains false and next actions explain what is missing. ONLY `go test \u2026 -json` argv is accepted. Requires a clean git tree unless allow_dirty \u2014 dirty evidence is recorded but never counts toward verification.",
        "name": "canary_evidence",
        "parameters": {
          "properties": {
            "allow_dirty": {
              "description": "Permit a dirty working tree; the records are stamped dirty and excluded from verification.",
              "type": "boolean"
            },
            "argv": {
              "description": "go test argv, e.g. [\"go\",\"test\",\"-json\",\"./...\"] \u2014 must start with go test and include -json.",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "preflight": {
              "description": "Inspect producer availability without tests or evidence writes; availability does not qualify trust.",
              "type": "boolean"
            }
          },
          "required": [
            "argv"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "evidence"
      },
      {
        "category": "canary",
        "description": "Build or refresh the CANARY token index (.canary/canary.db) by scanning the workspace. Run after editing CANARY tokens and before view/next/search on a fresh workspace.",
        "name": "canary_index",
        "parameters": {
          "properties": {},
          "type": "object"
        },
        "source": "native",
        "subcategory": "index"
      },
      {
        "category": "canary",
        "description": "Preview migration with plan=true; apply with expected_plan_id to guard reviewed inputs. Preserves informational evidence; adoption is explicit.",
        "name": "canary_migrate",
        "parameters": {
          "properties": {
            "expected_plan_id": {
              "description": "Apply only if current inputs match the reviewed preview plan ID.",
              "type": "string"
            },
            "plan": {
              "description": "Read-only bounded preview; does not initialize tracking or change routing.",
              "type": "boolean"
            },
            "plan_details": {
              "description": "Include before/after documents and database values; requires plan=true.",
              "type": "boolean"
            },
            "plan_limit": {
              "description": "Preview page size from 1 through 100; default 20; requires plan=true.",
              "type": "integer"
            },
            "plan_offset": {
              "description": "Preview offset from 0 through 1000000; requires plan=true.",
              "type": "integer"
            },
            "tracking_mode": {
              "description": "Explicit routing choice; omit to preserve mode. native_canary restores native routing without migration or deleting records. coexist/trace require validated parity.",
              "enum": [
                "native_canary",
                "coexist",
                "trace"
              ],
              "type": "string"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "migrate"
      },
      {
        "category": "canary",
        "description": "Select the next actionable CANARY requirement token (priority-aware, dependency-aware). Optional status/aspect filters.",
        "name": "canary_next",
        "parameters": {
          "properties": {
            "aspect": {
              "description": "Only tokens with this ASPECT.",
              "type": "string"
            },
            "status": {
              "description": "Only tokens with this STATUS (STUB, IMPL, TESTED, BENCHED).",
              "type": "string"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "next"
      },
      {
        "category": "canary",
        "description": "Read-only CANARY adoption analysis of this workspace: languages, directories, entry points, token count, configured requirement-ID sources, next id, suggested next steps. Bounded output; works before any index exists.",
        "name": "canary_onboard",
        "parameters": {
          "properties": {
            "limit": {
              "description": "Max entries per list section (default 20, max 100).",
              "type": "integer"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "onboard"
      },
      {
        "category": "canary",
        "description": "Keyword search over the indexed CANARY tokens (requirement id, feature, aspect, keywords). Compact summaries only.",
        "name": "canary_search",
        "parameters": {
          "properties": {
            "limit": {
              "description": "Max results (default 20, max 100).",
              "type": "integer"
            },
            "query": {
              "description": "Search keywords.",
              "type": "string"
            }
          },
          "required": [
            "query"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "search"
      },
      {
        "category": "canary",
        "description": "Verify claimed requirements against recorded evidence at HEAD (canary's evidence model: only origin=executed, non-dirty, PASS records count). Call before claiming completion.",
        "name": "canary_verify",
        "parameters": {
          "properties": {
            "claims_path": {
              "description": "Claims file relative to the workspace root (default GAP_ANALYSIS.md).",
              "type": "string"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "verify"
      },
      {
        "category": "canary",
        "description": "One-call hierarchical aggregate for one requirement id: status counts, declared-completion %, features, files, tests, dependencies, drift. Use this INSTEAD of separate list/show/status calls.",
        "name": "canary_view",
        "parameters": {
          "properties": {
            "limit": {
              "description": "Max entries per list section (default 20, max 100).",
              "type": "integer"
            },
            "req_id": {
              "description": "Requirement id, e.g. CBIN-100.",
              "type": "string"
            }
          },
          "required": [
            "req_id"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "view"
      },
      {
        "category": "build",
        "description": "Parse .gitlab-ci.yml (including local includes, as used by the devnw ci-catalog) and list the stages, jobs, each job's stage/image/script/allow_failure, and any remote includes that were not merged. Use this to understand the pipeline before running a job locally. Note: extends: is NOT resolved \u2014 a job whose script is inherited from a hidden .template job via extends: will appear with no script and cannot be run via ci_run_job.",
        "name": "ci_list_jobs",
        "parameters": {
          "properties": {
            "config_file": {
              "description": "Path to a CI file (default .gitlab-ci.yml in work_dir)",
              "type": "string"
            },
            "work_dir": {
              "description": "Directory containing .gitlab-ci.yml",
              "type": "string"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "ci"
      },
      {
        "category": "build",
        "description": "Run a GitLab CI job's `script` lines locally (joined with && so it stops on the first failure). Does not provision the job's container image or services \u2014 it runs in the current environment. Note: extends: is NOT resolved \u2014 jobs that inherit their script via extends: from a hidden .template job will appear with no script and will fail with 'job has no script'.",
        "name": "ci_run_job",
        "parameters": {
          "properties": {
            "config_file": {
              "description": "Path to a CI file (default .gitlab-ci.yml in work_dir)",
              "type": "string"
            },
            "job": {
              "description": "Name of the job to run",
              "type": "string"
            },
            "timeout": {
              "description": "Timeout in seconds (default 300)",
              "type": "integer"
            },
            "work_dir": {
              "description": "Directory containing .gitlab-ci.yml",
              "type": "string"
            }
          },
          "required": [
            "job"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "ci"
      },
      {
        "category": "datacollect",
        "description": "Turn a collected corpus into optimizer training/testing data: harvest windows, split by parent conversation (deterministic, contamination-verified), and write train.suite.yaml + test.suite.yaml + split_manifest.json for `sonny pack optimize --suite-file/--holdout-suite-file`.",
        "name": "conv_dataset",
        "parameters": {
          "properties": {
            "corpus": {
              "description": "Corpus JSONL from conv_collect, relative to the workspace.",
              "type": "string"
            },
            "limit": {
              "description": "Max windows (default 125).",
              "type": "integer"
            },
            "out_dir": {
              "description": "Output directory, relative to the workspace.",
              "type": "string"
            },
            "rules": {
              "description": "Behavior-rules YAML path relative to the workspace (default: embedded starter set).",
              "type": "string"
            },
            "test_ratio": {
              "description": "Holdout fraction by parent (default 0.4).",
              "type": "number"
            },
            "turns": {
              "description": "Window size in turns (default 6).",
              "type": "integer"
            }
          },
          "required": [
            "corpus",
            "out_dir"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "dataset"
      },
      {
        "category": "datacollect",
        "description": "Report which local AI-assistant conversation sources have data on this machine (claude, codex, copilot, gemini, continue, cursor, sonny; aider opt-in). HEAVYWEIGHT: parses every source file to count sessions/messages; nothing is written.",
        "name": "conv_sources",
        "parameters": {
          "properties": {
            "include_aider": {
              "description": "Also probe aider (walks all of $HOME).",
              "type": "boolean"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "sources"
      },
      {
        "category": "utility",
        "description": "Return the current calendar date (YYYY-MM-DD) plus weekday and day-of-year. Optional tz (IANA, default UTC) and format (Go time layout) overrides. Call this instead of guessing today's date when one is required.",
        "name": "date",
        "parameters": {
          "properties": {
            "format": {
              "description": "Go time-layout override (e.g. \"Mon 2 Jan 2006 15:04 MST\").",
              "type": "string"
            },
            "tz": {
              "description": "IANA timezone (e.g. America/Los_Angeles). Default UTC.",
              "type": "string"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "clock"
      },
      {
        "category": "utility",
        "description": "Return the current timestamp in multiple representations: RFC3339 UTC, RFC3339 local, date, time, Unix seconds + millis, timezone, weekday. Optional tz and format overrides.",
        "name": "datetime",
        "parameters": {
          "properties": {
            "format": {
              "description": "Go time-layout override (e.g. \"Mon 2 Jan 2006 15:04 MST\").",
              "type": "string"
            },
            "tz": {
              "description": "IANA timezone (e.g. America/Los_Angeles). Default UTC.",
              "type": "string"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "clock"
      },
      {
        "category": "workspace",
        "description": "Show the top-level files and README of a named project template (from list_project_templates). Read-only.",
        "name": "describe_project_template",
        "parameters": {
          "properties": {
            "name": {
              "description": "Template name as returned by list_project_templates",
              "type": "string"
            },
            "root": {
              "description": "Root to scan (default ~/src/devnw)",
              "type": "string"
            }
          },
          "required": [
            "name"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "template"
      },
      {
        "categories": [
          "config"
        ],
        "category": "build",
        "description": "Detect which language toolchains and build systems a directory uses (go.mod, package.json, pyproject.toml, build.zig, Cargo.toml, Makefile, .gitlab-ci.yml). Returns the detected toolchains and the actions each supports. Use this before calling toolchain_run.",
        "name": "detect_project",
        "parameters": {
          "properties": {
            "work_dir": {
              "description": "Directory to scan (defaults to the tool's base dir)",
              "type": "string"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "toolchain"
      },
      {
        "category": "utility",
        "description": "Get system diagnostics including OS, architecture, memory stats, and optionally environment variables.",
        "name": "diagnostics",
        "parameters": {
          "properties": {
            "include_env": {
              "description": "Include environment variables (sensitive vars are filtered)",
              "type": "boolean"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "system"
      },
      {
        "category": "workspace",
        "description": "Compare two files (unified diff) or two directories (recursive=true, returns added/removed/changed file list). Use this to verify 'duplicate file' or 'identical to X' claims before publishing them. A diff of two files stops after 120s and reports that it timed out.",
        "file_types": [
          "any"
        ],
        "name": "diff",
        "parameters": {
          "properties": {
            "brief_only": {
              "description": "If true, return only names of differing files (-q)",
              "type": "boolean"
            },
            "context": {
              "description": "Unified diff context lines (default 3; a value over 10000 is clamped to 10000)",
              "type": "integer"
            },
            "left": {
              "description": "Left-hand path",
              "type": "string"
            },
            "recursive": {
              "description": "Required for directory diff",
              "type": "boolean"
            },
            "right": {
              "description": "Right-hand path",
              "type": "string"
            }
          },
          "required": [
            "left",
            "right"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "inspect"
      },
      {
        "category": "network",
        "description": "Download a file from a URL to a local path.",
        "file_types": [
          "any"
        ],
        "name": "download",
        "parameters": {
          "properties": {
            "overwrite": {
              "description": "If true, overwrite existing file",
              "type": "boolean"
            },
            "path": {
              "description": "Local path to save the file, resolved inside the workspace root; a path outside it is refused",
              "type": "string"
            },
            "timeout": {
              "description": "Timeout in seconds",
              "type": "integer"
            },
            "url": {
              "description": "URL of the file to download",
              "type": "string"
            }
          },
          "required": [
            "url",
            "path"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "http"
      },
      {
        "category": "workspace",
        "description": "Edit a file. Literal mode: replace old_text with new_text (all=true for every occurrence). Regex mode (regex=true): substitute RE2 pattern with replacement ($1 capture refs). Line mode (start_line/end_line + operation): replace/insert/delete by line range. dry_run=true previews without writing.",
        "file_types": [
          "any"
        ],
        "name": "edit_file",
        "parameters": {
          "properties": {
            "all": {
              "description": "If true, replace all occurrences",
              "type": "boolean"
            },
            "dry_run": {
              "description": "If true, preview changes without applying",
              "type": "boolean"
            },
            "end_line": {
              "description": "1-based last line, inclusive (line mode; defaults to start_line).",
              "type": "integer"
            },
            "expected_digest": {
              "description": "Precondition: sha256:<hex> digest the file must currently have. The host attaches this automatically from your last read; a mismatch refuses the write with a stale_resource conflict.",
              "type": "string"
            },
            "new_text": {
              "description": "Text to replace with",
              "type": "string"
            },
            "old_text": {
              "description": "Text to find and replace",
              "type": "string"
            },
            "operation": {
              "description": "Line-mode operation: replace | insert_before | insert_after | delete.",
              "type": "string"
            },
            "path": {
              "description": "Path to the file to edit",
              "type": "string"
            },
            "pattern": {
              "description": "RE2 regular expression to match (regex mode). `.` does not match a newline; prefix (?s) to match across lines.",
              "type": "string"
            },
            "regex": {
              "description": "If true, treat old_text/pattern as an RE2 regular expression: match `pattern` and substitute `replacement` (with $1 capture references). Use all=true for every match.",
              "type": "boolean"
            },
            "replacement": {
              "description": "Replacement text for regex mode; supports $1/${name} capture references.",
              "type": "string"
            },
            "start_line": {
              "description": "1-based first line of the range (line mode). replace/delete: 1..line_count. insert_before: 1..line_count+1. insert_after: 0..line_count (0 inserts at the top, line_count appends).",
              "type": "integer"
            }
          },
          "required": [
            "path"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "file"
      },
      {
        "categories": [
          "config"
        ],
        "category": "context",
        "description": "Read one environment variable from this session's declared allowlist. Effect: sensitive_read. Names outside the allowlist are denied with a stable code regardless of whether the host holds them.",
        "name": "env_get",
        "parameters": {
          "properties": {
            "name": {
              "description": "Name of the environment variable",
              "type": "string"
            }
          },
          "required": [
            "name"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "environment"
      },
      {
        "category": "process",
        "description": "Execute a command via the system shell (default on this host: bash). On Windows the default is PowerShell/cmd, so write PowerShell syntax unless you set shell=cmd. Returns stdout, stderr, and exit code. The call ends when the shell exits, and processes the command leaves running (for example `server &`) are then terminated, with a warning. For a long-running command use background=true: it runs as a job (see job_output and job_kill), and what the job leaves running is likewise terminated when its shell exits; to keep a server or daemon running, start it with background=true as the job's own foreground command (no trailing &, not self-daemonizing) and stop it with job_kill. A process that starts a new session (for example with setsid) may escape that termination, and is then out of reach of job_kill and of any timeout; whether it escapes is timing-dependent (a `setsid cmd &` at the end of a command is usually terminated), so do not rely on it either way. (This tool was previously named bash; that name is still accepted.)",
        "name": "exec",
        "parameters": {
          "properties": {
            "background": {
              "description": "If true, run the command as a background job and return its job ID at once",
              "type": "boolean"
            },
            "command": {
              "description": "Command to execute in the selected shell",
              "type": "string"
            },
            "env": {
              "description": "Additional environment variables (KEY=VALUE format)",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "shell": {
              "description": "Interpreter: sh, bash, pwsh, powershell, or cmd. Omit to use the host default.",
              "type": "string"
            },
            "timeout": {
              "description": "Timeout in seconds, not milliseconds (default 120, maximum 1800; a larger value is clamped to 1800 with a warning). Pass a larger timeout for builds and test runs that can exceed 2 minutes. With background=true it limits the job's run time and has no default or maximum unless the host sets one.",
              "type": "integer"
            },
            "work_dir": {
              "description": "Working directory for the command",
              "type": "string"
            }
          },
          "required": [
            "command"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "shell"
      },
      {
        "category": "network",
        "description": "Fetch content from a URL. Supports GET, POST, and other HTTP methods.",
        "name": "fetch",
        "parameters": {
          "properties": {
            "body": {
              "description": "Request body (for POST, PUT, PATCH)",
              "type": "string"
            },
            "headers": {
              "description": "HTTP headers to include. Only standard content-negotiation and conditional headers are accepted (Accept, Accept-Charset, Accept-Encoding, Accept-Language, Cache-Control, Content-Language, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Range, If-Unmodified-Since, Pragma, Range, User-Agent), plus any the operator has explicitly allowed; any other header refuses the whole request. Credential, routing, proxy and hop-by-hop headers are always refused.",
              "type": "object"
            },
            "method": {
              "description": "HTTP method (GET, POST, PUT, DELETE, etc.)",
              "enum": [
                "GET",
                "POST",
                "PUT",
                "DELETE",
                "PATCH",
                "HEAD"
              ],
              "type": "string"
            },
            "timeout": {
              "description": "Timeout in seconds (default 30)",
              "type": "integer"
            },
            "url": {
              "description": "URL to fetch (must be http or https)",
              "type": "string"
            }
          },
          "required": [
            "url"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "http"
      },
      {
        "category": "workspace",
        "description": "Return size, line count, mode, mtime, MIME, and optional sha256 for a single file or directory. Does NOT return file content. Use this to learn how big a file is before deciding whether to read it whole or in windows.",
        "file_types": [
          "any"
        ],
        "name": "file_stat",
        "parameters": {
          "properties": {
            "path": {
              "description": "Path (relative to workdir or absolute)",
              "type": "string"
            },
            "sha256": {
              "description": "If true, compute sha256 of the file",
              "type": "boolean"
            }
          },
          "required": [
            "path"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "inspect"
      },
      {
        "category": "workspace",
        "description": "Find things in the workspace: search file contents (kind 'text'), find files by name or glob (kind 'file'), list a directory tree (kind 'dir'), or drain a live watch (kind 'watch'). Text search scans at most 1 GiB across 100000 files (plus one EOF lookahead byte), with 10 context lines per side and 96 KiB of charged result content/overhead. A walk stops after 20 seconds. Budget or time exhaustion reports partial and truncated results with lower-bound counts; narrow the scope (path, max_depth, exts, or a directory in the pattern such as 'proto/**') to continue.",
        "file_types": [
          "any"
        ],
        "name": "find",
        "parameters": {
          "properties": {
            "context_lines": {
              "description": "Kind 'text': context lines around each match, clamped to 0..10",
              "type": "integer"
            },
            "count_only": {
              "description": "Kind 'text': return only per-file match counts",
              "type": "boolean"
            },
            "exclude": {
              "description": "Globs on the relative path to exclude; exclude wins over include",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "exts": {
              "description": "Extension allow-list ('.go' and 'go' are both accepted)",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "files_only": {
              "description": "Kind 'text': return only matching file names",
              "type": "boolean"
            },
            "ignore_case": {
              "description": "Kind 'text': case-insensitive matching",
              "type": "boolean"
            },
            "include": {
              "description": "Globs on the relative path to include",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "kind": {
              "description": "What to find: 'text' searches file contents, 'file' finds files by name or glob, 'dir' lists a directory tree, 'watch' drains a live watch",
              "enum": [
                "text",
                "file",
                "dir",
                "watch"
              ],
              "type": "string"
            },
            "max_depth": {
              "description": "Maximum directory depth; 0 is unlimited. Kind 'dir' defaults to 1 unless 'recursive' is true",
              "type": "integer"
            },
            "max_results": {
              "description": "Maximum number of results (default 1000, 500 for kind 'dir'; hard ceiling 10000)",
              "type": "integer"
            },
            "max_size": {
              "description": "Maximum file size in bytes",
              "type": "integer"
            },
            "mime": {
              "description": "MIME patterns to include (e.g. 'text/*')",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "min_size": {
              "description": "Minimum file size in bytes",
              "type": "integer"
            },
            "modified_after": {
              "description": "Only entries modified after this RFC3339 timestamp",
              "type": "string"
            },
            "modified_before": {
              "description": "Only entries modified before this RFC3339 timestamp",
              "type": "string"
            },
            "path": {
              "description": "Directory to search in, relative to the workspace (defaults to the workspace root)",
              "type": "string"
            },
            "pattern": {
              "description": "For kind 'text', a regular expression to search for. For kinds 'file' and 'dir', a glob with real '**' recursion (e.g. '**/*.go') unless 'regex' is true",
              "type": "string"
            },
            "recursive": {
              "description": "Kind 'dir': list the whole tree instead of one level",
              "type": "boolean"
            },
            "regex": {
              "description": "Treat 'pattern' as a regular expression matched against the relative path. A pattern that is not a valid regular expression (such as '*_test.go') is matched as a glob, each '|' alternative its own glob, and a warning says so",
              "type": "boolean"
            },
            "show_hidden": {
              "description": "Include dot-files and dot-directories (default false, which also prunes .git, node_modules, vendor and __pycache__ and, for kinds 'text' and 'file', paths the workspace's .gitignore files exclude; a path you name explicitly is always searched). Without it, a kind 'file' or 'dir' glob with a segment starting with '.' (e.g. '.gitlab-ci.yml', '.github/*') still matches dot-files, .git excepted, and a notice counts the dot-files a result skipped",
              "type": "boolean"
            },
            "stop": {
              "description": "Kind 'watch': stop and forget the watch after draining",
              "type": "boolean"
            },
            "type": {
              "description": "Entry type to report: 'file', 'directory' or 'any'",
              "enum": [
                "file",
                "directory",
                "any"
              ],
              "type": "string"
            },
            "watch": {
              "description": "After the walk, start a recursive watch on 'path' and return its id",
              "type": "boolean"
            },
            "watch_duration": {
              "description": "Watch time-to-live in seconds (default 600, maximum 600)",
              "type": "integer"
            },
            "watch_id": {
              "description": "Kind 'watch': which watch to drain",
              "type": "string"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "search"
      },
      {
        "category": "workspace",
        "description": "Return the first N lines (default 50) of a file with absolute line numbers. Use this for fast partial reads instead of pulling the whole file into context.",
        "file_types": [
          "any"
        ],
        "name": "head_file",
        "parameters": {
          "properties": {
            "lines": {
              "description": "Number of leading lines (default 50)",
              "type": "integer"
            },
            "path": {
              "description": "Path to the file",
              "type": "string"
            }
          },
          "required": [
            "path"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "inspect"
      },
      {
        "category": "process",
        "description": "Kill a background job and every process in its process group: they are sent SIGTERM, and any still running 2s later SIGKILL (force=true sends SIGKILL at once; on Windows they are terminated at once either way). The job reads \"stopping\" until they have all exited, then \"killed\". A job whose shell has already exited is not killed and ends with its own status. A process that started a new session (for example with setsid) may have left the job's process group and is then out of reach; that is timing-dependent, so do not rely on it either way.",
        "name": "job_kill",
        "parameters": {
          "properties": {
            "force": {
              "description": "If true, send SIGKILL at once instead of SIGTERM followed by SIGKILL after 2s",
              "type": "boolean"
            },
            "job_id": {
              "description": "ID of the background job to kill",
              "type": "integer"
            }
          },
          "required": [
            "job_id"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "job"
      },
      {
        "category": "process",
        "description": "List all background jobs and their status.",
        "name": "job_list",
        "parameters": {
          "properties": {},
          "type": "object"
        },
        "source": "native",
        "subcategory": "job"
      },
      {
        "category": "process",
        "description": "Get the output, status (starting, running, stopping, completed, failed, killed or timed_out) and warnings of a background job. Without wait_seconds it returns the current snapshot immediately. To wait for the job to finish, pass wait_seconds (up to 300): the call blocks until the job ends or wait_seconds elapse, whichever comes first, and returns the same result, with status still running on a timeout. Wait with wait_seconds rather than calling job_output repeatedly.",
        "name": "job_output",
        "parameters": {
          "properties": {
            "job_id": {
              "description": "ID of the background job",
              "type": "integer"
            },
            "wait_seconds": {
              "description": "Optional seconds to wait for the job to end before returning (0-300; larger values are clamped to 300). 0 or absent returns the current snapshot immediately.",
              "type": "integer"
            }
          },
          "required": [
            "job_id"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "job"
      },
      {
        "categories": [
          "data"
        ],
        "category": "context",
        "description": "Query JSON data using dot notation paths. Useful for extracting specific values from JSON tool responses or configuration files.",
        "file_types": [
          "json"
        ],
        "name": "json_query",
        "parameters": {
          "properties": {
            "json_data": {
              "description": "JSON string to query",
              "type": "string"
            },
            "path": {
              "description": "Dot notation path to extract (e.g., 'data.items[0].name', 'results.*.id')",
              "type": "string"
            }
          },
          "required": [
            "json_data",
            "path"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "query"
      },
      {
        "category": "workspace",
        "description": "List the project scaffolding templates kept under ~/src/devnw (template-repos/* and <lang>/<lang>-template). Returns each template's name, path, detected language, and a one-line description. Read-only.",
        "name": "list_project_templates",
        "parameters": {
          "properties": {
            "root": {
              "description": "Root to scan (default ~/src/devnw)",
              "type": "string"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "template"
      },
      {
        "category": "memory",
        "description": "Delete one stored memory by id. Without scope, removes the deepest (most specific) occurrence only.",
        "name": "memory_delete",
        "parameters": {
          "properties": {
            "id": {
              "description": "The memory id.",
              "type": "string"
            },
            "scope": {
              "description": "Where the memory lives: \"project\" (default; the nearest .sonny directory, shared with anyone using this repo) or \"global\" (your user-wide store).",
              "type": "string"
            }
          },
          "required": [
            "id"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "store"
      },
      {
        "category": "memory",
        "description": "List stored memories (id, title, category, tags, scope \u2014 no content) across the global store and every .sonny directory on the path. Filter by category or tag.",
        "name": "memory_list",
        "parameters": {
          "properties": {
            "category": {
              "description": "Only this category.",
              "type": "string"
            },
            "tag": {
              "description": "Only memories carrying this tag.",
              "type": "string"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "store"
      },
      {
        "category": "memory",
        "description": "Fetch one stored memory by id, full content included. Deeper project scopes shadow global on id collisions.",
        "name": "memory_retrieve",
        "parameters": {
          "properties": {
            "id": {
              "description": "The memory id.",
              "type": "string"
            }
          },
          "required": [
            "id"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "store"
      },
      {
        "category": "memory",
        "description": "Full-text search stored memories (bm25-ranked over title, content, tags, category) across all scopes. Returns snippets; use memory_retrieve for full content.",
        "name": "memory_search",
        "parameters": {
          "properties": {
            "category": {
              "description": "Only this category.",
              "type": "string"
            },
            "query": {
              "description": "Search terms.",
              "type": "string"
            },
            "top_k": {
              "description": "Max results (default 8).",
              "type": "integer"
            }
          },
          "required": [
            "query"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "store"
      },
      {
        "category": "memory",
        "description": "Report the memory scope chain for this workspace: each scope's directory and record count.",
        "name": "memory_stats",
        "parameters": {
          "properties": {},
          "type": "object"
        },
        "source": "native",
        "subcategory": "store"
      },
      {
        "category": "memory",
        "description": "Save a structured memory for later sessions: a fact, preference, decision, or reference worth remembering about this project (scope \"project\", stored under .sonny/memory/) or for you globally (scope \"global\"). Storing to an existing id updates it in place.",
        "name": "memory_store",
        "parameters": {
          "properties": {
            "category": {
              "description": "Free-form category, e.g. \"fact\", \"preference\", \"decision\", \"reference\". Default \"fact\".",
              "type": "string"
            },
            "content": {
              "description": "The memory body: the fact itself, with enough context to be useful cold.",
              "type": "string"
            },
            "id": {
              "description": "Stable kebab-case id (derived from title when omitted).",
              "type": "string"
            },
            "scope": {
              "description": "Where the memory lives: \"project\" (default; the nearest .sonny directory, shared with anyone using this repo) or \"global\" (your user-wide store).",
              "type": "string"
            },
            "tags": {
              "description": "Search keywords.",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "title": {
              "description": "Short human-readable title.",
              "type": "string"
            }
          },
          "required": [
            "title",
            "content"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "store"
      },
      {
        "category": "workspace",
        "description": "Read the contents of a file. Supports reading specific line ranges with skip/len (or offset/limit) parameters. Returns warnings if file is large relative to context window.",
        "file_types": [
          "any"
        ],
        "name": "read_file",
        "parameters": {
          "properties": {
            "len": {
              "description": "Maximum number of lines to read. Alias for 'limit'.",
              "type": "integer"
            },
            "limit": {
              "description": "Maximum number of lines to read. Prefer 'len' for clarity.",
              "type": "integer"
            },
            "offset": {
              "description": "Line number to start reading from (0-based). Prefer 'skip' for clarity.",
              "type": "integer"
            },
            "path": {
              "description": "Path to the file to read (relative to working directory or absolute)",
              "type": "string"
            },
            "refresh": {
              "description": "Consumed by the host/session layer, not by read_file itself: the tool always returns the requested body and ignores this flag. Set it to tell the host not to reuse an unchanged retained copy of the file.",
              "type": "boolean"
            },
            "skip": {
              "description": "Number of lines to skip from start (0-based). Alias for 'offset'.",
              "type": "integer"
            }
          },
          "required": [
            "path"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "file"
      },
      {
        "category": "context",
        "description": "Retrieve un-shown ranges of a PRIOR tool result that Sonny truncated, WITHOUT re-running the tool. Pass the execution_id printed in the truncation envelope (_sonny_execution_id) or notice. Select ranges the same way as find/read_file: grep by pattern (optionally regex/ignore_case) with context_lines, or window by offset+limit lines; cap grep output with max_count, or ask for counts only with count_only. The full output is held in a transient in-memory LRU keyed by tool_call_id, so it can expire or be evicted \u2014 if the id is no longer cached (found=false), re-run the original tool with a narrower scope.",
        "name": "read_tool_output",
        "parameters": {
          "properties": {
            "context_lines": {
              "description": "Lines of surrounding context to include on each side of a match.",
              "minimum": 0,
              "type": "integer"
            },
            "count_only": {
              "description": "Return only match/line counts with empty content.",
              "type": "boolean"
            },
            "execution_id": {
              "description": "tool_call_id of the truncated execution to read (from _sonny_execution_id).",
              "type": "string"
            },
            "ignore_case": {
              "description": "Case-insensitive pattern matching.",
              "type": "boolean"
            },
            "len": {
              "description": "Hard cap on returned bytes; clamped to the tool-result budget.",
              "minimum": 0,
              "type": "integer"
            },
            "limit": {
              "description": "Window mode: maximum number of lines to return.",
              "minimum": 0,
              "type": "integer"
            },
            "max_bytes": {
              "description": "Deprecated alias for len.",
              "minimum": 0,
              "type": "integer"
            },
            "max_count": {
              "description": "Cap the number of matching lines returned in grep mode.",
              "minimum": 0,
              "type": "integer"
            },
            "max_matches": {
              "description": "Deprecated alias for max_count.",
              "minimum": 0,
              "type": "integer"
            },
            "offset": {
              "description": "Window mode (no pattern): skip this many leading lines.",
              "minimum": 0,
              "type": "integer"
            },
            "pattern": {
              "description": "Keep only lines matching this substring (or RE2 regex when regex=true).",
              "type": "string"
            },
            "regex": {
              "description": "Interpret pattern as an RE2 regular expression.",
              "type": "boolean"
            }
          },
          "required": [
            "execution_id"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "retrieve"
      },
      {
        "category": "workspace",
        "description": "Return the last N lines (default 50) of a file with absolute line numbers.",
        "file_types": [
          "any"
        ],
        "name": "tail_file",
        "parameters": {
          "properties": {
            "lines": {
              "description": "Number of trailing lines (default 50)",
              "type": "integer"
            },
            "path": {
              "description": "Path to the file",
              "type": "string"
            }
          },
          "required": [
            "path"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "inspect"
      },
      {
        "category": "utility",
        "description": "A tool for structured thinking and planning. Use this to organize your thoughts before taking action.",
        "name": "think",
        "parameters": {
          "properties": {
            "thought": {
              "description": "Your thought or reasoning about the current task (alias: 'content')",
              "type": "string"
            }
          },
          "required": [
            "thought"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "reasoning"
      },
      {
        "category": "utility",
        "description": "Return the current wall-clock time (HH:MM:SS). Optional tz and format overrides.",
        "name": "time",
        "parameters": {
          "properties": {
            "format": {
              "description": "Go time-layout override (e.g. \"Mon 2 Jan 2006 15:04 MST\").",
              "type": "string"
            },
            "tz": {
              "description": "IANA timezone (e.g. America/Los_Angeles). Default UTC.",
              "type": "string"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "clock"
      },
      {
        "category": "utility",
        "description": "Add a new todo item to track tasks and progress.",
        "name": "todo_add",
        "parameters": {
          "properties": {
            "content": {
              "description": "Description of the todo item",
              "type": "string"
            },
            "priority": {
              "description": "Priority level (low, medium, high)",
              "enum": [
                "low",
                "medium",
                "high"
              ],
              "type": "string"
            }
          },
          "required": [
            "content"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "task"
      },
      {
        "category": "utility",
        "description": "Delete a todo item by ID.",
        "name": "todo_delete",
        "parameters": {
          "properties": {
            "id": {
              "description": "ID of the todo item to delete",
              "type": "integer"
            }
          },
          "required": [
            "id"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "task"
      },
      {
        "category": "utility",
        "description": "List actionable todo items. By default returns only pending and in-progress items. Set include_completed=true for the full list, or set status for one exact status.",
        "name": "todo_list",
        "parameters": {
          "properties": {
            "include_completed": {
              "description": "Return the full todo list, including completed items. Ignored when status selects one exact status.",
              "type": "boolean"
            },
            "status": {
              "description": "Filter by status (pending, in_progress, completed)",
              "enum": [
                "pending",
                "in_progress",
                "completed"
              ],
              "type": "string"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "task"
      },
      {
        "category": "utility",
        "description": "Update an existing todo item's status or content.",
        "name": "todo_update",
        "parameters": {
          "properties": {
            "content": {
              "description": "New content for the todo item",
              "type": "string"
            },
            "id": {
              "description": "ID of the todo item to update",
              "type": "integer"
            },
            "status": {
              "description": "New status (pending, in_progress, completed)",
              "enum": [
                "pending",
                "in_progress",
                "completed"
              ],
              "type": "string"
            }
          },
          "required": [
            "id"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "task"
      },
      {
        "category": "context",
        "description": "Search for the tools you need by describing the task in query, for example \"reconcile ledger balances\" or \"convert a docx file to markdown\". Returns the best-ranked matches among the tools you are allowed to use, including tools that are not in your current tool array. Call an offered tool directly by its name. Tools not in your tools array are listed as local.<name>, mesh.<name> or <source>.<name> with withheld_reason \"exec_only\": run them with tool_exec using the row's name and input_schema. Optional filters (name, category, file_type, scope) narrow a search. Without a query only a short page of tools comes back, so describe what you need. Search here first for any tool not in your tools array, and before using exec for documents or remote services (GitLab, GitHub, Jira), search here for a built-in tool.",
        "name": "tool_search",
        "parameters": {
          "properties": {
            "category": {
              "description": "Optional. A category (workspace), a subcategory (search), or the dotted pair (workspace.search).",
              "type": "string"
            },
            "file_type": {
              "description": "Optional. File type the tool must work on: an extension (json), a dotted one (.json), a file name or a path.",
              "type": "string"
            },
            "limit": {
              "description": "Optional. Maximum rows to return. A search returns at most 8; a call without a query at most 20.",
              "maximum": 200,
              "minimum": 0,
              "type": "integer"
            },
            "name": {
              "description": "Optional. An exact tool name or alias ranks first, a substring second.",
              "type": "string"
            },
            "offset": {
              "description": "Optional. Continue a search's results with the previous result's next_offset; prefer a more specific query.",
              "minimum": 0,
              "type": "integer"
            },
            "query": {
              "description": "Describe the task you need a tool for, in plain words. Results are ranked by relevance to it; at most eight come back, so narrow the words or filters for others.",
              "type": "string"
            },
            "scope": {
              "description": "Optional. all (default), offered (declared this turn and callable now), or withheld (allowed but not declared this turn).",
              "enum": [
                "all",
                "offered",
                "withheld"
              ],
              "type": "string"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "discover"
      },
      {
        "category": "build",
        "description": "Run a language toolchain action: build, test, run, deps (install/tidy/fetch), fmt, clean, info (version), or list (make targets / available actions). Supported languages: go, python, node, zig, rust, make. For 'make' use action='list' to enumerate targets and action='run' with target=<name> to run one. For 'run' actions, 'target' is the package/binary/script and 'args' are forwarded to the program. The response's 'applicable' field reports whether the toolchain's marker/source files (e.g. go.mod/go.sum/*.go) exist in work_dir; use detect_project first if unsure.",
        "file_types": [
          "any"
        ],
        "name": "toolchain_run",
        "parameters": {
          "properties": {
            "action": {
              "description": "build | test | run | deps | fmt | clean | info | list",
              "type": "string"
            },
            "args": {
              "description": "Extra arguments appended to the command",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "env": {
              "description": "Additional KEY=VALUE environment entries. IGNORED by default: the toolchain subprocess does not inherit model-supplied env unless the operator has enabled it via SetAllowModelToolchainEnv(true). Compiler-driver env vars (COMPILER_PATH, GCC_EXEC_PREFIX, FC, ...) are an open-ended RCE class that a name denylist can never fully enumerate; host toolchain config plus 'args' cover legitimate needs without exposing that surface.",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "files": {
              "description": "Specific files for fmt/test where supported",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "fix": {
              "description": "For fmt: write changes in place instead of just checking",
              "type": "boolean"
            },
            "language": {
              "description": "go | python | node | zig | rust | make (alias: 'toolchain' -- the same value detect_project returns under that key)",
              "type": "string"
            },
            "target": {
              "description": "Make target, run package/binary/script, or dependency spec",
              "type": "string"
            },
            "timeout": {
              "description": "Timeout in seconds (default 300)",
              "type": "integer"
            },
            "work_dir": {
              "description": "Working directory (defaults to the tool's base dir; aliases: 'workdir', 'working_directory', 'cwd')",
              "type": "string"
            }
          },
          "required": [
            "language",
            "action"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "toolchain"
      },
      {
        "category": "vcs",
        "description": "Stage files for commit. Use vcs_status after to verify what's staged. In hg this runs hg addremove (new and deleted files); jj tracks files automatically, so it is unsupported there.",
        "file_types": [
          "any"
        ],
        "name": "vcs_add",
        "parameters": {
          "properties": {
            "all": {
              "description": "Stage all changes (-A)",
              "type": "boolean"
            },
            "dry_run": {
              "description": "Only show what would be added; nothing is added (git and hg; hg lists new and deleted files)",
              "type": "boolean"
            },
            "force": {
              "description": "Allow adding ignored files (not in hg, which refuses an ignored path)",
              "type": "boolean"
            },
            "intent": {
              "description": "Record intent to add (-N; not in hg)",
              "type": "boolean"
            },
            "paths": {
              "description": "Files or directories to stage (literal in hg: a glob or ':' pathspec is refused, and [...] matches only the literal name)",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "update": {
              "description": "Stage only tracked files (-u)",
              "type": "boolean"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "mutate"
      },
      {
        "categories": [
          "code"
        ],
        "category": "vcs",
        "description": "Show who last modified each line of a file, useful for understanding code history and ownership. Without a revision it annotates the work tree, uncommitted lines included, and refuses a file the VCS does not track. hg and jj refuse a revision range.",
        "file_types": [
          "any"
        ],
        "name": "vcs_blame",
        "parameters": {
          "properties": {
            "line_end": {
              "description": "End line number for range",
              "type": "integer"
            },
            "line_start": {
              "description": "Start line number for range",
              "type": "integer"
            },
            "path": {
              "description": "File path to blame (required; literal in hg and jj: a glob or ':' pathspec is refused, and [...] matches only the literal name)",
              "type": "string"
            },
            "revision": {
              "description": "Blame this revision's file instead of the work tree's",
              "type": "string"
            },
            "show_email": {
              "description": "Show author email instead of name (git's rendering; hg and jj print their own)",
              "type": "boolean"
            }
          },
          "required": [
            "path"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "inspect"
      },
      {
        "category": "vcs",
        "description": "List, create, or delete branches. Can filter by merged status or containing specific commits. In hg and jj only listing is supported (hg named branches, jj bookmarks): create and delete return an unsupported-operation error, as do the filters marked git only.",
        "name": "vcs_branch",
        "parameters": {
          "properties": {
            "all": {
              "description": "List all branches (local and remote)",
              "type": "boolean"
            },
            "contains": {
              "description": "List branches containing this commit (git only)",
              "type": "string"
            },
            "create": {
              "description": "Create new branch with this name",
              "type": "string"
            },
            "delete": {
              "description": "Delete branch with this name",
              "type": "string"
            },
            "force": {
              "description": "Force delete (even if not merged)",
              "type": "boolean"
            },
            "merged": {
              "description": "List only merged branches (git only)",
              "type": "boolean"
            },
            "no_merged": {
              "description": "List only unmerged branches (git only)",
              "type": "boolean"
            },
            "remote": {
              "description": "List remote branches only (git only)",
              "type": "boolean"
            },
            "start_point": {
              "description": "Start point for new branch",
              "type": "string"
            },
            "verbose": {
              "description": "Show last commit for each branch",
              "type": "boolean"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "mutate"
      },
      {
        "category": "vcs",
        "description": "Create a new commit with staged changes. IMPORTANT: Always run vcs_status first to see what will be committed. Without files, hg commits the changes to tracked files and the adds and removals vcs_add recorded (all adds the deletions); jj commits every change, new files included. hg refuses a named file deleted from disk but not recorded as removed, and an amend with all while a tracked file is so deleted: vcs_add with update records the deletion.",
        "file_types": [
          "any"
        ],
        "name": "vcs_commit",
        "parameters": {
          "properties": {
            "all": {
              "description": "Auto-stage all tracked files (-a; not in jj, which commits new files too)",
              "type": "boolean"
            },
            "allow_empty": {
              "description": "Allow an empty commit (without it, a commit of nothing fails in git, hg and jj)",
              "type": "boolean"
            },
            "amend": {
              "description": "Amend the previous commit",
              "type": "boolean"
            },
            "author": {
              "description": "Override author (format: 'Name <email>'; not in jj)",
              "type": "string"
            },
            "files": {
              "description": "Specific files to commit; only these are committed (in git, hg and jj; literal in hg and jj: a glob or ':' pathspec is refused, and [...] matches only the literal name)",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "message": {
              "description": "Commit message (required unless amend with no-edit; hg also needs one to amend)",
              "type": "string"
            },
            "no_verify": {
              "description": "Skip pre-commit hooks (refused by hg when its repository config is trusted)",
              "type": "boolean"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "mutate"
      },
      {
        "category": "vcs",
        "description": "Show changes between commits, working tree, and staging area. Use staged=true for staged changes, or specify commit range. hg and jj have no staging area: they compare the working tree with the last commit, and refuse staged and num_stat. hg refuses a working-tree diff while a tracked file under its paths is deleted from disk but not recorded as removed (vcs_add with update records it).",
        "file_types": [
          "any"
        ],
        "name": "vcs_diff",
        "parameters": {
          "properties": {
            "commit": {
              "description": "Compare working tree against this commit. In hg and jj a revision that names the all-zero commit (hg's null, a prefix of jj's root commit, or HEAD in an empty repository) is taken as that commit, where git reports an unknown revision. The diff is then against the empty tree.",
              "type": "string"
            },
            "commit_a": {
              "description": "Start commit for range comparison",
              "type": "string"
            },
            "commit_b": {
              "description": "End commit for range comparison",
              "type": "string"
            },
            "context": {
              "description": "Lines of context around changes",
              "type": "integer"
            },
            "ignore_space": {
              "description": "Ignore whitespace changes (not in jj, nor with name_only in hg)",
              "type": "boolean"
            },
            "name_only": {
              "description": "Show only file names",
              "type": "boolean"
            },
            "num_stat": {
              "description": "Show numeric statistics (additions/deletions; git only)",
              "type": "boolean"
            },
            "path": {
              "description": "Limit diff to specific file or directory (literal in hg and jj: a glob or ':' pathspec is refused, and [...] matches only the literal name)",
              "type": "string"
            },
            "staged": {
              "description": "Show staged changes (--cached; git only)",
              "type": "boolean"
            },
            "stat": {
              "description": "Show file statistics",
              "type": "boolean"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "inspect"
      },
      {
        "category": "vcs",
        "description": "Fetch (git fetch) commits, branches and tags from a configured remote so the local repository knows the remote's latest state. Updates only remote-tracking refs such as origin/main; never changes the working tree, the index or a local branch. Returns the refs that moved. remote must be a configured remote name (see vcs_remote), not a URL. Use vcs_pull to fast-forward the current branch afterwards. Uses the host's ssh agent; git never prompts for credentials. git only.",
        "name": "vcs_fetch",
        "parameters": {
          "properties": {
            "all": {
              "description": "Fetch every configured remote (--all); leave remote and branch empty",
              "type": "boolean"
            },
            "branch": {
              "description": "One branch to fetch from remote (default: every branch the remote's refspec covers); requires remote",
              "type": "string"
            },
            "prune": {
              "description": "Remove remote-tracking refs whose branch the remote deleted (--prune)",
              "type": "boolean"
            },
            "remote": {
              "description": "Configured remote to fetch from (default: git's default, usually origin)",
              "type": "string"
            },
            "tags": {
              "description": "Also fetch every tag (--tags)",
              "type": "boolean"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "mutate"
      },
      {
        "categories": [
          "code"
        ],
        "category": "vcs",
        "description": "Search for patterns in tracked files. Faster than regular grep as it only searches version-controlled files. In hg the pattern must be literal text, or fixed, and ASCII with ignore_case; count, context, max_count and a revision range are git only. jj has no grep.",
        "file_types": [
          "any"
        ],
        "name": "vcs_grep",
        "parameters": {
          "properties": {
            "context": {
              "description": "Lines of context around matches",
              "type": "integer"
            },
            "count": {
              "description": "Show only match count per file",
              "type": "boolean"
            },
            "extended": {
              "description": "Use extended regex",
              "type": "boolean"
            },
            "files_match": {
              "description": "Show only file names with matches",
              "type": "boolean"
            },
            "fixed": {
              "description": "Treat pattern as fixed string",
              "type": "boolean"
            },
            "ignore_case": {
              "description": "Case-insensitive search",
              "type": "boolean"
            },
            "line_number": {
              "description": "Show line numbers (default: true)",
              "type": "boolean"
            },
            "max_count": {
              "description": "Maximum matches to return",
              "type": "integer"
            },
            "paths": {
              "description": "Limit search to specific paths (literal in hg: a glob or ':' pathspec is refused, and [...] matches only the literal name)",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "pattern": {
              "description": "Search pattern (required)",
              "type": "string"
            },
            "revision": {
              "description": "Search in specific revision",
              "type": "string"
            },
            "word_regex": {
              "description": "Match whole words only",
              "type": "boolean"
            }
          },
          "required": [
            "pattern"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "search"
      },
      {
        "category": "vcs",
        "description": "View commit history with filtering options. Can filter by author, date range, path, and search commit messages. In hg and jj, author and grep match literal text only, and since and until need a full timestamp with an offset; what they cannot match exactly is refused. Under max_count, hg and jj take the newest commits in their own order (hg by revision number, jj topologically), which after merges can differ from git's date order.",
        "file_types": [
          "any"
        ],
        "name": "vcs_log",
        "parameters": {
          "properties": {
            "all": {
              "description": "Show commits from all branches (not in jj)",
              "type": "boolean"
            },
            "author": {
              "description": "Filter by author name or email (hg and jj: literal text; jj refuses text that could span 'Name <email>')",
              "type": "string"
            },
            "first_parent": {
              "description": "Follow only first parent (skip merge commits; not in hg, and not with a path in jj)",
              "type": "boolean"
            },
            "graph": {
              "description": "Show ASCII graph of branch history",
              "type": "boolean"
            },
            "grep": {
              "description": "Search commit messages (hg and jj: literal text, no regex characters)",
              "type": "string"
            },
            "max_count": {
              "description": "Maximum number of commits to show (default 50)",
              "type": "integer"
            },
            "one_line": {
              "description": "One line per commit format",
              "type": "boolean"
            },
            "patch": {
              "description": "Include full diff (-p)",
              "type": "boolean"
            },
            "path": {
              "description": "Filter commits affecting this file/directory (literal in hg and jj: a glob or ':' pathspec is refused, and [...] matches only the literal name)",
              "type": "string"
            },
            "revision": {
              "description": "Branch, tag, or commit range (e.g., 'main', 'HEAD~2', 'v1.0..v2.0'; jj cannot select a merge's second parent, ^2). In hg and jj a revision that names the all-zero commit (hg's null, a prefix of jj's root commit, or HEAD in an empty repository) is taken as that commit, where git reports an unknown revision. Its history is then empty.",
              "type": "string"
            },
            "since": {
              "description": "Show commits on or after this date (e.g., '2024-01-01', '1 week ago'; hg and jj: a full timestamp with an offset, e.g. '2024-01-01T00:00:00Z')",
              "type": "string"
            },
            "stat": {
              "description": "Include file change statistics",
              "type": "boolean"
            },
            "until": {
              "description": "Show commits on or before this date (hg and jj: a full timestamp with an offset)",
              "type": "string"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "inspect"
      },
      {
        "category": "vcs",
        "description": "Search commit history by message, diff content (pickaxe), or author. Useful for finding when changes were introduced. In hg and jj the pattern must be literal text (no regex characters); diff is git's, or jj's with regex.",
        "file_types": [
          "any"
        ],
        "name": "vcs_log_search",
        "parameters": {
          "properties": {
            "all": {
              "description": "Search all branches (not in jj)",
              "type": "boolean"
            },
            "ignore_case": {
              "description": "Case-insensitive search",
              "type": "boolean"
            },
            "max_count": {
              "description": "Maximum commits to return",
              "type": "integer"
            },
            "path": {
              "description": "Limit search to file/directory (literal in hg and jj: a glob or ':' pathspec is refused, and [...] matches only the literal name)",
              "type": "string"
            },
            "pattern": {
              "description": "Search pattern (required)",
              "type": "string"
            },
            "regex": {
              "description": "Treat pattern as regex (hg and jj match only a pattern that is literal text)",
              "type": "boolean"
            },
            "since": {
              "description": "Search commits on or after this date (hg and jj: a full timestamp with an offset)",
              "type": "string"
            },
            "type": {
              "description": "Search type: 'message' (commit messages), 'diff' (changes introducing/removing pattern; not in hg, and in jj only with regex), 'author'",
              "enum": [
                "message",
                "diff",
                "author"
              ],
              "type": "string"
            },
            "until": {
              "description": "Search commits on or before this date (hg and jj: a full timestamp with an offset)",
              "type": "string"
            }
          },
          "required": [
            "pattern"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "search"
      },
      {
        "category": "vcs",
        "description": "List tracked files in the repository. Can also show modified, untracked, or ignored files. hg and jj have no index: modified means changed since the last commit.",
        "file_types": [
          "any"
        ],
        "name": "vcs_ls_files",
        "parameters": {
          "properties": {
            "directory": {
              "description": "With others, show a wholly untracked directory by its name (git only)",
              "type": "boolean"
            },
            "ignored": {
              "description": "With others, show only the ignored untracked files (not in jj)",
              "type": "boolean"
            },
            "modified": {
              "description": "Show modified files",
              "type": "boolean"
            },
            "others": {
              "description": "Show untracked files, ignored ones included unless ignored is set (not in jj)",
              "type": "boolean"
            },
            "paths": {
              "description": "Paths to list (literal in hg and jj: a glob or ':' pathspec is refused, and [...] matches only the literal name)",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "stage": {
              "description": "Show stage information (git only)",
              "type": "boolean"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "inspect"
      },
      {
        "category": "vcs",
        "description": "Pull (git pull --ff-only): fetch from the remote and fast-forward the current branch to it, updating the working tree. Only fast-forwards: never creates a merge commit or rebases; when the local branch has diverged it changes nothing and says so. Returns HEAD before and after. With no arguments pulls the branch's upstream; remote must be a configured remote name and branch a branch on it. Uses the host's ssh agent; git never prompts for credentials. git only.",
        "file_types": [
          "any"
        ],
        "name": "vcs_pull",
        "parameters": {
          "properties": {
            "branch": {
              "description": "Remote branch to fast-forward to (default: the upstream branch); requires remote",
              "type": "string"
            },
            "remote": {
              "description": "Configured remote to pull from (default: the current branch's upstream)",
              "type": "string"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "mutate"
      },
      {
        "category": "vcs",
        "description": "Show the reference log - history of where HEAD has pointed. Useful for recovering lost commits. In jj the entries are its operations: no commit hash, the operation's id as the ref, and its description.",
        "name": "vcs_reflog",
        "parameters": {
          "properties": {
            "max_count": {
              "description": "Maximum entries to show",
              "type": "integer"
            },
            "ref": {
              "description": "Ref to show log for (default: HEAD; jj: HEAD only, its operation log; not in hg)",
              "type": "string"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "inspect"
      },
      {
        "category": "vcs",
        "description": "Manage remote repositories. List, add, remove, or show details of remotes. In hg and jj add and remove return an unsupported-operation error; hg reads its paths only when its repository config is trusted.",
        "name": "vcs_remote",
        "parameters": {
          "properties": {
            "add": {
              "description": "Name for new remote",
              "type": "string"
            },
            "remove": {
              "description": "Name of remote to remove",
              "type": "string"
            },
            "show": {
              "description": "Name of remote to show details (not in jj)",
              "type": "string"
            },
            "url": {
              "description": "URL for new remote",
              "type": "string"
            },
            "verbose": {
              "description": "Show URLs for remotes",
              "type": "boolean"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "mutate"
      },
      {
        "category": "vcs",
        "description": "Unstage files or move HEAD. Default mode 'mixed' unstages the given paths without touching the working tree \u2014 the common workaround for 'I accidentally staged this'. Mode 'soft' moves HEAD only. Mode 'hard' discards uncommitted changes and REQUIRES allow_hard=true to confirm. Path-limited reset is only valid in mixed mode (git restriction).",
        "file_types": [
          "any"
        ],
        "name": "vcs_reset",
        "parameters": {
          "properties": {
            "allow_hard": {
              "description": "Required to be true when mode='hard'. Confirms intent to discard uncommitted changes.",
              "type": "boolean"
            },
            "mode": {
              "description": "Reset mode: 'soft' (HEAD only), 'mixed' (default; HEAD + index), or 'hard' (HEAD + index + work tree; requires allow_hard).",
              "type": "string"
            },
            "paths": {
              "description": "Files to unstage (mixed mode only). Equivalent to 'git restore --staged <paths>'.",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "ref": {
              "description": "Target ref (default: HEAD). Only useful when paths is empty.",
              "type": "string"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "mutate"
      },
      {
        "category": "vcs",
        "description": "Parse and resolve version-control revisions. Get repository root, current branch, resolve commits, etc. hg and jj refuse a revision range. In hg and jj a revision that names the all-zero commit (hg's null, a prefix of jj's root commit, or HEAD in an empty repository) is taken as that commit, where git reports an unknown revision.",
        "name": "vcs_rev_parse",
        "parameters": {
          "properties": {
            "abbrev": {
              "description": "Show abbreviated ref name (git only)",
              "type": "boolean"
            },
            "git_dir": {
              "description": "Show .git directory path (git only)",
              "type": "boolean"
            },
            "is_bare_repo": {
              "description": "Check if bare repository (git only)",
              "type": "boolean"
            },
            "is_inside_work": {
              "description": "Check if inside work tree (git only)",
              "type": "boolean"
            },
            "revision": {
              "description": "Revision to parse (e.g., 'HEAD', 'main', 'HEAD~3')",
              "type": "string"
            },
            "short": {
              "description": "Abbreviate to N characters",
              "type": "integer"
            },
            "show_prefix": {
              "description": "Show path from repo root to cwd (git only)",
              "type": "boolean"
            },
            "show_toplevel": {
              "description": "Show repository root directory",
              "type": "boolean"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "inspect"
      },
      {
        "category": "vcs",
        "description": "Show details of a specific commit, tag, or other version-control object including changes made. hg and jj refuse a revision range. Of a merge, hg's name_only lists the files changed from the first parent (git's combined diff, often none) and jj's stat compares with the merged parents (git's with the first). For a path the revision does not change, hg shows the commit, while git's output there varies by version (2.55 shows the commit; some older versions print nothing). In hg and jj a revision that names the all-zero commit (hg's null, a prefix of jj's root commit, or HEAD in an empty repository) is taken as that commit, where git reports an unknown revision.",
        "file_types": [
          "any"
        ],
        "name": "vcs_show",
        "parameters": {
          "properties": {
            "name_only": {
              "description": "Show only changed file names",
              "type": "boolean"
            },
            "path": {
              "description": "Show changes only for this file (not in jj; in hg a literal path -- a glob or ':' pathspec is refused, and [...] matches only the literal name)",
              "type": "string"
            },
            "revision": {
              "description": "Commit hash, tag, or ref to show (default: the current commit: HEAD in git, '.' in hg, @'s first parent in jj)",
              "type": "string"
            },
            "stat": {
              "description": "Show file statistics",
              "type": "boolean"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "inspect"
      },
      {
        "category": "vcs",
        "description": "Temporarily save changes that aren't ready to commit. List, create, apply, or remove stashes.",
        "name": "vcs_stash",
        "parameters": {
          "properties": {
            "apply": {
              "description": "Apply stash (keep in list)",
              "type": "boolean"
            },
            "drop": {
              "description": "Remove stash from list",
              "type": "boolean"
            },
            "include_untracked": {
              "description": "Include untracked files in stash",
              "type": "boolean"
            },
            "index": {
              "description": "Stash index (0 = most recent)",
              "type": "integer"
            },
            "keep_index": {
              "description": "Keep staged changes when stashing",
              "type": "boolean"
            },
            "list": {
              "description": "List all stashes",
              "type": "boolean"
            },
            "message": {
              "description": "Message for new stash",
              "type": "string"
            },
            "patch": {
              "description": "Show diff when using show",
              "type": "boolean"
            },
            "pop": {
              "description": "Apply and remove stash",
              "type": "boolean"
            },
            "push": {
              "description": "Create a new stash",
              "type": "boolean"
            },
            "show": {
              "description": "Show stash contents",
              "type": "boolean"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "mutate"
      },
      {
        "category": "vcs",
        "description": "Get the current status of the version-control repository including branch, staged changes, modified files, and untracked files. hg and jj have no staging area: staged lists what vcs_commit commits without files (in hg the modified, added and removed files, in jj every change), and modified, in hg, the files deleted from disk but not recorded as removed. hg reports its named branch and lists each untracked file; jj has no current branch. truncated means the output cap cut the lists, which then hold only part of the changes.",
        "name": "vcs_status",
        "parameters": {
          "properties": {
            "short": {
              "description": "Use short format output",
              "type": "boolean"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "inspect"
      },
      {
        "category": "vcs",
        "description": "Manage multiple working trees. Allows working on multiple branches simultaneously in different directories. jj lists its workspaces only (each at HEAD, detached); hg has none.",
        "name": "vcs_worktree",
        "parameters": {
          "properties": {
            "add": {
              "description": "Path for new worktree",
              "type": "string"
            },
            "branch": {
              "description": "Branch to checkout in worktree",
              "type": "string"
            },
            "force": {
              "description": "Force operation",
              "type": "boolean"
            },
            "list": {
              "description": "List all worktrees",
              "type": "boolean"
            },
            "new_branch": {
              "description": "Create new branch (-b flag)",
              "type": "boolean"
            },
            "prune": {
              "description": "Prune stale worktree info",
              "type": "boolean"
            },
            "remove": {
              "description": "Path of worktree to remove",
              "type": "string"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "mutate"
      },
      {
        "category": "workspace",
        "description": "Return exactly the requested absolute line range from a file, optionally padded by context_lines on each side. Always cite the start_line/end_line in the response -- not approximations.",
        "file_types": [
          "any"
        ],
        "name": "view_lines",
        "parameters": {
          "properties": {
            "context_lines": {
              "description": "Extra lines of context on each side",
              "type": "integer"
            },
            "end_line": {
              "description": "Last absolute line (1-based, inclusive)",
              "type": "integer"
            },
            "path": {
              "description": "Path to the file",
              "type": "string"
            },
            "start_line": {
              "description": "First absolute line (1-based, inclusive)",
              "type": "integer"
            }
          },
          "required": [
            "path",
            "start_line",
            "end_line"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "inspect"
      },
      {
        "category": "context",
        "description": "Pause execution for a specified duration. Useful for waiting for agents to complete work, rate limiting, or coordination. Maximum wait time is 60 seconds. Do not use it to wait for a background job; call job_output with wait_seconds, which returns as soon as the job ends.",
        "name": "wait",
        "parameters": {
          "properties": {
            "reason": {
              "description": "Brief reason for waiting (for logging)",
              "type": "string"
            },
            "seconds": {
              "description": "Number of seconds to wait (1-60)",
              "type": "number"
            }
          },
          "required": [
            "seconds"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "coordination"
      },
      {
        "category": "workspace",
        "description": "Count lines, words, and bytes for one file or a glob of files. Defaults to lines-only. Cite this output instead of estimating file sizes. If truncated is true, some matched files were skipped or the glob was capped -- see skipped.",
        "file_types": [
          "any"
        ],
        "name": "wc",
        "parameters": {
          "properties": {
            "bytes": {
              "description": "Count bytes",
              "type": "boolean"
            },
            "glob": {
              "description": "Glob pattern (mutually exclusive with path)",
              "type": "string"
            },
            "lines": {
              "description": "Count lines (default if all false)",
              "type": "boolean"
            },
            "path": {
              "description": "Single file path",
              "type": "string"
            },
            "words": {
              "description": "Count words",
              "type": "boolean"
            }
          },
          "type": "object"
        },
        "source": "native",
        "subcategory": "inspect"
      },
      {
        "category": "context",
        "description": "Get information about the current workspace including working directory, git status, project type, and environment. Use this to understand the context before performing operations.",
        "name": "workspace_info",
        "parameters": {
          "properties": {},
          "type": "object"
        },
        "source": "native",
        "subcategory": "inspect"
      },
      {
        "category": "workspace",
        "description": "Write content to a file. Creates parent directories if needed. Use append=true to add to existing file. IMPORTANT: Path must be a literal file path - do NOT use shell syntax like {a,b} brace expansion. Call this tool once per file.",
        "file_types": [
          "any"
        ],
        "name": "write_file",
        "parameters": {
          "properties": {
            "append": {
              "description": "If true, append to existing file instead of overwriting",
              "type": "boolean"
            },
            "content": {
              "description": "Content to write to the file",
              "type": "string"
            },
            "expected_absent": {
              "description": "Precondition: the file must not exist (atomic create).",
              "type": "boolean"
            },
            "expected_digest": {
              "description": "Precondition: sha256:<hex> digest the file must currently have. The host attaches this automatically from your last read; a mismatch refuses the write with a stale_resource conflict.",
              "type": "string"
            },
            "path": {
              "description": "Literal path to the file (no shell syntax like {a,b})",
              "type": "string"
            }
          },
          "required": [
            "path",
            "content"
          ],
          "type": "object"
        },
        "source": "native",
        "subcategory": "file"
      }
    ]
  }
}
