specweave 0.28.32 → 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.
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts +14 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.js +57 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
- package/dist/src/cli/helpers/init/external-import.js +4 -0
- package/dist/src/cli/helpers/init/external-import.js.map +1 -1
- package/dist/src/importers/import-coordinator.d.ts +1 -0
- package/dist/src/importers/import-coordinator.d.ts.map +1 -1
- package/dist/src/importers/import-coordinator.js +3 -0
- package/dist/src/importers/import-coordinator.js.map +1 -1
- package/dist/src/living-docs/fs-id-allocator.d.ts +11 -0
- package/dist/src/living-docs/fs-id-allocator.d.ts.map +1 -1
- package/dist/src/living-docs/fs-id-allocator.js +24 -0
- package/dist/src/living-docs/fs-id-allocator.js.map +1 -1
- package/dist/src/sync/github-reconciler.d.ts +94 -0
- package/dist/src/sync/github-reconciler.d.ts.map +1 -0
- package/dist/src/sync/github-reconciler.js +435 -0
- package/dist/src/sync/github-reconciler.js.map +1 -0
- package/package.json +1 -1
- package/plugins/specweave/hooks/hooks.json +10 -0
- package/plugins/specweave/hooks/post-increment-status-change.sh +123 -32
- package/plugins/specweave/hooks/session-start-reconcile.sh +139 -0
- package/plugins/specweave/lib/hooks/close-github-issues-abandoned.js +37 -0
- package/plugins/specweave/lib/hooks/close-github-issues-abandoned.ts +59 -0
- package/plugins/specweave/lib/hooks/reopen-github-issues.js +37 -0
- package/plugins/specweave/lib/hooks/reopen-github-issues.ts +59 -0
- package/plugins/specweave/lib/hooks/session-start-reconcile-worker.js +36 -0
- package/plugins/specweave/lib/hooks/session-start-reconcile-worker.ts +58 -0
- package/plugins/specweave/lib/vendor/sync/github-reconciler.d.ts +94 -0
- package/plugins/specweave/lib/vendor/sync/github-reconciler.js +435 -0
- package/plugins/specweave/lib/vendor/sync/github-reconciler.js.map +1 -0
- package/plugins/specweave-github/commands/specweave-github-reconcile.md +110 -0
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +24 -0
- package/plugins/specweave-github/lib/github-client-v2.js +55 -0
- package/plugins/specweave-github/lib/github-client-v2.ts +68 -0
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +36 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Reconciler (NEW in v0.28.33)
|
|
3
|
+
*
|
|
4
|
+
* Reconciles GitHub issue states with increment metadata.json statuses.
|
|
5
|
+
* Fixes drift between local SpecWeave state and GitHub:
|
|
6
|
+
* - Closes issues for completed increments that are still open
|
|
7
|
+
* - Reopens issues for in-progress increments that are closed
|
|
8
|
+
*
|
|
9
|
+
* Triggered by:
|
|
10
|
+
* - /specweave-github:reconcile command (manual)
|
|
11
|
+
* - SessionStart hook (automatic, if configured)
|
|
12
|
+
* - post-increment-status-change.sh (on resume/abandon)
|
|
13
|
+
*/
|
|
14
|
+
import { Logger } from '../utils/logger.js';
|
|
15
|
+
export interface ReconcileOptions {
|
|
16
|
+
projectRoot: string;
|
|
17
|
+
dryRun?: boolean;
|
|
18
|
+
logger?: Logger;
|
|
19
|
+
}
|
|
20
|
+
export interface IncrementGitHubState {
|
|
21
|
+
incrementId: string;
|
|
22
|
+
incrementPath: string;
|
|
23
|
+
metadataStatus: string;
|
|
24
|
+
featureId?: string;
|
|
25
|
+
mainIssue?: {
|
|
26
|
+
number: number;
|
|
27
|
+
url?: string;
|
|
28
|
+
};
|
|
29
|
+
userStoryIssues: Array<{
|
|
30
|
+
userStoryId: string;
|
|
31
|
+
issueNumber: number;
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
export interface ReconcileResult {
|
|
35
|
+
scanned: number;
|
|
36
|
+
mismatches: number;
|
|
37
|
+
closed: number;
|
|
38
|
+
reopened: number;
|
|
39
|
+
errors: string[];
|
|
40
|
+
details: Array<{
|
|
41
|
+
incrementId: string;
|
|
42
|
+
action: 'close' | 'reopen' | 'skip' | 'error';
|
|
43
|
+
issueNumber: number;
|
|
44
|
+
reason: string;
|
|
45
|
+
}>;
|
|
46
|
+
}
|
|
47
|
+
export declare class GitHubReconciler {
|
|
48
|
+
private projectRoot;
|
|
49
|
+
private dryRun;
|
|
50
|
+
private logger;
|
|
51
|
+
private client;
|
|
52
|
+
constructor(options: ReconcileOptions);
|
|
53
|
+
/**
|
|
54
|
+
* Main reconciliation entry point
|
|
55
|
+
*/
|
|
56
|
+
reconcile(): Promise<ReconcileResult>;
|
|
57
|
+
/**
|
|
58
|
+
* Reconcile a single increment
|
|
59
|
+
*/
|
|
60
|
+
private reconcileIncrement;
|
|
61
|
+
/**
|
|
62
|
+
* Reconcile a single issue
|
|
63
|
+
*/
|
|
64
|
+
private reconcileIssue;
|
|
65
|
+
/**
|
|
66
|
+
* Scan all non-archived increments and extract GitHub state
|
|
67
|
+
*/
|
|
68
|
+
private scanIncrements;
|
|
69
|
+
/**
|
|
70
|
+
* Initialize GitHub client
|
|
71
|
+
*/
|
|
72
|
+
private initClient;
|
|
73
|
+
/**
|
|
74
|
+
* Load config
|
|
75
|
+
*/
|
|
76
|
+
private loadConfig;
|
|
77
|
+
/**
|
|
78
|
+
* Reopen all GitHub issues for an increment
|
|
79
|
+
* Called by post-increment-status-change.sh when resuming
|
|
80
|
+
*/
|
|
81
|
+
static reopenIncrementIssues(projectRoot: string, incrementId: string, reason: string, logger?: Logger): Promise<{
|
|
82
|
+
reopened: number;
|
|
83
|
+
errors: string[];
|
|
84
|
+
}>;
|
|
85
|
+
/**
|
|
86
|
+
* Close all GitHub issues for an abandoned increment
|
|
87
|
+
* Called by post-increment-status-change.sh when abandoning
|
|
88
|
+
*/
|
|
89
|
+
static closeAbandonedIncrementIssues(projectRoot: string, incrementId: string, reason: string, logger?: Logger): Promise<{
|
|
90
|
+
closed: number;
|
|
91
|
+
errors: string[];
|
|
92
|
+
}>;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=github-reconciler.d.ts.map
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Reconciler (NEW in v0.28.33)
|
|
3
|
+
*
|
|
4
|
+
* Reconciles GitHub issue states with increment metadata.json statuses.
|
|
5
|
+
* Fixes drift between local SpecWeave state and GitHub:
|
|
6
|
+
* - Closes issues for completed increments that are still open
|
|
7
|
+
* - Reopens issues for in-progress increments that are closed
|
|
8
|
+
*
|
|
9
|
+
* Triggered by:
|
|
10
|
+
* - /specweave-github:reconcile command (manual)
|
|
11
|
+
* - SessionStart hook (automatic, if configured)
|
|
12
|
+
* - post-increment-status-change.sh (on resume/abandon)
|
|
13
|
+
*/
|
|
14
|
+
import { promises as fs, existsSync } from 'fs';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { GitHubClientV2 } from '../../plugins/specweave-github/lib/github-client-v2.js';
|
|
17
|
+
import { consoleLogger } from '../utils/logger.js';
|
|
18
|
+
export class GitHubReconciler {
|
|
19
|
+
constructor(options) {
|
|
20
|
+
this.client = null;
|
|
21
|
+
this.projectRoot = options.projectRoot;
|
|
22
|
+
this.dryRun = options.dryRun ?? false;
|
|
23
|
+
this.logger = options.logger ?? consoleLogger;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Main reconciliation entry point
|
|
27
|
+
*/
|
|
28
|
+
async reconcile() {
|
|
29
|
+
const result = {
|
|
30
|
+
scanned: 0,
|
|
31
|
+
mismatches: 0,
|
|
32
|
+
closed: 0,
|
|
33
|
+
reopened: 0,
|
|
34
|
+
errors: [],
|
|
35
|
+
details: [],
|
|
36
|
+
};
|
|
37
|
+
try {
|
|
38
|
+
// 1. Check if GitHub sync is enabled
|
|
39
|
+
const config = await this.loadConfig();
|
|
40
|
+
const canUpdate = config.sync?.settings?.canUpdateExternalItems ?? false;
|
|
41
|
+
const githubEnabled = config.sync?.github?.enabled ?? false;
|
|
42
|
+
if (!canUpdate || !githubEnabled) {
|
|
43
|
+
this.logger.log('ℹ️ GitHub sync is disabled - skipping reconciliation');
|
|
44
|
+
this.logger.log(' Enable with: canUpdateExternalItems=true AND sync.github.enabled=true');
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
// 2. Initialize GitHub client
|
|
48
|
+
await this.initClient();
|
|
49
|
+
if (!this.client) {
|
|
50
|
+
result.errors.push('Failed to initialize GitHub client');
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
// 3. Scan all non-archived increments
|
|
54
|
+
const increments = await this.scanIncrements();
|
|
55
|
+
result.scanned = increments.length;
|
|
56
|
+
this.logger.log(`\n📊 Scanning ${increments.length} increment(s) for GitHub state drift...\n`);
|
|
57
|
+
// 4. Check and fix each increment
|
|
58
|
+
for (const inc of increments) {
|
|
59
|
+
await this.reconcileIncrement(inc, result);
|
|
60
|
+
}
|
|
61
|
+
// 5. Report summary
|
|
62
|
+
this.logger.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
63
|
+
this.logger.log('📊 RECONCILIATION SUMMARY');
|
|
64
|
+
this.logger.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
65
|
+
this.logger.log(` Increments scanned: ${result.scanned}`);
|
|
66
|
+
this.logger.log(` Mismatches found: ${result.mismatches}`);
|
|
67
|
+
this.logger.log(` Issues closed: ${result.closed}`);
|
|
68
|
+
this.logger.log(` Issues reopened: ${result.reopened}`);
|
|
69
|
+
this.logger.log(` Errors: ${result.errors.length}`);
|
|
70
|
+
if (this.dryRun) {
|
|
71
|
+
this.logger.log('\n ⚠️ DRY RUN - No changes were made');
|
|
72
|
+
}
|
|
73
|
+
this.logger.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
result.errors.push(`Reconciliation error: ${error.message}`);
|
|
78
|
+
this.logger.error('❌ Reconciliation failed:', error.message);
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Reconcile a single increment
|
|
84
|
+
*/
|
|
85
|
+
async reconcileIncrement(inc, result) {
|
|
86
|
+
const status = inc.metadataStatus;
|
|
87
|
+
// Determine expected GitHub state
|
|
88
|
+
const shouldBeClosed = status === 'completed' || status === 'abandoned';
|
|
89
|
+
const shouldBeOpen = status === 'in-progress' || status === 'active' || status === 'planning';
|
|
90
|
+
// Check main issue
|
|
91
|
+
if (inc.mainIssue) {
|
|
92
|
+
await this.reconcileIssue(inc.incrementId, inc.mainIssue.number, shouldBeClosed, shouldBeOpen, status, result);
|
|
93
|
+
}
|
|
94
|
+
// Check User Story issues
|
|
95
|
+
for (const us of inc.userStoryIssues) {
|
|
96
|
+
await this.reconcileIssue(`${inc.incrementId}/${us.userStoryId}`, us.issueNumber, shouldBeClosed, shouldBeOpen, status, result);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Reconcile a single issue
|
|
101
|
+
*/
|
|
102
|
+
async reconcileIssue(context, issueNumber, shouldBeClosed, shouldBeOpen, metadataStatus, result) {
|
|
103
|
+
try {
|
|
104
|
+
// Get current GitHub state
|
|
105
|
+
const issue = await this.client.getIssue(issueNumber);
|
|
106
|
+
const isCurrentlyClosed = issue.state === 'closed';
|
|
107
|
+
// Check for mismatch
|
|
108
|
+
if (shouldBeClosed && !isCurrentlyClosed) {
|
|
109
|
+
// Should be closed but is open
|
|
110
|
+
result.mismatches++;
|
|
111
|
+
this.logger.log(` ❌ Issue #${issueNumber} (${context}): OPEN but should be CLOSED (status=${metadataStatus})`);
|
|
112
|
+
if (!this.dryRun) {
|
|
113
|
+
const comment = `## 🔄 Auto-Reconciled
|
|
114
|
+
|
|
115
|
+
This issue was closed by SpecWeave reconciliation.
|
|
116
|
+
|
|
117
|
+
**Reason**: Increment status is \`${metadataStatus}\` but GitHub issue was still open.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
🤖 Auto-reconciled by SpecWeave`;
|
|
121
|
+
await this.client.closeIssue(issueNumber, comment);
|
|
122
|
+
result.closed++;
|
|
123
|
+
this.logger.log(` ✅ Closed issue #${issueNumber}`);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
this.logger.log(` [DRY RUN] Would close issue #${issueNumber}`);
|
|
127
|
+
}
|
|
128
|
+
result.details.push({
|
|
129
|
+
incrementId: context,
|
|
130
|
+
action: this.dryRun ? 'skip' : 'close',
|
|
131
|
+
issueNumber,
|
|
132
|
+
reason: `Status=${metadataStatus}, GH=open`,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
else if (shouldBeOpen && isCurrentlyClosed) {
|
|
136
|
+
// Should be open but is closed
|
|
137
|
+
result.mismatches++;
|
|
138
|
+
this.logger.log(` ❌ Issue #${issueNumber} (${context}): CLOSED but should be OPEN (status=${metadataStatus})`);
|
|
139
|
+
if (!this.dryRun) {
|
|
140
|
+
const comment = `## 🔄 Auto-Reopened
|
|
141
|
+
|
|
142
|
+
This issue was reopened by SpecWeave reconciliation.
|
|
143
|
+
|
|
144
|
+
**Reason**: Increment status is \`${metadataStatus}\` but GitHub issue was closed.
|
|
145
|
+
|
|
146
|
+
This typically happens when:
|
|
147
|
+
- Increment was resumed after being paused/completed
|
|
148
|
+
- Manual status change in metadata.json
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
🤖 Auto-reconciled by SpecWeave`;
|
|
152
|
+
await this.client.reopenIssue(issueNumber, comment);
|
|
153
|
+
result.reopened++;
|
|
154
|
+
this.logger.log(` ✅ Reopened issue #${issueNumber}`);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
this.logger.log(` [DRY RUN] Would reopen issue #${issueNumber}`);
|
|
158
|
+
}
|
|
159
|
+
result.details.push({
|
|
160
|
+
incrementId: context,
|
|
161
|
+
action: this.dryRun ? 'skip' : 'reopen',
|
|
162
|
+
issueNumber,
|
|
163
|
+
reason: `Status=${metadataStatus}, GH=closed`,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
// State matches - no action needed
|
|
168
|
+
this.logger.log(` ✅ Issue #${issueNumber} (${context}): State matches (${isCurrentlyClosed ? 'closed' : 'open'})`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
result.errors.push(`Issue #${issueNumber}: ${error.message}`);
|
|
173
|
+
result.details.push({
|
|
174
|
+
incrementId: context,
|
|
175
|
+
action: 'error',
|
|
176
|
+
issueNumber,
|
|
177
|
+
reason: error.message,
|
|
178
|
+
});
|
|
179
|
+
this.logger.error(` ⚠️ Error checking issue #${issueNumber}: ${error.message}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Scan all non-archived increments and extract GitHub state
|
|
184
|
+
*/
|
|
185
|
+
async scanIncrements() {
|
|
186
|
+
const incrementsDir = path.join(this.projectRoot, '.specweave/increments');
|
|
187
|
+
const results = [];
|
|
188
|
+
if (!existsSync(incrementsDir)) {
|
|
189
|
+
return results;
|
|
190
|
+
}
|
|
191
|
+
const entries = await fs.readdir(incrementsDir, { withFileTypes: true });
|
|
192
|
+
for (const entry of entries) {
|
|
193
|
+
// Skip non-directories and archive
|
|
194
|
+
if (!entry.isDirectory() || entry.name === '_archive' || entry.name.startsWith('.')) {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const incrementPath = path.join(incrementsDir, entry.name);
|
|
198
|
+
const metadataPath = path.join(incrementPath, 'metadata.json');
|
|
199
|
+
if (!existsSync(metadataPath)) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
|
|
204
|
+
const state = {
|
|
205
|
+
incrementId: entry.name,
|
|
206
|
+
incrementPath,
|
|
207
|
+
metadataStatus: metadata.status || 'unknown',
|
|
208
|
+
featureId: metadata.feature_id,
|
|
209
|
+
userStoryIssues: [],
|
|
210
|
+
};
|
|
211
|
+
// Extract main issue
|
|
212
|
+
if (metadata.github?.issue) {
|
|
213
|
+
state.mainIssue = {
|
|
214
|
+
number: metadata.github.issue,
|
|
215
|
+
url: metadata.github.url,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
// Extract User Story issues from metadata
|
|
219
|
+
if (metadata.github?.issues && Array.isArray(metadata.github.issues)) {
|
|
220
|
+
for (const issue of metadata.github.issues) {
|
|
221
|
+
if (issue.userStory && issue.number) {
|
|
222
|
+
state.userStoryIssues.push({
|
|
223
|
+
userStoryId: issue.userStory,
|
|
224
|
+
issueNumber: issue.number,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// FALLBACK: Search GitHub if metadata doesn't have issues stored
|
|
230
|
+
// This handles cases where issues were created but not recorded in metadata.json
|
|
231
|
+
if (state.userStoryIssues.length === 0 && state.featureId) {
|
|
232
|
+
// Check if we have user_stories array (indicates issues might exist)
|
|
233
|
+
const userStories = metadata.user_stories || [];
|
|
234
|
+
if (userStories.length > 0 && this.client) {
|
|
235
|
+
this.logger.log(` 🔍 Searching GitHub for ${state.featureId} issues (not in metadata)...`);
|
|
236
|
+
try {
|
|
237
|
+
// Search for all issues matching the feature pattern
|
|
238
|
+
const foundIssues = await this.client.searchIssuesByFeature(state.featureId);
|
|
239
|
+
for (const issue of foundIssues) {
|
|
240
|
+
// Extract user story ID from title: [FS-063][US-001] Title
|
|
241
|
+
const match = issue.title.match(/\[([A-Z]+-\d+)\]\[([A-Z]+-\d+)\]/);
|
|
242
|
+
if (match && match[1] === state.featureId) {
|
|
243
|
+
const usId = match[2];
|
|
244
|
+
state.userStoryIssues.push({
|
|
245
|
+
userStoryId: usId,
|
|
246
|
+
issueNumber: issue.number,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (state.userStoryIssues.length > 0) {
|
|
251
|
+
this.logger.log(` Found ${state.userStoryIssues.length} issue(s) via GitHub search`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
this.logger.log(` ⚠️ GitHub search failed: ${error.message}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Only include if has GitHub links
|
|
260
|
+
if (state.mainIssue || state.userStoryIssues.length > 0) {
|
|
261
|
+
results.push(state);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
// Skip invalid metadata
|
|
266
|
+
this.logger.log(` ⚠️ Skipping ${entry.name}: Invalid metadata.json`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return results;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Initialize GitHub client
|
|
273
|
+
*/
|
|
274
|
+
async initClient() {
|
|
275
|
+
const repoInfo = await GitHubClientV2.detectRepo(this.projectRoot);
|
|
276
|
+
if (!repoInfo) {
|
|
277
|
+
throw new Error('Could not detect GitHub repository. Ensure you have a git remote configured.');
|
|
278
|
+
}
|
|
279
|
+
this.client = GitHubClientV2.fromRepo(repoInfo.owner, repoInfo.repo);
|
|
280
|
+
this.logger.log(`🔗 GitHub repository: ${repoInfo.owner}/${repoInfo.repo}`);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Load config
|
|
284
|
+
*/
|
|
285
|
+
async loadConfig() {
|
|
286
|
+
const configPath = path.join(this.projectRoot, '.specweave/config.json');
|
|
287
|
+
if (!existsSync(configPath)) {
|
|
288
|
+
return {};
|
|
289
|
+
}
|
|
290
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
291
|
+
return JSON.parse(content);
|
|
292
|
+
}
|
|
293
|
+
// ==========================================================================
|
|
294
|
+
// Static helpers for single-increment operations (used by hooks)
|
|
295
|
+
// ==========================================================================
|
|
296
|
+
/**
|
|
297
|
+
* Reopen all GitHub issues for an increment
|
|
298
|
+
* Called by post-increment-status-change.sh when resuming
|
|
299
|
+
*/
|
|
300
|
+
static async reopenIncrementIssues(projectRoot, incrementId, reason, logger) {
|
|
301
|
+
const log = logger ?? consoleLogger;
|
|
302
|
+
const result = { reopened: 0, errors: [] };
|
|
303
|
+
try {
|
|
304
|
+
// Load metadata
|
|
305
|
+
const metadataPath = path.join(projectRoot, '.specweave/increments', incrementId, 'metadata.json');
|
|
306
|
+
if (!existsSync(metadataPath)) {
|
|
307
|
+
result.errors.push('metadata.json not found');
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
|
|
311
|
+
// Initialize client
|
|
312
|
+
const repoInfo = await GitHubClientV2.detectRepo(projectRoot);
|
|
313
|
+
if (!repoInfo) {
|
|
314
|
+
result.errors.push('Could not detect GitHub repository');
|
|
315
|
+
return result;
|
|
316
|
+
}
|
|
317
|
+
const client = GitHubClientV2.fromRepo(repoInfo.owner, repoInfo.repo);
|
|
318
|
+
const comment = `## ▶️ Increment Resumed
|
|
319
|
+
|
|
320
|
+
This issue was reopened because increment \`${incrementId}\` was resumed.
|
|
321
|
+
|
|
322
|
+
**Reason**: ${reason}
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
🤖 Auto-reopened by SpecWeave`;
|
|
326
|
+
// Reopen main issue
|
|
327
|
+
if (metadata.github?.issue) {
|
|
328
|
+
try {
|
|
329
|
+
const issue = await client.getIssue(metadata.github.issue);
|
|
330
|
+
if (issue.state === 'closed') {
|
|
331
|
+
await client.reopenIssue(metadata.github.issue, comment);
|
|
332
|
+
result.reopened++;
|
|
333
|
+
log.log(` ✅ Reopened main issue #${metadata.github.issue}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
result.errors.push(`Main issue: ${error.message}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// Reopen User Story issues
|
|
341
|
+
if (metadata.github?.issues && Array.isArray(metadata.github.issues)) {
|
|
342
|
+
for (const usIssue of metadata.github.issues) {
|
|
343
|
+
if (usIssue.number) {
|
|
344
|
+
try {
|
|
345
|
+
const issue = await client.getIssue(usIssue.number);
|
|
346
|
+
if (issue.state === 'closed') {
|
|
347
|
+
await client.reopenIssue(usIssue.number, comment);
|
|
348
|
+
result.reopened++;
|
|
349
|
+
log.log(` ✅ Reopened User Story issue #${usIssue.number}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
result.errors.push(`Issue #${usIssue.number}: ${error.message}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return result;
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
result.errors.push(error.message);
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Close all GitHub issues for an abandoned increment
|
|
367
|
+
* Called by post-increment-status-change.sh when abandoning
|
|
368
|
+
*/
|
|
369
|
+
static async closeAbandonedIncrementIssues(projectRoot, incrementId, reason, logger) {
|
|
370
|
+
const log = logger ?? consoleLogger;
|
|
371
|
+
const result = { closed: 0, errors: [] };
|
|
372
|
+
try {
|
|
373
|
+
// Load metadata
|
|
374
|
+
const metadataPath = path.join(projectRoot, '.specweave/increments', incrementId, 'metadata.json');
|
|
375
|
+
if (!existsSync(metadataPath)) {
|
|
376
|
+
result.errors.push('metadata.json not found');
|
|
377
|
+
return result;
|
|
378
|
+
}
|
|
379
|
+
const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
|
|
380
|
+
// Initialize client
|
|
381
|
+
const repoInfo = await GitHubClientV2.detectRepo(projectRoot);
|
|
382
|
+
if (!repoInfo) {
|
|
383
|
+
result.errors.push('Could not detect GitHub repository');
|
|
384
|
+
return result;
|
|
385
|
+
}
|
|
386
|
+
const client = GitHubClientV2.fromRepo(repoInfo.owner, repoInfo.repo);
|
|
387
|
+
const comment = `## 🗑️ Increment Abandoned
|
|
388
|
+
|
|
389
|
+
This issue was closed because increment \`${incrementId}\` was abandoned.
|
|
390
|
+
|
|
391
|
+
**Reason**: ${reason}
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
🤖 Auto-closed by SpecWeave`;
|
|
395
|
+
// Close main issue
|
|
396
|
+
if (metadata.github?.issue) {
|
|
397
|
+
try {
|
|
398
|
+
const issue = await client.getIssue(metadata.github.issue);
|
|
399
|
+
if (issue.state === 'open') {
|
|
400
|
+
await client.closeIssue(metadata.github.issue, comment);
|
|
401
|
+
result.closed++;
|
|
402
|
+
log.log(` ✅ Closed main issue #${metadata.github.issue}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
result.errors.push(`Main issue: ${error.message}`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
// Close User Story issues
|
|
410
|
+
if (metadata.github?.issues && Array.isArray(metadata.github.issues)) {
|
|
411
|
+
for (const usIssue of metadata.github.issues) {
|
|
412
|
+
if (usIssue.number) {
|
|
413
|
+
try {
|
|
414
|
+
const issue = await client.getIssue(usIssue.number);
|
|
415
|
+
if (issue.state === 'open') {
|
|
416
|
+
await client.closeIssue(usIssue.number, comment);
|
|
417
|
+
result.closed++;
|
|
418
|
+
log.log(` ✅ Closed User Story issue #${usIssue.number}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
result.errors.push(`Issue #${usIssue.number}: ${error.message}`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return result;
|
|
428
|
+
}
|
|
429
|
+
catch (error) {
|
|
430
|
+
result.errors.push(error.message);
|
|
431
|
+
return result;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
//# sourceMappingURL=github-reconciler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-reconciler.js","sourceRoot":"","sources":["../../../src/sync/github-reconciler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: specweave-github:reconcile
|
|
3
|
+
description: Reconcile GitHub issue states with increment statuses. Fixes drift by closing issues for completed increments and reopening issues for resumed increments.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GitHub Status Reconciliation
|
|
7
|
+
|
|
8
|
+
Scan all increments and fix any drift between local metadata.json status and GitHub issue states.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
/specweave-github:reconcile [options]
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Options
|
|
17
|
+
|
|
18
|
+
- `--dry-run`: Preview changes without making them
|
|
19
|
+
- `--verbose`: Show detailed output for each issue checked
|
|
20
|
+
|
|
21
|
+
## What It Does
|
|
22
|
+
|
|
23
|
+
1. **Scans** all non-archived increments
|
|
24
|
+
2. **Compares** metadata.json status with GitHub issue state
|
|
25
|
+
3. **Fixes** mismatches:
|
|
26
|
+
- `metadata=completed` + `GH=open` → **Close** the issue
|
|
27
|
+
- `metadata=in-progress` + `GH=closed` → **Reopen** the issue
|
|
28
|
+
|
|
29
|
+
## When to Use
|
|
30
|
+
|
|
31
|
+
- After manual metadata.json edits
|
|
32
|
+
- After git pulls that changed increment statuses
|
|
33
|
+
- When you notice open issues for completed work
|
|
34
|
+
- As a periodic health check
|
|
35
|
+
|
|
36
|
+
## Implementation
|
|
37
|
+
|
|
38
|
+
Run the reconciliation using the GitHubReconciler:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { GitHubReconciler } from '../../../src/sync/github-reconciler.js';
|
|
42
|
+
|
|
43
|
+
const reconciler = new GitHubReconciler({
|
|
44
|
+
projectRoot: process.cwd(),
|
|
45
|
+
dryRun: args.includes('--dry-run'),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const result = await reconciler.reconcile();
|
|
49
|
+
|
|
50
|
+
// Report results
|
|
51
|
+
console.log(`\nReconciliation complete:`);
|
|
52
|
+
console.log(` Scanned: ${result.scanned} increments`);
|
|
53
|
+
console.log(` Fixed: ${result.closed} closed, ${result.reopened} reopened`);
|
|
54
|
+
if (result.errors.length > 0) {
|
|
55
|
+
console.log(` Errors: ${result.errors.length}`);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Example Output
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
📊 Scanning 5 increment(s) for GitHub state drift...
|
|
63
|
+
|
|
64
|
+
✅ Issue #765 (0056-plugin-fix/US-001): State matches (open)
|
|
65
|
+
❌ Issue #771 (0066-import-wizard/US-003): OPEN but should be CLOSED (status=completed)
|
|
66
|
+
✅ Closed issue #771
|
|
67
|
+
❌ Issue #763 (0063-multi-repo/US-001): CLOSED but should be OPEN (status=in-progress)
|
|
68
|
+
✅ Reopened issue #763
|
|
69
|
+
|
|
70
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
71
|
+
📊 RECONCILIATION SUMMARY
|
|
72
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
73
|
+
Increments scanned: 5
|
|
74
|
+
Mismatches found: 2
|
|
75
|
+
Issues closed: 1
|
|
76
|
+
Issues reopened: 1
|
|
77
|
+
Errors: 0
|
|
78
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Dry Run Mode
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
/specweave-github:reconcile --dry-run
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Shows what would be changed without making any modifications:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
❌ Issue #771 (0066-import-wizard/US-003): OPEN but should be CLOSED
|
|
91
|
+
[DRY RUN] Would close issue #771
|
|
92
|
+
|
|
93
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
94
|
+
📊 RECONCILIATION SUMMARY
|
|
95
|
+
⚠️ DRY RUN - No changes were made
|
|
96
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Requirements
|
|
100
|
+
|
|
101
|
+
- GitHub CLI (`gh`) installed and authenticated
|
|
102
|
+
- `sync.github.enabled = true` in config.json
|
|
103
|
+
- `sync.settings.canUpdateExternalItems = true` in config.json
|
|
104
|
+
|
|
105
|
+
## Related Commands
|
|
106
|
+
|
|
107
|
+
- `/specweave-github:status`: View sync status for increments
|
|
108
|
+
- `/specweave-github:sync`: Manual sync to GitHub
|
|
109
|
+
- `/specweave:done`: Close increment (triggers auto-close)
|
|
110
|
+
- `/specweave:resume`: Resume increment (now triggers auto-reopen)
|