CLI reference
The nova-spec CLI lives at bin/nova-spec.js (registered in package.json → bin). Every subcommand is implemented in lib/.
npx nova-spec <subcommand> [args...]init
Interactive installer. Run once per project (or once globally).
npx nova-spec init
# or simply:
npx nova-specPrompts:
- Scope —
project(current dir) /global(~/.claude) /update - Runtime —
claude/opencode/both - Ticket system —
jira/none - Jira config (only if
jira) — URL, project, email, token validation, transition discovery - Base branch — default
main - Forge — auto-detected from
git remote; you confirm or override
Side effects:
- Copies framework files from the installed package into
novaspec/ - Generates
novaspec/config.yml - Creates
.claude/and/or.opencode/symlinks - Installs
SessionStarthook in.claude/settings.local.json - Creates
context/scaffolding (project install only):stack.md,conventions.md, plusdecisions/,gotchas/,services/,changes/active/,changes/archive/ - Generates initial
.nova-manifest.json - Adds entries to
.gitignore
init is safe to re-run if you need to repair a broken install. It preserves novaspec/config.yml and does not overwrite an existing CLAUDE.md, but it will re-copy stock framework files under novaspec/. For updates that must not clobber local edits, use npx nova-spec sync.
update scope is a shortcut for npx nova-spec sync.
sync
Apply package updates to an existing install.
npx nova-spec syncRuns automatically on every Claude Code / OpenCode session start via the SessionStart hook. You can also call it manually.
Steps:
- Reads
novaspec/.nova-manifest.json(last-shipped hashes) - Walks every framework file in the installed package
- For each file, hash-compares against the consumer's local copy
- Untouched locally → overwrites with the new version
- Modified locally → skips, reports
- New in this version → creates
- Removed upstream and untouched locally → deletes
- Removed upstream but modified locally → keeps with warning
- Migrates
config.yml(idempotent) - Refreshes the
SessionStarthook command (in case it changed) - Regenerates the manifest
Output is sectioned: + new, ↻ updated, ⚠ NOT updated, − removed, ⚠ removed upstream but kept.
See Architecture → Sync internals.
jira <subcmd> [args...]
Deterministic Jira client. Reads novaspec/config.yml for URL/email and resolves ${JIRA_API_TOKEN} from env.
npx nova-spec jira get PROJ-42
npx nova-spec jira transitions PROJ-42
npx nova-spec jira transition PROJ-42 41Output is JSON. Exit codes:
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Generic error |
| 2 | Usage error |
| 401 | Invalid credentials |
| 404 | Ticket not found |
The token never appears in the command line — Node passes it as an HTTP Authorization: Basic ... header internally.
See Integrations → Jira.
forge <subcmd> [args...]
Multi-forge abstraction. Reads novaspec/config.yml → forge: and falls back to git-remote detection.
# What does the repo's remote suggest?
npx nova-spec forge detect
# → github | gitlab | bitbucket | (exit 1 if none)
# Build the create-PR command
npx nova-spec forge pr-command "Title" "Body" "main"
# → gh pr create --base 'main' --title 'Title' --body 'Body'
# or
# → glab mr create --target-branch 'main' --title 'Title' --description 'Body' --yes
# What's the right vocabulary for user-facing messages?
npx nova-spec forge term
# → PR (github) or MR (gitlab)/nova-wrap invokes pr-command and term. You can use them in your own scripts too.
See Integrations → Forge.
source <relative-path>
Print the absolute path to a framework file inside the installed nova-spec package.
npx nova-spec source novaspec/templates/pr-body.md
# → /Users/adan/.npm/_npx/<hash>/node_modules/nova-spec/novaspec/templates/pr-body.mdUsed by /nova-diff to locate the package version of a file the user has edited locally.
The subcommand sandboxes the path inside the installed package: any input that resolves outside (e.g. ../../etc/passwd) is rejected with exit 1 and ✗ Path escapes the nova-spec package.
| Exit | Meaning |
|---|---|
| 0 | Path resolved and printed |
| 1 | Path escapes the package OR file does not exist inside the package |
| 2 | Usage error (no <path> argument given) |
Exit code summary
| Code | Where | Meaning |
|---|---|---|
| 0 | Any | Success |
| 1 | Generic | Operation failed (file missing, path escapes, network error, etc.) |
| 2 | Any | Usage error (bad args, missing config) |
| 127 | forge pr-command | Configured forge CLI (gh / glab) is not installed |
| 401 | jira | Invalid Jira credentials |
| 404 | jira | Ticket not found |
Implementation map
| Subcommand | File |
|---|---|
init | lib/installer.js |
sync | lib/sync.js |
jira | lib/cli.js → runJira → lib/jira.js |
forge | lib/cli.js → runForge → lib/forge.js |
source | lib/cli.js → runSource |
| Migrations | lib/migrate-config.js (called from sync) |
Edit any of these in your project to change CLI behavior — but remember sync hash-compares them too: your edits survive updates.