specweave 1.0.326 → 1.0.327

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.
@@ -82,7 +82,15 @@ const _GitHubFeatureSync = class _GitHubFeatureSync {
82
82
  \u{1F504} Syncing Feature ${featureId} to GitHub...`);
83
83
  const featureFolder = await this.findFeatureFolder(featureId);
84
84
  if (!featureFolder) {
85
- throw new Error(`Feature ${featureId} not found in ${this.specsDir}`);
85
+ console.log(` \u26A0\uFE0F Feature ${featureId} not found in ${this.specsDir} (no living docs and auto-create failed)`);
86
+ console.log(` \u{1F4A1} Run /sw:sync-docs or /sw:living-docs to generate living docs first`);
87
+ return {
88
+ milestoneNumber: 0,
89
+ milestoneUrl: "",
90
+ issuesCreated: 0,
91
+ issuesUpdated: 0,
92
+ userStoriesProcessed: 0
93
+ };
86
94
  }
87
95
  const featurePath = path.join(featureFolder, "FEATURE.md");
88
96
  const featureData = await this.parseFeatureMd(featurePath);
@@ -246,16 +254,19 @@ const _GitHubFeatureSync = class _GitHubFeatureSync {
246
254
  }
247
255
  const specContent = await readFile(specPath, "utf-8");
248
256
  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;
257
+ let frontmatter = {};
258
+ if (fmMatch) {
259
+ try {
260
+ frontmatter = yaml.parse(fmMatch[1]) || {};
261
+ } catch {
262
+ }
252
263
  }
253
- const frontmatter = yaml.parse(fmMatch[1]);
254
- const title = frontmatter.title || path.basename(incrementFolder).replace(/^\d+-/, "");
264
+ const incrementBasename = path.basename(incrementFolder);
265
+ const title = frontmatter.title || specContent.match(/^#\s+(.+)/m)?.[1]?.trim() || incrementBasename.replace(/^\d+-/, "").replace(/-/g, " ");
255
266
  const status = frontmatter.status || "active";
256
267
  const priority = frontmatter.priority || "P2";
257
268
  const created = frontmatter.created || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
258
- const incrementId = frontmatter.increment || path.basename(incrementFolder);
269
+ const incrementId = frontmatter.increment || incrementBasename;
259
270
  let targetProjectFolder = projectFolders[0];
260
271
  const projectMatch = specContent.match(/\*\*Project\*\*:\s*(\S+)/);
261
272
  if (projectMatch) {
@@ -154,7 +154,15 @@ export class GitHubFeatureSync {
154
154
  // 1. Load Feature FEATURE.md
155
155
  const featureFolder = await this.findFeatureFolder(featureId);
156
156
  if (!featureFolder) {
157
- throw new Error(`Feature ${featureId} not found in ${this.specsDir}`);
157
+ console.log(` ⚠️ Feature ${featureId} not found in ${this.specsDir} (no living docs and auto-create failed)`);
158
+ console.log(` 💡 Run /sw:sync-docs or /sw:living-docs to generate living docs first`);
159
+ return {
160
+ milestoneNumber: 0,
161
+ milestoneUrl: '',
162
+ issuesCreated: 0,
163
+ issuesUpdated: 0,
164
+ userStoriesProcessed: 0,
165
+ };
158
166
  }
159
167
 
160
168
  const featurePath = path.join(featureFolder, 'FEATURE.md');
@@ -407,19 +415,25 @@ export class GitHubFeatureSync {
407
415
 
408
416
  const specContent = await readFile(specPath, 'utf-8');
409
417
 
410
- // Parse frontmatter
418
+ // Parse frontmatter (optional — many specs don't have YAML frontmatter)
411
419
  const fmMatch = specContent.match(/^---\n([\s\S]*?)\n---/);
412
- if (!fmMatch) {
413
- console.log(` ⚠️ spec.md has no YAML frontmatter`);
414
- return null;
420
+ let frontmatter: Record<string, string> = {};
421
+ if (fmMatch) {
422
+ try {
423
+ frontmatter = yaml.parse(fmMatch[1]) || {};
424
+ } catch {
425
+ // Invalid YAML — proceed without frontmatter
426
+ }
415
427
  }
416
428
 
417
- const frontmatter = yaml.parse(fmMatch[1]);
418
- const title = frontmatter.title || path.basename(incrementFolder).replace(/^\d+-/, '');
429
+ const incrementBasename = path.basename(incrementFolder);
430
+ const title = frontmatter.title
431
+ || specContent.match(/^#\s+(.+)/m)?.[1]?.trim()
432
+ || incrementBasename.replace(/^\d+-/, '').replace(/-/g, ' ');
419
433
  const status = frontmatter.status || 'active';
420
434
  const priority = frontmatter.priority || 'P2';
421
435
  const created = frontmatter.created || new Date().toISOString().split('T')[0];
422
- const incrementId = frontmatter.increment || path.basename(incrementFolder);
436
+ const incrementId = frontmatter.increment || incrementBasename;
423
437
 
424
438
  // Determine target project folder from spec.md user stories or first available
425
439
  let targetProjectFolder = projectFolders[0]; // Default: first project folder
@@ -60,6 +60,11 @@ async function autoCloseCompletedUserStories(incrementId, affectedUSIds, specPat
60
60
  execOpts
61
61
  );
62
62
  if (closeResult.success) {
63
+ await execFileNoThrow(
64
+ "gh",
65
+ ["issue", "edit", issueNum, "--remove-label", "status:active", "--add-label", "status:completed", "-R", repoSlug],
66
+ execOpts
67
+ );
63
68
  result.closed.push({ usId, issueNumber: link.issueNumber });
64
69
  } else {
65
70
  result.errors.push({
@@ -130,6 +130,12 @@ export async function autoCloseCompletedUserStories(
130
130
  );
131
131
 
132
132
  if (closeResult.success) {
133
+ // Update labels: remove status:active, add status:completed
134
+ await execFileNoThrow(
135
+ 'gh',
136
+ ['issue', 'edit', issueNum, '--remove-label', 'status:active', '--add-label', 'status:completed', '-R', repoSlug],
137
+ execOpts,
138
+ );
133
139
  result.closed.push({ usId, issueNumber: link.issueNumber });
134
140
  } else {
135
141
  result.errors.push({