specweave 0.33.3 → 0.33.5
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/CLAUDE.md +85 -19
- package/dist/src/cli/cleanup-zombies.js +8 -5
- package/dist/src/cli/cleanup-zombies.js.map +1 -1
- package/dist/src/cli/commands/jobs.js +19 -2
- package/dist/src/cli/commands/jobs.js.map +1 -1
- package/dist/src/cli/commands/living-docs.js +1 -1
- package/dist/src/cli/commands/living-docs.js.map +1 -1
- package/dist/src/cli/helpers/init/external-import-grouping.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/external-import-grouping.js +11 -7
- package/dist/src/cli/helpers/init/external-import-grouping.js.map +1 -1
- package/dist/src/cli/workers/clone-worker.js +22 -5
- package/dist/src/cli/workers/clone-worker.js.map +1 -1
- package/dist/src/config/types.d.ts +203 -1208
- package/dist/src/config/types.d.ts.map +1 -1
- package/dist/src/core/background/job-dependency.d.ts.map +1 -1
- package/dist/src/core/background/job-dependency.js +1 -0
- package/dist/src/core/background/job-dependency.js.map +1 -1
- package/dist/src/core/background/job-launcher.js +2 -2
- package/dist/src/core/background/job-launcher.js.map +1 -1
- package/dist/src/core/background/job-manager.d.ts +8 -0
- package/dist/src/core/background/job-manager.d.ts.map +1 -1
- package/dist/src/core/background/job-manager.js +19 -1
- package/dist/src/core/background/job-manager.js.map +1 -1
- package/dist/src/core/background/types.d.ts +9 -1
- package/dist/src/core/background/types.d.ts.map +1 -1
- package/dist/src/core/background/types.js +8 -1
- package/dist/src/core/background/types.js.map +1 -1
- package/dist/src/importers/external-importer.d.ts +26 -5
- package/dist/src/importers/external-importer.d.ts.map +1 -1
- package/dist/src/importers/item-converter.d.ts.map +1 -1
- package/dist/src/importers/item-converter.js +18 -1
- package/dist/src/importers/item-converter.js.map +1 -1
- package/dist/src/importers/jira-importer.d.ts +10 -0
- package/dist/src/importers/jira-importer.d.ts.map +1 -1
- package/dist/src/importers/jira-importer.js +70 -6
- package/dist/src/importers/jira-importer.js.map +1 -1
- package/dist/src/init/architecture/types.d.ts +33 -140
- package/dist/src/init/architecture/types.d.ts.map +1 -1
- package/dist/src/init/compliance/types.d.ts +30 -27
- package/dist/src/init/compliance/types.d.ts.map +1 -1
- package/dist/src/init/repo/types.d.ts +11 -34
- package/dist/src/init/repo/types.d.ts.map +1 -1
- package/dist/src/init/research/src/config/types.d.ts +15 -82
- package/dist/src/init/research/src/config/types.d.ts.map +1 -1
- package/dist/src/init/research/types.d.ts +38 -93
- package/dist/src/init/research/types.d.ts.map +1 -1
- package/dist/src/init/team/types.d.ts +4 -42
- package/dist/src/init/team/types.d.ts.map +1 -1
- package/dist/src/living-docs/smart-doc-organizer.js +1 -1
- package/dist/src/living-docs/smart-doc-organizer.js.map +1 -1
- package/dist/src/sync/closure-metrics.d.ts +102 -0
- package/dist/src/sync/closure-metrics.d.ts.map +1 -0
- package/dist/src/sync/closure-metrics.js +267 -0
- package/dist/src/sync/closure-metrics.js.map +1 -0
- package/dist/src/sync/sync-coordinator.d.ts +29 -0
- package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
- package/dist/src/sync/sync-coordinator.js +153 -16
- package/dist/src/sync/sync-coordinator.js.map +1 -1
- package/dist/src/utils/docs-preview/config-generator.d.ts.map +1 -1
- package/dist/src/utils/docs-preview/config-generator.js +4 -0
- package/dist/src/utils/docs-preview/config-generator.js.map +1 -1
- package/dist/src/utils/notification-constants.d.ts +87 -0
- package/dist/src/utils/notification-constants.d.ts.map +1 -0
- package/dist/src/utils/notification-constants.js +131 -0
- package/dist/src/utils/notification-constants.js.map +1 -0
- package/dist/src/utils/notification-manager.d.ts +24 -0
- package/dist/src/utils/notification-manager.d.ts.map +1 -1
- package/dist/src/utils/notification-manager.js +29 -0
- package/dist/src/utils/notification-manager.js.map +1 -1
- package/dist/src/utils/platform-utils.d.ts +13 -3
- package/dist/src/utils/platform-utils.d.ts.map +1 -1
- package/dist/src/utils/platform-utils.js +17 -6
- package/dist/src/utils/platform-utils.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/commands/specweave-increment.md +46 -0
- package/plugins/specweave/commands/specweave-jobs.md +153 -8
- package/plugins/specweave/commands/specweave-judge-llm.md +296 -0
- package/plugins/specweave/commands/specweave-organize-docs.md +2 -2
- package/plugins/specweave/hooks/hooks.json +10 -0
- package/plugins/specweave/hooks/spec-project-validator.sh +24 -2
- package/plugins/specweave/hooks/universal/hook-wrapper.cmd +26 -26
- package/plugins/specweave/hooks/universal/session-start.cmd +16 -16
- package/plugins/specweave/hooks/universal/session-start.ps1 +16 -16
- package/plugins/specweave/hooks/v2/guards/metadata-json-guard.sh +87 -0
- package/plugins/specweave/hooks/v2/guards/metadata-json-guard.test.sh +302 -0
- package/plugins/specweave/hooks/v2/guards/per-us-project-validator.sh +72 -18
- package/plugins/specweave/hooks/v2/guards/per-us-project-validator.test.sh +406 -0
- package/plugins/specweave/scripts/session-watchdog.sh +288 -134
- package/plugins/specweave/skills/increment-planner/SKILL.md +48 -18
- package/plugins/specweave/skills/increment-planner/templates/spec-multi-project.md +27 -14
- package/plugins/specweave/skills/increment-planner/templates/spec-single-project.md +16 -5
- package/plugins/specweave/skills/spec-generator/SKILL.md +74 -15
- package/plugins/specweave-docs/commands/build.md +4 -4
- package/plugins/specweave-docs/commands/generate.md +1 -1
- package/plugins/specweave-docs/commands/health.md +1 -1
- package/plugins/specweave-docs/commands/init.md +1 -1
- package/plugins/specweave-docs/commands/organize.md +2 -2
- package/plugins/specweave-docs/commands/validate.md +1 -1
- package/plugins/specweave-docs/commands/view.md +391 -0
- package/plugins/specweave-docs/skills/preview/SKILL.md +56 -17
- package/src/templates/AGENTS.md.template +24 -28
- package/src/templates/CLAUDE.md.template +12 -8
- package/plugins/specweave/commands/specweave-judge.md +0 -276
- package/plugins/specweave-docs/commands/preview.md +0 -274
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +0 -738
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +0 -1107
|
@@ -12,9 +12,16 @@
|
|
|
12
12
|
# - file_path matches: .specweave/increments/*/spec.md
|
|
13
13
|
#
|
|
14
14
|
# Rules:
|
|
15
|
-
# - Each
|
|
15
|
+
# - Each US section MUST have **Project**: <value> on next few lines
|
|
16
16
|
# - For 2-level structures, each US MUST also have **Board**: <value>
|
|
17
17
|
# - Project values MUST match configured projects (not generic keywords)
|
|
18
|
+
# - Supports ALL US formats:
|
|
19
|
+
# - ### US-001: (simple)
|
|
20
|
+
# - #### US-001: (4 hashes)
|
|
21
|
+
# - ### US-FE-001: (with project prefix)
|
|
22
|
+
# - #### US-FE-001: (multi-project)
|
|
23
|
+
# - #### US-BE-001: (backend)
|
|
24
|
+
# - #### US-SHARED-001: (shared)
|
|
18
25
|
# - Fallback allowed for existing specs via SPECWEAVE_LEGACY_SPEC=1
|
|
19
26
|
#
|
|
20
27
|
# Returns exit code 1 (block) if validation fails, 0 (allow) otherwise.
|
|
@@ -23,6 +30,7 @@
|
|
|
23
30
|
# - SPECWEAVE_FORCE_PROJECT=1 - Skip all project validation
|
|
24
31
|
# - SPECWEAVE_LEGACY_SPEC=1 - Allow specs without per-US project (legacy mode)
|
|
25
32
|
#
|
|
33
|
+
# v0.34.0 - Fixed to handle all US ID formats (US-001, US-FE-001, etc.)
|
|
26
34
|
|
|
27
35
|
set -e
|
|
28
36
|
|
|
@@ -75,8 +83,13 @@ log_debug "Validating per-US project fields for: $FILE_PATH"
|
|
|
75
83
|
# Extract file content
|
|
76
84
|
CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // ""')
|
|
77
85
|
|
|
78
|
-
# Count User Stories
|
|
79
|
-
|
|
86
|
+
# Count User Stories - support ALL formats:
|
|
87
|
+
# - ### US-001: (simple, 3 hashes)
|
|
88
|
+
# - #### US-001: (4 hashes)
|
|
89
|
+
# - ### US-FE-001: (with project prefix like FE, BE, SHARED)
|
|
90
|
+
# - #### US-FE-001: (multi-project with 4 hashes)
|
|
91
|
+
# Pattern: 3-4 hashes, space, US-, optional prefix (letters+dash), digits, colon
|
|
92
|
+
US_PATTERN='^#{3,4} US-([A-Z]+-)?[0-9]+:'
|
|
80
93
|
TOTAL_US=$(echo "$CONTENT" | grep -cE "$US_PATTERN" || echo 0)
|
|
81
94
|
|
|
82
95
|
log_debug "Total User Stories found: $TOTAL_US"
|
|
@@ -88,7 +101,7 @@ if [ "$TOTAL_US" -eq 0 ]; then
|
|
|
88
101
|
fi
|
|
89
102
|
|
|
90
103
|
# Extract User Story sections and check for **Project**: field
|
|
91
|
-
# Strategy: For each
|
|
104
|
+
# Strategy: For each US heading, look at next 10 lines for **Project**:
|
|
92
105
|
|
|
93
106
|
MISSING_PROJECT=()
|
|
94
107
|
MISSING_BOARD=()
|
|
@@ -96,26 +109,46 @@ MULTI_PROJECT=() # USs with multiple projects (comma-separated)
|
|
|
96
109
|
MULTI_BOARD=() # USs with multiple boards (comma-separated)
|
|
97
110
|
US_WITH_PROJECT=0
|
|
98
111
|
|
|
99
|
-
# Use
|
|
112
|
+
# Use grep to find all US headings (both ### and ####, with or without prefix)
|
|
100
113
|
while IFS= read -r us_line; do
|
|
101
|
-
# Extract US ID
|
|
102
|
-
US_ID=$(echo "$us_line" | grep -oE 'US-[0-9]+' | head -1)
|
|
114
|
+
# Extract US ID - handles US-001, US-FE-001, US-BE-001, US-SHARED-001, etc.
|
|
115
|
+
US_ID=$(echo "$us_line" | grep -oE 'US-([A-Z]+-)?[0-9]+' | head -1)
|
|
103
116
|
|
|
104
117
|
if [ -z "$US_ID" ]; then
|
|
105
118
|
continue
|
|
106
119
|
fi
|
|
107
120
|
|
|
108
|
-
# Get line number of this US heading
|
|
109
|
-
|
|
121
|
+
# Get line number of this US heading (works with both ### and ####)
|
|
122
|
+
# Escape the US_ID for grep (the dash needs no escape, but be safe)
|
|
123
|
+
LINE_NUM=$(echo "$CONTENT" | grep -nE "^#{3,4} ${US_ID}:" | head -1 | cut -d: -f1)
|
|
110
124
|
|
|
111
125
|
if [ -z "$LINE_NUM" ]; then
|
|
126
|
+
log_debug "Could not find line number for $US_ID"
|
|
112
127
|
continue
|
|
113
128
|
fi
|
|
114
129
|
|
|
115
|
-
# Extract next
|
|
116
|
-
|
|
130
|
+
# Extract lines after heading UNTIL next US heading, separator (---), or max 15 lines
|
|
131
|
+
# This prevents reading **Project** from a DIFFERENT user story
|
|
132
|
+
SECTION=""
|
|
133
|
+
line_count=0
|
|
134
|
+
while IFS= read -r line; do
|
|
135
|
+
# Stop at next US heading (### US- or #### US-)
|
|
136
|
+
if [[ "$line" =~ ^#{3,4}\ US- ]] && [ "$line_count" -gt 0 ]; then
|
|
137
|
+
break
|
|
138
|
+
fi
|
|
139
|
+
# Stop at separator
|
|
140
|
+
if [[ "$line" =~ ^---$ ]] && [ "$line_count" -gt 0 ]; then
|
|
141
|
+
break
|
|
142
|
+
fi
|
|
143
|
+
# Max 15 lines
|
|
144
|
+
if [ "$line_count" -ge 15 ]; then
|
|
145
|
+
break
|
|
146
|
+
fi
|
|
147
|
+
SECTION+="$line"$'\n'
|
|
148
|
+
line_count=$((line_count + 1))
|
|
149
|
+
done < <(echo "$CONTENT" | tail -n +$((LINE_NUM + 1)))
|
|
117
150
|
|
|
118
|
-
# Check for **Project**: field
|
|
151
|
+
# Check for **Project**: field within this US section only
|
|
119
152
|
PROJECT_LINE=$(echo "$SECTION" | grep -E '^\*\*Project\*\*:\s*\S' | head -1)
|
|
120
153
|
|
|
121
154
|
if [ -n "$PROJECT_LINE" ]; then
|
|
@@ -136,7 +169,7 @@ while IFS= read -r us_line; do
|
|
|
136
169
|
log_debug "$US_ID MISSING **Project**: field ✗"
|
|
137
170
|
fi
|
|
138
171
|
|
|
139
|
-
done < <(echo "$CONTENT" | grep -E "
|
|
172
|
+
done < <(echo "$CONTENT" | grep -E "^#{3,4} US-([A-Z]+-)?[0-9]+:")
|
|
140
173
|
|
|
141
174
|
log_debug "User Stories with **Project**: $US_WITH_PROJECT / $TOTAL_US"
|
|
142
175
|
|
|
@@ -167,21 +200,42 @@ log_debug "Structure level: $STRUCTURE_LEVEL"
|
|
|
167
200
|
# For 2-level structures, also check for **Board**: field
|
|
168
201
|
if [ "$STRUCTURE_LEVEL" = "2" ]; then
|
|
169
202
|
while IFS= read -r us_line; do
|
|
170
|
-
|
|
203
|
+
# Extract US ID - handles all formats (US-001, US-FE-001, etc.)
|
|
204
|
+
US_ID=$(echo "$us_line" | grep -oE 'US-([A-Z]+-)?[0-9]+' | head -1)
|
|
171
205
|
|
|
172
206
|
if [ -z "$US_ID" ]; then
|
|
173
207
|
continue
|
|
174
208
|
fi
|
|
175
209
|
|
|
176
|
-
|
|
210
|
+
# Get line number (works with both ### and ####)
|
|
211
|
+
LINE_NUM=$(echo "$CONTENT" | grep -nE "^#{3,4} ${US_ID}:" | head -1 | cut -d: -f1)
|
|
177
212
|
|
|
178
213
|
if [ -z "$LINE_NUM" ]; then
|
|
214
|
+
log_debug "Could not find line number for $US_ID (board check)"
|
|
179
215
|
continue
|
|
180
216
|
fi
|
|
181
217
|
|
|
182
|
-
|
|
218
|
+
# Extract lines after heading UNTIL next US heading, separator, or max 15 lines
|
|
219
|
+
SECTION=""
|
|
220
|
+
line_count=0
|
|
221
|
+
while IFS= read -r line; do
|
|
222
|
+
# Stop at next US heading
|
|
223
|
+
if [[ "$line" =~ ^#{3,4}\ US- ]] && [ "$line_count" -gt 0 ]; then
|
|
224
|
+
break
|
|
225
|
+
fi
|
|
226
|
+
# Stop at separator
|
|
227
|
+
if [[ "$line" =~ ^---$ ]] && [ "$line_count" -gt 0 ]; then
|
|
228
|
+
break
|
|
229
|
+
fi
|
|
230
|
+
# Max 15 lines
|
|
231
|
+
if [ "$line_count" -ge 15 ]; then
|
|
232
|
+
break
|
|
233
|
+
fi
|
|
234
|
+
SECTION+="$line"$'\n'
|
|
235
|
+
line_count=$((line_count + 1))
|
|
236
|
+
done < <(echo "$CONTENT" | tail -n +$((LINE_NUM + 1)))
|
|
183
237
|
|
|
184
|
-
# Check for **Board**: field
|
|
238
|
+
# Check for **Board**: field within this US section only
|
|
185
239
|
BOARD_LINE=$(echo "$SECTION" | grep -E '^\*\*Board\*\*:\s*\S' | head -1)
|
|
186
240
|
|
|
187
241
|
if [ -n "$BOARD_LINE" ]; then
|
|
@@ -200,7 +254,7 @@ if [ "$STRUCTURE_LEVEL" = "2" ]; then
|
|
|
200
254
|
log_debug "$US_ID MISSING **Board**: field ✗"
|
|
201
255
|
fi
|
|
202
256
|
|
|
203
|
-
done < <(echo "$CONTENT" | grep -E "
|
|
257
|
+
done < <(echo "$CONTENT" | grep -E "^#{3,4} US-([A-Z]+-)?[0-9]+:")
|
|
204
258
|
fi
|
|
205
259
|
|
|
206
260
|
# Build error message if validation fails
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Comprehensive test suite for per-us-project-validator.sh
|
|
3
|
+
# Tests that each User Story in spec.md has **Project**: (and **Board**: for 2-level)
|
|
4
|
+
#
|
|
5
|
+
# Usage: bash per-us-project-validator.test.sh
|
|
6
|
+
#
|
|
7
|
+
# v0.34.0 - Initial test suite with all US formats
|
|
8
|
+
|
|
9
|
+
set -e
|
|
10
|
+
|
|
11
|
+
GUARD="$(dirname "$0")/per-us-project-validator.sh"
|
|
12
|
+
TEST_DIR=$(mktemp -d)
|
|
13
|
+
PASS=0
|
|
14
|
+
FAIL=0
|
|
15
|
+
TOTAL=0
|
|
16
|
+
|
|
17
|
+
# Colors for output
|
|
18
|
+
RED='\033[0;31m'
|
|
19
|
+
GREEN='\033[0;32m'
|
|
20
|
+
YELLOW='\033[1;33m'
|
|
21
|
+
NC='\033[0m' # No Color
|
|
22
|
+
|
|
23
|
+
cleanup() {
|
|
24
|
+
rm -rf "$TEST_DIR"
|
|
25
|
+
}
|
|
26
|
+
trap cleanup EXIT
|
|
27
|
+
|
|
28
|
+
# Test helper: should block
|
|
29
|
+
test_should_block() {
|
|
30
|
+
local name="$1"
|
|
31
|
+
local spec_content="$2"
|
|
32
|
+
TOTAL=$((TOTAL + 1))
|
|
33
|
+
|
|
34
|
+
local file_path="$TEST_DIR/.specweave/increments/0001-test/spec.md"
|
|
35
|
+
mkdir -p "$(dirname "$file_path")"
|
|
36
|
+
|
|
37
|
+
# Build JSON input for the guard
|
|
38
|
+
# Use printf + jq -Rs to properly escape newlines in the content
|
|
39
|
+
local json_input
|
|
40
|
+
json_input=$(printf '%s' "$spec_content" | jq -Rs \
|
|
41
|
+
--arg tool_name "Write" \
|
|
42
|
+
--arg file_path "$file_path" \
|
|
43
|
+
'{tool_name: $tool_name, tool_input: {file_path: $file_path, content: .}}')
|
|
44
|
+
|
|
45
|
+
result=$(echo "$json_input" | bash "$GUARD" 2>&1; echo "EXIT:$?")
|
|
46
|
+
exit_code=$(echo "$result" | grep -o 'EXIT:[0-9]*' | cut -d: -f2)
|
|
47
|
+
|
|
48
|
+
if [[ "$exit_code" == "0" ]] && echo "$result" | grep -q '"decision".*"block"'; then
|
|
49
|
+
echo -e "${GREEN}✓ BLOCKED${NC}: $name"
|
|
50
|
+
PASS=$((PASS + 1))
|
|
51
|
+
else
|
|
52
|
+
echo -e "${RED}✗ NOT BLOCKED${NC}: $name"
|
|
53
|
+
echo " Exit code: $exit_code"
|
|
54
|
+
echo " Result: $(echo "$result" | head -3)"
|
|
55
|
+
FAIL=$((FAIL + 1))
|
|
56
|
+
fi
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Test helper: should allow
|
|
60
|
+
test_should_allow() {
|
|
61
|
+
local name="$1"
|
|
62
|
+
local spec_content="$2"
|
|
63
|
+
TOTAL=$((TOTAL + 1))
|
|
64
|
+
|
|
65
|
+
local file_path="$TEST_DIR/.specweave/increments/0001-test/spec.md"
|
|
66
|
+
mkdir -p "$(dirname "$file_path")"
|
|
67
|
+
|
|
68
|
+
# Build JSON input for the guard
|
|
69
|
+
# Use printf + jq -Rs to properly escape newlines in the content
|
|
70
|
+
local json_input
|
|
71
|
+
json_input=$(printf '%s' "$spec_content" | jq -Rs \
|
|
72
|
+
--arg tool_name "Write" \
|
|
73
|
+
--arg file_path "$file_path" \
|
|
74
|
+
'{tool_name: $tool_name, tool_input: {file_path: $file_path, content: .}}')
|
|
75
|
+
|
|
76
|
+
result=$(echo "$json_input" | bash "$GUARD" 2>&1; echo "EXIT:$?")
|
|
77
|
+
exit_code=$(echo "$result" | grep -o 'EXIT:[0-9]*' | cut -d: -f2)
|
|
78
|
+
|
|
79
|
+
if [[ "$exit_code" == "0" ]] && echo "$result" | grep -q '"decision".*"allow"'; then
|
|
80
|
+
echo -e "${GREEN}✓ ALLOWED${NC}: $name"
|
|
81
|
+
PASS=$((PASS + 1))
|
|
82
|
+
else
|
|
83
|
+
echo -e "${RED}✗ WRONGLY BLOCKED${NC}: $name"
|
|
84
|
+
echo " Exit code: $exit_code"
|
|
85
|
+
echo " Result: $(echo "$result" | head -5)"
|
|
86
|
+
FAIL=$((FAIL + 1))
|
|
87
|
+
fi
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
echo "========================================"
|
|
91
|
+
echo " PER-US PROJECT VALIDATOR - COMPREHENSIVE TESTS"
|
|
92
|
+
echo "========================================"
|
|
93
|
+
echo ""
|
|
94
|
+
echo "Test directory: $TEST_DIR"
|
|
95
|
+
echo ""
|
|
96
|
+
|
|
97
|
+
echo -e "${YELLOW}=== SIMPLE US FORMAT (### US-001:) ===${NC}"
|
|
98
|
+
|
|
99
|
+
test_should_allow "Simple US with Project field" '---
|
|
100
|
+
increment: 0001-test
|
|
101
|
+
project: my-app
|
|
102
|
+
---
|
|
103
|
+
# Feature
|
|
104
|
+
|
|
105
|
+
### US-001: Login Form
|
|
106
|
+
**Project**: frontend-app
|
|
107
|
+
|
|
108
|
+
**As a** user
|
|
109
|
+
**I want** to log in
|
|
110
|
+
**So that** I can access the app
|
|
111
|
+
|
|
112
|
+
**Acceptance Criteria**:
|
|
113
|
+
- [ ] **AC-US1-01**: Form exists
|
|
114
|
+
'
|
|
115
|
+
|
|
116
|
+
test_should_block "Simple US WITHOUT Project field" '---
|
|
117
|
+
increment: 0001-test
|
|
118
|
+
project: my-app
|
|
119
|
+
---
|
|
120
|
+
# Feature
|
|
121
|
+
|
|
122
|
+
### US-001: Login Form
|
|
123
|
+
|
|
124
|
+
**As a** user
|
|
125
|
+
**I want** to log in
|
|
126
|
+
**So that** I can access the app
|
|
127
|
+
'
|
|
128
|
+
|
|
129
|
+
echo ""
|
|
130
|
+
echo -e "${YELLOW}=== MULTI-PROJECT US FORMAT (#### US-FE-001:) ===${NC}"
|
|
131
|
+
|
|
132
|
+
test_should_allow "Multi-project US-FE-001 with Project field" '---
|
|
133
|
+
increment: 0001-test
|
|
134
|
+
project: my-app
|
|
135
|
+
multi_project: true
|
|
136
|
+
---
|
|
137
|
+
# Feature
|
|
138
|
+
|
|
139
|
+
#### US-FE-001: Login Form UI
|
|
140
|
+
**Project**: frontend-app
|
|
141
|
+
|
|
142
|
+
**As a** user
|
|
143
|
+
**I want** to see a login form
|
|
144
|
+
|
|
145
|
+
#### US-BE-001: Auth API
|
|
146
|
+
**Project**: backend-api
|
|
147
|
+
|
|
148
|
+
**As a** frontend
|
|
149
|
+
**I want** auth endpoints
|
|
150
|
+
'
|
|
151
|
+
|
|
152
|
+
test_should_block "Multi-project US-FE-001 WITHOUT Project field" '---
|
|
153
|
+
increment: 0001-test
|
|
154
|
+
project: my-app
|
|
155
|
+
multi_project: true
|
|
156
|
+
---
|
|
157
|
+
# Feature
|
|
158
|
+
|
|
159
|
+
#### US-FE-001: Login Form UI
|
|
160
|
+
|
|
161
|
+
**As a** user
|
|
162
|
+
**I want** to see a login form
|
|
163
|
+
'
|
|
164
|
+
|
|
165
|
+
echo ""
|
|
166
|
+
echo -e "${YELLOW}=== MIXED FORMAT (### with prefixes) ===${NC}"
|
|
167
|
+
|
|
168
|
+
test_should_allow "Mixed: ### US-FE-001 with Project" '---
|
|
169
|
+
increment: 0001-test
|
|
170
|
+
project: my-app
|
|
171
|
+
---
|
|
172
|
+
# Feature
|
|
173
|
+
|
|
174
|
+
### US-FE-001: Frontend Story
|
|
175
|
+
**Project**: frontend
|
|
176
|
+
|
|
177
|
+
### US-BE-001: Backend Story
|
|
178
|
+
**Project**: backend
|
|
179
|
+
'
|
|
180
|
+
|
|
181
|
+
test_should_block "Mixed: ### US-FE-001 without Project" '---
|
|
182
|
+
increment: 0001-test
|
|
183
|
+
project: my-app
|
|
184
|
+
---
|
|
185
|
+
# Feature
|
|
186
|
+
|
|
187
|
+
### US-FE-001: Frontend Story
|
|
188
|
+
|
|
189
|
+
### US-BE-001: Backend Story
|
|
190
|
+
**Project**: backend
|
|
191
|
+
'
|
|
192
|
+
|
|
193
|
+
echo ""
|
|
194
|
+
echo -e "${YELLOW}=== SHARED PROJECT PREFIX ===${NC}"
|
|
195
|
+
|
|
196
|
+
test_should_allow "US-SHARED-001 with Project" '---
|
|
197
|
+
increment: 0001-test
|
|
198
|
+
project: my-app
|
|
199
|
+
---
|
|
200
|
+
# Feature
|
|
201
|
+
|
|
202
|
+
#### US-SHARED-001: Shared Types
|
|
203
|
+
**Project**: shared-lib
|
|
204
|
+
|
|
205
|
+
**As a** developer
|
|
206
|
+
**I want** shared types
|
|
207
|
+
'
|
|
208
|
+
|
|
209
|
+
test_should_block "US-SHARED-001 without Project" '---
|
|
210
|
+
increment: 0001-test
|
|
211
|
+
project: my-app
|
|
212
|
+
---
|
|
213
|
+
# Feature
|
|
214
|
+
|
|
215
|
+
#### US-SHARED-001: Shared Types
|
|
216
|
+
|
|
217
|
+
**As a** developer
|
|
218
|
+
**I want** shared types
|
|
219
|
+
'
|
|
220
|
+
|
|
221
|
+
echo ""
|
|
222
|
+
echo -e "${YELLOW}=== MULTIPLE PROJECTS (1:1 VIOLATION - COMMA SEPARATED) ===${NC}"
|
|
223
|
+
|
|
224
|
+
test_should_block "Multiple projects (comma separated) - FORBIDDEN" '---
|
|
225
|
+
increment: 0001-test
|
|
226
|
+
project: my-app
|
|
227
|
+
---
|
|
228
|
+
# Feature
|
|
229
|
+
|
|
230
|
+
### US-001: Cross-cutting Story
|
|
231
|
+
**Project**: frontend-app, backend-api
|
|
232
|
+
|
|
233
|
+
**As a** user
|
|
234
|
+
**I want** everything
|
|
235
|
+
'
|
|
236
|
+
|
|
237
|
+
test_should_block "Multiple projects with spaces" '---
|
|
238
|
+
increment: 0001-test
|
|
239
|
+
project: my-app
|
|
240
|
+
---
|
|
241
|
+
# Feature
|
|
242
|
+
|
|
243
|
+
### US-001: Cross-cutting Story
|
|
244
|
+
**Project**: frontend-app,backend-api,shared
|
|
245
|
+
|
|
246
|
+
**As a** user
|
|
247
|
+
**I want** everything
|
|
248
|
+
'
|
|
249
|
+
|
|
250
|
+
echo ""
|
|
251
|
+
echo -e "${YELLOW}=== NO USER STORIES (should allow) ===${NC}"
|
|
252
|
+
|
|
253
|
+
test_should_allow "Spec without any User Stories" '---
|
|
254
|
+
increment: 0001-test
|
|
255
|
+
project: my-app
|
|
256
|
+
---
|
|
257
|
+
# Feature Overview
|
|
258
|
+
|
|
259
|
+
This is just an overview document.
|
|
260
|
+
|
|
261
|
+
## Goals
|
|
262
|
+
|
|
263
|
+
- Goal 1
|
|
264
|
+
- Goal 2
|
|
265
|
+
'
|
|
266
|
+
|
|
267
|
+
test_should_allow "Tasks-only spec (no US headings)" '---
|
|
268
|
+
increment: 0001-test
|
|
269
|
+
project: my-app
|
|
270
|
+
---
|
|
271
|
+
# Implementation Tasks
|
|
272
|
+
|
|
273
|
+
## T-001: Setup
|
|
274
|
+
|
|
275
|
+
Do setup stuff.
|
|
276
|
+
'
|
|
277
|
+
|
|
278
|
+
echo ""
|
|
279
|
+
echo -e "${YELLOW}=== ALL USER STORIES HAVE PROJECT ===${NC}"
|
|
280
|
+
|
|
281
|
+
test_should_allow "Multiple US, all with Project" '---
|
|
282
|
+
increment: 0001-test
|
|
283
|
+
project: my-app
|
|
284
|
+
---
|
|
285
|
+
# Feature
|
|
286
|
+
|
|
287
|
+
### US-001: First Story
|
|
288
|
+
**Project**: app-a
|
|
289
|
+
|
|
290
|
+
### US-002: Second Story
|
|
291
|
+
**Project**: app-b
|
|
292
|
+
|
|
293
|
+
### US-003: Third Story
|
|
294
|
+
**Project**: app-c
|
|
295
|
+
'
|
|
296
|
+
|
|
297
|
+
test_should_block "Multiple US, one missing Project" '---
|
|
298
|
+
increment: 0001-test
|
|
299
|
+
project: my-app
|
|
300
|
+
---
|
|
301
|
+
# Feature
|
|
302
|
+
|
|
303
|
+
### US-001: First Story
|
|
304
|
+
**Project**: app-a
|
|
305
|
+
|
|
306
|
+
### US-002: Second Story
|
|
307
|
+
|
|
308
|
+
### US-003: Third Story
|
|
309
|
+
**Project**: app-c
|
|
310
|
+
'
|
|
311
|
+
|
|
312
|
+
echo ""
|
|
313
|
+
echo -e "${YELLOW}=== NON-WRITE TOOLS ===${NC}"
|
|
314
|
+
|
|
315
|
+
# Test with Edit tool (should allow - no full content)
|
|
316
|
+
TOTAL=$((TOTAL + 1))
|
|
317
|
+
json_input=$(jq -n \
|
|
318
|
+
--arg tool_name "Edit" \
|
|
319
|
+
--arg file_path "$TEST_DIR/.specweave/increments/0001-test/spec.md" \
|
|
320
|
+
'{tool_name: $tool_name, tool_input: {file_path: $file_path}}')
|
|
321
|
+
|
|
322
|
+
result=$(echo "$json_input" | bash "$GUARD" 2>&1; echo "EXIT:$?")
|
|
323
|
+
exit_code=$(echo "$result" | grep -o 'EXIT:[0-9]*' | cut -d: -f2)
|
|
324
|
+
if [[ "$exit_code" == "0" ]]; then
|
|
325
|
+
echo -e "${GREEN}✓ ALLOWED${NC}: Edit tool (no full content validation)"
|
|
326
|
+
PASS=$((PASS + 1))
|
|
327
|
+
else
|
|
328
|
+
echo -e "${RED}✗ FAILED${NC}: Edit tool should be allowed"
|
|
329
|
+
FAIL=$((FAIL + 1))
|
|
330
|
+
fi
|
|
331
|
+
|
|
332
|
+
echo ""
|
|
333
|
+
echo -e "${YELLOW}=== BYPASS MODES ===${NC}"
|
|
334
|
+
|
|
335
|
+
# Test SPECWEAVE_FORCE_PROJECT bypass
|
|
336
|
+
TOTAL=$((TOTAL + 1))
|
|
337
|
+
spec_content='---
|
|
338
|
+
increment: 0001-test
|
|
339
|
+
---
|
|
340
|
+
### US-001: No Project
|
|
341
|
+
**As a** user
|
|
342
|
+
**I want** something
|
|
343
|
+
'
|
|
344
|
+
json_input=$(jq -n \
|
|
345
|
+
--arg tool_name "Write" \
|
|
346
|
+
--arg file_path "$TEST_DIR/.specweave/increments/0001-test/spec.md" \
|
|
347
|
+
--arg content "$spec_content" \
|
|
348
|
+
'{tool_name: $tool_name, tool_input: {file_path: $file_path, content: $content}}')
|
|
349
|
+
|
|
350
|
+
result=$(SPECWEAVE_FORCE_PROJECT=1 bash -c "echo '$json_input' | bash '$GUARD'" 2>&1; echo "EXIT:$?")
|
|
351
|
+
exit_code=$(echo "$result" | grep -o 'EXIT:[0-9]*' | cut -d: -f2)
|
|
352
|
+
if [[ "$exit_code" == "0" ]] && echo "$result" | grep -q "bypassed"; then
|
|
353
|
+
echo -e "${GREEN}✓ ALLOWED (bypass)${NC}: SPECWEAVE_FORCE_PROJECT=1"
|
|
354
|
+
PASS=$((PASS + 1))
|
|
355
|
+
else
|
|
356
|
+
echo -e "${RED}✗ BYPASS FAILED${NC}: SPECWEAVE_FORCE_PROJECT=1"
|
|
357
|
+
FAIL=$((FAIL + 1))
|
|
358
|
+
fi
|
|
359
|
+
|
|
360
|
+
# Test SPECWEAVE_LEGACY_SPEC bypass
|
|
361
|
+
TOTAL=$((TOTAL + 1))
|
|
362
|
+
result=$(SPECWEAVE_LEGACY_SPEC=1 bash -c "echo '$json_input' | bash '$GUARD'" 2>&1; echo "EXIT:$?")
|
|
363
|
+
exit_code=$(echo "$result" | grep -o 'EXIT:[0-9]*' | cut -d: -f2)
|
|
364
|
+
if [[ "$exit_code" == "0" ]] && echo "$result" | grep -q "legacy"; then
|
|
365
|
+
echo -e "${GREEN}✓ ALLOWED (bypass)${NC}: SPECWEAVE_LEGACY_SPEC=1"
|
|
366
|
+
PASS=$((PASS + 1))
|
|
367
|
+
else
|
|
368
|
+
echo -e "${RED}✗ BYPASS FAILED${NC}: SPECWEAVE_LEGACY_SPEC=1"
|
|
369
|
+
FAIL=$((FAIL + 1))
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
echo ""
|
|
373
|
+
echo -e "${YELLOW}=== FILES OUTSIDE INCREMENTS ===${NC}"
|
|
374
|
+
|
|
375
|
+
# Test file outside increments folder
|
|
376
|
+
TOTAL=$((TOTAL + 1))
|
|
377
|
+
json_input=$(jq -n \
|
|
378
|
+
--arg tool_name "Write" \
|
|
379
|
+
--arg file_path "$TEST_DIR/other/spec.md" \
|
|
380
|
+
--arg content "no project" \
|
|
381
|
+
'{tool_name: $tool_name, tool_input: {file_path: $file_path, content: $content}}')
|
|
382
|
+
|
|
383
|
+
result=$(echo "$json_input" | bash "$GUARD" 2>&1; echo "EXIT:$?")
|
|
384
|
+
exit_code=$(echo "$result" | grep -o 'EXIT:[0-9]*' | cut -d: -f2)
|
|
385
|
+
if [[ "$exit_code" == "0" ]] && echo "$result" | grep -q '"decision".*"allow"'; then
|
|
386
|
+
echo -e "${GREEN}✓ ALLOWED${NC}: spec.md outside increments folder"
|
|
387
|
+
PASS=$((PASS + 1))
|
|
388
|
+
else
|
|
389
|
+
echo -e "${RED}✗ FAILED${NC}: Files outside increments should be allowed"
|
|
390
|
+
FAIL=$((FAIL + 1))
|
|
391
|
+
fi
|
|
392
|
+
|
|
393
|
+
echo ""
|
|
394
|
+
echo "========================================"
|
|
395
|
+
echo " RESULTS"
|
|
396
|
+
echo "========================================"
|
|
397
|
+
echo -e "Total: $TOTAL"
|
|
398
|
+
echo -e "${GREEN}Passed: $PASS${NC}"
|
|
399
|
+
if [[ $FAIL -gt 0 ]]; then
|
|
400
|
+
echo -e "${RED}Failed: $FAIL${NC}"
|
|
401
|
+
exit 1
|
|
402
|
+
else
|
|
403
|
+
echo -e "Failed: 0"
|
|
404
|
+
echo ""
|
|
405
|
+
echo -e "${GREEN}ALL TESTS PASSED!${NC}"
|
|
406
|
+
fi
|