Skip to content

CLI reference

The nova-spec CLI lives at bin/nova-spec.js (registered in package.jsonbin). Every subcommand is implemented in lib/.

text
npx nova-spec <subcommand> [args...]

init

Interactive installer. Run once per project (or once globally).

bash
npx nova-spec init
# or simply:
npx nova-spec

Prompts:

  1. Scopeproject (current dir) / global (~/.claude) / update
  2. Runtimeclaude / opencode / both
  3. Ticket systemjira / none
  4. Jira config (only if jira) — URL, project, email, token validation, transition discovery
  5. Base branch — default main
  6. 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 SessionStart hook in .claude/settings.local.json
  • Creates context/ scaffolding (project install only): stack.md, conventions.md, plus decisions/, 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.

bash
npx nova-spec sync

Runs automatically on every Claude Code / OpenCode session start via the SessionStart hook. You can also call it manually.

Steps:

  1. Reads novaspec/.nova-manifest.json (last-shipped hashes)
  2. Walks every framework file in the installed package
  3. For each file, hash-compares against the consumer's local copy
  4. Untouched locally → overwrites with the new version
  5. Modified locally → skips, reports
  6. New in this version → creates
  7. Removed upstream and untouched locally → deletes
  8. Removed upstream but modified locally → keeps with warning
  9. Migrates config.yml (idempotent)
  10. Refreshes the SessionStart hook command (in case it changed)
  11. 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.

bash
npx nova-spec jira get PROJ-42
npx nova-spec jira transitions PROJ-42
npx nova-spec jira transition PROJ-42 41

Output is JSON. Exit codes:

CodeMeaning
0Success
1Generic error
2Usage error
401Invalid credentials
404Ticket 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.ymlforge: and falls back to git-remote detection.

bash
# 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.

bash
npx nova-spec source novaspec/templates/pr-body.md
# → /Users/adan/.npm/_npx/<hash>/node_modules/nova-spec/novaspec/templates/pr-body.md

Used 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.

ExitMeaning
0Path resolved and printed
1Path escapes the package OR file does not exist inside the package
2Usage error (no <path> argument given)

Exit code summary

CodeWhereMeaning
0AnySuccess
1GenericOperation failed (file missing, path escapes, network error, etc.)
2AnyUsage error (bad args, missing config)
127forge pr-commandConfigured forge CLI (gh / glab) is not installed
401jiraInvalid Jira credentials
404jiraTicket not found

Implementation map

SubcommandFile
initlib/installer.js
synclib/sync.js
jiralib/cli.jsrunJiralib/jira.js
forgelib/cli.jsrunForgelib/forge.js
sourcelib/cli.jsrunSource
Migrationslib/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.

Released under the MIT License.