Releases: fredrikolis/annotated-tree
Releases · fredrikolis/annotated-tree
Release list
v0.7.0
Added
--hiddenwalks dot-files and dot-directories, which were pruned unconditionally before. A repo
keeps its hooks, workflows and agent configuration behind a leading dot —.githooks/,
.github/,.claude/— and every one of them was invisible to the tree AND to--strict-check,
which reported all files passed for a tree it had never opened. What is then CHECKED under a
revealed directory is the ordinary rule, a file that maps to a configured language: on this repo
the flag brings in.cargo-lint-extra.tomlon its extension, plus — through the two entries
below —.githooks/and.github/workflows/. Also settable as
[display] hiddenin a config file. It is off by default, so no existing invocation changes;
the three goldens are unchanged with a dot-directory added tosample/, which is the proof.
Two boundaries:.gitis never walked under any flag combination, and the switch is orthogonal
to.gitignore— a path that is both hidden and ignored needs--no-gitignoretoo. The
--githook-guiderecipe and all three copies of this repo's own gate —pre-commit, CI, and the
pre-tag release check — now pass it, for the same reason they already pass--include-tests.- BREAKING (gate). An EXTENSIONLESS file resolves its language from its
#!line. A git hook,
abin/script, aconfigure— the files that decide how a repo builds and gates itself — carry
their language in a shebang and nowhere else, so they were unlistable and unlintable, and
--includecould not fix it: that flag widens the VIEW, and the gate never honours it. Each
language now declaresinterpreters;sh bash zsh dash kshresolve to shell and
python python3to python, by the BASENAME of the shebang's first word, or of the next word
when the first isenv. The probe fires only when a path has no extension at all, so a.rs
opening with#!is still rust andCargo.lockis never opened. A file that opens with anything
but a literal#!, such as aLICENSE, resolves to nothing and stays out of scope exactly as
before. Expect an adopting repo's first run under--hiddento fail on hooks nothing had ever
checked — a.githooks/pre-commitwhose annotation is missing, or a few characters over the 200
bound, has had nothing to tell it so. A<script>.annotationsidecar written before this goes
inert, as one beside any file that maps to a comment marker does: move the line into the script
itself, behind a#, and delete the sidecar. - BREAKING (gate).
yamlis a recognized language (.yml,.yaml), with#as its comment
marker. A workflow, a compose file and a Claude Code skill definition are where a repo's build,
release and agent behaviour actually lives, and every one of them was a file an agent could only
route by opening. A leading#comment is legal above any YAML document, so the annotation costs
the file nothing, and it goes on line 1, above any---: in YAML that marker starts a
document rather than the metadata block the scanner skips for a Markdown skill file, so without
the distinction a file opening---/name: first/---would certify as annotated off a
comment further down. Frontmatter handling is unchanged for every other language; a language
whose own comment marker is--, such as SQL, has the same shape and is untouched here.
Excluding a file is the ordinary-I/.gitignore, not a carve-out. A
<name>.yml.annotationsidecar written before this goes inert, as the.tomland shebang
entries describe: move the line into the YAML itself, behind a#, and delete the sidecar. - BREAKING (gate).
.tomlis a recognized language, with#as its comment marker (#19).
A.tomlfile is listed in the tree and must carry a first-line annotation, at the same bar
as a.pyor a.rs. Config was the one place the tool told an agent to route by opening the
file. A manifest is a.tomllike any other and gets no waiver:Cargo.tomland
pyproject.tomlnow render as rows of their own, above the dependency edges their directory
already states. A repo that does not want a given file held to this excludes it with-I
or.gitignore, as it would any other file — the check gained no carve-out to configure.
A<name>.toml.annotationsidecar written before this goes inert, as a sidecar beside any
file that maps to a comment marker does: move the line into the.tomlitself, behind a#,
and delete the sidecar.
Changed
- BREAKING (library).
walk::configured_walktakes aWalkFilterparams struct in place of its
positionalgitignore,include_testsandexcludesarguments. Four filtering choices, three of
thembool, is a signature a caller transposes silently, and the struct is what lets one walk's
policy be handed to another rather than restated. - BREAKING (library). Four public structs gain fields, for a consumer that builds one by
struct literal:config::Languagegainsinterpretersandfrontmatter_prefix, and
config::Display,config::CliOverridesand the crate-rootClieach gainhidden.
v0.6.0
Added
trailing_contenton the--strict-checkreport (its own list, likeorphan_sidecars: content
past line 1 is a defect of the FILE, not an issue about an Annotation, soCHECK1is untouched).
BREAKING for a consumer that builds aStrictReportby struct literal.- Per-file
.annotationsidecars (#1). A file that maps to no comment marker — a CSV, a
dataset, a binary — carries its contract in a<name>.annotationfile beside it, holding the
same bare three-field line a folder's.annotationholds. Three consequences:
a file carrying a sidecar is listed whatever its extension (writing the sidecar is the
opt-in, so no--includeis needed); the sidecar's own row is suppressed, under a criterion
the report now states (see Changed); and a sidecar is only ever read for a file that cannot hold
a first-line comment, sofoo.rs.annotationbeside afoo.rsis an ordinary file, not a
sidecar, and an annotation's location stays determined by the path it annotates. orphan_sidecarson the--strict-checkreport: a<name>.annotationwhose named file does
not exist annotates nothing, and is reported aspath: message(its own list — a dangling path
is not an issue about an Annotation, and no[rules]table configures it). It FAILS the check.
Nothing is deleted or rewritten:--strict-checkstill makes no write of any kind.--strict-checkenforces a sidecar body with the same grammar as a folder charter, reported at
the sidecar's own path withlanguage: "sidecar".FileNode.sidecarin the JSON map (omitted when false): the row's annotation came from the
sidecar beside it. BREAKING for a consumer that builds aFileNodeby struct literal.- A leading YAML frontmatter block is skipped when looking for a file's annotation, exactly as
a#!shebang already was (#16). A Claude Code skill/agent/command, or any static-site page,
must keep its frontmatter on line 1; before this, such a file could not carry an annotation at
all, so shipping skills and enforcing--strict-checkwere mutually exclusive. Only a CLOSED
block at the very start is a prefix — a---further down stays a horizontal rule. annotated-tree bash-annotatorputs each file's contract in an agent's owngrep,find
andlsresults, through a Claude CodePreToolUsehook that pipes eligible calls through an
annotator;--install-claude-hookswitches it on. It is a verb on the one binary, so it ships
on every channel: npx,cargo binstall, the curl installer,cargo install. The tool
itself is never substituted and the wrapped command's exit status is preserved.annotated-tree bash-annotator --install-claude-hook [FILE]and
--uninstall-claude-hook [FILE]. Cargo has no post-install or pre-uninstall step — the only
code it runs isbuild.rs, at build time — so switching the hook on is an explicit command.
It MERGES its entries into the settings file, keeping every other key: that file
holds the permissions a user has accepted, and a setup step that overwrote it would cost them
all of them. Defaults to~/.claude/settings.json; pass.claude/settings.local.jsonfor a
single repo. Idempotent, writes atomically, refuses a file that does not parse rather than
replacing it, and--uninstall-claude-hookremoves only the entries it added.bash-annotatorsays what it does ONCE, through aSessionStartentry--install-claude-hook
writes beside thePreToolUseone, runningannotated-tree bash-annotator --session-announcement
— a verb you can run by hand to read exactly what the agent is told. Not anadditionalContexton
each rewritten call:PreToolUsefires before the command runs, so that text would repeat per call
and describe contracts nothing printed. Claude Code adds aSessionStarthook's stdout to the
agent's context verbatim, so it is printed bare, with no JSON envelope. Each entry names the one
verb that does its job, so the settings file says what each is for.- That announcement introduces the tool itself, not only the trailing annotations it explains: that
annotated-treeis installed, that running it on a directory yields a map you can route from
without opening the files, when to reach for one, and that--annotation-guideis the reference
for writing an annotation. A notice that only prevents confusion leaves the map unused. The text
now lives insrc/bash_annotator/session-announcement.md, so--session-announcementstill
prints exactly what the agent is handed. --annotation-guideprints the annotation-writing guide to stdout and exits — the same full text
a failing--strict-checkappends, reachable without a violation to trigger it. It ADDS a way to
reach the guide, it does not move it:--helpstill carries its compact head, and a failing
--strict-checkstill appends it in full.annotated_tree::resolve_charter— resolve a directory's charter through the public API.
Charterwas already exported with no way to obtain one.
Changed
- BREAKING (gate). An
.annotationfile — a directory charter or a<name>.annotation
sidecar — must hold ONE bare annotation line and nothing but whitespace after it.
--strict-checknow FAILS a body with prose below line 1, reporting it in a new
trailing_contentlist; the map shows such a directory or file WITHOUT a contract rather than
with one. Before this, the stray text landed inside theIOfield newline and all, and one
render row printed as two — atreeview whose line count was wrong, a JSON string with a\n
in it, and a strict-check finding spanning three lines with an unpasteablesuggestion. The
rule is "nothing but whitespace after the first line", never "contains a newline": every editor
writes a trailing one, so a charter that ends with a newline (or with blank lines) is ordinary
and passes. - The
trailing_contentfinding is reported ALONE for an offending file: its parts are not
diagnosed until it is one line again, because echoing the offending text intofoundand
suggestionis what split the report line in the first place. - BREAKING.
[rules] max_annotation_length/--max-length <N>now bounds the WHOLE
annotation, not each field. The bound is on the line an agent ingests: three fields each
under a per-field bound could still add up to a line nobody wants in a map read a hundred
times a session. A repo that passes today at 200 per-field will fail — this one did, in 51
files, every one of which shortened without losing anything it said. The guide now states
the diagnosis: a line that will not fit is an architecture defect, not a compression
problem. Do not raise the bound; it is the detector. - BREAKING (JSON).
defect.too_long(an array naming each over-length field) is replaced
bydefect.length(the annotation's own length).defect.maxis unchanged. - The maximal-span measurement is gone with it. It existed only because a per-field bound was
evadable — prose quoting| Non-concern:split the line early and every measured part came
in under the limit. A whole-line count cannot be evaded that way, so the machinery was
deleted rather than carried forward. - The annotation guide gains a FOLDER CHARTERS section: a
.annotationstates the directory's
one job one altitude above its files, and never restates them. -L LEVELcaps the walk, not just the render (#15). The traversal stops at the deepest
level the output can show, soannotated-tree -L 1 ~no longer walks an entire home directory
to print one level (measured on one: 2.4 s warm and 253k directory reads, down to 17 ms and
88). Three user-visible consequences:
empty directories are listed — a directory earns its row by being VISITED, not by holding
a listable file somewhere beneath it. Below the cutoff nothing is visited, so "has a listable
descendant" is a question the deepest rows can no longer answer, and answering it at one depth
but not another would be the worse rule; a folder whose contents are all unlistable (only a
notes.txt, or nothing at all) now gets a row at every depth, where it used to be invisible.
The-Lcap cuts the input to the dependency graph, so a shallow render shows a shallower
graph of the same tree instead of edges drawn from manifests the caller asked not to see. The
manifest walk runs exactly ONE level below the deepest row, because a package's manifest lives
INSIDE the package, one level under the row that names it — so every directory the map
DISPLAYS still states its own<- depends on […]/used by: […]facts, while a package
below the cutoff is no row, is never read, and contributes no edge (a path/workspace
dependency on one now renders as(unresolved)). The extra level reads manifests only and
can never add a row.
And--strict-checkis not capped: a gate is not a rendered view, so it still lints every
file at every depth,-Lor no-L.- The text map states the one exclusion criterion it applies to
.annotationfiles, on stderr
and only when a sidecar row was actually suppressed. The JSON map states it structurally
instead, as"sidecar": trueon the row that took the contract. - A malformed
.annotationbody that is a conforming line wrapped in a comment marker is
now diagnosed as exactly that — "remove the<!--and-->" — instead of "the|field
separators are missing", which was the one explanation that could not be true (#17). The
printedsuggestionis the line from inside the wrapper, so it is usable as printed; it used
to embed the malformed text and could not be pasted. Same verdict, same parts reported. SPEC.mdgains an accessory vocabulary entry, stating that anything annotated-tree offers
that helps an agent consume Annotations outside a Report performs no run, emits no Report, and
is therefore governed by none of...
v0.5.0
Added
[rules] max_annotation_lengthand--max-length <N>: fail any annotation field
(Concern,Non-concern,IO) whose MAXIMAL SPAN runs over N characters — on a conforming
line that span is exactly the trimmed value (see Fixed, below). N itself passes. 200 by
default (see Changed);--max-length 0normalizes to no bound, like--max-per-node 0.annotation_too_longcategory, carryingdefect.too_long(each offending part and its
length) anddefect.max(the bound).annotation::Outcome::TooLongvariant.Outcomeis apub enumwith no
#[non_exhaustive], so a downstream exhaustivematchmust handle it.
Changed
- BREAKING: the annotation length bound ships ON at 200 characters per field —
default_config.tomlnow sets[rules] max_annotation_length = 200, the built-in layer every
other layer overrides. A repo that passed--strict-checkon 0.4.0 with longer annotations now
FAILS with no config change. A bound nobody enables catches nothing: 0.4.0 in real repos let
agents write 500–1600 character fields. Raise it with[rules] max_annotation_length = <N>or
--max-length <N>;--max-length 0disables it. The failing TEXT report names that escape once. --strict-checkis form-only: every field present, non-empty, within the length
bound. FillerConcerns, inwardNon-concerns,<placeholder>slots and an empty IO
operand (IO: (a) ->) all pass. The annotation guide still advises against filler.- An empty field is fatal as
malformed_annotation, naming the field indefect.missing. A
line whose three keys are present but unextractable (Concern: a|Non-concern: b|IO: c, or
keys out of order) reports all three parts. Both carry a humandetailclause. annotation::Outcome::Malformedgains adetailfield: any struct-variant pattern on
Malformedmust be updated.annotation::analyze/analyze_charter/analyze_filetake the length bound as a
trailingOption<usize>(None= no bound).config::CliOverridesgains a publicmax_annotation_lengthfield: a struct-literal
construction must supply it, or fall back to..Default::default().Cligains a publicmax_lengthfield.Cliderives noDefault, so a struct-literal
construction must supply it.suggestionis absent forannotation_too_long. The TEXT message never carried one for
this category either, so TEXT-only consumers see no change.- The
suggestionstub passes the form check as printed. Its<…>slots are still judgments
an agent has to write out, and a configuredmax_annotation_lengthapplies to it
(<concern owned elsewhere>alone is 25 characters). --helpoutput: the embedded annotation guide declares theNon-concern's where-it-lives
pointer OPTIONAL and carries a BREVITY section.--githook-guiderecipe and the commit-message attestation format: every per-principle line
carries a severity —none,N/A — reason, orMAJOR/MODERATE/MINORplus the finding —
in place of a numeric score.MEDIUM:is renamedMODERATE:, no alias. A numericMINOR:
count is now required, and never gated. The gate cross-checks the lines against the counts: a
line carryingMAJORunder a declaredMAJOR: 0fails. BREAKING for a shipped recipe: a repo
that wired the example hook in has every commit message rejected until it adopts the format.--githook-guidedefines the three severity tiers (MAJOR / MODERATE / MINOR).
Removed
--symbols, the[display] show_symbolskey, theFileNode.symbolsJSON field (and its
--schemalines), thesymbolsmodule, and thesymbolsCargo feature with its
tree-sitter,tree-sitter-python,tree-sitter-rust,tree-sitter-go,
tree-sitter-typescriptandstreaming-iteratoroptional dependencies. The tool reports on a
file as a NODE — its declared contract — never on its body; a parsed declaration list is a
second, derived map that can disagree with the annotated one.- BREAKING: the
SymbolandSymbolKindcrate-root re-exports are gone, andDisplay,
CliOverrides,CliandFileNodelose their publicshow_symbols/symbolsfields; none is
#[non_exhaustive], so a struct literal or field read must drop it. A repo config still
carryingshow_symbolsnow fails to parse (deny_unknown_fields) — delete the key.mcpis
the only remaining Cargo feature. --tokens, the[display] show_tokenskey, theDirNode.tokens/FileNode.tokensJSON
fields (and their--schemalines), and thetokensmodule. It was a~4 bytes/token
heuristic, and an unreliable estimate in a tool sold on ingest efficiency is worse than none
— an agent may budget against it.- BREAKING:
Display,CliOverrides,Cli,DirNodeandFileNodelose their public
show_tokens/tokensfields; none is#[non_exhaustive], so a struct literal or field read
must drop it. A repo config still carryingshow_tokensnow fails to parse
(deny_unknown_fields) — delete the key. - The vacuity gate: the
annotation_vacuouscategory, thedefect.vacuousJSON key, and
annotation::Outcome::Vacuous. - The
annotation_on_orphanadvisory, the strict report's top-levelwarningsarray (and its
Found N warning(s)TEXT block), andexit::code::ANNOTATION_ON_ORPHAN. The opt-in
[rules] forbid_orphans/orphan_packagerule is untouched.
Fixed
.githooks/commit-msgread the FIRST match for each count, so a body line at column 0 reading
MAJOR: 0 blockers remainedshadowed the real trailer and a commit with unresolved blockers
passed the gate. Every count now takes the LAST match.- The length bound under-measured a field whose prose quoted the
| Non-concern:/| IO:
separators with their colons, in EITHER direction: the parser splits at the first occurrence, so
a quote ahead of the real key hid every character after it (a 300-characterConcernmeasured
150 and passed--max-length 200), and splitting at the last occurrence instead merely moved the
shortfall onto a quote that FOLLOWS the real key (a 207-characterIOmeasured 100 and passed).
The three fields partition the line, so choosing an occurrence only redistributes length; the
bound therefore no longer measures the parsed values at all. It measures each field over its
MAXIMAL extent —Concernup to the LAST| Non-concern:,Non-concernfrom the first of
those to the LAST| IO:,IOfrom the first of those to the end of the line. On a conforming
line each separator occurs once, so the spans are exactly the parsed fields and no reported
length changes; a line that quotes a separator over-measures and fails loudly, and can no longer
under-measure. Parsing and rendering are byte-for-byte unchanged. - The
malformed_annotationsuggestion seeded itsConcern:from the text before the first bare
|, so aConcernwhose own prose held a pipe (a shell pipeline, a|x|closure, SQL||)
was truncated mid-sentence in the suggested stub. It now cuts at the| Non-concern:separator
the parser splits on, so the seed is exactly theConcernthe checker read.
v0.4.0
Added
- Map + render library surface — the crate now re-exports the tree model and renderer at
the crate root, so a downstream consumer can assemble aCodebaseMapfromDirNode/
FileNodeby hand (thecharter/deps/symbols/warningsfields may beNone/
Vec::new()) and render it viafor_format(Format, ascii)+ theRenderertrait, driving
its own tree without the internalbuildpipeline. Access-only: no behavior or schema change.
The node field types (Charter,DirDeps,InternalDep,Warning,Symbol,SymbolKind)
are re-exported too so every field is nameable; the graph/symbols/strict builder machinery
stays crate-internal.
v0.3.0
Added
--include <GLOB>— a positive glob selector, the counterpart to-I/--ignore:
it adds files of any type to the tree even when their extension maps to no known
language (repeatable, pipe-separated;--include '*'shows every file). An included
file's annotation is read marker-agnostically (keyed on the invariantConcern:
opener), so extensionless and unrecognized files still surface their one-line
annotation. Config-enablable via[display] include = ["*.sh", "Dockerfile"].
--strict-checkis unaffected — it stays recognized-languages-only (an unknown comment
grammar cannot be validated).- Library API — the crate now exposes its low-level primitives so another program can
reuse theignore-based walk (walk::configured_walk,walk::collect_code_files) and
the annotation grammar (annotation::extract, the marker-agnosticannotation::extract_any,
annotation::analyze) over files of any shape, driving its own rendering. Theconfig,
walk, andannotationmodules are public, plus thebuild_globsetglob-compile helper;
the tree model, graph, renderers, and strict-check stay crate-internal.
v0.2.1
Docs-only release: no change to the binary. Cut to refresh the README shipped
to crates.io and npmjs.
Changed
- README.md rewritten around adoption — what the tool is, intended usage
(annotate, enforce via a local git hook, read the map every session), a TL;DR
for humans, the rationale for agents, and install/wire/enforce/configure
steps. Roughly half its former length. - README_APPENDIX.md (new) — the extended argument (the infinite-context
objection, related work, what is still unproven) and the full bibliography
for every inline citation, cross-linked from the README. - Annotation guide — the Non-concern owner may now be an external system or
out of the repo's scope, not only a named sibling; "true of every file" is
called out as a truism, not a boundary. - The repo now carries its own root
.annotationcharter, a
docs/communication-style.mdreview rubric, and a fixed executable bit on
.githooks/pre-commit(the strict-check gate was being silently skipped).
v0.2.0
Added
- Annotation guide on
--helpand failing--strict-check— the format, a
GOOD/FAILS contrast, and how to find the Non-concern, shown inline where you fix an
annotation. Opt out with--no-guide. - Single-file
--strict-check— lint one file, not just a directory (pre-commit
hook, or the file you just wrote). - Directory charters — a directory carries its own
Concern | Non-concern | IO
line (a.annotationbreadcrumb, else its entry file), promoted onto its tree row. - Actionable
--strict-checkdiagnostics — each violation now names the file's
language, the exact comment marker to use, the real line number (past any
shebang/blank lines — no more hardcoded:1that led fixes to clobber a shebang),
the offending content, and a copy-pasteable conformant example. Missing vs.
non-conforming annotations get distinct wording, and a wrong-marker line is echoed
asfound: '…'. The machine-parseablepath:LINE:prefix is preserved. MCP
strict_checkinherits the richer messages. - Machine-readable
--strict-check --format json— the strict check now emits a
structured document ({passed, error_count, files_checked, violations, rule_violations}) when--format jsonis passed, one record per violation with
path/line/language/category/marker/hint/example/found. The default
TEXT report is unchanged. MCPstrict_checkreturns this same structured object
(byte-for-byte the CLI's--format json) instead of a flat text report. ANNOTATION FORMATin--help—--helpnow shows what a conforming
annotation looks like, with a verbatim example sourced from the built-in
[convention].exampleso help and enforcement can never disagree.[convention].exampleconfig field — a full, conformant annotation line
(per-language overridable, likerequire/hint) that feeds both--helpand the
strict-check diagnostics. A test proves every built-in language's example passes
the lint it advertises.- Per-directory display cap —
--max-per-node <N>(default 50) shows at most
N subdirectories and N files per directory, folding the overflow into a single
[+N folders and F files, use --full to expand]marker. Keeps signal-dense
source trees fully visible while collapsing massive test/corpus folders to one
line — the overview an agent wants without the token noise. Aggregate--tokens
totals still reflect the full (untruncated) subtree, so a collapsed folder still
reports its true size. Expand everything with--full(or--max-per-node 0).
Display-only: the walk still visits every file, so--max-filesis unaffected.
JSON/MCP carry the breakdown aselided_dirs/elided_files(omitted when
zero — no schema bump).
Changed
- One invariant annotation format — the three-field
Concern | Non-concern | IO
grammar is now fixed (not configurable); the only per-language knob is the comment
marker. - Stricter vacuity enforcement — a filler
Concern(utils/helpers/…) and an
inwardNon-concern(this file's own …) now fail, matching what the guide teaches.
Removed
--explain— superseded by the annotation guide, now shown inline on a failing
--strict-checkand in--help.
v0.1.1
Fixed
npx annotated-tree/npm installfailed — the launcher shim
(bin/annotated-tree.js) opened with its annotation comment instead of a
#!/usr/bin/env nodeshebang, so the npm-linked executable could not run. The
shebang is restored and CI now asserts it on line 1.- Strip a leading UTF-8 BOM when reading a file's annotation head, so a
BOM-prefixed shebang file is no longer mis-read as lacking a first-line shebang.
Added
- Shell script support —
.sh/.bashfiles are now recognized by the
annotation engine (shebang skipped, annotation read from the first comment
below it).
v0.1.0
Initial release.
Added
- Annotated tree view — a directory tree where every source file shows its
first-line responsibility annotation, extracted by a configurable, per-language
engine (structured comment tokens plus a regex escape hatch). - Cross-ecosystem dependency graph in the tree —
pyproject.toml,
package.json,Cargo.toml, andgo.modare cross-referenced into internal
deps, external deps, and reverse "used by" edges; unresolved workspace/path deps
are flagged. --strict-checklint mode — nonzero exit on any code file lacking a
conforming annotation. Enforces architectural dependency[rules](deny edges,
forbid cycles, forbid orphans) declared in.annotated-tree.toml.--format json(versioned, stable schema) and--format mdoutput for
tooling and agents.--symbols— per-file top-level definition outline via tree-sitter
(feature-gated: build with--features symbols).--mcp— serve the map, dependency, and strict-check tools over stdio as a
Model Context Protocol server (feature-gated: build with--features mcp).--changed/--since <ref>— restrict the view to files changed versus a
git ref plus their reverse-dependency blast radius.--tokensrough per-file/package token estimate;--agemodification
times;--max-filesrunaway-scope safety valve (aborts with exit 2 before
any output).- Flags:
-L/--max-depth,--include-tests,--no-gitignore,--ascii,
-I/--ignore,--config,--no-limit,--ignore-parsing-errors. - Layered configuration — built-in defaults <
~/.config/annotated-tree/config.toml
< repo./.annotated-tree.toml< CLI flags. Regex-configurable extraction and
validation convention per language. - Non-fatal stderr warnings for unparseable manifests (silence with
--ignore-parsing-errors); a corrupt manifest never aborts the run. - Distribution — crates.io, cargo-binstall, Homebrew, npm/npx, and a
checksum-verifyingcurl | shinstaller. - Golden-file and integration test suite; CI across Linux, macOS, and Windows.