specweave 0.28.15 → 0.28.19

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 (146) hide show
  1. package/bin/specweave.js +1 -1
  2. package/dist/plugins/specweave-ado/lib/ado-board-resolver.d.ts +94 -0
  3. package/dist/plugins/specweave-ado/lib/ado-board-resolver.d.ts.map +1 -0
  4. package/dist/plugins/specweave-ado/lib/ado-board-resolver.js +219 -0
  5. package/dist/plugins/specweave-ado/lib/ado-board-resolver.js.map +1 -0
  6. package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts +6 -11
  7. package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
  8. package/dist/plugins/specweave-github/lib/github-feature-sync.js +6 -11
  9. package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
  10. package/dist/plugins/specweave-github/lib/github-increment-sync-cli.d.ts +19 -0
  11. package/dist/plugins/specweave-github/lib/github-increment-sync-cli.d.ts.map +1 -0
  12. package/dist/plugins/specweave-github/lib/github-increment-sync-cli.js +380 -0
  13. package/dist/plugins/specweave-github/lib/github-increment-sync-cli.js.map +1 -0
  14. package/dist/plugins/specweave-github/lib/increment-issue-builder.d.ts +92 -0
  15. package/dist/plugins/specweave-github/lib/increment-issue-builder.d.ts.map +1 -0
  16. package/dist/plugins/specweave-github/lib/increment-issue-builder.js +349 -0
  17. package/dist/plugins/specweave-github/lib/increment-issue-builder.js.map +1 -0
  18. package/dist/plugins/specweave-jira/lib/jira-board-resolver.d.ts +50 -0
  19. package/dist/plugins/specweave-jira/lib/jira-board-resolver.d.ts.map +1 -0
  20. package/dist/plugins/specweave-jira/lib/jira-board-resolver.js +84 -0
  21. package/dist/plugins/specweave-jira/lib/jira-board-resolver.js.map +1 -0
  22. package/dist/src/cli/commands/import-external.d.ts.map +1 -1
  23. package/dist/src/cli/commands/import-external.js +12 -7
  24. package/dist/src/cli/commands/import-external.js.map +1 -1
  25. package/dist/src/cli/helpers/init/external-import.d.ts.map +1 -1
  26. package/dist/src/cli/helpers/init/external-import.js +138 -23
  27. package/dist/src/cli/helpers/init/external-import.js.map +1 -1
  28. package/dist/src/cli/helpers/issue-tracker/ado-area-selection.d.ts +65 -0
  29. package/dist/src/cli/helpers/issue-tracker/ado-area-selection.d.ts.map +1 -0
  30. package/dist/src/cli/helpers/issue-tracker/ado-area-selection.js +278 -0
  31. package/dist/src/cli/helpers/issue-tracker/ado-area-selection.js.map +1 -0
  32. package/dist/src/cli/helpers/issue-tracker/jira-board-selection.d.ts +64 -0
  33. package/dist/src/cli/helpers/issue-tracker/jira-board-selection.d.ts.map +1 -0
  34. package/dist/src/cli/helpers/issue-tracker/jira-board-selection.js +251 -0
  35. package/dist/src/cli/helpers/issue-tracker/jira-board-selection.js.map +1 -0
  36. package/dist/src/core/ac-test-validator-cli.js +4 -1
  37. package/dist/src/core/ac-test-validator-cli.js.map +1 -1
  38. package/dist/src/core/ac-test-validator.d.ts.map +1 -1
  39. package/dist/src/core/ac-test-validator.js +4 -1
  40. package/dist/src/core/ac-test-validator.js.map +1 -1
  41. package/dist/src/core/qa/qa-runner.js +7 -10
  42. package/dist/src/core/qa/qa-runner.js.map +1 -1
  43. package/dist/src/core/types/increment-metadata.d.ts +75 -0
  44. package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
  45. package/dist/src/core/types/sync-profile.d.ts +137 -5
  46. package/dist/src/core/types/sync-profile.d.ts.map +1 -1
  47. package/dist/src/core/types/sync-profile.js +63 -0
  48. package/dist/src/core/types/sync-profile.js.map +1 -1
  49. package/dist/src/importers/external-importer.d.ts +25 -0
  50. package/dist/src/importers/external-importer.d.ts.map +1 -1
  51. package/dist/src/importers/github-importer.d.ts.map +1 -1
  52. package/dist/src/importers/github-importer.js +5 -3
  53. package/dist/src/importers/github-importer.js.map +1 -1
  54. package/dist/src/importers/item-converter.d.ts +51 -0
  55. package/dist/src/importers/item-converter.d.ts.map +1 -1
  56. package/dist/src/importers/item-converter.js +39 -12
  57. package/dist/src/importers/item-converter.js.map +1 -1
  58. package/dist/src/init/repo/types.d.ts +1 -1
  59. package/dist/src/living-docs/fs-id-allocator.d.ts +72 -3
  60. package/dist/src/living-docs/fs-id-allocator.d.ts.map +1 -1
  61. package/dist/src/living-docs/fs-id-allocator.js +142 -16
  62. package/dist/src/living-docs/fs-id-allocator.js.map +1 -1
  63. package/dist/src/locales/de/cli.json +14 -0
  64. package/dist/src/locales/es/cli.json +14 -0
  65. package/dist/src/locales/fr/cli.json +14 -0
  66. package/dist/src/locales/ja/cli.json +14 -0
  67. package/dist/src/locales/ko/cli.json +14 -0
  68. package/dist/src/locales/pt/cli.json +14 -0
  69. package/dist/src/locales/ru/cli.json +14 -0
  70. package/dist/src/locales/zh/cli.json +14 -0
  71. package/dist/src/utils/chalk-fallback.d.ts +38 -0
  72. package/dist/src/utils/chalk-fallback.d.ts.map +1 -0
  73. package/dist/src/utils/chalk-fallback.js +118 -0
  74. package/dist/src/utils/chalk-fallback.js.map +1 -0
  75. package/dist/src/utils/project-id-generator.d.ts +127 -0
  76. package/dist/src/utils/project-id-generator.d.ts.map +1 -0
  77. package/dist/src/utils/project-id-generator.js +228 -0
  78. package/dist/src/utils/project-id-generator.js.map +1 -0
  79. package/package.json +1 -1
  80. package/plugins/specweave/agents/AGENTS-INDEX.md +9 -7
  81. package/plugins/specweave/agents/pm/AGENT.md +202 -0
  82. package/plugins/specweave/commands/specweave-import-external.md +5 -3
  83. package/plugins/specweave/commands/specweave-qa.md +9 -9
  84. package/plugins/specweave/commands/specweave-save.md +531 -193
  85. package/plugins/specweave/commands/specweave-sync-docs.md +6 -2
  86. package/plugins/specweave/commands/specweave-validate.md +8 -7
  87. package/plugins/specweave/hooks/pre-task-completion.sh +35 -17
  88. package/plugins/specweave/lib/vendor/core/ac-test-validator-cli.d.ts +16 -0
  89. package/plugins/specweave/lib/vendor/core/ac-test-validator-cli.js +121 -0
  90. package/plugins/specweave/lib/vendor/core/ac-test-validator-cli.js.map +1 -0
  91. package/plugins/specweave/lib/vendor/core/ac-test-validator.d.ts +111 -0
  92. package/plugins/specweave/lib/vendor/core/ac-test-validator.js +295 -0
  93. package/plugins/specweave/lib/vendor/core/ac-test-validator.js.map +1 -0
  94. package/plugins/specweave/lib/vendor/core/types/increment-metadata.d.ts +75 -0
  95. package/plugins/specweave/lib/vendor/utils/chalk-fallback.d.ts +38 -0
  96. package/plugins/specweave/lib/vendor/utils/chalk-fallback.js +118 -0
  97. package/plugins/specweave/lib/vendor/utils/chalk-fallback.js.map +1 -0
  98. package/plugins/specweave/lib/vendor/utils/fs-native.d.ts +179 -0
  99. package/plugins/specweave/lib/vendor/utils/fs-native.js +319 -0
  100. package/plugins/specweave/lib/vendor/utils/fs-native.js.map +1 -0
  101. package/plugins/specweave/skills/code-reviewer/SKILL.md +1 -1
  102. package/plugins/specweave/skills/docs-updater/SKILL.md +61 -0
  103. package/plugins/specweave/skills/increment-planner/SKILL.md +10 -335
  104. package/plugins/specweave/skills/increment-planner/templates/metadata.json +13 -0
  105. package/plugins/specweave/skills/increment-planner/templates/plan.md +50 -0
  106. package/plugins/specweave/skills/increment-planner/templates/spec-multi-project.md +86 -0
  107. package/plugins/specweave/skills/increment-planner/templates/spec-single-project.md +50 -0
  108. package/plugins/specweave/skills/increment-planner/templates/tasks-multi-project.md +86 -0
  109. package/plugins/specweave/skills/increment-planner/templates/tasks-single-project.md +48 -0
  110. package/plugins/specweave/skills/increment-quality-judge-v2/SKILL.md +18 -0
  111. package/plugins/specweave-ado/commands/specweave-ado-import-areas.md +358 -0
  112. package/plugins/specweave-alternatives/skills/architecture-alternatives/SKILL.md +1 -0
  113. package/plugins/specweave-alternatives/skills/bmad-method/SKILL.md +1 -0
  114. package/plugins/specweave-core/skills/code-quality/SKILL.md +1 -0
  115. package/plugins/specweave-core/skills/design-patterns/SKILL.md +1 -0
  116. package/plugins/specweave-core/skills/software-architecture/SKILL.md +1 -0
  117. package/plugins/specweave-github/commands/specweave-github-cleanup-duplicates.md +14 -10
  118. package/plugins/specweave-github/commands/specweave-github-sync.md +57 -0
  119. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +86 -0
  120. package/plugins/specweave-github/lib/github-feature-sync.ts +6 -11
  121. package/plugins/specweave-github/lib/github-increment-sync-cli.js +343 -0
  122. package/plugins/specweave-github/lib/github-increment-sync-cli.ts +484 -0
  123. package/plugins/specweave-github/lib/increment-issue-builder.js +368 -0
  124. package/plugins/specweave-github/lib/increment-issue-builder.ts +471 -0
  125. package/plugins/specweave-github/skills/github-issue-standard/SKILL.md +19 -24
  126. package/plugins/specweave-infrastructure/agents/observability-engineer/AGENT.md +15 -23
  127. package/plugins/specweave-jira/commands/specweave-jira-import-boards.md +331 -0
  128. package/plugins/specweave-ml/agents/data-scientist/AGENT.md +16 -20
  129. package/plugins/specweave-ml/agents/ml-engineer/AGENT.md +18 -19
  130. package/plugins/specweave-ml/skills/{ml-pipeline-workflow → mlops-dag-builder}/SKILL.md +18 -14
  131. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +129 -0
  132. package/plugins/specweave-ui/skills/browser-automation/SKILL.md +1 -1
  133. package/plugins/specweave-ui/skills/ui-testing/SKILL.md +10 -122
  134. package/dist/plugins/specweave-github/lib/epic-content-builder.d.ts +0 -70
  135. package/dist/plugins/specweave-github/lib/epic-content-builder.d.ts.map +0 -1
  136. package/dist/plugins/specweave-github/lib/epic-content-builder.js +0 -258
  137. package/dist/plugins/specweave-github/lib/epic-content-builder.js.map +0 -1
  138. package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts +0 -83
  139. package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts.map +0 -1
  140. package/dist/plugins/specweave-github/lib/github-epic-sync.js +0 -466
  141. package/dist/plugins/specweave-github/lib/github-epic-sync.js.map +0 -1
  142. package/plugins/specweave/agents/increment-quality-judge-v2/AGENT.md +0 -705
  143. package/plugins/specweave-github/lib/epic-content-builder.js +0 -265
  144. package/plugins/specweave-github/lib/epic-content-builder.ts +0 -376
  145. package/plugins/specweave-github/lib/github-epic-sync.js +0 -488
  146. package/plugins/specweave-github/lib/github-epic-sync.ts +0 -715
@@ -1,18 +1,13 @@
1
1
  /**
2
2
  * GitHub Feature Sync - Universal Hierarchy Implementation
3
3
  *
4
- * Architecture (CORRECT):
5
- * - Feature (FS-033) → GitHub Milestone (Container)
6
- * - User Story (US-001, US-002, etc.) → GitHub Issue (Trackable item)
7
- * - Tasks (T-001, T-002, etc.) → Checkboxes in User Story issue body
4
+ * Architecture:
5
+ * - Feature (FS-XXX) → GitHub Milestone (Container)
6
+ * - User Story (US-XXX) → GitHub Issue with format [FS-XXX][US-YYY] Title
7
+ * - Tasks (T-XXX) → Checkboxes in User Story issue body
8
8
  *
9
- * This implements the TRUE Universal Hierarchy architecture for GitHub.
10
- *
11
- * Key Differences from old github-epic-sync.ts:
12
- * - ❌ OLD: Feature/Increment → GitHub Issue (WRONG!)
13
- * - ✅ NEW: User Story → GitHub Issue (CORRECT!)
14
- * - ✅ Creates ONE issue PER user story file (not one per increment)
15
- * - ✅ Reads us-*.md files from specs/{project}/FS-XXX/
9
+ * This implements the Universal Hierarchy architecture for GitHub sync.
10
+ * Creates ONE issue PER user story file from specs/{project}/FS-XXX/us-*.md
16
11
  */
17
12
 
18
13
  import { readdir, readFile, writeFile } from 'fs/promises';
@@ -0,0 +1,343 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync } from "fs";
3
+ import * as fs from "fs/promises";
4
+ import * as path from "path";
5
+ import { IncrementIssueBuilder } from "./increment-issue-builder.js";
6
+ import { execFileNoThrow } from "../../../src/utils/execFileNoThrow.js";
7
+ async function loadGitHubConfig() {
8
+ const projectRoot = process.cwd();
9
+ const configPath = path.join(projectRoot, ".specweave/config.json");
10
+ let owner = process.env.GITHUB_OWNER || "";
11
+ let repo = process.env.GITHUB_REPO || "";
12
+ const token = process.env.GITHUB_TOKEN || "";
13
+ if (existsSync(configPath)) {
14
+ try {
15
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
16
+ if (config.sync?.github?.owner && config.sync?.github?.repo) {
17
+ owner = config.sync.github.owner;
18
+ repo = config.sync.github.repo;
19
+ } else if (config.multiProject?.enabled && config.multiProject?.activeProject) {
20
+ const activeProject = config.multiProject.activeProject;
21
+ const projectConfig = config.multiProject.projects?.[activeProject];
22
+ if (projectConfig?.externalTools?.github?.repository) {
23
+ const parts = projectConfig.externalTools.github.repository.split("/");
24
+ if (parts.length === 2) {
25
+ owner = parts[0];
26
+ repo = parts[1];
27
+ }
28
+ }
29
+ } else if (config.sync?.activeProfile && config.sync?.profiles) {
30
+ const profile = config.sync.profiles[config.sync.activeProfile];
31
+ if (profile?.config?.owner && profile?.config?.repo) {
32
+ owner = profile.config.owner;
33
+ repo = profile.config.repo;
34
+ }
35
+ }
36
+ } catch (error) {
37
+ console.error("\u26A0\uFE0F Failed to parse config.json:", error);
38
+ }
39
+ }
40
+ if (!owner || !repo) {
41
+ const result = await execFileNoThrow("git", ["remote", "get-url", "origin"]);
42
+ if (result.exitCode === 0 && result.stdout) {
43
+ const remoteUrl = result.stdout.trim();
44
+ const match = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
45
+ if (match) {
46
+ owner = owner || match[1];
47
+ repo = repo || match[2];
48
+ }
49
+ }
50
+ }
51
+ if (!token) {
52
+ console.error("\u274C GITHUB_TOKEN not set");
53
+ console.error(" Set it in .env file or export GITHUB_TOKEN=ghp_xxx");
54
+ return null;
55
+ }
56
+ if (!owner || !repo) {
57
+ console.error("\u274C Could not detect GitHub owner/repo");
58
+ console.error(" Set sync.github.owner and sync.github.repo in .specweave/config.json");
59
+ return null;
60
+ }
61
+ return { owner, repo, token };
62
+ }
63
+ async function findIncrementFolder(incrementId) {
64
+ const projectRoot = process.cwd();
65
+ const incrementsDir = path.join(projectRoot, ".specweave/increments");
66
+ if (!existsSync(incrementsDir)) {
67
+ return null;
68
+ }
69
+ const entries = await fs.readdir(incrementsDir, { withFileTypes: true });
70
+ for (const entry of entries) {
71
+ if (!entry.isDirectory()) continue;
72
+ if (entry.name.startsWith("_")) continue;
73
+ if (entry.name === incrementId) {
74
+ return path.join(incrementsDir, entry.name);
75
+ }
76
+ if (entry.name.startsWith(incrementId + "-")) {
77
+ return path.join(incrementsDir, entry.name);
78
+ }
79
+ }
80
+ return null;
81
+ }
82
+ async function findExistingIssue(owner, repo, featureId) {
83
+ const result = await execFileNoThrow("gh", [
84
+ "search",
85
+ "issues",
86
+ `repo:${owner}/${repo}`,
87
+ `"[${featureId}]" in:title`,
88
+ "is:open",
89
+ "--json",
90
+ "number,title",
91
+ "--limit",
92
+ "5"
93
+ ]);
94
+ if (result.exitCode !== 0 || !result.stdout.trim()) {
95
+ return null;
96
+ }
97
+ try {
98
+ const issues = JSON.parse(result.stdout);
99
+ if (issues.length > 0) {
100
+ return issues[0].number;
101
+ }
102
+ } catch {
103
+ }
104
+ return null;
105
+ }
106
+ async function createGitHubIssue(owner, repo, title, body, labels) {
107
+ const args = [
108
+ "issue",
109
+ "create",
110
+ "--repo",
111
+ `${owner}/${repo}`,
112
+ "--title",
113
+ title,
114
+ "--body",
115
+ body
116
+ ];
117
+ if (labels.length > 0) {
118
+ args.push("--label", labels.join(","));
119
+ }
120
+ const result = await execFileNoThrow("gh", args);
121
+ if (result.exitCode !== 0) {
122
+ throw new Error(`Failed to create issue: ${result.stderr || result.stdout}`);
123
+ }
124
+ const urlMatch = result.stdout.match(/https:\/\/github\.com\/[^\s]+\/issues\/(\d+)/);
125
+ if (!urlMatch) {
126
+ throw new Error("Could not parse issue URL from gh output");
127
+ }
128
+ return {
129
+ number: parseInt(urlMatch[1], 10),
130
+ url: urlMatch[0]
131
+ };
132
+ }
133
+ async function updateGitHubIssue(owner, repo, issueNumber, body) {
134
+ const result = await execFileNoThrow("gh", [
135
+ "issue",
136
+ "edit",
137
+ String(issueNumber),
138
+ "--repo",
139
+ `${owner}/${repo}`,
140
+ "--body",
141
+ body
142
+ ]);
143
+ if (result.exitCode !== 0) {
144
+ throw new Error(`Failed to update issue: ${result.stderr || result.stdout}`);
145
+ }
146
+ }
147
+ function loadExistingGitHubLink(incrementPath) {
148
+ const metadataPath = path.join(incrementPath, "metadata.json");
149
+ if (!existsSync(metadataPath)) {
150
+ return null;
151
+ }
152
+ try {
153
+ const metadata = JSON.parse(readFileSync(metadataPath, "utf-8"));
154
+ if (metadata.github?.issue) {
155
+ return {
156
+ issue: metadata.github.issue,
157
+ url: metadata.github.url
158
+ };
159
+ }
160
+ if (metadata.sync?.issueNumber) {
161
+ return {
162
+ issue: metadata.sync.issueNumber,
163
+ url: metadata.sync.issueUrl
164
+ };
165
+ }
166
+ return null;
167
+ } catch {
168
+ return null;
169
+ }
170
+ }
171
+ async function updateIncrementMetadata(incrementPath, issueNumber, issueUrl) {
172
+ const metadataPath = path.join(incrementPath, "metadata.json");
173
+ let metadata = {};
174
+ if (existsSync(metadataPath)) {
175
+ try {
176
+ metadata = JSON.parse(readFileSync(metadataPath, "utf-8"));
177
+ } catch {
178
+ }
179
+ }
180
+ metadata.github = {
181
+ issue: issueNumber,
182
+ url: issueUrl,
183
+ lastSync: (/* @__PURE__ */ new Date()).toISOString()
184
+ };
185
+ await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2) + "\n");
186
+ }
187
+ async function main() {
188
+ const args = process.argv.slice(2);
189
+ if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
190
+ console.log("Usage: node github-increment-sync-cli.js <increment-id> [options]");
191
+ console.log("");
192
+ console.log("Arguments:");
193
+ console.log(" increment-id Increment ID (e.g., 0063 or 0063-fix-external-import)");
194
+ console.log("");
195
+ console.log("Options:");
196
+ console.log(" --force Force create even if issue exists");
197
+ console.log(" --dry-run Preview issue without creating");
198
+ console.log("");
199
+ console.log("Environment:");
200
+ console.log(" GITHUB_TOKEN Required - GitHub personal access token");
201
+ console.log("");
202
+ console.log("Example:");
203
+ console.log(" GITHUB_TOKEN=ghp_xxx node github-increment-sync-cli.js 0063");
204
+ process.exit(args.length === 0 ? 1 : 0);
205
+ }
206
+ const incrementId = args[0];
207
+ const force = args.includes("--force");
208
+ const dryRun = args.includes("--dry-run");
209
+ console.log(`
210
+ \u{1F419} GitHub Increment Sync CLI`);
211
+ console.log(` Increment: ${incrementId}`);
212
+ const incrementPath = await findIncrementFolder(incrementId);
213
+ if (!incrementPath) {
214
+ console.error(`\u274C Increment not found: ${incrementId}`);
215
+ console.error(" Check: ls .specweave/increments/");
216
+ process.exit(1);
217
+ }
218
+ const fullIncrementId = path.basename(incrementPath);
219
+ console.log(` Found: ${fullIncrementId}`);
220
+ let config = null;
221
+ if (!dryRun) {
222
+ config = await loadGitHubConfig();
223
+ if (!config) {
224
+ process.exit(1);
225
+ }
226
+ console.log(` Repository: ${config.owner}/${config.repo}`);
227
+ } else {
228
+ config = await loadGitHubConfig().catch(() => null);
229
+ if (config) {
230
+ console.log(` Repository: ${config.owner}/${config.repo}`);
231
+ } else {
232
+ console.log(` Repository: (not detected - dry run mode)`);
233
+ }
234
+ }
235
+ const projectRoot = process.cwd();
236
+ const builder = new IncrementIssueBuilder(incrementPath, projectRoot);
237
+ try {
238
+ console.log(`
239
+ \u{1F504} Parsing increment spec.md...`);
240
+ const incrementData = await builder.parse();
241
+ console.log(` \u{1F4E6} Title: ${incrementData.title}`);
242
+ console.log(` \u{1F4DD} User Stories: ${incrementData.userStories.length}`);
243
+ const totalACs = incrementData.userStories.reduce(
244
+ (sum, us) => sum + us.acceptanceCriteria.length,
245
+ 0
246
+ );
247
+ console.log(` \u2713 Acceptance Criteria: ${totalACs}`);
248
+ const githubRepo = config ? `${config.owner}/${config.repo}` : void 0;
249
+ const issue = builder.buildIncrementIssue(incrementData, githubRepo);
250
+ console.log(`
251
+ \u{1F4CB} Issue Preview:`);
252
+ console.log(` Title: ${issue.title}`);
253
+ console.log(` Labels: ${issue.labels.join(", ")}`);
254
+ if (dryRun) {
255
+ console.log(`
256
+ \u{1F4C4} Issue Body Preview:
257
+ `);
258
+ console.log(issue.body);
259
+ console.log(`
260
+ \u2705 Dry run complete (no issue created)`);
261
+ process.exit(0);
262
+ }
263
+ if (!config) {
264
+ console.error("\u274C GitHub config not available");
265
+ process.exit(1);
266
+ }
267
+ const metadataLink = loadExistingGitHubLink(incrementPath);
268
+ if (metadataLink) {
269
+ console.log(`
270
+ \u{1F4CE} Found existing issue link in metadata: #${metadataLink.issue}`);
271
+ console.log(`\u{1F504} Updating issue #${metadataLink.issue} with new format...`);
272
+ await updateGitHubIssue(config.owner, config.repo, metadataLink.issue, issue.body);
273
+ console.log(` \u2705 Body updated with User Stories and ACs`);
274
+ const updateTitleResult = await execFileNoThrow("gh", [
275
+ "issue",
276
+ "edit",
277
+ String(metadataLink.issue),
278
+ "--repo",
279
+ `${config.owner}/${config.repo}`,
280
+ "--title",
281
+ issue.title
282
+ ]);
283
+ if (updateTitleResult.exitCode === 0) {
284
+ console.log(` \u2705 Title updated to: ${issue.title}`);
285
+ } else {
286
+ console.log(` \u26A0\uFE0F Could not update title (may need permissions)`);
287
+ }
288
+ await updateIncrementMetadata(
289
+ incrementPath,
290
+ metadataLink.issue,
291
+ `https://github.com/${config.owner}/${config.repo}/issues/${metadataLink.issue}`
292
+ );
293
+ console.log(`
294
+ \u2705 Sync complete!`);
295
+ console.log(` \u{1F517} https://github.com/${config.owner}/${config.repo}/issues/${metadataLink.issue}`);
296
+ process.exit(0);
297
+ }
298
+ const featureId = incrementData.frontmatter.feature_id || `FS-${fullIncrementId.match(/^(\d+)/)?.[1]?.padStart(3, "0") || "UNKNOWN"}`;
299
+ console.log(`
300
+ \u{1F50D} Searching GitHub for existing issue [${featureId}]...`);
301
+ const existingIssue = await findExistingIssue(config.owner, config.repo, featureId);
302
+ if (existingIssue) {
303
+ console.log(` Found existing issue: #${existingIssue}`);
304
+ console.log(`\u{1F504} Updating issue #${existingIssue}...`);
305
+ await updateGitHubIssue(config.owner, config.repo, existingIssue, issue.body);
306
+ console.log(` \u2705 Issue #${existingIssue} updated`);
307
+ await updateIncrementMetadata(
308
+ incrementPath,
309
+ existingIssue,
310
+ `https://github.com/${config.owner}/${config.repo}/issues/${existingIssue}`
311
+ );
312
+ console.log(`
313
+ \u2705 Sync complete!`);
314
+ console.log(` \u{1F517} https://github.com/${config.owner}/${config.repo}/issues/${existingIssue}`);
315
+ process.exit(0);
316
+ }
317
+ console.log(` No existing issue found`);
318
+ console.log(`
319
+ \u{1F680} Creating GitHub issue...`);
320
+ const created = await createGitHubIssue(
321
+ config.owner,
322
+ config.repo,
323
+ issue.title,
324
+ issue.body,
325
+ issue.labels
326
+ );
327
+ console.log(` \u2705 Issue #${created.number} created`);
328
+ await updateIncrementMetadata(incrementPath, created.number, created.url);
329
+ console.log(` \u{1F4DD} Metadata updated`);
330
+ console.log(`
331
+ \u2705 Sync complete!`);
332
+ console.log(` \u{1F517} ${created.url}`);
333
+ process.exit(0);
334
+ } catch (error) {
335
+ console.error(`
336
+ \u274C Sync failed:`, error);
337
+ process.exit(1);
338
+ }
339
+ }
340
+ main().catch((error) => {
341
+ console.error("Fatal error:", error);
342
+ process.exit(1);
343
+ });