specweave 1.0.550 → 1.0.552

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/CLAUDE.md +1 -1
  2. package/bin/specweave.js +23 -1
  3. package/dist/src/cli/commands/hook.d.ts +15 -0
  4. package/dist/src/cli/commands/hook.d.ts.map +1 -0
  5. package/dist/src/cli/commands/hook.js +61 -0
  6. package/dist/src/cli/commands/hook.js.map +1 -0
  7. package/dist/src/cli/commands/init.d.ts.map +1 -1
  8. package/dist/src/cli/commands/init.js +5 -0
  9. package/dist/src/cli/commands/init.js.map +1 -1
  10. package/dist/src/cli/commands/refresh-plugins.d.ts.map +1 -1
  11. package/dist/src/cli/commands/refresh-plugins.js +11 -1
  12. package/dist/src/cli/commands/refresh-plugins.js.map +1 -1
  13. package/dist/src/cli/commands/sync-setup.d.ts.map +1 -1
  14. package/dist/src/cli/commands/sync-setup.js +7 -3
  15. package/dist/src/cli/commands/sync-setup.js.map +1 -1
  16. package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.d.ts +9 -0
  17. package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.d.ts.map +1 -1
  18. package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.js +9 -3
  19. package/dist/src/cli/helpers/issue-tracker/project-mapping-wizard.js.map +1 -1
  20. package/dist/src/config/types.d.ts +2 -2
  21. package/dist/src/core/config/types.d.ts +18 -2
  22. package/dist/src/core/config/types.d.ts.map +1 -1
  23. package/dist/src/core/config/types.js.map +1 -1
  24. package/dist/src/core/hooks/handlers/hook-router.d.ts +19 -0
  25. package/dist/src/core/hooks/handlers/hook-router.d.ts.map +1 -0
  26. package/dist/src/core/hooks/handlers/hook-router.js +75 -0
  27. package/dist/src/core/hooks/handlers/hook-router.js.map +1 -0
  28. package/dist/src/core/hooks/handlers/index.d.ts +10 -0
  29. package/dist/src/core/hooks/handlers/index.d.ts.map +1 -0
  30. package/dist/src/core/hooks/handlers/index.js +9 -0
  31. package/dist/src/core/hooks/handlers/index.js.map +1 -0
  32. package/dist/src/core/hooks/handlers/post-tool-use-analytics.d.ts +11 -0
  33. package/dist/src/core/hooks/handlers/post-tool-use-analytics.d.ts.map +1 -0
  34. package/dist/src/core/hooks/handlers/post-tool-use-analytics.js +73 -0
  35. package/dist/src/core/hooks/handlers/post-tool-use-analytics.js.map +1 -0
  36. package/dist/src/core/hooks/handlers/post-tool-use.d.ts +11 -0
  37. package/dist/src/core/hooks/handlers/post-tool-use.d.ts.map +1 -0
  38. package/dist/src/core/hooks/handlers/post-tool-use.js +76 -0
  39. package/dist/src/core/hooks/handlers/post-tool-use.js.map +1 -0
  40. package/dist/src/core/hooks/handlers/pre-compact.d.ts +11 -0
  41. package/dist/src/core/hooks/handlers/pre-compact.d.ts.map +1 -0
  42. package/dist/src/core/hooks/handlers/pre-compact.js +77 -0
  43. package/dist/src/core/hooks/handlers/pre-compact.js.map +1 -0
  44. package/dist/src/core/hooks/handlers/pre-tool-use.d.ts +11 -0
  45. package/dist/src/core/hooks/handlers/pre-tool-use.d.ts.map +1 -0
  46. package/dist/src/core/hooks/handlers/pre-tool-use.js +318 -0
  47. package/dist/src/core/hooks/handlers/pre-tool-use.js.map +1 -0
  48. package/dist/src/core/hooks/handlers/session-start.d.ts +9 -0
  49. package/dist/src/core/hooks/handlers/session-start.d.ts.map +1 -0
  50. package/dist/src/core/hooks/handlers/session-start.js +111 -0
  51. package/dist/src/core/hooks/handlers/session-start.js.map +1 -0
  52. package/dist/src/core/hooks/handlers/stop-auto.d.ts +16 -0
  53. package/dist/src/core/hooks/handlers/stop-auto.d.ts.map +1 -0
  54. package/dist/src/core/hooks/handlers/stop-auto.js +122 -0
  55. package/dist/src/core/hooks/handlers/stop-auto.js.map +1 -0
  56. package/dist/src/core/hooks/handlers/stop-reflect.d.ts +14 -0
  57. package/dist/src/core/hooks/handlers/stop-reflect.d.ts.map +1 -0
  58. package/dist/src/core/hooks/handlers/stop-reflect.js +43 -0
  59. package/dist/src/core/hooks/handlers/stop-reflect.js.map +1 -0
  60. package/dist/src/core/hooks/handlers/stop-sync.d.ts +15 -0
  61. package/dist/src/core/hooks/handlers/stop-sync.d.ts.map +1 -0
  62. package/dist/src/core/hooks/handlers/stop-sync.js +68 -0
  63. package/dist/src/core/hooks/handlers/stop-sync.js.map +1 -0
  64. package/dist/src/core/hooks/handlers/types.d.ts +63 -0
  65. package/dist/src/core/hooks/handlers/types.d.ts.map +1 -0
  66. package/dist/src/core/hooks/handlers/types.js +27 -0
  67. package/dist/src/core/hooks/handlers/types.js.map +1 -0
  68. package/dist/src/core/hooks/handlers/user-prompt-submit.d.ts +14 -0
  69. package/dist/src/core/hooks/handlers/user-prompt-submit.d.ts.map +1 -0
  70. package/dist/src/core/hooks/handlers/user-prompt-submit.js +173 -0
  71. package/dist/src/core/hooks/handlers/user-prompt-submit.js.map +1 -0
  72. package/dist/src/core/hooks/handlers/utils.d.ts +25 -0
  73. package/dist/src/core/hooks/handlers/utils.d.ts.map +1 -0
  74. package/dist/src/core/hooks/handlers/utils.js +64 -0
  75. package/dist/src/core/hooks/handlers/utils.js.map +1 -0
  76. package/dist/src/core/increment/completion-validator.d.ts.map +1 -1
  77. package/dist/src/core/increment/completion-validator.js +32 -0
  78. package/dist/src/core/increment/completion-validator.js.map +1 -1
  79. package/dist/src/init/research/types.d.ts +1 -1
  80. package/dist/src/sync/sync-target-resolver.js.map +1 -1
  81. package/dist/src/utils/lock-manager.d.ts.map +1 -1
  82. package/dist/src/utils/lock-manager.js +5 -0
  83. package/dist/src/utils/lock-manager.js.map +1 -1
  84. package/dist/src/utils/plugin-copier.d.ts +10 -0
  85. package/dist/src/utils/plugin-copier.d.ts.map +1 -1
  86. package/dist/src/utils/plugin-copier.js +63 -35
  87. package/dist/src/utils/plugin-copier.js.map +1 -1
  88. package/package.json +1 -1
  89. package/plugins/specweave/agents/sw-closer.md +3 -2
  90. package/plugins/specweave/hooks/hooks.json +10 -10
  91. package/plugins/specweave/skills/code-reviewer/SKILL.md +180 -16
  92. package/plugins/specweave/skills/code-reviewer/agents/reviewer-comments.md +83 -0
  93. package/plugins/specweave/skills/code-reviewer/agents/reviewer-silent-failures.md +19 -0
  94. package/plugins/specweave/skills/code-reviewer/agents/reviewer-spec-compliance.md +19 -0
  95. package/plugins/specweave/skills/code-reviewer/agents/reviewer-tests.md +101 -0
  96. package/plugins/specweave/skills/code-reviewer/agents/reviewer-types.md +20 -0
  97. package/plugins/specweave/skills/done/SKILL.md +56 -21
  98. package/plugins/specweave/skills/grill/SKILL.md +1 -1
  99. package/plugins/specweave/skills/team-lead/agents/reviewer-logic.md +19 -0
  100. package/plugins/specweave/skills/team-lead/agents/reviewer-performance.md +20 -0
  101. package/plugins/specweave/skills/team-lead/agents/reviewer-security.md +20 -0
  102. package/src/templates/CLAUDE.md.template +7 -4
  103. package/plugins/specweave/hooks/README.md +0 -493
  104. package/plugins/specweave/hooks/_archive/stop-auto-v4-legacy.sh +0 -1319
  105. package/plugins/specweave/hooks/lib/common-setup.sh +0 -144
  106. package/plugins/specweave/hooks/lib/hook-errors.sh +0 -414
  107. package/plugins/specweave/hooks/lib/migrate-increment-work.sh +0 -245
  108. package/plugins/specweave/hooks/lib/resolve-package.sh +0 -146
  109. package/plugins/specweave/hooks/lib/scheduler-startup.sh +0 -135
  110. package/plugins/specweave/hooks/lib/score-increment.sh +0 -87
  111. package/plugins/specweave/hooks/lib/sync-spec-content.sh +0 -193
  112. package/plugins/specweave/hooks/lib/update-active-increment.sh +0 -95
  113. package/plugins/specweave/hooks/lib/update-status-line.sh +0 -233
  114. package/plugins/specweave/hooks/lib/validate-spec-status.sh +0 -171
  115. package/plugins/specweave/hooks/llm-judge-validator.sh +0 -219
  116. package/plugins/specweave/hooks/log-decision.sh +0 -168
  117. package/plugins/specweave/hooks/pre-compact.sh +0 -64
  118. package/plugins/specweave/hooks/startup-health-check.sh +0 -64
  119. package/plugins/specweave/hooks/stop-auto-v5.sh +0 -276
  120. package/plugins/specweave/hooks/stop-reflect.sh +0 -336
  121. package/plugins/specweave/hooks/stop-sync.sh +0 -283
  122. package/plugins/specweave/hooks/tests/test-auto-context-integration.sh +0 -126
  123. package/plugins/specweave/hooks/tests/test-stop-auto-enriched.sh +0 -128
  124. package/plugins/specweave/hooks/universal/dispatcher.mjs +0 -336
  125. package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +0 -325
  126. package/plugins/specweave/hooks/universal/hook-wrapper.cmd +0 -26
  127. package/plugins/specweave/hooks/universal/hook-wrapper.sh +0 -69
  128. package/plugins/specweave/hooks/universal/run-hook.sh +0 -20
  129. package/plugins/specweave/hooks/universal/session-start.cmd +0 -16
  130. package/plugins/specweave/hooks/universal/session-start.ps1 +0 -16
  131. package/plugins/specweave/hooks/user-prompt-submit.sh +0 -2550
  132. package/plugins/specweave/hooks/v2/detectors/lifecycle-detector.sh +0 -87
  133. package/plugins/specweave/hooks/v2/detectors/us-completion-detector.sh +0 -186
  134. package/plugins/specweave/hooks/v2/dispatchers/post-tool-use-analytics.sh +0 -83
  135. package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +0 -447
  136. package/plugins/specweave/hooks/v2/dispatchers/pre-tool-use.sh +0 -104
  137. package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +0 -270
  138. package/plugins/specweave/hooks/v2/guards/completion-guard.sh +0 -14
  139. package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +0 -14
  140. package/plugins/specweave/hooks/v2/guards/increment-existence-guard.sh +0 -240
  141. package/plugins/specweave/hooks/v2/guards/interview-enforcement-guard.sh +0 -171
  142. package/plugins/specweave/hooks/v2/guards/metadata-json-guard.sh +0 -14
  143. package/plugins/specweave/hooks/v2/guards/skill-chain-enforcement-guard.sh +0 -222
  144. package/plugins/specweave/hooks/v2/guards/spec-template-enforcement-guard.sh +0 -21
  145. package/plugins/specweave/hooks/v2/guards/spec-validation-guard.sh +0 -14
  146. package/plugins/specweave/hooks/v2/guards/status-completion-guard.sh +0 -84
  147. package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +0 -475
  148. package/plugins/specweave/hooks/v2/guards/tdd-enforcement-guard.sh +0 -268
  149. package/plugins/specweave/hooks/v2/handlers/ac-sync-dispatcher.sh +0 -332
  150. package/plugins/specweave/hooks/v2/handlers/ac-validation-handler.sh +0 -50
  151. package/plugins/specweave/hooks/v2/handlers/github-sync-handler.sh +0 -347
  152. package/plugins/specweave/hooks/v2/handlers/living-docs-handler.sh +0 -83
  153. package/plugins/specweave/hooks/v2/handlers/living-specs-handler.sh +0 -268
  154. package/plugins/specweave/hooks/v2/handlers/project-bridge-handler.sh +0 -104
  155. package/plugins/specweave/hooks/v2/handlers/status-line-handler.sh +0 -165
  156. package/plugins/specweave/hooks/v2/handlers/status-update.sh +0 -61
  157. package/plugins/specweave/hooks/v2/handlers/universal-auto-create-dispatcher.sh +0 -270
  158. package/plugins/specweave/hooks/v2/integrations/ado-post-living-docs-update.sh +0 -367
  159. package/plugins/specweave/hooks/v2/integrations/ado-post-task.sh +0 -179
  160. package/plugins/specweave/hooks/v2/integrations/github-auto-create-handler.sh +0 -553
  161. package/plugins/specweave/hooks/v2/integrations/github-post-task.sh +0 -345
  162. package/plugins/specweave/hooks/v2/integrations/jira-post-task.sh +0 -180
  163. package/plugins/specweave/hooks/v2/lib/check-provider-enabled.sh +0 -52
  164. package/plugins/specweave/hooks/v2/queue/enqueue.sh +0 -81
  165. package/plugins/specweave/hooks/v2/session-end.sh +0 -139
  166. package/plugins/specweave/hooks/validate-skill-activations.sh +0 -227
@@ -1,553 +0,0 @@
1
- #!/bin/bash
2
- # github-auto-create-handler.sh — Auto-creates GitHub issues from spec.md user stories
3
- #
4
- # Triggered by post-tool-use.sh when spec.md is created/updated.
5
- # Pure bash using gh CLI — no TypeScript compilation required.
6
- #
7
- # Features:
8
- # - Idempotent: checks existing issues before creating
9
- # - Updates metadata.json with external links
10
- # - Debounce (30s window to batch rapid spec edits)
11
- # - Circuit breaker (3 consecutive failures → auto-disable)
12
- # - Non-blocking (all errors → exit 0)
13
- #
14
- # Usage: github-auto-create-handler.sh <INCREMENT_ID>
15
- # Called by: safe_run_background in post-tool-use.sh
16
-
17
- set +e # Never crash Claude Code
18
-
19
- [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]] && exit 0
20
-
21
- # ============================================================================
22
- # ARGS & PROJECT ROOT
23
- # ============================================================================
24
-
25
- INC_ID="$1"
26
- [[ -z "$INC_ID" ]] && exit 0
27
-
28
- PROJECT_ROOT="$PWD"
29
- while [[ "$PROJECT_ROOT" != "/" ]] && [[ ! -f "$PROJECT_ROOT/.specweave/config.json" ]]; do
30
- PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
31
- done
32
- [[ ! -f "$PROJECT_ROOT/.specweave/config.json" ]] && exit 0
33
-
34
- SPEC_PATH="$PROJECT_ROOT/.specweave/increments/$INC_ID/spec.md"
35
- META_PATH="$PROJECT_ROOT/.specweave/increments/$INC_ID/metadata.json"
36
- CONFIG_PATH="$PROJECT_ROOT/.specweave/config.json"
37
- STATE_DIR="$PROJECT_ROOT/.specweave/state"
38
- LOGS_DIR="$PROJECT_ROOT/.specweave/logs"
39
- LOG_FILE="$LOGS_DIR/github-auto-create.log"
40
-
41
- mkdir -p "$STATE_DIR" "$LOGS_DIR" 2>/dev/null || true
42
-
43
- log() {
44
- echo "[$(date '+%Y-%m-%d %H:%M:%S')] [github-auto-create] $1" >> "$LOG_FILE" 2>/dev/null || true
45
- }
46
-
47
- # ============================================================================
48
- # PRECONDITIONS
49
- # ============================================================================
50
-
51
- [[ ! -f "$SPEC_PATH" ]] && exit 0
52
- [[ ! -f "$CONFIG_PATH" ]] && exit 0
53
-
54
- # Template guard: skip if spec.md still contains placeholder markers
55
- grep -q '\[Story Title\]' "$SPEC_PATH" && { log "Skipping: spec.md still contains [Story Title] template markers"; exit 0; }
56
-
57
- command -v gh &>/dev/null || { log "gh CLI not found"; exit 0; }
58
- command -v jq &>/dev/null || { log "jq not found"; exit 0; }
59
-
60
- # Check GitHub sync enabled (shared 3-method detection)
61
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
62
- SHARED_LIB_PATHS=(
63
- "$SCRIPT_DIR/../../specweave/hooks/v2/lib/check-provider-enabled.sh"
64
- "$SCRIPT_DIR/../../../specweave/hooks/v2/lib/check-provider-enabled.sh"
65
- )
66
- for _lib in "${SHARED_LIB_PATHS[@]}"; do
67
- [[ -f "$_lib" ]] && { source "$_lib"; break; }
68
- done
69
-
70
- if type check_provider_enabled &>/dev/null; then
71
- check_provider_enabled "$CONFIG_PATH" "github" || { log "GitHub sync not enabled"; exit 0; }
72
- else
73
- GH_ENABLED=$(jq -r '.sync.github.enabled // false' "$CONFIG_PATH" 2>/dev/null)
74
- [[ "$GH_ENABLED" != "true" ]] && { log "GitHub sync not enabled"; exit 0; }
75
- fi
76
-
77
- # Check auto-create enabled
78
- # Two flags control auto-creation behavior:
79
- # - sync.autoSync (boolean): Global auto-sync toggle. When true, enables ALL auto-sync
80
- # operations including issue creation, progress sync, and status updates.
81
- # - hooks.post_increment_planning.auto_create_github_issue (boolean): Fine-grained toggle
82
- # for ONLY the auto-create-issue behavior in post-increment-planning hooks.
83
- #
84
- # Precedence: auto_create_github_issue (specific) > autoSync (global)
85
- # If BOTH are set, auto_create_github_issue takes precedence for issue creation.
86
- # If ONLY autoSync is true, issue creation is enabled (as part of the global auto-sync).
87
- AUTO_SYNC=$(jq -r '.sync.autoSync // false' "$CONFIG_PATH" 2>/dev/null)
88
- AUTO_CREATE=$(jq -r '.hooks.post_increment_planning.auto_create_github_issue // false' "$CONFIG_PATH" 2>/dev/null)
89
-
90
- # Warn if only one flag is set (likely misconfiguration)
91
- if [[ "$AUTO_SYNC" == "true" ]] && [[ "$AUTO_CREATE" == "false" ]]; then
92
- log "Warning: sync.autoSync=true but auto_create_github_issue=false. Issue creation enabled via autoSync. Set auto_create_github_issue=true to be explicit."
93
- elif [[ "$AUTO_SYNC" == "false" ]] && [[ "$AUTO_CREATE" == "true" ]]; then
94
- log "Warning: auto_create_github_issue=true but sync.autoSync=false. Only issue creation is enabled; other sync operations are disabled."
95
- fi
96
-
97
- if [[ "$AUTO_SYNC" != "true" ]] && [[ "$AUTO_CREATE" != "true" ]]; then
98
- log "Auto-sync disabled (autoSync=$AUTO_SYNC, auto_create=$AUTO_CREATE)"
99
- exit 0
100
- fi
101
-
102
- # Log which flag triggered activation
103
- if [[ "$AUTO_CREATE" == "true" ]]; then
104
- log "Auto-create enabled via hooks.post_increment_planning.auto_create_github_issue"
105
- else
106
- log "Auto-create enabled via sync.autoSync (global toggle)"
107
- fi
108
-
109
- # Get owner/repo
110
- OWNER=$(jq -r '.sync.github.owner // ""' "$CONFIG_PATH" 2>/dev/null)
111
- REPO=$(jq -r '.sync.github.repo // ""' "$CONFIG_PATH" 2>/dev/null)
112
- if [[ -z "$OWNER" ]] || [[ -z "$REPO" ]]; then
113
- log "GitHub owner/repo not configured"
114
- exit 0
115
- fi
116
- REPO_SLUG="$OWNER/$REPO"
117
-
118
- # Get project name from config for labels
119
- PROJECT_NAME=$(jq -r '.project.name // "specweave"' "$CONFIG_PATH" 2>/dev/null)
120
- PROJECT_LABEL="project:${PROJECT_NAME}"
121
-
122
- # ============================================================================
123
- # CIRCUIT BREAKER
124
- # ============================================================================
125
-
126
- CB_FILE="$STATE_DIR/.hook-circuit-breaker-github-auto-create"
127
- if [[ -f "$CB_FILE" ]]; then
128
- CB_COUNT=$(cat "$CB_FILE" 2>/dev/null || echo 0)
129
- if (( CB_COUNT >= 3 )); then
130
- log "Circuit breaker OPEN ($CB_COUNT failures). Skipping. Delete $CB_FILE to reset."
131
- exit 0
132
- fi
133
- fi
134
-
135
- # ============================================================================
136
- # DEBOUNCE (30s window — spec.md may be written multiple times during planning)
137
- # ============================================================================
138
-
139
- if [[ "${SPECWEAVE_SKIP_DEBOUNCE:-0}" != "1" ]]; then
140
- DEBOUNCE_FILE="$STATE_DIR/.github-auto-create-pending-$INC_ID"
141
- if [[ -f "$DEBOUNCE_FILE" ]]; then
142
- # POSIX-portable: use perl for mtime (works on macOS and Linux)
143
- FILE_MTIME=$(perl -e 'print((stat($ARGV[0]))[9])' "$DEBOUNCE_FILE" 2>/dev/null || echo 0)
144
- SIGNAL_AGE=$(($(date +%s) - FILE_MTIME))
145
- if (( SIGNAL_AGE < 30 )); then
146
- log "Debounce: signal age ${SIGNAL_AGE}s < 30s. Deferring."
147
- exit 0
148
- fi
149
- fi
150
- echo "$(date +%s)" > "$DEBOUNCE_FILE" 2>/dev/null || true
151
- sleep 30
152
- # Check if a newer invocation took over
153
- if [[ -f "$DEBOUNCE_FILE" ]]; then
154
- CURRENT_SIGNAL=$(cat "$DEBOUNCE_FILE" 2>/dev/null || echo 0)
155
- NOW=$(date +%s)
156
- SIGNAL_AGE=$((NOW - CURRENT_SIGNAL))
157
- if (( SIGNAL_AGE > 32 )); then
158
- log "Debounce: newer invocation detected. Exiting."
159
- exit 0
160
- fi
161
- fi
162
- rm -f "$DEBOUNCE_FILE" 2>/dev/null || true
163
- fi
164
-
165
- # ============================================================================
166
- # CHECK IF ISSUES ALREADY EXIST
167
- # ============================================================================
168
-
169
- if [[ -f "$META_PATH" ]]; then
170
- # Check BOTH new (externalLinks) and old (github.issues[]) formats for existing issues
171
- ISSUE_COUNT=$(jq '
172
- [
173
- (.externalLinks.github.issues // {} | to_entries[] | select(.value.issueNumber != null)),
174
- (.github.issues // [] | .[] | select(.number != null))
175
- ] | length
176
- ' "$META_PATH" 2>/dev/null || echo 0)
177
- if (( ISSUE_COUNT > 0 )); then
178
- log "Issues already exist for $INC_ID ($ISSUE_COUNT). Skipping."
179
- exit 0
180
- fi
181
- fi
182
-
183
- # ============================================================================
184
- # PARSE USER STORIES FROM SPEC.MD
185
- # ============================================================================
186
-
187
- # Get default priority from metadata.json
188
- DEFAULT_PRIORITY="P2"
189
- if [[ -f "$META_PATH" ]]; then
190
- META_PRIORITY=$(jq -r '.priority // "P2"' "$META_PATH" 2>/dev/null)
191
- [[ -n "$META_PRIORITY" ]] && DEFAULT_PRIORITY="$META_PRIORITY"
192
- fi
193
-
194
- log "Parsing user stories from $SPEC_PATH..."
195
-
196
- # Arrays to hold parsed user stories
197
- declare -a US_IDS US_TITLES US_PRIORITIES
198
- US_COUNT=0
199
-
200
- while IFS= read -r line; do
201
- # Match: ### US-001: Title Here (P1) — with priority
202
- if [[ "$line" =~ ^###[[:space:]]+(US-[0-9]+):[[:space:]]+(.+)[[:space:]]+\((P[0-9])\)[[:space:]]*$ ]]; then
203
- US_IDS+=("${BASH_REMATCH[1]}")
204
- US_TITLES+=("${BASH_REMATCH[2]}")
205
- US_PRIORITIES+=("${BASH_REMATCH[3]}")
206
- ((US_COUNT++))
207
- # Match: ### US-001: Title Here — without priority
208
- elif [[ "$line" =~ ^###[[:space:]]+(US-[0-9]+):[[:space:]]+(.+)[[:space:]]*$ ]]; then
209
- US_IDS+=("${BASH_REMATCH[1]}")
210
- US_TITLES+=("${BASH_REMATCH[2]}")
211
- # Try to extract priority from metadata.json or default to P2
212
- US_PRIORITIES+=("${DEFAULT_PRIORITY:-P2}")
213
- ((US_COUNT++))
214
- fi
215
- done < "$SPEC_PATH"
216
-
217
- if (( US_COUNT == 0 )); then
218
- log "No user stories found in spec.md. Skipping."
219
- exit 0
220
- fi
221
-
222
- log "Found $US_COUNT user stories. Creating GitHub issues..."
223
-
224
- # Get increment title from frontmatter
225
- INC_TITLE=$(grep -m1 '^title:' "$SPEC_PATH" | sed 's/^title:[[:space:]]*//' | sed 's/^"//' | sed 's/"$//')
226
-
227
- # ============================================================================
228
- # DERIVE FEATURE ID (ADR-0187: FS-{number} from increment number)
229
- # ============================================================================
230
- # Extract numeric prefix from increment ID: "0192-github-sync-v2-multi-repo" → "0192"
231
- INC_NUM=$(echo "$INC_ID" | grep -oE '^[0-9]+')
232
- # Strip leading zeros: "0192" → "192", "0001" → "1"
233
- FEATURE_NUM=$(echo "$INC_NUM" | sed 's/^0*//')
234
- [[ -z "$FEATURE_NUM" ]] && FEATURE_NUM="0"
235
- FEATURE_ID="FS-${FEATURE_NUM}"
236
- log "Derived Feature ID: $FEATURE_ID (from increment $INC_ID)"
237
-
238
- # ============================================================================
239
- # CREATE MILESTONE (one per increment, idempotent)
240
- # ============================================================================
241
-
242
- # Milestone uses Feature ID format: "FS-192: Short Title"
243
- MILESTONE_TITLE="${FEATURE_ID}"
244
- MILESTONE_NUM=""
245
-
246
- # Check existing milestones (list all, including closed)
247
- EXISTING_MS=$(gh api "repos/$REPO_SLUG/milestones?state=all&per_page=100" --jq ".[] | select(.title == \"$MILESTONE_TITLE\") | .number" 2>/dev/null || echo "")
248
- if [[ -n "$EXISTING_MS" ]]; then
249
- MILESTONE_NUM="$EXISTING_MS"
250
- log "Using existing milestone #$MILESTONE_NUM"
251
- else
252
- MS_RESULT=$(gh api "repos/$REPO_SLUG/milestones" \
253
- -f "title=$MILESTONE_TITLE" \
254
- -f "description=$INC_TITLE" \
255
- --method POST --jq '.number' 2>/dev/null || echo "")
256
- if [[ -n "$MS_RESULT" ]] && [[ "$MS_RESULT" =~ ^[0-9]+$ ]]; then
257
- MILESTONE_NUM="$MS_RESULT"
258
- log "Created milestone #$MILESTONE_NUM: $MILESTONE_TITLE"
259
- else
260
- log "Warning: Failed to create milestone (continuing without it)"
261
- fi
262
- fi
263
-
264
- # ============================================================================
265
- # ENSURE REQUIRED LABELS EXIST (idempotent)
266
- # ============================================================================
267
-
268
- ensure_label() {
269
- local label_name="$1"
270
- local label_color="${2:-ededed}"
271
- local label_desc="${3:-}"
272
- gh label create "$label_name" --repo "$REPO_SLUG" --color "$label_color" \
273
- --description "$label_desc" --force 2>/dev/null || true
274
- }
275
-
276
- # Core labels
277
- ensure_label "user-story" "0075ca" "User story from SpecWeave spec"
278
- ensure_label "specweave" "6f42c1" "Managed by SpecWeave"
279
- ensure_label "$PROJECT_LABEL" "c5def5" "$PROJECT_NAME project"
280
- ensure_label "status:active" "0e8a16" "Active work item"
281
- ensure_label "status:completed" "6f42c1" "Completed work item"
282
- ensure_label "status:not_started" "fbca04" "User story not started"
283
-
284
- # Priority labels (collect unique priorities from parsed user stories)
285
- for i in $(seq 0 $((US_COUNT - 1))); do
286
- PRIO_LABEL="priority:$(echo "${US_PRIORITIES[$i]}" | tr '[:upper:]' '[:lower:]')"
287
- case "${US_PRIORITIES[$i]}" in
288
- P1) ensure_label "$PRIO_LABEL" "b60205" "Critical priority" ;;
289
- P2) ensure_label "$PRIO_LABEL" "fbca04" "Medium priority" ;;
290
- P3) ensure_label "$PRIO_LABEL" "0e8a16" "Low priority" ;;
291
- *) ensure_label "$PRIO_LABEL" "ededed" "Priority label" ;;
292
- esac
293
- done
294
-
295
- log "Labels ensured"
296
-
297
- # ============================================================================
298
- # EXTRACT ACCEPTANCE CRITERIA FOR EACH USER STORY
299
- # ============================================================================
300
-
301
- # Function to extract ACs for a given US from spec.md
302
- # Handles both formats:
303
- # - [ ] **AC-US1-01**: Description (bold AC ID)
304
- # - [ ] AC-US1-01: Description (plain AC ID)
305
- extract_acs_for_us() {
306
- local us_id="$1"
307
- local in_section=false
308
- local in_ac=false
309
- local acs=""
310
-
311
- while IFS= read -r line; do
312
- # Start of our US section
313
- if [[ "$line" =~ ^###[[:space:]]+"$us_id": ]] || [[ "$line" =~ ^###[[:space:]]+"$us_id"[[:space:]] ]]; then
314
- in_section=true
315
- continue
316
- fi
317
- # Start of next US section or next ## section — stop
318
- if $in_section && { [[ "$line" =~ ^###[[:space:]]+US- ]] || [[ "$line" =~ ^##[[:space:]] ]]; }; then
319
- break
320
- fi
321
- # Inside our section, look for AC lines
322
- if $in_section; then
323
- # Acceptance Criteria heading: both bold (**AC**) and heading (#### AC) formats
324
- if [[ "$line" =~ ^\*\*Acceptance[[:space:]]Criteria ]] || [[ "$line" =~ ^#+[[:space:]]+Acceptance[[:space:]]Criteria ]]; then
325
- in_ac=true
326
- continue
327
- fi
328
- # Format 1: Bold AC ID — - [ ] **AC-US1-01**: Description
329
- if $in_ac && [[ "$line" =~ ^-[[:space:]]+\[(.)\][[:space:]]+\*\*([^*]+)\*\*:[[:space:]]*(.*) ]]; then
330
- local checked="${BASH_REMATCH[1]}"
331
- local ac_id="${BASH_REMATCH[2]}"
332
- local ac_desc="${BASH_REMATCH[3]}"
333
- if [[ "$checked" == "x" ]]; then
334
- acs+="- [x] **${ac_id}**: ${ac_desc}"$'\n'
335
- else
336
- acs+="- [ ] **${ac_id}**: ${ac_desc}"$'\n'
337
- fi
338
- continue
339
- fi
340
- # Format 2: Plain AC ID — - [ ] AC-US1-01: Description
341
- if $in_ac && [[ "$line" =~ ^-[[:space:]]+\[(.)\][[:space:]]+(AC-[^:]+):[[:space:]]*(.*) ]]; then
342
- local checked="${BASH_REMATCH[1]}"
343
- local ac_id="${BASH_REMATCH[2]}"
344
- local ac_desc="${BASH_REMATCH[3]}"
345
- if [[ "$checked" == "x" ]]; then
346
- acs+="- [x] **${ac_id}**: ${ac_desc}"$'\n'
347
- else
348
- acs+="- [ ] **${ac_id}**: ${ac_desc}"$'\n'
349
- fi
350
- continue
351
- fi
352
- # Stop ACs at section divider or next heading
353
- if $in_ac && [[ "$line" =~ ^---$ ]]; then
354
- break
355
- fi
356
- fi
357
- done < "$SPEC_PATH"
358
-
359
- echo "$acs"
360
- }
361
-
362
- # Function to extract description for a given US
363
- # Collects ALL text between the US heading and the Acceptance Criteria / next section
364
- extract_desc_for_us() {
365
- local us_id="$1"
366
- local in_section=false
367
- local desc=""
368
-
369
- while IFS= read -r line; do
370
- if [[ "$line" =~ ^###[[:space:]]+"$us_id": ]]; then
371
- in_section=true
372
- continue
373
- fi
374
- if $in_section; then
375
- # Stop at next US heading, next ## section, or Acceptance Criteria
376
- # Match ## but NOT ### or ####: ^##[[:space:]] catches "## Heading"
377
- # Match ### US- for next user story: ^###[[:space:]]+US-
378
- if [[ "$line" =~ ^###[[:space:]]+US- ]] || [[ "$line" =~ ^##[[:space:]] ]]; then
379
- break
380
- fi
381
- # Acceptance Criteria heading: both bold (**AC**) and heading (#### AC) formats
382
- if [[ "$line" =~ ^\*\*Acceptance[[:space:]]Criteria ]] || [[ "$line" =~ ^#+[[:space:]]+Acceptance[[:space:]]Criteria ]]; then
383
- break
384
- fi
385
- # Collect non-empty lines as description
386
- if [[ -n "$line" ]]; then
387
- desc+="$line"$'\n'
388
- fi
389
- fi
390
- done < "$SPEC_PATH"
391
-
392
- echo "$desc"
393
- }
394
-
395
- # ============================================================================
396
- # CREATE ISSUES
397
- # ============================================================================
398
-
399
- CREATED_JSON="{}"
400
- ERRORS=0
401
-
402
- for i in $(seq 0 $((US_COUNT - 1))); do
403
- US_ID="${US_IDS[$i]}"
404
- US_TITLE="${US_TITLES[$i]}"
405
- US_PRIORITY="${US_PRIORITIES[$i]}"
406
-
407
- ISSUE_TITLE="[${FEATURE_ID}][${US_ID}] ${US_TITLE}"
408
-
409
- # Check if issue already exists (by title prefix search — match both old and new format)
410
- EXISTING=$(gh issue list --repo "$REPO_SLUG" --search "[${FEATURE_ID}][${US_ID}] in:title" --json number,url --limit 1 2>/dev/null || echo "[]")
411
- EXISTING_NUM=$(echo "$EXISTING" | jq -r '.[0].number // empty' 2>/dev/null)
412
-
413
- if [[ -n "$EXISTING_NUM" ]]; then
414
- EXISTING_URL=$(echo "$EXISTING" | jq -r '.[0].url // empty' 2>/dev/null)
415
- log "Issue already exists for $US_ID: #$EXISTING_NUM"
416
- CREATED_JSON=$(echo "$CREATED_JSON" | jq --arg us "$US_ID" --arg num "$EXISTING_NUM" --arg url "$EXISTING_URL" \
417
- '. + {($us): {"issueNumber": ($num | tonumber), "issueUrl": $url, "status": "existing"}}' 2>/dev/null)
418
- continue
419
- fi
420
-
421
- # Build issue body
422
- US_DESC=$(extract_desc_for_us "$US_ID")
423
- # Ensure blank line after description for proper markdown paragraph separation
424
- [[ -n "$US_DESC" ]] && US_DESC="${US_DESC}"$'\n'
425
- US_ACS=$(extract_acs_for_us "$US_ID")
426
-
427
- ISSUE_BODY="## Description
428
-
429
- ${US_DESC}
430
- **Priority**: ${US_PRIORITY}
431
-
432
- ## Acceptance Criteria
433
-
434
- <!-- specweave:ac-start -->
435
- ${US_ACS}<!-- specweave:ac-end -->
436
-
437
- ---
438
- **Feature**: ${FEATURE_ID} | **Increment**: \`${INC_ID}\`
439
- <!-- specweave:sync feature=${FEATURE_ID} increment=${INC_ID} us=${US_ID} -->"
440
-
441
- # Create the issue
442
- CREATE_ARGS=(
443
- "issue" "create"
444
- "--repo" "$REPO_SLUG"
445
- "--title" "$ISSUE_TITLE"
446
- "--body" "$ISSUE_BODY"
447
- "--label" "user-story"
448
- "--label" "specweave"
449
- "--label" "$PROJECT_LABEL"
450
- "--label" "status:active"
451
- "--label" "priority:$(echo "$US_PRIORITY" | tr '[:upper:]' '[:lower:]')"
452
- )
453
-
454
- # Add milestone if available
455
- if [[ -n "$MILESTONE_NUM" ]]; then
456
- CREATE_ARGS+=("--milestone" "$MILESTONE_TITLE")
457
- fi
458
-
459
- RESULT=$(gh "${CREATE_ARGS[@]}" 2>&1)
460
- CREATE_EXIT=$?
461
-
462
- if [[ $CREATE_EXIT -ne 0 ]]; then
463
- log "ERROR creating issue for $US_ID: $RESULT"
464
- ((ERRORS++))
465
-
466
- # Update circuit breaker
467
- CB_VAL=$(cat "$CB_FILE" 2>/dev/null || echo 0)
468
- echo "$((CB_VAL + 1))" > "$CB_FILE" 2>/dev/null || true
469
- continue
470
- fi
471
-
472
- # Parse issue URL from result (gh outputs the URL on success)
473
- ISSUE_URL=$(echo "$RESULT" | grep -oE 'https://github.com/[^ ]+' | head -1)
474
- ISSUE_NUM=$(echo "$ISSUE_URL" | grep -oE '[0-9]+$')
475
-
476
- if [[ -n "$ISSUE_NUM" ]]; then
477
- log "Created issue #$ISSUE_NUM for $US_ID: $ISSUE_URL"
478
- CREATED_JSON=$(echo "$CREATED_JSON" | jq --arg us "$US_ID" --arg num "$ISSUE_NUM" --arg url "$ISSUE_URL" \
479
- '. + {($us): {"issueNumber": ($num | tonumber), "issueUrl": $url, "status": "created"}}' 2>/dev/null)
480
- fi
481
- done
482
-
483
- # ============================================================================
484
- # UPDATE METADATA.JSON WITH EXTERNAL LINKS
485
- # ============================================================================
486
-
487
- if [[ -f "$META_PATH" ]]; then
488
- UPDATED_META=$(jq --argjson issues "$CREATED_JSON" --arg ms "${MILESTONE_NUM:-}" \
489
- '.externalLinks.github = {
490
- "issues": $issues,
491
- "milestone": (if $ms != "" then ($ms | tonumber) else null end),
492
- "syncedAt": (now | strftime("%Y-%m-%dT%H:%M:%SZ"))
493
- }' "$META_PATH" 2>/dev/null)
494
-
495
- if [[ -n "$UPDATED_META" ]]; then
496
- echo "$UPDATED_META" > "$META_PATH" 2>/dev/null
497
- log "Updated metadata.json with GitHub issue links (externalLinks format)"
498
-
499
- # v1.0.240 FIX: Also write OLD format (metadata.github.issues[]) for reconciler compatibility
500
- # The reconciler and closure flows read metadata.github.issues[], not externalLinks
501
- OLD_FORMAT_JSON=$(echo "$CREATED_JSON" | jq '[to_entries[] | select(.value.issueNumber != null) | {
502
- userStory: .key,
503
- number: .value.issueNumber,
504
- url: .value.issueUrl,
505
- createdAt: (now | strftime("%Y-%m-%dT%H:%M:%SZ"))
506
- }]' 2>/dev/null)
507
-
508
- if [[ -n "$OLD_FORMAT_JSON" ]] && [[ "$OLD_FORMAT_JSON" != "[]" ]]; then
509
- UPDATED_META_V2=$(jq --argjson old_issues "$OLD_FORMAT_JSON" '
510
- if .github == null then .github = {} else . end |
511
- .github.issues = ((.github.issues // []) + $old_issues | unique_by(.userStory)) |
512
- .github.lastSync = (now | strftime("%Y-%m-%dT%H:%M:%SZ"))
513
- ' "$META_PATH" 2>/dev/null)
514
-
515
- if [[ -n "$UPDATED_META_V2" ]]; then
516
- echo "$UPDATED_META_V2" > "$META_PATH" 2>/dev/null
517
- log "Also backfilled old-format github.issues[] for reconciler"
518
- fi
519
- fi
520
- fi
521
- fi
522
-
523
- # ============================================================================
524
- # UPDATE SYNC METADATA
525
- # ============================================================================
526
-
527
- SYNC_META="$PROJECT_ROOT/.specweave/sync-metadata.json"
528
- if [[ -f "$SYNC_META" ]]; then
529
- jq --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
530
- '.github.lastSync = $ts | .github.lastSyncResult = "success"' \
531
- "$SYNC_META" > "${SYNC_META}.tmp" 2>/dev/null && \
532
- mv "${SYNC_META}.tmp" "$SYNC_META" 2>/dev/null
533
- fi
534
-
535
- # Reset circuit breaker on success
536
- if (( ERRORS == 0 )); then
537
- echo "0" > "$CB_FILE" 2>/dev/null || true
538
- fi
539
-
540
- CREATED_COUNT=$(echo "$CREATED_JSON" | jq 'to_entries | map(select(.value.status == "created")) | length' 2>/dev/null || echo 0)
541
- EXISTING_COUNT=$(echo "$CREATED_JSON" | jq 'to_entries | map(select(.value.status == "existing")) | length' 2>/dev/null || echo 0)
542
-
543
- log "Done: $CREATED_COUNT created, $EXISTING_COUNT existing, $ERRORS errors"
544
-
545
- # Output summary for user visibility (will appear as hook output)
546
- if (( CREATED_COUNT > 0 )); then
547
- echo ""
548
- echo " [GitHub Sync] Auto-created $CREATED_COUNT issue(s) for increment $INC_ID"
549
- echo " Milestone: $MILESTONE_TITLE"
550
- echo ""
551
- fi
552
-
553
- exit 0