specweave 1.0.32 → 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.
Files changed (123) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CLAUDE.md +205 -148
  3. package/README.md +0 -2
  4. package/bin/specweave.js +11 -0
  5. package/dist/src/cli/commands/init.js +1 -1
  6. package/dist/src/cli/commands/init.js.map +1 -1
  7. package/dist/src/cli/commands/update-instructions.d.ts +16 -0
  8. package/dist/src/cli/commands/update-instructions.d.ts.map +1 -0
  9. package/dist/src/cli/commands/update-instructions.js +134 -0
  10. package/dist/src/cli/commands/update-instructions.js.map +1 -0
  11. package/dist/src/cli/helpers/init/directory-structure.d.ts +28 -1
  12. package/dist/src/cli/helpers/init/directory-structure.d.ts.map +1 -1
  13. package/dist/src/cli/helpers/init/directory-structure.js +163 -33
  14. package/dist/src/cli/helpers/init/directory-structure.js.map +1 -1
  15. package/dist/src/cli/helpers/init/index.d.ts +2 -1
  16. package/dist/src/cli/helpers/init/index.d.ts.map +1 -1
  17. package/dist/src/cli/helpers/init/index.js +3 -1
  18. package/dist/src/cli/helpers/init/index.js.map +1 -1
  19. package/dist/src/cli/helpers/init/instruction-file-merger.d.ts +23 -0
  20. package/dist/src/cli/helpers/init/instruction-file-merger.d.ts.map +1 -0
  21. package/dist/src/cli/helpers/init/instruction-file-merger.js +243 -0
  22. package/dist/src/cli/helpers/init/instruction-file-merger.js.map +1 -0
  23. package/dist/src/cli/helpers/init/plugin-installer.js +49 -0
  24. package/dist/src/cli/helpers/init/plugin-installer.js.map +1 -1
  25. package/dist/src/config/types.d.ts +2 -2
  26. package/dist/src/core/living-docs/external-sync-orchestrator.d.ts +26 -0
  27. package/dist/src/core/living-docs/external-sync-orchestrator.d.ts.map +1 -1
  28. package/dist/src/core/living-docs/external-sync-orchestrator.js +61 -0
  29. package/dist/src/core/living-docs/external-sync-orchestrator.js.map +1 -1
  30. package/dist/src/core/living-docs/scaffolding/index.d.ts +12 -0
  31. package/dist/src/core/living-docs/scaffolding/index.d.ts.map +1 -0
  32. package/dist/src/core/living-docs/scaffolding/index.js +15 -0
  33. package/dist/src/core/living-docs/scaffolding/index.js.map +1 -0
  34. package/dist/src/core/living-docs/scaffolding/merger.d.ts +183 -0
  35. package/dist/src/core/living-docs/scaffolding/merger.d.ts.map +1 -0
  36. package/dist/src/core/living-docs/scaffolding/merger.js +523 -0
  37. package/dist/src/core/living-docs/scaffolding/merger.js.map +1 -0
  38. package/dist/src/core/living-docs/scaffolding/scaffold.d.ts +102 -0
  39. package/dist/src/core/living-docs/scaffolding/scaffold.d.ts.map +1 -0
  40. package/dist/src/core/living-docs/scaffolding/scaffold.js +346 -0
  41. package/dist/src/core/living-docs/scaffolding/scaffold.js.map +1 -0
  42. package/dist/src/core/living-docs/scaffolding/template-engine.d.ts +108 -0
  43. package/dist/src/core/living-docs/scaffolding/template-engine.d.ts.map +1 -0
  44. package/dist/src/core/living-docs/scaffolding/template-engine.js +204 -0
  45. package/dist/src/core/living-docs/scaffolding/template-engine.js.map +1 -0
  46. package/dist/src/core/living-docs/sync-helpers/generators.d.ts +38 -2
  47. package/dist/src/core/living-docs/sync-helpers/generators.d.ts.map +1 -1
  48. package/dist/src/core/living-docs/sync-helpers/generators.js +65 -10
  49. package/dist/src/core/living-docs/sync-helpers/generators.js.map +1 -1
  50. package/dist/src/core/living-docs/sync-helpers/index.d.ts +1 -1
  51. package/dist/src/core/living-docs/sync-helpers/index.d.ts.map +1 -1
  52. package/dist/src/core/living-docs/sync-helpers/index.js.map +1 -1
  53. package/dist/src/core/tools/index.d.ts +11 -0
  54. package/dist/src/core/tools/index.d.ts.map +1 -0
  55. package/dist/src/core/tools/index.js +10 -0
  56. package/dist/src/core/tools/index.js.map +1 -0
  57. package/dist/src/core/tools/tool-event-bus.d.ts +33 -0
  58. package/dist/src/core/tools/tool-event-bus.d.ts.map +1 -0
  59. package/dist/src/core/tools/tool-event-bus.js +84 -0
  60. package/dist/src/core/tools/tool-event-bus.js.map +1 -0
  61. package/dist/src/core/tools/tool-index-builder.d.ts +27 -0
  62. package/dist/src/core/tools/tool-index-builder.d.ts.map +1 -0
  63. package/dist/src/core/tools/tool-index-builder.js +289 -0
  64. package/dist/src/core/tools/tool-index-builder.js.map +1 -0
  65. package/dist/src/core/tools/tool-registry.d.ts +51 -0
  66. package/dist/src/core/tools/tool-registry.d.ts.map +1 -0
  67. package/dist/src/core/tools/tool-registry.js +224 -0
  68. package/dist/src/core/tools/tool-registry.js.map +1 -0
  69. package/dist/src/core/tools/tool-search-engine.d.ts +22 -0
  70. package/dist/src/core/tools/tool-search-engine.d.ts.map +1 -0
  71. package/dist/src/core/tools/tool-search-engine.js +174 -0
  72. package/dist/src/core/tools/tool-search-engine.js.map +1 -0
  73. package/dist/src/core/tools/types/tool-registry-types.d.ts +112 -0
  74. package/dist/src/core/tools/types/tool-registry-types.d.ts.map +1 -0
  75. package/dist/src/core/tools/types/tool-registry-types.js +7 -0
  76. package/dist/src/core/tools/types/tool-registry-types.js.map +1 -0
  77. package/dist/src/init/compliance/types.d.ts +1 -1
  78. package/package.json +1 -1
  79. package/plugins/specweave/hooks/hooks.json +3 -13
  80. package/plugins/specweave/hooks/lib/common-setup.sh +47 -321
  81. package/plugins/specweave/hooks/lib/migrate-increment-work.sh +5 -5
  82. package/plugins/specweave/hooks/lib/sync-spec-content.sh +5 -5
  83. package/plugins/specweave/hooks/universal/dispatcher.mjs +4 -5
  84. package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +43 -296
  85. package/plugins/specweave/hooks/universal/hook-wrapper.sh +3 -1
  86. package/plugins/specweave/hooks/user-prompt-submit.sh +1 -1
  87. package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +2 -2
  88. package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +1 -10
  89. package/plugins/specweave/hooks/v2/guards/completion-guard.sh +12 -29
  90. package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +27 -29
  91. package/plugins/specweave/hooks/v2/guards/metadata-json-guard.sh +10 -4
  92. package/plugins/specweave/hooks/v2/guards/spec-validation-guard.sh +139 -0
  93. package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +4 -2
  94. package/plugins/specweave/hooks/v2/session-end.sh +3 -1
  95. package/plugins/specweave/hooks/v2/session-start.sh +3 -1
  96. package/plugins/specweave/skills/increment-planner/templates/plan.md +14 -0
  97. package/plugins/specweave/skills/update-instructions/SKILL.md +80 -0
  98. package/plugins/specweave-ado/hooks/post-living-docs-update.sh +1 -1
  99. package/plugins/specweave-mobile/README.md +55 -35
  100. package/plugins/specweave-mobile/agents/mobile-architect/AGENT.md +805 -329
  101. package/plugins/specweave-mobile/skills/expo-workflow/SKILL.md +226 -9
  102. package/plugins/specweave-mobile/skills/native-modules/SKILL.md +221 -20
  103. package/plugins/specweave-mobile/skills/performance-optimization/SKILL.md +186 -14
  104. package/plugins/specweave-mobile/skills/react-native-setup/SKILL.md +151 -54
  105. package/plugins/specweave-release/commands/npm.md +61 -17
  106. package/plugins/specweave-release/hooks/post-task-completion.sh +2 -3
  107. package/src/templates/AGENTS.md.template +34 -0
  108. package/src/templates/CLAUDE.md.template +121 -155
  109. package/plugins/specweave/hooks/config-env-separator.sh +0 -99
  110. package/plugins/specweave/hooks/github-metadata-guard.sh +0 -73
  111. package/plugins/specweave/hooks/lib/circuit-breaker.sh +0 -381
  112. package/plugins/specweave/hooks/lib/crash-prevention.sh +0 -336
  113. package/plugins/specweave/hooks/lib/logging.sh +0 -231
  114. package/plugins/specweave/hooks/lib/metrics.sh +0 -347
  115. package/plugins/specweave/hooks/lib/semaphore.sh +0 -216
  116. package/plugins/specweave/hooks/project-folder-guard.sh +0 -274
  117. package/plugins/specweave/hooks/spec-project-validator.sh +0 -210
  118. package/plugins/specweave/hooks/v2/guards/bash-file-guard.sh +0 -212
  119. package/plugins/specweave/hooks/v2/guards/bash-file-guard.test.sh +0 -163
  120. package/plugins/specweave/hooks/v2/guards/features-folder-guard.sh +0 -51
  121. package/plugins/specweave/hooks/v2/guards/increment-root-guard.sh +0 -63
  122. package/plugins/specweave/hooks/v2/guards/per-us-project-validator.sh +0 -335
  123. 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