# src/service

Server-less `#[cli]` service layer — the primary CLI registration point. Every `#[cli]` method carries an explicit `display_with = "..."` attribute routing to `OutputFormatter`; orphaned `impl Display` blocks have been removed. The `NormalizeService` root struct composes sub-services: `AnalyzeService`, `ConfigService`, `ContextService`, `DaemonService`, `EditService`, `FactsService` (CLI name `structure`), `GrammarService`, `GuideService`, `GenerateService`, `PackageService`, `RankService`, `RatchetService`, `RulesService` (from `normalize-rules`), `ServeService`, `SessionsService`, `SyntaxService`, `ToolsService`, `TrendService`, `ViewService`. The `sync` command (`normalize sync <dest>`) is a leaf method on `NormalizeService` (not a sub-service) — it copies a project tree and session metadata with `--dry-run`, `--all`, `--active N`, `--repo`/`--exclude`, and rewrites index DB paths after copy via libsql. Helper methods live in separate impl blocks; JSON/schema output is generated automatically by the proc macro. `edit.rs` `EditService` hosts `normalize edit introduce-variable <file> <range> <name>`: parses the file with tree-sitter, finds the expression at the given `line:col-line:col` range, inserts a language-appropriate binding (`let`/`const`/bare assignment) before the containing statement, and replaces the expression with the variable name. Report type: `IntroduceVariableReport` with `file`, `name`, `inserted_line`, `replaced_start`, `replaced_end`, `dry_run`. Also hosts `normalize edit add-parameter <file> <function> --param <name> --default <value> [--type <type>] [--position <N>]`: inserts a new parameter into a function signature and updates all call sites using the facts index. Report type: `AddParameterReport` with `function_file`, `function_name`, `param_name`, `call_sites_updated`, `dry_run`. Falls back gracefully when the index is unavailable. All refactoring commands (`rename`, `introduce-variable`, `inline-variable`, `add-parameter`, `move`, `inline-function`) call `notify_daemon_after_edit()` after writing files — this sends a `Request::FilesChanged { root, paths }` to the daemon (via `DaemonClient::notify_files_changed()`) so the index refreshes immediately without waiting for inotify. Non-fatal: silently ignored if the daemon is not running. `rename.rs` implements the top-level `normalize rename <target> <new-name>` command: `RenameReport` and `RenameSite` report types with `OutputFormatter`, and `do_rename_report()` which resolves callers and importers via the facts index, checks for name conflicts, and applies (or previews with `--dry-run`) the batch identifier rewrite across all affected files. `grep` in `mod.rs` accepts an optional positional `path` argument (overrides `--root` when both are given). `FactsService.rebuild()` accepts `--only`/`--exclude` to filter which files are retained in the index after a full walk, plus `--strict` to exit non-zero when any tree-sitter grammar is missing. `RebuildReport` carries a `missing_grammars: Vec<MissingGrammarEntry>` summary populated from `normalize_facts::take_missing_grammars()` so users see which languages were skipped (`format_text` prints `"Skipped N files due to missing grammars: <grammar> (<count>), ..."`). `FactsService.test_fixtures()` implements `normalize structure test-fixtures [--lang <lang>] [--fixture-dir <dir>] [--update]` — a language-agnostic extraction fixture runner that discovers `<lang>/<case>/input.<ext>` + `expected.json` pairs under `crates/normalize-languages/tests/fixtures/`, extracts symbols/imports/calls via `SymbolParser`, and diffs against the expected JSON (subset matching by default; `"exhaustive": true` enables full-list checking). Report type: `ExtractionFixtureTestReport`.

Report type naming convention: all report structs use the `Report` suffix. `DaemonRunReport` is the return type of `daemon run` (foreground execution); `DaemonActionReport` covers lifecycle actions (start/stop); `DaemonRootReport` covers root management (add/remove); `DaemonRootsReport` is the return type of `daemon list` and now includes a `running: bool` field — when the daemon is not running, `list` returns exit 0 with `{running: false, roots: []}` rather than `Err`. Report structs do not carry a `success: bool` field — errors are signalled via `Err(...)` in the `Result` return type. The single-method display bridge pattern (`display_X` wrapping `format_text`/`format_pretty`) has been replaced: all services that needed pretty-routing now use a single `display_output<T: OutputFormatter>` generic method.

`ContextService` (`context.rs`) hosts the `normalize context` sub-service: the default `context` method (frontmatter-filtered context resolution from `.normalize/context/` directories, with daemon-backed v2 caching when available and a filesystem walk fallback) and `migrate` (migration helper for old `.context.md`/`CONTEXT.md` files — dry-run preview by default, `--apply` performs the migration). Report types: `ContextKindReport` (wraps `ContextListReport` or `ContextReport`), `ContextMigrateReport`, `ContextMigrateEntry`.

`PackageService` (`package.rs`) now returns typed report structs (`PackageInfoReport`, `PackageListReport`, `PackageTreeReport`, `PackageWhyReport`, `PackageOutdatedReport`, `PackageAuditReport`) backed by `normalize_ecosystems` types; the old `PackageReport { success, data }` hollow wrapper is gone. `PackageListReport` includes `ecosystems_detected: Vec<String>` — when multiple ecosystems are present, only the primary is queried, but the field names all detected ecosystems so agents can re-run with `--ecosystem` to get full coverage. `ConfigService` (`config.rs`) provides `normalize config schema/show/validate/set`; `schema()` now returns `ConfigSchemaReport` with a `format_text()` impl. `validate()` runs four phases per config file (TOML syntax, JSON Schema, serde deserialization, rules config parsing) on both project and global configs; `ConfigValidateReport` contains structured `ConfigError` entries with file, phase, message, and optional line/column. `RankService` (`rank.rs`) hosts all ranked-list commands. `TrendService` (`trend.rs`) hosts time-series commands (`normalize trend`): `multi` (all metrics), `complexity`, `length`, `density`, `test-ratio` — moved from `AnalyzeService` where they lived as `complexity-trend`, `length-trend`, `density-trend`, `test-ratio-trend`, and `trend`. `ViewService` (`view.rs`) hosts `view` plus graph navigation subcommands; `trace()` returns `TraceReport`. `referenced-by` returns `Vec<CallEntry>` where `CallEntry.access` is an optional `"read"` / `"write"` / `"read-write"` field (currently `None` — shape reserved for scope-engine population). `view()` now populates `ViewReport.dir_context` with the merged context content when `--dir-context` is passed, so JSON consumers receive the context alongside the main view output. `chunk()` is a new subcommand (`normalize view chunk <file> --chunk N [--chunk-size M]` or `--around <pattern> [--context-lines N] [--match-index I]`) returning `ChunkedViewReport` for navigating large files in fixed-size chunks or by pattern proximity. `import-path <from> <to>` finds the shortest import chain between two files via BFS over the resolved import graph (requires facts index); `--all` returns all simple paths up to `--limit` (default 5), `--reverse` finds paths from `<to>` to `<from>`. Returns `ImportPathReport` with `from`, `to`, `paths`, `all_paths`. `SessionsService` (`sessions.rs`) supports `--mode` on list/stats/messages/analyze; `list` now accepts `--reviewed`/`--unreviewed` to filter by `.normalize/sessions-reviewed`; `messages` now accepts composable turn/message filters: `--has-tool`, `--errors-only`, `--exclude-interrupted`, `--turn-range`, `--min-chars`, `--max-chars`; `mark`/`unmark` subcommands write/remove session IDs from `.normalize/sessions-reviewed`; `stats --by-repo` groups all sessions by repository and returns `RepoStatsReport` (per-repo session count, turns, tokens_in/out, error rate, parallelization rate, cost); `ngrams [session-id] [--n N] [--top K] [--role all|assistant|user]` extracts word n-gram frequencies from message text and returns `NgramsReport` (top-K entries with count). `RatchetService` (`ratchet.rs`) is a thin re-export of `normalize_ratchet::service::RatchetService`. The `normalize ci` method runs all three rule engines in sequence and exits non-zero on errors; flags `--no-syntax`, `--no-native`, `--no-fact`, `--strict`, `--sarif`, `--path` (scope to subdirectory). If the structural index is not built, fact rules are skipped with a warning diagnostic (`ci/fact-rules-skipped`) rather than erroring out. `AnalyzeService.health()` now accepts `--limit` (default 10, 0 = no limit) to cap the `large_files` array in output. `ArchitectureReport` now has a compact `format_text()` that emits one tagged line per finding (`HUBS:`, `LAYERS:`, `COUPLING:`, `SYMBOLS:`, `ORPHANS:`, `SUMMARY:`) — sections are always emitted even if empty so agents can distinguish "none found" from "not analysed"; `format_pretty()` retains the original tabular layout for human terminals. `SyntaxService.ast()` now accepts `--depth` (default 5; `-1` = unlimited) to truncate the CST tree at a given depth, and honors `--compact` to emit a node-type outline instead of a full tree dump. `SyntaxService.query()` compact output now shows one line per match (`path:line: @capture = text`) instead of a useless match count. The `ci` command now threads `WalkConfig` from the project config to its native rule invocations; the service module's health, rules, and daemon code paths all pass the project's `[walk]` configuration to file walkers.
