# Resume an existing system Most real work does not start on an empty repository: it is brownfield. You inherit a half-built service, rejoin a mission you paused, or face a system that was never built on any principle. runward's answer is a single read-only command, runward characterize, that turns an undocumented codebase into the factual starting point of a governed mission — and a workflow that tells you what to do with it. ## What `runward characterize` does (and does not do) `runward characterize` produces a factual inventory of an existing codebase and writes it to `runward/characterization.md`. It is deliberately, structurally limited: - **Read-only.** It opens files and parses artifacts at rest. It never installs, builds, runs, or instruments the target, and makes no network call to it (ADR-0014). - **Writes only into `runward/`.** It never writes to your source tree and never touches `.git/`. Reading git history is done through read-only git commands like `git rev-list`, `git log`, and `git shortlog`, never a write. - **Zero-LLM, deterministic.** The whole command is pure parsing; two runs on the same commit produce the same output. No model is called and none is bundled. Note this scopes to the *command* — the LLM that refines candidates into argued decisions lives in your harness, downstream, never in runward. - **Facts, not decisions.** The output header is stamped `confidence: high` and states plainly that nothing in it explains *why* the system exists. It never emits "here is your architecture" or "here is your compliance posture" — that temptation is exactly what the contract forbids by construction. ### Exit codes - `0` when the inventory is produced. - `2` when there is no readable target directory. ## What it reads The scan walks the tree — bounded to depth 6, skipping heavy directories like `.git`, `node_modules`, `dist`, `build`, `.venv`, `target` — and collects: - **Dependency ecosystems.** Node/JS/TS is parsed in full from `package.json`: runtime dep count, dev dep count, and the actual dependency names. Python (`pyproject.toml`, `requirements.txt`, `setup.py`, `Pipfile`), Go (`go.mod`, counting require-block lines), and Rust/Java/Ruby/PHP are detected by manifest presence plus lockfile; for these, dependency *counts* are partial (only `requirements.txt` and `go.mod` are line-counted; the rest report presence only). A missing lockfile is reported as **`none` (unpinned)** in the rendered table. A malformed `package.json` is still recorded, with deps left unknown rather than crashing. - **Entrypoints.** Declared `main`/`bin` in `package.json`, plus convention files like `index.ts`, `src/index.ts`, `src/main.ts`, `main.py`, `app.py`, `src/main.rs`, `main.go`. - **CI / pipelines.** Every `.yml`/`.yaml` under `.github/workflows/`, plus `.gitlab-ci.yml`, `azure-pipelines.yml`, `.circleci/config.yml`, `Jenkinsfile`, `.drone.yml`. - **Containers / deployment.** `Dockerfile`, `docker-compose*`, `compose.yaml`, `vercel.json`, `netlify.toml`, `fly.toml`, `Procfile`, `Chart.yaml`. - **Tests.** Presence of `test/`, `tests/`, `__tests__/`, `spec/` directories, and a count of files matching test naming conventions across JS/TS, Go (`_test.go`), Python (`test_*.py`), and Java (`*Test.java`). No coverage figure is read or rendered — only directory presence and a file count. - **Git shape.** Counts only: total commits, first-commit and last-commit dates (span), and distinct author count. The rendered section states outright that the log tells you *what* changed and *when*, rarely *why* — and the *why* is a decision to reconstruct, not a fact to read. If the target is not a git repo (or git is absent), this is `null` and reported as such. The rendered file also reports an approximate scanned file count. ## Optional: candidate ADR mining with `--mine` ### What it proposes Add `--mine` to also propose *candidate* retroactive decisions. This is still deterministic git archaeology with **no model call**. It proposes: - one candidate per detected stack; - one for the deployment target (if any); - one for the CI pipeline (if any); - up to eight for notable dependency families (web framework / UI, data store / ORM, queue / messaging, AI / model provider, external-service SDK). For each stack candidate it looks up the manifest's first-seen date via `git log --diff-filter=A --reverse`. ### How candidates are written Each candidate is written as `runward/adr/DRAFT-.md` with `Status: hypothesis`, the evidence pointers, `why: UNKNOWN — the operator must supply the rationale`, and a mandatory (empty) re-evaluation trigger. - DRAFTs are written with `force:false` so a DRAFT you have already started editing is **never** clobbered. - Operator-ratified `ADR-NNNN-*.md` files are never targeted or overwritten. - `characterization.md` itself is always refreshed (idempotent, `force:true`). ### None of it can pass the gate Nothing `--mine` emits can pass the gate: a DRAFT carries `Status: hypothesis`, and `runward check --strict` stays red until you rewrite the *why*, add a trigger, set `Status: accepted`, and rename the file to `ADR-NNNN-.md`. Ratifying a candidate is your call, not your agent's. ## The recommended brownfield workflow The command prints its own next steps, which route into `runward/workflows/brownfield.md`. The workflow's rule is: **characterize before touching, never rewrite in one block**. ### Four entry modes It identifies four entry modes and tells you to calibrate on the most demanding when they overlap: - **M1 — Resume your own mission.** Artifacts exist. Reread them, rebuild state from the persisted artifacts (never memory), find the phase reached, pick up there. - **M2 — Join a project in flight.** Inventory the system, then produce the missing upstream deliverables: a light retro-framing, an architecture note reconstituted by observation, and retroactive ADRs. Re-enter the chain, typically at `architect`. - **M3 — Audit then rebuild.** A principled audit and a section-by-section gap analysis first, then a staged rebuild — never one block. - **M4 — Derive a new system from an old one.** Treat the old system as requirements and constraints, not a base to copy; re-enter as greenfield at `frame`. ### Rebuilds (M3/M4) For rebuilds (M3/M4), the workflow prescribes three moves in order, never skipped: 1. Characterize behavior with characterization tests before any change — and note that *these runtime tests belong to you in your own harness, not to a runward command*. 2. Install an anticorruption layer at the boundary. 3. Replace progressively, strangler-style, each step guarded and reversible. Every structuring choice you keep or take is locked as an ADR, then you re-enter the standard `method` chain with the same gates as greenfield. ## Agent-operable, wire-boundary intact An agent can drive this end-to-end: run `runward characterize --mine`, read `runward/characterization.md` and the `DRAFT-*.md` files, and help you reconstruct each *why*. What it cannot do is launder a hypothesis into an accepted decision on its own — the gate stays red until *you* ratify. And `characterize` never wires anything into your repo: it writes only under `runward/`, never `.git/`, consistent with runward's rule that adapters are inert samples the operator wires (ADR-0012). The read-only boundary here is a deliberate authorization choice, not a missing feature. ## Quick reference ``` runward characterize # read-only inventory → runward/characterization.md runward characterize --path ./svc # target a subdirectory runward characterize --mine # also propose DRAFT retroactive ADRs (deterministic) RUNWARD_DRY_RUN=1 runward characterize --mine # print intended writes, write nothing ``` Dry-run works two ways: the global `--dry-run` flag (`runward --dry-run characterize --mine`) or the `RUNWARD_DRY_RUN=1` environment variable; both print the intended writes without touching disk. The command's own options are `--path` and `--mine`. Exit `0` = inventory produced; exit `2` = no readable target directory. `--mine` never changes the exit code: it is advisory. ## Why this choice The command is deliberately read-only and zero-LLM by construction, not convention. ADR-0014 records the alternatives rejected: 1. Letting characterize RUN/instrument the system to observe real behavior — rejected because it violates never-a-runtime and would execute untrusted code; runtime characterization tests belong to the operator's own harness. 2. Emitting ADRs directly or auto-accepting mined decisions — rejected because it launders a hypothesis into a validated fact and lets a machine guess satisfy the gate, breaking operator-owns-the-gate. 3. Putting mining in the default path — rejected so the deterministic inventory stands alone, offline and reproducible. 4. One combined artifact — rejected to keep confidence:high facts (characterization.md) sharply separate from hypothesis guesses (DRAFT-*.md). The point is to make it structurally impossible for an agent reading a real codebase to emit conclusions that reach the gate. ## What comes next ADR-0014's re-evaluation trigger watches field use of `characterize --mine`. Two signals matter: - operators promoting DRAFTs unread (trusting proposals without ratification); - the deterministic inventory being asked to make judgments beyond parsing. The response is to add friction to DRAFT promotion (an explicit per-ADR confirm) or narrow the inventory — never to let characterize drift toward asserting conclusions. ## See also - [Quickstart](https://runward.dev/docs/en/getting-started/quickstart/) - [The six phases](https://runward.dev/docs/en/concepts/six-phases/) - [From an AI agent](https://runward.dev/docs/en/operating/from-an-agent/) - [Maintain & diagnose](https://runward.dev/docs/en/operating/maintain/)