Skip to content

Multi-tool intake: --tool accepts a list across setup, tools adapt, pilot#127

Merged
JRS1986 merged 16 commits into
mainfrom
feat/multi-tool-intake
May 27, 2026
Merged

Multi-tool intake: --tool accepts a list across setup, tools adapt, pilot#127
JRS1986 merged 16 commits into
mainfrom
feat/multi-tool-intake

Conversation

@JRS1986
Copy link
Copy Markdown
Owner

@JRS1986 JRS1986 commented May 27, 2026

Summary

Implements the Multi-Tool Intake spec. --tool now accepts a list everywhere it's accepted, so a project can configure (and a user can pilot) two coding tools — e.g., Codex + Claude Code — in a single setup run.

# All of these work in one pass:
coding-scaffold setup run --tool codex --tool claude-code
coding-scaffold setup run --tool codex,claude-code
coding-scaffold pilot --tool codex,claude-code
coding-scaffold tools adapt --tool codex,claude-code

The pilot's multi-tool recipe prints one shared setup step plus a per-tool agent line (codex # /first-session, ... / claude # /first-session, ...). The single-tool format is preserved bit-for-bit so existing golden tests pass unchanged.

Spec + plan

  • Spec (committed in this PR)
  • Plan (committed in this PR; executed via subagent-driven development with two-stage review per task)

Breaking changes (in 0.6.0)

  • routing.json, project.json, and pilot JSON output now carry tools (a list) only. The singular tool key is gone.
  • The Python IntakeAnswers.tool attribute is removed; use IntakeAnswers.tools (a list) or the back-compat .agent property.

Legacy project.json files written by 0.5.x (with tool instead of tools) are back-filled when setup update reads them, so existing projects upgrade cleanly. The back-fill helper is removed in 0.7.0 alongside --tool both.

Deprecated

  • --tool both warns now (with one-fire stderr message naming the replacement and the 0.7.0 removal release); will be removed in 0.7.0.

Test plan

  • uv run pytest -q635 tests pass (590 baseline + 45 new for this feature).
  • uv run ruff check src/ tests/ — clean.
  • cd docs && npx rspress build — exits 0, no dead-link warnings.
  • tests/test_multi_tool.py covers end-to-end: setup run writes both adapter sets, tools adapt is idempotent, pilot JSON has the new shape, --tool both warns + works, --tool manual --tool codex rejects with the three-line error, legacy project.json migrates via setup update, --install-tools loops over every selected tool.
  • tests/test_normalize_tools.py covers the helper unit-level: every input shape, deduplication, deprecation latch, typo rejection, drift-guard between CODING_TOOLS (cli.py) and VALID_TOOLS (intake.py).

Execution notes

  • Spec + plan are committed; spec was reviewed and approved before plan was written.
  • Plan executed in 7 bundles via superpowers:subagent-driven-development; each bundle had a fresh implementer subagent plus separate spec-compliance and code-quality reviewers (sonnet for most, opus for the final whole-branch review).
  • Two code-review findings were caught mid-flight and fixed in-branch:
    1. Pilot's single-tool text format silently changed installed: True/Falseinstalled: yes/no (Bundle 6 review caught it; reverted + golden test strengthened to prevent regression).
    2. format_pilot_text misrendered persona + multi-tool by slicing persona override commands into "shared setup + agent lines" (Bundle 6 code review caught it; gated on persona == DEFAULT_PERSONA AND multi_tool + regression test).
  • One final-review finding fixed in this PR: setup run --install-tools only installed the primary tool in a multi-tool setup — directly contradicting the pilot recipe. Now loops over answers.tools. Regression test included.

Commits

12 focused commits with TDD-shaped boundaries. Browse with git log 6c1a013..7d0b7a4 --oneline.

🤖 Generated with Claude Code

JRS1986 added 16 commits May 27, 2026 07:13
… + Python

Per user feedback: small user base, one breaking change is cheaper than two
parallel shapes. `IntakeAnswers.tools` is the only canonical Python field;
routing.json / project.json / pilot JSON emit `tools` only. A small
`_normalize_persisted_intake` helper back-fills legacy reads for the 0.6.0
release; removed in 0.7.0 with `--tool both`. Added an explicit doc-audit
step that must re-run before merge.
…tool_adapter

Plan Tasks 2+3+5 bundled. IntakeAnswers.tool (singular) is removed; tools (list)
is canonical. _normalize_persisted_intake back-fills legacy project.json reads.
write_tool_adapter accepts str|list via normalize_tools (so this bundle keeps the
test suite green). Updates writers, updater, _load_project_intake, and every
IntakeAnswers(tool=...) test fixture.
- Move `_normalize_persisted_intake` from module-level import to inline within
  `_load_project_intake`, signalling its temporary status (sunset in 0.7.0).
- Absorb the legacy `agent` alias fallback into `_normalize_persisted_intake`
  so callers don't need their own back-fill branch. Adds a covering test.
- Correct the `agent` property docstring: no production call site reads it
  today; it's retained as a back-compat safety net for downstream consumers.
- Drop the redundant `if raw_tool_arg else list(DEFAULT_TOOLS)` ternary in
  `_cmd_init_or_wizard` — `normalize_tools(None)` already defaults correctly.
- Drop unused `tmp_path` fixture and `pathlib.Path` import in test_intake.py.
Adds RoutingPlan.tools mirroring IntakeAnswers.tools so consumers of
routing.json can answer 'which tools is this project configured for?'
without also reading project.json. Spec intentionally omits a singular
'tool' key — multi-tool projects carry all entries.
Post-parse normalizer routes args.tool (list) through normalize_tools and
populates args.tools (canonical deduped list). manual+real-tool rejected via
fail_with three-line shape, caught in main() and returned as rc=1.
Choices= dropped from widened surfaces to allow comma-separated tokens;
normalize_tools handles per-token validation. Pilot subparser stays
single-valued; Bundle 6 widens it.
argparse `choices=` was removed from widened surfaces so comma-separated
values parse correctly. This recovers the same typo-rejection at a slightly
later layer with a clearer recovery message naming the valid choices.

Adds an invariant test that fails if CODING_TOOLS (cli.py) and VALID_TOOLS
(intake.py) drift — we can't share the constant directly without a circular
import.
Helper never returns 1 — it raises SystemExit(1) via fail_with. Signature
declared int | None but the int branch was dead. Per code-review note.
The implementer's Bundle 6 changed 'installed: True/False' to 'installed:
yes/no', violating the spec's bit-for-bit single-tool format promise. Reverts
the rendering and strengthens the golden test so this can't regress again.
…hten test

Important: format_pilot_text branched on len(tools) > 1 only — when a non-
beginner persona produced override commands AND the user passed multiple
tools, the 3 persona commands got sliced into 'shared setup + agent lines',
labeling scaffold commands as 'start a session with whichever tool'. Now
gated on persona == DEFAULT_PERSONA AND multi-tool.

Minor:
- Drop the unreachable isinstance(bool) assert; coerce explicitly instead.
- Tighten test_pilot_accepts_multi_tool_list to count /first-session agent
  steps (matches plan acceptance criterion) instead of a weaker substring.
- Add regression test for persona+multi-tool layout.
…summary

Important (from final whole-branch review):
- `setup run --tool a --tool b --install-tools` previously only installed
  tool `a` because `_cmd_init_or_wizard` passed `answers.tools[0]` to
  `_maybe_install_tools`. The pilot recipe explicitly tells multi-tool users
  to use `--install-tools` (spec §7.1), so this broke the documented promise.
  Loop over `answers.tools` instead.

Minor:
- Surface the multi-tool count in the post-setup print summary per spec §6.3
  ('Wrote N tool adapter file(s) across M tool(s): codex, claude-code').

Regression test in test_multi_tool.py monkeypatches install_missing_tools
and asserts both selected tools are offered for install.
@JRS1986 JRS1986 merged commit 405d7c5 into main May 27, 2026
3 checks passed
@JRS1986 JRS1986 deleted the feat/multi-tool-intake branch May 27, 2026 10:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant