{
"name": "ct-tree",
"description": "Walk a directory for chosen file types and report the effective file tree with per-file line, word, and character counts (and on-disk byte size). Select files with --base/--name/--ext (--ext is a comma list of extensions added to --name as alternatives), filter with metric predicates (--min-lines/--max-lines/--min-words/--max-words/--min-chars/--max-chars/--min-bytes/--max-bytes) and per-folder predicates (--min-files-per-folder/--max-files-per-folder, counting matching files in a file's immediate directory), sort by path|name|lines|words|chars|bytes|ext (--desc for descending), and choose a summarisation level: --tree (indented tree with per-file counts and per-folder subtotals; default), --flat (one file per line: lines words chars path; best for ranked lists), or --summary (aggregates grouped by --group ext|dir|none). --bytes adds a byte-size column to the text modes (also enabled by --sort bytes). --json emits {tool, base, files:[{path,ext,lines,words,chars,bytes}], by_ext, totals}. Exit: 0 if any file is in the report, 1 if none, 2 on error. Read-only. Invoke as `ct tree ...` or `ct-tree ...`. Example: all *.rs files over 5000 lines sorted by line count descending = --ext rs --min-lines 5001 --flat --sort lines --desc.",
"input_schema": {
"type": "object",
"properties": {
"base": {
"type": "string",
"description": "Root directory to walk, relative or absolute. Default '.'.",
"default": "."
},
"name": {
"type": "string",
"description": "File-name pattern; '|'-separated alternatives, each substring->glob->regex promoted and anchored to the whole name."
},
"mode": {
"type": "string",
"enum": [
"literal",
"glob",
"regex"
],
"description": "Pin how --name/--ext patterns are interpreted; promotion off. Use literal for names whose characters would otherwise promote."
},
"ext": {
"type": "array",
"items": {
"type": "string"
},
"description": "Restrict to these extensions (no dots), e.g. ['rs','toml']. Added to --name as alternatives. May be comma-joined."
},
"hidden": {
"type": "boolean",
"description": "Include dot-entries (names starting with '.'). Default: skipped."
},
"follow": {
"type": "boolean",
"description": "Follow symlinks while traversing."
},
"min-lines": {
"type": "integer",
"description": "Only include files with at least N lines."
},
"max-lines": {
"type": "integer",
"description": "Only include files with at most N lines."
},
"min-words": {
"type": "integer",
"description": "Only include files with at least N words."
},
"max-words": {
"type": "integer",
"description": "Only include files with at most N words."
},
"min-chars": {
"type": "integer",
"description": "Only include files with at least N characters."
},
"max-chars": {
"type": "integer",
"description": "Only include files with at most N characters."
},
"min-bytes": {
"type": "integer",
"description": "Only include files of at least N bytes."
},
"max-bytes": {
"type": "integer",
"description": "Only include files of at most N bytes."
},
"min-files-per-folder": {
"type": "integer",
"description": "Only include folders that directly contain at least N matching files."
},
"max-files-per-folder": {
"type": "integer",
"description": "Only include folders that directly contain at most N matching files."
},
"sort": {
"type": "string",
"enum": [
"path",
"name",
"lines",
"words",
"chars",
"bytes",
"ext",
"okf-type"
],
"description": "Sort key. Default 'path'. In --flat the sort is global; in --tree it orders entries within each folder. Sorting by bytes also turns on the byte column. 'okf-type' sorts by a Markdown concept's frontmatter type (files without one sort first)."
},
"desc": {
"type": "boolean",
"description": "Sort descending instead of ascending."
},
"tree": {
"type": "boolean",
"description": "Output mode: indented file tree with per-file counts and per-folder subtotals. This is the default mode."
},
"flat": {
"type": "boolean",
"description": "Output mode: one matching file per line with its counts. Mutually exclusive with the other modes."
},
"summary": {
"type": "boolean",
"description": "Output mode: aggregate counts only, grouped by --group. Mutually exclusive with the other modes."
},
"bytes": {
"type": "boolean",
"description": "Add a byte-size (on-disk) column after chars in the text modes. Independent of --min-bytes/--max-bytes; also enabled by --sort bytes. JSON always includes bytes."
},
"group": {
"type": "string",
"enum": [
"ext",
"dir",
"none",
"okf-type"
],
"description": "Grouping for --summary: by extension (default), by immediate directory, by OKF frontmatter type (files without one group under '(none)'), or a single grand total."
},
"json": {
"type": "boolean",
"description": "Emit a structured JSON result instead of text."
},
"timeout": {
"type": "number",
"description": "Abort with exit 2 (and a one-line message) if the report exceeds SECS seconds (fractional allowed)."
},
"heartbeat": {
"type": "number",
"description": "Print a liveness pulse every SECS seconds (fractional allowed) while the run is in progress."
},
"heartbeat-emit": {
"type": "string",
"description": "Heartbeat line template. Tokens: {ELAPSED} (whole seconds so far) {TOOL}. Default: \"[{ELAPSED}s]\"."
},
"heartbeat-to": {
"type": "string",
"enum": [
"stderr",
"stdout"
],
"description": "Stream heartbeat pulses are written to. Default: stderr."
},
"no-ignore": {
"type": "boolean",
"description": "Walk gitignored / .ignore files too (the .git directory is always skipped); by default the walk skips what git would."
},
"json-pretty": {
"type": "boolean",
"description": "Like --json, but pretty-printed (indented)."
}
},
"required": []
},
"examples": [
{"cmd": "ct tree --ext rs --summary --group dir --base src", "why": "Per-directory line and file totals for Rust sources in one table, instead of ls -R and wc -l pipelines."},
{"cmd": "ct tree --ext rs --flat --sort lines --desc", "why": "List Rust files by descending line count, instead of wc -l on a find result piped to sort."},
{"cmd": "ct tree --ext rs --min-lines 500 --base src", "why": "Surface Rust files over 500 lines that may want splitting."}
]
}