feat(cli): report every command run to a debug function in astryx.config - #4812
Open
josephfarina wants to merge 2 commits into
Open
feat(cli): report every command run to a debug function in astryx.config#4812josephfarina wants to merge 2 commits into
debug function in astryx.config#4812josephfarina wants to merge 2 commits into
Conversation
…onfig
Set `debug` in astryx.config and that function receives one event per
command run — the command, its arguments and flags (with their Commander
source, so a typed flag is distinguishable from a default), the outcome,
exit code, duration, error code, an environment snapshot, and everything
the command printed to stdout and stderr. Setting it is the whole opt-in;
leave it out and nothing is recorded and nothing is stored.
Capturing the output is what makes the record useful for improving the
CLI rather than just counting invocations: you can see the answer a user
actually got, not only what they asked for.
Coverage is the point, so the seams are chosen for it. Events are
delivered from a `process.on('exit')` listener because `cliError()` exits
synchronously — anything hooked to normal completion would report
successes and almost no failures. The handler is loaded before Commander
parses, because parse errors and `--help` short-circuit before any hook
runs. Signals are handled separately since `exit` does not fire for them,
and the handler removes itself and re-raises so Ctrl-C still terminates.
`DebugEvent` is published from `@astryxdesign/cli/debug` with a sealed
zod validator drift-locked to the type, so the recorder cannot add a
field without publishing it. Handlers run synchronously and receive a
copy: one that throws, or mutates what it was given, can neither fail the
command nor affect anything else. Values are scrubbed before delivery.
Also fixes a pre-existing gap this uncovered: installJsonShim now shims
commands as they join the command tree rather than in one walk at
startup, so a command registered later can no longer fall out of the
--json contract or lose parse-error attribution.
Co-authored-by: Cursor <cursoragent@cursor.com>
`astryx init --json` emits its install receipt as a standard envelope — `init.run` with the mode, features run, agent-doc files written, any soft docsError and the template outcome, or `init.remove` for --remove-agents. Human output is suppressed so stdout carries only the envelope, and the exit code is unchanged from human mode. init was the last side-effecting command still refused by the --json gate. That gate exists so a command cannot write half a project and only then report that --json is unsupported; since init() already returned a typed receipt, the fix was to emit it rather than keep refusing. theme and layout remain off the allowlist, but both are command groups with no output of their own. The tests that used init as their example of an unsupported command move to theme, which still is one. Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
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
Set
debuginastryx.configand that function receives one event per command run:Setting it is the whole opt-in — there is no flag, no env var, and no
astryx debugcommand. Leave it out and nothing is recorded and nothing is stored anywhere.Each event carries the command, its arguments and flags (with their Commander source, so a typed flag is distinguishable from a default), the outcome, exit code, duration, error code, an environment snapshot including which coding agent invoked the CLI, and everything the command printed to stdout and stderr.
That last part is the reason to have this at all: you can see the answer a user actually got, not only what they asked for.
astryx build "analytics dashboard"records the prompt and the composition kit returned, which is the pair you need to judge whether the answer was any good.Second commit is independent:
astryx init --jsonnow works.Where the seams are, and why
Coverage is the whole point, so the choices are all in service of not missing failures.
process.on('exit').cliError()callsprocess.exit()synchronously, so atry/finallyaround an action never runs on an error andpostActionhooks are skipped. Anything hooked to normal completion would report successes and almost no failures.--helpshort-circuit before any hook runs, so anywhere later would leave exactly the failures you most want reported with nowhere to report them.exitdoes not fire for them — atheme build --watchsomeone Ctrl-Cs would otherwise vanish. The handler removes itself and re-raises, so Ctrl-C still terminates and the parent still sees true signal death.process.stdout.write/process.stderr.write. One seam catchesemit(), JSON envelopes,cliError, Commander's own help text, and direct writes.All 24 commands are covered without any of them knowing this exists; there is no per-command wiring to forget.
Contract
DebugEventis published from@astryxdesign/cli/debugwith a sealed zod validator (parseDebugEvent) drift-locked to the type, so the recorder cannot add a field without publishing it.schemaVersionis a literal, so widening it turns every consumer's branch into a compile error rather than a silent misread.Handlers run synchronously at exit and receive a copy. One that throws — or mutates what it was given — can neither fail the command nor affect anything else. A returned promise is never awaited, so network delivery from inside the handler will not work; write a file or spawn a detached child.
Values are scrubbed before delivery: home paths become
~, project paths become relative, emails and URL credentials are removed, options with sensitive-looking names are dropped, and recognizable credential formats (GitHub, Slack, AWS, JWT, bearer) are stripped wherever they appear. Verified on a real session: zero home paths and zero username occurrences across 37 recorded runs.The known limit, documented rather than hidden: a high-entropy string with no recognizable format under an ordinary option name is recorded as written, because it is indistinguishable from a filename.
Also fixed
A pre-existing gap this uncovered:
installJsonShimdid one recursive walk at startup, so any command registered afterwards silently kept Commander's default_exit— dropping out of the--jsoncontract and out of parse-error attribution. It now shims commands as they join the tree, removing the ordering requirement entirely.Test plan
pnpm -F @astryxdesign/cli test— 2645 passing. The 9 failures are pre-existing onmain(verified by stashing and re-running): macOS case-insensitive-filesystem issues and cwd leakage between parallel workers.typecheck:strict,typecheck:json-api,typecheck:authoringclean — the last of those is the drift-lock proving the published type and the zod schema infer identically.eslintclean;pnpm -F @astryxdesign/cli readmeregenerated; drift, manifest and readme-gen gates pass.--json, and Ctrl-C.--jsonstdout still parses as a single envelope.debugset is within noise (219–233ms with a config present vs 216–224ms without).Made with Cursor