regent-reverse
Reverse a Git repository into REgent's structured spec/ tree. The output is a
catalog an AI coding agent can read end-to-end to rebuild the project from
scratch.
When to Use
- User gives a repo URL (or local path) and asks to reverse it, describe it,
or "make a spec".
- User asks "what does this repo do" with a need for deep, structured output
rather than a chatty answer.
- The next step will be a build/refactor pipeline that needs a stable
spec schema as input.
Don't use for: a single-line "what does this file do", quick Q&A about a
repo, or any task where a structured tree is overkill.
Inputs
repo_url or local path (REQUIRED)
out_dir where spec/ will be written (defaults to ./spec-out/<repo-name>/)
depth — quick | normal (default) | deep. Affects how exhaustive
per-file analysis is. quick skips test-file deep dives.
scope — whole (default) | single-module <name> | single-file <path>.
For repos >30 modules you almost always want a focused scope; a full
scan on a large repo can blow the budget. Pick by intent: pick the
module that most users touch (e.g. console.py for rich).
When you pick anything other than whole, note the choice at the top
of AGENTS.md and write specs/_scope.md listing the modules you did
NOT cover (one line each is enough).
Workflow (the agent MUST follow in order)
Each step ends with a checkable condition. Do not advance until the previous
step's condition is true.
1. Acquire
- For a URL:
git clone --depth 1 <url> into a temp worktree.
- For a local path: copy or use in place. Never mutate the source.
- Verify:
ls <worktree> shows entry files (README, package.json / pyproject /
Cargo.toml / go.mod / etc.).
2. Recon (mandatory full-tree scan)
The agent MUST inspect every entry in the tree, including hidden files
(.github/, .vscode/, CI configs, Dockerfile, license headers). For each entry
record: path, kind (code | config | doc | test | asset | build | ci), rough
LOC, language/format.
Verification: produce a file count tally — total files, code files, test
files, config files. The tally must be non-zero in every category unless the
project genuinely lacks one.
3. Module / dependency mapping
Before per-file reading, identify:
- Top-level directory layout (
src/, lib/, cmd/, internal/, etc.).
- Public entry points (CLI entry, main,
__init__.py, exported package).
- Module boundaries — which directories are cohesive units?
- Dependency graph: imports between modules. Use
grep over import /
from / use / #include.
If the repo has 30+ top-level files, do this with a quick scan, not full read.
Lazy-import note: many real-world modules import dependencies inside
function bodies (from .jupyter import display inside a method). A top-of-file
grep misses these. Re-run the import scan after step 4 reveals them; any
module that imports lazily MUST be listed in architecture-rules.md with the
trigger site. Rust analog: use … inside function bodies AND
#[cfg(target_os = "…")] platform gates — both are visibility-class
boundaries and must be re-grepped after step 4.
4. Per-file deep read (lazy rule: read all source and all tests)
This is the core step. Do not skim. For every source file and every
test file, record:
- One-line purpose (what, not how).
- Public API: function/class names + signatures + 1-line behavior.
- Notable invariants: preconditions, error paths, side effects, locks.
- Test coverage hint: which test file(s) exercise this code.
For docs and config, summarize intent only — full API capture is for code.
Skip: generated files (under dist/, build/, node_modules/, lock
files), vendored deps, large binary blobs. List them with one line.
5. Inferred conventions
Mine the codebase for:
- Style: indentation, quote style, line length, formatter presence (look for
.editorconfig, .prettierrc, pyproject.toml [tool.black], etc.).
- Type system usage.
- Error handling patterns.
- Test framework and patterns (fixture style, mocking, parametrization).
- Build / lint commands (from
package.json scripts, tox.ini,
Makefile, .github/workflows/).
# pragma: no cover markers — they encode platform-conditional code
(e.g. Windows-only branches). Note them in architecture-rules.md so
the rebuild agent knows gaps are intentional.
- Compile-time backend selection: if the project has a feature-flag
mechanism (Rust
[features] in Cargo.toml, Python extras_require,
Go build tags), list each feature, what it gates, and the default
state in conventions/dev-env.md. Hidden compile-time paths break a
rebuild silently.
Verification: every inferred rule is sourced — quote the file/line.
Files that own I/O (printing, network, filesystem) MUST produce an
entry in architecture-rules.md covering: exception-to-exit policy,
SystemExit paths, std-stream side effects (os.dup2, broken-pipe
handling). A rebuild that misses these will pass unit tests and fail in
production. For Rust the analog scan keywords are std::fs, std::io::Write,
process::exit, #[cfg(target_os = "…")] — same rule, different syntax.
6. Build the functional inventory
Enumerate user-observable behaviors and verification commands. Examples:
- "Running
<cmd> --help prints usage and exits 0."
- "
pytest runs N tests, all pass."
- "Tool round-trips input file X to output identical to expected Y."
This list becomes inventory/functional-checklist.md and is the build
phase's grading key. Aim for 10–50 entries depending on repo size; small
repo may have fewer.
Distill a test-oracle side by side. For each public function or method,
identify the one test in the original test suite that most strongly pins
its contract — the one with the most discriminating input and the
strictest expectation. Record its essentials (input, expected output, the
property it pins) into inventory/test-oracle.md as a second, finer-grained
grading key. Purpose:
functional-checklist.md = black-box behaviour ("does the CLI exit 0?").
test-oracle.md = white-box per-function invariants (the exact
whitespace literal " " that triggers ValueError, the exact
precedence of case_insensitive vs multi_line, the exact failure
message when a missing style is requested).
test-oracle.md is not a copy of the original test_*.py. It is the
semantically meaning-bearing subset of the test suite — one or two
fixtures per public surface, no scaffolding. Copy-and-paste of the
original test files defeats the purpose: the rebuild should re-derive
the test from the oracle, not transcribe it.
7. Emit spec tree
Write to <out_dir>:
spec/
├── AGENTS.md # Index + rebuilding instructions (one file)
├── README.md # Human overview of the original project
├── architecture.md # Goals / quality goals / building blocks (arc42-lite)
├── layout/
│ ├── tree.txt # Original tree (sortable, with annotations)
│ └── src.map.md # File → purpose + public API
├── specs/
│ └── <module>.spec.md # One per module: should/must + Scenarios
├── conventions/
│ ├── code-style.md
│ ├── dev-env.md # Build / test / lint commands
│ └── architecture-rules.md
└── inventory/
├── functional-checklist.md # Black-box behaviour, build grading key
└── test-oracle.md # White-box per-function invariants
File rules:
AGENTS.md is mandatory and is the first file a coding agent reads.
It links to the rest and tells the agent to use this spec to rebuild from
scratch, not to copy code.
- Each
specs/*.spec.md uses OpenSpec-lite form:
## Purpose / ## Requirements (with SHOULD/MUST) / ## Scenarios
(WHEN...THEN... form).
- Each spec MUST include a literal table of any human-facing output
strings (greetings, error prefixes, log formats), byte-for-byte. The
rebuild agent must not have to infer punctuation from a checklist.
- Each spec MUST spell out the exit-code prefix for CLI error paths if
applicable (e.g.
error: <message> to stderr). If the module has no
CLI entry, substitute: list every exception type it raises with
byte-exact messages for non-DebugError ones.
- A
if __name__ == "__main__": block in any source file is NEVER part
of the contract — record it in layout/src.map.md with
role: self-demo, not API. A rebuild must omit it. Language-agnostic
rule: any self-test or self-demo entry point is not contract —
in Rust this is #[cfg(test)] mod tests { … } blocks or test-only
helper fns. The real fn main() of a binary crate IS contract.
- For every
Protocol / ABC / trait / interface declared in the scanned
source, list every abstract / duck-typed method in the spec's R-
section, with the caller dispatch site (hasattr(x, '__rich_console__')
in Python, &dyn Trait / impl<T: Trait> Trait for &T blanket impls in
Rust). Protocol contracts are silently invisible to importers; the spec
must carry them.
conventions/*.md cites files and line numbers wherever it states a rule
— every rule must be evidence-backed.
inventory/functional-checklist.md is plain markdown checklist,
machine-greppable. Each entry has a - [ ] form and a verification
command.
inventory/test-oracle.md contains per-function/per-method fixtures
in the form ### <symbol> / - input: … / - expects: … /
- pins: …. One entry per public surface that has a discriminating
test in the original suite. Do not include fixture scaffolding,
mocking, parametrization tables, or anything not directly load-bearing.
- Spec does not pin
pyproject.toml metadata fields (description,
author), LICENSE body, or README.md content — those are
presentation, not contract. The rebuild invents them.
8. Self-review
Before declaring done:
- All categories of files from step 2 accounted for in some output file.
- Every module has a spec OR a documented reason to skip.
- Every inferred convention in
conventions/ cites a source.
- The functional checklist has at least one entry per public CLI command /
script / API surface.
- Run
tree <out_dir> (or equivalent) and verify the layout matches step 7.
Common Pitfalls
- Skimming the tree. The whole point is thoroughness. If you read
fewer than 80% of source files, redo this step.
- Hallucinating APIs. Public APIs MUST come from the actual source. If
you cannot confirm a name exists, omit it; never invent.
- Generic conventions. "Use camelCase" without evidence is noise. Every
rule needs a sample file or config that backs it (
rustfmt.toml line 1,
.editorconfig, [tool.black], etc.) — language-agnostic, citation-based.
- Conflating original code with spec. The spec describes what the
project does and why — not line-for-line code. If
src.map.md starts
copying source, rewrite it.
- Skipping tests. Tests reveal the project's true contract. Read them.
- Missing functional checklist. Without it the build phase has no
grading key. Don't ship without one.
- Treating lazy imports as trivial / missing them in the dep graph.
Many real-world modules defer heavy imports (
display, windows-only,
LegacyWindowsTerm) into method bodies. A top-of-file import grep
reports zero deps. Re-scan after step 4 catches them.
- Copying the original test suite into
test-oracle.md. The oracle
is the load-bearing subset, not a transcription. If a test reads
exactly like tests/test_x.py, the agent is shipping the original
tests under a new name and the rebuild is no longer a roundtrip —
it's a copy.
Verification Checklist
One-Shot Recipe
# From REgent project root
git clone --depth 1 https://github.com/<owner>/<repo>.git /tmp/repo
# Then run regent-reverse skill, supplying the path
# Output appears at ./spec-out/<repo>/spec/
1---2name: regent-reverse3description: Use when the user asks to reverse-engineer a Git repository into a structured natural-language spec, write a structured spec for any codebase, or generate "repo → spec/" output. Triggers on phrases like "还原这个仓库", "把仓库转成 spec", "reverse engineer <url>", "生成结构化说明", or when the user supplies a repo URL/path and asks REgent to describe it. Drives the agent to deeply explore the entire codebase (every file, every test, every config) before writing the spec tree. Works across Python, Rust, Go, and other languages — the rules are language-aware only where they have to be (e.g. `trait` vs `Protocol` vs interface).4license: GPL-3.0-or-later5---67# regent-reverse89Reverse a Git repository into REgent's structured `spec/` tree. The output is a10catalog an AI coding agent can read end-to-end to rebuild the project from11scratch.1213## When to Use1415- User gives a repo URL (or local path) and asks to reverse it, describe it,16 or "make a spec".17- User asks "what does this repo do" with a need for deep, structured output18 rather than a chatty answer.19- The next step will be a build/refactor pipeline that needs a stable20 spec schema as input.2122**Don't use for**: a single-line "what does this file do", quick Q&A about a23repo, or any task where a structured tree is overkill.2425## Inputs2627- `repo_url` or local path (REQUIRED)28- `out_dir` where `spec/` will be written (defaults to `./spec-out/<repo-name>/`)29- `depth` — `quick` | `normal` (default) | `deep`. Affects how exhaustive30 per-file analysis is. `quick` skips test-file deep dives.31- `scope` — `whole` (default) | `single-module <name>` | `single-file <path>`.32 For repos >30 modules you almost always want a focused scope; a full33 scan on a large repo can blow the budget. Pick by intent: pick the34 module that most users touch (e.g. `console.py` for `rich`).35 When you pick anything other than `whole`, note the choice at the top36 of `AGENTS.md` and write `specs/_scope.md` listing the modules you did37 NOT cover (one line each is enough).3839## Workflow (the agent MUST follow in order)4041Each step ends with a checkable condition. Do not advance until the previous42step's condition is true.4344### 1. Acquire4546- For a URL: `git clone --depth 1 <url>` into a temp worktree.47- For a local path: copy or use in place. Never mutate the source.48- Verify: `ls <worktree>` shows entry files (README, package.json / pyproject /49 Cargo.toml / go.mod / etc.).5051### 2. Recon (mandatory full-tree scan)5253The agent MUST inspect every entry in the tree, including hidden files54(.github/, .vscode/, CI configs, Dockerfile, license headers). For each entry55record: path, kind (code | config | doc | test | asset | build | ci), rough56LOC, language/format.5758Verification: produce a file count tally — total files, code files, test59files, config files. The tally must be non-zero in every category unless the60project genuinely lacks one.6162### 3. Module / dependency mapping6364Before per-file reading, identify:6566- Top-level directory layout (`src/`, `lib/`, `cmd/`, `internal/`, etc.).67- Public entry points (CLI entry, main, `__init__.py`, exported package).68- Module boundaries — which directories are cohesive units?69- Dependency graph: imports between modules. Use `grep` over `import` /70 `from` / `use` / `#include`.7172If the repo has 30+ top-level files, do this with a quick scan, not full read.7374**Lazy-import note:** many real-world modules import dependencies *inside*75function bodies (`from .jupyter import display` inside a method). A top-of-file76`grep` misses these. Re-run the import scan after step 4 reveals them; any77module that imports lazily MUST be listed in `architecture-rules.md` with the78trigger site. **Rust analog:** `use …` inside function bodies AND79`#[cfg(target_os = "…")]` platform gates — both are visibility-class80boundaries and must be re-grepped after step 4.8182### 4. Per-file deep read (lazy rule: read all source and all tests)8384This is the core step. **Do not skim.** For every source file and every85test file, record:8687- One-line purpose (what, not how).88- Public API: function/class names + signatures + 1-line behavior.89- Notable invariants: preconditions, error paths, side effects, locks.90- Test coverage hint: which test file(s) exercise this code.9192For docs and config, summarize intent only — full API capture is for code.9394**Skip**: generated files (under `dist/`, `build/`, `node_modules/`, lock95files), vendored deps, large binary blobs. List them with one line.9697### 5. Inferred conventions9899Mine the codebase for:100101- Style: indentation, quote style, line length, formatter presence (look for102 `.editorconfig`, `.prettierrc`, `pyproject.toml [tool.black]`, etc.).103- Type system usage.104- Error handling patterns.105- Test framework and patterns (fixture style, mocking, parametrization).106- Build / lint commands (from `package.json scripts`, `tox.ini`,107 `Makefile`, `.github/workflows/`).108- `# pragma: no cover` markers — they encode platform-conditional code109 (e.g. Windows-only branches). Note them in `architecture-rules.md` so110 the rebuild agent knows gaps are intentional.111- **Compile-time backend selection:** if the project has a feature-flag112 mechanism (Rust `[features]` in `Cargo.toml`, Python `extras_require`,113 Go build tags), list each feature, what it gates, and the default114 state in `conventions/dev-env.md`. Hidden compile-time paths break a115 rebuild silently.116117Verification: every inferred rule is sourced — quote the file/line.118119**Files that own I/O (printing, network, filesystem) MUST produce an120entry in `architecture-rules.md` covering:** exception-to-exit policy,121`SystemExit` paths, std-stream side effects (`os.dup2`, broken-pipe122handling). A rebuild that misses these will pass unit tests and fail in123production. For Rust the analog scan keywords are `std::fs`, `std::io::Write`,124`process::exit`, `#[cfg(target_os = "…")]` — same rule, different syntax.125126### 6. Build the functional inventory127128Enumerate user-observable behaviors and verification commands. Examples:129130- "Running `<cmd> --help` prints usage and exits 0."131- "`pytest` runs N tests, all pass."132- "Tool round-trips input file X to output identical to expected Y."133134This list becomes `inventory/functional-checklist.md` and is the **build135phase's grading key**. Aim for 10–50 entries depending on repo size; small136repo may have fewer.137138**Distill a test-oracle side by side.** For each public function or method,139identify the *one* test in the original test suite that most strongly pins140its contract — the one with the most discriminating input and the141strictest expectation. Record its essentials (input, expected output, the142property it pins) into `inventory/test-oracle.md` as a second, finer-grained143grading key. Purpose:144145- `functional-checklist.md` = black-box behaviour ("does the CLI exit 0?").146- `test-oracle.md` = white-box per-function invariants (the exact147 whitespace literal `" "` that triggers `ValueError`, the exact148 precedence of `case_insensitive` vs `multi_line`, the exact failure149 message when a missing style is requested).150151`test-oracle.md` is **not** a copy of the original `test_*.py`. It is the152*semantically meaning-bearing subset* of the test suite — one or two153fixtures per public surface, no scaffolding. Copy-and-paste of the154original test files defeats the purpose: the rebuild should re-derive155the test from the oracle, not transcribe it.156157### 7. Emit spec tree158159Write to `<out_dir>`:160161```162spec/163├── AGENTS.md # Index + rebuilding instructions (one file)164├── README.md # Human overview of the original project165├── architecture.md # Goals / quality goals / building blocks (arc42-lite)166├── layout/167│ ├── tree.txt # Original tree (sortable, with annotations)168│ └── src.map.md # File → purpose + public API169├── specs/170│ └── <module>.spec.md # One per module: should/must + Scenarios171├── conventions/172│ ├── code-style.md173│ ├── dev-env.md # Build / test / lint commands174│ └── architecture-rules.md175└── inventory/176 ├── functional-checklist.md # Black-box behaviour, build grading key177 └── test-oracle.md # White-box per-function invariants178```179180File rules:181182- `AGENTS.md` is **mandatory** and is the first file a coding agent reads.183 It links to the rest and tells the agent to use this spec to rebuild from184 scratch, not to copy code.185- Each `specs/*.spec.md` uses OpenSpec-lite form:186 `## Purpose` / `## Requirements` (with SHOULD/MUST) / `## Scenarios`187 (WHEN...THEN... form).188- Each spec MUST include a literal table of any human-facing output189 strings (greetings, error prefixes, log formats), byte-for-byte. The190 rebuild agent must not have to infer punctuation from a checklist.191- Each spec MUST spell out the exit-code prefix for CLI error paths if192 applicable (e.g. `error: <message>` to stderr). If the module has **no193 CLI entry**, substitute: list every exception type it raises with194 byte-exact messages for non-`DebugError` ones.195- A `if __name__ == "__main__":` block in any source file is **NEVER part196 of the contract** — record it in `layout/src.map.md` with197 `role: self-demo, not API`. A rebuild must omit it. **Language-agnostic198 rule:** any *self-test* or self-demo entry point is not contract —199 in Rust this is `#[cfg(test)] mod tests { … }` blocks or test-only200 helper `fn`s. The real `fn main()` of a binary crate IS contract.201- For every `Protocol` / `ABC` / `trait` / interface declared in the scanned202 source, list every abstract / duck-typed method in the spec's `R-`203 section, with the caller dispatch site (`hasattr(x, '__rich_console__')`204 in Python, `&dyn Trait` / `impl<T: Trait> Trait for &T` blanket impls in205 Rust). Protocol contracts are silently invisible to importers; the spec206 must carry them.207- `conventions/*.md` cites files and line numbers wherever it states a rule208 — every rule must be evidence-backed.209- `inventory/functional-checklist.md` is plain markdown checklist,210 machine-greppable. Each entry has a `- [ ]` form and a verification211 command.212- `inventory/test-oracle.md` contains per-function/per-method fixtures213 in the form `### <symbol>` / `- input: …` / `- expects: …` /214 `- pins: …`. One entry per public surface that has a discriminating215 test in the original suite. **Do not include fixture scaffolding,216 mocking, parametrization tables, or anything not directly load-bearing.**217- Spec does not pin `pyproject.toml` metadata fields (description,218 author), `LICENSE` body, or `README.md` content — those are219 presentation, not contract. The rebuild invents them.220221### 8. Self-review222223Before declaring done:224225- All categories of files from step 2 accounted for in some output file.226- Every module has a spec OR a documented reason to skip.227- Every inferred convention in `conventions/` cites a source.228- The functional checklist has at least one entry per public CLI command /229 script / API surface.230- Run `tree <out_dir>` (or equivalent) and verify the layout matches step 7.231232## Common Pitfalls2332341. **Skimming the tree.** The whole point is *thoroughness*. If you read235 fewer than 80% of source files, redo this step.2362. **Hallucinating APIs.** Public APIs MUST come from the actual source. If237 you cannot confirm a name exists, omit it; never invent.2383. **Generic conventions.** "Use camelCase" without evidence is noise. Every239 rule needs a sample file or config that backs it (`rustfmt.toml` line 1,240 `.editorconfig`, `[tool.black]`, etc.) — language-agnostic, citation-based.2414. **Conflating original code with spec.** The spec describes what the242 project *does and why* — not line-for-line code. If `src.map.md` starts243 copying source, rewrite it.2445. **Skipping tests.** Tests reveal the project's true contract. Read them.2456. **Missing functional checklist.** Without it the build phase has no246 grading key. Don't ship without one.2477. **Treating lazy imports as trivial / missing them in the dep graph.**248 Many real-world modules defer heavy imports (`display`, `windows-only`,249 `LegacyWindowsTerm`) into method bodies. A top-of-file `import` grep250 reports zero deps. Re-scan after step 4 catches them.2518. **Copying the original test suite into `test-oracle.md`.** The oracle252 is the load-bearing subset, not a transcription. If a test reads253 exactly like `tests/test_x.py`, the agent is shipping the original254 tests under a new name and the rebuild is no longer a roundtrip —255 it's a copy.256257## Verification Checklist258259- [ ] Repo acquired without mutating the source.260- [ ] Full tree scanned; tally recorded in step 2.261- [ ] Module + dependency graph sketched.262- [ ] Every source file and every test file has at least one entry in263 `layout/src.map.md`.264- [ ] Each module has `specs/<module>.spec.md` with Purpose, Requirements,265 Scenarios.266- [ ] `conventions/*.md` cite file:line for every rule.267- [ ] `inventory/functional-checklist.md` has 10+ entries (or fewer if268 genuinely small repo, in which case state why).269- [ ] `inventory/test-oracle.md` exists and has at least one entry per270 public surface that had a discriminating test in the original suite271 (i.e. the inverse — every entry should be retraceable to a real272 test in the source).273- [ ] `AGENTS.md` is written and links the rest.274- [ ] `tree <out_dir>/spec` matches the layout in step 7.275- [ ] If scope ≠ whole, `specs/_scope.md` exists and lists every skipped276 module with a one-line reason.277- [ ] If any scanned file declared a `Protocol`/`ABC`/`trait`/interface,278 every abstract / required method appears as an `R-` requirement279 somewhere.280- [ ] If the project has compile-time feature flags (`[features]`,281 build tags, optional deps), every feature is documented in282 `conventions/dev-env.md` with what it gates.283284## One-Shot Recipe285286```bash287# From REgent project root288git clone --depth 1 https://github.com/<owner>/<repo>.git /tmp/repo289# Then run regent-reverse skill, supplying the path290# Output appears at ./spec-out/<repo>/spec/291```