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,165 @@
1
+ /**
2
+ * GitHub Field Sync — Status and Priority field sync for Projects V2
3
+ *
4
+ * Syncs Status and Priority custom fields on Projects V2 items
5
+ * using configurable field mappings.
6
+ *
7
+ * @module github-field-sync
8
+ */
9
+
10
+ import type { GitHubGraphQLClient } from './github-graphql-client.js';
11
+
12
+ export interface FieldSyncConfig {
13
+ projectId: string;
14
+ statusFieldMapping?: Record<string, string>;
15
+ priorityFieldMapping?: Record<string, string>;
16
+ }
17
+
18
+ export interface FieldSyncItem {
19
+ itemId: string;
20
+ status?: string;
21
+ priority?: string;
22
+ }
23
+
24
+ export interface FieldSyncResult {
25
+ updated: Array<{ itemId: string; field: string; value: string }>;
26
+ warnings: Array<{ itemId: string; field: string; message: string }>;
27
+ }
28
+
29
+ interface CachedField {
30
+ id: string;
31
+ name: string;
32
+ options: Map<string, string>; // option name → option ID
33
+ }
34
+
35
+ const DEFAULT_STATUS_MAPPING: Record<string, string> = {
36
+ 'planned': 'Todo',
37
+ 'in-progress': 'In Progress',
38
+ 'completed': 'Done',
39
+ };
40
+
41
+ const DEFAULT_PRIORITY_MAPPING: Record<string, string> = {
42
+ 'P1': 'Urgent',
43
+ 'P2': 'High',
44
+ 'P3': 'Medium',
45
+ 'P4': 'Low',
46
+ };
47
+
48
+ export class GitHubFieldSync {
49
+ private client: GitHubGraphQLClient;
50
+ private config: FieldSyncConfig;
51
+ private statusField?: CachedField;
52
+ private priorityField?: CachedField;
53
+ private fieldsLoaded = false;
54
+
55
+ constructor(client: GitHubGraphQLClient, config: FieldSyncConfig) {
56
+ this.client = client;
57
+ this.config = config;
58
+ }
59
+
60
+ /**
61
+ * Load project fields and cache field IDs and option IDs.
62
+ */
63
+ async loadFields(): Promise<void> {
64
+ if (this.fieldsLoaded) return;
65
+
66
+ const fields = await this.client.getProjectFields(this.config.projectId);
67
+
68
+ for (const field of fields) {
69
+ if (field.name === 'Status' && field.options) {
70
+ this.statusField = {
71
+ id: field.id,
72
+ name: field.name,
73
+ options: new Map(field.options.map(o => [o.name, o.id])),
74
+ };
75
+ } else if (field.name === 'Priority' && field.options) {
76
+ this.priorityField = {
77
+ id: field.id,
78
+ name: field.name,
79
+ options: new Map(field.options.map(o => [o.name, o.id])),
80
+ };
81
+ }
82
+ }
83
+
84
+ this.fieldsLoaded = true;
85
+ }
86
+
87
+ /**
88
+ * Sync fields for one or more items.
89
+ * Auto-calls loadFields if not called yet.
90
+ */
91
+ async syncItemFields(items: FieldSyncItem[]): Promise<FieldSyncResult> {
92
+ if (!this.fieldsLoaded) {
93
+ await this.loadFields();
94
+ }
95
+
96
+ const result: FieldSyncResult = { updated: [], warnings: [] };
97
+
98
+ for (const item of items) {
99
+ // Sync Status field
100
+ if (item.status !== undefined) {
101
+ await this.syncField(item, 'Status', item.status, this.statusField,
102
+ this.config.statusFieldMapping ?? DEFAULT_STATUS_MAPPING, result);
103
+ }
104
+
105
+ // Sync Priority field
106
+ if (item.priority !== undefined) {
107
+ await this.syncField(item, 'Priority', item.priority, this.priorityField,
108
+ this.config.priorityFieldMapping ?? DEFAULT_PRIORITY_MAPPING, result);
109
+ }
110
+ }
111
+
112
+ return result;
113
+ }
114
+
115
+ private async syncField(
116
+ item: FieldSyncItem,
117
+ fieldName: string,
118
+ value: string,
119
+ cachedField: CachedField | undefined,
120
+ mapping: Record<string, string>,
121
+ result: FieldSyncResult,
122
+ ): Promise<void> {
123
+ if (!cachedField) {
124
+ result.warnings.push({
125
+ itemId: item.itemId,
126
+ field: fieldName,
127
+ message: `${fieldName} field not found on project`,
128
+ });
129
+ return;
130
+ }
131
+
132
+ const mappedValue = mapping[value];
133
+ if (!mappedValue) {
134
+ result.warnings.push({
135
+ itemId: item.itemId,
136
+ field: fieldName,
137
+ message: `No mapping found for ${fieldName} value "${value}"`,
138
+ });
139
+ return;
140
+ }
141
+
142
+ const optionId = cachedField.options.get(mappedValue);
143
+ if (!optionId) {
144
+ result.warnings.push({
145
+ itemId: item.itemId,
146
+ field: fieldName,
147
+ message: `Option "${mappedValue}" not found on ${fieldName} field`,
148
+ });
149
+ return;
150
+ }
151
+
152
+ await this.client.updateItemFieldValue(
153
+ this.config.projectId,
154
+ item.itemId,
155
+ cachedField.id,
156
+ { singleSelectOptionId: optionId },
157
+ );
158
+
159
+ result.updated.push({
160
+ itemId: item.itemId,
161
+ field: fieldName,
162
+ value: mappedValue,
163
+ });
164
+ }
165
+ }
@@ -0,0 +1,129 @@
1
+ import { execFile } from "child_process";
2
+ class GitHubGraphQLClient {
3
+ constructor(token) {
4
+ this.token = token;
5
+ }
6
+ /**
7
+ * Resolve an owner login (user or org) to a node ID.
8
+ * Tries user first, falls back to organization.
9
+ */
10
+ async getOwnerNodeId(login) {
11
+ const userQuery = `query { user(login: "${login}") { id } }`;
12
+ const userResult = await this.executeGraphQL(userQuery);
13
+ if (userResult.data?.user && userResult.data.user.id) {
14
+ return userResult.data.user.id;
15
+ }
16
+ const orgQuery = `query { organization(login: "${login}") { id } }`;
17
+ const orgResult = await this.executeGraphQL(orgQuery);
18
+ if (orgResult.data?.organization && orgResult.data.organization.id) {
19
+ return orgResult.data.organization.id;
20
+ }
21
+ throw new Error(`Could not resolve to a User or Organization with the login of '${login}'.`);
22
+ }
23
+ /**
24
+ * Create a new GitHub Projects V2 board.
25
+ */
26
+ async createProjectV2(ownerId, title) {
27
+ const query = `mutation {
28
+ createProjectV2(input: { ownerId: "${ownerId}", title: "${title}" }) {
29
+ projectV2 { id number }
30
+ }
31
+ }`;
32
+ const result = await this.executeGraphQL(query);
33
+ const project = result.data?.createProjectV2?.projectV2;
34
+ return { id: project.id, number: project.number };
35
+ }
36
+ /**
37
+ * Add an issue (by node ID) to a Projects V2 board.
38
+ * Returns the project item ID. Idempotent.
39
+ */
40
+ async addProjectV2Item(projectId, contentId) {
41
+ const query = `mutation {
42
+ addProjectV2ItemById(input: { projectId: "${projectId}", contentId: "${contentId}" }) {
43
+ item { id }
44
+ }
45
+ }`;
46
+ const result = await this.executeGraphQL(query);
47
+ const item = result.data?.addProjectV2ItemById?.item;
48
+ return item.id;
49
+ }
50
+ /**
51
+ * Update a field value on a Projects V2 item.
52
+ */
53
+ async updateItemFieldValue(projectId, itemId, fieldId, value) {
54
+ const query = `mutation {
55
+ updateProjectV2ItemFieldValue(input: {
56
+ projectId: "${projectId}",
57
+ itemId: "${itemId}",
58
+ fieldId: "${fieldId}",
59
+ value: { singleSelectOptionId: "${value.singleSelectOptionId}" }
60
+ }) {
61
+ projectV2Item { id }
62
+ }
63
+ }`;
64
+ await this.executeGraphQL(query);
65
+ }
66
+ /**
67
+ * Get field definitions for a Projects V2 board.
68
+ */
69
+ async getProjectFields(projectId) {
70
+ const query = `query {
71
+ node(id: "${projectId}") {
72
+ ... on ProjectV2 {
73
+ fields(first: 50) {
74
+ nodes {
75
+ ... on ProjectV2Field { id name dataType }
76
+ ... on ProjectV2SingleSelectField {
77
+ id name dataType
78
+ options { id name }
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }`;
85
+ const result = await this.executeGraphQL(query);
86
+ const fields = result.data?.node?.fields?.nodes || [];
87
+ return fields.map((f) => {
88
+ const field = { id: f.id, name: f.name };
89
+ if (f.options && f.options.length > 0) {
90
+ field.options = f.options;
91
+ }
92
+ return field;
93
+ });
94
+ }
95
+ /**
96
+ * Execute a GraphQL query via `gh api graphql`.
97
+ */
98
+ executeGraphQL(query) {
99
+ return new Promise((resolve, reject) => {
100
+ const args = ["api", "graphql", "-f", `query=${query}`];
101
+ const opts = {};
102
+ if (this.token) {
103
+ opts.env = { ...process.env, GH_TOKEN: this.token };
104
+ }
105
+ execFile("gh", args, opts, (err, stdout, stderr) => {
106
+ if (err) {
107
+ reject(new Error(stderr || err.message));
108
+ return;
109
+ }
110
+ let parsed;
111
+ try {
112
+ parsed = JSON.parse(stdout);
113
+ } catch {
114
+ reject(new Error(`Failed to parse GraphQL response: ${stdout.slice(0, 200)}`));
115
+ return;
116
+ }
117
+ if (parsed.errors && parsed.errors.length > 0) {
118
+ const messages = parsed.errors.map((e) => e.message).join("; ");
119
+ reject(new Error(`GraphQL error: ${messages}`));
120
+ return;
121
+ }
122
+ resolve(parsed);
123
+ });
124
+ });
125
+ }
126
+ }
127
+ export {
128
+ GitHubGraphQLClient
129
+ };
@@ -0,0 +1,181 @@
1
+ /**
2
+ * GitHub GraphQL Client for Projects V2
3
+ *
4
+ * Wraps `gh api graphql` CLI calls for GitHub Projects V2 operations.
5
+ * All calls use child_process.execFile for security (no shell interpolation).
6
+ *
7
+ * @module github-graphql-client
8
+ */
9
+
10
+ import { execFile } from 'child_process';
11
+
12
+ interface ProjectField {
13
+ id: string;
14
+ name: string;
15
+ options?: Array<{ id: string; name: string }>;
16
+ }
17
+
18
+ interface GraphQLResponse {
19
+ data?: Record<string, unknown>;
20
+ errors?: Array<{ message: string; type?: string; path?: string[] }>;
21
+ }
22
+
23
+ export class GitHubGraphQLClient {
24
+ private token?: string;
25
+
26
+ constructor(token?: string) {
27
+ this.token = token;
28
+ }
29
+
30
+ /**
31
+ * Resolve an owner login (user or org) to a node ID.
32
+ * Tries user first, falls back to organization.
33
+ */
34
+ async getOwnerNodeId(login: string): Promise<string> {
35
+ // Try user query first
36
+ const userQuery = `query { user(login: "${login}") { id } }`;
37
+ const userResult = await this.executeGraphQL(userQuery);
38
+
39
+ if (userResult.data?.user && (userResult.data.user as { id?: string }).id) {
40
+ return (userResult.data.user as { id: string }).id;
41
+ }
42
+
43
+ // Fallback to organization
44
+ const orgQuery = `query { organization(login: "${login}") { id } }`;
45
+ const orgResult = await this.executeGraphQL(orgQuery);
46
+
47
+ if (orgResult.data?.organization && (orgResult.data.organization as { id?: string }).id) {
48
+ return (orgResult.data.organization as { id: string }).id;
49
+ }
50
+
51
+ throw new Error(`Could not resolve to a User or Organization with the login of '${login}'.`);
52
+ }
53
+
54
+ /**
55
+ * Create a new GitHub Projects V2 board.
56
+ */
57
+ async createProjectV2(ownerId: string, title: string): Promise<{ id: string; number: number }> {
58
+ const query = `mutation {
59
+ createProjectV2(input: { ownerId: "${ownerId}", title: "${title}" }) {
60
+ projectV2 { id number }
61
+ }
62
+ }`;
63
+
64
+ const result = await this.executeGraphQL(query);
65
+ const project = (result.data?.createProjectV2 as { projectV2: { id: string; number: number } })?.projectV2;
66
+ return { id: project.id, number: project.number };
67
+ }
68
+
69
+ /**
70
+ * Add an issue (by node ID) to a Projects V2 board.
71
+ * Returns the project item ID. Idempotent.
72
+ */
73
+ async addProjectV2Item(projectId: string, contentId: string): Promise<string> {
74
+ const query = `mutation {
75
+ addProjectV2ItemById(input: { projectId: "${projectId}", contentId: "${contentId}" }) {
76
+ item { id }
77
+ }
78
+ }`;
79
+
80
+ const result = await this.executeGraphQL(query);
81
+ const item = (result.data?.addProjectV2ItemById as { item: { id: string } })?.item;
82
+ return item.id;
83
+ }
84
+
85
+ /**
86
+ * Update a field value on a Projects V2 item.
87
+ */
88
+ async updateItemFieldValue(
89
+ projectId: string,
90
+ itemId: string,
91
+ fieldId: string,
92
+ value: { singleSelectOptionId: string },
93
+ ): Promise<void> {
94
+ const query = `mutation {
95
+ updateProjectV2ItemFieldValue(input: {
96
+ projectId: "${projectId}",
97
+ itemId: "${itemId}",
98
+ fieldId: "${fieldId}",
99
+ value: { singleSelectOptionId: "${value.singleSelectOptionId}" }
100
+ }) {
101
+ projectV2Item { id }
102
+ }
103
+ }`;
104
+
105
+ await this.executeGraphQL(query);
106
+ }
107
+
108
+ /**
109
+ * Get field definitions for a Projects V2 board.
110
+ */
111
+ async getProjectFields(projectId: string): Promise<ProjectField[]> {
112
+ const query = `query {
113
+ node(id: "${projectId}") {
114
+ ... on ProjectV2 {
115
+ fields(first: 50) {
116
+ nodes {
117
+ ... on ProjectV2Field { id name dataType }
118
+ ... on ProjectV2SingleSelectField {
119
+ id name dataType
120
+ options { id name }
121
+ }
122
+ }
123
+ }
124
+ }
125
+ }
126
+ }`;
127
+
128
+ const result = await this.executeGraphQL(query);
129
+ const fields = (result.data?.node as { fields?: { nodes: Array<{
130
+ id: string;
131
+ name: string;
132
+ dataType: string;
133
+ options?: Array<{ id: string; name: string }>;
134
+ }> } })?.fields?.nodes || [];
135
+
136
+ return fields.map((f) => {
137
+ const field: ProjectField = { id: f.id, name: f.name };
138
+ if (f.options && f.options.length > 0) {
139
+ field.options = f.options;
140
+ }
141
+ return field;
142
+ });
143
+ }
144
+
145
+ /**
146
+ * Execute a GraphQL query via `gh api graphql`.
147
+ */
148
+ private executeGraphQL(query: string): Promise<GraphQLResponse> {
149
+ return new Promise((resolve, reject) => {
150
+ const args = ['api', 'graphql', '-f', `query=${query}`];
151
+ const opts: { env?: NodeJS.ProcessEnv } = {};
152
+
153
+ if (this.token) {
154
+ opts.env = { ...process.env, GH_TOKEN: this.token };
155
+ }
156
+
157
+ execFile('gh', args, opts, (err, stdout, stderr) => {
158
+ if (err) {
159
+ reject(new Error(stderr || err.message));
160
+ return;
161
+ }
162
+
163
+ let parsed: GraphQLResponse;
164
+ try {
165
+ parsed = JSON.parse(stdout);
166
+ } catch {
167
+ reject(new Error(`Failed to parse GraphQL response: ${stdout.slice(0, 200)}`));
168
+ return;
169
+ }
170
+
171
+ if (parsed.errors && parsed.errors.length > 0) {
172
+ const messages = parsed.errors.map((e) => e.message).join('; ');
173
+ reject(new Error(`GraphQL error: ${messages}`));
174
+ return;
175
+ }
176
+
177
+ resolve(parsed);
178
+ });
179
+ });
180
+ }
181
+ }
@@ -0,0 +1,30 @@
1
+ function generateIssueBody(userStory) {
2
+ const lines = [];
3
+ lines.push("## Description");
4
+ lines.push("");
5
+ lines.push(userStory.description);
6
+ lines.push("");
7
+ lines.push(`**Priority**: ${userStory.priority}`);
8
+ lines.push("");
9
+ if (userStory.acceptanceCriteria.length > 0) {
10
+ lines.push("## Acceptance Criteria");
11
+ lines.push("");
12
+ lines.push("<!-- specweave:ac-start -->");
13
+ for (const ac of userStory.acceptanceCriteria) {
14
+ const checkbox = ac.completed ? "[x]" : "[ ]";
15
+ lines.push(`- ${checkbox} **${ac.id}**: ${ac.description}`);
16
+ }
17
+ lines.push("<!-- specweave:ac-end -->");
18
+ lines.push("");
19
+ }
20
+ const syncParts = [];
21
+ if (userStory.specId) {
22
+ syncParts.push(`spec=${userStory.specId}`);
23
+ }
24
+ syncParts.push(`us=${userStory.id}`);
25
+ lines.push(`<!-- specweave:sync ${syncParts.join(" ")} -->`);
26
+ return lines.join("\n");
27
+ }
28
+ export {
29
+ generateIssueBody
30
+ };
@@ -0,0 +1,76 @@
1
+ /**
2
+ * GitHub Issue Body Generator
3
+ *
4
+ * Generates GitHub issue body markdown from a User Story.
5
+ * Used by the spec-to-issue push sync to create rich issue content
6
+ * with AC checkboxes, priority badges, and sync markers.
7
+ *
8
+ * @module github-issue-body-generator
9
+ */
10
+
11
+ export interface UserStoryInput {
12
+ /** User story ID, e.g., "US-001" */
13
+ id: string;
14
+ /** User story title */
15
+ title: string;
16
+ /** User story description */
17
+ description: string;
18
+ /** Priority, e.g., "P1" */
19
+ priority: string;
20
+ /** Acceptance criteria */
21
+ acceptanceCriteria: Array<{
22
+ id: string;
23
+ description: string;
24
+ completed: boolean;
25
+ }>;
26
+ /** Spec ID, e.g., "spec-001" */
27
+ specId?: string;
28
+ /** Local file path to the spec */
29
+ specPath?: string;
30
+ }
31
+
32
+ /**
33
+ * Generate a GitHub issue body from a User Story input.
34
+ *
35
+ * Output format:
36
+ * - Description section
37
+ * - AC checkboxes between specweave markers
38
+ * - Priority badge
39
+ * - Sync footer marker
40
+ */
41
+ export function generateIssueBody(userStory: UserStoryInput): string {
42
+ const lines: string[] = [];
43
+
44
+ // Description
45
+ lines.push('## Description');
46
+ lines.push('');
47
+ lines.push(userStory.description);
48
+ lines.push('');
49
+
50
+ // Priority badge
51
+ lines.push(`**Priority**: ${userStory.priority}`);
52
+ lines.push('');
53
+
54
+ // Acceptance Criteria with markers
55
+ if (userStory.acceptanceCriteria.length > 0) {
56
+ lines.push('## Acceptance Criteria');
57
+ lines.push('');
58
+ lines.push('<!-- specweave:ac-start -->');
59
+ for (const ac of userStory.acceptanceCriteria) {
60
+ const checkbox = ac.completed ? '[x]' : '[ ]';
61
+ lines.push(`- ${checkbox} **${ac.id}**: ${ac.description}`);
62
+ }
63
+ lines.push('<!-- specweave:ac-end -->');
64
+ lines.push('');
65
+ }
66
+
67
+ // Sync footer marker
68
+ const syncParts: string[] = [];
69
+ if (userStory.specId) {
70
+ syncParts.push(`spec=${userStory.specId}`);
71
+ }
72
+ syncParts.push(`us=${userStory.id}`);
73
+ lines.push(`<!-- specweave:sync ${syncParts.join(' ')} -->`);
74
+
75
+ return lines.join('\n');
76
+ }
@@ -0,0 +1,55 @@
1
+ const AC_CHECKBOX_RE = /^-\s+\[([ xX])\]\s+\*\*(?<acId>AC-[A-Z0-9]+-\d+)\*\*:\s*(?<desc>.+)$/;
2
+ const SYNC_MARKER_RE = /<!--\s*specweave:sync\s+(?:spec=(?<specId>[^\s]+)\s+)?us=(?<usId>[^\s]+)\s*-->/;
3
+ function parseIssueBody(body) {
4
+ const result = {
5
+ acceptanceCriteria: {}
6
+ };
7
+ if (!body || !body.trim()) {
8
+ return result;
9
+ }
10
+ const lines = body.split("\n");
11
+ let inAcSection = false;
12
+ let foundAcSection = false;
13
+ for (const line of lines) {
14
+ if (/^##\s+Acceptance Criteria/i.test(line)) {
15
+ inAcSection = true;
16
+ foundAcSection = true;
17
+ continue;
18
+ }
19
+ if (inAcSection && /^##\s+/.test(line)) {
20
+ inAcSection = false;
21
+ continue;
22
+ }
23
+ if (inAcSection) {
24
+ const match = line.match(AC_CHECKBOX_RE);
25
+ if (match?.groups) {
26
+ result.acceptanceCriteria[match.groups.acId] = {
27
+ checked: match[1] !== " ",
28
+ description: match.groups.desc.trim()
29
+ };
30
+ }
31
+ }
32
+ }
33
+ if (!foundAcSection) {
34
+ for (const line of lines) {
35
+ const match = line.match(AC_CHECKBOX_RE);
36
+ if (match?.groups) {
37
+ result.acceptanceCriteria[match.groups.acId] = {
38
+ checked: match[1] !== " ",
39
+ description: match.groups.desc.trim()
40
+ };
41
+ }
42
+ }
43
+ }
44
+ const syncMatch = body.match(SYNC_MARKER_RE);
45
+ if (syncMatch?.groups?.usId) {
46
+ result.syncMarker = {
47
+ specId: syncMatch.groups.specId || "",
48
+ userStoryId: syncMatch.groups.usId
49
+ };
50
+ }
51
+ return result;
52
+ }
53
+ export {
54
+ parseIssueBody
55
+ };