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.
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.d.ts.map +1 -1
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.js +79 -13
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/hooks/docs-changed.sh.backup +0 -79
- package/plugins/specweave/hooks/human-input-required.sh.backup +0 -75
- package/plugins/specweave/hooks/post-first-increment.sh.backup +0 -61
- package/plugins/specweave/hooks/post-increment-change.sh.backup +0 -98
- package/plugins/specweave/hooks/post-increment-completion.sh.backup +0 -231
- package/plugins/specweave/hooks/post-increment-planning.sh.backup +0 -1048
- package/plugins/specweave/hooks/post-increment-status-change.sh.backup +0 -147
- package/plugins/specweave/hooks/post-spec-update.sh.backup +0 -158
- package/plugins/specweave/hooks/post-user-story-complete.sh.backup +0 -179
- package/plugins/specweave/hooks/pre-command-deduplication.sh.backup +0 -83
- package/plugins/specweave/hooks/pre-implementation.sh.backup +0 -67
- package/plugins/specweave/hooks/pre-task-completion.sh.backup +0 -194
- package/plugins/specweave/hooks/pre-tool-use.sh.backup +0 -133
- package/plugins/specweave/hooks/user-prompt-submit.sh.backup +0 -386
- package/plugins/specweave-ado/hooks/post-living-docs-update.sh.backup +0 -353
- package/plugins/specweave-ado/hooks/post-task-completion.sh.backup +0 -172
- package/plugins/specweave-ado/lib/enhanced-ado-sync.js +0 -170
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +0 -1262
- package/plugins/specweave-github/hooks/post-task-completion.sh.backup +0 -258
- package/plugins/specweave-github/lib/enhanced-github-sync.js +0 -220
- package/plugins/specweave-jira/hooks/post-task-completion.sh.backup +0 -172
- package/plugins/specweave-jira/lib/enhanced-jira-sync.js +0 -134
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +0 -1254
- 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
|