Skip to content

Split arm.py (4140 lines, 8 verbs) into one module per verb — it is now a structural source of merge conflicts #50

Description

@OriNachum

The state

arm101/cli/_commands/arm.py is 4140 lines — 8 verbs and 72 functions in one module:

overview, read, flex, explore, profile, rezero, setup, limits

Each verb brings its own register(), its argument surface, its handler, its dry-run/plan renderer, and its result formatter. They share a file and almost nothing else.

Why it hurts, concretely

  • Merge collisions are structural, not accidental. During the Every factory-offset joint is clamped exactly ON its encoder seam — #35 may affect all six #43 build, two parallel tasks (t10 = the commit path, t11 = the bounds diff) had to be serialised by hand purely because both would have edited this file. That is the dependency graph losing to a filesystem layout. It has bitten before: PR feat: encoder linearity — evict the seam so a linear tick axis is finally true (#35) #40's merge conflict was two branches adding a verb to this same file, and git's conflict regions did not align on function boundaries — the naive union spliced one verb's argument list into another verb's call.
  • The catalog-lockstep rule (CLAUDE.md: a new verb must update explain/catalog.py, overview.py's _VERBS, and learn.py together) is enforced by teken cli doctor --strict, but the fourth place — this file's help text — has repeatedly drifted. The false "only elbow_flex wraps inside its travel" claim survived in arm.py's --help after it had been retracted in arm_spec.

Suggested shape (not prescriptive)

One module per verb under arm101/cli/_commands/arm/, each exporting register(sub) and its handler; a thin arm/__init__.py that builds the noun group and calls each in turn. That is already the pattern _build_parser() uses for the top-level verbs — this just applies it one level down.

Two things must survive the split:

  1. parser_class=type(p) on the noun's subparsers, so child parse errors keep routing through the structured error contract instead of argparse's exit 2.
  2. The error/output contractsCliError never sys.exit, results to stdout and diagnostics to stderr never mixed, --json on every verb. These are enforced by tests and by teken cli doctor --strict; run both.

Prefer rendering help text from arm_spec

The drift above happened because operator-facing prose restated a table instead of rendering from it. Several surfaces now render from arm_spec at import (_ARM_REZERO in the catalog, REZERO_UNKNOWN_HEADLINE). The split is the moment to finish that: help text that quotes a measurement should be an f-string over the source of truth, not a copy of it.

Not urgent, and deliberately not now

Filed rather than done because it surfaced mid-hardware-run with PR #48 green. Sibling to the bus.py refactor.

Related: #43

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions