runward

RW™ · V0.22.0

Docs · Operate · Resume an existing system

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-<slug>.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-<slug>.md. Ratifying a candidate is your call, not your agent's.

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.

← Docs