specweave 1.0.30 → 1.0.32

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 (79) hide show
  1. package/CLAUDE.md +140 -1235
  2. package/bin/specweave.js +23 -0
  3. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts +3 -0
  4. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
  5. package/dist/plugins/specweave-github/lib/github-client-v2.js +39 -0
  6. package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
  7. package/dist/src/cli/commands/set-sync-target.d.ts +41 -0
  8. package/dist/src/cli/commands/set-sync-target.d.ts.map +1 -0
  9. package/dist/src/cli/commands/set-sync-target.js +126 -0
  10. package/dist/src/cli/commands/set-sync-target.js.map +1 -0
  11. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.d.ts.map +1 -1
  12. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js +5 -8
  13. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js.map +1 -1
  14. package/dist/src/core/hooks/HookScanner.d.ts +32 -0
  15. package/dist/src/core/hooks/HookScanner.d.ts.map +1 -1
  16. package/dist/src/core/hooks/HookScanner.js +125 -1
  17. package/dist/src/core/hooks/HookScanner.js.map +1 -1
  18. package/dist/src/core/hooks/types.d.ts +10 -1
  19. package/dist/src/core/hooks/types.d.ts.map +1 -1
  20. package/dist/src/core/increment/metadata-manager.d.ts +67 -1
  21. package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
  22. package/dist/src/core/increment/metadata-manager.js +93 -0
  23. package/dist/src/core/increment/metadata-manager.js.map +1 -1
  24. package/dist/src/core/project/index.d.ts +21 -0
  25. package/dist/src/core/project/index.d.ts.map +1 -0
  26. package/dist/src/core/project/index.js +22 -0
  27. package/dist/src/core/project/index.js.map +1 -0
  28. package/dist/src/core/project/project-service.d.ts +122 -0
  29. package/dist/src/core/project/project-service.d.ts.map +1 -0
  30. package/dist/src/core/project/project-service.js +334 -0
  31. package/dist/src/core/project/project-service.js.map +1 -0
  32. package/dist/src/core/sync/external-tool-resolver.d.ts +171 -0
  33. package/dist/src/core/sync/external-tool-resolver.d.ts.map +1 -0
  34. package/dist/src/core/sync/external-tool-resolver.js +569 -0
  35. package/dist/src/core/sync/external-tool-resolver.js.map +1 -0
  36. package/dist/src/core/types/increment-metadata.d.ts +92 -0
  37. package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
  38. package/dist/src/hooks/processor.d.ts +7 -3
  39. package/dist/src/hooks/processor.d.ts.map +1 -1
  40. package/dist/src/hooks/processor.js +11 -5
  41. package/dist/src/hooks/processor.js.map +1 -1
  42. package/package.json +1 -1
  43. package/plugins/specweave/hooks/hooks.json +0 -69
  44. package/plugins/specweave/hooks/v2/handlers/project-bridge-handler.sh +96 -0
  45. package/plugins/specweave/hooks/v2/queue/processor.sh +13 -5
  46. package/plugins/specweave/lib/hooks/project-bridge.js +76 -0
  47. package/plugins/specweave/lib/hooks/update-tasks-md.js +0 -0
  48. package/plugins/specweave/lib/hooks/us-completion-orchestrator.js +0 -0
  49. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.d.ts +67 -1
  50. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +93 -0
  51. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
  52. package/plugins/specweave/lib/vendor/core/types/increment-metadata.d.ts +92 -0
  53. package/plugins/specweave-github/lib/github-client-v2.js +39 -0
  54. package/plugins/specweave-github/lib/github-client-v2.ts +44 -0
  55. package/plugins/specweave/hooks/docs-changed.sh +0 -87
  56. package/plugins/specweave/hooks/human-input-required.sh +0 -83
  57. package/plugins/specweave/hooks/post-edit-write-consolidated.sh +0 -428
  58. package/plugins/specweave/hooks/post-first-increment.sh +0 -61
  59. package/plugins/specweave/hooks/post-increment-change.sh +0 -103
  60. package/plugins/specweave/hooks/post-increment-completion.sh +0 -513
  61. package/plugins/specweave/hooks/post-increment-planning.sh +0 -1204
  62. package/plugins/specweave/hooks/post-increment-status-change.sh +0 -243
  63. package/plugins/specweave/hooks/post-metadata-change.sh +0 -246
  64. package/plugins/specweave/hooks/post-spec-update.sh +0 -158
  65. package/plugins/specweave/hooks/post-task-completion.sh +0 -557
  66. package/plugins/specweave/hooks/post-task-edit.sh +0 -47
  67. package/plugins/specweave/hooks/post-user-story-complete.sh +0 -230
  68. package/plugins/specweave/hooks/pre-command-deduplication.sh +0 -68
  69. package/plugins/specweave/hooks/pre-edit-write-consolidated.sh +0 -225
  70. package/plugins/specweave/hooks/pre-implementation.sh +0 -75
  71. package/plugins/specweave/hooks/pre-increment-start.sh +0 -173
  72. package/plugins/specweave/hooks/pre-task-completion-edit.sh +0 -355
  73. package/plugins/specweave/hooks/pre-task-completion.sh +0 -269
  74. package/plugins/specweave/hooks/pre-tool-use.sh +0 -137
  75. package/plugins/specweave/hooks/session-start-reconcile.sh +0 -139
  76. package/plugins/specweave/hooks/shared/bulk-operation-detector.sh +0 -167
  77. package/plugins/specweave/hooks/test-pretooluse-env.sh +0 -72
  78. package/plugins/specweave/hooks/validate-increment-completion.sh +0 -113
  79. package/plugins/specweave/lib/hooks/consolidated-sync.js +0 -288
@@ -1,1204 +0,0 @@
1
- #!/bin/bash
2
-
3
- # SpecWeave Post-Increment-Planning Hook
4
- # Runs automatically after /sw:inc completes
5
- #
6
- # PURPOSE:
7
- # Translates newly generated spec.md, plan.md, and tasks.md from target language
8
- # back to English for maintainability.
9
- #
10
- # WHY THIS MATTERS:
11
- # - User works in native language (great UX during planning)
12
- # - Framework translates to English automatically (maintainable docs)
13
- # - Cost: ~$0.01 per increment (using Haiku)
14
- #
15
- # WORKFLOW:
16
- # 1. User runs: /sw:inc "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ AI Ņ‡Đ°Ņ‚-ĐąĐžŅ‚" (in Russian)
17
- # 2. PM agent generates spec.md in Russian (natural, user-friendly)
18
- # 3. THIS HOOK fires automatically
19
- # 4. Detects non-English content
20
- # 5. Translates spec.md, plan.md, tasks.md to English
21
- # 6. Files now in English (maintainable)
22
- #
23
- # @see .specweave/increments/0006-llm-native-i18n/reports/DESIGN-POST-GENERATION-TRANSLATION.md
24
-
25
- set +e # EMERGENCY FIX: Prevents Claude Code crashes
26
-
27
- # EMERGENCY KILL SWITCH
28
- if [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]; then
29
- exit 0
30
- fi
31
-
32
- # ============================================================================
33
- # PROJECT ROOT DETECTION
34
- # ============================================================================
35
-
36
- # Find project root by searching upward for .specweave/ directory
37
- find_project_root() {
38
- local dir="$1"
39
- while [ "$dir" != "/" ]; do
40
- if [ -d "$dir/.specweave" ]; then
41
- echo "$dir"
42
- return 0
43
- fi
44
- dir="$(dirname "$dir")"
45
- done
46
- # Fallback: try current directory
47
- if [ -d "$(pwd)/.specweave" ]; then
48
- pwd
49
- else
50
- echo "$(pwd)"
51
- fi
52
- }
53
-
54
- PROJECT_ROOT="$(find_project_root "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")"
55
- cd "$PROJECT_ROOT" 2>/dev/null || true
56
-
57
- # ============================================================================
58
- # CONFIGURATION
59
- # ============================================================================
60
-
61
- # Translation settings (loaded from .specweave/config.json)
62
- TRANSLATION_ENABLED=false # Opt-in: User MUST enable in config
63
- AUTO_TRANSLATE_INCREMENT_SPECS=false # Opt-in: scope.incrementSpecs
64
- AUTO_TRANSLATE_LIVING_DOCS=false # Opt-in: scope.livingDocs
65
- TARGET_LANGUAGE="en" # Loaded from config.language (default: en)
66
- KEEP_ENGLISH_ORIGINALS=false # If true, create .en.md backups
67
-
68
- # Paths
69
- SPECWEAVE_DIR=".specweave"
70
- INCREMENTS_DIR="$SPECWEAVE_DIR/increments"
71
- LOGS_DIR="$SPECWEAVE_DIR/logs"
72
- CONFIG_FILE="$SPECWEAVE_DIR/config.json"
73
- DEBUG_LOG="$LOGS_DIR/hooks-debug.log"
74
-
75
- mkdir -p "$LOGS_DIR" 2>/dev/null || true
76
-
77
- # ============================================================================
78
- # LOGGING
79
- # ============================================================================
80
-
81
- log_debug() {
82
- echo "[$(date '+%Y-%m-%d %H:%M:%S')] [post-increment-planning] $1" >> "$DEBUG_LOG" 2>/dev/null || true
83
- }
84
-
85
- log_info() {
86
- echo "$1"
87
- log_debug "$1"
88
- }
89
-
90
- log_error() {
91
- echo "❌ $1" >&2
92
- log_debug "ERROR: $1"
93
- }
94
-
95
- # ============================================================================
96
- # CONFIGURATION LOADING
97
- # ============================================================================
98
-
99
- load_config() {
100
- if [ ! -f "$CONFIG_FILE" ]; then
101
- log_debug "No config file found, using defaults (translation disabled)"
102
- return
103
- fi
104
-
105
- # Load target language from config.language (default: en)
106
- local config_language=$(cat "$CONFIG_FILE" | grep -o '"language"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)".*/\1/' || echo "en")
107
- if [ -n "$config_language" ]; then
108
- TARGET_LANGUAGE="$config_language"
109
- log_debug "Target language: $TARGET_LANGUAGE"
110
- fi
111
-
112
- # If target language is English, no translation needed
113
- if [ "$TARGET_LANGUAGE" = "en" ]; then
114
- TRANSLATION_ENABLED=false
115
- log_debug "Target language is English, translation not needed"
116
- return
117
- fi
118
-
119
- # Check if translation is enabled in config.translation.enabled
120
- # Search within "translation" block for "enabled"
121
- local translation_section=$(cat "$CONFIG_FILE" | awk '/"translation"[[:space:]]*:/,/^[[:space:]]*}/' 2>/dev/null)
122
- local translation_enabled=$(echo "$translation_section" | grep -o '"enabled"[[:space:]]*:[[:space:]]*\(true\|false\)' | head -1 | grep -o '\(true\|false\)' || echo "false")
123
-
124
- if [ "$translation_enabled" = "true" ]; then
125
- TRANSLATION_ENABLED=true
126
- log_debug "Translation enabled in config"
127
- else
128
- TRANSLATION_ENABLED=false
129
- log_debug "Translation disabled in config (opt-in required)"
130
- fi
131
-
132
- # Check translation scope (within translation.scope block)
133
- local scope_section=$(echo "$translation_section" | awk '/"scope"[[:space:]]*:/,/^[[:space:]]*}/' 2>/dev/null)
134
-
135
- # scope.incrementSpecs - translate spec.md, plan.md, tasks.md
136
- local scope_increment=$(echo "$scope_section" | grep -o '"incrementSpecs"[[:space:]]*:[[:space:]]*\(true\|false\)' | grep -o '\(true\|false\)' || echo "false")
137
- if [ "$scope_increment" = "true" ]; then
138
- AUTO_TRANSLATE_INCREMENT_SPECS=true
139
- log_debug "Auto-translate increment specs enabled"
140
- fi
141
-
142
- # scope.livingDocs - translate living docs on update
143
- local scope_living=$(echo "$scope_section" | grep -o '"livingDocs"[[:space:]]*:[[:space:]]*\(true\|false\)' | grep -o '\(true\|false\)' || echo "false")
144
- if [ "$scope_living" = "true" ]; then
145
- AUTO_TRANSLATE_LIVING_DOCS=true
146
- log_debug "Auto-translate living docs enabled"
147
- fi
148
-
149
- # keepEnglishOriginals - create .en.md backups
150
- local keep_originals=$(echo "$translation_section" | grep -o '"keepEnglishOriginals"[[:space:]]*:[[:space:]]*\(true\|false\)' | grep -o '\(true\|false\)' || echo "false")
151
- if [ "$keep_originals" = "true" ]; then
152
- KEEP_ENGLISH_ORIGINALS=true
153
- log_debug "Keep English originals enabled"
154
- fi
155
- }
156
-
157
- # ============================================================================
158
- # INCREMENT DETECTION
159
- # ============================================================================
160
-
161
- get_latest_increment() {
162
- # Find the most recently modified increment directory (excluding _backlog)
163
- local latest=$(find "$INCREMENTS_DIR" -maxdepth 1 -type d -name "[0-9][0-9][0-9][0-9]-*" ! -name "_backlog" -exec stat -f "%m %N" {} \; 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2-)
164
-
165
- if [ -z "$latest" ]; then
166
- log_error "No increment directory found"
167
- return 1
168
- fi
169
-
170
- echo "$latest"
171
- }
172
-
173
- # ============================================================================
174
- # LANGUAGE DETECTION
175
- # ============================================================================
176
-
177
- detect_file_language() {
178
- local file_path="$1"
179
-
180
- if [ ! -f "$file_path" ]; then
181
- echo "en" # Default to English if file doesn't exist
182
- return
183
- fi
184
-
185
- # Count non-ASCII characters (Cyrillic, Chinese, etc.)
186
- local total_chars=$(wc -c < "$file_path" | tr -d ' ')
187
- local non_ascii_chars=$(LC_ALL=C grep -o '[^ -~]' "$file_path" 2>/dev/null | wc -l | tr -d ' ')
188
-
189
- # If >10% non-ASCII, assume non-English
190
- if [ "$total_chars" -gt 0 ]; then
191
- local ratio=$((non_ascii_chars * 100 / total_chars))
192
- if [ "$ratio" -gt 10 ]; then
193
- echo "non-en"
194
- return
195
- fi
196
- fi
197
-
198
- echo "en"
199
- }
200
-
201
- # ============================================================================
202
- # TRANSLATION EXECUTION
203
- # ============================================================================
204
-
205
- translate_file() {
206
- local file_path="$1"
207
- local file_name=$(basename "$file_path")
208
- local target_lang="${TARGET_LANGUAGE:-en}"
209
-
210
- log_info " 📄 Translating $file_name to $target_lang..."
211
-
212
- # Call the translate-file.ts script
213
- # Uses TARGET_LANGUAGE from config (default: en)
214
-
215
- if [ -f "${CLAUDE_PLUGIN_ROOT}/lib/hooks/translate-file.js" ]; then
216
- # Production: Use compiled TypeScript
217
- node "${CLAUDE_PLUGIN_ROOT}/lib/hooks/translate-file.js" "$file_path" --target-lang "$target_lang" --verbose 2>&1 | while read -r line; do
218
- echo " $line"
219
- done
220
-
221
- if [ ${PIPESTATUS[0]} -eq 0 ]; then
222
- log_info " ✅ $file_name translated to $target_lang"
223
- return 0
224
- else
225
- log_error " âš ī¸ Translation failed for $file_name"
226
- return 1
227
- fi
228
- else
229
- # Development/Testing: Just mark the file
230
- log_info " â„šī¸ Translation script not compiled (run 'npm run build')"
231
- log_info " â„šī¸ In production, $file_name would be translated to $target_lang"
232
- return 0
233
- fi
234
- }
235
-
236
- # ============================================================================
237
- # LIVING DOCS TRANSLATION
238
- # ============================================================================
239
-
240
- translate_living_docs_specs() {
241
- local increment_id="$1"
242
-
243
- log_debug "Checking for newly created living docs specs for increment $increment_id..."
244
-
245
- # Directories to check for living docs
246
- local specs_dir="$SPECWEAVE_DIR/docs/internal/specs"
247
- local strategy_dir="$SPECWEAVE_DIR/docs/internal/strategy"
248
- local architecture_dir="$SPECWEAVE_DIR/docs/internal/architecture"
249
-
250
- local translated_count=0
251
- local total_checked=0
252
-
253
- # Find living docs files created in last 5 minutes (recently created by PM agent)
254
- # macOS and Linux compatible find command
255
- for dir in "$specs_dir" "$strategy_dir" "$architecture_dir"; do
256
- if [ ! -d "$dir" ]; then
257
- log_debug "Directory does not exist: $dir"
258
- continue
259
- fi
260
-
261
- # Find markdown files modified in last 5 minutes
262
- # Using -mmin -5 (last 5 minutes) to catch files created during increment planning
263
- local files=$(find "$dir" -type f -name "*.md" -mmin -5 2>/dev/null)
264
-
265
- if [ -z "$files" ]; then
266
- log_debug "No recently modified files in $dir"
267
- continue
268
- fi
269
-
270
- while IFS= read -r file; do
271
- # Skip empty lines
272
- [ -z "$file" ] && continue
273
-
274
- ((total_checked++))
275
-
276
- # Skip legacy folder
277
- if [[ "$file" == *"/legacy/"* ]]; then
278
- log_debug "Skipping legacy file: $file"
279
- continue
280
- fi
281
-
282
- # Detect language
283
- local file_lang=$(detect_file_language "$file")
284
-
285
- if [ "$file_lang" = "non-en" ]; then
286
- local basename_file=$(basename "$file")
287
- log_info " 📄 Living docs detected: $basename_file"
288
-
289
- if translate_file "$file"; then
290
- ((translated_count++))
291
- log_debug "Successfully translated living docs: $file"
292
- else
293
- log_debug "Failed to translate living docs: $file"
294
- fi
295
- else
296
- log_debug "File already in English: $file"
297
- fi
298
- done <<< "$files"
299
- done
300
-
301
- log_debug "Living docs check complete: $translated_count/$total_checked files translated"
302
-
303
- if [ "$translated_count" -gt 0 ]; then
304
- log_info ""
305
- log_info " ✅ Translated $translated_count living docs file(s) to English"
306
- else
307
- log_debug "No living docs files needed translation"
308
- fi
309
-
310
- return 0
311
- }
312
-
313
- # ============================================================================
314
- # GITHUB ISSUE CREATION
315
- # ============================================================================
316
-
317
- create_github_issue() {
318
- local increment_id="$1"
319
- local increment_dir="$2"
320
-
321
- local spec_file="$increment_dir/spec.md"
322
- local tasks_file="$increment_dir/tasks.md"
323
- local metadata_file="$increment_dir/metadata.json"
324
-
325
- # Validate files exist
326
- if [ ! -f "$spec_file" ]; then
327
- log_error "spec.md not found in $increment_dir"
328
- return 1
329
- fi
330
-
331
- if [ ! -f "$tasks_file" ]; then
332
- log_error "tasks.md not found in $increment_dir"
333
- return 1
334
- fi
335
-
336
- # Extract title from spec.md frontmatter
337
- local title=$(awk '/^---$/,/^---$/ {if (/^title:/) {sub(/^title:[[:space:]]*"?/, ""); sub(/"?[[:space:]]*$/, ""); print; exit}}' "$spec_file")
338
-
339
- if [ -z "$title" ]; then
340
- # Fallback: extract from first heading
341
- title=$(grep -m 1 '^# ' "$spec_file" | sed 's/^# //' | sed 's/Increment [0-9]*: //')
342
- fi
343
-
344
- if [ -z "$title" ]; then
345
- title="$increment_id"
346
- fi
347
-
348
- # Extract quick overview from spec.md (try multiple sections)
349
- local overview=$(awk '/## Quick Overview/,/^---$|^##/ {if (/## Quick Overview/) next; if (/^---$/ || /^##/) exit; print}' "$spec_file" | grep -v '^$' | head -5)
350
-
351
- # Fallback: try Summary section
352
- if [ -z "$overview" ]; then
353
- overview=$(awk '/## Summary/,/^---$|^##/ {if (/## Summary/) next; if (/^---$/ || /^##/) exit; print}' "$spec_file" | grep -v '^$' | head -5)
354
- fi
355
-
356
- # Fallback: extract first paragraph after frontmatter
357
- if [ -z "$overview" ]; then
358
- overview=$(awk '/^---$/{count++; if(count==2){flag=1; next}} flag && /^[^#]/ && NF>0 {print; count2++; if(count2>=5) exit}' "$spec_file")
359
- fi
360
-
361
- # Final fallback: use a default message
362
- if [ -z "$overview" ]; then
363
- overview="See spec.md for details."
364
- fi
365
-
366
- # Extract total tasks count
367
- local total_tasks=$(awk '/^total_tasks:/ {print $2; exit}' "$tasks_file")
368
-
369
- # Fallback: count tasks if total_tasks not in frontmatter
370
- if [ -z "$total_tasks" ] || [ "$total_tasks" = "0" ]; then
371
- total_tasks=$(grep -c '^### T-[0-9]*:' "$tasks_file" 2>/dev/null || echo "0")
372
- fi
373
-
374
- # Generate task checklist from tasks.md
375
- local task_list=$(awk '
376
- /^### T-[0-9]+:/ {
377
- task_id = $2
378
- task_title = substr($0, index($0, $3))
379
- in_task = 1
380
- next
381
- }
382
- in_task && /^\*\*Status\*\*:/ {
383
- if (/\[x\]/) {
384
- print "- [x] " task_id " " task_title
385
- } else {
386
- print "- [ ] " task_id " " task_title
387
- }
388
- in_task = 0
389
- }
390
- ' "$tasks_file")
391
-
392
- # Detect repository from profile-based config
393
- local repo=""
394
- local owner=""
395
- local repo_name=""
396
-
397
- # First, check if increment has a specific githubProfile in metadata
398
- local metadata_file="$2/metadata.json"
399
- local profile_id=""
400
-
401
- if [ -f "$metadata_file" ]; then
402
- profile_id=$(cat "$metadata_file" 2>/dev/null | grep -o '"githubProfile"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/')
403
- log_debug "Found githubProfile in metadata: $profile_id"
404
- fi
405
-
406
- # If no profile in metadata, use defaultProfile from config
407
- if [ -z "$profile_id" ]; then
408
- profile_id=$(cat "$CONFIG_FILE" 2>/dev/null | grep -o '"defaultProfile"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/')
409
- log_debug "Using defaultProfile from config: $profile_id"
410
- fi
411
-
412
- if [ -n "$profile_id" ]; then
413
- # Extract owner and repo from the profile
414
- local profile_section=$(cat "$CONFIG_FILE" 2>/dev/null | awk "/$profile_id/,/^[[:space:]]*\}/{print}")
415
- owner=$(echo "$profile_section" | grep -o '"owner"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/')
416
- repo_name=$(echo "$profile_section" | grep -o '"repo"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/')
417
-
418
- if [ -n "$owner" ] && [ -n "$repo_name" ]; then
419
- repo="$owner/$repo_name"
420
- log_debug "Using repo from profile: $repo"
421
- fi
422
- fi
423
-
424
- # Fallback to git remote detection if no profile config found
425
- if [ -z "$repo" ]; then
426
- repo=$(git remote get-url origin 2>/dev/null | sed 's/.*github\.com[:/]\(.*\)\.git/\1/' | sed 's/.*github\.com[:/]\(.*\)/\1/')
427
- log_debug "Fallback to git remote: $repo"
428
- fi
429
-
430
- # Legacy fallback to old config format
431
- if [ -z "$repo" ]; then
432
- repo=$(cat "$CONFIG_FILE" 2>/dev/null | grep -A 5 '"sync"' | grep -o '"repo"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/')
433
- log_debug "Legacy fallback to old config: $repo"
434
- fi
435
-
436
- if [ -z "$repo" ]; then
437
- log_error "Could not detect GitHub repository from profile or git remote"
438
- return 1
439
- fi
440
-
441
- # Extract creation date from metadata.json and format as FS-YY-MM-DD
442
- local issue_prefix="FS-UNKNOWN"
443
- if [ -f "$metadata_file" ]; then
444
- # Extract created date (format: "2025-11-12T12:46:00Z")
445
- local created_date=$(cat "$metadata_file" 2>/dev/null | grep -o '"created"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/')
446
- if [ -n "$created_date" ]; then
447
- # Extract YY-MM-DD from date (e.g., "2025-11-12" -> "25-11-12")
448
- local year=$(echo "$created_date" | cut -d'-' -f1 | cut -c3-4) # "2025" -> "25"
449
- local month=$(echo "$created_date" | cut -d'-' -f2) # "11"
450
- local day=$(echo "$created_date" | cut -d'-' -f3 | cut -d'T' -f1) # "12T..." -> "12"
451
- issue_prefix="FS-${year}-${month}-${day}"
452
- log_debug "Using date-based prefix from metadata: $issue_prefix"
453
- else
454
- log_debug "No created date in metadata, using fallback"
455
- fi
456
- else
457
- log_debug "No metadata.json found, using fallback prefix"
458
- fi
459
-
460
- log_debug "Creating issue for repo: $repo"
461
- log_debug "Title: [$issue_prefix] $title"
462
-
463
- # Generate issue body
464
- local issue_body=$(cat <<EOF
465
- # [$issue_prefix] $title
466
-
467
- **Status**: Planning → Implementation
468
- **Priority**: P1
469
- **Increment**: $increment_id
470
-
471
- ## Summary
472
-
473
- $overview
474
-
475
- ## Tasks
476
-
477
- Progress: 0/$total_tasks tasks (0%)
478
-
479
- $task_list
480
-
481
- ## Links
482
-
483
- - **Spec**: [\`spec.md\`](../../tree/develop/.specweave/increments/$increment_id/spec.md)
484
- - **Plan**: [\`plan.md\`](../../tree/develop/.specweave/increments/$increment_id/plan.md)
485
- - **Tasks**: [\`tasks.md\`](../../tree/develop/.specweave/increments/$increment_id/tasks.md)
486
-
487
- ---
488
-
489
- 🤖 Auto-created by SpecWeave | Updates automatically on task completion
490
- EOF
491
- )
492
-
493
- # Create temporary file for issue body
494
- local temp_body=$(mktemp)
495
- echo "$issue_body" > "$temp_body"
496
-
497
- # Create GitHub issue with FULL DUPLICATE PROTECTION
498
- log_debug "Creating issue with DuplicateDetector (global protection)..."
499
-
500
- # Call Node.js wrapper script with DuplicateDetector
501
- local node_output=$(node scripts/create-github-issue-with-protection.js \
502
- --title "[$issue_prefix] $title" \
503
- --body "$issue_body" \
504
- --pattern "[$issue_prefix]" \
505
- --labels "specweave,increment" \
506
- --repo "$repo" \
507
- 2>&1)
508
-
509
- local node_status=$?
510
-
511
- # Clean up temp file
512
- rm -f "$temp_body"
513
-
514
- if [ $node_status -ne 0 ]; then
515
- log_error "DuplicateDetector failed: $node_output"
516
- return 1
517
- fi
518
-
519
- # Parse JSON output using jq (if available) or fallback to grep
520
- local issue_number=""
521
- local issue_url=""
522
- local duplicates_found=0
523
- local duplicates_closed=0
524
- local was_reused="false"
525
-
526
- if command -v jq >/dev/null 2>&1; then
527
- issue_number=$(echo "$node_output" | jq -r '.issue.number')
528
- issue_url=$(echo "$node_output" | jq -r '.issue.url')
529
- duplicates_found=$(echo "$node_output" | jq -r '.duplicatesFound // 0')
530
- duplicates_closed=$(echo "$node_output" | jq -r '.duplicatesClosed // 0')
531
- was_reused=$(echo "$node_output" | jq -r '.wasReused // false')
532
- else
533
- # Fallback: grep-based parsing
534
- issue_number=$(echo "$node_output" | grep -o '"number"[[:space:]]*:[[:space:]]*[0-9]*' | grep -o '[0-9]*')
535
- issue_url=$(echo "$node_output" | grep -o '"url"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/')
536
- fi
537
-
538
- if [ -z "$issue_number" ]; then
539
- log_error "Could not extract issue number from DuplicateDetector output"
540
- log_debug "Output was: $node_output"
541
- return 1
542
- fi
543
-
544
- # Log results with duplicate detection info
545
- if [ "$was_reused" = "true" ]; then
546
- log_info " â™ģī¸ Using existing issue #$issue_number (duplicate prevention)"
547
- else
548
- log_info " 📝 Issue #$issue_number created"
549
- fi
550
- log_info " 🔗 $issue_url"
551
-
552
- if [ "$duplicates_found" -gt 0 ]; then
553
- log_info " đŸ›Ąī¸ Duplicates detected: $duplicates_found (auto-closed: $duplicates_closed)"
554
- fi
555
-
556
- # Create or update metadata.json
557
- local current_timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
558
-
559
- if [ -f "$metadata_file" ]; then
560
- # Update existing metadata.json with github section
561
- if command -v jq >/dev/null 2>&1; then
562
- local temp_metadata=$(mktemp)
563
- local jq_update=". + {\"github\": {\"issue\": $issue_number, \"url\": \"$issue_url\", \"synced\": \"$current_timestamp\"}"
564
-
565
- # Add profile ID if we have it
566
- if [ -n "$profile_id" ]; then
567
- jq_update="$jq_update, \"githubProfile\": \"$profile_id\""
568
- jq_update=". + {\"github\": {\"issue\": $issue_number, \"url\": \"$issue_url\", \"synced\": \"$current_timestamp\"}, \"githubProfile\": \"$profile_id\"}"
569
- fi
570
-
571
- jq "$jq_update" "$metadata_file" > "$temp_metadata"
572
- mv "$temp_metadata" "$metadata_file"
573
- else
574
- # Fallback: manual JSON construction (less reliable)
575
- log_debug "jq not found, using manual metadata update"
576
- cat >> "$metadata_file" <<EOF_META
577
- {
578
- "id": "$increment_id",
579
- "status": "active",
580
- "type": "feature",
581
- "created": "$current_timestamp",
582
- "githubProfile": "$profile_id",
583
- "github": {
584
- "issue": $issue_number,
585
- "url": "$issue_url",
586
- "synced": "$current_timestamp"
587
- }
588
- }
589
- EOF_META
590
- fi
591
- else
592
- # Create new metadata.json
593
- cat > "$metadata_file" <<EOF_META
594
- {
595
- "id": "$increment_id",
596
- "status": "active",
597
- "type": "feature",
598
- "created": "$current_timestamp",
599
- "github": {
600
- "issue": $issue_number,
601
- "url": "$issue_url",
602
- "synced": "$current_timestamp"
603
- }
604
- }
605
- EOF_META
606
- fi
607
-
608
- log_info " ✅ metadata.json updated"
609
- log_debug "Metadata saved to $metadata_file"
610
-
611
- return 0
612
- }
613
-
614
- # ============================================================================
615
- # MAIN LOGIC
616
- # ============================================================================
617
-
618
- main() {
619
- log_debug "=== POST-INCREMENT-PLANNING HOOK START ==="
620
-
621
- # 1. Load configuration
622
- load_config
623
-
624
- # Check if translation is needed (non-English target + enabled)
625
- if [ "$TRANSLATION_ENABLED" = "false" ]; then
626
- log_debug "Translation disabled or target is English, skipping translation"
627
- # Continue with other hook tasks (GitHub sync, living docs, etc.)
628
- fi
629
-
630
- # Log translation configuration
631
- if [ "$TRANSLATION_ENABLED" = "true" ]; then
632
- log_info ""
633
- log_info "🌐 Translation Configuration:"
634
- log_info " Target language: $TARGET_LANGUAGE"
635
- log_info " Increment specs: $AUTO_TRANSLATE_INCREMENT_SPECS"
636
- log_info " Living docs: $AUTO_TRANSLATE_LIVING_DOCS"
637
- log_info " Keep English originals: $KEEP_ENGLISH_ORIGINALS"
638
- fi
639
-
640
- # 2. Get latest increment directory
641
- local increment_dir=$(get_latest_increment)
642
-
643
- if [ $? -ne 0 ] || [ -z "$increment_dir" ]; then
644
- log_error "Could not find latest increment directory"
645
- cat <<EOF
646
- {
647
- "continue": true,
648
- "error": "No increment directory found"
649
- }
650
- EOF
651
- exit 0
652
- fi
653
-
654
- local increment_id=$(basename "$increment_dir")
655
- log_debug "Latest increment: $increment_id"
656
-
657
- # 3. Check if files need translation
658
- local spec_file="$increment_dir/spec.md"
659
- local plan_file="$increment_dir/plan.md"
660
- local tasks_file="$increment_dir/tasks.md"
661
-
662
- local needs_translation=false
663
- local files_to_translate=()
664
-
665
- # Detect language of each file
666
- if [ -f "$spec_file" ]; then
667
- local spec_lang=$(detect_file_language "$spec_file")
668
- if [ "$spec_lang" = "non-en" ]; then
669
- needs_translation=true
670
- files_to_translate+=("$spec_file")
671
- fi
672
- fi
673
-
674
- if [ -f "$plan_file" ]; then
675
- local plan_lang=$(detect_file_language "$plan_file")
676
- if [ "$plan_lang" = "non-en" ]; then
677
- needs_translation=true
678
- files_to_translate+=("$plan_file")
679
- fi
680
- fi
681
-
682
- if [ -f "$tasks_file" ]; then
683
- local tasks_lang=$(detect_file_language "$tasks_file")
684
- if [ "$tasks_lang" = "non-en" ]; then
685
- needs_translation=true
686
- files_to_translate+=("$tasks_file")
687
- fi
688
- fi
689
-
690
- # 4. Translate increment files (if enabled in scope and non-English content detected)
691
- local increment_success_count=0
692
- local increment_total_count=${#files_to_translate[@]}
693
-
694
- if [ "$TRANSLATION_ENABLED" = "true" ] && [ "$AUTO_TRANSLATE_INCREMENT_SPECS" = "true" ] && [ "$needs_translation" = "true" ] && [ ${#files_to_translate[@]} -gt 0 ]; then
695
- # 5. Perform translation of increment files TO user's language
696
- log_info ""
697
- log_info "🌐 Translating increment $increment_id to $TARGET_LANGUAGE..."
698
- log_info ""
699
-
700
- # Create English backups if configured
701
- if [ "$KEEP_ENGLISH_ORIGINALS" = "true" ]; then
702
- for file in "${files_to_translate[@]}"; do
703
- local backup="${file%.md}.en.md"
704
- cp "$file" "$backup" 2>/dev/null || true
705
- log_debug "Created English backup: $backup"
706
- done
707
- fi
708
-
709
- for file in "${files_to_translate[@]}"; do
710
- if translate_file "$file"; then
711
- ((increment_success_count++))
712
- fi
713
- done
714
-
715
- log_info ""
716
- if [ "$increment_success_count" -eq "$increment_total_count" ]; then
717
- log_info "✅ Increment files translated to $TARGET_LANGUAGE! ($increment_total_count file(s))"
718
- else
719
- log_error "Translation completed with errors: $increment_success_count/$increment_total_count files"
720
- fi
721
- elif [ "$TRANSLATION_ENABLED" = "true" ] && [ "$AUTO_TRANSLATE_INCREMENT_SPECS" = "false" ]; then
722
- log_debug "Increment spec translation disabled in scope.incrementSpecs"
723
- elif [ "$needs_translation" = "false" ]; then
724
- log_debug "All increment files already in target language"
725
- fi
726
-
727
- # 6. Translate living docs specs (if enabled in scope)
728
- if [ "$TRANSLATION_ENABLED" = "true" ] && [ "$AUTO_TRANSLATE_LIVING_DOCS" = "true" ]; then
729
- log_info ""
730
- log_info "🌐 Checking living docs for translation to $TARGET_LANGUAGE..."
731
- translate_living_docs_specs "$increment_id"
732
- else
733
- log_debug "Living docs translation disabled (scope.livingDocs=$AUTO_TRANSLATE_LIVING_DOCS)"
734
- fi
735
-
736
- # ============================================================================
737
- # INCREMENT-LEVEL GITHUB ISSUE CREATION (DEPRECATED v0.24.0+)
738
- # ============================================================================
739
- #
740
- # âš ī¸ DEPRECATED: SpecWeave now syncs ONLY at User Story level.
741
- #
742
- # Feature/Increment-level issues like "[FS-047] Title" are NO LONGER created.
743
- # Instead, use:
744
- # /sw-github:sync FS-047
745
- #
746
- # This creates PROPER User Story-level issues:
747
- # [FS-047][US-001] User Story Title
748
- # [FS-047][US-002] User Story Title
749
- # etc.
750
- #
751
- # @see .specweave/increments/0047-us-task-linkage/reports/GITHUB-TITLE-FORMAT-FIX-PLAN.md
752
- # ============================================================================
753
-
754
- # 7. Increment-level GitHub issue creation (DEPRECATED - disabled by default)
755
- log_info ""
756
- log_info "🔗 Checking GitHub issue auto-creation..."
757
-
758
- # Check if upsert permission is enabled in config (v0.24.0+: Three-Permission Architecture)
759
- local can_upsert=$(cat "$CONFIG_FILE" 2>/dev/null | grep -A 5 '"sync"' | grep -A 5 '"settings"' | grep -o '"canUpsertInternalItems"[[:space:]]*:[[:space:]]*\(true\|false\)' | grep -o '\(true\|false\)' || echo "false")
760
-
761
- # DEPRECATED: Override to false unless explicitly enabled via env var
762
- # This entire section is deprecated - use /sw-github:sync for User Story-level issues
763
- if [ "$SPECWEAVE_ENABLE_INCREMENT_GITHUB_SYNC" != "true" ]; then
764
- can_upsert="false"
765
- log_debug "Increment GitHub sync disabled (use /sw-github:sync for User Story-level issues)"
766
- fi
767
-
768
- log_debug "Can upsert internal items (deprecated increment sync): $can_upsert"
769
- local auto_create="$can_upsert" # Backward compatibility alias
770
-
771
- if [ "$auto_create" = "true" ]; then
772
- log_info " âš ī¸ WARNING: Increment-level sync is DEPRECATED"
773
- log_info " ✅ Use /sw-github:sync for [FS-XXX][US-YYY] format"
774
- log_info " đŸ“Ļ Auto-create enabled, checking for GitHub CLI..."
775
-
776
- # ============================================================================
777
- # DUPLICATE DETECTION (v0.14.1+)
778
- # ============================================================================
779
- # Check if GitHub issue already exists in metadata.json
780
- # If exists, skip creation (idempotent operation)
781
-
782
- local metadata_file="$increment_dir/metadata.json"
783
- local existing_issue=""
784
-
785
- if [ -f "$metadata_file" ]; then
786
- # Extract existing GitHub issue number from metadata
787
- existing_issue=$(cat "$metadata_file" 2>/dev/null | \
788
- grep -o '"github"[[:space:]]*:[[:space:]]*{[^}]*"issue"[[:space:]]*:[[:space:]]*[0-9]*' | \
789
- grep -o '[0-9]*$')
790
-
791
- if [ -n "$existing_issue" ]; then
792
- log_info " ✅ GitHub issue already exists: #$existing_issue"
793
- log_info " â­ī¸ Skipping creation (idempotent)"
794
- log_debug "Metadata already contains github.issue = $existing_issue"
795
-
796
- # Extract URL if available
797
- local existing_url=$(cat "$metadata_file" 2>/dev/null | \
798
- grep -o '"url"[[:space:]]*:[[:space:]]*"[^"]*"' | \
799
- sed 's/.*"\([^"]*\)".*/\1/')
800
-
801
- if [ -n "$existing_url" ]; then
802
- log_info " 🔗 $existing_url"
803
- fi
804
- fi
805
- fi
806
-
807
- # Only create if no existing issue found
808
- if [ -z "$existing_issue" ]; then
809
- # Check if gh CLI is available
810
- if ! command -v gh >/dev/null 2>&1; then
811
- log_info " âš ī¸ GitHub CLI (gh) not found, skipping issue creation"
812
- log_debug "Install: https://cli.github.com/"
813
- else
814
- log_info " ✓ GitHub CLI found"
815
- log_info ""
816
- log_info "🚀 Creating GitHub issue for $increment_id..."
817
-
818
- # Create issue (non-blocking)
819
- if create_github_issue "$increment_id" "$increment_dir"; then
820
- log_info " ✅ GitHub issue created successfully"
821
- else
822
- log_info " âš ī¸ GitHub issue creation failed (non-blocking)"
823
- log_debug "Issue creation failed, but continuing execution"
824
- fi
825
- fi
826
- fi
827
- else
828
- log_debug "Auto-create disabled in config"
829
- fi
830
-
831
- # ============================================================================
832
- # FALLBACK METADATA CREATION (v0.14.0+)
833
- # ============================================================================
834
- # CRITICAL: Ensure metadata.json exists even if GitHub integration failed
835
- # This prevents silent failures where increment appears complete but lacks metadata
836
-
837
- log_info ""
838
- log_info "🔍 Validating metadata.json existence..."
839
-
840
- local metadata_file="$increment_dir/metadata.json"
841
-
842
- if [ ! -f "$metadata_file" ]; then
843
- log_info " âš ī¸ metadata.json not found (hook may have failed)"
844
- log_info " 📝 Creating minimal metadata as fallback..."
845
-
846
- local current_timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
847
-
848
- # Extract type from spec.md frontmatter (if available)
849
- local increment_type="feature"
850
- if [ -f "$spec_file" ]; then
851
- local extracted_type=$(awk '/^---$/,/^---$/ {if (/^type:/) {sub(/^type:[[:space:]]*"?/, ""); sub(/"?[[:space:]]*$/, ""); print; exit}}' "$spec_file" 2>/dev/null)
852
- if [ -n "$extracted_type" ]; then
853
- increment_type="$extracted_type"
854
- fi
855
- fi
856
-
857
- # Read testing config from .specweave/config.json (NEW - v0.18.0+)
858
- local test_mode="TDD"
859
- local coverage_target=80
860
-
861
- if [ -f "$CONFIG_FILE" ] && command -v jq >/dev/null 2>&1; then
862
- test_mode=$(jq -r '.testing.defaultTestMode // "TDD"' "$CONFIG_FILE" 2>/dev/null || echo "TDD")
863
- coverage_target=$(jq -r '.testing.defaultCoverageTarget // 80' "$CONFIG_FILE" 2>/dev/null || echo "80")
864
- fi
865
-
866
- # Check for overrides in spec.md frontmatter
867
- if [ -f "$spec_file" ]; then
868
- local spec_test_mode=$(awk '/^---$/,/^---$/ {if (/^test_mode:/) {sub(/^test_mode:[[:space:]]*"?/, ""); sub(/"?[[:space:]]*$/, ""); print; exit}}' "$spec_file" 2>/dev/null)
869
- local spec_coverage=$(awk '/^---$/,/^---$/ {if (/^coverage_target:/) {sub(/^coverage_target:[[:space:]]*/, ""); sub(/[[:space:]]*$/, ""); print; exit}}' "$spec_file" 2>/dev/null)
870
-
871
- if [ -n "$spec_test_mode" ]; then
872
- test_mode="$spec_test_mode"
873
- fi
874
- if [ -n "$spec_coverage" ]; then
875
- coverage_target="$spec_coverage"
876
- fi
877
- fi
878
-
879
- # Create minimal metadata.json with testing config
880
- cat > "$metadata_file" <<EOF_MINIMAL
881
- {
882
- "id": "$increment_id",
883
- "status": "active",
884
- "type": "$increment_type",
885
- "created": "$current_timestamp",
886
- "lastActivity": "$current_timestamp",
887
- "testMode": "$test_mode",
888
- "coverageTarget": $coverage_target
889
- }
890
- EOF_MINIMAL
891
-
892
- log_info " ✅ Created minimal metadata.json"
893
- log_info " âš ī¸ Note: No GitHub issue linked"
894
- log_info " 💡 Run /sw-github:create-issue $increment_id to create one manually"
895
- else
896
- log_info " ✅ metadata.json exists"
897
-
898
- # Check if GitHub issue was created
899
- local has_github=$(cat "$metadata_file" 2>/dev/null | grep -o '"github"[[:space:]]*:[[:space:]]*{' || echo "")
900
- if [ -n "$has_github" ]; then
901
- local issue_num=$(cat "$metadata_file" 2>/dev/null | grep -o '"issue"[[:space:]]*:[[:space:]]*[0-9]*' | grep -o '[0-9]*' || echo "")
902
- if [ -n "$issue_num" ]; then
903
- log_info " ✅ GitHub issue #$issue_num linked"
904
- fi
905
- else
906
- log_debug " â„šī¸ No GitHub issue linked (autoCreateIssue may be disabled)"
907
- fi
908
- fi
909
-
910
- # ============================================================================
911
- # LIVING DOCS SYNC (NEW - v0.23.0+, Increment 0047)
912
- # ============================================================================
913
- # CRITICAL: Sync increment spec to living docs structure IMMEDIATELY
914
- # after increment creation (not just on task completion)
915
- #
916
- # Why this matters:
917
- # - Creates FS-XXX feature folder with User Story files
918
- # - Syncs acceptance criteria and task linkages
919
- # - Enables traceability from the moment increment is planned
920
- #
921
- # Previous behavior (BROKEN):
922
- # - Living docs only synced AFTER first task completion
923
- # - New increments had empty living docs until tasks marked complete
924
- # - Manual /sw:sync-docs required for every new increment
925
- #
926
- # New behavior (FIXED):
927
- # - Living docs auto-sync during increment creation
928
- # - FS-XXX folder created with all User Stories immediately
929
- # - No manual intervention needed
930
- #
931
- # @see .specweave/increments/0047-us-task-linkage/ (US-Task Linkage)
932
- # ============================================================================
933
-
934
- log_info ""
935
- log_info "📚 Syncing living docs for new increment..."
936
-
937
- if command -v node &> /dev/null && [ -n "$increment_id" ]; then
938
- # Extract feature ID from spec.md frontmatter (epic: FS-047)
939
- local FEATURE_ID=""
940
- local spec_md_path="$increment_dir/spec.md"
941
-
942
- if [ -f "$spec_md_path" ]; then
943
- # Extract feature ID from multiple possible frontmatter fields:
944
- # - epic: (preferred)
945
- # - feature_id:
946
- # - feature:
947
- FEATURE_ID=$(awk '
948
- BEGIN { in_frontmatter=0; feature_id="" }
949
- /^---$/ {
950
- if (in_frontmatter == 0) {
951
- in_frontmatter=1; next
952
- } else {
953
- exit
954
- }
955
- }
956
- in_frontmatter == 1 && /^epic:/ {
957
- gsub(/^epic:[ \t]*/, "");
958
- gsub(/["'\'']/, "");
959
- feature_id = $0;
960
- }
961
- in_frontmatter == 1 && /^feature_id:/ {
962
- gsub(/^feature_id:[ \t]*/, "");
963
- gsub(/["'\'']/, "");
964
- if (feature_id == "") feature_id = $0;
965
- }
966
- in_frontmatter == 1 && /^feature:/ {
967
- gsub(/^feature:[ \t]*/, "");
968
- gsub(/["'\'']/, "");
969
- if (feature_id == "") feature_id = $0;
970
- }
971
- END { print feature_id }
972
- ' "$spec_md_path" | tr -d '\r\n')
973
-
974
- if [ -n "$FEATURE_ID" ]; then
975
- log_debug " 📎 Extracted feature ID: $FEATURE_ID"
976
- else
977
- # AUTO-GENERATE feature ID from increment number (NEW - ADR-0139)
978
- local increment_num=$(echo "$increment_id" | grep -oE '^[0-9]+')
979
- if [ -n "$increment_num" ]; then
980
- FEATURE_ID="FS-${increment_num}"
981
- log_info " 📝 Auto-generated feature ID: $FEATURE_ID (no epic/feature_id in spec.md)"
982
- else
983
- log_debug " âš ī¸ No feature ID found and could not auto-generate"
984
- fi
985
- fi
986
- fi
987
-
988
- # Extract project ID from config (defaults to "default")
989
- local PROJECT_ID="default"
990
- if [ -f "$CONFIG_FILE" ]; then
991
- if command -v jq >/dev/null 2>&1; then
992
- local active_project=$(jq -r '.multiProject.activeProject // .project.name // "default"' "$CONFIG_FILE" 2>/dev/null)
993
- if [ -n "$active_project" ] && [ "$active_project" != "null" ]; then
994
- PROJECT_ID="$active_project"
995
- fi
996
- fi
997
- fi
998
- log_debug " 📁 Project ID: $PROJECT_ID"
999
-
1000
- # Determine sync script location (same logic as post-task-completion.sh)
1001
- local SYNC_SCRIPT=""
1002
- if [ -f "$PROJECT_ROOT/plugins/specweave/lib/hooks/sync-living-docs.js" ]; then
1003
- SYNC_SCRIPT="$PROJECT_ROOT/plugins/specweave/lib/hooks/sync-living-docs.js"
1004
- log_debug " Using in-place compiled hook: $SYNC_SCRIPT"
1005
- elif [ -f "$PROJECT_ROOT/dist/plugins/specweave/lib/hooks/sync-living-docs.js" ]; then
1006
- SYNC_SCRIPT="$PROJECT_ROOT/dist/plugins/specweave/lib/hooks/sync-living-docs.js"
1007
- log_debug " Using local dist: $SYNC_SCRIPT"
1008
- elif [ -f "$PROJECT_ROOT/node_modules/specweave/dist/plugins/specweave/lib/hooks/sync-living-docs.js" ]; then
1009
- SYNC_SCRIPT="$PROJECT_ROOT/node_modules/specweave/dist/plugins/specweave/lib/hooks/sync-living-docs.js"
1010
- log_debug " Using node_modules: $SYNC_SCRIPT"
1011
- elif [ -n "${CLAUDE_PLUGIN_ROOT}" ] && [ -f "${CLAUDE_PLUGIN_ROOT}/lib/hooks/sync-living-docs.js" ]; then
1012
- SYNC_SCRIPT="${CLAUDE_PLUGIN_ROOT}/lib/hooks/sync-living-docs.js"
1013
- log_debug " Using plugin marketplace: $SYNC_SCRIPT"
1014
- fi
1015
-
1016
- if [ -n "$SYNC_SCRIPT" ]; then
1017
- log_info " 🔄 Syncing $increment_id to living docs..."
1018
-
1019
- # Run living docs sync (non-blocking, best-effort)
1020
- if [ -n "$FEATURE_ID" ]; then
1021
- (cd "$PROJECT_ROOT" && FEATURE_ID="$FEATURE_ID" PROJECT_ID="$PROJECT_ID" node "$SYNC_SCRIPT" "$increment_id") 2>&1 | \
1022
- grep -E "✅|❌|âš ī¸|📚" | while read -r line; do echo " $line"; done || {
1023
- log_info " âš ī¸ Living docs sync failed (non-blocking)"
1024
- log_debug "Sync error, but continuing hook execution"
1025
- }
1026
- else
1027
- (cd "$PROJECT_ROOT" && PROJECT_ID="$PROJECT_ID" node "$SYNC_SCRIPT" "$increment_id") 2>&1 | \
1028
- grep -E "✅|❌|âš ī¸|📚" | while read -r line; do echo " $line"; done || {
1029
- log_info " âš ī¸ Living docs sync failed (non-blocking)"
1030
- log_debug "Sync error, but continuing hook execution"
1031
- }
1032
- fi
1033
-
1034
- log_info " ✅ Living docs sync complete!"
1035
- else
1036
- log_info " âš ī¸ sync-living-docs.js not found, skipping auto-sync"
1037
- log_info " 💡 Run /sw:sync-docs manually to sync living docs"
1038
- fi
1039
- else
1040
- if ! command -v node &> /dev/null; then
1041
- log_debug " âš ī¸ Node.js not found, skipping living docs sync"
1042
- else
1043
- log_debug " âš ī¸ No increment ID, skipping living docs sync"
1044
- fi
1045
- fi
1046
-
1047
- # ============================================================================
1048
- # STEP 8: EXPLICIT GITHUB ISSUE CREATION (NEW - ADR-0139)
1049
- # ============================================================================
1050
- # After living docs are created, explicitly create GitHub issues for User Stories.
1051
- # This ensures GitHub issues are created even if sync-living-docs.js gates fail.
1052
- #
1053
- # Why this is needed:
1054
- # - sync-living-docs.js has 5 config gates that can silently skip GitHub sync
1055
- # - Living docs exist but GitHub issues might not be created
1056
- # - User expects GitHub issues after /sw:increment
1057
- #
1058
- # Retry mechanism: 2 attempts with 3 second delay
1059
- # ============================================================================
1060
-
1061
- log_info ""
1062
- log_info "🐙 Creating GitHub issues for User Stories..."
1063
-
1064
- # Check if GitHub is enabled
1065
- local github_enabled=$(cat "$CONFIG_FILE" 2>/dev/null | grep -A 5 '"github"' | grep -o '"enabled"[[:space:]]*:[[:space:]]*\(true\|false\)' | head -1 | grep -o '\(true\|false\)' || echo "false")
1066
-
1067
- if [ "$github_enabled" = "true" ] && [ -n "$FEATURE_ID" ]; then
1068
- # Find the GitHub feature sync script
1069
- # Priority: dist/ first (compiled with correct imports), then marketplace
1070
- local GITHUB_SYNC_SCRIPT=""
1071
- if [ -f "$PROJECT_ROOT/dist/plugins/specweave-github/lib/github-feature-sync-cli.js" ]; then
1072
- # Prefer dist/ (compiled with correct imports)
1073
- GITHUB_SYNC_SCRIPT="$PROJECT_ROOT/dist/plugins/specweave-github/lib/github-feature-sync-cli.js"
1074
- elif [ -n "${CLAUDE_PLUGIN_ROOT}" ]; then
1075
- # Try specweave-github plugin path (marketplace)
1076
- local github_plugin_root="${CLAUDE_PLUGIN_ROOT/specweave/specweave-github}"
1077
- if [ -f "$github_plugin_root/lib/github-feature-sync-cli.js" ]; then
1078
- GITHUB_SYNC_SCRIPT="$github_plugin_root/lib/github-feature-sync-cli.js"
1079
- fi
1080
- elif [ -f "$PROJECT_ROOT/node_modules/specweave/dist/plugins/specweave-github/lib/github-feature-sync-cli.js" ]; then
1081
- # Try node_modules (npm install)
1082
- GITHUB_SYNC_SCRIPT="$PROJECT_ROOT/node_modules/specweave/dist/plugins/specweave-github/lib/github-feature-sync-cli.js"
1083
- fi
1084
-
1085
- if [ -n "$GITHUB_SYNC_SCRIPT" ]; then
1086
- log_info " 🔄 Syncing $FEATURE_ID to GitHub..."
1087
-
1088
- # Load GitHub token
1089
- local GITHUB_TOKEN=""
1090
- if [ -f "$PROJECT_ROOT/.env" ]; then
1091
- GITHUB_TOKEN=$(grep -E '^GITHUB_TOKEN=' "$PROJECT_ROOT/.env" 2>/dev/null | head -1 | cut -d'=' -f2- | sed 's/^["'\'']//' | sed 's/["'\'']$//')
1092
- fi
1093
-
1094
- if [ -z "$GITHUB_TOKEN" ]; then
1095
- log_info " âš ī¸ GITHUB_TOKEN not found in .env - skipping GitHub sync"
1096
- log_info " 💡 To enable: Add GITHUB_TOKEN=ghp_xxx to .env"
1097
- else
1098
- # Retry mechanism: 2 attempts
1099
- local github_sync_success=false
1100
- local max_attempts=2
1101
-
1102
- for attempt in $(seq 1 $max_attempts); do
1103
- log_debug " GitHub sync attempt $attempt of $max_attempts..."
1104
-
1105
- if GITHUB_TOKEN="$GITHUB_TOKEN" node "$GITHUB_SYNC_SCRIPT" "$FEATURE_ID" 2>&1 | \
1106
- grep -E "✅|❌|âš ī¸|📝|đŸŽ¯|Issue|Milestone" | while read -r line; do echo " $line"; done; then
1107
- github_sync_success=true
1108
- break
1109
- else
1110
- if [ "$attempt" -lt "$max_attempts" ]; then
1111
- log_info " âš ī¸ Attempt $attempt failed, retrying in 3 seconds..."
1112
- sleep 3
1113
- fi
1114
- fi
1115
- done
1116
-
1117
- if [ "$github_sync_success" = "true" ]; then
1118
- log_info " ✅ GitHub issues created successfully!"
1119
- else
1120
- log_info " âš ī¸ GitHub sync failed after $max_attempts attempts (non-blocking)"
1121
- log_info " 💡 Run /sw-github:sync $FEATURE_ID manually to retry"
1122
- fi
1123
- fi
1124
- else
1125
- log_debug " GitHub sync script not found"
1126
- log_info " â„šī¸ GitHub sync will be handled by living docs sync"
1127
- fi
1128
- elif [ "$github_enabled" != "true" ]; then
1129
- log_info " â„šī¸ GitHub sync disabled (sync.github.enabled = false)"
1130
- log_info " 💡 To enable: Set sync.github.enabled = true in config.json"
1131
- elif [ -z "$FEATURE_ID" ]; then
1132
- log_info " âš ī¸ No feature ID - GitHub issues cannot be created"
1133
- log_info " 💡 Add epic: FS-XXX or feature_id: FS-XXX to spec.md frontmatter"
1134
- fi
1135
-
1136
- # 9. Sync spec content to external tools (if configured)
1137
- log_info ""
1138
- log_info "🔗 Checking spec content sync..."
1139
-
1140
- # Check if sync is enabled in config
1141
- local sync_enabled=$(cat "$CONFIG_FILE" 2>/dev/null | grep -o '"enabled"[[:space:]]*:[[:space:]]*\(true\|false\)' | head -1 | grep -o '\(true\|false\)' || echo "false")
1142
-
1143
- if [ "$sync_enabled" = "true" ]; then
1144
- log_info " đŸ“Ļ Sync enabled, syncing spec content to external tool..."
1145
-
1146
- # Find the helper script
1147
- local sync_script="${CLAUDE_PLUGIN_ROOT}/hooks/lib/sync-spec-content.sh"
1148
-
1149
- # Fallback to relative path if CLAUDE_PLUGIN_ROOT not set
1150
- if [ -z "$CLAUDE_PLUGIN_ROOT" ]; then
1151
- local hook_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1152
- sync_script="$hook_dir/lib/sync-spec-content.sh"
1153
- fi
1154
-
1155
- if [ -f "$sync_script" ] && [ -x "$sync_script" ]; then
1156
- log_debug "Calling sync-spec-content.sh for $spec_file"
1157
-
1158
- # Call the sync script (non-blocking)
1159
- if "$sync_script" "$spec_file" 2>&1 | while read -r line; do echo " $line"; done; then
1160
- log_info " ✅ Spec content sync completed successfully!"
1161
- else
1162
- log_info " âš ī¸ Spec content sync failed (non-blocking)"
1163
- log_debug "Spec sync failed, continuing..."
1164
- fi
1165
- else
1166
- log_debug "Sync script not found or not executable: $sync_script"
1167
- fi
1168
- else
1169
- log_debug "Spec content sync disabled in config"
1170
- fi
1171
-
1172
- # 9. Final summary
1173
- log_info ""
1174
- local total_translated=$((increment_success_count))
1175
-
1176
- if [ "$increment_total_count" -gt 0 ] || [ "$total_translated" -gt 0 ]; then
1177
- log_info "✅ Translation complete!"
1178
- log_info " Increment files: $increment_success_count/$increment_total_count"
1179
- log_info " Living docs: See above"
1180
- log_info " Estimated cost: ~\$0.01-0.02 (using Haiku)"
1181
- log_info ""
1182
- fi
1183
-
1184
- # Return success JSON
1185
- cat <<EOF
1186
- {
1187
- "continue": true,
1188
- "message": "Translation complete (increment: $increment_success_count/$increment_total_count files)",
1189
- "files": $(printf '%s\n' "${files_to_translate[@]}" | jq -R . | jq -s .)
1190
- }
1191
- EOF
1192
-
1193
- log_debug "=== POST-INCREMENT-PLANNING HOOK END ==="
1194
- }
1195
-
1196
- # Run main function
1197
- main
1198
-
1199
- # Update status line cache (new increment created)
1200
- HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1201
- bash "$HOOK_DIR/lib/update-status-line.sh" 2>/dev/null || true
1202
-
1203
- # Always return success (non-blocking hook)
1204
- exit 0