Skip to content

feat(launchpad): add --format json to buzz pack inspect (#239 STEP 1) - #257

Merged
serina-mcfall merged 3 commits into
launchpadfrom
feat/issue-239-projector-step1
Aug 20, 2026
Merged

feat(launchpad): add --format json to buzz pack inspect (#239 STEP 1)#257
serina-mcfall merged 3 commits into
launchpadfrom
feat/issue-239-projector-step1

Conversation

@serina-mcfall

@serina-mcfall serina-mcfall commented Aug 20, 2026

Copy link
Copy Markdown

Summary

Adds a local --format human|json flag to buzz pack inspect (STEP 1 of the merged Route 3 projector plan, #251). human is the pre-existing pretty-print, unchanged; json emits the full resolved persona config, the shape STEP 2's projector script will parse.

Related issue

Refs #239

Issue type

Task


Agent provenance

Field Value
Harness / provider Claude Code
Model claude-sonnet-5
Session reference N/A - harness does not expose a stable run id/URL for this session
Initiating human @serina-mcfall

Objective

Give buzz pack inspect a machine-readable JSON output mode, without changing its existing human-readable default, so a future projector script (#239 STEP 2) has something deterministic to parse.

Impacted components

crates/buzz-cli/src/lib.rs
crates/buzz-cli/src/commands/pack.rs
crates/buzz-persona/src/resolve.rs

Approach and rejected alternatives

Added a Serialize derive to ResolvedPack/ResolvedPersona/ResolvedMcpServer/ResolvedHooks/ResolvedTriggers in buzz-persona::resolve and reused resolve_pack() as-is, rather than writing a second JSON projection — the plan (#251) explicitly calls out that a second parser of pack YAML would duplicate precedence-resolution logic in a second place and drift from it.

Made --format a flag local to the pack inspect subcommand (parsed after the subcommand name) rather than reusing the CLI's existing global --format flag (parsed before the subcommand, default json, used by every relay-querying command). Rejected wiring the global flag directly: its default is already json, and pack inspect has never honored it — wiring it as-is would have silently flipped pack inspect's long-documented default output from human text to JSON for every existing caller (README.md, PERSONA_PACK_SPEC.md, the-professor-design.md all demonstrate the human default). A local flag defaulting to human preserves that default byte-for-byte and only adds json as an explicit opt-in. This also matches the plan's own literal invocation syntax (buzz pack inspect --format json <dir> — flag after the subcommand), which the global flag's position convention (before the subcommand) would not have produced.

Verification

Command run:

/home/serina/Launchpad/buzz/bin/cargo test -p buzz-cli -p buzz-persona

Raw output (tail):

test result: ok. 353 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.7xs

running 128 tests
...
test result: ok. 128 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s

Also ran (clean, no warnings):

cargo clippy -p buzz-cli -p buzz-persona --all-targets

Manual check against the real pack this projector will target:

buzz pack inspect launchpad/agents/the-professor
buzz pack inspect launchpad/agents/the-professor --format json

--format json output contains model: "claude-sonnet-5", temperature: 0.4, one mcp_servers entry (professor-tools), and triggers all false — identical to what the human printer already showed for the same pack.

Full pre-push gate (just rust-tests, desktop-tauri-checks, branch-skew) also ran clean on push.

Follow-up commit 1 (e49d1881b, env-only redaction, see Security implications below): cargo test -p buzz-cli -p buzz-persona — 354 + 128 + 5 + 13 passed, 0 failed, including the new redact_mcp_env_masks_values_keeps_keys test. cargo clippy -p buzz-cli -p buzz-persona --all-targets clean. Full pre-push gate ran clean on push (branch-skew, rust-tests, desktop-tauri-checks).

Follow-up commit 2 (24ea61896, widened to also redact args — see Security implications below): cargo test -p buzz-cli -p buzz-persona — same 354 + 128 + 5 + 13 passed, 0 failed, now via the renamed/extended redact_mcp_secrets_masks_env_values_and_args_keeps_env_keys. cargo clippy -p buzz-cli -p buzz-persona --all-targets clean, cargo fmt --check clean. Manually ran buzz pack inspect --format json against a test pack with args: ["--api-key", "sk-super-secret-123"] and env: {TOKEN: super-secret-value} — output shows "args": ["***", "***"] and "env": [["TOKEN", "***"]]. Full pre-push gate ran clean on push.

  • Tests or checks were run and the raw output is pasted above
  • The diff is confined to the scope of the linked issue
  • No secrets, keys, tokens or hostnames were added to tracked files

Not verified

buzz pack validate was left untouched — the plan's STEP 1 only names pack inspect, so validate's output format was not audited or changed. Also not verified: whether any other in-repo tooling greps pack inspect's stdout expecting the old plain-text shape by default — a repo-wide grep found only documentation references (README.md, PERSONA_PACK_SPEC.md, the-professor-design.md, this issue's own plan), none of which are executable, but a script outside what grep covers (e.g. a CI step) was not separately searched for.

Security implications

Updated twice after review — second pass fixed a gap the first pass left open. The original text ("None") was wrong — @benmitchell11 caught it. The pre-existing human printer only ever prints an MCP server count; it never shows command, args, or env. --format json serializes the full ResolvedMcpServer, and both env values and args are things a pack author may write as literals directly into mcp_servers[] in their persona.md — a secret is exactly as easy to put in an --api-key sk-...-shaped arg as in an env var. e49d1881b only redacted env; args was still serialized verbatim, so the exposure was only half-closed. Caught on a second QA pass and fixed in 24ea61896.

pack inspect --format json now redacts MCP server env values (keys kept, values become "***") and every args element (masked the same way; array length kept so a reader can still see how many there are — there's no "key" half to preserve for a positional arg, unlike env). The redaction function is now redact_mcp_secrets (renamed from redact_mcp_env to match its widened scope). Confirmed neither buzz-acp's build_mcp_servers() (always spawns with an empty arg list regardless of what a pack declares) nor the #239 projector script (launchpad/agents/project-pack.py, which refuses to project any persona whose MCP server has args at all, by design) reads the real args values, so redacting them costs nothing functionally — same conclusion as env. Test renamed/extended to redact_mcp_secrets_masks_env_values_and_args_keeps_env_keys, and manually verified against a pack with both a real-looking TOKEN env value and a --api-key sk-...-shaped arg — env key survives, env value is masked, every arg element is masked, array length is unchanged. Human format output remains byte-for-byte unchanged (still shows neither field, same as before either fix).

Escalations

mcp_servers[].env serializes as an array of [key, value] pairs rather than a JSON object, since the underlying type is Vec<(String, String)> — flagging this for whoever builds STEP 2's Python projector, since it needs to parse that shape rather than assume a dict, but it isn't a decision this PR needed to make (the underlying tuple-vec type is unchanged, matching every other consumer of ResolvedMcpServer). Note the values in this shape, and every args element, are now "***" per the redaction above; only env's key names are real.

Adds a local --format human|json flag to `buzz pack inspect`, defaulting to
human (the pre-existing pretty-print, byte-for-byte unchanged) with an
opt-in json mode that serializes the full ResolvedPack -- the shape the
Route 3 projector script (#239 STEP 2) will parse to configure a
buzz-acp+goose runtime from a persona pack.

Kept local to the subcommand rather than reusing the CLI's global --format
flag: pack commands are local-only (no relay round trip), the global flag's
default (json) would have silently changed pack inspect's long-documented
default output, and the plan's own invocation syntax places the flag after
the subcommand rather than before, where the global flag lives.

Verification:
cargo test -p buzz-cli -p buzz-persona -- 353 + 128 passed, 0 failed
cargo clippy -p buzz-cli -p buzz-persona --all-targets -- clean
Manually confirmed against launchpad/agents/the-professor: --format json
emits model/temperature/mcp_servers/triggers matching the human printer's
values exactly.

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
@serina-mcfall serina-mcfall added the by:agent Filed or authored by an AI agent, not a human label Aug 20, 2026

@benmitchell11 benmitchell11 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read the full diff. The design choices are sound — reusing resolve_pack() rather than a second YAML parser (matches the plan's own reasoning), making --format local to pack inspect rather than reusing the global flag to avoid silently flipping the long-documented human default, and the tests genuinely exercise the JSON shape (including the tuple-vs-object env serialization, correctly flagged as an escalation for STEP 2's consumer).

One real gap: "Security implications: None" isn't accurate. Checked the pre-existing human-format printer directly (pack.rs, unchanged by this diff) — for mcp_servers, it only ever prints "MCP servers: {}", persona.mcp_servers.len(). It never prints command, args, or env for any MCP server, count only. But --format json serializes the full ResolvedPack, including every ResolvedMcpServer.env: Vec<(String, String)> verbatim — and per this same diff's own doc comment, those are "literals (no interpolation in this PR)," i.e. whatever a pack author wrote directly into mcp_servers[].env in their persona.md.

So this PR is the first thing in the repo's history that will print an MCP server's env values to stdout at all. That's not a privilege escalation (a local user already has filesystem read access to the same persona.md), but it is a genuine, new exposure surface in a machine-readable, easily-copy-pasted-into-a-log-or-chat format that the tool never produced before — exactly the class of thing this repo's own secrets-and-access review dimension (just merged in #248/#250/#252) exists to catch. If a future pack author puts something sensitive-looking in an MCP server's env block (a placeholder that looks like a real token, an internal URL, anything), --format json now prints it where the human format never did.

Not asking for a specific fix — that's a judgment call (redact/mask values in JSON, or just an accepted-risk note) — but the Security implications section should say this plainly instead of "None," so whoever merges this is making that call knowingly rather than by omission. Given this same repo just spent real effort teaching a review dimension to catch exactly this pattern, it seems worth holding this PR to the same bar.

Everything else — the local-flag design, the Serialize derives, the test coverage, the clippy/test verification — is solid. Happy to re-review once the Security implications section is updated.

… json (#239)

benmitchell11 requested changes on #257: --format json serializes the
full ResolvedMcpServer, including env values a pack author wrote as
literals -- something the human printer never showed (it only prints
a server count). Neither buzz-acp nor the #239 projector plan reads
this field, so masking values (keeping keys) closes the new exposure
for free.

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
@serina-mcfall

Copy link
Copy Markdown
Author

@benmitchell11 fixed in e49d1881b: pack inspect --format json now redacts MCP server env values (keys kept, values masked to "***") before serializing. Confirmed neither buzz-acp nor the #239 projector plan reads that field, so nothing downstream needed the real values. Added redact_mcp_env_masks_values_keeps_keys and manually verified against a pack with a real-looking TOKEN value. Updated the "Security implications" section in the PR body accordingly. Ready for re-review.

…json (#239)

The prior fix (e49d188) only redacted mcp_servers[].env, but Ben's
original review named command/args/env as the fields the human printer
hides -- args was still serialized verbatim. A pack author can put a
secret in an MCP server's CLI args (--api-key sk-...) exactly as easily
as in its env block, so the exposure was only half-closed.

redact_mcp_env is renamed redact_mcp_secrets and now masks each args
element too (array length preserved, no key to keep since args aren't
key/value). Confirmed neither buzz-acp's build_mcp_servers() (always
spawns with an empty arg list) nor the #239 projector script (refuses
to project a persona whose MCP server has any args) reads the real
values, so nothing downstream depends on them.

Signed-off-by: Serina Mcfall <serina.mcfall@gmail.com>
@serina-mcfall

Copy link
Copy Markdown
Author

@benmitchell11 you were right to flag this as still open. The first fix (e49d1881b) only redacted mcp_servers[].env, but your original review named command/args/env as the fields the human printer hides — args was still serialized verbatim, so a secret in an --api-key sk-...-shaped arg would have been exactly as exposed as one in env.

Fixed in 24ea61896: redact_mcp_env is now redact_mcp_secrets and masks every args element too (array length kept, no key to preserve unlike env). Confirmed neither buzz-acp's build_mcp_servers() (always spawns with an empty arg list) nor the #239 projector script (refuses to project any persona whose MCP server has args at all) reads the real values, so nothing downstream depends on them. Test extended, manually verified against a pack with both a fake TOKEN env value and a fake --api-key arg — both masked, env key preserved. PR body's Security implications section updated to describe both passes honestly.

Re-review welcome.

@benmitchell11 benmitchell11 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both follow-up commits checked directly, not just the write-up.

e49d1881b redacted env values only. 24ea61896 correctly caught that my original review named command/args/env together as the fields the human printer hides — args was still serialized verbatim after the first fix, and an --api-key sk-...-shaped arg is exactly as easy a mistake as a secret in env. Good catch on the second pass; the reasoning for why redacting both costs nothing downstream (buzz-acp::build_mcp_servers() always spawns with an empty arg list; the #239 projector refuses to project any persona whose MCP server has args at all) is verified against real behavior, not assumed.

Ran the new test myself rather than trusting the pasted output: redact_mcp_secrets_masks_env_values_and_args_keeps_env_keys passes. command is correctly left unredacted — it's the executable path, not secret material, and the JSON output needs it to stay useful.

The Security implications section is now honest about the two-pass process rather than presenting the final state as if it were right the first time. Approving.

@serina-mcfall
serina-mcfall merged commit f36d10f into launchpad Aug 20, 2026
25 of 26 checks passed
@serina-mcfall
serina-mcfall deleted the feat/issue-239-projector-step1 branch August 20, 2026 22:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

by:agent Filed or authored by an AI agent, not a human

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants