Vinaya CLI
Vinaya’s command reference.
documented against @attalabs/vinaya@0.19.2
help
$ vinaya helpShow this help text
version
$ vinaya versionPrint the CLI version
Options
--jsonEnveloped JSON output (schema: 1)init
$ npx @attalabs/vinaya initInstall Vinaya's git hooks, CI workflow, and starter config (diff-and-confirm, non-destructive)
It detects your repo, prints the complete diff of every intended change, and waits for your confirmation before installing anything. --dry-run prints that same diff and installs nothing. Nothing ever runs automatically on package install.
It installs one CI workflow that runs vinaya check --all --diff-only, alongside your existing workflows — refusing to overwrite rather than touching foreign content already at that path. Git hook stubs invoke the vinaya binary directly; if a hook already exists, it appends a delimited managed block, shown verbatim in the diff first, rather than overwriting it.
vinaya.config.json is seeded with a starter ruleset extracted from Vinaya's own battle-tested gates, not invented defaults. Issue and PR templates carrying the brief schema are added alongside your own; tier and needs:*-input labels are created only if they don't already exist — your existing labels are never modified.
The recommended branch-protection command is printed for you to run yourself — it is never applied, and your PATH is never touched. eject removes exactly the managed block it owns, or a whole file only if init created it.
Options
--dry-runPrint the full diff without installing anything--yesSkip the confirmation promptinit product
$ vinaya init productRegister a project in .vinaya/projects.md (and its project:<name> label) in an already-initialized repo
Writes (or appends to) .vinaya/projects.md — the registry Vinaya Studio's tranche board resolves a project's board link against. Idempotent: re-running with the same name updates nothing.
Also creates a project:<name> label (create-if-absent) — informational only, not read by any shipped check or by Studio.
A missing GitHub remote skips the label (the one forge-reaching op) but still writes the registry row, which is a pure local file write.
Options
--path <path>The project's home folder — declared, not derived. Defaults to the repo root.check
$ vinaya checkRun one check, or every registered check
Each spawned check's child process sees only a fixed baseline (PATH, LANG, HOME, HTTPS_PROXY, HTTP_PROXY, NO_PROXY, TMPDIR) plus whatever its CheckSpec['env'] declaration explicitly forwards — never the full parent environment. A required (true) or unsatisfied anyOf declaration missing from the caller's environment synthesizes a CheckError before the check ever spawns. Declare env (a core check's own registration, or vinaya.config.json's checks.<name>.env for a custom one) for any check that reads process.env/Bun.env/Deno.env directly — vinaya doctor carries the permanent diagnostic for one that doesn't.
--plan composes with --json. It requires zero env vars and never prints an env value — only how each one resolves (passthrough, optional, literal, or anyOf). A FAIL_CLOSED entry (a bare key with no namespace matching no core check) always renders inline rather than being dropped, and exits non-zero. --plan previews what the next minor's execution will enforce — vinaya check itself still runs the flat, unvalidated registry this release.
--local exists because a requiresOpenPr check (the core closes-n/test-plan, or a custom check declaring the same field) can only evaluate for real once a pull request exists — the generated pre-commit/pre-push hooks pass it so the first commit on a fresh task branch is never asked to satisfy a PR-body field before a PR can possibly exist. CI's vinaya-checks.yml omits it, so these checks always run for real once a PR is open.
Options
--allRun every registered check instead of one named check--jsonEnveloped JSON output (schema: 1)--diff-onlyScope diff-declared checks to changed files--localSkip every requiresOpenPr check (closes-n, test-plan) — set by the generated hooks, never by CI--parallel[=n]Concurrency cap (default: cpu-derived)--planPrint the resolved check registry (default/overridden/additive) without running anythingnew check
$ vinaya new checkScaffold a custom check into ./scripts/vinaya-checks/
pr create
$ vinaya pr createOpen a pull request after full brief-schema validation
Runs the config-defined brief-schema gate (briefSchema.pr in vinaya.config.json) LOCALLY before any gh write — prevention, not detection. On any failure it refuses with the versioned CheckError contract (one JSON line per finding on stderr, exit 1) whose agent_recovery_prompt names the exact corrective command.
Options
--titlePR title (validated against the forge-title grammar)--body-filePath to the PR body (stream-safe; the same bytes are validated and sent)--labelLabel(s) to apply (repeatable, comma-separated)--validate-onlyRun every gate and report PASS without opening the PR--jsonEnveloped JSON output (schema: 1)pr edit
$ vinaya pr editEdit an existing pull request (<n>) after full brief-schema validation
The target PR's real head branch and changed files are fetched from the forge to build the validation context — a failed fetch is a hard refusal, never a fall-back to the local checkout.
Options
--titleNew PR title (validated against the forge-title grammar)--body-filePath to the new PR body (stream-safe; same bytes validated and sent)--validate-onlyRun every gate and report PASS without editing the PR--jsonEnveloped JSON output (schema: 1)issue create
$ vinaya issue createOpen an issue after full brief-schema validation
A task Issue (any vinaya/tranche:* label) must carry the full Planner rationale (briefSchema.issue); non-task Issues pass through unvalidated.
Options
--titleIssue title (validated on task Issues)--body-filePath to the Issue body (stream-safe; same bytes validated and sent)--labelLabel(s) to apply; a `vinaya/tranche:*` label marks a task Issue--validate-onlyRun every gate and report PASS without opening the Issue--jsonEnveloped JSON output (schema: 1)issue edit
$ vinaya issue editEdit an existing issue (<n>) after full brief-schema validation
The target Issue's actual labels are fetched from the forge and unioned with argv to decide task-Issue applicability — a failed fetch is a hard refusal.
Options
--titleNew Issue title (validated on task Issues)--body-filePath to the new Issue body (stream-safe; same bytes validated and sent)--validate-onlyRun every gate and report PASS without editing the Issue--jsonEnveloped JSON output (schema: 1)doctor
$ vinaya doctorDiagnose hook, workflow, and config health — report only, never mutates
Carries the same env-declaration diagnostic vinaya check warns with — permanently, at info severity, not just ahead of the spawn-default flip — plus a warn-severity lint over suspicious env literal forms (a stray "true"/"false" string, or a high-entropy literal that reads like a leaked secret committed to config).
Options
--jsonEnveloped JSON output (schema: 1)upgrade
$ vinaya upgradeRegenerate hooks, workflow, and config to the current contract version (diff-and-confirm)
Options
--dry-runPrint the full diff without regenerating anything--yesSkip the confirmation promptarchive
$ vinaya archiveRun the post-merge Archivist directly: provenance + Issue close-out for a merged task PR
Resolves the merge commit's associated PR via gh, and — if it's a task PR without a provenance comment yet — posts the provenance block and closes the linked Issue. Idempotent: re-running against an already-archived PR is a no-op.
The same logic the generated vinaya-archivist.yml workflow's post-merge job runs on every push to main — callable directly for a one-off run or local verification.
Options
--merge-shaThe merge commit to resolve (defaults to the current HEAD)archive tranche
$ vinaya archive trancheClose a tranche — the tranche-level bookend to `init product`, closing the Milestone via the CLI
Refuses if any task Issue attached to the named tranche is still open, naming each one — closing a tranche with unresolved work is never silently allowed.
Once every task Issue is closed, prompts for confirmation (unless --yes) and closes the GitHub Milestone.
Options
--yesSkip the confirmation promptaudit
$ vinaya auditRun the ring-2 dead-branch-push and direct-main-push detection checks directly
Dead-branch-push is never-red — a notification channel that flags (label + PR comment) any task/* branch whose tip commit lands after its own PR already resolved. Direct-main-push is a real pass/fail — it opens an incident Issue and exits 1 if a commit on main has no associated merged PR.
The same logic the generated vinaya-archivist.yml workflow's daily-drift and direct-main-push-detection jobs run on schedule / on every push to main — callable directly for a one-off run or local verification.
Options
--onlyScope to one check: 'dead-branches' or 'direct-push'--shaThe commit to check for direct-main-push (defaults to the current HEAD)--jsonEnveloped JSON outputeject
$ vinaya ejectRemove every Vinaya-installed artifact, restoring the repo to stock
Options
--dry-runPrint the full removal diff without removing anything--yesSkip the confirmation promptdemo break
$ vinaya demo breakRun a guided refusal-then-fix demo on an isolated, discardable branch
Creates a collision-safe vinaya/demo-break-<id> branch off the current one, stages a deliberately incomplete draft brief, and attempts a real git commit — the repo's actually-installed pre-commit hook refuses it with its real output, not a scripted string. Applies the minimal fix, commits again, then switches back and deletes the demo branch.
Safe to run twice: refuses on a dirty working tree, refuses from a detached HEAD, and recovers automatically from a prior crashed run before starting a fresh one — never leaves the original branch touched or a stray demo branch behind.
Options
--keepSkip cleanup and leave the demo branch checked out to inspectwaiver
$ vinaya waiverApply the actor-verified 'vinaya/waiver:docs' or 'vinaya/waiver:review' label after prompting for a reason
Applies the label via gh pr edit --add-label, under the invoking human's own authenticated gh identity — this command never fabricates an actor. A waiver is never an agent-emittable string: not a PR body field, not a commit trailer, not a comment — the label plus its own labeling-timeline actor is the only mechanism ring 1 honors.
--print-only prints the exact gh pr edit/gh pr comment commands and runs nothing — for a human who wants to run them itself, or a CI/non-interactive context where an agent session should never be the one applying its own waiver.
Options
--reasonThe waiver rationale, posted as a PR comment (prompted for if omitted)--print-onlyPrint the exact `gh` commands instead of running themstudio
$ vinaya studioLaunch local Vinaya Studio against this repo — works standalone via npx, not just inside a Vinaya workspace checkout
quickstart
$ vinaya quickstartGuided wizard: init, doc-owners, project, commit, demo break, doctor, push — one command
Calls init's own diff-and-confirm flow unchanged (pausing on Enter before the diff prints, so its own step header isn't scrolled off by a long diff), then Y/n-prompts through the workarounds a guest used to run by hand: binding .vinaya/doc-owners pairs (bad input offers a retry instead of silently skipping, and a pointer that doesn't exist on disk is refused outright — both loop across as many pairs as the guest wants, not just one), registering tracked projects (init product, same retry/loop shape), committing the install, running demo break as proof the gates actually work (default yes — the one step this wizard makes hardest to skip), running doctor, and pushing. Each declined prompt skips only that step; the install commit itself is never prompt-gated — it just no-ops when there is genuinely nothing to commit.
Never reimplements or edits init/init product/demo break/doctor — it only calls their existing, unmodified entry points in sequence.