nova-spec 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +13 -0
- package/README.md +42 -28
- package/lib/installer.js +33 -4
- package/lib/sync.js +40 -0
- package/novaspec/.nova-manifest.json +30 -0
- package/novaspec/commands/nova-sync.md +2 -2
- package/novaspec/config.yml +23 -0
- package/package.json +3 -3
package/AGENTS.md
CHANGED
|
@@ -42,6 +42,19 @@ context/
|
|
|
42
42
|
|
|
43
43
|
Memory model: one fact = one file, filename = index, explicit supersede via `> Supersedes: <old>.md`. Wisdom about memory itself lives in `decisions/`, not in `AGENTS.md`.
|
|
44
44
|
|
|
45
|
+
## Auto-sync on session start
|
|
46
|
+
|
|
47
|
+
At the start of every session, check if nova-spec has pending updates by running:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
/nova-sync
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
If `novaspec/.nova-manifest.json` exists and the installed version matches the latest, skip it silently. If there are updates or outdated custom overrides, report them to the user before proceeding.
|
|
54
|
+
|
|
55
|
+
> **Claude Code** handles this automatically via a `SessionStart` hook in `.claude/settings.json`.
|
|
56
|
+
> **OpenCode** relies on this instruction — always run `/nova-sync` at session start.
|
|
57
|
+
|
|
45
58
|
## Symlinks
|
|
46
59
|
|
|
47
60
|
Claude Code discovers commands via `.claude/` symlinks pointing to `novaspec/`.
|
package/README.md
CHANGED
|
@@ -3,53 +3,49 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<strong>Spec-Driven Development
|
|
6
|
+
<strong>Spec-Driven Development for Claude Code and OpenCode.</strong><br>
|
|
7
7
|
From a ticket to a merged PR in explicit steps, with architectural memory that doesn't decay.
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
10
|
<p align="center">
|
|
11
11
|
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
|
|
12
12
|
<img alt="Status: experimental" src="https://img.shields.io/badge/status-experimental-orange.svg">
|
|
13
|
-
<img alt="
|
|
13
|
+
<a href="https://www.npmjs.com/package/nova-spec"><img alt="npm" src="https://img.shields.io/npm/v/nova-spec.svg"></a>
|
|
14
|
+
<img alt="Built for Claude Code" src="https://img.shields.io/badge/built%20for-Claude%20Code%20%7C%20OpenCode-purple.svg">
|
|
14
15
|
</p>
|
|
15
16
|
|
|
16
17
|
---
|
|
17
18
|
|
|
18
19
|
## What it is
|
|
19
20
|
|
|
20
|
-
`nova-spec` adds seven `/nova-*` commands to Claude Code that turn a ticket into a traceable change: classify, close requirements, plan, implement task by task, review, and wrap up with commit + PR + memory update.
|
|
21
|
+
`nova-spec` adds seven `/nova-*` slash commands to Claude Code (and OpenCode) that turn a ticket into a traceable change: classify, close requirements, plan, implement task by task, review, and wrap up with commit + PR + memory update.
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
Architectural memory (`context/decisions/`, `context/gotchas/`, `context/services/`) lives in atomic markdown files that humans edit and `grep` finds.
|
|
24
|
+
|
|
25
|
+
It's not a template or a generator. It's a set of conventions + commands that your AI agent runs as slash commands inside your repo.
|
|
23
26
|
|
|
24
27
|
## Who is this for
|
|
25
28
|
|
|
26
|
-
- Developers using **Claude Code**
|
|
29
|
+
- Developers using **Claude Code** or **OpenCode** on real projects, not toy demos.
|
|
27
30
|
- Teams that want their AI agent to follow a **disciplined ticket → PR flow** instead of one-shotting code.
|
|
28
31
|
- Anyone tired of **re-explaining the same architectural context** every new chat.
|
|
29
32
|
|
|
30
33
|
If you only use Claude Code for one-off scripts, this is overkill. If you ship to production with it, read on.
|
|
31
34
|
|
|
32
|
-
## Why it exists
|
|
33
|
-
|
|
34
|
-
Without discipline, an agent writes code fast and loses the *why*. The next ticket forces you to re-explain the same context. `nova-spec` enforces human checkpoints, separates spec from executable tasks, and leaves a trail in `context/` so the next ticket starts informed.
|
|
35
|
-
|
|
36
35
|
## Quickstart
|
|
37
36
|
|
|
38
37
|
```bash
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
npx nova-spec init
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
That's it. The interactive wizard asks where to install (global or per-project), which runtime (Claude Code, OpenCode, or both), and optionally your Jira connection. It generates a ready-to-use `config.yml` — no manual editing required.
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
cd /path/to/your-project
|
|
44
|
-
bash ~/tools/nova-spec/install.sh
|
|
43
|
+
Then open your editor and run your first ticket:
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
claude
|
|
45
|
+
```
|
|
48
46
|
/nova-start PROJ-123
|
|
49
47
|
```
|
|
50
48
|
|
|
51
|
-
Full details in [INSTALL.md](./INSTALL.md).
|
|
52
|
-
|
|
53
49
|
## A taste of it
|
|
54
50
|
|
|
55
51
|
What `/nova-start PROJ-42` actually does:
|
|
@@ -72,7 +68,7 @@ Loaded context:
|
|
|
72
68
|
Next step: /nova-spec
|
|
73
69
|
```
|
|
74
70
|
|
|
75
|
-
No code yet. The agent
|
|
71
|
+
No code yet. The agent classified the work, created the branch, and pulled in only the architectural decisions that matter for this ticket.
|
|
76
72
|
|
|
77
73
|
## Flow
|
|
78
74
|
|
|
@@ -82,7 +78,6 @@ No code yet. The agent has classified the work, created the branch, and pulled i
|
|
|
82
78
|
|
|
83
79
|
| Command | What it does |
|
|
84
80
|
|---|---|
|
|
85
|
-
| `/nova-init` | One-off bootstrap: scans the repo and generates draft `context/services/` files with TODOs |
|
|
86
81
|
| `/nova-start <TICKET>` | Pulls the ticket, classifies it (quick-fix / feature / architecture), creates a branch, loads context |
|
|
87
82
|
| `/nova-spec` | Closes open decisions and writes `proposal.md` |
|
|
88
83
|
| `/nova-plan` | Translates the spec into `tasks.md` (plan + tasks) |
|
|
@@ -90,8 +85,32 @@ No code yet. The agent has classified the work, created the branch, and pulled i
|
|
|
90
85
|
| `/nova-review` | Final code review against spec, conventions and decisions |
|
|
91
86
|
| `/nova-wrap` | Updates memory, archives the spec, creates commit and PR |
|
|
92
87
|
| `/nova-status [TICKET]` | Current status of the ticket (read-only) |
|
|
88
|
+
| `/nova-sync` | Updates nova-spec core to the latest version |
|
|
89
|
+
| `/nova-diff <name>` | Shows diff between your custom override and the new core version |
|
|
90
|
+
|
|
91
|
+
`quick-fix` tickets skip `/nova-spec` and `/nova-plan`.
|
|
92
|
+
|
|
93
|
+
## Customizing skills and commands
|
|
94
|
+
|
|
95
|
+
Place any file in `novaspec/custom/` to override the core version — same name, your rules:
|
|
93
96
|
|
|
94
|
-
|
|
97
|
+
```
|
|
98
|
+
novaspec/
|
|
99
|
+
├── skills/ ← core (managed by nova-spec)
|
|
100
|
+
└── custom/
|
|
101
|
+
└── skills/
|
|
102
|
+
└── nova-wrap/ ← your override, same name wins
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Run `/nova-sync` to update the core. Your `custom/` folder is never touched.
|
|
106
|
+
|
|
107
|
+
## Keeping up to date
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
npx nova-spec sync
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Updates the core, preserves your custom overrides and `config.yml`, and tells you if any of your overrides have upstream changes worth reviewing.
|
|
95
114
|
|
|
96
115
|
## Principles
|
|
97
116
|
|
|
@@ -102,15 +121,10 @@ No code yet. The agent has classified the work, created the branch, and pulled i
|
|
|
102
121
|
|
|
103
122
|
## Documentation
|
|
104
123
|
|
|
105
|
-
-
|
|
106
|
-
-
|
|
107
|
-
- Quick reference: [novaspec/README.quickref.md](./novaspec/README.quickref.md)
|
|
124
|
+
- Install options: [INSTALL.md](./INSTALL.md)
|
|
125
|
+
- Design philosophy: [PHILOSOPHY.md](./PHILOSOPHY.md)
|
|
108
126
|
- Contributing: [CONTRIBUTING.md](./CONTRIBUTING.md)
|
|
109
127
|
|
|
110
|
-
## See also
|
|
111
|
-
|
|
112
|
-
`nova-spec` was built using itself. The full development history — including specs, decisions, gotchas and dogfooding — is preserved in the lab repo: [adansuku/nova-spec-lab](https://github.com/adansuku/nova-spec-lab).
|
|
113
|
-
|
|
114
128
|
## License
|
|
115
129
|
|
|
116
130
|
MIT — see [LICENSE](./LICENSE).
|
package/lib/installer.js
CHANGED
|
@@ -131,9 +131,10 @@ function installFiles(destDir, runtime, scope) {
|
|
|
131
131
|
if (!fs.existsSync(notes)) fs.writeFileSync(notes, '');
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
// Runtime symlinks /
|
|
134
|
+
// Runtime symlinks / settings
|
|
135
135
|
if (runtime === 'claude' || runtime === 'both') {
|
|
136
136
|
createSymlinks(destDir, '.claude');
|
|
137
|
+
writeClaudeSettings(path.join(destDir, '.claude'));
|
|
137
138
|
}
|
|
138
139
|
if (runtime === 'opencode' || runtime === 'both') {
|
|
139
140
|
createSymlinks(destDir, '.opencode');
|
|
@@ -150,13 +151,40 @@ function createSymlinks(destDir, dotDir) {
|
|
|
150
151
|
for (const name of ['commands', 'skills', 'agents']) {
|
|
151
152
|
const link = path.join(dir, name);
|
|
152
153
|
const target = path.join('..', 'novaspec', name);
|
|
153
|
-
|
|
154
|
-
fs.rmSync(link, { recursive: true, force: true });
|
|
155
|
-
}
|
|
154
|
+
fs.rmSync(link, { recursive: true, force: true });
|
|
156
155
|
fs.symlinkSync(target, link);
|
|
157
156
|
}
|
|
158
157
|
}
|
|
159
158
|
|
|
159
|
+
function writeClaudeSettings(claudeDir) {
|
|
160
|
+
const settingsPath = path.join(claudeDir, 'settings.local.json');
|
|
161
|
+
const hook = {
|
|
162
|
+
hooks: {
|
|
163
|
+
SessionStart: [
|
|
164
|
+
{
|
|
165
|
+
hooks: [
|
|
166
|
+
{
|
|
167
|
+
type: 'command',
|
|
168
|
+
command: 'npx nova-spec@latest sync 2>/dev/null || true',
|
|
169
|
+
timeout: 30,
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
if (fs.existsSync(settingsPath)) {
|
|
178
|
+
const existing = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
179
|
+
if (existing.hooks?.SessionStart) return; // already configured
|
|
180
|
+
existing.hooks = existing.hooks || {};
|
|
181
|
+
existing.hooks.SessionStart = hook.hooks.SessionStart;
|
|
182
|
+
fs.writeFileSync(settingsPath, JSON.stringify(existing, null, 2) + '\n');
|
|
183
|
+
} else {
|
|
184
|
+
fs.writeFileSync(settingsPath, JSON.stringify(hook, null, 2) + '\n');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
160
188
|
function writeOpenCodeSettings(opencodeDir) {
|
|
161
189
|
const settingsPath = path.join(opencodeDir, 'settings.local.json');
|
|
162
190
|
if (!fs.existsSync(settingsPath)) {
|
|
@@ -216,6 +244,7 @@ function ensureGitignore(destDir) {
|
|
|
216
244
|
'novaspec/custom/',
|
|
217
245
|
'.env',
|
|
218
246
|
'notes.md',
|
|
247
|
+
'.claude/settings.local.json',
|
|
219
248
|
'.opencode/settings.local.json',
|
|
220
249
|
'.opencode/node_modules/',
|
|
221
250
|
'.DS_Store',
|
package/lib/sync.js
CHANGED
|
@@ -87,6 +87,15 @@ async function sync(destDir = process.cwd()) {
|
|
|
87
87
|
// Restore config
|
|
88
88
|
if (configBackup) fs.writeFileSync(configPath, configBackup);
|
|
89
89
|
|
|
90
|
+
// Update AGENTS.md and CLAUDE.md (always overwrite — these are framework files)
|
|
91
|
+
for (const file of ['AGENTS.md', 'CLAUDE.md']) {
|
|
92
|
+
const src = path.join(PACKAGE_ROOT, file);
|
|
93
|
+
const dest = path.join(destDir, file);
|
|
94
|
+
if (fs.existsSync(src) && fs.existsSync(dest)) {
|
|
95
|
+
fs.copyFileSync(src, dest);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
90
99
|
// Regenerate manifest with new hashes
|
|
91
100
|
const newManifest = generateManifest(novaspecDest);
|
|
92
101
|
|
|
@@ -114,6 +123,9 @@ async function sync(destDir = process.cwd()) {
|
|
|
114
123
|
newManifest.outdated_customs = outdated;
|
|
115
124
|
fs.writeFileSync(manifestPath, JSON.stringify(newManifest, null, 2) + '\n');
|
|
116
125
|
|
|
126
|
+
// Ensure SessionStart hook is present (idempotent)
|
|
127
|
+
ensureSessionStartHook(destDir);
|
|
128
|
+
|
|
117
129
|
// Report
|
|
118
130
|
console.log('\n ✓ nova-spec core updated to v' + newManifest.version + '\n');
|
|
119
131
|
|
|
@@ -144,4 +156,32 @@ function copyDirSync(src, dest) {
|
|
|
144
156
|
}
|
|
145
157
|
}
|
|
146
158
|
|
|
159
|
+
function ensureSessionStartHook(destDir) {
|
|
160
|
+
const hook = {
|
|
161
|
+
type: 'command',
|
|
162
|
+
command: 'npx nova-spec@latest sync 2>/dev/null || true',
|
|
163
|
+
timeout: 30,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const targets = [
|
|
167
|
+
path.join(destDir, '.claude', 'settings.local.json'),
|
|
168
|
+
path.join(destDir, '.opencode', 'settings.local.json'),
|
|
169
|
+
].filter(p => fs.existsSync(path.dirname(p))); // only if runtime dir exists
|
|
170
|
+
|
|
171
|
+
for (const settingsPath of targets) {
|
|
172
|
+
let settings = {};
|
|
173
|
+
if (fs.existsSync(settingsPath)) {
|
|
174
|
+
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (_) {}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const existing = settings.hooks?.SessionStart?.[0]?.hooks?.[0];
|
|
178
|
+
if (existing?.command === hook.command) continue; // already up to date
|
|
179
|
+
|
|
180
|
+
// Overwrite if missing or using outdated command (e.g. without @latest)
|
|
181
|
+
settings.hooks = settings.hooks || {};
|
|
182
|
+
settings.hooks.SessionStart = [{ hooks: [hook] }];
|
|
183
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
147
187
|
module.exports = { sync, generateManifest };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.1",
|
|
3
|
+
"generated_at": "2026-05-10T07:46:23.881Z",
|
|
4
|
+
"hashes": {
|
|
5
|
+
"commands/nova-build": "5d9c103168f97dd60e4efbe1f9a277a7",
|
|
6
|
+
"commands/nova-diff": "529389d8d7d187c70de708610101009c",
|
|
7
|
+
"commands/nova-plan": "44709e96a49e86d808f3cad94c0bdc86",
|
|
8
|
+
"commands/nova-review": "aa2e4e91fe5e774ae7f374c7f3bf8882",
|
|
9
|
+
"commands/nova-spec": "726194e235d566dfbb81e785a6437b46",
|
|
10
|
+
"commands/nova-start": "d031e1958cf769864f566e9d768b1552",
|
|
11
|
+
"commands/nova-status": "cf0b7db18d4290479c35ed3b87fecc56",
|
|
12
|
+
"commands/nova-sync": "4364efbb594eeddaff7deba8d7e2a947",
|
|
13
|
+
"commands/nova-wrap": "846dfee6faaba65d80c082bb90e9b306",
|
|
14
|
+
"skills/close-requirement": {
|
|
15
|
+
"SKILL.md": "43d5f32320a635b15601a21ef5a0bf93"
|
|
16
|
+
},
|
|
17
|
+
"skills/jira-integration": {
|
|
18
|
+
"SKILL.md": "513f8b116be4bed955a8e3631bbf250c"
|
|
19
|
+
},
|
|
20
|
+
"skills/update-service-context": {
|
|
21
|
+
"SKILL.md": "b0788661cc61e2c6acb4bead24f48017"
|
|
22
|
+
},
|
|
23
|
+
"skills/write-decision": {
|
|
24
|
+
"SKILL.md": "20ea0ac9f19ec17a72d562f811c4a61e"
|
|
25
|
+
},
|
|
26
|
+
"agents/context-loader": "42a4cf797452bd76c720bd8f36e261f4",
|
|
27
|
+
"agents/nova-review-agent": "5658926243d4ce01cfc3b79ee8e4ad93"
|
|
28
|
+
},
|
|
29
|
+
"outdated_customs": []
|
|
30
|
+
}
|
|
@@ -12,7 +12,7 @@ which custom overrides may need attention.
|
|
|
12
12
|
Execute in the terminal:
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
npx nova-spec sync
|
|
15
|
+
npx nova-spec@latest sync
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
Show the output to the user as-is.
|
|
@@ -43,4 +43,4 @@ If everything is clean:
|
|
|
43
43
|
|
|
44
44
|
- Don't modify any custom files.
|
|
45
45
|
- Don't auto-merge or apply changes.
|
|
46
|
-
- If `npx nova-spec sync` fails, show the error and stop.
|
|
46
|
+
- If `npx nova-spec@latest sync` fails, show the error and stop.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# nova-spec — project configuration
|
|
2
|
+
# This file is gitignored — do not push it to the repo.
|
|
3
|
+
|
|
4
|
+
branch:
|
|
5
|
+
pattern: "{type}/{ticket}-{slug}"
|
|
6
|
+
types:
|
|
7
|
+
bugfix: bugfix
|
|
8
|
+
hotfix: hotfix
|
|
9
|
+
feature: feature
|
|
10
|
+
documentation: docs
|
|
11
|
+
refactor: refactor
|
|
12
|
+
chore: chore
|
|
13
|
+
architecture: arch
|
|
14
|
+
ticket_case: upper
|
|
15
|
+
base: main
|
|
16
|
+
|
|
17
|
+
jira:
|
|
18
|
+
skill: "jira-integration"
|
|
19
|
+
url: https://your-workspace.atlassian.net
|
|
20
|
+
project: PROJ
|
|
21
|
+
email:
|
|
22
|
+
token: ${JIRA_API_TOKEN}
|
|
23
|
+
done_transition_id: "41"
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nova-spec",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Spec-Driven Development framework for Claude Code and OpenCode",
|
|
5
5
|
"bin": {
|
|
6
|
-
"nova-spec": "
|
|
6
|
+
"nova-spec": "bin/nova-spec.js"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
"bin/",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"repository": {
|
|
25
25
|
"type": "git",
|
|
26
|
-
"url": "https://github.com/Adansuku/nova-spec"
|
|
26
|
+
"url": "git+https://github.com/Adansuku/nova-spec.git"
|
|
27
27
|
},
|
|
28
28
|
"keywords": [
|
|
29
29
|
"claude-code",
|