specweave 1.0.301 → 1.0.304
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-ac-comment-poster.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-ac-comment-poster.js +44 -25
- package/dist/plugins/specweave-github/lib/github-ac-comment-poster.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync-cli.js +6 -0
- package/dist/plugins/specweave-github/lib/github-feature-sync-cli.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts +36 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.js +266 -5
- package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/user-story-content-builder.d.ts +2 -1
- package/dist/plugins/specweave-github/lib/user-story-content-builder.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/user-story-content-builder.js +6 -4
- package/dist/plugins/specweave-github/lib/user-story-content-builder.js.map +1 -1
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.js +37 -17
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.js.map +1 -1
- package/dist/src/cli/commands/refresh-plugins.d.ts.map +1 -1
- package/dist/src/cli/commands/refresh-plugins.js +9 -0
- package/dist/src/cli/commands/refresh-plugins.js.map +1 -1
- package/dist/src/cli/commands/sync-progress.d.ts.map +1 -1
- package/dist/src/cli/commands/sync-progress.js +72 -2
- package/dist/src/cli/commands/sync-progress.js.map +1 -1
- package/dist/src/config/types.d.ts +2 -2
- package/dist/src/core/increment/increment-utils.d.ts +27 -4
- package/dist/src/core/increment/increment-utils.d.ts.map +1 -1
- package/dist/src/core/increment/increment-utils.js +44 -17
- package/dist/src/core/increment/increment-utils.js.map +1 -1
- package/dist/src/core/increment/template-creator.d.ts +26 -0
- package/dist/src/core/increment/template-creator.d.ts.map +1 -1
- package/dist/src/core/increment/template-creator.js +179 -20
- package/dist/src/core/increment/template-creator.js.map +1 -1
- package/dist/src/importers/import-to-increment.d.ts +111 -0
- package/dist/src/importers/import-to-increment.d.ts.map +1 -0
- package/dist/src/importers/import-to-increment.js +223 -0
- package/dist/src/importers/import-to-increment.js.map +1 -0
- package/dist/src/importers/increment-external-ref-detector.d.ts +78 -0
- package/dist/src/importers/increment-external-ref-detector.d.ts.map +1 -0
- package/dist/src/importers/increment-external-ref-detector.js +130 -0
- package/dist/src/importers/increment-external-ref-detector.js.map +1 -0
- package/dist/src/init/research/types.d.ts +1 -1
- package/dist/src/sync/external-issue-auto-creator.d.ts.map +1 -1
- package/dist/src/sync/external-issue-auto-creator.js +28 -1
- package/dist/src/sync/external-issue-auto-creator.js.map +1 -1
- package/dist/src/sync/sync-coordinator.d.ts +6 -0
- package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
- package/dist/src/sync/sync-coordinator.js +42 -2
- package/dist/src/sync/sync-coordinator.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/hooks/lib/update-active-increment.sh +2 -2
- package/plugins/specweave/hooks/lib/update-status-line.sh +2 -2
- package/plugins/specweave/hooks/stop-auto-v5.sh +28 -8
- package/plugins/specweave/hooks/stop-sync.sh +10 -5
- package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +8 -4
- package/plugins/specweave/hooks/user-prompt-submit.sh +130 -112
- package/plugins/specweave/hooks/v2/handlers/github-sync-handler.sh +6 -3
- package/plugins/specweave/hooks/v2/handlers/project-bridge-handler.sh +4 -3
- package/plugins/specweave/skills/auto/SKILL.md +5 -3
- package/plugins/specweave/skills/done/SKILL.md +9 -3
- package/plugins/specweave/skills/import/SKILL.md +186 -0
- package/plugins/specweave/skills/increment/SKILL.md +30 -16
- package/plugins/specweave/skills/pm/SKILL.md +29 -2
- package/plugins/specweave/skills/pm/phases/00-deep-interview.md +12 -0
- package/plugins/specweave/skills/team-lead/SKILL.md +4 -2
- package/plugins/specweave/skills/team-merge/SKILL.md +2 -2
- package/plugins/specweave-github/lib/github-ac-comment-poster.js +31 -19
- package/plugins/specweave-github/lib/github-ac-comment-poster.ts +44 -27
- package/plugins/specweave-github/lib/github-feature-sync-cli.js +5 -0
- package/plugins/specweave-github/lib/github-feature-sync-cli.ts +7 -1
- package/plugins/specweave-github/lib/github-feature-sync.js +274 -6
- package/plugins/specweave-github/lib/github-feature-sync.ts +353 -5
- package/plugins/specweave-github/lib/user-story-content-builder.js +6 -4
- package/plugins/specweave-github/lib/user-story-content-builder.ts +6 -4
- package/plugins/specweave-github/lib/user-story-issue-builder.js +26 -11
- package/plugins/specweave-github/lib/user-story-issue-builder.ts +37 -19
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readdir, readFile, writeFile } from "fs/promises";
|
|
1
|
+
import { readdir, readFile, writeFile, mkdir } from "fs/promises";
|
|
2
2
|
import { existsSync } from "fs";
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import * as yaml from "yaml";
|
|
@@ -10,12 +10,39 @@ import { getGitHubAuthFromProject } from "../../../src/utils/auth-helpers.js";
|
|
|
10
10
|
const _GitHubFeatureSync = class _GitHubFeatureSync {
|
|
11
11
|
// 30 seconds
|
|
12
12
|
constructor(client, specsDir, projectRoot) {
|
|
13
|
+
// Cached default branch for the sync session (one API call per session)
|
|
14
|
+
this.defaultBranch = null;
|
|
13
15
|
this.client = client;
|
|
14
16
|
this.specsDir = specsDir;
|
|
15
17
|
this.projectRoot = projectRoot;
|
|
16
18
|
this.calculator = new CompletionCalculator(projectRoot);
|
|
17
19
|
this.token = getGitHubAuthFromProject(projectRoot).token;
|
|
18
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Detect the default branch from the GitHub API.
|
|
23
|
+
* Caches the result per sync session to avoid repeated API calls.
|
|
24
|
+
* Falls back to 'main' if API call fails.
|
|
25
|
+
*/
|
|
26
|
+
async detectDefaultBranch() {
|
|
27
|
+
if (this.defaultBranch) {
|
|
28
|
+
return this.defaultBranch;
|
|
29
|
+
}
|
|
30
|
+
const owner = this.client.getOwner();
|
|
31
|
+
const repo = this.client.getRepo();
|
|
32
|
+
const result = await execFileNoThrow("gh", [
|
|
33
|
+
"api",
|
|
34
|
+
`repos/${owner}/${repo}`,
|
|
35
|
+
"--jq",
|
|
36
|
+
".default_branch"
|
|
37
|
+
], { env: this.getGhEnv() });
|
|
38
|
+
if (result.exitCode === 0 && result.stdout.trim()) {
|
|
39
|
+
this.defaultBranch = result.stdout.trim();
|
|
40
|
+
} else {
|
|
41
|
+
console.warn(` \u26A0\uFE0F Failed to detect default branch, falling back to 'main': ${result.stderr}`);
|
|
42
|
+
this.defaultBranch = "main";
|
|
43
|
+
}
|
|
44
|
+
return this.defaultBranch;
|
|
45
|
+
}
|
|
19
46
|
/**
|
|
20
47
|
* Get environment object with GH_TOKEN for gh CLI commands.
|
|
21
48
|
* This ensures the token from .env is passed to all gh operations,
|
|
@@ -83,14 +110,15 @@ const _GitHubFeatureSync = class _GitHubFeatureSync {
|
|
|
83
110
|
\u{1F4DD} Found ${userStories.length} User Stories to sync...`);
|
|
84
111
|
let issuesCreated = 0;
|
|
85
112
|
let issuesUpdated = 0;
|
|
113
|
+
const detectedBranch = await this.detectDefaultBranch();
|
|
114
|
+
console.log(` \u{1F33F} Default branch: ${detectedBranch}`);
|
|
86
115
|
for (const userStory of userStories) {
|
|
87
116
|
console.log(`
|
|
88
117
|
\u{1F539} Processing ${userStory.id}: ${userStory.title}`);
|
|
89
118
|
const repoInfo = {
|
|
90
119
|
owner: this.client.getOwner(),
|
|
91
120
|
repo: this.client.getRepo(),
|
|
92
|
-
branch:
|
|
93
|
-
// TODO: detect from git
|
|
121
|
+
branch: detectedBranch
|
|
94
122
|
};
|
|
95
123
|
const builder = new UserStoryIssueBuilder(
|
|
96
124
|
userStory.filePath,
|
|
@@ -160,7 +188,8 @@ const _GitHubFeatureSync = class _GitHubFeatureSync {
|
|
|
160
188
|
};
|
|
161
189
|
}
|
|
162
190
|
/**
|
|
163
|
-
* Find Feature folder in specs directory
|
|
191
|
+
* Find Feature folder in specs directory.
|
|
192
|
+
* Falls back to auto-creating from increment spec.md if living docs don't exist.
|
|
164
193
|
*/
|
|
165
194
|
async findFeatureFolder(featureId) {
|
|
166
195
|
const projectFolders = await this.findProjectFolders();
|
|
@@ -175,8 +204,230 @@ const _GitHubFeatureSync = class _GitHubFeatureSync {
|
|
|
175
204
|
console.log(` \u26A0\uFE0F Found feature in legacy _features folder - consider migrating to project folder`);
|
|
176
205
|
return legacyFolder;
|
|
177
206
|
}
|
|
207
|
+
console.log(` \u2139\uFE0F Feature folder not found in living docs, attempting auto-create from spec.md...`);
|
|
208
|
+
const created = await this.createFeatureFolderFromSpec(featureId, projectFolders);
|
|
209
|
+
if (created) {
|
|
210
|
+
return created;
|
|
211
|
+
}
|
|
178
212
|
return null;
|
|
179
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Find the increment folder for a given feature ID.
|
|
216
|
+
* Converts FS-271 -> finds 0271-xxx-xxx/ in .specweave/increments/
|
|
217
|
+
*/
|
|
218
|
+
async findIncrementFolder(featureId) {
|
|
219
|
+
const numMatch = featureId.match(/FS-0*(\d+)E?/i);
|
|
220
|
+
if (!numMatch) return null;
|
|
221
|
+
const num = parseInt(numMatch[1], 10);
|
|
222
|
+
const paddedNum = String(num).padStart(4, "0");
|
|
223
|
+
const incrementsDir = path.join(this.projectRoot, ".specweave/increments");
|
|
224
|
+
if (!existsSync(incrementsDir)) return null;
|
|
225
|
+
const entries = await readdir(incrementsDir);
|
|
226
|
+
const match = entries.find((e) => e.startsWith(paddedNum + "-"));
|
|
227
|
+
if (!match) return null;
|
|
228
|
+
return path.join(incrementsDir, match);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Auto-create a feature folder (FEATURE.md + us-NNN.md files) from an
|
|
232
|
+
* increment's spec.md. This enables GitHub sync even when the living docs
|
|
233
|
+
* builder hasn't run yet.
|
|
234
|
+
*/
|
|
235
|
+
async createFeatureFolderFromSpec(featureId, projectFolders) {
|
|
236
|
+
try {
|
|
237
|
+
const incrementFolder = await this.findIncrementFolder(featureId);
|
|
238
|
+
if (!incrementFolder) {
|
|
239
|
+
console.log(` \u26A0\uFE0F No increment folder found for ${featureId}`);
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
const specPath = path.join(incrementFolder, "spec.md");
|
|
243
|
+
if (!existsSync(specPath)) {
|
|
244
|
+
console.log(` \u26A0\uFE0F No spec.md found in ${path.basename(incrementFolder)}`);
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
const specContent = await readFile(specPath, "utf-8");
|
|
248
|
+
const fmMatch = specContent.match(/^---\n([\s\S]*?)\n---/);
|
|
249
|
+
if (!fmMatch) {
|
|
250
|
+
console.log(` \u26A0\uFE0F spec.md has no YAML frontmatter`);
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
const frontmatter = yaml.parse(fmMatch[1]);
|
|
254
|
+
const title = frontmatter.title || path.basename(incrementFolder).replace(/^\d+-/, "");
|
|
255
|
+
const status = frontmatter.status || "active";
|
|
256
|
+
const priority = frontmatter.priority || "P2";
|
|
257
|
+
const created = frontmatter.created || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
258
|
+
const incrementId = frontmatter.increment || path.basename(incrementFolder);
|
|
259
|
+
let targetProjectFolder = projectFolders[0];
|
|
260
|
+
const projectMatch = specContent.match(/\*\*Project\*\*:\s*(\S+)/);
|
|
261
|
+
if (projectMatch) {
|
|
262
|
+
const projectName = projectMatch[1];
|
|
263
|
+
const matchingFolder = projectFolders.find((f) => path.basename(f) === projectName);
|
|
264
|
+
if (matchingFolder) {
|
|
265
|
+
targetProjectFolder = matchingFolder;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (!targetProjectFolder) {
|
|
269
|
+
console.log(` \u26A0\uFE0F No project folder available for feature creation`);
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
const featureFolder = path.join(targetProjectFolder, featureId);
|
|
273
|
+
await mkdir(featureFolder, { recursive: true });
|
|
274
|
+
const userStories = this.parseUserStoriesFromSpec(specContent, featureId);
|
|
275
|
+
const featureMd = this.buildFeatureMd(featureId, title, status, priority, created, incrementId, userStories);
|
|
276
|
+
await writeFile(path.join(featureFolder, "FEATURE.md"), featureMd, "utf-8");
|
|
277
|
+
for (const us of userStories) {
|
|
278
|
+
const usFilename = `us-${us.id.replace("US-", "").padStart(3, "0")}-${this.slugify(us.title)}.md`;
|
|
279
|
+
const usMd = this.buildUserStoryMd(us, featureId, incrementId);
|
|
280
|
+
await writeFile(path.join(featureFolder, usFilename), usMd, "utf-8");
|
|
281
|
+
}
|
|
282
|
+
console.log(` \u2705 Auto-created feature folder with ${userStories.length} user stories`);
|
|
283
|
+
return featureFolder;
|
|
284
|
+
} catch (error) {
|
|
285
|
+
console.log(` \u26A0\uFE0F Failed to auto-create feature folder: ${error.message}`);
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Parse user stories from spec.md markdown content.
|
|
291
|
+
*/
|
|
292
|
+
parseUserStoriesFromSpec(specContent, featureId) {
|
|
293
|
+
const stories = [];
|
|
294
|
+
const usRegex = /### (US-\d+):\s*(.+?)(?:\s*\((P\d)\))?\s*\n([\s\S]*?)(?=\n### US-|\n## |\n---\s*\n### US-|$)/g;
|
|
295
|
+
let match;
|
|
296
|
+
while ((match = usRegex.exec(specContent)) !== null) {
|
|
297
|
+
const usId = match[1];
|
|
298
|
+
const rawTitle = match[2].trim();
|
|
299
|
+
const priority = match[3] || "P2";
|
|
300
|
+
const body = match[4];
|
|
301
|
+
if (rawTitle === "[Story Title]") continue;
|
|
302
|
+
const projectMatch = body.match(/\*\*Project\*\*:\s*(\S+)/);
|
|
303
|
+
const project = projectMatch ? projectMatch[1] : "specweave";
|
|
304
|
+
const storyMatch = body.match(/\*\*As a\*\*\s+([\s\S]*?)(?=\n\*\*Acceptance Criteria|$)/);
|
|
305
|
+
const storyText = storyMatch ? storyMatch[1].trim() : "";
|
|
306
|
+
const acs = [];
|
|
307
|
+
const acRegex = /- \[[ x]\] \*\*AC-[^*]+\*\*:\s*(.+)/g;
|
|
308
|
+
let acMatch;
|
|
309
|
+
while ((acMatch = acRegex.exec(body)) !== null) {
|
|
310
|
+
acs.push(acMatch[0]);
|
|
311
|
+
}
|
|
312
|
+
const totalAcs = acs.length;
|
|
313
|
+
const completedAcs = acs.filter((ac) => ac.startsWith("- [x]")).length;
|
|
314
|
+
let status = "not-started";
|
|
315
|
+
if (totalAcs > 0 && completedAcs === totalAcs) status = "complete";
|
|
316
|
+
else if (completedAcs > 0) status = "active";
|
|
317
|
+
stories.push({
|
|
318
|
+
id: usId,
|
|
319
|
+
title: rawTitle,
|
|
320
|
+
priority,
|
|
321
|
+
project,
|
|
322
|
+
storyText,
|
|
323
|
+
acceptanceCriteria: acs,
|
|
324
|
+
status
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
return stories;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Build FEATURE.md content matching the living docs format.
|
|
331
|
+
*/
|
|
332
|
+
buildFeatureMd(featureId, title, status, priority, created, incrementId, userStories) {
|
|
333
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
334
|
+
const mappedStatus = status === "planned" ? "planning" : status === "completed" || status === "done" ? "complete" : status === "active" || status === "in-progress" ? "active" : "planning";
|
|
335
|
+
const fm = {
|
|
336
|
+
id: featureId,
|
|
337
|
+
title,
|
|
338
|
+
type: "feature",
|
|
339
|
+
status: mappedStatus,
|
|
340
|
+
priority,
|
|
341
|
+
created,
|
|
342
|
+
lastUpdated: now,
|
|
343
|
+
tldr: title,
|
|
344
|
+
complexity: "medium",
|
|
345
|
+
auto_created: true
|
|
346
|
+
};
|
|
347
|
+
const yamlFm = yaml.stringify(fm);
|
|
348
|
+
let body = `
|
|
349
|
+
# ${title}
|
|
350
|
+
|
|
351
|
+
## TL;DR
|
|
352
|
+
|
|
353
|
+
**What**: ${title}
|
|
354
|
+
**Status**: ${mappedStatus} | **Priority**: ${priority}
|
|
355
|
+
**User Stories**: ${userStories.length}
|
|
356
|
+
|
|
357
|
+
## Overview
|
|
358
|
+
|
|
359
|
+
${title}
|
|
360
|
+
|
|
361
|
+
## Implementation History
|
|
362
|
+
|
|
363
|
+
| Increment | Status |
|
|
364
|
+
|-----------|--------|
|
|
365
|
+
| [${incrementId}](../../../../../increments/${incrementId}/spec.md) | ${mappedStatus} |
|
|
366
|
+
|
|
367
|
+
## User Stories
|
|
368
|
+
`;
|
|
369
|
+
for (const us of userStories) {
|
|
370
|
+
body += `
|
|
371
|
+
- [${us.id}: ${us.title}](./${us.id.toLowerCase()}.md)`;
|
|
372
|
+
}
|
|
373
|
+
return `---
|
|
374
|
+
${yamlFm}---${body}
|
|
375
|
+
`;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Build us-NNN.md content matching the living docs format.
|
|
379
|
+
*/
|
|
380
|
+
buildUserStoryMd(us, featureId, incrementId) {
|
|
381
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
382
|
+
const fm = {
|
|
383
|
+
id: us.id,
|
|
384
|
+
feature: featureId,
|
|
385
|
+
title: us.title,
|
|
386
|
+
status: us.status,
|
|
387
|
+
priority: us.priority,
|
|
388
|
+
created: now,
|
|
389
|
+
project: us.project
|
|
390
|
+
};
|
|
391
|
+
const yamlFm = yaml.stringify(fm);
|
|
392
|
+
let body = `
|
|
393
|
+
# ${us.id}: ${us.title}
|
|
394
|
+
|
|
395
|
+
**Feature**: [${featureId}](./FEATURE.md)
|
|
396
|
+
|
|
397
|
+
`;
|
|
398
|
+
if (us.storyText) {
|
|
399
|
+
body += `${us.storyText}
|
|
400
|
+
|
|
401
|
+
`;
|
|
402
|
+
}
|
|
403
|
+
body += `---
|
|
404
|
+
|
|
405
|
+
## Acceptance Criteria
|
|
406
|
+
|
|
407
|
+
`;
|
|
408
|
+
if (us.acceptanceCriteria.length > 0) {
|
|
409
|
+
body += us.acceptanceCriteria.join("\n") + "\n";
|
|
410
|
+
} else {
|
|
411
|
+
body += `- [ ] **AC-${us.id.replace("US-", "US")}-01**: Pending specification
|
|
412
|
+
`;
|
|
413
|
+
}
|
|
414
|
+
body += `
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## Implementation
|
|
418
|
+
|
|
419
|
+
**Increment**: [${incrementId}](../../../../../increments/${incrementId}/spec.md)
|
|
420
|
+
`;
|
|
421
|
+
return `---
|
|
422
|
+
${yamlFm}---${body}
|
|
423
|
+
`;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Convert a title to a URL-safe slug.
|
|
427
|
+
*/
|
|
428
|
+
slugify(text) {
|
|
429
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").substring(0, 60);
|
|
430
|
+
}
|
|
180
431
|
/**
|
|
181
432
|
* Backfill increment metadata.json with GitHub issue reference (v1.0.240)
|
|
182
433
|
*
|
|
@@ -282,9 +533,11 @@ const _GitHubFeatureSync = class _GitHubFeatureSync {
|
|
|
282
533
|
*/
|
|
283
534
|
async createMilestone(featureData) {
|
|
284
535
|
const title = `${featureData.id}: ${featureData.title}`;
|
|
536
|
+
const owner = this.client.getOwner();
|
|
537
|
+
const repo = this.client.getRepo();
|
|
285
538
|
const existingResult = await execFileNoThrow("gh", [
|
|
286
539
|
"api",
|
|
287
|
-
|
|
540
|
+
`repos/${owner}/${repo}/milestones?per_page=100&state=all`,
|
|
288
541
|
"--jq",
|
|
289
542
|
`.[] | select(.title == "${title}") | {number, html_url}`
|
|
290
543
|
], { env: this.getGhEnv() });
|
|
@@ -307,7 +560,7 @@ Status: ${featureData.status}
|
|
|
307
560
|
Created: ${featureData.created}`;
|
|
308
561
|
const result = await execFileNoThrow("gh", [
|
|
309
562
|
"api",
|
|
310
|
-
|
|
563
|
+
`repos/${owner}/${repo}/milestones`,
|
|
311
564
|
"-X",
|
|
312
565
|
"POST",
|
|
313
566
|
"-f",
|
|
@@ -490,6 +743,21 @@ Created: ${featureData.created}`;
|
|
|
490
743
|
} else {
|
|
491
744
|
console.warn(` \u26A0\uFE0F Failed to add label ${newStatusLabel}: ${result.stderr}`);
|
|
492
745
|
}
|
|
746
|
+
if (newStatusLabel === "status:complete" && issueData.state.toLowerCase() !== "closed") {
|
|
747
|
+
try {
|
|
748
|
+
const completionComment = this.calculator.buildCompletionComment(completion);
|
|
749
|
+
await execFileNoThrow("gh", [
|
|
750
|
+
"issue",
|
|
751
|
+
"close",
|
|
752
|
+
issueNumber.toString(),
|
|
753
|
+
"--comment",
|
|
754
|
+
completionComment
|
|
755
|
+
], { env: this.getGhEnv() });
|
|
756
|
+
console.log(` \u2705 Auto-closed issue #${issueNumber} (status:complete)`);
|
|
757
|
+
} catch (closeError) {
|
|
758
|
+
console.warn(` \u26A0\uFE0F Failed to auto-close issue #${issueNumber}: ${closeError.message}`);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
493
761
|
} catch (error) {
|
|
494
762
|
console.warn(` \u26A0\uFE0F Failed to update status labels: ${error.message}`);
|
|
495
763
|
}
|