M12: CLI polish — surface v4 manifest fields, add --json, expose snapshot/task flags#60
Merged
Conversation
Catch the user-facing CLI up to v4 bundle schema and clear two M12 deferrals from the existing roadmap: - `leadforge inspect` now prints `primary_task`, `label_window_days`, `snapshot_day` (with a "(full horizon, no windowing)" annotation when null/equal to horizon), and a count + list of redacted columns (full list for <=4 columns, truncated for more). - `leadforge inspect --json` / `-j` dumps the parsed manifest as pipe-friendly JSON. - `leadforge generate` exposes `--snapshot-day`, `--primary-task`, `--label-window-days` flags. They thread to existing `Generator.from_recipe()` kwargs; recipe defaults still apply when omitted. - Help strings on `--n-accounts`, `--n-contacts`, `--n-leads`, and `--horizon-days` rewritten from "Number of leads." style to "Override recipe default ...". Closes deferrals "M12: CLI --json flag" and "M12: CLI help text polish" in .agent-plan.md. `validate --json` and `--strict` remain scoped out as separate follow-up PRs (--strict still needs a per-check vs global gating design call). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
Updates the leadforge CLI to better reflect bundle schema v4 by exposing manifest windowing/task metadata in inspect, adding a JSON output mode for scripting, and making snapshot/task config overrides available directly via generate flags.
Changes:
leadforge inspectnow printsprimary_task,label_window_days,snapshot_day(including a full-horizon annotation), and a summarizedredacted_columnslist.- Added
leadforge inspect --json / -jto emit the parsed manifest as JSON to stdout. - Added
leadforge generateflags--snapshot-day,--primary-task,--label-window-days, plus help-text improvements; tests cover new CLI behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
leadforge/cli/commands/inspect.py |
Adds --json/-j mode and surfaces v4 manifest fields (task/windowing/redactions) in human-readable output. |
leadforge/cli/commands/generate.py |
Exposes task/windowing overrides as first-class CLI flags and improves help text for population/horizon overrides. |
tests/test_cli.py |
Adds integration tests for new generate flags, inspect v4 field surfacing, redaction formatting, and JSON output behavior. |
.agent-plan.md |
Updates milestone deferrals table to reflect completed CLI polish items and newly deferred validate --json. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Acts on the brutal self-review of #60. Summary: - Drop the snapshot_day == horizon_days "(full horizon)" branch. The manifest is the source of truth; if a user pinned snapshot_day=90 on a 90-day horizon, inspect now prints "90 days" verbatim instead of silently relabelling it. Only manifest snapshot_day=null prints the full-horizon annotation. - Skip v3+ rows (Primary task / Label window / Snapshot day / Redactions) entirely when the manifest doesn't carry those keys — v2 bundles render cleanly with no "?" placeholders. - Inline the two _format_* helpers; drop the redundant runtime isinstance() guards (manifest dict-shape was already validated up top). - Redaction line cleanup: pluralize correctly ("1 column" / "N columns"), drop the "(N total)" redundancy, omit the line entirely on empty redacted_columns instead of printing "0 column(s) []". - Pin the truncation boundary explicitly in tests: 4 cols → full list, 5 cols → first-3 + ellipsis (assertions fail if c4/c5 leak into the truncated head). - Add header-order regression test pinning the 8 pre-existing rows. - Add the missing --json contract test: stdout is JSON-equivalent to on-disk manifest.json. - Add CHANGELOG entry under Unreleased and a `--json | jq` example to the README CLI section. All 954 tests pass; ruff + mypy clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
pr-agent-context report: This run includes an unresolved review comment on PR #60 in repository https://github.com/leadforge-dev/leadforge
For each unresolved review comment, recommend one of: resolve as irrelevant, accept and implement
the recommended solution, open a separate issue and resolve as out-of-scope for this PR, accept and
implement a different solution, or resolve as already treated by the code.
After I reply with my decision per item, implement the accepted actions, resolve the corresponding
PR comments, and push all of these changes in a single commit.
# Copilot Comments
## COPILOT-1
Location: leadforge/cli/commands/inspect.py
URL: https://github.com/leadforge-dev/leadforge/pull/60#discussion_r3186586897
Status: outdated
Root author: copilot-pull-request-reviewer
Comment:
`Label window` always appends the literal " days" even when `label_window_days` is missing or not an int, producing output like "? days" for older bundles / minimal manifests. Consider formatting this field like `_format_snapshot_day()` (only add the units when the value is an int, otherwise print just "?") to avoid confusing human-readable output.Run metadata: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Catches the user-facing CLI up to bundle schema v4 (which shipped today as #59) and clears two M12 deferrals from
.agent-plan.md.Before this PR, a user who downloaded a v4 bundle and ran
leadforge inspectgot no signal that the bundle is windowed, no idea which columns were redacted, no view of the primary task or label window — they had tocat manifest.json | jq. The mirror gap onleadforge generatewas that--snapshot-day,--primary-task, and--label-window-dayswere only reachable via a YAML--overridefile even thoughGenerator.from_recipe()already accepted all three.What changed
(A)
leadforge inspectsurfaces v3/v4 manifest fields. Immediately afterSchema ver, the human-readable header now prints:Rules:
?placeholders....for ≥5 columns. The leading count is the only count printed (no redundant(N total)).1 column/N columns).Snapshot day:mirrors the manifest verbatim. Onlynulltriggers the(full horizon, no windowing)annotation; numeric values — even when equal tohorizon_days— print asN days.(B)
leadforge inspect --json/-j. Dumps the parsed manifest as JSON to stdout — pipe-friendly, no header. The contract is byte-equivalent JSON to the on-diskmanifest.json, asserted bytest_inspect_json_equals_manifest_file.(C)
leadforge generateflags. Added--snapshot-day,--primary-task,--label-window-days(kebab-case ↔ snake_case). AllOptional[int]/Optional[str]defaulting toNone; recipe defaults flow through unchanged when omitted (regression test included).(D) Help-text tightening, scoped narrowly. A pass over
cli/commands/*.pyrevealed four help strings ongenerate.pyflags that just restated the flag name (--n-accounts: "Number of accounts."). Those were rewritten to"Override recipe default account count."-style. Every other help string in the CLI was reviewed and left alone — they were already informative.Deferrals closed
--jsonflaggenerateThe deferral rationale was "no consumer needs it yet" — that stopped being true the moment v1.0 shipped to public consumers.
Explicitly out of scope
leadforge validate --json— separate follow-up; tracked as a new entry in.agent-plan.md's deferred table.leadforge validate --strict— still needs a design call (per-check vs global gating). Remains deferred.leadforge/cli/.manifest.jsonshape — this PR only consumes it.Self-review pass (commit 68404b8)
After opening the PR, I ran a brutally honest senior-dev review of my own diff. Real problems found and fixed:
snapshot_day == horizon_days → "(full horizon)"branch was silently lying about what the manifest contained. Removed. Onlynulltriggers the annotation now.Label window: ? daysand similar?-padded noise. v3+ rows are now omitted entirely when the manifest doesn't carry those keys."6 column(s) [c1, c2, c3, ...] (6 total)"printed the count twice and used a 1990scolumn(s)hedge. Now: correct pluralization, no redundant(N total), line omitted entirely on empty list.[:3]slice vs ≤4-cols-full rule) wasn't pinned in tests — a[:4]regression would have passed. Added explicit 4-vs-5 boundary tests that fail ifc4/c5leak into the truncated head._format_*helpers had runtimeisinstance(cols, list)guards against malformed manifests we'd already validated. Inlined and dropped the guards.--jsoncontract test:json.loads(stdout) == json.loads(manifest.json).--json | jqexample in the README CLI section.Acknowledged but not unwound: this PR does four cohesive things in one diff. Splitting (B) and (C) into separate PRs would have been cleaner for blame/revert. Cost of unwinding now > cost of leaving as-is; flagging it as a process note for next time.
Test plan
pytest: 954 passing (937 baseline + 17 new tests after self-review)ruff check . && ruff format --check .: cleanmypy leadforge/: cleanleadforge generate --recipe b2b_saas_procurement_v1 --seed 42 --mode student_public --out /tmp/v4smoke …produces a v4 bundleleadforge inspect /tmp/v4smokeshows clean v4 fields (no(N total)redundancy, no empty-list noise)leadforge inspect /tmp/v4smoke --json | jq .snapshot_dayreturns30New tests in
tests/test_cli.py(17 total)inspect:
?placeholderscolumn, notcolumn(s)orcolumns)(N total)suffix--jsonoutput is JSON-equal to on-diskmanifest.json-jshort flag works--jsonand plain mode produce non-overlapping outputgenerate:
--snapshot-dayflows to manifest--primary-taskand--label-window-daysboth flow to manifest🤖 Generated with Claude Code