feat(ultraplan-local): M0 — profile foundation, no behaviour change

Introduces a profile-loader infrastructure for runtime-instantiable
ultraplan variants (depth × domain × goal axes). M0 ships only the
`default` profile, which mirrors the current hardcoded Phase 5/9 agent
set — so existing flows are unaffected.

What lands:
- profiles/default.yaml — schema v1, lists current 8 exploration agents
  + 2 review agents, captures today's adversarial regime
- scripts/profile-loader.mjs — null-deps Node loader with limited-subset
  YAML parser, listProfiles(), loadProfile(), validateProfile() that
  cross-checks every referenced agent exists in agents/
- scripts/profile-loader.test.mjs — 26 node:test cases (parser, validation,
  loader, integration with built-in default.yaml)
- commands/ultraplan-local.md — Phase 1 gains a "Resolve the profile"
  step (--profile flag → brief.recommended_profile → default fallback)
  and prints profile + source in the mode report. Phase 5/9 unchanged.
- README.md, CLAUDE.md, marketplace README — documentation of the M0
  foundation, the universal-brief design principle, and the M1/M2/M3
  milestones to come.

M1 (next) wires profile recommendation into ultrabrief Phase 4. M2
ships the additional built-in profiles (quick, bugfix, feature, refactor,
security-deep, research-heavy) and replaces the hardcoded Phase 5 agent
table with profile-driven selection. M3 adds user-extensible profiles.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-04-30 14:14:20 +02:00
commit 0b28f008ae
7 changed files with 989 additions and 1 deletions

View file

@ -91,7 +91,12 @@ Parse `$ARGUMENTS` for mode flags. Order of precedence:
7. **`--quick`** — set **mode = quick**. Skip agent swarm; use lightweight
Glob/Grep scan and go directly to planning + adversarial review.
8. If neither `--brief` nor `--project` is present after flag parsing,
8. **`--profile <name>`** — explicit profile selection. Overrides any
`recommended_profile` from the brief frontmatter. Set
**profile_override = {name}**. Validation of the profile happens in the
profile-resolution step below; an unknown name aborts with a clear error.
9. If neither `--brief` nor `--project` is present after flag parsing,
output usage and stop:
```
@ -100,6 +105,7 @@ Usage: /ultraplan-local --brief <path-to-brief.md>
/ultraplan-local --brief <path> --research <research-brief.md>
/ultraplan-local --project <dir> --fg
/ultraplan-local --project <dir> --quick
/ultraplan-local --project <dir> --profile <name>
/ultraplan-local --export <pr|issue|markdown|headless> <plan-path>
/ultraplan-local --decompose <plan-path>
@ -111,6 +117,8 @@ Modes:
--research Add up to 3 extra research briefs as planning context
--fg No-op alias (foreground is the only mode as of v2.4.0)
--quick Skip exploration agent swarm; plan directly
--profile Explicit profile (overrides brief's recommended_profile).
List available with: node profile-loader.mjs list
--export Generate shareable output from an existing plan (no new planning)
--decompose Split an existing plan into self-contained headless sessions
@ -119,6 +127,7 @@ Examples:
/ultraplan-local --brief .claude/projects/2026-04-18-jwt-auth/brief.md
/ultraplan-local --project .claude/projects/2026-04-18-jwt-auth --research extra.md
/ultraplan-local --project .claude/projects/2026-04-18-jwt-auth --fg
/ultraplan-local --project .claude/projects/2026-04-18-jwt-auth --profile default
/ultraplan-local --export pr .claude/plans/ultraplan-2026-04-06-rate-limiting.md
/ultraplan-local --decompose .claude/plans/ultraplan-2026-04-06-rate-limiting.md
@ -144,12 +153,52 @@ If `research_status == pending` and `research_topics > 0`:
- If cancel: print the research invocations from the brief's "How to continue"
section and stop.
### Resolve the profile
After reading the brief, determine which profile this plan run should use.
Profiles describe which exploration/review agents to spawn, which
adversarial regime to apply, and which catalog filter the architect should
use. The full schema lives in `${CLAUDE_PLUGIN_ROOT}/profiles/`; the loader
is `${CLAUDE_PLUGIN_ROOT}/scripts/profile-loader.mjs`.
Resolution order (highest precedence first):
1. **`--profile <name>` flag** — use it. If the profile cannot be loaded,
abort with a clear error listing available profiles:
```
Error: profile '{name}' not found.
Available: {comma-separated list from `profile-loader.mjs list`}.
Use --profile=default to fall back.
```
2. **Brief frontmatter `recommended_profile`** — if present, use it.
3. **Otherwise** — use `default`.
If the resolved profile name is `default`, Phase 5 keeps its existing
size/conditional logic (convention-scanner only for medium+, research-scout
only for unfamiliar tech). For non-default profiles (M2 onward), Phase 5
will run exactly the agents listed in `agents.exploration`. **In M0 only
`default` exists**, so resolution effectively always lands on `default` and
no behaviour changes.
Load the resolved profile:
```
node ${CLAUDE_PLUGIN_ROOT}/scripts/profile-loader.mjs load {profile_name}
```
Capture the JSON output into **profile** state. If the loader exits non-zero,
abort with the loader's error text.
Set **profile_source** to one of `flag | brief | default-fallback` for the
mode report below.
Report the detected mode:
```
Mode: {foreground | quick | export | decompose}
Brief: {brief_path}
Project: {project_dir or "-"}
Research: {N local briefs, M extra via --research}
Profile: {profile.name} (source: {profile_source})
```
## Phase 1.5 — Export (runs only when mode = export)