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 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 / .opencode settings
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.1",
3
+ "version": "1.0.2",
4
4
  "description": "Spec-Driven Development framework for Claude Code and OpenCode",
5
5
  "bin": {
6
- "nova-spec": "./bin/nova-spec.js"
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",