specweave 1.0.21 → 1.0.22

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