nova-spec 1.0.1 → 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/lib/installer.js +32 -1
- 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/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');
|
|
@@ -155,6 +156,35 @@ function createSymlinks(destDir, dotDir) {
|
|
|
155
156
|
}
|
|
156
157
|
}
|
|
157
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
|
+
|
|
158
188
|
function writeOpenCodeSettings(opencodeDir) {
|
|
159
189
|
const settingsPath = path.join(opencodeDir, 'settings.local.json');
|
|
160
190
|
if (!fs.existsSync(settingsPath)) {
|
|
@@ -214,6 +244,7 @@ function ensureGitignore(destDir) {
|
|
|
214
244
|
'novaspec/custom/',
|
|
215
245
|
'.env',
|
|
216
246
|
'notes.md',
|
|
247
|
+
'.claude/settings.local.json',
|
|
217
248
|
'.opencode/settings.local.json',
|
|
218
249
|
'.opencode/node_modules/',
|
|
219
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",
|