specweave 0.33.2 → 0.33.4

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 (101) hide show
  1. package/CLAUDE.md +133 -19
  2. package/dist/plugins/specweave-ado/lib/per-us-sync.d.ts +120 -0
  3. package/dist/plugins/specweave-ado/lib/per-us-sync.d.ts.map +1 -0
  4. package/dist/plugins/specweave-ado/lib/per-us-sync.js +276 -0
  5. package/dist/plugins/specweave-ado/lib/per-us-sync.js.map +1 -0
  6. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts +4 -1
  7. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
  8. package/dist/plugins/specweave-github/lib/github-client-v2.js +13 -3
  9. package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
  10. package/dist/plugins/specweave-github/lib/per-us-sync.d.ts +97 -0
  11. package/dist/plugins/specweave-github/lib/per-us-sync.d.ts.map +1 -0
  12. package/dist/plugins/specweave-github/lib/per-us-sync.js +274 -0
  13. package/dist/plugins/specweave-github/lib/per-us-sync.js.map +1 -0
  14. package/dist/plugins/specweave-jira/lib/per-us-sync.d.ts +113 -0
  15. package/dist/plugins/specweave-jira/lib/per-us-sync.d.ts.map +1 -0
  16. package/dist/plugins/specweave-jira/lib/per-us-sync.js +254 -0
  17. package/dist/plugins/specweave-jira/lib/per-us-sync.js.map +1 -0
  18. package/dist/src/cli/cleanup-zombies.js +8 -5
  19. package/dist/src/cli/cleanup-zombies.js.map +1 -1
  20. package/dist/src/config/types.d.ts +203 -1208
  21. package/dist/src/config/types.d.ts.map +1 -1
  22. package/dist/src/core/config/config-manager.d.ts.map +1 -1
  23. package/dist/src/core/config/config-manager.js +58 -0
  24. package/dist/src/core/config/config-manager.js.map +1 -1
  25. package/dist/src/core/config/types.d.ts +80 -0
  26. package/dist/src/core/config/types.d.ts.map +1 -1
  27. package/dist/src/core/config/types.js.map +1 -1
  28. package/dist/src/core/living-docs/cross-project-sync.d.ts +87 -15
  29. package/dist/src/core/living-docs/cross-project-sync.d.ts.map +1 -1
  30. package/dist/src/core/living-docs/cross-project-sync.js +147 -28
  31. package/dist/src/core/living-docs/cross-project-sync.js.map +1 -1
  32. package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
  33. package/dist/src/core/living-docs/living-docs-sync.js +26 -22
  34. package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
  35. package/dist/src/core/living-docs/types.d.ts +24 -3
  36. package/dist/src/core/living-docs/types.d.ts.map +1 -1
  37. package/dist/src/core/types/config.d.ts +79 -0
  38. package/dist/src/core/types/config.d.ts.map +1 -1
  39. package/dist/src/core/types/config.js.map +1 -1
  40. package/dist/src/importers/jira-importer.d.ts +10 -0
  41. package/dist/src/importers/jira-importer.d.ts.map +1 -1
  42. package/dist/src/importers/jira-importer.js +55 -5
  43. package/dist/src/importers/jira-importer.js.map +1 -1
  44. package/dist/src/init/architecture/types.d.ts +33 -140
  45. package/dist/src/init/architecture/types.d.ts.map +1 -1
  46. package/dist/src/init/compliance/types.d.ts +30 -27
  47. package/dist/src/init/compliance/types.d.ts.map +1 -1
  48. package/dist/src/init/repo/types.d.ts +11 -34
  49. package/dist/src/init/repo/types.d.ts.map +1 -1
  50. package/dist/src/init/research/src/config/types.d.ts +15 -82
  51. package/dist/src/init/research/src/config/types.d.ts.map +1 -1
  52. package/dist/src/init/research/types.d.ts +38 -93
  53. package/dist/src/init/research/types.d.ts.map +1 -1
  54. package/dist/src/init/team/types.d.ts +4 -42
  55. package/dist/src/init/team/types.d.ts.map +1 -1
  56. package/dist/src/sync/closure-metrics.d.ts +102 -0
  57. package/dist/src/sync/closure-metrics.d.ts.map +1 -0
  58. package/dist/src/sync/closure-metrics.js +267 -0
  59. package/dist/src/sync/closure-metrics.js.map +1 -0
  60. package/dist/src/sync/sync-coordinator.d.ts +49 -0
  61. package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
  62. package/dist/src/sync/sync-coordinator.js +399 -37
  63. package/dist/src/sync/sync-coordinator.js.map +1 -1
  64. package/dist/src/utils/notification-constants.d.ts +85 -0
  65. package/dist/src/utils/notification-constants.d.ts.map +1 -0
  66. package/dist/src/utils/notification-constants.js +129 -0
  67. package/dist/src/utils/notification-constants.js.map +1 -0
  68. package/dist/src/utils/platform-utils.d.ts +13 -3
  69. package/dist/src/utils/platform-utils.d.ts.map +1 -1
  70. package/dist/src/utils/platform-utils.js +17 -6
  71. package/dist/src/utils/platform-utils.js.map +1 -1
  72. package/dist/src/utils/project-resolver.d.ts +156 -0
  73. package/dist/src/utils/project-resolver.d.ts.map +1 -0
  74. package/dist/src/utils/project-resolver.js +587 -0
  75. package/dist/src/utils/project-resolver.js.map +1 -0
  76. package/package.json +1 -1
  77. package/plugins/specweave/commands/specweave-increment.md +46 -0
  78. package/plugins/specweave/commands/specweave-jobs.md +153 -8
  79. package/plugins/specweave/hooks/hooks.json +10 -0
  80. package/plugins/specweave/hooks/spec-project-validator.sh +24 -2
  81. package/plugins/specweave/hooks/universal/hook-wrapper.cmd +26 -26
  82. package/plugins/specweave/hooks/universal/session-start.cmd +16 -16
  83. package/plugins/specweave/hooks/universal/session-start.ps1 +16 -16
  84. package/plugins/specweave/hooks/user-prompt-submit.sh +105 -3
  85. package/plugins/specweave/hooks/v2/guards/per-us-project-validator.sh +281 -0
  86. package/plugins/specweave/hooks/v2/handlers/living-specs-handler.sh +29 -0
  87. package/plugins/specweave/scripts/session-watchdog.sh +278 -130
  88. package/plugins/specweave/skills/increment-planner/SKILL.md +48 -18
  89. package/plugins/specweave/skills/increment-planner/templates/spec-multi-project.md +27 -14
  90. package/plugins/specweave/skills/increment-planner/templates/spec-single-project.md +16 -5
  91. package/plugins/specweave/skills/spec-generator/SKILL.md +74 -15
  92. package/plugins/specweave-ado/lib/per-us-sync.js +247 -0
  93. package/plugins/specweave-ado/lib/per-us-sync.ts +410 -0
  94. package/plugins/specweave-github/lib/github-client-v2.js +10 -3
  95. package/plugins/specweave-github/lib/github-client-v2.ts +15 -3
  96. package/plugins/specweave-github/lib/per-us-sync.js +241 -0
  97. package/plugins/specweave-github/lib/per-us-sync.ts +375 -0
  98. package/plugins/specweave-jira/lib/per-us-sync.js +224 -0
  99. package/plugins/specweave-jira/lib/per-us-sync.ts +366 -0
  100. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +0 -738
  101. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +0 -1107
@@ -0,0 +1,281 @@
1
+ #!/bin/bash
2
+ #
3
+ # per-us-project-validator.sh
4
+ #
5
+ # Pre-tool-use hook that validates EACH User Story in spec.md has
6
+ # **Project**: (and **Board**: for 2-level) fields.
7
+ #
8
+ # This is the CRITICAL ENFORCEMENT for increment 0137.
9
+ #
10
+ # Activation:
11
+ # - tool_name: Write (also Edit if full content provided)
12
+ # - file_path matches: .specweave/increments/*/spec.md
13
+ #
14
+ # Rules:
15
+ # - Each ### US-XXX section MUST have **Project**: <value> on next few lines
16
+ # - For 2-level structures, each US MUST also have **Board**: <value>
17
+ # - Project values MUST match configured projects (not generic keywords)
18
+ # - Fallback allowed for existing specs via SPECWEAVE_LEGACY_SPEC=1
19
+ #
20
+ # Returns exit code 1 (block) if validation fails, 0 (allow) otherwise.
21
+ #
22
+ # Bypass:
23
+ # - SPECWEAVE_FORCE_PROJECT=1 - Skip all project validation
24
+ # - SPECWEAVE_LEGACY_SPEC=1 - Allow specs without per-US project (legacy mode)
25
+ #
26
+
27
+ set -e
28
+
29
+ # Source common utilities if available
30
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
31
+ PLUGIN_ROOT="${SCRIPT_DIR}/../.."
32
+
33
+ # Logging (silent by default)
34
+ log_debug() {
35
+ if [ "$SPECWEAVE_DEBUG" = "1" ]; then
36
+ echo "[per-us-project] $*" >&2
37
+ fi
38
+ }
39
+
40
+ # Check for force bypass
41
+ if [ "$SPECWEAVE_FORCE_PROJECT" = "1" ]; then
42
+ echo '{"decision": "allow", "message": "⚠️ Per-US project validation bypassed (SPECWEAVE_FORCE_PROJECT=1)"}'
43
+ exit 0
44
+ fi
45
+
46
+ # Check for legacy mode bypass
47
+ if [ "$SPECWEAVE_LEGACY_SPEC" = "1" ]; then
48
+ echo '{"decision": "allow", "message": "⚠️ Per-US project validation skipped (SPECWEAVE_LEGACY_SPEC=1 - legacy mode)"}'
49
+ exit 0
50
+ fi
51
+
52
+ # Read tool input from stdin
53
+ INPUT=$(cat)
54
+
55
+ # Extract tool name
56
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
57
+
58
+ # Only validate Write tool calls (Edit doesn't provide full content)
59
+ if [ "$TOOL_NAME" != "Write" ]; then
60
+ echo '{"decision": "allow"}'
61
+ exit 0
62
+ fi
63
+
64
+ # Extract file path
65
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
66
+
67
+ # Only validate spec.md files in increments folder
68
+ if [[ ! "$FILE_PATH" =~ \.specweave/increments/[0-9]{3,4}E?-[^/]+/spec\.md$ ]]; then
69
+ echo '{"decision": "allow"}'
70
+ exit 0
71
+ fi
72
+
73
+ log_debug "Validating per-US project fields for: $FILE_PATH"
74
+
75
+ # Extract file content
76
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // ""')
77
+
78
+ # Count User Stories (### US-XXX pattern)
79
+ US_PATTERN='### US-[0-9]+'
80
+ TOTAL_US=$(echo "$CONTENT" | grep -cE "$US_PATTERN" || echo 0)
81
+
82
+ log_debug "Total User Stories found: $TOTAL_US"
83
+
84
+ # If no user stories, allow (might be tasks-only spec or overview)
85
+ if [ "$TOTAL_US" -eq 0 ]; then
86
+ echo '{"decision": "allow"}'
87
+ exit 0
88
+ fi
89
+
90
+ # Extract User Story sections and check for **Project**: field
91
+ # Strategy: For each ### US-XXX, look at next 10 lines for **Project**:
92
+
93
+ MISSING_PROJECT=()
94
+ MISSING_BOARD=()
95
+ MULTI_PROJECT=() # USs with multiple projects (comma-separated)
96
+ MULTI_BOARD=() # USs with multiple boards (comma-separated)
97
+ US_WITH_PROJECT=0
98
+
99
+ # Use awk to extract US sections and check for Project field
100
+ while IFS= read -r us_line; do
101
+ # Extract US ID (e.g., US-001)
102
+ US_ID=$(echo "$us_line" | grep -oE 'US-[0-9]+' | head -1)
103
+
104
+ if [ -z "$US_ID" ]; then
105
+ continue
106
+ fi
107
+
108
+ # Get line number of this US heading
109
+ LINE_NUM=$(echo "$CONTENT" | grep -nE "^### $US_ID:" | head -1 | cut -d: -f1)
110
+
111
+ if [ -z "$LINE_NUM" ]; then
112
+ continue
113
+ fi
114
+
115
+ # Extract next 10 lines after heading
116
+ SECTION=$(echo "$CONTENT" | tail -n +$((LINE_NUM + 1)) | head -n 10)
117
+
118
+ # Check for **Project**: field
119
+ PROJECT_LINE=$(echo "$SECTION" | grep -E '^\*\*Project\*\*:\s*\S' | head -1)
120
+
121
+ if [ -n "$PROJECT_LINE" ]; then
122
+ US_WITH_PROJECT=$((US_WITH_PROJECT + 1))
123
+
124
+ # Extract project value
125
+ PROJECT_VALUE=$(echo "$PROJECT_LINE" | sed 's/^\*\*Project\*\*:\s*//' | tr -d '[:space:]')
126
+
127
+ # Check for comma (multiple projects - FORBIDDEN)
128
+ if echo "$PROJECT_VALUE" | grep -q ','; then
129
+ MULTI_PROJECT+=("$US_ID (has: $PROJECT_VALUE)")
130
+ log_debug "$US_ID has MULTIPLE projects (FORBIDDEN): $PROJECT_VALUE"
131
+ else
132
+ log_debug "$US_ID has **Project**: field ✓ ($PROJECT_VALUE)"
133
+ fi
134
+ else
135
+ MISSING_PROJECT+=("$US_ID")
136
+ log_debug "$US_ID MISSING **Project**: field ✗"
137
+ fi
138
+
139
+ done < <(echo "$CONTENT" | grep -E "^### US-[0-9]+:")
140
+
141
+ log_debug "User Stories with **Project**: $US_WITH_PROJECT / $TOTAL_US"
142
+
143
+ # Get project root for structure detection
144
+ PROJECT_ROOT="${FILE_PATH%%/.specweave/*}"
145
+ cd "$PROJECT_ROOT" 2>/dev/null || true
146
+
147
+ # Get structure level from config
148
+ STRUCTURE_LEVEL=1
149
+ if command -v specweave >/dev/null 2>&1; then
150
+ CONTEXT_OUTPUT=$(specweave context projects 2>/dev/null || echo '{"level": 1}')
151
+ STRUCTURE_LEVEL=$(echo "$CONTEXT_OUTPUT" | jq -r '.level // 1')
152
+ AVAILABLE_PROJECTS=$(echo "$CONTEXT_OUTPUT" | jq -r '.projects[].id // empty' | tr '\n' ', ' | sed 's/,$//')
153
+ else
154
+ # Fallback: check for 2-level indicators in config
155
+ if [ -f "$PROJECT_ROOT/.specweave/config.json" ]; then
156
+ HAS_AREA_PATH=$(jq -r '.sync.profiles | to_entries | map(select(.value.config.areaPathMapping or .value.config.areaPaths)) | length' "$PROJECT_ROOT/.specweave/config.json" 2>/dev/null || echo 0)
157
+ HAS_BOARD_MAPPING=$(jq -r '.sync.profiles | to_entries | map(select(.value.config.boardMapping.boards | length > 0)) | length' "$PROJECT_ROOT/.specweave/config.json" 2>/dev/null || echo 0)
158
+
159
+ if [ "$HAS_AREA_PATH" -gt 0 ] || [ "$HAS_BOARD_MAPPING" -gt 0 ]; then
160
+ STRUCTURE_LEVEL=2
161
+ fi
162
+ fi
163
+ fi
164
+
165
+ log_debug "Structure level: $STRUCTURE_LEVEL"
166
+
167
+ # For 2-level structures, also check for **Board**: field
168
+ if [ "$STRUCTURE_LEVEL" = "2" ]; then
169
+ while IFS= read -r us_line; do
170
+ US_ID=$(echo "$us_line" | grep -oE 'US-[0-9]+' | head -1)
171
+
172
+ if [ -z "$US_ID" ]; then
173
+ continue
174
+ fi
175
+
176
+ LINE_NUM=$(echo "$CONTENT" | grep -nE "^### $US_ID:" | head -1 | cut -d: -f1)
177
+
178
+ if [ -z "$LINE_NUM" ]; then
179
+ continue
180
+ fi
181
+
182
+ SECTION=$(echo "$CONTENT" | tail -n +$((LINE_NUM + 1)) | head -n 10)
183
+
184
+ # Check for **Board**: field
185
+ BOARD_LINE=$(echo "$SECTION" | grep -E '^\*\*Board\*\*:\s*\S' | head -1)
186
+
187
+ if [ -n "$BOARD_LINE" ]; then
188
+ # Extract board value
189
+ BOARD_VALUE=$(echo "$BOARD_LINE" | sed 's/^\*\*Board\*\*:\s*//' | tr -d '[:space:]')
190
+
191
+ # Check for comma (multiple boards - FORBIDDEN)
192
+ if echo "$BOARD_VALUE" | grep -q ','; then
193
+ MULTI_BOARD+=("$US_ID (has: $BOARD_VALUE)")
194
+ log_debug "$US_ID has MULTIPLE boards (FORBIDDEN): $BOARD_VALUE"
195
+ else
196
+ log_debug "$US_ID has **Board**: field ✓ ($BOARD_VALUE)"
197
+ fi
198
+ else
199
+ MISSING_BOARD+=("$US_ID")
200
+ log_debug "$US_ID MISSING **Board**: field ✗"
201
+ fi
202
+
203
+ done < <(echo "$CONTENT" | grep -E "^### US-[0-9]+:")
204
+ fi
205
+
206
+ # Build error message if validation fails
207
+ ERRORS=""
208
+
209
+ if [ ${#MISSING_PROJECT[@]} -gt 0 ]; then
210
+ MISSING_LIST=$(printf ", %s" "${MISSING_PROJECT[@]}")
211
+ MISSING_LIST=${MISSING_LIST:2} # Remove leading ", "
212
+
213
+ ERRORS="$ERRORS\n❌ User Stories MISSING **Project**: field:\n ${MISSING_LIST}\n"
214
+ ERRORS="$ERRORS\n Each User Story MUST have **Project**: <project_id> after the heading.\n"
215
+ ERRORS="$ERRORS\n Example:\n"
216
+ ERRORS="$ERRORS ### US-001: Login Form\n"
217
+ ERRORS="$ERRORS **Project**: frontend-app\n"
218
+
219
+ if [ -n "$AVAILABLE_PROJECTS" ]; then
220
+ ERRORS="$ERRORS\n Available projects: ${AVAILABLE_PROJECTS}\n"
221
+ fi
222
+ fi
223
+
224
+ if [ ${#MISSING_BOARD[@]} -gt 0 ]; then
225
+ MISSING_LIST=$(printf ", %s" "${MISSING_BOARD[@]}")
226
+ MISSING_LIST=${MISSING_LIST:2}
227
+
228
+ ERRORS="$ERRORS\n❌ User Stories MISSING **Board**: field (2-level structure):\n ${MISSING_LIST}\n"
229
+ ERRORS="$ERRORS\n For 2-level structures, each US MUST have **Board**: <board_id>\n"
230
+ ERRORS="$ERRORS\n Example:\n"
231
+ ERRORS="$ERRORS ### US-001: Login Form\n"
232
+ ERRORS="$ERRORS **Project**: acme-corp\n"
233
+ ERRORS="$ERRORS **Board**: mobile-team\n"
234
+ fi
235
+
236
+ # Check for multiple projects (1:1 violation)
237
+ if [ ${#MULTI_PROJECT[@]} -gt 0 ]; then
238
+ MULTI_LIST=$(printf ", %s" "${MULTI_PROJECT[@]}")
239
+ MULTI_LIST=${MULTI_LIST:2}
240
+
241
+ ERRORS="$ERRORS\n❌ User Stories with MULTIPLE projects (FORBIDDEN - 1:1 mapping required):\n ${MULTI_LIST}\n"
242
+ ERRORS="$ERRORS\n Each User Story MUST map to EXACTLY ONE project.\n"
243
+ ERRORS="$ERRORS Split cross-project features into separate USs:\n"
244
+ ERRORS="$ERRORS\n WRONG:\n"
245
+ ERRORS="$ERRORS ### US-001: OAuth Implementation\n"
246
+ ERRORS="$ERRORS **Project**: frontend-app, backend-api ← FORBIDDEN\n"
247
+ ERRORS="$ERRORS\n CORRECT:\n"
248
+ ERRORS="$ERRORS ### US-001: OAuth Login Form\n"
249
+ ERRORS="$ERRORS **Project**: frontend-app\n"
250
+ ERRORS="$ERRORS\n ### US-002: OAuth API Endpoints\n"
251
+ ERRORS="$ERRORS **Project**: backend-api\n"
252
+ fi
253
+
254
+ # Check for multiple boards (1:1 violation)
255
+ if [ ${#MULTI_BOARD[@]} -gt 0 ]; then
256
+ MULTI_LIST=$(printf ", %s" "${MULTI_BOARD[@]}")
257
+ MULTI_LIST=${MULTI_LIST:2}
258
+
259
+ ERRORS="$ERRORS\n❌ User Stories with MULTIPLE boards (FORBIDDEN - 1:1 mapping required):\n ${MULTI_LIST}\n"
260
+ ERRORS="$ERRORS\n Each User Story MUST map to EXACTLY ONE board.\n"
261
+ ERRORS="$ERRORS Split cross-board features into separate USs.\n"
262
+ fi
263
+
264
+ # If errors found, block the write
265
+ if [ -n "$ERRORS" ]; then
266
+ # Add bypass instructions
267
+ ERRORS="$ERRORS\n💡 To bypass this validation:\n"
268
+ ERRORS="$ERRORS - SPECWEAVE_FORCE_PROJECT=1 (skip all validation)\n"
269
+ ERRORS="$ERRORS - SPECWEAVE_LEGACY_SPEC=1 (allow specs without per-US project)\n"
270
+
271
+ # Escape for JSON
272
+ REASON=$(echo -e "$ERRORS" | jq -Rs .)
273
+
274
+ echo "{\"decision\": \"block\", \"reason\": $REASON}"
275
+ exit 0
276
+ fi
277
+
278
+ # All validations passed
279
+ log_debug "All $TOTAL_US User Stories have required fields ✓"
280
+ echo '{"decision": "allow"}'
281
+ exit 0
@@ -120,6 +120,35 @@ case "$EVENT" in
120
120
  run_with_timeout 30 node "$SYNC_SCRIPT" "$INC_ID" >/dev/null 2>&1 &
121
121
  fi
122
122
  fi
123
+
124
+ # ========================================================================
125
+ # CRITICAL FIX (v0.34.0): Close GitHub/JIRA/ADO issues on increment.done
126
+ # ========================================================================
127
+ # Root cause: sync-increment-closure.js was NEVER being called when
128
+ # increments completed. The EDA architecture routes increment.done to
129
+ # this handler, but only sync-living-docs.js was called (updates docs).
130
+ #
131
+ # sync-increment-closure.js contains the actual logic to:
132
+ # - Close GitHub issues linked to User Stories
133
+ # - Close JIRA/ADO issues (with proper gates)
134
+ # - Post completion comments on external issues
135
+ #
136
+ # Without this, GitHub issues stayed OPEN forever after increment closure!
137
+ # See: GitHub Issues #817-#822 (increment 0136) stayed open until manual fix
138
+ CLOSURE_SCRIPT=""
139
+ for path in \
140
+ "$PROJECT_ROOT/plugins/specweave/lib/hooks/sync-increment-closure.js" \
141
+ "$PROJECT_ROOT/dist/plugins/specweave/lib/hooks/sync-increment-closure.js" \
142
+ "${CLAUDE_PLUGIN_ROOT:-}/lib/hooks/sync-increment-closure.js"; do
143
+ [[ -f "$path" ]] && { CLOSURE_SCRIPT="$path"; break; }
144
+ done
145
+
146
+ if [[ -n "$CLOSURE_SCRIPT" ]]; then
147
+ cd "$PROJECT_ROOT" || exit 0
148
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] Calling sync-increment-closure.js for $INC_ID" >> "$LOG_FILE" 2>/dev/null
149
+ # Run closure sync in background (can take 10-30s for multiple issues)
150
+ run_with_timeout 60 node "$CLOSURE_SCRIPT" "$INC_ID" >> "$LOG_FILE" 2>&1 &
151
+ fi
123
152
  ;;
124
153
 
125
154
  increment.archived)