nova-spec 1.0.2 → 1.0.4
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/INSTALL.md +144 -0
- package/PHILOSOPHY.md +149 -0
- package/README.md +27 -12
- package/lib/cli.js +153 -1
- package/lib/forge.js +61 -0
- package/lib/installer.js +322 -131
- package/lib/jira.js +138 -0
- package/lib/migrate-config.js +59 -0
- package/lib/sync.js +179 -118
- package/novaspec/agents/context-loader.md +1 -1
- package/novaspec/guardrails/nova-installed.sh +21 -0
- package/novaspec/guardrails/proposal-closed.sh +49 -0
- package/novaspec/guardrails/review-checks.sh +115 -0
- package/novaspec/templates/ticket-summary.md +14 -0
- package/package.json +11 -4
- package/novaspec/.nova-manifest.json +0 -30
- package/novaspec/config.yml +0 -23
- package/novaspec/custom/agents/.gitkeep +0 -0
- package/novaspec/custom/commands/.gitkeep +0 -0
- package/novaspec/custom/skills/.gitkeep +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Guardrail: deterministic pre-review checks.
|
|
3
|
+
#
|
|
4
|
+
# Usage: bash novaspec/guardrails/review-checks.sh <ticket-id> [base-branch]
|
|
5
|
+
#
|
|
6
|
+
# Runs (in order):
|
|
7
|
+
# 1. diff is non-empty (committed + working tree)
|
|
8
|
+
# 2. every "Files to touch" entry from tasks.md appears in the diff
|
|
9
|
+
# 3. lint clean (if `npm run lint` / `pnpm lint` / `yarn lint` exists)
|
|
10
|
+
# 4. tests pass (if `npm test` / `pnpm test` / `yarn test` exists)
|
|
11
|
+
#
|
|
12
|
+
# Exits 0 if every applicable check passes, 1 if any blocking check fails,
|
|
13
|
+
# 2 on usage error. Skipped checks (e.g. no lint script) are reported but
|
|
14
|
+
# don't fail.
|
|
15
|
+
|
|
16
|
+
set -uo pipefail
|
|
17
|
+
|
|
18
|
+
if [ -z "${1:-}" ]; then
|
|
19
|
+
echo "Usage: $0 <ticket-id> [base-branch]" >&2
|
|
20
|
+
exit 2
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
ticket="$1"
|
|
24
|
+
base="${2:-main}"
|
|
25
|
+
fail=0
|
|
26
|
+
|
|
27
|
+
# 1. Diff non-empty
|
|
28
|
+
diff_total="$(git diff "$base"...HEAD 2>/dev/null; git diff HEAD 2>/dev/null)"
|
|
29
|
+
if [ -z "$diff_total" ]; then
|
|
30
|
+
echo "✗ Empty diff: no committed or staged changes against $base."
|
|
31
|
+
fail=1
|
|
32
|
+
else
|
|
33
|
+
echo "✓ Diff is non-empty."
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# 2. Files to touch
|
|
37
|
+
tasks="context/changes/active/${ticket}/tasks.md"
|
|
38
|
+
if [ -f "$tasks" ]; then
|
|
39
|
+
# Pull every path under a "Files to touch" section. We look for lines that
|
|
40
|
+
# look like list items containing a path-ish token, but only between that
|
|
41
|
+
# heading and the next heading.
|
|
42
|
+
declared="$(awk '
|
|
43
|
+
/^#+ +Files to touch/i, /^#+ +/ {
|
|
44
|
+
if (NR > 1 && /^#+ +/ && !/^#+ +Files to touch/i) next_section=1
|
|
45
|
+
if (next_section) next
|
|
46
|
+
if (match($0, /[`"]?([\.a-zA-Z0-9_\/-]+\.[a-zA-Z0-9]+)[`"]?/, m)) print m[1]
|
|
47
|
+
}
|
|
48
|
+
' "$tasks" | sort -u)"
|
|
49
|
+
|
|
50
|
+
if [ -n "$declared" ]; then
|
|
51
|
+
missing=""
|
|
52
|
+
while IFS= read -r f; do
|
|
53
|
+
[ -z "$f" ] && continue
|
|
54
|
+
if ! git diff "$base"...HEAD --name-only | grep -qxF "$f" \
|
|
55
|
+
&& ! git diff HEAD --name-only | grep -qxF "$f"; then
|
|
56
|
+
missing+=" - $f"$'\n'
|
|
57
|
+
fi
|
|
58
|
+
done <<< "$declared"
|
|
59
|
+
|
|
60
|
+
if [ -n "$missing" ]; then
|
|
61
|
+
echo "✗ Files declared in tasks.md but missing from diff:"
|
|
62
|
+
printf '%s' "$missing"
|
|
63
|
+
fail=1
|
|
64
|
+
else
|
|
65
|
+
echo "✓ All declared files present in diff."
|
|
66
|
+
fi
|
|
67
|
+
else
|
|
68
|
+
echo "ℹ︎ tasks.md has no 'Files to touch' section — skipping declared-files check."
|
|
69
|
+
fi
|
|
70
|
+
else
|
|
71
|
+
echo "ℹ︎ No tasks.md (quick-fix path) — skipping declared-files check."
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# 3 + 4. Lint and test
|
|
75
|
+
detect_pm() {
|
|
76
|
+
if [ -f pnpm-lock.yaml ]; then echo pnpm
|
|
77
|
+
elif [ -f yarn.lock ]; then echo yarn
|
|
78
|
+
elif [ -f package-lock.json ] || [ -f package.json ]; then echo npm
|
|
79
|
+
else echo ''
|
|
80
|
+
fi
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
has_script() {
|
|
84
|
+
local pm="$1" script="$2"
|
|
85
|
+
if [ ! -f package.json ]; then return 1; fi
|
|
86
|
+
node -e "process.exit(((require('./package.json').scripts||{})['$script'])?0:1)" 2>/dev/null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
pm="$(detect_pm)"
|
|
90
|
+
|
|
91
|
+
if [ -n "$pm" ] && has_script "$pm" lint; then
|
|
92
|
+
echo "Running $pm run lint…"
|
|
93
|
+
if "$pm" run lint --silent >/dev/null 2>&1; then
|
|
94
|
+
echo "✓ Lint clean."
|
|
95
|
+
else
|
|
96
|
+
echo "✗ Lint failed. Run \`$pm run lint\` and fix before review."
|
|
97
|
+
fail=1
|
|
98
|
+
fi
|
|
99
|
+
else
|
|
100
|
+
echo "ℹ︎ No lint script — skipping."
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
if [ -n "$pm" ] && has_script "$pm" test; then
|
|
104
|
+
echo "Running $pm test…"
|
|
105
|
+
if "$pm" test --silent >/dev/null 2>&1; then
|
|
106
|
+
echo "✓ Tests pass."
|
|
107
|
+
else
|
|
108
|
+
echo "✗ Tests failed. Run \`$pm test\` and fix before review."
|
|
109
|
+
fail=1
|
|
110
|
+
fi
|
|
111
|
+
else
|
|
112
|
+
echo "ℹ︎ No test script — skipping."
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
[ "$fail" -eq 0 ]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
## Ticket: <TICKET-ID> — "<title>"
|
|
2
|
+
|
|
3
|
+
Classification : <quick-fix | feature | architecture> (<estimated effort>)
|
|
4
|
+
Affected services : <svc-1> ✓, <svc-2> ✓
|
|
5
|
+
Branch created : <type>/<TICKET>-<slug> (from <base>)
|
|
6
|
+
|
|
7
|
+
Loaded context:
|
|
8
|
+
Services : <names with ✓ if `context/services/<name>.md` exists, ✗ otherwise>
|
|
9
|
+
Decisions read: <list of decision filenames, or "none">
|
|
10
|
+
Gotchas read : <list, or "none">
|
|
11
|
+
Gaps : <list of missing context, or "none">
|
|
12
|
+
Questions : <open questions for the user, or "none">
|
|
13
|
+
|
|
14
|
+
Next step: <next command, e.g. `/nova-spec` or `/nova-build`>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nova-spec",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Spec-Driven Development framework for Claude Code and OpenCode",
|
|
5
5
|
"bin": {
|
|
6
6
|
"nova-spec": "bin/nova-spec.js"
|
|
@@ -8,11 +8,18 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
"bin/",
|
|
10
10
|
"lib/",
|
|
11
|
-
"novaspec/",
|
|
12
|
-
"
|
|
11
|
+
"novaspec/agents/",
|
|
12
|
+
"novaspec/commands/",
|
|
13
|
+
"novaspec/guardrails/",
|
|
14
|
+
"novaspec/skills/",
|
|
15
|
+
"novaspec/templates/",
|
|
16
|
+
"novaspec/config.example.yml",
|
|
17
|
+
"AGENTS.md",
|
|
18
|
+
"INSTALL.md",
|
|
19
|
+
"PHILOSOPHY.md"
|
|
13
20
|
],
|
|
14
21
|
"scripts": {
|
|
15
|
-
"test": "
|
|
22
|
+
"test": "node test/smoke.test.js"
|
|
16
23
|
},
|
|
17
24
|
"dependencies": {
|
|
18
25
|
"@inquirer/prompts": "^7.0.0"
|
|
@@ -1,30 +0,0 @@
|
|
|
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
|
-
}
|
package/novaspec/config.yml
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
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"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|