Skip to content

feat(loop): hew loop run --scope={ready|epics} flag (hew-b3yl)#58

Merged
droidnoob merged 7 commits into
mainfrom
feat/loop-scope-flag
May 30, 2026
Merged

feat(loop): hew loop run --scope={ready|epics} flag (hew-b3yl)#58
droidnoob merged 7 commits into
mainfrom
feat/loop-scope-flag

Conversation

@droidnoob
Copy link
Copy Markdown
Owner

Closes the hew-b3yl epic — hew loop run --scope={ready|epics} flag.

Lets the operator (or a calling agent) tell hew loop run which set of tasks counts as the queue for this run. Agents calling agents MUST pass --scope explicitly; interactive humans get a picker.

v1 scopes

  • --scope=ready — current behavior. Every bd ready task is fair game.
  • --scope=epics --epics=<csv> — restricted to children of selected epics (transitive descendants, including the epic itself).

Resolution policy

argv > interactive picker > non-interactive error.

invocation non-interactive interactive TTY
--scope=ready runs runs
--scope=epics --epics=hew-X,hew-Y runs runs
--scope=epics (no --epics) MissingFlag { flag: "epics" } multi-select picker (id + title, sorted by priority)
(no --scope) MissingFlag { flag: "scope" } single-select (ready/epics) → chains into multi-select on epics
--scope=ready --epics=X rejected as contradiction rejected as contradiction

What lands

6 atomic commits, one per task in the decomposed graph:

commit task what
2fe599b hew-55ne hew_core::scope module — Scope enum + serde (#[serde(tag = "kind")]) + resolve_descendants against BdClient
e94c9b6 hew-7ind dispatcher threads Scope; dispatch_tick recomputes descendant set per tick so mid-run epic-child additions get picked up
35c905c hew-6nxs RunLog.scope: Option<Scope> with #[serde(default, skip_serializing_if)] for backward-compat parsing of pre-scope run.json fixtures
cae3827 hew-ry5r RunConfig.scope (default Ready); threaded into Dispatcher::new
7d8d988 hew-xhhw CLI: --scope (clap enum), --epics (CSV), --epic (singular alias); resolve_scope() implements the resolution policy with inquire pickers
2ce2ca7 hew-cjhj loop_summary renders scope: line; docs/LOOP.md gains a ## Scope section; CHANGELOG.md entry

Summary surface

scope:     epics [hew-6az, hew-1tq]
scope:     ready
scope:     ready (legacy)   # for pre-scope run.json

Smoke (hew-mu5j — closed)

Full surface smoke report at /tmp/scope-smoke-report.md. Highlights:

  • Per-child test selectors run individually — scope::* (11), dispatcher::dispatch_tick_* (4), loop_log::run_log_* (7), runner::run_config* (1), loop_scope_e2e (7), loop_summary::summary_renders_scope/legacy/line (4).
  • All four contract assertions for --scope resolution covered by hew/tests/loop_scope_e2e.rs.
  • Full workspace via cargo nextest run --workspace: 1060 passed / 2 skipped (slow-marked) / 0 failed.
  • One canonical Scope enum at hew-core/src/scope.rs:33; verified no duplicate definitions across modules.

Honest caveat: the 6 children landed on this single stacked branch (one commit per task, in dependency order) rather than 6 sibling branches. Per-branch isolation therefore wasn't proven separately — the smoke chore explicitly flagged the deviation rather than fabricating results, and ran every per-child test selector against the stacked branch instead.

Non-goals (v1)

  • Priority / label / branch filters (extension point, not v1).
  • Persistent loop.scope.default config knob.
  • Mid-run scope changes.
  • /hew:auto changes (already epic-scoped per hew-6n0v).

🤖 Generated with Claude Code

droidnoob and others added 7 commits May 30, 2026 08:11
…d-compat

- New `hew_core::scope` module exporting `Scope { Ready, Epics { epic_ids } }`
  with `#[serde(tag = "kind", rename_all = "snake_case")]` mirroring the
  external_gate::GateKind tagged-union pattern.
- `Default` is `Ready` so legacy RunConfig payloads without a scope key
  deserialize unchanged (covered by scope_serde_backward_compat_missing_field).
- `Scope::includes(task_id, &epic_descendant_set)` filters via a pre-resolved
  HashSet so the hot path is a set lookup, not a graph walk per task.
- `resolve_descendants(bd, &[epic_ids])` BFS-walks `tasks::children` with a
  visited set — handles shared descendants and accidental cycles.
- 9 unit tests: default, includes (Ready/Epics), serde roundtrip both
  variants, missing-field compat, transitive resolution, multi-epic
  union/dedupe, empty input. Per-parent fake BdClient added inline since
  the shared MockBd in tasks::tests keys on argv[0] alone.

Closes hew-55ne (parent epic hew-b3yl).
Dispatcher::new now takes a Scope. dispatch_tick resolves the
descendant set on every tick for Scope::Epics (so mid-run-added
children are picked up) and filters the bd ready list before slot
assignment. ready_seen counts the filtered set so callers detect
"queue drained" per scope.

Loop_cmd hands Scope::Ready for now; hew-ry5r threads the real value
from RunConfig.

- 4 new dispatcher unit tests cover Ready (unfiltered), Epics
  (descendant filter), Epics (no match → empty), Epics (recompute
  picks up newly-added child between ticks).
- MockBd extended to answer 'children <id> --json'.

Closes hew-7ind.
- Add Option<Scope> field with serde(default, skip_serializing_if = "Option::is_none")
  so legacy run.json (missing scope) parses to None and Some(Ready) serializes verbatim
- from_run sets scope = None; hew loop populates after RunConfig.scope lands (hew-ry5r)
- Add tests/fixtures/run-log-pre-scope.json + 5 tests covering Ready/Epics persistence,
  backward-compat fixture load, two-id epics round-trip, and default-None invariant
- Mirrors IterLog.model defensive serde pattern from hew-2cq

Closes hew-6nxs.
- RunConfig gains pub scope: Scope (default Ready)
- RunLog::from_run propagates run.config.scope verbatim, replacing the
  hew-6nxs placeholder-None — Some(Scope::Ready) on default cfg keeps
  the "defaulted vs explicit Ready" distinction intact
- loop_cmd grows resolve_scope(args) as the single source of truth;
  Dispatcher::new in run_loop_parallel and the RunConfig builder in
  run_worker_loop both read through it (returns Ready until hew-xhhw
  wires the --scope CLI flag)

Closes hew-ry5r.
Wires the run-scope CLI surface for hew loop run. argv > interactive
picker > non-interactive MissingFlag error.

- New ScopeArg ValueEnum + --scope, --epics (CSV), --epic (singular
  alias merged into --epics).
- resolve_scope(args, ctx, bd): validates --scope=ready against
  --epics conflict; validates each epic id via tasks::show (exists,
  is epic, open); interactive paths use inquire Select then
  MultiSelect; ResolvedScope::Cancelled exits 0 with a note.
- Scope is resolved once at run_loop top and threaded through new
  run_loop_with_scope / run_worker_loop_with_scope. Legacy
  run_loop_with / run_worker_loop kept as Scope::Ready back-compat
  wrappers so loop_backpressure / loop_dynamic_model /
  loop_parallel_e2e don't need new args.
- 8 unit tests on a FakeBd stub cover every resolve_scope branch.
- 7 e2e tests cover the CLI surface: clap rejection, no-picker
  argv paths, MissingFlag enforcement, conflict rejection, --help.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Summary gains `scope: Option<Scope>`; render() prints
  `scope:     <label>` between outcomes and tokens.
- Live print path passes `Some(run.config.scope.clone())`; the
  re-render path (`hew loop summary`) reads `RunLog.scope`, so
  pre-scope `run.json` files surface as `scope: ready (legacy)`.
- Parallel summary reads scope from worker 0's `run.json` (dispatcher-
  level scope is identical across workers).
- `Scope::label` + `scope::label_optional` keep the label format in
  one place: "ready", "epics [hew-6az, hew-1tq]", "ready (legacy)".
- Tests: scope_label_ready_and_epics,
  label_optional_distinguishes_none_from_explicit_ready,
  summary_renders_scope_ready, summary_renders_scope_epics_with_ids,
  summary_renders_legacy_no_scope_field_as_ready_legacy,
  summary_scope_line_appears_between_outcomes_and_tokens.
- docs/LOOP.md gets a `## Scope` section above Stop signals; CHANGELOG
  Unreleased Added entry; commands/loop.md examples now lead with the
  required `--scope=ready` (or `--scope=epics --epics=…`) flag.

Closes hew-cjhj.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI lacked `bd` on PATH, which made `hew loop run` exit with
`bd: not found` before ever reaching the scope MissingFlag check.
The three contract tests in loop_scope_e2e.rs that assert on the
exact `MissingFlag(scope|epics)` string therefore failed in CI
even though they passed locally where `bd` was installed.

Argv-level validation shouldn't depend on external binaries. Adds
a `precheck_scope_argv(args, ctx)` that runs ahead of `bd discover`
and fires the three errors that don't need bd:

  1. `--scope=ready` + `--epics`  (contradiction)
  2. `--scope=epics` + no epics + non-interactive  (MissingFlag epics)
  3. no `--scope` + non-interactive               (MissingFlag scope)

The rest of resolution (id validation, interactive pickers) still
lives in `resolve_scope` and runs after bd is on hand.

Verified by running the suite with bd scrubbed from PATH —
loop_scope_e2e 7/7 pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@droidnoob droidnoob merged commit 92db1bd into main May 30, 2026
21 of 22 checks passed
droidnoob added a commit that referenced this pull request May 30, 2026
* chore(release): 0.11.0

- workspace Cargo.toml: 0.10.0 -> 0.11.0
- 23 skill body `hew:version=` markers bumped to match
- .claude/ install snapshot refreshed via `hew init --runtime=claude`
- CHANGELOG.md: move [Unreleased] content into [0.11.0] — 2026-05-30

Release contents since 0.10.0:

#53 parallel hew loop via per-worker git worktrees (hew-6az)
#54 per-task model selection + per-model token spend (hew-1tq)
#55 init re-run UX — refresh/reconfigure/cancel (hew-0wa)
#56 split /hew:auto from /hew:loop semantics (hew-6n0v)
#57 cut local cargo test from ~2 min to ~22s (hew-v2ib)
#58 hew loop run --scope={ready|epics} (hew-b3yl)
#59 batch planner + end-of-run verify + loop graph (hew-lf40)
#60 retry_etxtbsy stub flake fix (hew-0rky)

Breaking surface: hew loop run in non-interactive mode now requires
--scope. Justifies the minor bump.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(readme): reflect 0.11.0 surface changes

- /hew:auto description updated to in-conversation epic walk (was the
  legacy plan→decompose→execute→verify; rewritten in hew-6n0v / #56)
- slash count 40 → 41 (new /hew:auto + various)
- loop snippets show --scope (required in non-interactive mode per
  hew-b3yl / #58), --jobs N, --verify-tests, hew loop summary,
  hew loop graph
- autonomous-loop bullets gain parallel-workers, scoped-runs +
  per-task-model, end-of-run-verification entries
- Selected knobs table adds loop.model.*, loop.planner.*,
  loop.end_of_run.verify_tests, loop.fallback_runtime

No changes to brand, hero copy, or repo description.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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