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,226 @@
1
+ /**
2
+ * GitHub US Auto-Closer — Closes GitHub issues when all ACs are complete
3
+ *
4
+ * For each affected user story, checks if ALL acceptance criteria are
5
+ * marked complete in spec.md. If so, posts a completion comment and
6
+ * closes the associated GitHub issue.
7
+ *
8
+ * @module github-us-auto-closer
9
+ */
10
+
11
+ import { readFile } from 'fs/promises';
12
+ import { execFileNoThrow } from '../../../src/utils/execFileNoThrow.js';
13
+
14
+ export interface AutoCloseOptions {
15
+ owner: string;
16
+ repo: string;
17
+ token?: string;
18
+ }
19
+
20
+ export interface AutoCloseResult {
21
+ closed: Array<{ usId: string; issueNumber: number }>;
22
+ skipped: Array<{ usId: string; reason: string }>;
23
+ errors: Array<{ usId: string; error: string }>;
24
+ }
25
+
26
+ interface ParsedACState {
27
+ id: string;
28
+ description: string;
29
+ completed: boolean;
30
+ }
31
+
32
+ interface ParsedUSIssueLink {
33
+ issueNumber: number;
34
+ issueUrl: string;
35
+ }
36
+
37
+ /**
38
+ * Auto-close GitHub issues for user stories with all ACs complete.
39
+ *
40
+ * For each affected US:
41
+ * 1. Parse AC states from spec.md
42
+ * 2. Skip if not all ACs complete
43
+ * 3. Look up GitHub issue link from frontmatter
44
+ * 4. Check if issue already closed (idempotent)
45
+ * 5. Post completion comment
46
+ * 6. Close issue via `gh issue close`
47
+ *
48
+ * Never throws — all errors captured in result.errors.
49
+ */
50
+ export async function autoCloseCompletedUserStories(
51
+ incrementId: string,
52
+ affectedUSIds: string[],
53
+ specPath: string,
54
+ options: AutoCloseOptions,
55
+ ): Promise<AutoCloseResult> {
56
+ const result: AutoCloseResult = { closed: [], skipped: [], errors: [] };
57
+
58
+ if (affectedUSIds.length === 0) {
59
+ return result;
60
+ }
61
+
62
+ let content: string;
63
+ try {
64
+ content = await readFile(specPath, 'utf-8');
65
+ } catch (err) {
66
+ result.errors.push({
67
+ usId: affectedUSIds[0],
68
+ error: err instanceof Error ? err.message : String(err),
69
+ });
70
+ return result;
71
+ }
72
+
73
+ const issueLinks = parseIssueLinks(content);
74
+ const repoSlug = `${options.owner}/${options.repo}`;
75
+ const env = options.token ? { GH_TOKEN: options.token } : undefined;
76
+ const execOpts = env ? { env } : {};
77
+
78
+ for (const usId of affectedUSIds) {
79
+ const acStates = parseACStatesForUS(content, usId);
80
+
81
+ // Skip if not all ACs complete
82
+ if (acStates.length === 0 || acStates.some(ac => !ac.completed)) {
83
+ result.skipped.push({ usId, reason: 'incomplete-acs' });
84
+ continue;
85
+ }
86
+
87
+ // Skip if no issue link
88
+ const link = issueLinks[usId];
89
+ if (!link) {
90
+ result.skipped.push({ usId, reason: 'no-issue-link' });
91
+ continue;
92
+ }
93
+
94
+ const issueNum = String(link.issueNumber);
95
+
96
+ // Check if already closed (idempotent)
97
+ const viewResult = await execFileNoThrow(
98
+ 'gh',
99
+ ['issue', 'view', issueNum, '--json', 'state', '-R', repoSlug],
100
+ execOpts,
101
+ );
102
+
103
+ if (viewResult.success) {
104
+ try {
105
+ const issueState = JSON.parse(viewResult.stdout);
106
+ if (issueState.state === 'CLOSED') {
107
+ result.skipped.push({ usId, reason: 'already-closed' });
108
+ continue;
109
+ }
110
+ } catch {
111
+ // Parse error — proceed with close attempt
112
+ }
113
+ }
114
+
115
+ // Post completion comment
116
+ const commentBody = buildCompletionComment(incrementId, usId, acStates);
117
+ await execFileNoThrow(
118
+ 'gh',
119
+ ['issue', 'comment', issueNum, '--body', commentBody, '-R', repoSlug],
120
+ execOpts,
121
+ );
122
+
123
+ // Close the issue
124
+ const closeResult = await execFileNoThrow(
125
+ 'gh',
126
+ ['issue', 'close', issueNum, '-R', repoSlug],
127
+ execOpts,
128
+ );
129
+
130
+ if (closeResult.success) {
131
+ result.closed.push({ usId, issueNumber: link.issueNumber });
132
+ } else {
133
+ result.errors.push({
134
+ usId,
135
+ error: closeResult.stderr || 'Unknown error closing issue',
136
+ });
137
+ }
138
+ }
139
+
140
+ return result;
141
+ }
142
+
143
+ /**
144
+ * Build a completion comment for a fully-complete user story.
145
+ */
146
+ function buildCompletionComment(
147
+ incrementId: string,
148
+ usId: string,
149
+ acStates: ParsedACState[],
150
+ ): string {
151
+ const total = acStates.length;
152
+
153
+ let comment = `**All acceptance criteria completed** — ${usId} (Increment ${incrementId})\n\n`;
154
+ comment += `**Status**: ${total}/${total} ACs complete (100%)\n\n`;
155
+
156
+ comment += `**Completed**:\n`;
157
+ for (const ac of acStates) {
158
+ comment += `- [x] **${ac.id}**: ${ac.description}\n`;
159
+ }
160
+ comment += '\n';
161
+
162
+ comment += `---\n`;
163
+ comment += `Auto-closed by SpecWeave | ${new Date().toISOString().split('T')[0]}\n`;
164
+
165
+ return comment;
166
+ }
167
+
168
+ /**
169
+ * Parse issue links from spec.md YAML frontmatter.
170
+ */
171
+ function parseIssueLinks(content: string): Record<string, ParsedUSIssueLink> {
172
+ const links: Record<string, ParsedUSIssueLink> = {};
173
+
174
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
175
+ if (!fmMatch) return links;
176
+
177
+ const frontmatter = fmMatch[1];
178
+
179
+ const usBlockMatch = frontmatter.match(/userStories:\s*\n((?:\s{6,}[\s\S]*?)(?=\n\s{0,3}\S|$))/);
180
+ if (!usBlockMatch) return links;
181
+
182
+ const usBlock = usBlockMatch[1];
183
+ const usEntries = usBlock.match(/^\s+(US-\d+):\s*\n((?:\s+\w[\s\S]*?)(?=\n\s+US-|\s*$))/gm);
184
+
185
+ if (!usEntries) return links;
186
+
187
+ for (const entry of usEntries) {
188
+ const idMatch = entry.match(/(US-\d+):/);
189
+ const numMatch = entry.match(/issueNumber:\s*(\d+)/);
190
+ const urlMatch = entry.match(/issueUrl:\s*"([^"]+)"/);
191
+
192
+ if (idMatch && numMatch) {
193
+ links[idMatch[1]] = {
194
+ issueNumber: parseInt(numMatch[1], 10),
195
+ issueUrl: urlMatch ? urlMatch[1] : '',
196
+ };
197
+ }
198
+ }
199
+
200
+ return links;
201
+ }
202
+
203
+ /**
204
+ * Extract AC states for a specific user story from spec.md content.
205
+ */
206
+ function parseACStatesForUS(content: string, usId: string): ParsedACState[] {
207
+ const states: ParsedACState[] = [];
208
+ // AC IDs use unpadded US number: US-001 → AC-US1-XX
209
+ const usNum = String(parseInt(usId.replace('US-', ''), 10));
210
+
211
+ const acPattern = new RegExp(
212
+ `- \\[([ x])\\] \\*\\*AC-US${usNum}-(\\d+)\\*\\*:\\s*(.+)`,
213
+ 'g',
214
+ );
215
+
216
+ let match;
217
+ while ((match = acPattern.exec(content)) !== null) {
218
+ states.push({
219
+ id: `AC-US${usNum}-${match[2]}`,
220
+ description: match[3].trim(),
221
+ completed: match[1] === 'x',
222
+ });
223
+ }
224
+
225
+ return states;
226
+ }
@@ -1,11 +1,5 @@
1
1
  import { GitHubClient } from "./github-client";
2
- import { TaskParser } from "./task-parser";
3
- import { TaskSync } from "./task-sync";
4
- import { SubtaskSync } from "./subtask-sync";
5
2
  export * from "./types";
6
3
  export {
7
- GitHubClient,
8
- SubtaskSync,
9
- TaskParser,
10
- TaskSync
4
+ GitHubClient
11
5
  };
@@ -1,10 +1,7 @@
1
1
  /**
2
2
  * SpecWeave GitHub Plugin - Library Entry Point
3
- * Task-level GitHub synchronization
3
+ * Spec-based GitHub synchronization
4
4
  */
5
5
 
6
6
  export { GitHubClient } from './github-client';
7
- export { TaskParser } from './task-parser';
8
- export { TaskSync } from './task-sync';
9
- export { SubtaskSync } from './subtask-sync';
10
7
  export * from './types';
@@ -457,14 +457,86 @@ For monorepos with multiple GitHub repositories:
457
457
 
458
458
  ---
459
459
 
460
+ ## Projects V2 Integration (v3.0.0+)
461
+
462
+ ### Enabling Projects V2
463
+
464
+ Add `projectV2Enabled: true` to your sync config:
465
+
466
+ ```json
467
+ {
468
+ "sync": {
469
+ "profiles": {
470
+ "myproject": {
471
+ "provider": "github",
472
+ "config": {
473
+ "owner": "myorg",
474
+ "repo": "myrepo",
475
+ "projectV2Enabled": true,
476
+ "projectV2Number": 5
477
+ }
478
+ }
479
+ }
480
+ }
481
+ }
482
+ ```
483
+
484
+ ### What Happens with V2 Enabled
485
+
486
+ 1. **Push sync** creates/updates GitHub issues (same as before)
487
+ 2. **Issues are added to Projects V2 board** automatically
488
+ 3. **Status field** is set based on user story status (planned/in-progress/completed)
489
+ 4. **Priority field** is set based on user story priority (P1-P4)
490
+
491
+ ### Custom Field Mappings
492
+
493
+ ```json
494
+ {
495
+ "statusFieldMapping": {
496
+ "planned": "Todo",
497
+ "in-progress": "In Progress",
498
+ "completed": "Done"
499
+ },
500
+ "priorityFieldMapping": {
501
+ "P1": "Urgent",
502
+ "P2": "High",
503
+ "P3": "Medium",
504
+ "P4": "Low"
505
+ }
506
+ }
507
+ ```
508
+
509
+ ### Pull Sync (GitHub to Spec)
510
+
511
+ Pull sync fetches issue state and compares with spec ACs:
512
+ - AC checkbox toggles on GitHub are detected and applied to spec
513
+ - Issue close/reopen is detected as status change
514
+ - Conflicts are detected when both spec and GitHub changed the same field
515
+
516
+ ### Batch Sync
517
+
518
+ Sync all specs at once:
519
+ - Discovers all `spec-*.md` files in `.specweave/docs/internal/specs/`
520
+ - Syncs each sequentially
521
+ - Reports summary with created/updated/failed counts
522
+
523
+ ### Cross-Repo Sync
524
+
525
+ For distributed strategies, issues can be created in different repos:
526
+ - User stories specify target repos via `targetRepos` field
527
+ - Cross-references added: "Also tracked in: org/other-repo#XX"
528
+ - All cross-repo issues added to shared org-level Projects V2 board
529
+
530
+ ---
531
+
460
532
  ## Related
461
533
 
462
- - **github-issue-tracker**: Track individual tasks as issue comments (DEPRECATED - use spec sync instead)
463
- - **github-manager agent**: AI agent for GitHub operations
534
+ - **github-issue-tracker**: DEPRECATED - use spec sync instead
464
535
  - **Commands**: `/sw-github:sync-spec`, `/sw-github:import-project`, `/sw-github:status`
536
+ - **Team Skills**: `/sw:team-orchestrate`, `/sw:team-status`, `/sw:team-merge`
465
537
 
466
538
  ---
467
539
 
468
- **Version**: 2.0.0 (Spec-based architecture)
540
+ **Version**: 3.0.0 (Projects V2 + Pull Sync + Cross-Repo)
469
541
  **Plugin**: specweave-github
470
- **Last Updated**: 2025-11-11
542
+ **Last Updated**: 2026-02-06
@@ -8,6 +8,24 @@ Set up comprehensive Playwright E2E testing with best practices, page objects, a
8
8
 
9
9
  You are an expert E2E testing engineer who implements production-ready Playwright test suites.
10
10
 
11
+ ## CLI vs MCP Mode
12
+
13
+ SpecWeave supports two modes for Playwright browser automation:
14
+
15
+ - **@playwright/cli** (recommended for test execution): Token-efficient, file-based output, CI-friendly
16
+ - **@playwright/mcp** (for interactive exploration): Rich inline snapshots, good for debugging
17
+
18
+ Install CLI mode: `npm install -g @playwright/cli@latest`
19
+
20
+ Configure preference in `.specweave/config.json`:
21
+ ```json
22
+ {
23
+ "testing": {
24
+ "playwright": { "preferCli": true }
25
+ }
26
+ }
27
+ ```
28
+
11
29
  ## Your Task
12
30
 
13
31
  Set up a complete Playwright E2E testing framework with page objects, fixtures, and testing patterns.
@@ -21,6 +21,8 @@ Create and execute automated browser workflows using Playwright. Generate script
21
21
 
22
22
  > **Why Code-First?** Anthropic research shows [code execution beats MCP tool calls](https://www.anthropic.com/engineering/code-execution-with-mcp) with 98% token reduction. Playwright code is reusable, committable, CI-runnable, and deterministic.
23
23
 
24
+ > **CLI Mode Available**: When `@playwright/cli` is installed, this command routes to the CLI by default for maximum token efficiency (~250 chars per interaction vs ~5K+ via MCP). The CLI keeps browser state external and returns file references instead of inline DOM trees. Install: `npm install -g @playwright/cli@latest`
25
+
24
26
  ## Workflow Types
25
27
 
26
28
  ### 1. Form Automation
@@ -63,6 +63,14 @@ Provides:
63
63
  - **Accessibility Audit**: Check element accessibility
64
64
  - **Bug Investigation**: Debug element behavior and properties
65
65
 
66
+ ## Browser Mode
67
+
68
+ This command **prefers MCP mode** for rich DOM introspection. When the Playwright MCP plugin provides inline accessibility tree snapshots, the AI can reason about page structure and find optimal selectors.
69
+
70
+ If MCP is unavailable, falls back to `@playwright/cli snapshot` which saves the accessibility tree to a file (`.playwright-cli/*.yml`). The AI can then read the file, but loses the ability to iterate on snapshots without re-reading.
71
+
72
+ **Recommended**: Keep the Playwright MCP plugin installed for best `ui-inspect` experience.
73
+
66
74
  ## Requirements
67
75
 
68
76
  - Playwright browser binaries (auto-installed on first use)
@@ -0,0 +1,6 @@
1
+ export type CiConfig = {
2
+ headed: boolean;
3
+ isCI: boolean;
4
+ outputDir: string;
5
+ };
6
+ export declare function getCiDefaults(options?: Partial<CiConfig>): CiConfig;
@@ -0,0 +1,14 @@
1
+ function isRunningInCI() {
2
+ return !!(process.env.CI === 'true' ||
3
+ process.env.GITHUB_ACTIONS === 'true' ||
4
+ process.env.GITLAB_CI === 'true' ||
5
+ process.env.JENKINS_URL);
6
+ }
7
+ export function getCiDefaults(options) {
8
+ const isCI = isRunningInCI();
9
+ return {
10
+ isCI,
11
+ headed: isCI ? false : (options?.headed ?? false),
12
+ outputDir: options?.outputDir ?? '.playwright-cli',
13
+ };
14
+ }
@@ -0,0 +1,24 @@
1
+ export type CiConfig = {
2
+ headed: boolean;
3
+ isCI: boolean;
4
+ outputDir: string;
5
+ };
6
+
7
+ function isRunningInCI(): boolean {
8
+ return !!(
9
+ process.env.CI === 'true' ||
10
+ process.env.GITHUB_ACTIONS === 'true' ||
11
+ process.env.GITLAB_CI === 'true' ||
12
+ process.env.JENKINS_URL
13
+ );
14
+ }
15
+
16
+ export function getCiDefaults(options?: Partial<CiConfig>): CiConfig {
17
+ const isCI = isRunningInCI();
18
+
19
+ return {
20
+ isCI,
21
+ headed: isCI ? false : (options?.headed ?? false),
22
+ outputDir: options?.outputDir ?? '.playwright-cli',
23
+ };
24
+ }
@@ -0,0 +1,33 @@
1
+ import { execSync } from "child_process";
2
+ let cachedResult = null;
3
+ function detectPlaywrightCli(options = {}) {
4
+ const { useCache = false } = options;
5
+ if (useCache && cachedResult) {
6
+ return cachedResult;
7
+ }
8
+ let path;
9
+ let version;
10
+ try {
11
+ path = execSync("which playwright-cli", {
12
+ encoding: "utf-8",
13
+ timeout: 5e3
14
+ }).trim();
15
+ } catch {
16
+ const result2 = { installed: false };
17
+ if (useCache) cachedResult = result2;
18
+ return result2;
19
+ }
20
+ try {
21
+ version = execSync("playwright-cli --version", {
22
+ encoding: "utf-8",
23
+ timeout: 5e3
24
+ }).trim();
25
+ } catch {
26
+ }
27
+ const result = { installed: true, path, version };
28
+ if (useCache) cachedResult = result;
29
+ return result;
30
+ }
31
+ export {
32
+ detectPlaywrightCli
33
+ };
@@ -0,0 +1,48 @@
1
+ import { execSync } from 'child_process';
2
+
3
+ export interface CliDetectionResult {
4
+ installed: boolean;
5
+ version?: string;
6
+ path?: string;
7
+ }
8
+
9
+ export interface DetectOptions {
10
+ useCache?: boolean;
11
+ }
12
+
13
+ let cachedResult: CliDetectionResult | null = null;
14
+
15
+ export function detectPlaywrightCli(options: DetectOptions = {}): CliDetectionResult {
16
+ const { useCache = false } = options;
17
+
18
+ if (useCache && cachedResult) {
19
+ return cachedResult;
20
+ }
21
+
22
+ let path: string | undefined;
23
+ let version: string | undefined;
24
+
25
+ try {
26
+ path = execSync('which playwright-cli', {
27
+ encoding: 'utf-8',
28
+ timeout: 5_000,
29
+ }).trim();
30
+ } catch {
31
+ const result: CliDetectionResult = { installed: false };
32
+ if (useCache) cachedResult = result;
33
+ return result;
34
+ }
35
+
36
+ try {
37
+ version = execSync('playwright-cli --version', {
38
+ encoding: 'utf-8',
39
+ timeout: 5_000,
40
+ }).trim();
41
+ } catch {
42
+ // Version check failed but binary exists
43
+ }
44
+
45
+ const result: CliDetectionResult = { installed: true, path, version };
46
+ if (useCache) cachedResult = result;
47
+ return result;
48
+ }
@@ -0,0 +1,58 @@
1
+ import { execSync } from "child_process";
2
+ class PlaywrightCliRunner {
3
+ constructor(config = {}) {
4
+ this.config = {
5
+ headed: config.headed ?? false,
6
+ browser: config.browser ?? "chrome",
7
+ session: config.session,
8
+ timeout: config.timeout ?? 3e4,
9
+ ...config
10
+ };
11
+ }
12
+ exec(command) {
13
+ const sessionFlag = this.config.session ? `-s=${this.config.session} ` : "";
14
+ const fullCommand = `playwright-cli ${sessionFlag}${command}`;
15
+ try {
16
+ const output = execSync(fullCommand, {
17
+ encoding: "utf-8",
18
+ timeout: this.config.timeout
19
+ }).trim();
20
+ return { ok: true, output };
21
+ } catch (e) {
22
+ return { ok: false, output: e.message };
23
+ }
24
+ }
25
+ open(url) {
26
+ const headedFlag = this.config.headed ? " --headed" : "";
27
+ const urlPart = url ? ` ${url}` : "";
28
+ return this.exec(`open${urlPart}${headedFlag}`);
29
+ }
30
+ navigate(url) {
31
+ return this.exec(`goto ${url}`);
32
+ }
33
+ snapshot() {
34
+ return this.exec("snapshot");
35
+ }
36
+ screenshot(filename) {
37
+ const flag = filename ? ` --filename ${filename}` : "";
38
+ return this.exec(`screenshot${flag}`);
39
+ }
40
+ close() {
41
+ return this.exec("close");
42
+ }
43
+ click(ref) {
44
+ return this.exec(`click ${ref}`);
45
+ }
46
+ type(text) {
47
+ return this.exec(`type "${text}"`);
48
+ }
49
+ fill(ref, text) {
50
+ return this.exec(`fill ${ref} "${text}"`);
51
+ }
52
+ evaluate(fn) {
53
+ return this.exec(`eval "${fn}"`);
54
+ }
55
+ }
56
+ export {
57
+ PlaywrightCliRunner
58
+ };
@@ -0,0 +1,80 @@
1
+ import { execSync } from 'child_process';
2
+
3
+ export interface CliRunnerConfig {
4
+ headed?: boolean;
5
+ browser?: string;
6
+ session?: string;
7
+ timeout?: number;
8
+ }
9
+
10
+ export interface CliResult {
11
+ ok: boolean;
12
+ output: string;
13
+ }
14
+
15
+ export class PlaywrightCliRunner {
16
+ readonly config: Required<Pick<CliRunnerConfig, 'headed' | 'browser'>> & CliRunnerConfig;
17
+
18
+ constructor(config: CliRunnerConfig = {}) {
19
+ this.config = {
20
+ headed: config.headed ?? false,
21
+ browser: config.browser ?? 'chrome',
22
+ session: config.session,
23
+ timeout: config.timeout ?? 30_000,
24
+ ...config,
25
+ };
26
+ }
27
+
28
+ private exec(command: string): CliResult {
29
+ const sessionFlag = this.config.session ? `-s=${this.config.session} ` : '';
30
+ const fullCommand = `playwright-cli ${sessionFlag}${command}`;
31
+ try {
32
+ const output = execSync(fullCommand, {
33
+ encoding: 'utf-8',
34
+ timeout: this.config.timeout,
35
+ }).trim();
36
+ return { ok: true, output };
37
+ } catch (e: unknown) {
38
+ return { ok: false, output: (e as Error).message };
39
+ }
40
+ }
41
+
42
+ open(url?: string): CliResult {
43
+ const headedFlag = this.config.headed ? ' --headed' : '';
44
+ const urlPart = url ? ` ${url}` : '';
45
+ return this.exec(`open${urlPart}${headedFlag}`);
46
+ }
47
+
48
+ navigate(url: string): CliResult {
49
+ return this.exec(`goto ${url}`);
50
+ }
51
+
52
+ snapshot(): CliResult {
53
+ return this.exec('snapshot');
54
+ }
55
+
56
+ screenshot(filename?: string): CliResult {
57
+ const flag = filename ? ` --filename ${filename}` : '';
58
+ return this.exec(`screenshot${flag}`);
59
+ }
60
+
61
+ close(): CliResult {
62
+ return this.exec('close');
63
+ }
64
+
65
+ click(ref: string): CliResult {
66
+ return this.exec(`click ${ref}`);
67
+ }
68
+
69
+ type(text: string): CliResult {
70
+ return this.exec(`type "${text}"`);
71
+ }
72
+
73
+ fill(ref: string, text: string): CliResult {
74
+ return this.exec(`fill ${ref} "${text}"`);
75
+ }
76
+
77
+ evaluate(fn: string): CliResult {
78
+ return this.exec(`eval "${fn}"`);
79
+ }
80
+ }
@@ -0,0 +1,16 @@
1
+ import { detectPlaywrightCli } from "./playwright-cli-detector.js";
2
+ const MCP_PREFERRED_TASKS = /* @__PURE__ */ new Set([
3
+ "ui-inspect",
4
+ "page-exploration",
5
+ "self-healing-test"
6
+ ]);
7
+ function resolvePlaywrightMode(task, config = {}) {
8
+ const { preferCli = true } = config;
9
+ if (!preferCli) return "mcp";
10
+ const detection = detectPlaywrightCli({ useCache: true });
11
+ if (!detection.installed) return "mcp";
12
+ return MCP_PREFERRED_TASKS.has(task) ? "mcp" : "cli";
13
+ }
14
+ export {
15
+ resolvePlaywrightMode
16
+ };