'Characterize existing code into a rebuild package a team can reimplement from without the source: behavior specs, domain model, BR-NNN rules (explicit/implicit), quirks, gaps, entry-point completeness, each claim cited file:line with confidence. Use on "reverse-engineer this for a rebuild", "extract the business rules", "reimplement X without the original code", "characterize code for a rewrite".'
Recursos
5Instal·la
npx skillscat add blackbelttechnology/pi-agent-dashboard/reverse-spec-for-rebuild Instal·la des del registre de SkillsCat.
reverse-spec-for-rebuild
Turn existing code into a rebuild package: enough data shapes, business
rules, implicit behavior, provenance and known unknowns that a team can rebuild
the same business logic and use cases without the original source. Works in any
git repository; a kb tree and the OpenSpec CLI are optional accelerators.
Not the same as a kb-oriented behavioral spec generator: those strip line
numbers and rule catalogs on purpose. This skill keeps them, so its output never
goes under a kb-indexed root.
Methodology (provenance citations, confidence levels, state/edge/error
checklists, entry-point completeness) adapted and rewritten from greenfield
(Apache-2.0); see the package NOTICE.
When to use
- A system will be rewritten (new stack, new team, vendor exit) and its business
logic must survive. - You need a catalog of business rules, including the implicit ones that live in
defaults, exception handlers and ordering. - You need to know what is NOT known about a system (registered gaps).
Skip for a single trivial file, or when you only want searchable docs.
Inputs
- Target directory (default: repository root) and an optional single capability.
- Optional previous rebuild package (for stable identifiers on re-run).
- Optional protected roots override (default
openspec docs packages .pi).
Procedure
G below is node <this skill dir>/scripts/guard.mjs (the deterministic guards inscripts/guard.mjs: check-dest, sweep, lint-spec, lint-cite), run from the
repository root. PKG is .reverse-spec-scratch/<target-slug>/rebuild/
(<target-slug> = output of G slug <target>: root for the repository root,
else a kebab-case form of the canonical repo-relative path plus a short hash —
never ./.., never shared by two targets; it exits 2 for a target outside
the repository. Never derive the slug by hand).
- Scratch must be ignored.
git check-ignore -q .reverse-spec-scratch/
(keep the trailing slash: adir/ignore pattern does not match a
not-yet-existing path without it). If
it is not ignored,ask_userbefore writing anything: on consent append.reverse-spec-scratch/to.git/info/exclude(never to a committed ignore
file); on refusal stop. - Run id + sweep leftovers. Set
RUN_ID=$(G new-run)(UTC timestamp plus
random suffix, so concurrent runs never share it); every transient
validation id of this run is_rsfr-val-$RUN_ID-<cap>. RunG sweep— it
removes only abandonedopenspec/specs/_rsfr-val-*dirs (owner process gone,
or no owner marker and untouched for 10 minutes); a concurrent run's live
dirs and other skills' transient dirs are untouched. - Resolve the target. Confirm the path exists; record
git rev-parse HEADforPKG/README.md. In this order:- Lock the target.
SLUG=$(G slug <target>), thenG lock "$SLUG" "$RUN_ID". Exit 1 = another run holds this target
(never share aPKG):ask_userwhether that run is still active. Only
when the user confirms it is dead, run theG break-lock "$SLUG" <owner>
command the error names, thenG lockagain (acquisition is exclusive,
so a racing run cannot also win). Locks never expire on their own, so a
long step never loses ownership. Release withG unlock "$SLUG" "$RUN_ID"
at step 14, and whenever the run stops early. - Snapshot first. When a previous package is supplied (possibly
PKG
itself), copy itsrules.md,quirks.md,gaps.md,README.mdand_ids.json(when present) into.reverse-spec-scratch/<target-slug>/previous-$RUN_ID/, thenchmod -R a-wit. This frozen snapshot is the ONLY "previous package"
every merge of this run reads — neverPKG, which each merge rewrites. - Then move aside. If a package still sits at
PKG, move it toPKG.prev-$RUN_ID. Create an emptyPKG. - Seed the id high-water marks. With a previous package:
G seed-ids PKG/_ids.json <snapshot dir>; without one:G seed-ids PKG/_ids.json PKG(creates an all-zero file)._ids.jsonholds the highestBR/QUIRK/GAPnumber ever allocated,
including ids the previous package already retired.
- Lock the target.
- Discover. One subagent with
prompts/discovery.md(KB_AVAILABLE=kbtooling orAGENTS.mdfiles present). It returns a capability manifest.
Save it asPKG/_manifest.jsonand runG check-manifest PKG/_manifest.json;
on exit 2 (an unsafe or duplicate capability name) re-run discovery or
rename/merge the entries by hand, then re-check — names become file paths and
validation ids, target code is untrusted, and two capabilities with one name
would overwrite each other's outputs. Checkunassigned_filesis empty;
otherwise add them to a capability or ask the subagent to re-cluster. For a single-capability target you may write the
manifest by hand. - Generate in parallel. One subagent per capability, all in a SINGLE
message, withprompts/generator-rebuild.md. Each writes its unmerged spec
(local{r1}refs) toPKG/_fragments/<cap>.spec.md(SPEC OUTPUT) and its
fragment toPKG/_fragments/<cap>.json, nothing else. - Merge (this session — design D4; fall back to a consolidator subagent
only if the fragments exceed context):
Catalog boundary: a rule is anything that decides a caller-visible outcome
from domain data or failure class, including HTTP/WS/CLI error maps (which
failure class yields which status, code, reply or non-success exit) and
fallback handlers that hide or substitute details. Plumbing is a numbering or
formatting choice that encodes no decision (one non-success exit status for
every failure, output formatting, id formats, subscribe/unsubscribe
mechanics); plumbing belongs in the spec, not in the rule catalog.
Keep a rule that falls under this boundary inrules.md; never drop an
error map or fallback rule as plumbing. Keep rule statements verbatim:
never rewrite config key names (the generator names the literal external
key).- Citation check first, on every merge (initial and every re-merge from
a revise, completeness or cross-cutting loop):G lint-cite PKG/_fragments/*.json PKG/_fragments/*.spec.md.
Exit 1 lists everyconfirmedcite with more than one location and every
unterminated cite. For each finding this session may only LOWER the
confidence toinferred— the item in<cap>.jsonand the matching cite
comment in<cap>.spec.mdtogether, mechanically (the one exception to
step 5's rule that only generators write fragments). Narrowing a cite to
one location is the generator's job: route it asFINDINGS. Re-run until
exit 0; never merge while it fails. - Load
rules.md/quirks.md/gaps.mdfrom the frozenprevious-$RUN_ID/
snapshot (step 3) when one exists — on every merge of the run, so ids the
previous package retired stay retired even after this run rewritesPKG. - Walk fragments in manifest order. For each rule/quirk/gap, match it BY
MEANING against items already merged in this run (dedupe; add the
capability to the existing item), then against the frozen snapshot
(reuse its id), then against the catalogs of this run's previous merge
(reuse its id). Otherwise allocate withG next-id PKG/_ids.json <BR|QUIRK|GAP>
— never compute an id by hand. The mark only grows, so an id retired by
the previous package or by an earlier revision of this run is never
reused._ids.jsonships with the package so the next run inherits it. - Render EVERY capability's
PKG/capabilities/<cap>/spec.mdfrom its
unmergedPKG/_fragments/<cap>.spec.md, replacing each{r1}/{q1}/{g1}
with its global id — on every merge, for all capabilities, so a re-merge
that shifts ids cannot leave a stale reference in an unrevised spec. No{...}local reference may remain. Never edit the rendered spec by hand. - A merged item's confidence is the LOWEST of its sources; never raise it.
A merged item's cite is the union of its sources' locations; a union of
more than one location caps its confidence atinferred.
When a fragment rule spans several catalog rules, map its local id to
the closest one and list the others in the spec text, rather than
narrowing the claim. - Merge entities by name into
model.md; renderrules.md,quirks.md,gaps.md, usingreferences/package-templates.md. Keep a JSON origin mapitem id -> capabilityfor the cross-cutting audit. - At the end of EVERY merge, before step 7:
G lint-cite PKG/rules.md PKG/model.md PKG/quirks.md PKG/gaps.md PKG/capabilities/*/spec.md.
A finding only in merged output is a merge error: re-run the merge
applying rule 4 (never hand-edit a catalog). No audit runs while it fails.
- Citation check first, on every merge (initial and every re-merge from
- Audit in parallel. One subagent per capability in a SINGLE message,
prompts/auditor-rebuild.md,MODE=capability. - Revise loop. For each
verdict: revise, re-run its generator with the
audit JSON asFINDINGS, then go back to step 6 (every revision re-merges),
and re-audit every capability that was regenerated, whose rendered spec
changed, or that is listed under (or contributed) a catalog item — rule,
quirk, gap or entity field — whose statement, class, citation, confidence or
id changed in that merge. Diff the previous merge's catalog files against the
new ones to find those items. Stop after 3 rounds for a capability
that still fails: report it as not promotable. - Format gate. For every spec:
G lint-spec PKG/capabilities/<cap>/spec.md
(exit 1 listsfile:line: reason). Additionally, only when the OpenSpec CLI
is on PATH ANDopenspec/exists, validate each spec through a transient id
that is deleted in the same iteration. Run this block once per capability
(it is a subshell, safe to repeat in one shell) with shell variablesPKG
(package dir),CAP,RUN_IDandGset; it refuses (exit 2) aCAPorRUN_IDthat is not a single safe path component before touching any path; its exit status is the validation
result, and it records its own pid in.ownerso a concurrent sweep leaves
it alone while it runs:
A failing spec is regenerated with the failures as( $G check-cap "$CAP" && $G check-run "$RUN_ID" || exit 2 id="_rsfr-val-$RUN_ID-$CAP"; d="openspec/specs/$id" trap 'rm -rf "$d"' EXIT mkdir -p "$d" && sh -c 'echo $PPID' > "$d/.owner" && cp "$PKG/capabilities/$CAP/spec.md" "$d/spec.md" && openspec validate "$id" --type spec )FINDINGS(back to step
5 for that capability, then 6-9). It is never promotable while failing. - Completeness gate. One subagent with
prompts/completeness.mdwritesPKG/completeness.md, thenG lint-cite PKG/completeness.mdbefore the
cross-cutting audit (a finding re-runs completeness). Any unmapped entry point = FAIL: re-run the
suggested capability's generator with the unmapped list as FINDINGS — it
either specs the entry point or records it as a gap (and an entry point)
in its fragment. Never editrules.md/quirks.md/gaps.mdor a rendered
spec directly: every merge re-renders them from fragments, so a direct edit
is lost. Then loop back through steps 6-10. A package that fails is not
promotable.
Allow at most 3 rounds of this loop; if entry points are still unmapped,
report the package as not promotable, runG unlock "$SLUG" "$RUN_ID"
and stop. - Cross-cutting audit. One subagent with
prompts/auditor-rebuild.md,MODE=cross-cutting,ORIGINS= the origin map. Route each failing item
to the capability in itsorigin, regenerate it with the finding, and loop
back through steps 6-11.
Allow at most 3 rounds of this loop; if a cross-cutting finding persists,
report the package as not promotable, runG unlock "$SLUG" "$RUN_ID"
and stop. - Sweep again.
G sweep --run "$RUN_ID"thenG sweep; confirmgit status --porcelainshows nothing
new outside.reverse-spec-scratch/(a change elsewhere means a subagent
wrote outside its outputs — investigate before going on). - Gate summary. Write
PKG/README.md(template inreferences/package-templates.md, with the commit SHA) and report per
capability: audit verdict, format gate (lint-spec, plusopenspec validatewhen it ran), citation check (lint-citeon fragments, catalogs
andcompleteness.md), rule counts (explicit/implicit), quirks, gaps, and
the completeness verdict. - Promote on confirm. Offer promotion only when every capability passed
audit and format gate, completeness is PASS and the citation check
(G lint-cite) is clean.ask_userfor a
destination; runG check-dest <dest>(add--protect <dir>per root when
the user overrides the protected roots). Exit 1 = inside a protected root:
say so and ask for another destination. On exit 0 MOVE (not copy)PKGto
the destination. Never promote without explicit confirmation. FinallyG unlock "$SLUG" "$RUN_ID"(also when the user declines promotion).
Frontend UI model (optional, after step 13)
For a target with a user interface, after the package gate and before promotion
(the UI model cites the package's BR/QUIRK/GAP ids, so the catalogs must be
final). Full commands, adapter contract and rules: references/ui-extraction.md;
record format: references/ui-model.md. Programs: scripts/ui-extract/
(dependency-free); stack specifics in an adapter (built-inscripts/ui-extract/adapters/angularjs.mjs, or a project adapter by path). Application
knowledge (config layering, string tables, toolbar conventions, code page, template
helpers) goes in a project-owned profile with parent: "<built-in>" — never into the skill.
- Inventory + effective config + forms (deterministic):
inventory.mjs,config.mjsper customer variant,forms.mjs/screen-form.mjs. - Screen records. One subagent per screen or dialog, in a SINGLE message,
withprompts/ui-screen-generator.mdfilled byfill.mjsfrom a screen job;
each writes onlyPKG/ui/screens/<ID>.json. - Gate.
gate.mjs APP PKGuntil PASS; route a failing record back to its
generator with the gate lines. Never hand-edit a record to pass. - Style kit + screen plans (deterministic):
style-kit.mjs(kit job maps
components to real selectors), thenscreen-plan.mjsper screen and dialog;
an unlinked control (exit 1) means the record misses an action, field orunmappedreason — fix the record (step 2-3), or the adapter. - Flows from code per use case:
flow.mjs→ layout →check-trace;compare.mjsagainst the prose flow, and report the differences. - Behaviour models (optional): per key user action, a
rebuild-package-diagramssequence-from-uidraft deepened by one subagent withprompts/sequence-generator.md;
per stateful entity field (amodel.mdfield withallowed:values or a lock/report
flag), one subagent withprompts/state-machine-generator.md. Each record is accepted
only whencheck-sequences/check-stateswith--apppass; route gate lines back as
findings. Candidate quirks the generators report go to the merge as findings, never
straight intoquirks.md. - Use-case UI links (optional, before CRUD): per use-case batch one subagent with
prompts/uc-linker.mdlinks BPMN steps to UI actions with evidence (shared ref or a cite
inside the action's code), or recordsnoUi; accepted only whencheck-uc-linkspasses;
finish withcheck-uc-links PKG --complete. Links extend each use case's screens and UI
actions (CRUD columns, IFML scope, flows).
7a. Customer variability (optional, configurable apps):config-reads.mjs APP <adapter> PKG
(adapter hooksconfigReads,variantInfo) →ui/_config-reads.json; one subagent withprompts/variability-classifier.mdwritesdiagrams/variability/features.json; accepted only
whencheck-variability PKG APP --completepasses. Route findings (dead everywhere,
single-customer, constant) to the merge as gaps/questions.
7c. Usage evidence (optional, when the app keeps its own logs and a snapshot is available):
a project job describes the log sources per customer; one subagent withprompts/usage-mapper.mdmaps log types to code, UI actions and use cases
(diagrams/usage/mapping.json, shared); accepted only whencheck-usage PKG APP JOB --complete
passes. Counts are aggregated locally only (usage, then thecheck-usage-outputprivacy
gate) — never into the shared package.
7b. CRUD matrix (optional): per screen batch one subagent withprompts/crud-classifier.md
classifies every data effect (fromrebuild-package-diagramscrud-draft) as entity + C/R/U/D
or unmapped; accepted only whencheck-crudpasses; finish withcheck-crud PKG --complete.
Route its findings (never written / never read / created never deleted / untouched) and
tables with no model entity to the merge as gaps or questions. - Render: the
rebuild-package-diagramsskill'srender.sh PKG APP(all
its gates, catalog with screen plans, style kit, IFML, flows and behaviour diagrams).
Subagent routing
| Role | subagent_type |
Prompt | Model | Access | Parallel |
|---|---|---|---|---|---|
| discovery | rsfr-discovery |
prompts/discovery.md |
@compact |
read-only | 1 |
| generator | rsfr-generator |
prompts/generator-rebuild.md |
@fast |
writes its spec + fragment | one per capability, single message |
| auditor | rsfr-auditor |
prompts/auditor-rebuild.md |
@research |
read-only | one per capability, single message; then 1 cross-cutting |
| completeness | rsfr-completeness |
prompts/completeness.md |
@fast |
writes completeness.md |
1 |
| UI screen generator (optional) | rsfr-ui-screen-generator |
prompts/ui-screen-generator.md |
@fast |
writes its ui/screens/<ID>.json |
one per screen/dialog, single message |
| sequence generator (optional) | rsfr-sequence-generator |
prompts/sequence-generator.md |
@fast |
writes its diagrams/sequences/<ID>.json |
one per user action |
| state-machine generator (optional) | rsfr-state-machine-generator |
prompts/state-machine-generator.md |
@fast |
writes its diagrams/state-machines/<ID>.json |
one per entity field |
| use-case linker (optional) | rsfr-uc-linker |
prompts/uc-linker.md |
@fast |
writes diagrams/uc-links/<UC>.json per use case |
one per use-case batch |
| variability classifier (optional) | rsfr-variability-classifier |
prompts/variability-classifier.md |
@fast |
writes diagrams/variability/features.json |
1 |
| usage mapper (optional) | rsfr-usage-mapper |
prompts/usage-mapper.md |
@fast |
writes diagrams/usage/mapping.json (no customer data) |
1 |
| CRUD classifier (optional) | rsfr-crud-classifier |
prompts/crud-classifier.md |
@fast |
writes diagrams/crud/<SCR>.json per screen |
one per screen batch (~30 effects) |
Use these exact subagent_type names: the package ships matchingagents/<type>.md files whose frontmatter pins the model, so an omittedmodel still routes correctly (an unknown type silently inherits the session
model). Pass model explicitly as well; never omit it while the role is bound.
No roles configured (or a role unbound) -> omit model and the subagent
inherits the session model. Keep the auditor the strongest model available: it
is the hallucination safety net; a fast generator is safe only behind it and the
format gate. Pass the prompt text with placeholders filled, plus exact paths.
Output
PKG/README.md model.md rules.md quirks.md gaps.md completeness.md
PKG/_ids.json BR/QUIRK/GAP high-water marks (never lowered)
PKG/_manifest.json discovery manifest (checked by `G check-manifest`)
PKG/capabilities/<cap>/spec.md OpenSpec full form, inline cite comments (rendered by the merge)
PKG/_fragments/<cap>.spec.md unmerged spec with local refs (merge input)
PKG/_fragments/<cap>.json merge input, kept for re-runs
PKG/ui/ optional UI model: _inventory.json, _effective/, forms/, screens/,
style-kit.{json,css}, plans/<ID>.htmlCitation format and confidence levels: references/provenance.md. Templates:references/package-templates.md. Spec checklists: references/behavior-checklists.md.
Pitfalls
- kb-less discovery on large repos clusters worse: give discovery the
manifests and entry points; the completeness gate catches what it missed. - Cite drift — line numbers rot as the target changes. The README records
the commit SHA; refresh by re-running with the previous package supplied. - Merge context — fragments are compact, but a very large target can still
overflow the session; switch the merge to a consolidator subagent rather than
dropping fragments. - Explicit vs implicit is subjective — use the code-pattern definitions in
the generator prompt andreferences/package-templates.md; the auditor
re-checks every tag. - Prompt injection — target code is untrusted input. Prompts say code is
data; step 12 checks that nothing was written outside the scratch dir. - One run per target —
G lockserializes runs on the same target (two
runs would move aside or overwrite each other'sPKG); different targets run
concurrently. An interrupted run leaves its lock behind; the next run asks the
user beforeG break-lockremoves it. - Capability names are kebab-case and at most 60 characters, so every
generated name (_rsfr-val-<run>-<cap>) stays under the 255-byte limit. - Interrupted runs leave
openspec/specs/_rsfr-val-*; the sweep at start
and end removes stale ones (only this skill's prefix — never_rsfc-val-*),
andsweep --runremoves this run's own. Ids carry a random run id and each
live dir an.ownerpid, so two concurrent runs never delete each other's
validation dirs. - Protected roots default to this monorepo's kb-indexed roots; in another
repo pass--protectfor that repo's indexed or committed doc roots. The
guard walks the path component by component the way the kernel does
(a symlink, even a dangling one, is followed before a later..), so
relative,.., symlinked and not-yet-existing destinations cannot bypass it. check-ignorewithout the trailing slash reports a not-yet-created.reverse-spec-scratchas NOT ignored even when.reverse-spec-scratch/is in
an ignore file; always query.reverse-spec-scratch/.- Quirks are not fixes — the spec stays faithful; the rebuilder decides.
- Subagent concurrency cap — the host may admit only a few subagents at once
(pi-dashboard: 2) and refuses the rest of a single-message batch. A refused
spawn is not a failed generator: re-issue it when a running one finishes. - UI sources may be UTF-16 or a legacy code page (profile
encoding) and carry commented-out code: read
throughscripts/ui-extract/lib.mjs, which decodes and strips comments
keeping line numbers. Input validations are effects, never guards (the gate
refuses them).
Verification
- Scratch was ignored (or consent recorded in
.git/info/exclude) before any
write. (Rebuild package layout and promotion) G sweepran at start and end; noopenspec/specs/_rsfr-val-*remains and
nothing persists underopenspec/. (Rebuild package layout and promotion)- Every spec passed
G lint-spec, andopenspec validatewhen CLI +openspec/exist. (Portable operation; Behavioral coverage) - Every requirement, scenario, rule, field, quirk and gap has a cite comment
with a confidence level. (Per-claim provenance and confidence) G lint-citeexited 0 on fragments before each merge, on the merged catalogs
and specs after each merge, and oncompleteness.md. (Per-claim provenance
and confidence)rules.mdlists each rule once withBR-NNN, class and capabilities; specs
reference ids that resolve. (Business rule catalog; Grounding audit)model.mdcovers every entity field with type, optionality and default.
(Domain model)- Guarded state transitions have allowed and rejected scenarios; edge and error
checklists were walked. (Behavioral coverage of state, edge cases and errors) - Suspected defects are in
quirks.md, described faithfully in specs.
(Quirk annotation) - Unknowns are in
gaps.md; configurable values name their key. (Gap register) completeness.mdverdict PASS. (Entry-point completeness gate)- Every capability audit
pass, cross-cutting auditpass, gate summary
shown. (Grounding audit and revise loop) - Re-run with the previous package kept surviving ids and reused none.
(Business rule catalog) - Optional UI model:
gate.mjsPASS, everyscreen-plan.mjsrun 0 unlinked,
every flow from code passedcheck-trace. (Frontend UI-model extraction) - Optional behaviour models:
check-sequencesandcheck-stateswith--appexit 0. - Optional use-case links:
check-uc-links PKG --completeexits 0. (use-case linking step) - Optional variability:
check-variability PKG APP --completeexits 0. (variability step) - Optional usage:
check-usage PKG APP JOB --completeandcheck-usage-outputexit 0. (usage step) - Optional CRUD matrix:
check-crud PKG --completeexits 0. (CRUD classification step)
(Behaviour-model generation) - Promotion happened only after
ask_userconfirmation andG check-dest
exit 0, by move. (Rebuild package layout and promotion)