| name | debundle_architect |
| description | Audit a debundle spec's named modules for idiomatic JavaScript structure, infer project conventions from source behavior, and maintain current-state architecture notes and reorganization recommendations. Use for structural review, convention discovery, module-boundary cleanup, and reorg planning in any debundle target. |
Debundle Architect
Use this role for structural review of an in-progress debundle spec. The
architect owns source-tree taxonomy health for the target: it should infer,
maintain, and course-correct the conventions that make the emitted tree read
like a real application rather than an accumulation of peel decisions. Current
architecture notes are about the app architecture inferred from decompiled
source behavior, not a ratification of the current split, names, or paths. The
architect does not author spec edits; it turns evidence into current-state
notes and concrete reorganization tasks for workers.
Read bundled references as needed:
references/workflow.md for the full multi-agent workflow
references/module_shape.md for shared seam, layering, and convention
induction guidance
Inputs
The project adapter must provide:
<modules-dir>: active modules/**/*.yaml tree
<emitted-js-root>: generated readable JS tree
<graph>: current owner_graph.json, when available
<report-tree>: emitted reports/tree/ report tree, when
available
<conventions-docs>: project-local docs such as AGENTS.md,
taxonomy notes, or architecture guides
<architecture-notes> and <module-reorg> output paths
Use debundle_plan_work for graph/source inspection. Route pure symbol
naming work to debundle_mint_names.
Evidence Model
Treat current module assignments, paths, and names as useful but fallible
evidence. They may encode earlier architecture insight, but they may also be
wrong, overly literal, stale, or shaped by what was easy to peel at the time.
Use pulled-out modules primarily because they make the JavaScript easier to
read and preserve prior architectural work. Do not assume that a module boundary
is real just because it exists, or that a name is correct just because it was
assigned. Re-ground important conclusions in implementation behavior, graph
edges, call sites, source proximity, ownership patterns, and repeated internal
structure.
Do not infer the app organization from file paths or assigned names alone.
Names and directories can be decorative or wrong. A tree can be arranged to
look like a React app while the source bodies and dependency graph describe
something else. In that case, report the mismatch and update the architecture
notes toward the source/graph reality.
Job
Audit the named active modules and emitted JS for structure that does not
look like a natural JavaScript codebase:
- modules that are too tiny to represent real seams
- tiny modules that are only imported by one meaningful consumer and look like
implementation details of that consumer
- modules named after one callable or implementation step instead of a
coherent architecture concept
- modules that glue unrelated subsystems together
- helpers/config/constants separated from their only meaningful owner
- directory levels that exist only to contain one item, unless they are a
justified namespace or stable public boundary
- directories with too many unrelated siblings, indicating a missing
subdivision or an over-broad bucket
- layer-direction violations under the project's architecture
- inconsistent naming/path conventions inside a directory or subsystem
- duplicated concept families spread across arbitrary homes
- parallel top-level or mid-level axes for the same concept family
- repeated
foo/foo.js or wrapper-shaped leaves that came from mechanical
naming instead of a useful namespace
- source bodies whose behavior contradicts their current path or name
- extracted modules not reachable from the generated graph
Prefer project-local conventions over generic instincts. When conventions
are missing or weak, infer them from repeated evidence.
Selector health is part of structural review. Run
debundle spec selector-debt --modules <modules-dir> --group-module-depth N to
surface grouped fragile name-only minified selectors, source_match bodies
copied verbatim across modules (a duplication smell — route a worker to
collapse them to one contextual selector), and — with
--against <prior-spec-modules> — selectors whose minified binding drifted
across a re-pin (candidates to re-express as structural AST-shaped selectors
with holes/placeholders for volatile subtrees). The architect surfaces these as
findings and recommends broad coherent buckets; workers use
debundle spec synthesize-selectors or focused YAML edits to apply them. If the
same selector shape repeatedly needs verbose or unsupported syntax, route it as
a Ducktape tooling gap instead of normal module-architecture work.
Tree Shape Audit
Every architect pass should look at both the spec tree and the emitted JS tree.
Do not only review individual module boundaries; review the directory structure
as a system.
Every architect pass should read the complete active module-name list. Do not
substitute a canned prefix list for that review. Read the path segments, leaf
names, export names, and importer neighborhoods, then decide whether each name
describes a durable concept or only the operation performed by one helper
function.
Actively hunt small-LOC modules to fold — tiny modules are a smell. A debundle
over-splits when the chunker emits a separate module for what a developer would
have written inline in a larger file. Judge by LINES OF CODE, not member count:
a one-binding module that is a 500-line React component is idiomatic and must be
left alone; the smell is small-LOC standalone files (a 1-3 line accessor,
predicate, constant, or wrapper in its own module). The suspicious shape is such
a small-LOC helper/config/style/function whose only real caller is an adjacent
component, command, service, parser, or state module — exclude non-semantic
re-export catalogs / bundle barrels when counting consumers. Recommend folding
it into that single consumer, or into a sibling that was clearly the same
original source file (use source_location adjacency / shared CSS-module class
prefixes as evidence), unless layer ownership argues against it. Do NOT fold a
small-LOC module that is a widely-consumed shared primitive (a shared constant,
a React context, a public predicate), a real public-API/service/class boundary,
or anything whose fold would cross a layer boundary or break the realizability
gate. See references/module_shape.md for the full rule.
Treat one-callable modules as a recurring antipattern when the module name is
just the callable name and the callable is not itself the architecture boundary.
Keep a one-callable module standalone when it is a stable public API, a shared
domain primitive, or has multiple real consumers. Otherwise crawl one layer
higher to find the component/service/state/parser that owns the behavior, and
propose a merge or co-peel with that owner.
Cross-check directory findings against graph and source behavior. A singleton
directory is not bad only because it has one child; it is bad when the source
body and graph do not show a namespace/API/family boundary. A broad directory
is not bad only because it has many children; it is bad when source behavior
and graph neighborhoods show multiple concepts sharing one bucket.
When <report-tree> includes directory reports, consult them before proposing
hierarchy changes. Use their incoming/outgoing symbol, file, and edge-kind
attribution to find leaky directory boundaries, over-broad buckets, misplaced
subtrees, and singleton wrappers that do not carry their own API boundary.
Directory reports are quantitative evidence; still read the source bodies and
owner graph behind the highest-attribution symbols before writing
recommendations.
Check for:
- singleton directories: directories with exactly one child or exactly one
source file
- overloaded directories: directories whose sibling count is high enough that
a reader cannot infer the grouping rule
- inconsistent depth: similar modules represented at different depths without
a reason
- duplicated axes: the same product concept split across roots such as
domain, feature, app, shared, or project-specific equivalents
- generic buckets: paths such as
utils, helpers, core, common,
runtime, or misc that are not backed by a documented local convention
- mechanical wrappers: redundant directory/file pairs or wrapper directories
that merely repeat a module name
Singleton directories are not automatically wrong. They can be correct when
they are a stable namespace, a public API boundary, a route/package boundary,
or the first landed member of an evident family. The architect should record
that reason. Without a reason, propose flattening or merging the path.
Overloaded directories are also not automatically wrong. They can be correct
for a small, coherent subsystem with a flat public surface. Without a clear
cohesion rule, propose a subdivision convention.
When the audit finds repeated bad shapes, update <conventions-docs> or
<architecture-notes> with the convention that future peels should follow.
An architect pass that only proposes one-off moves, while leaving the
underlying path convention ambiguous, is incomplete.
Audit Snippets
Use these as starting points when the project adapter provides
<emitted-js-root> and <graph>. They are not a replacement for reading the
source bodies behind suspicious paths.
find "$EMITTED_JS_ROOT" -type f |
sed "s#^$EMITTED_JS_ROOT/##" |
awk -F/ '{print $1}' |
sort | uniq -c | sort -nr
find "$EMITTED_JS_ROOT" -type d |
while read -r d; do
n=$(find "$d" -mindepth 1 -maxdepth 1 | wc -l)
[ "$n" -eq 1 ] && printf '%s\n' "$d"
done |
sed "s#^$EMITTED_JS_ROOT/##"
find "$EMITTED_JS_ROOT" -type f |
sed "s#^$EMITTED_JS_ROOT/##" |
awk -F/ 'NF >= 2 {
leaf=$NF; sub(/\.js$/, "", leaf); parent=$(NF-1);
if (leaf == parent) print $0
}'
find "$EMITTED_JS_ROOT" -type f -name styles.js | wc -l
find "$EMITTED_JS_ROOT" -type f -name |
sed |
find - f -name |
sed |
awk -F/ |
-nr
find - f -name |
-r f;
lines=$( -l < )
[ -le 25 ] &&
|
-n | -100
find -name index.json -print0 |
xargs -0 -n1 jq -r |
-k2,2nr | -50
jq -r | -50
jq -r | -80
jq -r | -100
Convention Induction
Record conventions as scoped hypotheses before treating them as rules.
- Gather evidence from graph edges, source proximity, naming families,
import direction, call sites, directory fan-in/fan-out, sibling naming, and
existing well-shaped modules.
- Write a hypothesis with scope, evidence, counterexamples, and open
questions in
<architecture-notes>.
- Promote to
<module-reorg> only when the change is concrete enough for
a worker to apply without re-deciding the design.
- Promote durable conventions into
<conventions-docs> once they affect
multiple future edits.
- Demote or delete hypotheses when later evidence contradicts them.
Architecture notes and reorg recommendations are current-state documents,
not append-only logs. "Current-state" means the best current inference about
the decompiled app's internal architecture; it does not mean "whatever the
current spec happens to call things." Rewrite stale sections in place; git is
the history.
Structural Conformance Metrics
Prose conventions decay: an architect writes "shared/ is framework-agnostic"
or "panel logic should live under app/panel," workers drift from it, and
nobody notices until the next manual audit. Whenever you draft a desired
layering or cross-module dependency relationship — the structure you believe
should exist in the debundled app — also encode it as a rerunnable metric
script that measures how well the current output meets it, and run those
scripts on every architect pass.
This serves two distinct purposes, and you should label which one each metric
is for:
- RULE — a boundary you are asserting should hold. Any violation is a
finding to route to a worker. Example: "no module under
shared/ may import
from domains/, features/, or app/."
- OBSERVATORY — a hypothesis you are not yet asserting. You suspect a
structure but want to watch what naturally happens and track how it evolves.
Record the current value plus a baseline; report drift, do not fail.
Example: "what fraction of edges point 'up' the layer stack, and is it
creeping?"
An observatory that stabilizes can be promoted to a rule; a rule that reality
keeps violating for good reasons is a signal that the assumption was wrong —
update the rule (or conclude the debundle should be restructured), don't just
keep failing it. This is the same hypothesis→rule lifecycle as Convention
Induction, made executable.
How to build them
- Consume the debundler's JSON side outputs, not the emitted JS text. The
owner graph (
owner_graph.json) carries, per module, its target_file and
the typed module-to-module edges (module_graph.edges with edge_kinds and
constrains_init_order). That is a stable, rerunnable contract; re-parsing
import statements from the JS is brittle and misses lazy/eager/rebind edge
semantics the graph already classifies. Use describe, cluster, scc,
coverage, and graph-summary outputs the same way when a metric needs
them.
- One named expectation per check, each emitting: its current measured
value, PASS/FAIL (rules) or drift-vs-baseline (observatories), and a bounded
list of concrete violating edges/modules so a worker can act without
re-deriving them.
- Make rule failures exit nonzero so the script doubles as a CI/gate hook;
observatories never fail the run.
- Git-track the baselines (a small JSON next to the script) so drift shows
up in review diffs, and update them deliberately with an explicit flag after
an intentional structural shift.
- Keep the script in the project's tree (adapter-owned), not in the generic
skill — the method is generic, the expectations are project-specific.
Example expectations
Concrete shapes (drawn from a React/MobX target; adapt the layers and vendor
names to your project). Each is one named check emitting value + verdict +
bounded violation list.
- RULE, intra-app graph — a low layer must not depend on higher layers.
"
shared/ is framework-agnostic primitives": flag every module_graph edge
whose source is under shared/ and whose destination is under domains/,
features/, or app/. Violations are mis-homed modules (e.g.
shared/ui/Button -> features/outliner) or values that should move down.
- RULE, vendor import — a layer must not import a framework. "React-free
domains/": React arrives via a vendor swap, so it is invisible in the
module graph — scan the emitted JS for import ... from ".../react*.js" under
domains/. Hits are components/hooks mis-filed in the logic layer (e.g. a
module that defines a JSX component or calls useMemo). Exempt any
documented-immovable fused module so the rule measures only what is fixable.
- OBSERVATORY, tracked ratio — watch a hypothesis without asserting it.
"fraction of edges pointing up the layer stack" or "share of
X -> app edges
that hit the global-state/registry subtrees": record the value + a baseline
and report drift. A rising registry share is evidence that app/ is
overloaded and should be split — a hypothesis you are accumulating evidence
for, not yet enforcing.
- OBSERVATORY paired with a rule — the grey-area sibling. When a rule
forbids the clear-cut case (raw React), track the ambiguous neighbor (e.g. a
framework-react/observer reactivity wrapper that sometimes legitimately
sits with a store) as an observatory so it does not silently become the new
leak path once the rule goes green.
On every architect pass
Run the conformance script before writing recommendations. Treat the output as
evidence:
- A newly-RED rule is either a real regression (route a fix) or an assumption
reality has outgrown (update the rule and the architecture notes, and say
why).
- A drifting observatory tells you where the structure is moving; decide
whether to intervene, promote it to a rule, or revise the hypothesis.
- When you add or sharpen a desired relationship in
<architecture-notes>, add
or update the matching metric in the same pass. An architecture note that
asserts a boundary without a check to measure it is incomplete.
Precedence Model
Co-consumption is useful evidence, but architecture ownership is stronger.
Do not co-locate an artifact with its only consumer if that would move domain,
policy, persistence, integration, or infrastructure logic into a presentation
or feature layer incorrectly.
Examples of inferable conventions, not built-in policy:
- In React-like code, component-local presentation helpers or styling
artifacts may belong with their sole component consumer.
- Reducers, action constants, and selectors may form one state-management
module when they share a public contract.
- A parser may own grammar tables and token predicates when they are internal
implementation details.
- A command handler may own metadata only when the metadata has no separate
registry or policy role.
Always state the exception boundary. For example, a view component's sole
consumer relationship does not make authorization policy presentation-owned.
Outputs
<architecture-notes> contains evolving understanding:
- observations
- tentative conventions
- source-tree taxonomy rules and exceptions
- suspected layer boundaries
- names of subsystems that need more evidence
- questions for intake or lane workers
- for each asserted boundary or tracked hypothesis, a pointer to the
rerunnable metric (rule or observatory) that measures it — see Structural
Conformance Metrics. A layering/dependency claim in the notes should have a
matching check.
<module-reorg> contains firm worker-ready recommendations:
## <one-line change>
**Files involved**:
- `<modules-dir>/path/to/module.yaml`
**Evidence**:
- ...
- directory-shape evidence when relevant, such as singleton paths,
overloaded sibling counts, or duplicated concept roots
**Proposed change**:
- ...
**Confidence**: high | medium | low
**Blocked by**:
- ...
**Status**: proposed | dispatched | rejected
Delete landed recommendations on the next audit pass. Keep rejected entries
briefly only when they prevent re-proposing the same mistake.
Boundaries
- Do not author module YAML edits.
- Do not run gates or regenerate emitted JS.
- Do not read large minified residual bodies; ask intake to ground them.
- Use the owner graph only as a constraint signal. Workers run the gate.
- Do not bake framework examples into project policy; promote discovered
project conventions into project docs.