specweave 0.16.5 → 0.17.0
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/bin/fix-marketplace-errors.sh +136 -0
- package/dist/cli/helpers/issue-tracker/index.d.ts.map +1 -1
- package/dist/cli/helpers/issue-tracker/index.js +21 -0
- package/dist/cli/helpers/issue-tracker/index.js.map +1 -1
- package/package.json +2 -2
- package/plugins/specweave-ado/agents/ado-multi-project-mapper/AGENT.md +521 -0
- package/plugins/specweave-ado/agents/ado-sync-judge/AGENT.md +418 -0
- package/plugins/specweave-ado/hooks/post-living-docs-update.sh +353 -0
- package/plugins/specweave-ado/lib/ado-project-detector.js +469 -0
- package/plugins/specweave-ado/lib/ado-project-detector.ts +510 -0
- package/plugins/specweave-ado/lib/conflict-resolver.js +297 -0
- package/plugins/specweave-ado/lib/conflict-resolver.ts +443 -0
- package/plugins/specweave-ado/skills/ado-multi-project/SKILL.md +541 -0
- package/plugins/specweave-ado/skills/ado-resource-validator/SKILL.md +719 -0
- package/src/templates/CLAUDE.md.template +24 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Post Living Docs Update Hook - Azure DevOps Sync
|
|
5
|
+
# ============================================================================
|
|
6
|
+
#
|
|
7
|
+
# Triggered after living docs are updated to sync with Azure DevOps.
|
|
8
|
+
# CRITICAL: External tool status ALWAYS wins in conflicts!
|
|
9
|
+
#
|
|
10
|
+
# Triggers:
|
|
11
|
+
# 1. After /specweave:done (increment completion)
|
|
12
|
+
# 2. After /specweave:sync-docs update
|
|
13
|
+
# 3. After manual spec edits
|
|
14
|
+
# 4. After webhook from ADO
|
|
15
|
+
#
|
|
16
|
+
# ============================================================================
|
|
17
|
+
|
|
18
|
+
set -e
|
|
19
|
+
|
|
20
|
+
# Configuration
|
|
21
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
22
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
23
|
+
LIVING_DOCS_DIR="$PROJECT_ROOT/.specweave/docs/internal/specs"
|
|
24
|
+
LOG_FILE="$PROJECT_ROOT/.specweave/logs/ado-sync.log"
|
|
25
|
+
DEBUG=${DEBUG:-0}
|
|
26
|
+
|
|
27
|
+
# Ensure log directory exists
|
|
28
|
+
mkdir -p "$(dirname "$LOG_FILE")"
|
|
29
|
+
|
|
30
|
+
# ============================================================================
|
|
31
|
+
# Logging
|
|
32
|
+
# ============================================================================
|
|
33
|
+
|
|
34
|
+
log() {
|
|
35
|
+
local level=$1
|
|
36
|
+
shift
|
|
37
|
+
local message="$@"
|
|
38
|
+
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
|
39
|
+
echo "[$timestamp] [$level] $message" >> "$LOG_FILE"
|
|
40
|
+
[ "$DEBUG" -eq 1 ] && echo "[$level] $message" >&2
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
log_info() {
|
|
44
|
+
log "INFO" "$@"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
log_error() {
|
|
48
|
+
log "ERROR" "$@"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
log_debug() {
|
|
52
|
+
[ "$DEBUG" -eq 1 ] && log "DEBUG" "$@"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# ============================================================================
|
|
56
|
+
# External Tool Detection
|
|
57
|
+
# ============================================================================
|
|
58
|
+
|
|
59
|
+
detect_external_tool() {
|
|
60
|
+
local spec_path=$1
|
|
61
|
+
|
|
62
|
+
# Check for external links in spec metadata
|
|
63
|
+
if grep -q "externalLinks:" "$spec_path"; then
|
|
64
|
+
if grep -q "ado:" "$spec_path"; then
|
|
65
|
+
echo "ado"
|
|
66
|
+
elif grep -q "jira:" "$spec_path"; then
|
|
67
|
+
echo "jira"
|
|
68
|
+
elif grep -q "github:" "$spec_path"; then
|
|
69
|
+
echo "github"
|
|
70
|
+
fi
|
|
71
|
+
fi
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# ============================================================================
|
|
75
|
+
# Status Mapping
|
|
76
|
+
# ============================================================================
|
|
77
|
+
|
|
78
|
+
map_ado_status_to_local() {
|
|
79
|
+
local ado_status=$1
|
|
80
|
+
|
|
81
|
+
case "$ado_status" in
|
|
82
|
+
"New")
|
|
83
|
+
echo "draft"
|
|
84
|
+
;;
|
|
85
|
+
"Active")
|
|
86
|
+
echo "in-progress"
|
|
87
|
+
;;
|
|
88
|
+
"Resolved")
|
|
89
|
+
echo "implemented"
|
|
90
|
+
;;
|
|
91
|
+
"Closed")
|
|
92
|
+
echo "complete"
|
|
93
|
+
;;
|
|
94
|
+
"In Review"|"In QA")
|
|
95
|
+
echo "in-qa"
|
|
96
|
+
;;
|
|
97
|
+
*)
|
|
98
|
+
echo "unknown"
|
|
99
|
+
;;
|
|
100
|
+
esac
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
map_local_status_to_ado() {
|
|
104
|
+
local local_status=$1
|
|
105
|
+
|
|
106
|
+
case "$local_status" in
|
|
107
|
+
"draft")
|
|
108
|
+
echo "New"
|
|
109
|
+
;;
|
|
110
|
+
"in-progress")
|
|
111
|
+
echo "Active"
|
|
112
|
+
;;
|
|
113
|
+
"implemented")
|
|
114
|
+
echo "Resolved"
|
|
115
|
+
;;
|
|
116
|
+
"complete")
|
|
117
|
+
echo "Closed"
|
|
118
|
+
;;
|
|
119
|
+
"in-qa")
|
|
120
|
+
echo "In Review"
|
|
121
|
+
;;
|
|
122
|
+
*)
|
|
123
|
+
echo "Active"
|
|
124
|
+
;;
|
|
125
|
+
esac
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# ============================================================================
|
|
129
|
+
# ADO API Functions
|
|
130
|
+
# ============================================================================
|
|
131
|
+
|
|
132
|
+
get_ado_work_item_status() {
|
|
133
|
+
local work_item_id=$1
|
|
134
|
+
local org="${AZURE_DEVOPS_ORG}"
|
|
135
|
+
local project="${AZURE_DEVOPS_PROJECT}"
|
|
136
|
+
local pat="${AZURE_DEVOPS_PAT}"
|
|
137
|
+
|
|
138
|
+
if [ -z "$org" ] || [ -z "$pat" ]; then
|
|
139
|
+
log_error "ADO credentials not configured"
|
|
140
|
+
return 1
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
local api_url="https://dev.azure.com/${org}/${project}/_apis/wit/workitems/${work_item_id}?api-version=7.0"
|
|
144
|
+
|
|
145
|
+
log_debug "Fetching ADO work item $work_item_id status"
|
|
146
|
+
|
|
147
|
+
local response=$(curl -s -u ":${pat}" \
|
|
148
|
+
-H "Content-Type: application/json" \
|
|
149
|
+
"$api_url")
|
|
150
|
+
|
|
151
|
+
if [ $? -ne 0 ]; then
|
|
152
|
+
log_error "Failed to fetch ADO work item status"
|
|
153
|
+
return 1
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
# Extract status from response
|
|
157
|
+
local status=$(echo "$response" | jq -r '.fields["System.State"]')
|
|
158
|
+
|
|
159
|
+
if [ "$status" = "null" ] || [ -z "$status" ]; then
|
|
160
|
+
log_error "Could not extract status from ADO response"
|
|
161
|
+
return 1
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
echo "$status"
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
update_ado_work_item() {
|
|
168
|
+
local work_item_id=$1
|
|
169
|
+
local spec_content=$2
|
|
170
|
+
local org="${AZURE_DEVOPS_ORG}"
|
|
171
|
+
local project="${AZURE_DEVOPS_PROJECT}"
|
|
172
|
+
local pat="${AZURE_DEVOPS_PAT}"
|
|
173
|
+
|
|
174
|
+
if [ -z "$org" ] || [ -z "$pat" ]; then
|
|
175
|
+
log_error "ADO credentials not configured"
|
|
176
|
+
return 1
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# Extract current status from spec
|
|
180
|
+
local local_status=$(echo "$spec_content" | grep "^status:" | cut -d: -f2 | tr -d ' ')
|
|
181
|
+
local ado_status=$(map_local_status_to_ado "$local_status")
|
|
182
|
+
|
|
183
|
+
local api_url="https://dev.azure.com/${org}/${project}/_apis/wit/workitems/${work_item_id}?api-version=7.0"
|
|
184
|
+
|
|
185
|
+
# Create update payload
|
|
186
|
+
local payload=$(cat <<EOF
|
|
187
|
+
[
|
|
188
|
+
{
|
|
189
|
+
"op": "add",
|
|
190
|
+
"path": "/fields/System.State",
|
|
191
|
+
"value": "$ado_status"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"op": "add",
|
|
195
|
+
"path": "/fields/System.History",
|
|
196
|
+
"value": "Updated from SpecWeave living docs"
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
EOF
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
log_debug "Updating ADO work item $work_item_id with status: $ado_status"
|
|
203
|
+
|
|
204
|
+
curl -s -X PATCH \
|
|
205
|
+
-u ":${pat}" \
|
|
206
|
+
-H "Content-Type: application/json-patch+json" \
|
|
207
|
+
-d "$payload" \
|
|
208
|
+
"$api_url" > /dev/null
|
|
209
|
+
|
|
210
|
+
if [ $? -ne 0 ]; then
|
|
211
|
+
log_error "Failed to update ADO work item"
|
|
212
|
+
return 1
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
log_info "Updated ADO work item $work_item_id"
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
# ============================================================================
|
|
219
|
+
# Conflict Resolution - CRITICAL: External Wins!
|
|
220
|
+
# ============================================================================
|
|
221
|
+
|
|
222
|
+
resolve_status_conflict() {
|
|
223
|
+
local spec_path=$1
|
|
224
|
+
local local_status=$2
|
|
225
|
+
local external_status=$3
|
|
226
|
+
|
|
227
|
+
local mapped_external=$(map_ado_status_to_local "$external_status")
|
|
228
|
+
|
|
229
|
+
if [ "$local_status" != "$mapped_external" ]; then
|
|
230
|
+
log_info "Status conflict detected:"
|
|
231
|
+
log_info " Local: $local_status"
|
|
232
|
+
log_info " External: $external_status (mapped: $mapped_external)"
|
|
233
|
+
log_info " Resolution: EXTERNAL WINS - applying $mapped_external"
|
|
234
|
+
|
|
235
|
+
# Update local spec with external status
|
|
236
|
+
sed -i.bak "s/^status: .*/status: $mapped_external/" "$spec_path"
|
|
237
|
+
|
|
238
|
+
# Add sync metadata
|
|
239
|
+
local timestamp=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
|
240
|
+
|
|
241
|
+
# Check if syncedAt exists, update or add
|
|
242
|
+
if grep -q "syncedAt:" "$spec_path"; then
|
|
243
|
+
sed -i.bak "s/syncedAt: .*/syncedAt: \"$timestamp\"/" "$spec_path"
|
|
244
|
+
else
|
|
245
|
+
# Add after externalLinks section
|
|
246
|
+
sed -i.bak "/externalLinks:/a\\
|
|
247
|
+
syncedAt: \"$timestamp\"" "$spec_path"
|
|
248
|
+
fi
|
|
249
|
+
|
|
250
|
+
# Clean up backup files
|
|
251
|
+
rm -f "${spec_path}.bak"
|
|
252
|
+
|
|
253
|
+
log_info "Local spec updated with external status: $mapped_external"
|
|
254
|
+
return 0
|
|
255
|
+
else
|
|
256
|
+
log_debug "No status conflict - local and external match: $local_status"
|
|
257
|
+
return 0
|
|
258
|
+
fi
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
# ============================================================================
|
|
262
|
+
# Main Sync Function
|
|
263
|
+
# ============================================================================
|
|
264
|
+
|
|
265
|
+
sync_spec_with_ado() {
|
|
266
|
+
local spec_path=$1
|
|
267
|
+
|
|
268
|
+
if [ ! -f "$spec_path" ]; then
|
|
269
|
+
log_error "Spec file not found: $spec_path"
|
|
270
|
+
return 1
|
|
271
|
+
fi
|
|
272
|
+
|
|
273
|
+
local spec_name=$(basename "$spec_path")
|
|
274
|
+
log_info "Syncing spec: $spec_name"
|
|
275
|
+
|
|
276
|
+
# Read spec content
|
|
277
|
+
local spec_content=$(cat "$spec_path")
|
|
278
|
+
|
|
279
|
+
# Extract ADO work item ID from metadata
|
|
280
|
+
local work_item_id=$(echo "$spec_content" | grep -A5 "externalLinks:" | grep -A3 "ado:" | grep "featureId:" | cut -d: -f2 | tr -d ' ')
|
|
281
|
+
|
|
282
|
+
if [ -z "$work_item_id" ]; then
|
|
283
|
+
log_debug "No ADO work item linked to spec, skipping sync"
|
|
284
|
+
return 0
|
|
285
|
+
fi
|
|
286
|
+
|
|
287
|
+
log_info "Found ADO work item ID: $work_item_id"
|
|
288
|
+
|
|
289
|
+
# Step 1: Push updates to ADO (content changes)
|
|
290
|
+
update_ado_work_item "$work_item_id" "$spec_content"
|
|
291
|
+
|
|
292
|
+
# Step 2: CRITICAL - Pull status from ADO (external wins!)
|
|
293
|
+
local external_status=$(get_ado_work_item_status "$work_item_id")
|
|
294
|
+
|
|
295
|
+
if [ -z "$external_status" ]; then
|
|
296
|
+
log_error "Could not fetch ADO status"
|
|
297
|
+
return 1
|
|
298
|
+
fi
|
|
299
|
+
|
|
300
|
+
log_info "ADO status: $external_status"
|
|
301
|
+
|
|
302
|
+
# Step 3: Extract local status
|
|
303
|
+
local local_status=$(echo "$spec_content" | grep "^status:" | cut -d: -f2 | tr -d ' ')
|
|
304
|
+
|
|
305
|
+
log_info "Local status: $local_status"
|
|
306
|
+
|
|
307
|
+
# Step 4: Resolve conflicts - EXTERNAL WINS
|
|
308
|
+
resolve_status_conflict "$spec_path" "$local_status" "$external_status"
|
|
309
|
+
|
|
310
|
+
log_info "Sync completed for $spec_name"
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
# ============================================================================
|
|
314
|
+
# Entry Point
|
|
315
|
+
# ============================================================================
|
|
316
|
+
|
|
317
|
+
main() {
|
|
318
|
+
log_info "=== Post Living Docs Update Hook Started ==="
|
|
319
|
+
|
|
320
|
+
# Get the spec path from arguments or environment
|
|
321
|
+
local spec_path="${1:-$SPECWEAVE_UPDATED_SPEC}"
|
|
322
|
+
|
|
323
|
+
if [ -z "$spec_path" ]; then
|
|
324
|
+
log_error "No spec path provided"
|
|
325
|
+
exit 1
|
|
326
|
+
fi
|
|
327
|
+
|
|
328
|
+
# Detect external tool
|
|
329
|
+
local tool=$(detect_external_tool "$spec_path")
|
|
330
|
+
|
|
331
|
+
if [ "$tool" != "ado" ]; then
|
|
332
|
+
log_debug "Not an ADO-linked spec, skipping"
|
|
333
|
+
exit 0
|
|
334
|
+
fi
|
|
335
|
+
|
|
336
|
+
log_info "Detected ADO integration for spec"
|
|
337
|
+
|
|
338
|
+
# Perform sync
|
|
339
|
+
sync_spec_with_ado "$spec_path"
|
|
340
|
+
|
|
341
|
+
local exit_code=$?
|
|
342
|
+
|
|
343
|
+
if [ $exit_code -eq 0 ]; then
|
|
344
|
+
log_info "=== Sync completed successfully ==="
|
|
345
|
+
else
|
|
346
|
+
log_error "=== Sync failed with exit code: $exit_code ==="
|
|
347
|
+
fi
|
|
348
|
+
|
|
349
|
+
exit $exit_code
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
# Run main function
|
|
353
|
+
main "$@"
|