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.
- package/CLAUDE.md +140 -1235
- package/bin/specweave.js +23 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts +3 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.js +39 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
- package/dist/src/cli/commands/set-sync-target.d.ts +41 -0
- package/dist/src/cli/commands/set-sync-target.d.ts.map +1 -0
- package/dist/src/cli/commands/set-sync-target.js +126 -0
- package/dist/src/cli/commands/set-sync-target.js.map +1 -0
- package/dist/src/cli/helpers/issue-tracker/github-multi-repo.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js +5 -8
- package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js.map +1 -1
- package/dist/src/core/hooks/HookScanner.d.ts +32 -0
- package/dist/src/core/hooks/HookScanner.d.ts.map +1 -1
- package/dist/src/core/hooks/HookScanner.js +125 -1
- package/dist/src/core/hooks/HookScanner.js.map +1 -1
- package/dist/src/core/hooks/types.d.ts +10 -1
- package/dist/src/core/hooks/types.d.ts.map +1 -1
- package/dist/src/core/increment/metadata-manager.d.ts +67 -1
- package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
- package/dist/src/core/increment/metadata-manager.js +93 -0
- package/dist/src/core/increment/metadata-manager.js.map +1 -1
- package/dist/src/core/project/index.d.ts +21 -0
- package/dist/src/core/project/index.d.ts.map +1 -0
- package/dist/src/core/project/index.js +22 -0
- package/dist/src/core/project/index.js.map +1 -0
- package/dist/src/core/project/project-service.d.ts +122 -0
- package/dist/src/core/project/project-service.d.ts.map +1 -0
- package/dist/src/core/project/project-service.js +334 -0
- package/dist/src/core/project/project-service.js.map +1 -0
- package/dist/src/core/sync/external-tool-resolver.d.ts +171 -0
- package/dist/src/core/sync/external-tool-resolver.d.ts.map +1 -0
- package/dist/src/core/sync/external-tool-resolver.js +569 -0
- package/dist/src/core/sync/external-tool-resolver.js.map +1 -0
- package/dist/src/core/types/increment-metadata.d.ts +92 -0
- package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
- package/dist/src/hooks/processor.d.ts +7 -3
- package/dist/src/hooks/processor.d.ts.map +1 -1
- package/dist/src/hooks/processor.js +11 -5
- package/dist/src/hooks/processor.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/hooks/hooks.json +0 -69
- package/plugins/specweave/hooks/v2/handlers/project-bridge-handler.sh +96 -0
- package/plugins/specweave/hooks/v2/queue/processor.sh +13 -5
- package/plugins/specweave/lib/hooks/project-bridge.js +76 -0
- package/plugins/specweave/lib/hooks/update-tasks-md.js +0 -0
- package/plugins/specweave/lib/hooks/us-completion-orchestrator.js +0 -0
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.d.ts +67 -1
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +93 -0
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
- package/plugins/specweave/lib/vendor/core/types/increment-metadata.d.ts +92 -0
- package/plugins/specweave-github/lib/github-client-v2.js +39 -0
- package/plugins/specweave-github/lib/github-client-v2.ts +44 -0
- package/plugins/specweave/hooks/docs-changed.sh +0 -87
- package/plugins/specweave/hooks/human-input-required.sh +0 -83
- package/plugins/specweave/hooks/post-edit-write-consolidated.sh +0 -428
- package/plugins/specweave/hooks/post-first-increment.sh +0 -61
- package/plugins/specweave/hooks/post-increment-change.sh +0 -103
- package/plugins/specweave/hooks/post-increment-completion.sh +0 -513
- package/plugins/specweave/hooks/post-increment-planning.sh +0 -1204
- package/plugins/specweave/hooks/post-increment-status-change.sh +0 -243
- package/plugins/specweave/hooks/post-metadata-change.sh +0 -246
- package/plugins/specweave/hooks/post-spec-update.sh +0 -158
- package/plugins/specweave/hooks/post-task-completion.sh +0 -557
- package/plugins/specweave/hooks/post-task-edit.sh +0 -47
- package/plugins/specweave/hooks/post-user-story-complete.sh +0 -230
- package/plugins/specweave/hooks/pre-command-deduplication.sh +0 -68
- package/plugins/specweave/hooks/pre-edit-write-consolidated.sh +0 -225
- package/plugins/specweave/hooks/pre-implementation.sh +0 -75
- package/plugins/specweave/hooks/pre-increment-start.sh +0 -173
- package/plugins/specweave/hooks/pre-task-completion-edit.sh +0 -355
- package/plugins/specweave/hooks/pre-task-completion.sh +0 -269
- package/plugins/specweave/hooks/pre-tool-use.sh +0 -137
- package/plugins/specweave/hooks/session-start-reconcile.sh +0 -139
- package/plugins/specweave/hooks/shared/bulk-operation-detector.sh +0 -167
- package/plugins/specweave/hooks/test-pretooluse-env.sh +0 -72
- package/plugins/specweave/hooks/validate-increment-completion.sh +0 -113
- 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
|