specweave 1.0.31 → 1.0.33
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-plugin/marketplace.json +1 -1
- package/CLAUDE.md +205 -148
- package/README.md +0 -2
- package/bin/specweave.js +11 -0
- package/dist/src/cli/commands/init.js +1 -1
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/update-instructions.d.ts +16 -0
- package/dist/src/cli/commands/update-instructions.d.ts.map +1 -0
- package/dist/src/cli/commands/update-instructions.js +134 -0
- package/dist/src/cli/commands/update-instructions.js.map +1 -0
- package/dist/src/cli/helpers/init/directory-structure.d.ts +28 -1
- package/dist/src/cli/helpers/init/directory-structure.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/directory-structure.js +163 -33
- package/dist/src/cli/helpers/init/directory-structure.js.map +1 -1
- package/dist/src/cli/helpers/init/index.d.ts +2 -1
- package/dist/src/cli/helpers/init/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/index.js +3 -1
- package/dist/src/cli/helpers/init/index.js.map +1 -1
- package/dist/src/cli/helpers/init/instruction-file-merger.d.ts +23 -0
- package/dist/src/cli/helpers/init/instruction-file-merger.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/instruction-file-merger.js +243 -0
- package/dist/src/cli/helpers/init/instruction-file-merger.js.map +1 -0
- package/dist/src/cli/helpers/init/plugin-installer.js +49 -0
- package/dist/src/cli/helpers/init/plugin-installer.js.map +1 -1
- package/dist/src/config/types.d.ts +2 -2
- package/dist/src/core/living-docs/external-sync-orchestrator.d.ts +26 -0
- package/dist/src/core/living-docs/external-sync-orchestrator.d.ts.map +1 -1
- package/dist/src/core/living-docs/external-sync-orchestrator.js +61 -0
- package/dist/src/core/living-docs/external-sync-orchestrator.js.map +1 -1
- package/dist/src/core/living-docs/scaffolding/index.d.ts +12 -0
- package/dist/src/core/living-docs/scaffolding/index.d.ts.map +1 -0
- package/dist/src/core/living-docs/scaffolding/index.js +15 -0
- package/dist/src/core/living-docs/scaffolding/index.js.map +1 -0
- package/dist/src/core/living-docs/scaffolding/merger.d.ts +183 -0
- package/dist/src/core/living-docs/scaffolding/merger.d.ts.map +1 -0
- package/dist/src/core/living-docs/scaffolding/merger.js +523 -0
- package/dist/src/core/living-docs/scaffolding/merger.js.map +1 -0
- package/dist/src/core/living-docs/scaffolding/scaffold.d.ts +102 -0
- package/dist/src/core/living-docs/scaffolding/scaffold.d.ts.map +1 -0
- package/dist/src/core/living-docs/scaffolding/scaffold.js +346 -0
- package/dist/src/core/living-docs/scaffolding/scaffold.js.map +1 -0
- package/dist/src/core/living-docs/scaffolding/template-engine.d.ts +108 -0
- package/dist/src/core/living-docs/scaffolding/template-engine.d.ts.map +1 -0
- package/dist/src/core/living-docs/scaffolding/template-engine.js +204 -0
- package/dist/src/core/living-docs/scaffolding/template-engine.js.map +1 -0
- package/dist/src/core/living-docs/sync-helpers/generators.d.ts +38 -2
- package/dist/src/core/living-docs/sync-helpers/generators.d.ts.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/generators.js +65 -10
- package/dist/src/core/living-docs/sync-helpers/generators.js.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/index.d.ts +1 -1
- package/dist/src/core/living-docs/sync-helpers/index.d.ts.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/index.js.map +1 -1
- package/dist/src/core/tools/index.d.ts +11 -0
- package/dist/src/core/tools/index.d.ts.map +1 -0
- package/dist/src/core/tools/index.js +10 -0
- package/dist/src/core/tools/index.js.map +1 -0
- package/dist/src/core/tools/tool-event-bus.d.ts +33 -0
- package/dist/src/core/tools/tool-event-bus.d.ts.map +1 -0
- package/dist/src/core/tools/tool-event-bus.js +84 -0
- package/dist/src/core/tools/tool-event-bus.js.map +1 -0
- package/dist/src/core/tools/tool-index-builder.d.ts +27 -0
- package/dist/src/core/tools/tool-index-builder.d.ts.map +1 -0
- package/dist/src/core/tools/tool-index-builder.js +289 -0
- package/dist/src/core/tools/tool-index-builder.js.map +1 -0
- package/dist/src/core/tools/tool-registry.d.ts +51 -0
- package/dist/src/core/tools/tool-registry.d.ts.map +1 -0
- package/dist/src/core/tools/tool-registry.js +224 -0
- package/dist/src/core/tools/tool-registry.js.map +1 -0
- package/dist/src/core/tools/tool-search-engine.d.ts +22 -0
- package/dist/src/core/tools/tool-search-engine.d.ts.map +1 -0
- package/dist/src/core/tools/tool-search-engine.js +174 -0
- package/dist/src/core/tools/tool-search-engine.js.map +1 -0
- package/dist/src/core/tools/types/tool-registry-types.d.ts +112 -0
- package/dist/src/core/tools/types/tool-registry-types.d.ts.map +1 -0
- package/dist/src/core/tools/types/tool-registry-types.js +7 -0
- package/dist/src/core/tools/types/tool-registry-types.js.map +1 -0
- package/dist/src/init/compliance/types.d.ts +1 -1
- package/package.json +1 -1
- package/plugins/specweave/hooks/hooks.json +3 -13
- package/plugins/specweave/hooks/lib/common-setup.sh +47 -321
- package/plugins/specweave/hooks/lib/migrate-increment-work.sh +5 -5
- package/plugins/specweave/hooks/lib/sync-spec-content.sh +5 -5
- package/plugins/specweave/hooks/universal/dispatcher.mjs +4 -5
- package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +43 -296
- package/plugins/specweave/hooks/universal/hook-wrapper.sh +3 -1
- package/plugins/specweave/hooks/user-prompt-submit.sh +1 -1
- package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +2 -2
- package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +1 -10
- package/plugins/specweave/hooks/v2/guards/completion-guard.sh +12 -29
- package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +27 -29
- package/plugins/specweave/hooks/v2/guards/metadata-json-guard.sh +10 -4
- package/plugins/specweave/hooks/v2/guards/spec-validation-guard.sh +139 -0
- package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +4 -2
- package/plugins/specweave/hooks/v2/session-end.sh +3 -1
- package/plugins/specweave/hooks/v2/session-start.sh +3 -1
- package/plugins/specweave/skills/increment-planner/templates/plan.md +14 -0
- package/plugins/specweave/skills/update-instructions/SKILL.md +80 -0
- package/plugins/specweave-ado/hooks/post-living-docs-update.sh +1 -1
- package/plugins/specweave-mobile/README.md +55 -35
- package/plugins/specweave-mobile/agents/mobile-architect/AGENT.md +805 -329
- package/plugins/specweave-mobile/skills/expo-workflow/SKILL.md +226 -9
- package/plugins/specweave-mobile/skills/native-modules/SKILL.md +221 -20
- package/plugins/specweave-mobile/skills/performance-optimization/SKILL.md +186 -14
- package/plugins/specweave-mobile/skills/react-native-setup/SKILL.md +151 -54
- package/plugins/specweave-release/commands/npm.md +61 -17
- package/plugins/specweave-release/hooks/post-task-completion.sh +2 -3
- package/src/templates/AGENTS.md.template +34 -0
- package/src/templates/CLAUDE.md.template +121 -155
- package/plugins/specweave/hooks/config-env-separator.sh +0 -99
- package/plugins/specweave/hooks/github-metadata-guard.sh +0 -73
- package/plugins/specweave/hooks/lib/circuit-breaker.sh +0 -381
- package/plugins/specweave/hooks/lib/crash-prevention.sh +0 -336
- package/plugins/specweave/hooks/lib/logging.sh +0 -231
- package/plugins/specweave/hooks/lib/metrics.sh +0 -347
- package/plugins/specweave/hooks/lib/semaphore.sh +0 -216
- package/plugins/specweave/hooks/project-folder-guard.sh +0 -274
- package/plugins/specweave/hooks/spec-project-validator.sh +0 -210
- package/plugins/specweave/hooks/v2/guards/bash-file-guard.sh +0 -212
- package/plugins/specweave/hooks/v2/guards/bash-file-guard.test.sh +0 -163
- package/plugins/specweave/hooks/v2/guards/features-folder-guard.sh +0 -51
- package/plugins/specweave/hooks/v2/guards/increment-root-guard.sh +0 -63
- package/plugins/specweave/hooks/v2/guards/per-us-project-validator.sh +0 -335
- package/plugins/specweave/hooks/v2/guards/per-us-project-validator.test.sh +0 -406
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
#
|
|
3
|
-
# per-us-project-validator.sh
|
|
4
|
-
#
|
|
5
|
-
# Pre-tool-use hook that validates EACH User Story in spec.md has
|
|
6
|
-
# **Project**: (and **Board**: for 2-level) fields.
|
|
7
|
-
#
|
|
8
|
-
# This is the CRITICAL ENFORCEMENT for increment 0137.
|
|
9
|
-
#
|
|
10
|
-
# Activation:
|
|
11
|
-
# - tool_name: Write (also Edit if full content provided)
|
|
12
|
-
# - file_path matches: .specweave/increments/*/spec.md
|
|
13
|
-
#
|
|
14
|
-
# Rules:
|
|
15
|
-
# - Each US section MUST have **Project**: <value> on next few lines
|
|
16
|
-
# - For 2-level structures, each US MUST also have **Board**: <value>
|
|
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)
|
|
25
|
-
# - Fallback allowed for existing specs via SPECWEAVE_LEGACY_SPEC=1
|
|
26
|
-
#
|
|
27
|
-
# Returns exit code 1 (block) if validation fails, 0 (allow) otherwise.
|
|
28
|
-
#
|
|
29
|
-
# Bypass:
|
|
30
|
-
# - SPECWEAVE_FORCE_PROJECT=1 - Skip all project validation
|
|
31
|
-
# - SPECWEAVE_LEGACY_SPEC=1 - Allow specs without per-US project (legacy mode)
|
|
32
|
-
#
|
|
33
|
-
# v0.34.0 - Fixed to handle all US ID formats (US-001, US-FE-001, etc.)
|
|
34
|
-
|
|
35
|
-
set -e
|
|
36
|
-
|
|
37
|
-
# Source common utilities if available
|
|
38
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
39
|
-
PLUGIN_ROOT="${SCRIPT_DIR}/../.."
|
|
40
|
-
|
|
41
|
-
# Logging (silent by default)
|
|
42
|
-
log_debug() {
|
|
43
|
-
if [ "$SPECWEAVE_DEBUG" = "1" ]; then
|
|
44
|
-
echo "[per-us-project] $*" >&2
|
|
45
|
-
fi
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
# Check for force bypass
|
|
49
|
-
if [ "$SPECWEAVE_FORCE_PROJECT" = "1" ]; then
|
|
50
|
-
echo '{"decision": "allow", "message": "⚠️ Per-US project validation bypassed (SPECWEAVE_FORCE_PROJECT=1)"}'
|
|
51
|
-
exit 0
|
|
52
|
-
fi
|
|
53
|
-
|
|
54
|
-
# Check for legacy mode bypass
|
|
55
|
-
if [ "$SPECWEAVE_LEGACY_SPEC" = "1" ]; then
|
|
56
|
-
echo '{"decision": "allow", "message": "⚠️ Per-US project validation skipped (SPECWEAVE_LEGACY_SPEC=1 - legacy mode)"}'
|
|
57
|
-
exit 0
|
|
58
|
-
fi
|
|
59
|
-
|
|
60
|
-
# Read tool input from stdin
|
|
61
|
-
INPUT=$(cat)
|
|
62
|
-
|
|
63
|
-
# Extract tool name
|
|
64
|
-
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
|
|
65
|
-
|
|
66
|
-
# Only validate Write tool calls (Edit doesn't provide full content)
|
|
67
|
-
if [ "$TOOL_NAME" != "Write" ]; then
|
|
68
|
-
echo '{"decision": "allow"}'
|
|
69
|
-
exit 0
|
|
70
|
-
fi
|
|
71
|
-
|
|
72
|
-
# Extract file path
|
|
73
|
-
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
|
|
74
|
-
|
|
75
|
-
# Only validate spec.md files in increments folder
|
|
76
|
-
if [[ ! "$FILE_PATH" =~ \.specweave/increments/[0-9]{3,4}E?-[^/]+/spec\.md$ ]]; then
|
|
77
|
-
echo '{"decision": "allow"}'
|
|
78
|
-
exit 0
|
|
79
|
-
fi
|
|
80
|
-
|
|
81
|
-
log_debug "Validating per-US project fields for: $FILE_PATH"
|
|
82
|
-
|
|
83
|
-
# Extract file content
|
|
84
|
-
CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // ""')
|
|
85
|
-
|
|
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]+:'
|
|
93
|
-
TOTAL_US=$(echo "$CONTENT" | grep -cE "$US_PATTERN" || echo 0)
|
|
94
|
-
|
|
95
|
-
log_debug "Total User Stories found: $TOTAL_US"
|
|
96
|
-
|
|
97
|
-
# If no user stories, allow (might be tasks-only spec or overview)
|
|
98
|
-
if [ "$TOTAL_US" -eq 0 ]; then
|
|
99
|
-
echo '{"decision": "allow"}'
|
|
100
|
-
exit 0
|
|
101
|
-
fi
|
|
102
|
-
|
|
103
|
-
# Extract User Story sections and check for **Project**: field
|
|
104
|
-
# Strategy: For each US heading, look at next 10 lines for **Project**:
|
|
105
|
-
|
|
106
|
-
MISSING_PROJECT=()
|
|
107
|
-
MISSING_BOARD=()
|
|
108
|
-
MULTI_PROJECT=() # USs with multiple projects (comma-separated)
|
|
109
|
-
MULTI_BOARD=() # USs with multiple boards (comma-separated)
|
|
110
|
-
US_WITH_PROJECT=0
|
|
111
|
-
|
|
112
|
-
# Use grep to find all US headings (both ### and ####, with or without prefix)
|
|
113
|
-
while IFS= read -r us_line; do
|
|
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)
|
|
116
|
-
|
|
117
|
-
if [ -z "$US_ID" ]; then
|
|
118
|
-
continue
|
|
119
|
-
fi
|
|
120
|
-
|
|
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)
|
|
124
|
-
|
|
125
|
-
if [ -z "$LINE_NUM" ]; then
|
|
126
|
-
log_debug "Could not find line number for $US_ID"
|
|
127
|
-
continue
|
|
128
|
-
fi
|
|
129
|
-
|
|
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)))
|
|
150
|
-
|
|
151
|
-
# Check for **Project**: field within this US section only
|
|
152
|
-
PROJECT_LINE=$(echo "$SECTION" | grep -E '^\*\*Project\*\*:\s*\S' | head -1)
|
|
153
|
-
|
|
154
|
-
if [ -n "$PROJECT_LINE" ]; then
|
|
155
|
-
US_WITH_PROJECT=$((US_WITH_PROJECT + 1))
|
|
156
|
-
|
|
157
|
-
# Extract project value
|
|
158
|
-
PROJECT_VALUE=$(echo "$PROJECT_LINE" | sed 's/^\*\*Project\*\*:\s*//' | tr -d '[:space:]')
|
|
159
|
-
|
|
160
|
-
# Check for comma (multiple projects - FORBIDDEN)
|
|
161
|
-
if echo "$PROJECT_VALUE" | grep -q ','; then
|
|
162
|
-
MULTI_PROJECT+=("$US_ID (has: $PROJECT_VALUE)")
|
|
163
|
-
log_debug "$US_ID has MULTIPLE projects (FORBIDDEN): $PROJECT_VALUE"
|
|
164
|
-
else
|
|
165
|
-
log_debug "$US_ID has **Project**: field ✓ ($PROJECT_VALUE)"
|
|
166
|
-
fi
|
|
167
|
-
else
|
|
168
|
-
MISSING_PROJECT+=("$US_ID")
|
|
169
|
-
log_debug "$US_ID MISSING **Project**: field ✗"
|
|
170
|
-
fi
|
|
171
|
-
|
|
172
|
-
done < <(echo "$CONTENT" | grep -E "^#{3,4} US-([A-Z]+-)?[0-9]+:")
|
|
173
|
-
|
|
174
|
-
log_debug "User Stories with **Project**: $US_WITH_PROJECT / $TOTAL_US"
|
|
175
|
-
|
|
176
|
-
# Get project root for structure detection
|
|
177
|
-
PROJECT_ROOT="${FILE_PATH%%/.specweave/*}"
|
|
178
|
-
cd "$PROJECT_ROOT" 2>/dev/null || true
|
|
179
|
-
|
|
180
|
-
# Get structure level from config
|
|
181
|
-
STRUCTURE_LEVEL=1
|
|
182
|
-
if command -v specweave >/dev/null 2>&1; then
|
|
183
|
-
CONTEXT_OUTPUT=$(specweave context projects 2>/dev/null || echo '{"level": 1}')
|
|
184
|
-
STRUCTURE_LEVEL=$(echo "$CONTEXT_OUTPUT" | jq -r '.level // 1')
|
|
185
|
-
AVAILABLE_PROJECTS=$(echo "$CONTEXT_OUTPUT" | jq -r '.projects[].id // empty' | tr '\n' ', ' | sed 's/,$//')
|
|
186
|
-
else
|
|
187
|
-
# Fallback: check for 2-level indicators in config
|
|
188
|
-
if [ -f "$PROJECT_ROOT/.specweave/config.json" ]; then
|
|
189
|
-
HAS_AREA_PATH=$(jq -r '.sync.profiles | to_entries | map(select(.value.config.areaPathMapping or .value.config.areaPaths)) | length' "$PROJECT_ROOT/.specweave/config.json" 2>/dev/null || echo 0)
|
|
190
|
-
HAS_BOARD_MAPPING=$(jq -r '.sync.profiles | to_entries | map(select(.value.config.boardMapping.boards | length > 0)) | length' "$PROJECT_ROOT/.specweave/config.json" 2>/dev/null || echo 0)
|
|
191
|
-
|
|
192
|
-
if [ "$HAS_AREA_PATH" -gt 0 ] || [ "$HAS_BOARD_MAPPING" -gt 0 ]; then
|
|
193
|
-
STRUCTURE_LEVEL=2
|
|
194
|
-
fi
|
|
195
|
-
fi
|
|
196
|
-
fi
|
|
197
|
-
|
|
198
|
-
log_debug "Structure level: $STRUCTURE_LEVEL"
|
|
199
|
-
|
|
200
|
-
# For 2-level structures, also check for **Board**: field
|
|
201
|
-
if [ "$STRUCTURE_LEVEL" = "2" ]; then
|
|
202
|
-
while IFS= read -r us_line; do
|
|
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)
|
|
205
|
-
|
|
206
|
-
if [ -z "$US_ID" ]; then
|
|
207
|
-
continue
|
|
208
|
-
fi
|
|
209
|
-
|
|
210
|
-
# Get line number (works with both ### and ####)
|
|
211
|
-
LINE_NUM=$(echo "$CONTENT" | grep -nE "^#{3,4} ${US_ID}:" | head -1 | cut -d: -f1)
|
|
212
|
-
|
|
213
|
-
if [ -z "$LINE_NUM" ]; then
|
|
214
|
-
log_debug "Could not find line number for $US_ID (board check)"
|
|
215
|
-
continue
|
|
216
|
-
fi
|
|
217
|
-
|
|
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)))
|
|
237
|
-
|
|
238
|
-
# Check for **Board**: field within this US section only
|
|
239
|
-
BOARD_LINE=$(echo "$SECTION" | grep -E '^\*\*Board\*\*:\s*\S' | head -1)
|
|
240
|
-
|
|
241
|
-
if [ -n "$BOARD_LINE" ]; then
|
|
242
|
-
# Extract board value
|
|
243
|
-
BOARD_VALUE=$(echo "$BOARD_LINE" | sed 's/^\*\*Board\*\*:\s*//' | tr -d '[:space:]')
|
|
244
|
-
|
|
245
|
-
# Check for comma (multiple boards - FORBIDDEN)
|
|
246
|
-
if echo "$BOARD_VALUE" | grep -q ','; then
|
|
247
|
-
MULTI_BOARD+=("$US_ID (has: $BOARD_VALUE)")
|
|
248
|
-
log_debug "$US_ID has MULTIPLE boards (FORBIDDEN): $BOARD_VALUE"
|
|
249
|
-
else
|
|
250
|
-
log_debug "$US_ID has **Board**: field ✓ ($BOARD_VALUE)"
|
|
251
|
-
fi
|
|
252
|
-
else
|
|
253
|
-
MISSING_BOARD+=("$US_ID")
|
|
254
|
-
log_debug "$US_ID MISSING **Board**: field ✗"
|
|
255
|
-
fi
|
|
256
|
-
|
|
257
|
-
done < <(echo "$CONTENT" | grep -E "^#{3,4} US-([A-Z]+-)?[0-9]+:")
|
|
258
|
-
fi
|
|
259
|
-
|
|
260
|
-
# Build error message if validation fails
|
|
261
|
-
ERRORS=""
|
|
262
|
-
|
|
263
|
-
if [ ${#MISSING_PROJECT[@]} -gt 0 ]; then
|
|
264
|
-
MISSING_LIST=$(printf ", %s" "${MISSING_PROJECT[@]}")
|
|
265
|
-
MISSING_LIST=${MISSING_LIST:2} # Remove leading ", "
|
|
266
|
-
|
|
267
|
-
ERRORS="$ERRORS\n❌ User Stories MISSING **Project**: field:\n ${MISSING_LIST}\n"
|
|
268
|
-
ERRORS="$ERRORS\n Each User Story MUST have **Project**: <project_id> after the heading.\n"
|
|
269
|
-
ERRORS="$ERRORS\n Example:\n"
|
|
270
|
-
ERRORS="$ERRORS ### US-001: Login Form\n"
|
|
271
|
-
ERRORS="$ERRORS **Project**: frontend-app\n"
|
|
272
|
-
|
|
273
|
-
if [ -n "$AVAILABLE_PROJECTS" ]; then
|
|
274
|
-
ERRORS="$ERRORS\n Available projects: ${AVAILABLE_PROJECTS}\n"
|
|
275
|
-
fi
|
|
276
|
-
fi
|
|
277
|
-
|
|
278
|
-
if [ ${#MISSING_BOARD[@]} -gt 0 ]; then
|
|
279
|
-
MISSING_LIST=$(printf ", %s" "${MISSING_BOARD[@]}")
|
|
280
|
-
MISSING_LIST=${MISSING_LIST:2}
|
|
281
|
-
|
|
282
|
-
ERRORS="$ERRORS\n❌ User Stories MISSING **Board**: field (2-level structure):\n ${MISSING_LIST}\n"
|
|
283
|
-
ERRORS="$ERRORS\n For 2-level structures, each US MUST have **Board**: <board_id>\n"
|
|
284
|
-
ERRORS="$ERRORS\n Example:\n"
|
|
285
|
-
ERRORS="$ERRORS ### US-001: Login Form\n"
|
|
286
|
-
ERRORS="$ERRORS **Project**: acme-corp\n"
|
|
287
|
-
ERRORS="$ERRORS **Board**: mobile-team\n"
|
|
288
|
-
fi
|
|
289
|
-
|
|
290
|
-
# Check for multiple projects (1:1 violation)
|
|
291
|
-
if [ ${#MULTI_PROJECT[@]} -gt 0 ]; then
|
|
292
|
-
MULTI_LIST=$(printf ", %s" "${MULTI_PROJECT[@]}")
|
|
293
|
-
MULTI_LIST=${MULTI_LIST:2}
|
|
294
|
-
|
|
295
|
-
ERRORS="$ERRORS\n❌ User Stories with MULTIPLE projects (FORBIDDEN - 1:1 mapping required):\n ${MULTI_LIST}\n"
|
|
296
|
-
ERRORS="$ERRORS\n Each User Story MUST map to EXACTLY ONE project.\n"
|
|
297
|
-
ERRORS="$ERRORS Split cross-project features into separate USs:\n"
|
|
298
|
-
ERRORS="$ERRORS\n WRONG:\n"
|
|
299
|
-
ERRORS="$ERRORS ### US-001: OAuth Implementation\n"
|
|
300
|
-
ERRORS="$ERRORS **Project**: frontend-app, backend-api ← FORBIDDEN\n"
|
|
301
|
-
ERRORS="$ERRORS\n CORRECT:\n"
|
|
302
|
-
ERRORS="$ERRORS ### US-001: OAuth Login Form\n"
|
|
303
|
-
ERRORS="$ERRORS **Project**: frontend-app\n"
|
|
304
|
-
ERRORS="$ERRORS\n ### US-002: OAuth API Endpoints\n"
|
|
305
|
-
ERRORS="$ERRORS **Project**: backend-api\n"
|
|
306
|
-
fi
|
|
307
|
-
|
|
308
|
-
# Check for multiple boards (1:1 violation)
|
|
309
|
-
if [ ${#MULTI_BOARD[@]} -gt 0 ]; then
|
|
310
|
-
MULTI_LIST=$(printf ", %s" "${MULTI_BOARD[@]}")
|
|
311
|
-
MULTI_LIST=${MULTI_LIST:2}
|
|
312
|
-
|
|
313
|
-
ERRORS="$ERRORS\n❌ User Stories with MULTIPLE boards (FORBIDDEN - 1:1 mapping required):\n ${MULTI_LIST}\n"
|
|
314
|
-
ERRORS="$ERRORS\n Each User Story MUST map to EXACTLY ONE board.\n"
|
|
315
|
-
ERRORS="$ERRORS Split cross-board features into separate USs.\n"
|
|
316
|
-
fi
|
|
317
|
-
|
|
318
|
-
# If errors found, block the write
|
|
319
|
-
if [ -n "$ERRORS" ]; then
|
|
320
|
-
# Add bypass instructions
|
|
321
|
-
ERRORS="$ERRORS\n💡 To bypass this validation:\n"
|
|
322
|
-
ERRORS="$ERRORS - SPECWEAVE_FORCE_PROJECT=1 (skip all validation)\n"
|
|
323
|
-
ERRORS="$ERRORS - SPECWEAVE_LEGACY_SPEC=1 (allow specs without per-US project)\n"
|
|
324
|
-
|
|
325
|
-
# Escape for JSON
|
|
326
|
-
REASON=$(echo -e "$ERRORS" | jq -Rs .)
|
|
327
|
-
|
|
328
|
-
echo "{\"decision\": \"block\", \"reason\": $REASON}"
|
|
329
|
-
exit 0
|
|
330
|
-
fi
|
|
331
|
-
|
|
332
|
-
# All validations passed
|
|
333
|
-
log_debug "All $TOTAL_US User Stories have required fields ✓"
|
|
334
|
-
echo '{"decision": "allow"}'
|
|
335
|
-
exit 0
|