specweave 1.0.235 → 1.0.239

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 (196) hide show
  1. package/README.md +89 -193
  2. package/dist/plugins/specweave-github/lib/github-ac-comment-poster.d.ts +37 -0
  3. package/dist/plugins/specweave-github/lib/github-ac-comment-poster.d.ts.map +1 -0
  4. package/dist/plugins/specweave-github/lib/github-ac-comment-poster.js +176 -0
  5. package/dist/plugins/specweave-github/lib/github-ac-comment-poster.js.map +1 -0
  6. package/dist/plugins/specweave-github/lib/github-batch-sync.d.ts +36 -0
  7. package/dist/plugins/specweave-github/lib/github-batch-sync.d.ts.map +1 -0
  8. package/dist/plugins/specweave-github/lib/github-batch-sync.js +115 -0
  9. package/dist/plugins/specweave-github/lib/github-batch-sync.js.map +1 -0
  10. package/dist/plugins/specweave-github/lib/github-board-resolver-v2.d.ts +37 -0
  11. package/dist/plugins/specweave-github/lib/github-board-resolver-v2.d.ts.map +1 -0
  12. package/dist/plugins/specweave-github/lib/github-board-resolver-v2.js +56 -0
  13. package/dist/plugins/specweave-github/lib/github-board-resolver-v2.js.map +1 -0
  14. package/dist/plugins/specweave-github/lib/github-conflict-resolver.d.ts +68 -0
  15. package/dist/plugins/specweave-github/lib/github-conflict-resolver.d.ts.map +1 -0
  16. package/dist/plugins/specweave-github/lib/github-conflict-resolver.js +102 -0
  17. package/dist/plugins/specweave-github/lib/github-conflict-resolver.js.map +1 -0
  18. package/dist/plugins/specweave-github/lib/github-cross-repo-sync.d.ts +64 -0
  19. package/dist/plugins/specweave-github/lib/github-cross-repo-sync.d.ts.map +1 -0
  20. package/dist/plugins/specweave-github/lib/github-cross-repo-sync.js +162 -0
  21. package/dist/plugins/specweave-github/lib/github-cross-repo-sync.js.map +1 -0
  22. package/dist/plugins/specweave-github/lib/github-field-sync.d.ts +50 -0
  23. package/dist/plugins/specweave-github/lib/github-field-sync.d.ts.map +1 -0
  24. package/dist/plugins/specweave-github/lib/github-field-sync.js +107 -0
  25. package/dist/plugins/specweave-github/lib/github-field-sync.js.map +1 -0
  26. package/dist/plugins/specweave-github/lib/github-graphql-client.d.ts +53 -0
  27. package/dist/plugins/specweave-github/lib/github-graphql-client.d.ts.map +1 -0
  28. package/dist/plugins/specweave-github/lib/github-graphql-client.js +138 -0
  29. package/dist/plugins/specweave-github/lib/github-graphql-client.js.map +1 -0
  30. package/dist/plugins/specweave-github/lib/github-issue-body-generator.d.ts +40 -0
  31. package/dist/plugins/specweave-github/lib/github-issue-body-generator.d.ts.map +1 -0
  32. package/dist/plugins/specweave-github/lib/github-issue-body-generator.js +50 -0
  33. package/dist/plugins/specweave-github/lib/github-issue-body-generator.js.map +1 -0
  34. package/dist/plugins/specweave-github/lib/github-issue-body-parser.d.ts +30 -0
  35. package/dist/plugins/specweave-github/lib/github-issue-body-parser.d.ts.map +1 -0
  36. package/dist/plugins/specweave-github/lib/github-issue-body-parser.js +75 -0
  37. package/dist/plugins/specweave-github/lib/github-issue-body-parser.js.map +1 -0
  38. package/dist/plugins/specweave-github/lib/github-pull-sync.d.ts +94 -0
  39. package/dist/plugins/specweave-github/lib/github-pull-sync.d.ts.map +1 -0
  40. package/dist/plugins/specweave-github/lib/github-pull-sync.js +232 -0
  41. package/dist/plugins/specweave-github/lib/github-pull-sync.js.map +1 -0
  42. package/dist/plugins/specweave-github/lib/github-push-sync.d.ts +50 -0
  43. package/dist/plugins/specweave-github/lib/github-push-sync.d.ts.map +1 -0
  44. package/dist/plugins/specweave-github/lib/github-push-sync.js +114 -0
  45. package/dist/plugins/specweave-github/lib/github-push-sync.js.map +1 -0
  46. package/dist/plugins/specweave-github/lib/github-rate-limiter.d.ts +53 -0
  47. package/dist/plugins/specweave-github/lib/github-rate-limiter.d.ts.map +1 -0
  48. package/dist/plugins/specweave-github/lib/github-rate-limiter.js +109 -0
  49. package/dist/plugins/specweave-github/lib/github-rate-limiter.js.map +1 -0
  50. package/dist/plugins/specweave-github/lib/github-spec-frontmatter-updater.d.ts +21 -0
  51. package/dist/plugins/specweave-github/lib/github-spec-frontmatter-updater.d.ts.map +1 -0
  52. package/dist/plugins/specweave-github/lib/github-spec-frontmatter-updater.js +161 -0
  53. package/dist/plugins/specweave-github/lib/github-spec-frontmatter-updater.js.map +1 -0
  54. package/dist/plugins/specweave-github/lib/github-sync-orchestrator.d.ts +46 -0
  55. package/dist/plugins/specweave-github/lib/github-sync-orchestrator.d.ts.map +1 -0
  56. package/dist/plugins/specweave-github/lib/github-sync-orchestrator.js +99 -0
  57. package/dist/plugins/specweave-github/lib/github-sync-orchestrator.js.map +1 -0
  58. package/dist/plugins/specweave-github/lib/github-us-auto-closer.d.ts +43 -0
  59. package/dist/plugins/specweave-github/lib/github-us-auto-closer.d.ts.map +1 -0
  60. package/dist/plugins/specweave-github/lib/github-us-auto-closer.js +153 -0
  61. package/dist/plugins/specweave-github/lib/github-us-auto-closer.js.map +1 -0
  62. package/dist/plugins/specweave-github/lib/index.d.ts +1 -4
  63. package/dist/plugins/specweave-github/lib/index.d.ts.map +1 -1
  64. package/dist/plugins/specweave-github/lib/index.js +1 -4
  65. package/dist/plugins/specweave-github/lib/index.js.map +1 -1
  66. package/dist/plugins/specweave-testing/lib/playwright-ci-defaults.d.ts +7 -0
  67. package/dist/plugins/specweave-testing/lib/playwright-ci-defaults.d.ts.map +1 -0
  68. package/dist/plugins/specweave-testing/lib/playwright-ci-defaults.js +15 -0
  69. package/dist/plugins/specweave-testing/lib/playwright-ci-defaults.js.map +1 -0
  70. package/dist/plugins/specweave-testing/lib/playwright-cli-detector.d.ts +10 -0
  71. package/dist/plugins/specweave-testing/lib/playwright-cli-detector.d.ts.map +1 -0
  72. package/dist/plugins/specweave-testing/lib/playwright-cli-detector.js +36 -0
  73. package/dist/plugins/specweave-testing/lib/playwright-cli-detector.js.map +1 -0
  74. package/dist/plugins/specweave-testing/lib/playwright-cli-runner.d.ts +25 -0
  75. package/dist/plugins/specweave-testing/lib/playwright-cli-runner.d.ts.map +1 -0
  76. package/dist/plugins/specweave-testing/lib/playwright-cli-runner.js +57 -0
  77. package/dist/plugins/specweave-testing/lib/playwright-cli-runner.js.map +1 -0
  78. package/dist/plugins/specweave-testing/lib/playwright-routing.d.ts +7 -0
  79. package/dist/plugins/specweave-testing/lib/playwright-routing.d.ts.map +1 -0
  80. package/dist/plugins/specweave-testing/lib/playwright-routing.js +17 -0
  81. package/dist/plugins/specweave-testing/lib/playwright-routing.js.map +1 -0
  82. package/dist/src/cli/commands/auto.d.ts.map +1 -1
  83. package/dist/src/cli/commands/auto.js +1 -2
  84. package/dist/src/cli/commands/auto.js.map +1 -1
  85. package/dist/src/cli/commands/cancel-auto.js +1 -2
  86. package/dist/src/cli/commands/cancel-auto.js.map +1 -1
  87. package/dist/src/cli/commands/living-docs.js +2 -2
  88. package/dist/src/cli/commands/living-docs.js.map +1 -1
  89. package/dist/src/cli/commands/update.d.ts.map +1 -1
  90. package/dist/src/cli/commands/update.js +1 -2
  91. package/dist/src/cli/commands/update.js.map +1 -1
  92. package/dist/src/core/config/types.d.ts +8 -0
  93. package/dist/src/core/config/types.d.ts.map +1 -1
  94. package/dist/src/core/config/types.js +3 -0
  95. package/dist/src/core/config/types.js.map +1 -1
  96. package/dist/src/core/types/sync-profile.d.ts +72 -0
  97. package/dist/src/core/types/sync-profile.d.ts.map +1 -1
  98. package/dist/src/core/types/sync-profile.js +6 -0
  99. package/dist/src/core/types/sync-profile.js.map +1 -1
  100. package/package.json +2 -2
  101. package/plugins/specweave/hooks/hooks.json +2 -2
  102. package/plugins/specweave/hooks/startup-health-check.sh +1 -1
  103. package/plugins/specweave/hooks/stop-auto-v5.sh +166 -0
  104. package/plugins/specweave/hooks/user-prompt-submit.sh +10 -0
  105. package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +21 -1
  106. package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +1 -1
  107. package/plugins/specweave/skills/auto/SKILL.md +71 -251
  108. package/plugins/specweave/skills/team-build/SKILL.md +370 -0
  109. package/plugins/specweave/skills/team-merge/SKILL.md +123 -0
  110. package/plugins/specweave/skills/team-orchestrate/SKILL.md +800 -0
  111. package/plugins/specweave/skills/team-status/SKILL.md +89 -0
  112. package/plugins/specweave-github/MULTI-PROJECT-SYNC-ARCHITECTURE.md +94 -8
  113. package/plugins/specweave-github/commands/sync.md +17 -3
  114. package/plugins/specweave-github/hooks/github-ac-sync-handler.sh +255 -0
  115. package/plugins/specweave-github/hooks/github-auto-create-handler.sh +455 -0
  116. package/plugins/specweave-github/lib/github-ac-comment-poster.js +150 -0
  117. package/plugins/specweave-github/lib/github-ac-comment-poster.ts +245 -0
  118. package/plugins/specweave-github/lib/github-batch-sync.js +93 -0
  119. package/plugins/specweave-github/lib/github-batch-sync.ts +152 -0
  120. package/plugins/specweave-github/lib/github-board-resolver-v2.js +47 -0
  121. package/plugins/specweave-github/lib/github-board-resolver-v2.ts +73 -0
  122. package/plugins/specweave-github/lib/github-conflict-resolver.js +90 -0
  123. package/plugins/specweave-github/lib/github-conflict-resolver.ts +154 -0
  124. package/plugins/specweave-github/lib/github-cross-repo-sync.js +168 -0
  125. package/plugins/specweave-github/lib/github-cross-repo-sync.ts +252 -0
  126. package/plugins/specweave-github/lib/github-field-sync.js +116 -0
  127. package/plugins/specweave-github/lib/github-field-sync.ts +165 -0
  128. package/plugins/specweave-github/lib/github-graphql-client.js +129 -0
  129. package/plugins/specweave-github/lib/github-graphql-client.ts +181 -0
  130. package/plugins/specweave-github/lib/github-issue-body-generator.js +30 -0
  131. package/plugins/specweave-github/lib/github-issue-body-generator.ts +76 -0
  132. package/plugins/specweave-github/lib/github-issue-body-parser.js +55 -0
  133. package/plugins/specweave-github/lib/github-issue-body-parser.ts +92 -0
  134. package/plugins/specweave-github/lib/github-pull-sync.js +185 -0
  135. package/plugins/specweave-github/lib/github-pull-sync.ts +343 -0
  136. package/plugins/specweave-github/lib/github-push-sync.js +119 -0
  137. package/plugins/specweave-github/lib/github-push-sync.ts +174 -0
  138. package/plugins/specweave-github/lib/github-rate-limiter.js +96 -0
  139. package/plugins/specweave-github/lib/github-rate-limiter.ts +143 -0
  140. package/plugins/specweave-github/lib/github-spec-frontmatter-updater.js +117 -0
  141. package/plugins/specweave-github/lib/github-spec-frontmatter-updater.ts +180 -0
  142. package/plugins/specweave-github/lib/github-sync-orchestrator.js +84 -0
  143. package/plugins/specweave-github/lib/github-sync-orchestrator.ts +156 -0
  144. package/plugins/specweave-github/lib/github-us-auto-closer.js +134 -0
  145. package/plugins/specweave-github/lib/github-us-auto-closer.ts +226 -0
  146. package/plugins/specweave-github/lib/index.js +1 -7
  147. package/plugins/specweave-github/lib/index.ts +1 -4
  148. package/plugins/specweave-github/skills/github-sync/SKILL.md +76 -4
  149. package/plugins/specweave-testing/commands/e2e-setup.md +18 -0
  150. package/plugins/specweave-testing/commands/ui-automate.md +2 -0
  151. package/plugins/specweave-testing/commands/ui-inspect.md +8 -0
  152. package/plugins/specweave-testing/lib/playwright-ci-defaults.d.ts +6 -0
  153. package/plugins/specweave-testing/lib/playwright-ci-defaults.js +14 -0
  154. package/plugins/specweave-testing/lib/playwright-ci-defaults.ts +24 -0
  155. package/plugins/specweave-testing/lib/playwright-cli-detector.js +33 -0
  156. package/plugins/specweave-testing/lib/playwright-cli-detector.ts +48 -0
  157. package/plugins/specweave-testing/lib/playwright-cli-runner.js +58 -0
  158. package/plugins/specweave-testing/lib/playwright-cli-runner.ts +80 -0
  159. package/plugins/specweave-testing/lib/playwright-routing.js +16 -0
  160. package/plugins/specweave-testing/lib/playwright-routing.ts +38 -0
  161. package/plugins/specweave-testing/skills/e2e-testing/SKILL.md +38 -0
  162. package/src/templates/CLAUDE.md.template +7 -0
  163. package/src/templates/config.json.template +9 -1
  164. package/dist/plugins/specweave-github/lib/subtask-sync.d.ts +0 -51
  165. package/dist/plugins/specweave-github/lib/subtask-sync.d.ts.map +0 -1
  166. package/dist/plugins/specweave-github/lib/subtask-sync.js +0 -147
  167. package/dist/plugins/specweave-github/lib/subtask-sync.js.map +0 -1
  168. package/dist/plugins/specweave-github/lib/task-parser.d.ts +0 -37
  169. package/dist/plugins/specweave-github/lib/task-parser.d.ts.map +0 -1
  170. package/dist/plugins/specweave-github/lib/task-parser.js +0 -211
  171. package/dist/plugins/specweave-github/lib/task-parser.js.map +0 -1
  172. package/dist/plugins/specweave-github/lib/task-sync.d.ts +0 -56
  173. package/dist/plugins/specweave-github/lib/task-sync.d.ts.map +0 -1
  174. package/dist/plugins/specweave-github/lib/task-sync.js +0 -375
  175. package/dist/plugins/specweave-github/lib/task-sync.js.map +0 -1
  176. package/plugins/specweave/hooks/validate-completion-conditions.sh +0 -474
  177. package/plugins/specweave-github/lib/subtask-sync.d.ts +0 -51
  178. package/plugins/specweave-github/lib/subtask-sync.d.ts.map +0 -1
  179. package/plugins/specweave-github/lib/subtask-sync.js +0 -154
  180. package/plugins/specweave-github/lib/subtask-sync.js.map +0 -1
  181. package/plugins/specweave-github/lib/subtask-sync.ts +0 -225
  182. package/plugins/specweave-github/lib/task-parser.d.js +0 -0
  183. package/plugins/specweave-github/lib/task-parser.d.ts +0 -37
  184. package/plugins/specweave-github/lib/task-parser.d.ts.map +0 -1
  185. package/plugins/specweave-github/lib/task-parser.js +0 -195
  186. package/plugins/specweave-github/lib/task-parser.js.map +0 -1
  187. package/plugins/specweave-github/lib/task-parser.ts +0 -246
  188. package/plugins/specweave-github/lib/task-sync.d.js +0 -0
  189. package/plugins/specweave-github/lib/task-sync.d.ts +0 -51
  190. package/plugins/specweave-github/lib/task-sync.d.ts.map +0 -1
  191. package/plugins/specweave-github/lib/task-sync.js +0 -415
  192. package/plugins/specweave-github/lib/task-sync.js.map +0 -1
  193. package/plugins/specweave-github/lib/task-sync.ts +0 -451
  194. package/plugins/specweave-github/skills/github-issue-tracker/SKILL.md +0 -496
  195. /package/plugins/specweave/hooks/{stop-auto.sh → _archive/stop-auto-v4-legacy.sh} +0 -0
  196. /package/plugins/{specweave-github/lib/subtask-sync.d.js → specweave-testing/lib/playwright-ci-defaults.d.js} +0 -0
@@ -0,0 +1,180 @@
1
+ /**
2
+ * GitHub Spec Frontmatter Updater
3
+ *
4
+ * Updates spec.md YAML frontmatter after push sync to record
5
+ * GitHub issue links for each user story.
6
+ *
7
+ * @module github-spec-frontmatter-updater
8
+ */
9
+
10
+ import { readFile, writeFile } from 'fs/promises';
11
+ import type { PushSyncResult } from './github-push-sync.js';
12
+ import type { GitHubSyncMetadata, GitHubUserStoryLink } from '../../../src/core/types/sync-profile.js';
13
+
14
+ /**
15
+ * Update spec.md frontmatter with GitHub sync results.
16
+ *
17
+ * Reads the spec file, parses YAML frontmatter, merges sync results
18
+ * into the externalLinks.github section, and writes back.
19
+ */
20
+ export async function updateSpecFrontmatter(
21
+ specPath: string,
22
+ syncResult: PushSyncResult,
23
+ options?: { projectV2Id?: string; projectV2Number?: number },
24
+ ): Promise<GitHubSyncMetadata> {
25
+ const content = await readFile(specPath, 'utf-8');
26
+
27
+ // Parse frontmatter
28
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
29
+ let frontmatter: Record<string, unknown> = {};
30
+ let body = content;
31
+
32
+ if (fmMatch) {
33
+ frontmatter = parseYamlSimple(fmMatch[1]);
34
+ body = content.slice(fmMatch[0].length);
35
+ }
36
+
37
+ // Get existing externalLinks
38
+ const externalLinks = (frontmatter.externalLinks ?? {}) as Record<string, unknown>;
39
+ const existingGithub = (externalLinks.github ?? {}) as Record<string, unknown>;
40
+ const existingUserStories = (existingGithub.userStories ?? {}) as Record<string, GitHubUserStoryLink>;
41
+
42
+ // Build updated user stories map (preserve existing)
43
+ const userStories: Record<string, GitHubUserStoryLink> = { ...existingUserStories };
44
+ const now = new Date().toISOString();
45
+
46
+ // Merge created issues
47
+ for (const item of syncResult.created) {
48
+ userStories[item.userStoryId] = {
49
+ issueNumber: item.issueNumber,
50
+ issueUrl: item.issueUrl,
51
+ issueNodeId: item.issueNodeId,
52
+ syncedAt: now,
53
+ };
54
+ }
55
+
56
+ // Merge updated issues (preserve existing nodeId)
57
+ for (const item of syncResult.updated) {
58
+ const existing = userStories[item.userStoryId];
59
+ userStories[item.userStoryId] = {
60
+ issueNumber: item.issueNumber,
61
+ issueUrl: item.issueUrl,
62
+ issueNodeId: existing?.issueNodeId,
63
+ syncedAt: now,
64
+ };
65
+ }
66
+
67
+ // Determine sync status
68
+ const syncStatus: GitHubSyncMetadata['syncStatus'] =
69
+ syncResult.errors.length > 0 ? 'dirty' : 'synced';
70
+
71
+ // Build metadata result
72
+ const metadata: GitHubSyncMetadata = {
73
+ syncStatus,
74
+ userStories,
75
+ };
76
+
77
+ // ProjectV2 info: prefer options, fall back to existing
78
+ if (options?.projectV2Id) {
79
+ metadata.projectV2Id = options.projectV2Id;
80
+ } else if (existingGithub.projectV2Id) {
81
+ metadata.projectV2Id = existingGithub.projectV2Id as string;
82
+ }
83
+
84
+ if (options?.projectV2Number) {
85
+ metadata.projectV2Number = options.projectV2Number;
86
+ } else if (existingGithub.projectV2Number) {
87
+ metadata.projectV2Number = existingGithub.projectV2Number as number;
88
+ }
89
+
90
+ // Update frontmatter
91
+ externalLinks.github = metadata;
92
+ frontmatter.externalLinks = externalLinks;
93
+
94
+ // Write back
95
+ const newFrontmatter = stringifyYaml(frontmatter);
96
+ const newContent = `---\n${newFrontmatter}\n---${body}`;
97
+ await writeFile(specPath, newContent, 'utf-8');
98
+
99
+ return metadata;
100
+ }
101
+
102
+ /**
103
+ * Simple YAML parser for spec frontmatter.
104
+ * Handles nested objects, strings, numbers, booleans, null.
105
+ */
106
+ function parseYamlSimple(yaml: string): Record<string, unknown> {
107
+ const result: Record<string, unknown> = {};
108
+ const lines = yaml.split('\n');
109
+ const stack: Array<{ obj: Record<string, unknown>; indent: number }> = [
110
+ { obj: result, indent: -1 },
111
+ ];
112
+
113
+ for (const line of lines) {
114
+ if (!line.trim() || line.trim().startsWith('#')) continue;
115
+
116
+ const indent = line.search(/\S/);
117
+ const trimmed = line.trim();
118
+ const colonIdx = trimmed.indexOf(':');
119
+ if (colonIdx === -1) continue;
120
+
121
+ const key = trimmed.slice(0, colonIdx).trim();
122
+ const rawValue = trimmed.slice(colonIdx + 1).trim();
123
+
124
+ // Pop stack to correct indent level
125
+ while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
126
+ stack.pop();
127
+ }
128
+
129
+ const parent = stack[stack.length - 1].obj;
130
+
131
+ if (rawValue === '' || rawValue === undefined) {
132
+ // Nested object
133
+ const child: Record<string, unknown> = {};
134
+ parent[key] = child;
135
+ stack.push({ obj: child, indent });
136
+ } else {
137
+ parent[key] = parseYamlValue(rawValue);
138
+ }
139
+ }
140
+
141
+ return result;
142
+ }
143
+
144
+ function parseYamlValue(raw: string): unknown {
145
+ if (raw === 'null') return null;
146
+ if (raw === 'true') return true;
147
+ if (raw === 'false') return false;
148
+ if (/^-?\d+$/.test(raw)) return parseInt(raw, 10);
149
+ if (/^-?\d+\.\d+$/.test(raw)) return parseFloat(raw);
150
+ if ((raw.startsWith('"') && raw.endsWith('"')) ||
151
+ (raw.startsWith("'") && raw.endsWith("'"))) {
152
+ return raw.slice(1, -1);
153
+ }
154
+ return raw;
155
+ }
156
+
157
+ /**
158
+ * Simple YAML stringifier.
159
+ */
160
+ function stringifyYaml(obj: Record<string, unknown>, indent = 0): string {
161
+ const prefix = ' '.repeat(indent);
162
+ const parts: string[] = [];
163
+
164
+ for (const [key, value] of Object.entries(obj)) {
165
+ if (value === null || value === undefined) {
166
+ parts.push(`${prefix}${key}: null`);
167
+ } else if (typeof value === 'object' && !Array.isArray(value)) {
168
+ parts.push(`${prefix}${key}:`);
169
+ parts.push(stringifyYaml(value as Record<string, unknown>, indent + 1));
170
+ } else if (typeof value === 'string') {
171
+ parts.push(`${prefix}${key}: "${value}"`);
172
+ } else if (typeof value === 'boolean' || typeof value === 'number') {
173
+ parts.push(`${prefix}${key}: ${value}`);
174
+ } else {
175
+ parts.push(`${prefix}${key}: ${JSON.stringify(value)}`);
176
+ }
177
+ }
178
+
179
+ return parts.join('\n');
180
+ }
@@ -0,0 +1,84 @@
1
+ import { pushSyncUserStories } from "./github-push-sync.js";
2
+ import { GitHubBoardResolverV2 } from "./github-board-resolver-v2.js";
3
+ import { GitHubFieldSync } from "./github-field-sync.js";
4
+ import { updateSpecFrontmatter } from "./github-spec-frontmatter-updater.js";
5
+ import { GitHubGraphQLClient } from "./github-graphql-client.js";
6
+ class GitHubSyncOrchestrator {
7
+ constructor(config) {
8
+ this.config = config;
9
+ }
10
+ /**
11
+ * Run the full sync flow for a spec.
12
+ */
13
+ async syncSpec(specPath, userStories) {
14
+ const pushResult = await pushSyncUserStories(userStories, {
15
+ owner: this.config.owner,
16
+ repo: this.config.repo,
17
+ token: this.config.token,
18
+ dryRun: this.config.dryRun
19
+ });
20
+ let projectV2Result;
21
+ if (this.config.projectV2Enabled && !this.config.dryRun) {
22
+ projectV2Result = await this.syncProjectV2(pushResult, userStories);
23
+ }
24
+ const frontmatterOptions = {};
25
+ if (projectV2Result) {
26
+ frontmatterOptions.projectV2Id = projectV2Result.projectId;
27
+ frontmatterOptions.projectV2Number = projectV2Result.projectNumber;
28
+ }
29
+ const frontmatterResult = await updateSpecFrontmatter(
30
+ specPath,
31
+ pushResult,
32
+ frontmatterOptions
33
+ );
34
+ return {
35
+ pushResult,
36
+ projectV2Result,
37
+ frontmatterResult
38
+ };
39
+ }
40
+ async syncProjectV2(pushResult, userStories) {
41
+ const graphqlClient = new GitHubGraphQLClient(this.config.token);
42
+ const boardResolver = new GitHubBoardResolverV2(graphqlClient, {
43
+ owner: this.config.owner,
44
+ projectV2Number: this.config.projectV2Number,
45
+ projectV2Id: this.config.projectV2Id
46
+ });
47
+ const project = await boardResolver.findOrCreateProject("SpecWeave Sync Board");
48
+ const nodeIds = pushResult.created.filter((item) => item.issueNodeId).map((item) => item.issueNodeId);
49
+ const itemIds = await boardResolver.addIssuesToProject(project.id, nodeIds);
50
+ const nodeIdToStory = /* @__PURE__ */ new Map();
51
+ for (const created of pushResult.created) {
52
+ if (created.issueNodeId) {
53
+ const story = userStories.find((s) => s.id === created.userStoryId);
54
+ if (story) {
55
+ nodeIdToStory.set(created.issueNodeId, story);
56
+ }
57
+ }
58
+ }
59
+ const fieldSync = new GitHubFieldSync(graphqlClient, {
60
+ projectId: project.id,
61
+ statusFieldMapping: this.config.statusFieldMapping,
62
+ priorityFieldMapping: this.config.priorityFieldMapping
63
+ });
64
+ const fieldSyncItems = itemIds.map((itemId, idx) => {
65
+ const nodeId = nodeIds[idx];
66
+ const story = nodeId ? nodeIdToStory.get(nodeId) : void 0;
67
+ return {
68
+ itemId,
69
+ status: story?.status,
70
+ priority: story?.priority
71
+ };
72
+ });
73
+ const fieldSyncResult = await fieldSync.syncItemFields(fieldSyncItems);
74
+ return {
75
+ projectId: project.id,
76
+ projectNumber: project.number,
77
+ itemIds,
78
+ fieldSyncResult
79
+ };
80
+ }
81
+ }
82
+ export {
83
+ GitHubSyncOrchestrator
84
+ };
@@ -0,0 +1,156 @@
1
+ /**
2
+ * GitHub Sync Orchestrator — Wires push sync, Projects V2, and frontmatter updates
3
+ *
4
+ * Composes all sync sub-components into a single flow:
5
+ * 1. Push user stories → GitHub issues
6
+ * 2. (Optional) Add issues to Projects V2 board
7
+ * 3. (Optional) Set Status/Priority fields on V2 items
8
+ * 4. Update spec frontmatter with sync results
9
+ *
10
+ * @module github-sync-orchestrator
11
+ */
12
+
13
+ import { pushSyncUserStories } from './github-push-sync.js';
14
+ import { GitHubBoardResolverV2 } from './github-board-resolver-v2.js';
15
+ import { GitHubFieldSync } from './github-field-sync.js';
16
+ import { updateSpecFrontmatter } from './github-spec-frontmatter-updater.js';
17
+ import { GitHubGraphQLClient } from './github-graphql-client.js';
18
+ import type { UserStoryForSync, PushSyncResult } from './github-push-sync.js';
19
+ import type { FieldSyncResult } from './github-field-sync.js';
20
+ import type { GitHubSyncMetadata } from '../../../src/core/types/sync-profile.js';
21
+
22
+ export interface SyncOrchestratorConfig {
23
+ owner: string;
24
+ repo: string;
25
+ token?: string;
26
+ dryRun?: boolean;
27
+ projectV2Enabled?: boolean;
28
+ projectV2Number?: number;
29
+ projectV2Id?: string;
30
+ statusFieldMapping?: Record<string, string>;
31
+ priorityFieldMapping?: Record<string, string>;
32
+ }
33
+
34
+ export interface ProjectV2Result {
35
+ projectId: string;
36
+ projectNumber: number;
37
+ itemIds: string[];
38
+ fieldSyncResult: FieldSyncResult;
39
+ }
40
+
41
+ export interface SyncOrchestratorResult {
42
+ pushResult: PushSyncResult;
43
+ projectV2Result?: ProjectV2Result;
44
+ frontmatterResult: GitHubSyncMetadata;
45
+ }
46
+
47
+ export class GitHubSyncOrchestrator {
48
+ private config: SyncOrchestratorConfig;
49
+
50
+ constructor(config: SyncOrchestratorConfig) {
51
+ this.config = config;
52
+ }
53
+
54
+ /**
55
+ * Run the full sync flow for a spec.
56
+ */
57
+ async syncSpec(
58
+ specPath: string,
59
+ userStories: UserStoryForSync[],
60
+ ): Promise<SyncOrchestratorResult> {
61
+ // Step 1: Push sync — create/update GitHub issues
62
+ const pushResult = await pushSyncUserStories(userStories, {
63
+ owner: this.config.owner,
64
+ repo: this.config.repo,
65
+ token: this.config.token,
66
+ dryRun: this.config.dryRun,
67
+ });
68
+
69
+ // Step 2: Projects V2 (optional, skip in dry run)
70
+ let projectV2Result: ProjectV2Result | undefined;
71
+
72
+ if (this.config.projectV2Enabled && !this.config.dryRun) {
73
+ projectV2Result = await this.syncProjectV2(pushResult, userStories);
74
+ }
75
+
76
+ // Step 3: Update spec frontmatter
77
+ const frontmatterOptions: { projectV2Id?: string; projectV2Number?: number } = {};
78
+ if (projectV2Result) {
79
+ frontmatterOptions.projectV2Id = projectV2Result.projectId;
80
+ frontmatterOptions.projectV2Number = projectV2Result.projectNumber;
81
+ }
82
+
83
+ const frontmatterResult = await updateSpecFrontmatter(
84
+ specPath,
85
+ pushResult,
86
+ frontmatterOptions,
87
+ );
88
+
89
+ return {
90
+ pushResult,
91
+ projectV2Result,
92
+ frontmatterResult,
93
+ };
94
+ }
95
+
96
+ private async syncProjectV2(
97
+ pushResult: PushSyncResult,
98
+ userStories: UserStoryForSync[],
99
+ ): Promise<ProjectV2Result> {
100
+ const graphqlClient = new GitHubGraphQLClient(this.config.token);
101
+
102
+ // Find or create the Projects V2 board
103
+ const boardResolver = new GitHubBoardResolverV2(graphqlClient, {
104
+ owner: this.config.owner,
105
+ projectV2Number: this.config.projectV2Number,
106
+ projectV2Id: this.config.projectV2Id,
107
+ });
108
+
109
+ const project = await boardResolver.findOrCreateProject('SpecWeave Sync Board');
110
+
111
+ // Collect issue node IDs from created issues
112
+ const nodeIds = pushResult.created
113
+ .filter(item => item.issueNodeId)
114
+ .map(item => item.issueNodeId);
115
+
116
+ // Add issues to project
117
+ const itemIds = await boardResolver.addIssuesToProject(project.id, nodeIds);
118
+
119
+ // Build a map from nodeId → user story for field sync
120
+ const nodeIdToStory = new Map<string, UserStoryForSync>();
121
+ for (const created of pushResult.created) {
122
+ if (created.issueNodeId) {
123
+ const story = userStories.find(s => s.id === created.userStoryId);
124
+ if (story) {
125
+ nodeIdToStory.set(created.issueNodeId, story);
126
+ }
127
+ }
128
+ }
129
+
130
+ // Sync Status and Priority fields
131
+ const fieldSync = new GitHubFieldSync(graphqlClient, {
132
+ projectId: project.id,
133
+ statusFieldMapping: this.config.statusFieldMapping,
134
+ priorityFieldMapping: this.config.priorityFieldMapping,
135
+ });
136
+
137
+ const fieldSyncItems = itemIds.map((itemId, idx) => {
138
+ const nodeId = nodeIds[idx];
139
+ const story = nodeId ? nodeIdToStory.get(nodeId) : undefined;
140
+ return {
141
+ itemId,
142
+ status: story?.status,
143
+ priority: story?.priority,
144
+ };
145
+ });
146
+
147
+ const fieldSyncResult = await fieldSync.syncItemFields(fieldSyncItems);
148
+
149
+ return {
150
+ projectId: project.id,
151
+ projectNumber: project.number,
152
+ itemIds,
153
+ fieldSyncResult,
154
+ };
155
+ }
156
+ }
@@ -0,0 +1,134 @@
1
+ import { readFile } from "fs/promises";
2
+ import { execFileNoThrow } from "../../../src/utils/execFileNoThrow.js";
3
+ async function autoCloseCompletedUserStories(incrementId, affectedUSIds, specPath, options) {
4
+ const result = { closed: [], skipped: [], errors: [] };
5
+ if (affectedUSIds.length === 0) {
6
+ return result;
7
+ }
8
+ let content;
9
+ try {
10
+ content = await readFile(specPath, "utf-8");
11
+ } catch (err) {
12
+ result.errors.push({
13
+ usId: affectedUSIds[0],
14
+ error: err instanceof Error ? err.message : String(err)
15
+ });
16
+ return result;
17
+ }
18
+ const issueLinks = parseIssueLinks(content);
19
+ const repoSlug = `${options.owner}/${options.repo}`;
20
+ const env = options.token ? { GH_TOKEN: options.token } : void 0;
21
+ const execOpts = env ? { env } : {};
22
+ for (const usId of affectedUSIds) {
23
+ const acStates = parseACStatesForUS(content, usId);
24
+ if (acStates.length === 0 || acStates.some((ac) => !ac.completed)) {
25
+ result.skipped.push({ usId, reason: "incomplete-acs" });
26
+ continue;
27
+ }
28
+ const link = issueLinks[usId];
29
+ if (!link) {
30
+ result.skipped.push({ usId, reason: "no-issue-link" });
31
+ continue;
32
+ }
33
+ const issueNum = String(link.issueNumber);
34
+ const viewResult = await execFileNoThrow(
35
+ "gh",
36
+ ["issue", "view", issueNum, "--json", "state", "-R", repoSlug],
37
+ execOpts
38
+ );
39
+ if (viewResult.success) {
40
+ try {
41
+ const issueState = JSON.parse(viewResult.stdout);
42
+ if (issueState.state === "CLOSED") {
43
+ result.skipped.push({ usId, reason: "already-closed" });
44
+ continue;
45
+ }
46
+ } catch {
47
+ }
48
+ }
49
+ const commentBody = buildCompletionComment(incrementId, usId, acStates);
50
+ await execFileNoThrow(
51
+ "gh",
52
+ ["issue", "comment", issueNum, "--body", commentBody, "-R", repoSlug],
53
+ execOpts
54
+ );
55
+ const closeResult = await execFileNoThrow(
56
+ "gh",
57
+ ["issue", "close", issueNum, "-R", repoSlug],
58
+ execOpts
59
+ );
60
+ if (closeResult.success) {
61
+ result.closed.push({ usId, issueNumber: link.issueNumber });
62
+ } else {
63
+ result.errors.push({
64
+ usId,
65
+ error: closeResult.stderr || "Unknown error closing issue"
66
+ });
67
+ }
68
+ }
69
+ return result;
70
+ }
71
+ function buildCompletionComment(incrementId, usId, acStates) {
72
+ const total = acStates.length;
73
+ let comment = `**All acceptance criteria completed** \u2014 ${usId} (Increment ${incrementId})
74
+
75
+ `;
76
+ comment += `**Status**: ${total}/${total} ACs complete (100%)
77
+
78
+ `;
79
+ comment += `**Completed**:
80
+ `;
81
+ for (const ac of acStates) {
82
+ comment += `- [x] **${ac.id}**: ${ac.description}
83
+ `;
84
+ }
85
+ comment += "\n";
86
+ comment += `---
87
+ `;
88
+ comment += `Auto-closed by SpecWeave | ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
89
+ `;
90
+ return comment;
91
+ }
92
+ function parseIssueLinks(content) {
93
+ const links = {};
94
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
95
+ if (!fmMatch) return links;
96
+ const frontmatter = fmMatch[1];
97
+ const usBlockMatch = frontmatter.match(/userStories:\s*\n((?:\s{6,}[\s\S]*?)(?=\n\s{0,3}\S|$))/);
98
+ if (!usBlockMatch) return links;
99
+ const usBlock = usBlockMatch[1];
100
+ const usEntries = usBlock.match(/^\s+(US-\d+):\s*\n((?:\s+\w[\s\S]*?)(?=\n\s+US-|\s*$))/gm);
101
+ if (!usEntries) return links;
102
+ for (const entry of usEntries) {
103
+ const idMatch = entry.match(/(US-\d+):/);
104
+ const numMatch = entry.match(/issueNumber:\s*(\d+)/);
105
+ const urlMatch = entry.match(/issueUrl:\s*"([^"]+)"/);
106
+ if (idMatch && numMatch) {
107
+ links[idMatch[1]] = {
108
+ issueNumber: parseInt(numMatch[1], 10),
109
+ issueUrl: urlMatch ? urlMatch[1] : ""
110
+ };
111
+ }
112
+ }
113
+ return links;
114
+ }
115
+ function parseACStatesForUS(content, usId) {
116
+ const states = [];
117
+ const usNum = String(parseInt(usId.replace("US-", ""), 10));
118
+ const acPattern = new RegExp(
119
+ `- \\[([ x])\\] \\*\\*AC-US${usNum}-(\\d+)\\*\\*:\\s*(.+)`,
120
+ "g"
121
+ );
122
+ let match;
123
+ while ((match = acPattern.exec(content)) !== null) {
124
+ states.push({
125
+ id: `AC-US${usNum}-${match[2]}`,
126
+ description: match[3].trim(),
127
+ completed: match[1] === "x"
128
+ });
129
+ }
130
+ return states;
131
+ }
132
+ export {
133
+ autoCloseCompletedUserStories
134
+ };