Skip to content

Inverse recursion: nest_all_under('dz') and non-library-consumer parent embedding #51

@djdarcy

Description

@djdarcy

Summary

Design pass + implementation for "inverse recursion": cases where dazzlecmd is nested under another tool's CLI surface, rather than the (currently supported) downward composition where dazzlecmd or a dazzlecmd-lib aggregator imports kits.

This addresses two distinct scopes:

  1. Library-consumer parents (immediate): MetaCommandRegistry.nest_all_under('dz') — let downstream aggregators built on dazzlecmd-lib nest all library meta-commands under a sub-parser group. Originally proposed in Root command namespace flexibility: nest, rename, selective exposure #47 as the primary feature request.
  2. Non-library-consumer parents (design pass): mytool dz some-cmd where the parent tool is NOT built on dazzlecmd-lib. Currently architecturally unaddressed — the framework assumes downward-only composition. Solving requires either (a) AggregatorEngine.run() accepting a parent argparse subparser to attach to, or (b) the parent shelling out to dz via subprocess.

Why

The user's stated architectural vision is for dazzlecmd to be "completely recursive" — dz itself is just one configured instance of AggregatorEngine. The downward case is fully validated (wtf-windows v0.1.4-alpha). The inverse case (parents subordinating dazzlecmd) is undesigned and unimplemented.

Scope 1 — MetaCommandRegistry.nest_all_under(group_name)

Library API addition. A consumer constructs an aggregator and chooses to nest all library meta-commands (list, info, kit, tree, mode, setup, add, new, etc.) under a single sub-parser group:

engine = AggregatorEngine(name="my-tool", command="mt", ...)
engine.meta_commands.nest_all_under("dz")
# Now: `mt dz list` instead of `mt list`
# Aggregator's own commands stay at top level

Acceptance criteria (Scope 1):

  • MetaCommandRegistry.nest_all_under(group_name) API works
  • All currently-registered meta-commands appear under the named sub-parser group
  • Top-level help (mt --help) shows the group as a single command with its own help
  • mt dz --help shows all nested meta-commands
  • mt list falls back to aggregator-level command if registered (precedence: top-level wins)
  • Companion MetaCommandRegistry.rename(old, new) for individual command renaming
  • Companion MetaCommandRegistry.nest_under(group, members=[...]) for partial nesting

Scope 2 — Non-library-consumer parents (design only)

Design problem: a third-party tool (Bash script, Go binary, Rust CLI, anything not Python) wants to expose dazzlecmd as a sub-command tree. Today the only path is subprocess shell-out (subprocess.run(["dz", cmd, ...])), which loses error context, can't share state, and is brittle.

Design questions (to resolve in /dev-workflow-process):

  • Is this in scope at all, or is "build a thin shell wrapper that calls dz" sufficient?
  • If in scope: does AggregatorEngine expose a register_with_parser(parent_subparsers) API that attaches the engine's commands to an external parser?
  • How does is_root=False interact? Currently it just suppresses meta-commands; this scope might need a third mode.
  • What's the import-time concern? A non-Python parent can't import dazzlecmd_lib at all.

Acceptance criteria (Scope 2):

  • Dev-workflow-process design doc covering the design space
  • Decision: implement, or formally close as "subprocess shell-out is the supported path"
  • If implementing: API design + tests + at least one example (Python parent embedding dazzlecmd as sub-namespace)

Phasing

Related issues

Analysis

See 2026-04-29__07-41-11__claude-plan__0-7-x-closeout-ultraplan.md (X-6/X-7/X-8/X-9) for placement in the closeout sequence.

Metadata

Metadata

Assignees

No one assigned

    Labels

    architectureStructural and design decisionsenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions