specweave 0.28.29 β†’ 0.28.33

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 (37) hide show
  1. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts +14 -0
  2. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
  3. package/dist/plugins/specweave-github/lib/github-client-v2.js +57 -0
  4. package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
  5. package/dist/src/cli/helpers/init/external-import.d.ts.map +1 -1
  6. package/dist/src/cli/helpers/init/external-import.js +51 -7
  7. package/dist/src/cli/helpers/init/external-import.js.map +1 -1
  8. package/dist/src/importers/import-coordinator.d.ts +1 -0
  9. package/dist/src/importers/import-coordinator.d.ts.map +1 -1
  10. package/dist/src/importers/import-coordinator.js +3 -0
  11. package/dist/src/importers/import-coordinator.js.map +1 -1
  12. package/dist/src/living-docs/fs-id-allocator.d.ts +32 -0
  13. package/dist/src/living-docs/fs-id-allocator.d.ts.map +1 -1
  14. package/dist/src/living-docs/fs-id-allocator.js +69 -0
  15. package/dist/src/living-docs/fs-id-allocator.js.map +1 -1
  16. package/dist/src/sync/github-reconciler.d.ts +94 -0
  17. package/dist/src/sync/github-reconciler.d.ts.map +1 -0
  18. package/dist/src/sync/github-reconciler.js +435 -0
  19. package/dist/src/sync/github-reconciler.js.map +1 -0
  20. package/package.json +1 -1
  21. package/plugins/specweave/hooks/hooks.json +10 -0
  22. package/plugins/specweave/hooks/post-increment-status-change.sh +123 -32
  23. package/plugins/specweave/hooks/session-start-reconcile.sh +139 -0
  24. package/plugins/specweave/lib/hooks/close-github-issues-abandoned.js +37 -0
  25. package/plugins/specweave/lib/hooks/close-github-issues-abandoned.ts +59 -0
  26. package/plugins/specweave/lib/hooks/reopen-github-issues.js +37 -0
  27. package/plugins/specweave/lib/hooks/reopen-github-issues.ts +59 -0
  28. package/plugins/specweave/lib/hooks/session-start-reconcile-worker.js +36 -0
  29. package/plugins/specweave/lib/hooks/session-start-reconcile-worker.ts +58 -0
  30. package/plugins/specweave/lib/vendor/sync/github-reconciler.d.ts +94 -0
  31. package/plugins/specweave/lib/vendor/sync/github-reconciler.js +435 -0
  32. package/plugins/specweave/lib/vendor/sync/github-reconciler.js.map +1 -0
  33. package/plugins/specweave-github/commands/specweave-github-reconcile.md +110 -0
  34. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +24 -0
  35. package/plugins/specweave-github/lib/github-client-v2.js +55 -0
  36. package/plugins/specweave-github/lib/github-client-v2.ts +68 -0
  37. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +36 -0
@@ -1,15 +1,15 @@
1
1
  #!/bin/bash
2
2
 
3
- # SpecWeave Post-Increment-Status-Change Hook
3
+ # SpecWeave Post-Increment-Status-Change Hook (v0.28.33)
4
4
  # Runs automatically after increment status changes (pause/resume/abandon)
5
5
  #
6
6
  # Trigger: /specweave:pause, /specweave:resume, /specweave:abandon commands
7
- # Purpose: Notify GitHub when increment status changes
7
+ # Purpose: Sync GitHub issue state with increment status
8
8
  #
9
9
  # What it does:
10
10
  # 1. Detects status change (paused, resumed, abandoned)
11
11
  # 2. Posts comment to GitHub issue
12
- # 3. Updates metadata
12
+ # 3. NEW (v0.28.33): Reopens issues when resumed, closes when abandoned
13
13
  #
14
14
  # Usage:
15
15
  # ./post-increment-status-change.sh <incrementId> <newStatus> <reason>
@@ -43,6 +43,7 @@ cd "$PROJECT_ROOT" 2>/dev/null || true
43
43
  # Configuration
44
44
  LOGS_DIR=".specweave/logs"
45
45
  DEBUG_LOG="$LOGS_DIR/hooks-debug.log"
46
+ HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
46
47
 
47
48
  mkdir -p "$LOGS_DIR" 2>/dev/null || true
48
49
 
@@ -61,7 +62,7 @@ echo "[$(date)] πŸ“Š Status changed: $NEW_STATUS" >> "$DEBUG_LOG" 2>/dev/null ||
61
62
 
62
63
  # Validate status
63
64
  case "$NEW_STATUS" in
64
- paused|resumed|abandoned)
65
+ paused|resumed|abandoned|active|in-progress)
65
66
  ;;
66
67
  *)
67
68
  echo "[$(date)] ⚠️ Unknown status: $NEW_STATUS (skipping sync)" >> "$DEBUG_LOG" 2>/dev/null || true
@@ -89,14 +90,9 @@ if [ ! -f "$METADATA_FILE" ]; then
89
90
  exit 0
90
91
  fi
91
92
 
92
- # Extract GitHub issue number
93
+ # Extract GitHub issue number (main increment issue)
93
94
  GITHUB_ISSUE=$(jq -r '.github.issue // empty' "$METADATA_FILE" 2>/dev/null)
94
95
 
95
- if [ -z "$GITHUB_ISSUE" ]; then
96
- echo "[$(date)] ℹ️ No GitHub issue linked, skipping sync" >> "$DEBUG_LOG" 2>/dev/null || true
97
- exit 0
98
- fi
99
-
100
96
  # Detect repository
101
97
  GITHUB_REMOTE=$(git remote get-url origin 2>/dev/null || echo "")
102
98
  REPO_MATCH=$(echo "$GITHUB_REMOTE" | grep -o 'github\.com[:/][^/]*/[^/]*' | sed 's/github\.com[:/]//')
@@ -106,29 +102,122 @@ if [ -z "$REPO_MATCH" ]; then
106
102
  exit 0
107
103
  fi
108
104
 
109
- echo "[$(date)] πŸ”„ Posting status change to GitHub issue #$GITHUB_ISSUE" >> "$DEBUG_LOG" 2>/dev/null || true
105
+ # ============================================================================
106
+ # STATUS-BASED ACTIONS (NEW in v0.28.33)
107
+ # ============================================================================
110
108
 
111
- # Build comment based on status
112
- EMOJI=""
113
- TITLE=""
114
109
  case "$NEW_STATUS" in
115
- paused)
116
- EMOJI="⏸️"
117
- TITLE="Increment Paused"
118
- ;;
119
- resumed)
120
- EMOJI="▢️"
121
- TITLE="Increment Resumed"
110
+ resumed|active|in-progress)
111
+ # ========================================================================
112
+ # REOPEN GitHub Issues (NEW)
113
+ # ========================================================================
114
+ # When increment is resumed, reopen all closed GitHub issues
115
+ echo "[$(date)] ▢️ Status is $NEW_STATUS - checking if issues need reopening" >> "$DEBUG_LOG" 2>/dev/null || true
116
+
117
+ if command -v node &> /dev/null; then
118
+ # Find reopen script
119
+ REOPEN_SCRIPT=""
120
+ if [ -f "$PROJECT_ROOT/plugins/specweave/lib/hooks/reopen-github-issues.js" ]; then
121
+ REOPEN_SCRIPT="$PROJECT_ROOT/plugins/specweave/lib/hooks/reopen-github-issues.js"
122
+ elif [ -f "$PROJECT_ROOT/dist/plugins/specweave/lib/hooks/reopen-github-issues.js" ]; then
123
+ REOPEN_SCRIPT="$PROJECT_ROOT/dist/plugins/specweave/lib/hooks/reopen-github-issues.js"
124
+ elif [ -f "$PROJECT_ROOT/node_modules/specweave/dist/plugins/specweave/lib/hooks/reopen-github-issues.js" ]; then
125
+ REOPEN_SCRIPT="$PROJECT_ROOT/node_modules/specweave/dist/plugins/specweave/lib/hooks/reopen-github-issues.js"
126
+ elif [ -n "${CLAUDE_PLUGIN_ROOT}" ] && [ -f "${CLAUDE_PLUGIN_ROOT}/lib/hooks/reopen-github-issues.js" ]; then
127
+ REOPEN_SCRIPT="${CLAUDE_PLUGIN_ROOT}/lib/hooks/reopen-github-issues.js"
128
+ fi
129
+
130
+ if [ -n "$REOPEN_SCRIPT" ]; then
131
+ # Load GITHUB_TOKEN from .env
132
+ if [ -f "$PROJECT_ROOT/.env" ]; then
133
+ GITHUB_TOKEN_FROM_ENV=$(grep -E '^GITHUB_TOKEN=' "$PROJECT_ROOT/.env" 2>/dev/null | head -1 | cut -d'=' -f2- | sed 's/^["'\'']//' | sed 's/["'\'']$//')
134
+ if [ -n "$GITHUB_TOKEN_FROM_ENV" ]; then
135
+ export GITHUB_TOKEN="$GITHUB_TOKEN_FROM_ENV"
136
+ fi
137
+ fi
138
+
139
+ echo "▢️ Reopening GitHub issues for resumed increment..."
140
+ (cd "$PROJECT_ROOT" && node "$REOPEN_SCRIPT" "$INCREMENT_ID" "${REASON:-Increment resumed}") 2>&1 | tee -a "$DEBUG_LOG" || {
141
+ echo "[$(date)] ⚠️ Failed to reopen issues (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
142
+ }
143
+ else
144
+ echo "[$(date)] ⚠️ reopen-github-issues.js not found" >> "$DEBUG_LOG" 2>/dev/null || true
145
+ fi
146
+ fi
122
147
  ;;
148
+
123
149
  abandoned)
124
- EMOJI="πŸ—‘οΈ"
125
- TITLE="Increment Abandoned"
150
+ # ========================================================================
151
+ # CLOSE GitHub Issues on Abandon (NEW)
152
+ # ========================================================================
153
+ echo "[$(date)] πŸ—‘οΈ Status is abandoned - closing GitHub issues" >> "$DEBUG_LOG" 2>/dev/null || true
154
+
155
+ if command -v node &> /dev/null; then
156
+ # Find close script
157
+ CLOSE_SCRIPT=""
158
+ if [ -f "$PROJECT_ROOT/plugins/specweave/lib/hooks/close-github-issues-abandoned.js" ]; then
159
+ CLOSE_SCRIPT="$PROJECT_ROOT/plugins/specweave/lib/hooks/close-github-issues-abandoned.js"
160
+ elif [ -f "$PROJECT_ROOT/dist/plugins/specweave/lib/hooks/close-github-issues-abandoned.js" ]; then
161
+ CLOSE_SCRIPT="$PROJECT_ROOT/dist/plugins/specweave/lib/hooks/close-github-issues-abandoned.js"
162
+ elif [ -f "$PROJECT_ROOT/node_modules/specweave/dist/plugins/specweave/lib/hooks/close-github-issues-abandoned.js" ]; then
163
+ CLOSE_SCRIPT="$PROJECT_ROOT/node_modules/specweave/dist/plugins/specweave/lib/hooks/close-github-issues-abandoned.js"
164
+ elif [ -n "${CLAUDE_PLUGIN_ROOT}" ] && [ -f "${CLAUDE_PLUGIN_ROOT}/lib/hooks/close-github-issues-abandoned.js" ]; then
165
+ CLOSE_SCRIPT="${CLAUDE_PLUGIN_ROOT}/lib/hooks/close-github-issues-abandoned.js"
166
+ fi
167
+
168
+ if [ -n "$CLOSE_SCRIPT" ]; then
169
+ # Load GITHUB_TOKEN from .env
170
+ if [ -f "$PROJECT_ROOT/.env" ]; then
171
+ GITHUB_TOKEN_FROM_ENV=$(grep -E '^GITHUB_TOKEN=' "$PROJECT_ROOT/.env" 2>/dev/null | head -1 | cut -d'=' -f2- | sed 's/^["'\'']//' | sed 's/["'\'']$//')
172
+ if [ -n "$GITHUB_TOKEN_FROM_ENV" ]; then
173
+ export GITHUB_TOKEN="$GITHUB_TOKEN_FROM_ENV"
174
+ fi
175
+ fi
176
+
177
+ echo "πŸ—‘οΈ Closing GitHub issues for abandoned increment..."
178
+ (cd "$PROJECT_ROOT" && node "$CLOSE_SCRIPT" "$INCREMENT_ID" "${REASON:-Increment abandoned}") 2>&1 | tee -a "$DEBUG_LOG" || {
179
+ echo "[$(date)] ⚠️ Failed to close issues (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
180
+ }
181
+ else
182
+ echo "[$(date)] ⚠️ close-github-issues-abandoned.js not found" >> "$DEBUG_LOG" 2>/dev/null || true
183
+ fi
184
+ fi
126
185
  ;;
127
- esac
128
186
 
129
- TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
187
+ paused)
188
+ # Paused: Just post comment (no close/reopen)
189
+ echo "[$(date)] ⏸️ Status is paused - posting comment only" >> "$DEBUG_LOG" 2>/dev/null || true
190
+ ;;
191
+ esac
130
192
 
131
- COMMENT="$EMOJI **$TITLE**
193
+ # ============================================================================
194
+ # POST COMMENT TO MAIN ISSUE (always, for all status changes)
195
+ # ============================================================================
196
+
197
+ if [ -n "$GITHUB_ISSUE" ]; then
198
+ echo "[$(date)] πŸ”„ Posting status change to GitHub issue #$GITHUB_ISSUE" >> "$DEBUG_LOG" 2>/dev/null || true
199
+
200
+ # Build comment based on status
201
+ EMOJI=""
202
+ TITLE=""
203
+ case "$NEW_STATUS" in
204
+ paused)
205
+ EMOJI="⏸️"
206
+ TITLE="Increment Paused"
207
+ ;;
208
+ resumed|active|in-progress)
209
+ EMOJI="▢️"
210
+ TITLE="Increment Resumed"
211
+ ;;
212
+ abandoned)
213
+ EMOJI="πŸ—‘οΈ"
214
+ TITLE="Increment Abandoned"
215
+ ;;
216
+ esac
217
+
218
+ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
219
+
220
+ COMMENT="$EMOJI **$TITLE**
132
221
 
133
222
  **Reason**: ${REASON:-Not specified}
134
223
 
@@ -137,15 +226,17 @@ COMMENT="$EMOJI **$TITLE**
137
226
  ---
138
227
  πŸ€– Auto-updated by SpecWeave"
139
228
 
140
- # Post comment
141
- echo "$COMMENT" | gh issue comment "$GITHUB_ISSUE" --body-file - 2>&1 | tee -a "$DEBUG_LOG" >/dev/null || {
142
- echo "[$(date)] ⚠️ Failed to post comment (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
143
- }
229
+ # Post comment
230
+ echo "$COMMENT" | gh issue comment "$GITHUB_ISSUE" --body-file - 2>&1 | tee -a "$DEBUG_LOG" >/dev/null || {
231
+ echo "[$(date)] ⚠️ Failed to post comment (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
232
+ }
144
233
 
145
- echo "[$(date)] βœ… Status change synced to GitHub" >> "$DEBUG_LOG" 2>/dev/null || true
234
+ echo "[$(date)] βœ… Status change synced to GitHub" >> "$DEBUG_LOG" 2>/dev/null || true
235
+ else
236
+ echo "[$(date)] ℹ️ No main GitHub issue linked" >> "$DEBUG_LOG" 2>/dev/null || true
237
+ fi
146
238
 
147
239
  # Update status line cache (status changed - may affect which increment is "current")
148
- HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
149
240
  bash "$HOOK_DIR/lib/update-status-line.sh" 2>/dev/null || true
150
241
 
151
242
  # Return success (non-blocking)
@@ -0,0 +1,139 @@
1
+ #!/bin/bash
2
+
3
+ # SpecWeave Session Start Reconcile Hook (NEW in v0.28.33)
4
+ #
5
+ # Runs on Claude Code session start to detect and fix GitHub status drift.
6
+ # This is a lightweight hook that runs in background to not block startup.
7
+ #
8
+ # Features:
9
+ # - Debounced: Only runs if >1 hour since last reconcile
10
+ # - Non-blocking: Runs in background, errors logged but don't crash
11
+ # - Optional: Controlled by config.sync.github.autoReconcileOnSessionStart
12
+ #
13
+ # Trigger: SessionStart hook event
14
+
15
+ set +e # Prevent crashes
16
+
17
+ # EMERGENCY KILL SWITCH
18
+ if [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]; then
19
+ exit 0
20
+ fi
21
+
22
+ # Find project root
23
+ find_project_root() {
24
+ local dir="$PWD"
25
+ while [[ "$dir" != "/" ]]; do
26
+ if [[ -d "$dir/.specweave" ]]; then
27
+ echo "$dir"
28
+ return 0
29
+ fi
30
+ dir=$(dirname "$dir")
31
+ done
32
+ echo "$PWD"
33
+ }
34
+
35
+ PROJECT_ROOT=$(find_project_root)
36
+ CONFIG_FILE="$PROJECT_ROOT/.specweave/config.json"
37
+ STATE_DIR="$PROJECT_ROOT/.specweave/state"
38
+ LAST_RECONCILE_FILE="$STATE_DIR/.last-reconcile-timestamp"
39
+ DEBUG_LOG="$PROJECT_ROOT/.specweave/logs/hooks-debug.log"
40
+
41
+ mkdir -p "$STATE_DIR" 2>/dev/null || true
42
+ mkdir -p "$(dirname "$DEBUG_LOG")" 2>/dev/null || true
43
+
44
+ # Consume stdin (required for hooks)
45
+ cat > /dev/null
46
+
47
+ echo "[$(date)] session-start-reconcile: Hook fired" >> "$DEBUG_LOG" 2>/dev/null || true
48
+
49
+ # ============================================================================
50
+ # CHECK IF AUTO-RECONCILE IS ENABLED
51
+ # ============================================================================
52
+
53
+ if [[ ! -f "$CONFIG_FILE" ]]; then
54
+ echo "[$(date)] session-start-reconcile: No config.json - skipping" >> "$DEBUG_LOG" 2>/dev/null || true
55
+ exit 0
56
+ fi
57
+
58
+ # Check config (requires jq)
59
+ if ! command -v jq &> /dev/null; then
60
+ echo "[$(date)] session-start-reconcile: jq not found - skipping" >> "$DEBUG_LOG" 2>/dev/null || true
61
+ exit 0
62
+ fi
63
+
64
+ # Check if auto-reconcile is enabled
65
+ AUTO_RECONCILE=$(jq -r '.sync.github.autoReconcileOnSessionStart // false' "$CONFIG_FILE" 2>/dev/null)
66
+
67
+ if [[ "$AUTO_RECONCILE" != "true" ]]; then
68
+ echo "[$(date)] session-start-reconcile: Disabled in config - skipping" >> "$DEBUG_LOG" 2>/dev/null || true
69
+ exit 0
70
+ fi
71
+
72
+ echo "[$(date)] session-start-reconcile: Auto-reconcile enabled" >> "$DEBUG_LOG" 2>/dev/null || true
73
+
74
+ # ============================================================================
75
+ # DEBOUNCE: Only run if >1 hour since last reconcile
76
+ # ============================================================================
77
+
78
+ DEBOUNCE_SECONDS=3600 # 1 hour
79
+
80
+ if [[ -f "$LAST_RECONCILE_FILE" ]]; then
81
+ LAST_TIMESTAMP=$(cat "$LAST_RECONCILE_FILE" 2>/dev/null || echo "0")
82
+ CURRENT_TIMESTAMP=$(date +%s)
83
+ ELAPSED=$((CURRENT_TIMESTAMP - LAST_TIMESTAMP))
84
+
85
+ if [[ $ELAPSED -lt $DEBOUNCE_SECONDS ]]; then
86
+ REMAINING=$((DEBOUNCE_SECONDS - ELAPSED))
87
+ echo "[$(date)] session-start-reconcile: Debounced - last run ${ELAPSED}s ago (wait ${REMAINING}s)" >> "$DEBUG_LOG" 2>/dev/null || true
88
+ exit 0
89
+ fi
90
+ fi
91
+
92
+ echo "[$(date)] session-start-reconcile: Debounce passed - running reconcile" >> "$DEBUG_LOG" 2>/dev/null || true
93
+
94
+ # Update timestamp BEFORE running (prevents concurrent runs)
95
+ date +%s > "$LAST_RECONCILE_FILE"
96
+
97
+ # ============================================================================
98
+ # RUN RECONCILE IN BACKGROUND (non-blocking)
99
+ # ============================================================================
100
+
101
+ if ! command -v node &> /dev/null; then
102
+ echo "[$(date)] session-start-reconcile: Node.js not found - skipping" >> "$DEBUG_LOG" 2>/dev/null || true
103
+ exit 0
104
+ fi
105
+
106
+ # Find reconcile script
107
+ RECONCILE_SCRIPT=""
108
+ if [[ -f "$PROJECT_ROOT/plugins/specweave/lib/hooks/session-start-reconcile-worker.js" ]]; then
109
+ RECONCILE_SCRIPT="$PROJECT_ROOT/plugins/specweave/lib/hooks/session-start-reconcile-worker.js"
110
+ elif [[ -f "$PROJECT_ROOT/dist/plugins/specweave/lib/hooks/session-start-reconcile-worker.js" ]]; then
111
+ RECONCILE_SCRIPT="$PROJECT_ROOT/dist/plugins/specweave/lib/hooks/session-start-reconcile-worker.js"
112
+ elif [[ -f "$PROJECT_ROOT/node_modules/specweave/dist/plugins/specweave/lib/hooks/session-start-reconcile-worker.js" ]]; then
113
+ RECONCILE_SCRIPT="$PROJECT_ROOT/node_modules/specweave/dist/plugins/specweave/lib/hooks/session-start-reconcile-worker.js"
114
+ fi
115
+
116
+ if [[ -z "$RECONCILE_SCRIPT" ]]; then
117
+ echo "[$(date)] session-start-reconcile: Worker script not found - skipping" >> "$DEBUG_LOG" 2>/dev/null || true
118
+ exit 0
119
+ fi
120
+
121
+ # Load GITHUB_TOKEN from .env
122
+ if [[ -f "$PROJECT_ROOT/.env" ]]; then
123
+ GITHUB_TOKEN_FROM_ENV=$(grep -E '^GITHUB_TOKEN=' "$PROJECT_ROOT/.env" 2>/dev/null | head -1 | cut -d'=' -f2- | sed 's/^["'\'']//' | sed 's/["'\'']$//')
124
+ if [[ -n "$GITHUB_TOKEN_FROM_ENV" ]]; then
125
+ export GITHUB_TOKEN="$GITHUB_TOKEN_FROM_ENV"
126
+ fi
127
+ fi
128
+
129
+ # Run in background with nohup (fully detached)
130
+ # Output goes to debug log, doesn't block session start
131
+ echo "[$(date)] session-start-reconcile: Starting background reconcile" >> "$DEBUG_LOG" 2>/dev/null || true
132
+
133
+ (
134
+ cd "$PROJECT_ROOT" && \
135
+ node "$RECONCILE_SCRIPT" >> "$DEBUG_LOG" 2>&1
136
+ ) &
137
+
138
+ # Immediately exit (don't wait for background job)
139
+ exit 0
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ import { GitHubReconciler } from "../vendor/sync/github-reconciler.js";
3
+ import { consoleLogger } from "../vendor/utils/logger.js";
4
+ async function main() {
5
+ const incrementId = process.argv[2];
6
+ const reason = process.argv[3] || "Increment abandoned";
7
+ if (!incrementId) {
8
+ console.error("Usage: node close-github-issues-abandoned.js <increment-id> [reason]");
9
+ process.exit(1);
10
+ }
11
+ console.log("\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
12
+ console.log(`\u{1F5D1}\uFE0F CLOSING GITHUB ISSUES (ABANDONED): ${incrementId}`);
13
+ console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
14
+ const projectRoot = process.cwd();
15
+ const result = await GitHubReconciler.closeAbandonedIncrementIssues(
16
+ projectRoot,
17
+ incrementId,
18
+ reason,
19
+ consoleLogger
20
+ );
21
+ console.log("\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
22
+ if (result.closed > 0) {
23
+ console.log(`\u2705 Closed ${result.closed} GitHub issue(s)`);
24
+ } else {
25
+ console.log("\u2139\uFE0F No issues needed closing (all already closed or none linked)");
26
+ }
27
+ if (result.errors.length > 0) {
28
+ console.log(`\u26A0\uFE0F ${result.errors.length} error(s):`);
29
+ result.errors.forEach((err) => console.log(` - ${err}`));
30
+ }
31
+ console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
32
+ process.exit(0);
33
+ }
34
+ main().catch((error) => {
35
+ console.error("\u274C Fatal error:", error);
36
+ process.exit(0);
37
+ });
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Close GitHub Issues for Abandoned Increment (NEW in v0.28.33)
4
+ *
5
+ * Called by post-increment-status-change.sh when an increment is abandoned.
6
+ * Closes all GitHub issues (main + User Stories) with abandon comment.
7
+ *
8
+ * Usage:
9
+ * node close-github-issues-abandoned.js <increment-id> [reason]
10
+ *
11
+ * Environment:
12
+ * GITHUB_TOKEN - Required for GitHub API access
13
+ */
14
+
15
+ import { GitHubReconciler } from '../vendor/sync/github-reconciler.js';
16
+ import { consoleLogger } from '../vendor/utils/logger.js';
17
+
18
+ async function main(): Promise<void> {
19
+ const incrementId = process.argv[2];
20
+ const reason = process.argv[3] || 'Increment abandoned';
21
+
22
+ if (!incrementId) {
23
+ console.error('Usage: node close-github-issues-abandoned.js <increment-id> [reason]');
24
+ process.exit(1);
25
+ }
26
+
27
+ console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
28
+ console.log(`πŸ—‘οΈ CLOSING GITHUB ISSUES (ABANDONED): ${incrementId}`);
29
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
30
+
31
+ const projectRoot = process.cwd();
32
+
33
+ const result = await GitHubReconciler.closeAbandonedIncrementIssues(
34
+ projectRoot,
35
+ incrementId,
36
+ reason,
37
+ consoleLogger
38
+ );
39
+
40
+ console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
41
+ if (result.closed > 0) {
42
+ console.log(`βœ… Closed ${result.closed} GitHub issue(s)`);
43
+ } else {
44
+ console.log('ℹ️ No issues needed closing (all already closed or none linked)');
45
+ }
46
+ if (result.errors.length > 0) {
47
+ console.log(`⚠️ ${result.errors.length} error(s):`);
48
+ result.errors.forEach(err => console.log(` - ${err}`));
49
+ }
50
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
51
+
52
+ // Exit 0 to not block hook chain
53
+ process.exit(0);
54
+ }
55
+
56
+ main().catch((error) => {
57
+ console.error('❌ Fatal error:', error);
58
+ process.exit(0); // Non-blocking
59
+ });
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ import { GitHubReconciler } from "../vendor/sync/github-reconciler.js";
3
+ import { consoleLogger } from "../vendor/utils/logger.js";
4
+ async function main() {
5
+ const incrementId = process.argv[2];
6
+ const reason = process.argv[3] || "Increment resumed";
7
+ if (!incrementId) {
8
+ console.error("Usage: node reopen-github-issues.js <increment-id> [reason]");
9
+ process.exit(1);
10
+ }
11
+ console.log("\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
12
+ console.log(`\u25B6\uFE0F REOPENING GITHUB ISSUES: ${incrementId}`);
13
+ console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
14
+ const projectRoot = process.cwd();
15
+ const result = await GitHubReconciler.reopenIncrementIssues(
16
+ projectRoot,
17
+ incrementId,
18
+ reason,
19
+ consoleLogger
20
+ );
21
+ console.log("\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
22
+ if (result.reopened > 0) {
23
+ console.log(`\u2705 Reopened ${result.reopened} GitHub issue(s)`);
24
+ } else {
25
+ console.log("\u2139\uFE0F No issues needed reopening (all already open or none linked)");
26
+ }
27
+ if (result.errors.length > 0) {
28
+ console.log(`\u26A0\uFE0F ${result.errors.length} error(s):`);
29
+ result.errors.forEach((err) => console.log(` - ${err}`));
30
+ }
31
+ console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
32
+ process.exit(0);
33
+ }
34
+ main().catch((error) => {
35
+ console.error("\u274C Fatal error:", error);
36
+ process.exit(0);
37
+ });
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Reopen GitHub Issues Hook (NEW in v0.28.33)
4
+ *
5
+ * Called by post-increment-status-change.sh when an increment is resumed.
6
+ * Reopens all GitHub issues (main + User Stories) for the increment.
7
+ *
8
+ * Usage:
9
+ * node reopen-github-issues.js <increment-id> [reason]
10
+ *
11
+ * Environment:
12
+ * GITHUB_TOKEN - Required for GitHub API access
13
+ */
14
+
15
+ import { GitHubReconciler } from '../vendor/sync/github-reconciler.js';
16
+ import { consoleLogger } from '../vendor/utils/logger.js';
17
+
18
+ async function main(): Promise<void> {
19
+ const incrementId = process.argv[2];
20
+ const reason = process.argv[3] || 'Increment resumed';
21
+
22
+ if (!incrementId) {
23
+ console.error('Usage: node reopen-github-issues.js <increment-id> [reason]');
24
+ process.exit(1);
25
+ }
26
+
27
+ console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
28
+ console.log(`▢️ REOPENING GITHUB ISSUES: ${incrementId}`);
29
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
30
+
31
+ const projectRoot = process.cwd();
32
+
33
+ const result = await GitHubReconciler.reopenIncrementIssues(
34
+ projectRoot,
35
+ incrementId,
36
+ reason,
37
+ consoleLogger
38
+ );
39
+
40
+ console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
41
+ if (result.reopened > 0) {
42
+ console.log(`βœ… Reopened ${result.reopened} GitHub issue(s)`);
43
+ } else {
44
+ console.log('ℹ️ No issues needed reopening (all already open or none linked)');
45
+ }
46
+ if (result.errors.length > 0) {
47
+ console.log(`⚠️ ${result.errors.length} error(s):`);
48
+ result.errors.forEach(err => console.log(` - ${err}`));
49
+ }
50
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
51
+
52
+ // Exit 0 to not block hook chain
53
+ process.exit(0);
54
+ }
55
+
56
+ main().catch((error) => {
57
+ console.error('❌ Fatal error:', error);
58
+ process.exit(0); // Non-blocking
59
+ });
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ import { GitHubReconciler } from "../vendor/sync/github-reconciler.js";
3
+ const silentLogger = {
4
+ log: () => {
5
+ },
6
+ // Suppress normal logs
7
+ error: (msg, ...args) => console.error(`[reconcile] ${msg}`, ...args)
8
+ };
9
+ async function main() {
10
+ console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] session-start-reconcile-worker: Starting...`);
11
+ const projectRoot = process.cwd();
12
+ try {
13
+ const reconciler = new GitHubReconciler({
14
+ projectRoot,
15
+ dryRun: false,
16
+ logger: silentLogger
17
+ });
18
+ const result = await reconciler.reconcile();
19
+ if (result.mismatches > 0) {
20
+ console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] session-start-reconcile-worker: Fixed ${result.closed} closed, ${result.reopened} reopened`);
21
+ } else {
22
+ console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] session-start-reconcile-worker: No drift detected (${result.scanned} increments)`);
23
+ }
24
+ if (result.errors.length > 0) {
25
+ console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] session-start-reconcile-worker: ${result.errors.length} error(s)`);
26
+ result.errors.forEach((err) => console.error(` - ${err}`));
27
+ }
28
+ } catch (error) {
29
+ console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] session-start-reconcile-worker: Error - ${error.message}`);
30
+ }
31
+ console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] session-start-reconcile-worker: Complete`);
32
+ }
33
+ main().catch((error) => {
34
+ console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] session-start-reconcile-worker: Fatal error - ${error}`);
35
+ process.exit(0);
36
+ });
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Session Start Reconcile Worker (NEW in v0.28.33)
4
+ *
5
+ * Lightweight background worker that runs GitHub reconciliation on session start.
6
+ * Called by session-start-reconcile.sh in detached mode.
7
+ *
8
+ * Features:
9
+ * - Silent mode: Minimal output, errors logged to debug log
10
+ * - Non-blocking: Parent shell exits immediately
11
+ * - Idempotent: Safe to run multiple times
12
+ */
13
+
14
+ import { GitHubReconciler } from '../vendor/sync/github-reconciler.js';
15
+
16
+ // Silent logger - only logs errors
17
+ const silentLogger = {
18
+ log: () => {}, // Suppress normal logs
19
+ error: (msg: string, ...args: any[]) => console.error(`[reconcile] ${msg}`, ...args),
20
+ };
21
+
22
+ async function main(): Promise<void> {
23
+ console.log(`[${new Date().toISOString()}] session-start-reconcile-worker: Starting...`);
24
+
25
+ const projectRoot = process.cwd();
26
+
27
+ try {
28
+ const reconciler = new GitHubReconciler({
29
+ projectRoot,
30
+ dryRun: false,
31
+ logger: silentLogger as any,
32
+ });
33
+
34
+ const result = await reconciler.reconcile();
35
+
36
+ // Log summary
37
+ if (result.mismatches > 0) {
38
+ console.log(`[${new Date().toISOString()}] session-start-reconcile-worker: Fixed ${result.closed} closed, ${result.reopened} reopened`);
39
+ } else {
40
+ console.log(`[${new Date().toISOString()}] session-start-reconcile-worker: No drift detected (${result.scanned} increments)`);
41
+ }
42
+
43
+ if (result.errors.length > 0) {
44
+ console.error(`[${new Date().toISOString()}] session-start-reconcile-worker: ${result.errors.length} error(s)`);
45
+ result.errors.forEach(err => console.error(` - ${err}`));
46
+ }
47
+
48
+ } catch (error: any) {
49
+ console.error(`[${new Date().toISOString()}] session-start-reconcile-worker: Error - ${error.message}`);
50
+ }
51
+
52
+ console.log(`[${new Date().toISOString()}] session-start-reconcile-worker: Complete`);
53
+ }
54
+
55
+ main().catch((error) => {
56
+ console.error(`[${new Date().toISOString()}] session-start-reconcile-worker: Fatal error - ${error}`);
57
+ process.exit(0); // Non-blocking
58
+ });