specweave 1.0.351 → 1.0.352

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 (211) hide show
  1. package/bin/specweave.js +9 -0
  2. package/dist/plugins/specweave-ado/lib/ado-client-v2.d.ts +5 -0
  3. package/dist/plugins/specweave-ado/lib/ado-client-v2.d.ts.map +1 -1
  4. package/dist/plugins/specweave-ado/lib/ado-client-v2.js +61 -23
  5. package/dist/plugins/specweave-ado/lib/ado-client-v2.js.map +1 -1
  6. package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.d.ts.map +1 -1
  7. package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.js +3 -2
  8. package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.js.map +1 -1
  9. package/dist/plugins/specweave-ado/lib/ado-profile-resolver.d.ts.map +1 -1
  10. package/dist/plugins/specweave-ado/lib/ado-profile-resolver.js +2 -1
  11. package/dist/plugins/specweave-ado/lib/ado-profile-resolver.js.map +1 -1
  12. package/dist/plugins/specweave-ado/lib/ado-spec-sync.d.ts +1 -1
  13. package/dist/plugins/specweave-ado/lib/ado-spec-sync.d.ts.map +1 -1
  14. package/dist/plugins/specweave-ado/lib/ado-spec-sync.js +25 -9
  15. package/dist/plugins/specweave-ado/lib/ado-spec-sync.js.map +1 -1
  16. package/dist/plugins/specweave-ado/lib/conflict-resolver.d.ts.map +1 -1
  17. package/dist/plugins/specweave-ado/lib/conflict-resolver.js +17 -1
  18. package/dist/plugins/specweave-ado/lib/conflict-resolver.js.map +1 -1
  19. package/dist/plugins/specweave-ado/lib/per-us-sync.d.ts +3 -0
  20. package/dist/plugins/specweave-ado/lib/per-us-sync.d.ts.map +1 -1
  21. package/dist/plugins/specweave-ado/lib/per-us-sync.js +14 -1
  22. package/dist/plugins/specweave-ado/lib/per-us-sync.js.map +1 -1
  23. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
  24. package/dist/plugins/specweave-github/lib/github-client-v2.js +10 -7
  25. package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
  26. package/dist/plugins/specweave-github/lib/github-client.d.ts +1 -1
  27. package/dist/plugins/specweave-github/lib/github-client.d.ts.map +1 -1
  28. package/dist/plugins/specweave-github/lib/github-client.js +7 -5
  29. package/dist/plugins/specweave-github/lib/github-client.js.map +1 -1
  30. package/dist/plugins/specweave-github/lib/github-cross-repo-sync.js +13 -3
  31. package/dist/plugins/specweave-github/lib/github-cross-repo-sync.js.map +1 -1
  32. package/dist/plugins/specweave-github/lib/github-feature-sync-cli.d.ts +24 -1
  33. package/dist/plugins/specweave-github/lib/github-feature-sync-cli.d.ts.map +1 -1
  34. package/dist/plugins/specweave-github/lib/github-feature-sync-cli.js +36 -20
  35. package/dist/plugins/specweave-github/lib/github-feature-sync-cli.js.map +1 -1
  36. package/dist/plugins/specweave-github/lib/github-graphql-client.d.ts +1 -0
  37. package/dist/plugins/specweave-github/lib/github-graphql-client.d.ts.map +1 -1
  38. package/dist/plugins/specweave-github/lib/github-graphql-client.js +32 -22
  39. package/dist/plugins/specweave-github/lib/github-graphql-client.js.map +1 -1
  40. package/dist/plugins/specweave-github/lib/github-spec-frontmatter-updater.js +144 -8
  41. package/dist/plugins/specweave-github/lib/github-spec-frontmatter-updater.js.map +1 -1
  42. package/dist/plugins/specweave-github/lib/github-spec-sync.d.ts +8 -1
  43. package/dist/plugins/specweave-github/lib/github-spec-sync.d.ts.map +1 -1
  44. package/dist/plugins/specweave-github/lib/github-spec-sync.js +94 -24
  45. package/dist/plugins/specweave-github/lib/github-spec-sync.js.map +1 -1
  46. package/dist/plugins/specweave-github/lib/github-sync-orchestrator.d.ts +1 -0
  47. package/dist/plugins/specweave-github/lib/github-sync-orchestrator.d.ts.map +1 -1
  48. package/dist/plugins/specweave-github/lib/github-sync-orchestrator.js +2 -1
  49. package/dist/plugins/specweave-github/lib/github-sync-orchestrator.js.map +1 -1
  50. package/dist/plugins/specweave-github/lib/github-us-auto-closer.d.ts.map +1 -1
  51. package/dist/plugins/specweave-github/lib/github-us-auto-closer.js +25 -0
  52. package/dist/plugins/specweave-github/lib/github-us-auto-closer.js.map +1 -1
  53. package/dist/plugins/specweave-github/lib/per-us-sync.d.ts +3 -0
  54. package/dist/plugins/specweave-github/lib/per-us-sync.d.ts.map +1 -1
  55. package/dist/plugins/specweave-github/lib/per-us-sync.js +29 -9
  56. package/dist/plugins/specweave-github/lib/per-us-sync.js.map +1 -1
  57. package/dist/plugins/specweave-jira/lib/content-format-adapter.d.ts +59 -0
  58. package/dist/plugins/specweave-jira/lib/content-format-adapter.d.ts.map +1 -0
  59. package/dist/plugins/specweave-jira/lib/content-format-adapter.js +159 -0
  60. package/dist/plugins/specweave-jira/lib/content-format-adapter.js.map +1 -0
  61. package/dist/plugins/specweave-jira/lib/jira-deployment-detector.d.ts +45 -0
  62. package/dist/plugins/specweave-jira/lib/jira-deployment-detector.d.ts.map +1 -0
  63. package/dist/plugins/specweave-jira/lib/jira-deployment-detector.js +92 -0
  64. package/dist/plugins/specweave-jira/lib/jira-deployment-detector.js.map +1 -0
  65. package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.d.ts.map +1 -1
  66. package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.js +13 -28
  67. package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.js.map +1 -1
  68. package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts +2 -1
  69. package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts.map +1 -1
  70. package/dist/plugins/specweave-jira/lib/jira-epic-sync.js +19 -7
  71. package/dist/plugins/specweave-jira/lib/jira-epic-sync.js.map +1 -1
  72. package/dist/plugins/specweave-jira/lib/jira-field-discovery.d.ts +47 -0
  73. package/dist/plugins/specweave-jira/lib/jira-field-discovery.d.ts.map +1 -0
  74. package/dist/plugins/specweave-jira/lib/jira-field-discovery.js +110 -0
  75. package/dist/plugins/specweave-jira/lib/jira-field-discovery.js.map +1 -0
  76. package/dist/plugins/specweave-jira/lib/jira-paginated-search.d.ts +26 -0
  77. package/dist/plugins/specweave-jira/lib/jira-paginated-search.d.ts.map +1 -0
  78. package/dist/plugins/specweave-jira/lib/jira-paginated-search.js +77 -0
  79. package/dist/plugins/specweave-jira/lib/jira-paginated-search.js.map +1 -0
  80. package/dist/plugins/specweave-jira/lib/jira-spec-commit-sync.d.ts.map +1 -1
  81. package/dist/plugins/specweave-jira/lib/jira-spec-commit-sync.js +5 -3
  82. package/dist/plugins/specweave-jira/lib/jira-spec-commit-sync.js.map +1 -1
  83. package/dist/plugins/specweave-jira/lib/jira-spec-sync.d.ts +17 -2
  84. package/dist/plugins/specweave-jira/lib/jira-spec-sync.d.ts.map +1 -1
  85. package/dist/plugins/specweave-jira/lib/jira-spec-sync.js +103 -33
  86. package/dist/plugins/specweave-jira/lib/jira-spec-sync.js.map +1 -1
  87. package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts +4 -0
  88. package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts.map +1 -1
  89. package/dist/plugins/specweave-jira/lib/jira-status-sync.js +19 -6
  90. package/dist/plugins/specweave-jira/lib/jira-status-sync.js.map +1 -1
  91. package/dist/plugins/specweave-jira/lib/metadata-paths.d.ts +29 -0
  92. package/dist/plugins/specweave-jira/lib/metadata-paths.d.ts.map +1 -0
  93. package/dist/plugins/specweave-jira/lib/metadata-paths.js +73 -0
  94. package/dist/plugins/specweave-jira/lib/metadata-paths.js.map +1 -0
  95. package/dist/plugins/specweave-jira/lib/reorganization-detector.d.ts +15 -2
  96. package/dist/plugins/specweave-jira/lib/reorganization-detector.d.ts.map +1 -1
  97. package/dist/plugins/specweave-jira/lib/reorganization-detector.js +121 -33
  98. package/dist/plugins/specweave-jira/lib/reorganization-detector.js.map +1 -1
  99. package/dist/src/cli/commands/sync-progress.d.ts +6 -0
  100. package/dist/src/cli/commands/sync-progress.d.ts.map +1 -1
  101. package/dist/src/cli/commands/sync-progress.js +37 -0
  102. package/dist/src/cli/commands/sync-progress.js.map +1 -1
  103. package/dist/src/cli/commands/sync-task.d.ts +16 -0
  104. package/dist/src/cli/commands/sync-task.d.ts.map +1 -0
  105. package/dist/src/cli/commands/sync-task.js +42 -0
  106. package/dist/src/cli/commands/sync-task.js.map +1 -0
  107. package/dist/src/core/hooks/LifecycleHookDispatcher.d.ts +9 -1
  108. package/dist/src/core/hooks/LifecycleHookDispatcher.d.ts.map +1 -1
  109. package/dist/src/core/hooks/LifecycleHookDispatcher.js +26 -8
  110. package/dist/src/core/hooks/LifecycleHookDispatcher.js.map +1 -1
  111. package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
  112. package/dist/src/core/increment/metadata-manager.js +26 -0
  113. package/dist/src/core/increment/metadata-manager.js.map +1 -1
  114. package/dist/src/core/increment/status-commands.d.ts.map +1 -1
  115. package/dist/src/core/increment/status-commands.js +33 -11
  116. package/dist/src/core/increment/status-commands.js.map +1 -1
  117. package/dist/src/locales/de/cli.json +245 -77
  118. package/dist/src/locales/es/cli.json +238 -3
  119. package/dist/src/locales/fr/cli.json +254 -86
  120. package/dist/src/locales/ja/cli.json +246 -78
  121. package/dist/src/locales/ko/cli.json +246 -78
  122. package/dist/src/locales/pt/cli.json +245 -77
  123. package/dist/src/locales/zh/cli.json +251 -83
  124. package/dist/src/sync/github-reconciler.d.ts +30 -0
  125. package/dist/src/sync/github-reconciler.d.ts.map +1 -1
  126. package/dist/src/sync/github-reconciler.js +235 -1
  127. package/dist/src/sync/github-reconciler.js.map +1 -1
  128. package/package.json +1 -1
  129. package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +31 -0
  130. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +26 -0
  131. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
  132. package/plugins/specweave/lib/vendor/sync/github-reconciler.d.ts +30 -0
  133. package/plugins/specweave/lib/vendor/sync/github-reconciler.js +235 -1
  134. package/plugins/specweave/lib/vendor/sync/github-reconciler.js.map +1 -1
  135. package/plugins/specweave/skills/team-lead/agents/security.md +0 -1
  136. package/plugins/specweave-ado/hooks/post-task-completion.sh +2 -2
  137. package/plugins/specweave-ado/lib/ado-client-v2.js +51 -21
  138. package/plugins/specweave-ado/lib/ado-client-v2.ts +62 -23
  139. package/plugins/specweave-ado/lib/ado-duplicate-detector.js +4 -4
  140. package/plugins/specweave-ado/lib/ado-duplicate-detector.ts +4 -3
  141. package/plugins/specweave-ado/lib/ado-hierarchical-sync.js +54 -12
  142. package/plugins/specweave-ado/lib/ado-hierarchical-sync.ts +88 -18
  143. package/plugins/specweave-ado/lib/ado-profile-resolver.js +1 -1
  144. package/plugins/specweave-ado/lib/ado-profile-resolver.ts +3 -1
  145. package/plugins/specweave-ado/lib/ado-spec-sync.js +22 -9
  146. package/plugins/specweave-ado/lib/ado-spec-sync.ts +27 -9
  147. package/plugins/specweave-ado/lib/conflict-resolver.js +17 -1
  148. package/plugins/specweave-ado/lib/conflict-resolver.ts +17 -1
  149. package/plugins/specweave-ado/lib/enhanced-ado-sync.js +11 -1
  150. package/plugins/specweave-ado/lib/per-us-sync.js +8 -1
  151. package/plugins/specweave-ado/lib/per-us-sync.ts +17 -2
  152. package/plugins/specweave-github/hooks/github-auto-create-handler.sh +28 -2
  153. package/plugins/specweave-github/hooks/post-task-completion.sh +6 -3
  154. package/plugins/specweave-github/lib/enhanced-github-sync.js +35 -6
  155. package/plugins/specweave-github/lib/github-board-resolver.js +4 -4
  156. package/plugins/specweave-github/lib/github-board-resolver.ts +4 -4
  157. package/plugins/specweave-github/lib/github-client-v2.js +6 -6
  158. package/plugins/specweave-github/lib/github-client-v2.ts +11 -7
  159. package/plugins/specweave-github/lib/github-client.js +5 -4
  160. package/plugins/specweave-github/lib/github-client.ts +7 -5
  161. package/plugins/specweave-github/lib/github-cross-repo-sync.js +17 -3
  162. package/plugins/specweave-github/lib/github-cross-repo-sync.ts +16 -3
  163. package/plugins/specweave-github/lib/github-feature-sync-cli.js +20 -11
  164. package/plugins/specweave-github/lib/github-feature-sync-cli.ts +42 -20
  165. package/plugins/specweave-github/lib/github-graphql-client.js +29 -20
  166. package/plugins/specweave-github/lib/github-graphql-client.ts +34 -22
  167. package/plugins/specweave-github/lib/github-hierarchical-sync.js +2 -2
  168. package/plugins/specweave-github/lib/github-hierarchical-sync.ts +2 -2
  169. package/plugins/specweave-github/lib/github-multi-project-sync.js +23 -7
  170. package/plugins/specweave-github/lib/github-multi-project-sync.ts +26 -8
  171. package/plugins/specweave-github/lib/github-spec-frontmatter-updater.js +110 -5
  172. package/plugins/specweave-github/lib/github-spec-frontmatter-updater.ts +135 -9
  173. package/plugins/specweave-github/lib/github-spec-sync.js +85 -24
  174. package/plugins/specweave-github/lib/github-spec-sync.ts +100 -26
  175. package/plugins/specweave-github/lib/github-sync-orchestrator.js +2 -1
  176. package/plugins/specweave-github/lib/github-sync-orchestrator.ts +3 -1
  177. package/plugins/specweave-github/lib/github-us-auto-closer.js +25 -0
  178. package/plugins/specweave-github/lib/github-us-auto-closer.ts +43 -0
  179. package/plugins/specweave-github/lib/per-us-sync.js +26 -11
  180. package/plugins/specweave-github/lib/per-us-sync.ts +29 -11
  181. package/plugins/specweave-jira/hooks/post-task-completion.sh +2 -1
  182. package/plugins/specweave-jira/lib/content-format-adapter.js +116 -0
  183. package/plugins/specweave-jira/lib/content-format-adapter.ts +189 -0
  184. package/plugins/specweave-jira/lib/enhanced-jira-sync.js +21 -5
  185. package/plugins/specweave-jira/lib/jira-deployment-detector.js +63 -0
  186. package/plugins/specweave-jira/lib/jira-deployment-detector.ts +113 -0
  187. package/plugins/specweave-jira/lib/jira-duplicate-detector.js +12 -29
  188. package/plugins/specweave-jira/lib/jira-duplicate-detector.ts +13 -27
  189. package/plugins/specweave-jira/lib/jira-epic-sync.js +15 -5
  190. package/plugins/specweave-jira/lib/jira-epic-sync.ts +22 -7
  191. package/plugins/specweave-jira/lib/jira-field-discovery.js +76 -0
  192. package/plugins/specweave-jira/lib/jira-field-discovery.ts +139 -0
  193. package/plugins/specweave-jira/lib/jira-hierarchical-sync.js +10 -0
  194. package/plugins/specweave-jira/lib/jira-hierarchical-sync.ts +11 -0
  195. package/plugins/specweave-jira/lib/jira-multi-project-sync.js +19 -9
  196. package/plugins/specweave-jira/lib/jira-multi-project-sync.ts +25 -14
  197. package/plugins/specweave-jira/lib/jira-paginated-search.js +55 -0
  198. package/plugins/specweave-jira/lib/jira-paginated-search.ts +108 -0
  199. package/plugins/specweave-jira/lib/jira-spec-commit-sync.js +5 -3
  200. package/plugins/specweave-jira/lib/jira-spec-commit-sync.ts +5 -3
  201. package/plugins/specweave-jira/lib/jira-spec-sync.js +102 -31
  202. package/plugins/specweave-jira/lib/jira-spec-sync.ts +123 -45
  203. package/plugins/specweave-jira/lib/jira-status-sync.js +18 -5
  204. package/plugins/specweave-jira/lib/jira-status-sync.ts +21 -6
  205. package/plugins/specweave-jira/lib/metadata-paths.js +38 -0
  206. package/plugins/specweave-jira/lib/metadata-paths.ts +73 -0
  207. package/plugins/specweave-jira/lib/reorganization-detector.js +101 -23
  208. package/plugins/specweave-jira/lib/reorganization-detector.ts +125 -35
  209. package/plugins/specweave-jira/scripts/refresh-cache.js +1 -1
  210. package/plugins/specweave-jira/scripts/refresh-cache.ts +2 -2
  211. package/plugins/specweave-jira/skills/jira-resource-validator/SKILL.md +3 -5
@@ -32,17 +32,17 @@ export class GitHubGraphQLClient {
32
32
  * Tries user first, falls back to organization.
33
33
  */
34
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);
35
+ // Try user query first (parameterized)
36
+ const userQuery = `query($login: String!) { user(login: $login) { id } }`;
37
+ const userResult = await this.executeGraphQL(userQuery, { login });
38
38
 
39
39
  if (userResult.data?.user && (userResult.data.user as { id?: string }).id) {
40
40
  return (userResult.data.user as { id: string }).id;
41
41
  }
42
42
 
43
- // Fallback to organization
44
- const orgQuery = `query { organization(login: "${login}") { id } }`;
45
- const orgResult = await this.executeGraphQL(orgQuery);
43
+ // Fallback to organization (parameterized)
44
+ const orgQuery = `query($login: String!) { organization(login: $login) { id } }`;
45
+ const orgResult = await this.executeGraphQL(orgQuery, { login });
46
46
 
47
47
  if (orgResult.data?.organization && (orgResult.data.organization as { id?: string }).id) {
48
48
  return (orgResult.data.organization as { id: string }).id;
@@ -55,13 +55,13 @@ export class GitHubGraphQLClient {
55
55
  * Create a new GitHub Projects V2 board.
56
56
  */
57
57
  async createProjectV2(ownerId: string, title: string): Promise<{ id: string; number: number }> {
58
- const query = `mutation {
59
- createProjectV2(input: { ownerId: "${ownerId}", title: "${title}" }) {
58
+ const query = `mutation($ownerId: ID!, $title: String!) {
59
+ createProjectV2(input: { ownerId: $ownerId, title: $title }) {
60
60
  projectV2 { id number }
61
61
  }
62
62
  }`;
63
63
 
64
- const result = await this.executeGraphQL(query);
64
+ const result = await this.executeGraphQL(query, { ownerId, title });
65
65
  const project = (result.data?.createProjectV2 as { projectV2: { id: string; number: number } })?.projectV2;
66
66
  return { id: project.id, number: project.number };
67
67
  }
@@ -71,13 +71,13 @@ export class GitHubGraphQLClient {
71
71
  * Returns the project item ID. Idempotent.
72
72
  */
73
73
  async addProjectV2Item(projectId: string, contentId: string): Promise<string> {
74
- const query = `mutation {
75
- addProjectV2ItemById(input: { projectId: "${projectId}", contentId: "${contentId}" }) {
74
+ const query = `mutation($projectId: ID!, $contentId: ID!) {
75
+ addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) {
76
76
  item { id }
77
77
  }
78
78
  }`;
79
79
 
80
- const result = await this.executeGraphQL(query);
80
+ const result = await this.executeGraphQL(query, { projectId, contentId });
81
81
  const item = (result.data?.addProjectV2ItemById as { item: { id: string } })?.item;
82
82
  return item.id;
83
83
  }
@@ -91,26 +91,31 @@ export class GitHubGraphQLClient {
91
91
  fieldId: string,
92
92
  value: { singleSelectOptionId: string },
93
93
  ): Promise<void> {
94
- const query = `mutation {
94
+ const query = `mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
95
95
  updateProjectV2ItemFieldValue(input: {
96
- projectId: "${projectId}",
97
- itemId: "${itemId}",
98
- fieldId: "${fieldId}",
99
- value: { singleSelectOptionId: "${value.singleSelectOptionId}" }
96
+ projectId: $projectId,
97
+ itemId: $itemId,
98
+ fieldId: $fieldId,
99
+ value: { singleSelectOptionId: $optionId }
100
100
  }) {
101
101
  projectV2Item { id }
102
102
  }
103
103
  }`;
104
104
 
105
- await this.executeGraphQL(query);
105
+ await this.executeGraphQL(query, {
106
+ projectId,
107
+ itemId,
108
+ fieldId,
109
+ optionId: value.singleSelectOptionId,
110
+ });
106
111
  }
107
112
 
108
113
  /**
109
114
  * Get field definitions for a Projects V2 board.
110
115
  */
111
116
  async getProjectFields(projectId: string): Promise<ProjectField[]> {
112
- const query = `query {
113
- node(id: "${projectId}") {
117
+ const query = `query($nodeId: ID!) {
118
+ node(id: $nodeId) {
114
119
  ... on ProjectV2 {
115
120
  fields(first: 50) {
116
121
  nodes {
@@ -125,7 +130,7 @@ export class GitHubGraphQLClient {
125
130
  }
126
131
  }`;
127
132
 
128
- const result = await this.executeGraphQL(query);
133
+ const result = await this.executeGraphQL(query, { nodeId: projectId });
129
134
  const fields = (result.data?.node as { fields?: { nodes: Array<{
130
135
  id: string;
131
136
  name: string;
@@ -144,10 +149,17 @@ export class GitHubGraphQLClient {
144
149
 
145
150
  /**
146
151
  * Execute a GraphQL query via `gh api graphql`.
152
+ * All variables are passed via `-F` flags to prevent injection.
147
153
  */
148
- private executeGraphQL(query: string): Promise<GraphQLResponse> {
154
+ private executeGraphQL(query: string, variables: Record<string, string> = {}): Promise<GraphQLResponse> {
149
155
  return new Promise((resolve, reject) => {
150
156
  const args = ['api', 'graphql', '-f', `query=${query}`];
157
+
158
+ // Pass all variables as structured parameters via -F
159
+ for (const [key, value] of Object.entries(variables)) {
160
+ args.push('-F', `${key}=${value}`);
161
+ }
162
+
151
163
  const opts: { env?: NodeJS.ProcessEnv } = {};
152
164
 
153
165
  if (this.token) {
@@ -156,8 +156,8 @@ async function executeSearch(query) {
156
156
  "1000"
157
157
  // Max results
158
158
  ], { env: getGhEnv() });
159
- if (result.status !== 0) {
160
- throw new Error(`Failed to search issues: ${result.stderr || result.stdout}`);
159
+ if (result.exitCode !== 0) {
160
+ throw new Error(`Failed to search issues (exit code ${result.exitCode}): ${result.stderr || result.stdout}`);
161
161
  }
162
162
  if (!result.stdout.trim()) {
163
163
  return [];
@@ -317,8 +317,8 @@ async function executeSearch(query: string): Promise<GitHubIssue[]> {
317
317
  '1000', // Max results
318
318
  ], { env: getGhEnv() });
319
319
 
320
- if (result.status !== 0) {
321
- throw new Error(`Failed to search issues: ${result.stderr || result.stdout}`);
320
+ if (result.exitCode !== 0) {
321
+ throw new Error(`Failed to search issues (exit code ${result.exitCode}): ${result.stderr || result.stdout}`);
322
322
  }
323
323
 
324
324
  if (!result.stdout.trim()) {
@@ -251,13 +251,29 @@ ${userStory.technicalContext}
251
251
 
252
252
  \u{1F916} Auto-generated by SpecWeave
253
253
  `;
254
- const existingIssues = await this.octokit.issues.listForRepo({
255
- owner: this.config.owner,
256
- repo,
257
- labels: "specweave",
258
- state: "all"
259
- });
260
- const existing = existingIssues.data.find((issue) => issue.title === title);
254
+ let existing;
255
+ const seenNumbers = /* @__PURE__ */ new Set();
256
+ let page = 1;
257
+ while (!existing) {
258
+ const existingIssues = await this.octokit.issues.listForRepo({
259
+ owner: this.config.owner,
260
+ repo,
261
+ labels: "specweave",
262
+ state: "all",
263
+ per_page: 100,
264
+ page
265
+ });
266
+ for (const issue of existingIssues.data) {
267
+ if (seenNumbers.has(issue.number)) continue;
268
+ seenNumbers.add(issue.number);
269
+ if (issue.title === title) {
270
+ existing = { number: issue.number, title: issue.title };
271
+ break;
272
+ }
273
+ }
274
+ if (existingIssues.data.length < 100) break;
275
+ page++;
276
+ }
261
277
  if (existing) {
262
278
  const response = await this.octokit.issues.update({
263
279
  owner: this.config.owner,
@@ -356,15 +356,33 @@ ${userStory.technicalContext ? `\n## Technical Context\n\n${userStory.technicalC
356
356
  🤖 Auto-generated by SpecWeave
357
357
  `;
358
358
 
359
- // Check if issue already exists (search by title)
360
- const existingIssues = await this.octokit.issues.listForRepo({
361
- owner: this.config.owner,
362
- repo,
363
- labels: 'specweave',
364
- state: 'all'
365
- });
359
+ // Check if issue already exists (search by title, paginated)
360
+ let existing: { number: number; title: string } | undefined;
361
+ const seenNumbers = new Set<number>();
362
+ let page = 1;
363
+
364
+ while (!existing) {
365
+ const existingIssues = await this.octokit.issues.listForRepo({
366
+ owner: this.config.owner,
367
+ repo,
368
+ labels: 'specweave',
369
+ state: 'all',
370
+ per_page: 100,
371
+ page,
372
+ });
366
373
 
367
- const existing = existingIssues.data.find(issue => issue.title === title);
374
+ for (const issue of existingIssues.data) {
375
+ if (seenNumbers.has(issue.number)) continue;
376
+ seenNumbers.add(issue.number);
377
+ if (issue.title === title) {
378
+ existing = { number: issue.number, title: issue.title };
379
+ break;
380
+ }
381
+ }
382
+
383
+ if (existingIssues.data.length < 100) break;
384
+ page++;
385
+ }
368
386
 
369
387
  if (existing) {
370
388
  // Update existing issue
@@ -60,10 +60,38 @@ function parseYamlSimple(yaml) {
60
60
  const stack = [
61
61
  { obj: result, indent: -1 }
62
62
  ];
63
- for (const line of lines) {
63
+ for (let i = 0; i < lines.length; i++) {
64
+ const line = lines[i];
64
65
  if (!line.trim() || line.trim().startsWith("#")) continue;
65
66
  const indent = line.search(/\S/);
66
67
  const trimmed = line.trim();
68
+ if (trimmed.startsWith("- ")) {
69
+ while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
70
+ stack.pop();
71
+ }
72
+ const parent2 = stack[stack.length - 1];
73
+ const key2 = parent2.currentKey;
74
+ if (key2 && parent2.obj[key2] !== void 0) {
75
+ const arr = parent2.obj[key2];
76
+ if (Array.isArray(arr)) {
77
+ const itemText = trimmed.slice(2).trim();
78
+ const nestedColonIdx = itemText.indexOf(":");
79
+ if (nestedColonIdx > 0 && !itemText.startsWith('"') && !itemText.startsWith("'")) {
80
+ const nestedKey = itemText.slice(0, nestedColonIdx).trim();
81
+ const nestedVal = itemText.slice(nestedColonIdx + 1).trim();
82
+ if (nestedVal) {
83
+ const obj = { [nestedKey]: parseYamlValue(nestedVal) };
84
+ arr.push(obj);
85
+ } else {
86
+ arr.push(parseYamlValue(itemText));
87
+ }
88
+ } else {
89
+ arr.push(parseYamlValue(itemText));
90
+ }
91
+ }
92
+ }
93
+ continue;
94
+ }
67
95
  const colonIdx = trimmed.indexOf(":");
68
96
  if (colonIdx === -1) continue;
69
97
  const key = trimmed.slice(0, colonIdx).trim();
@@ -73,15 +101,60 @@ function parseYamlSimple(yaml) {
73
101
  }
74
102
  const parent = stack[stack.length - 1].obj;
75
103
  if (rawValue === "" || rawValue === void 0) {
76
- const child = {};
77
- parent[key] = child;
78
- stack.push({ obj: child, indent });
104
+ const nextIdx = findNextNonEmptyLine(lines, i + 1);
105
+ if (nextIdx !== -1 && lines[nextIdx].trim().startsWith("- ")) {
106
+ parent[key] = [];
107
+ stack.push({ obj: parent, indent, currentKey: key });
108
+ } else {
109
+ const child = {};
110
+ parent[key] = child;
111
+ stack.push({ obj: child, indent });
112
+ }
113
+ } else if (rawValue.startsWith("[") && rawValue.endsWith("]")) {
114
+ parent[key] = parseFlowArray(rawValue);
79
115
  } else {
80
116
  parent[key] = parseYamlValue(rawValue);
81
117
  }
82
118
  }
83
119
  return result;
84
120
  }
121
+ function findNextNonEmptyLine(lines, start) {
122
+ for (let i = start; i < lines.length; i++) {
123
+ const trimmed = lines[i].trim();
124
+ if (trimmed && !trimmed.startsWith("#")) return i;
125
+ }
126
+ return -1;
127
+ }
128
+ function parseFlowArray(raw) {
129
+ const inner = raw.slice(1, -1).trim();
130
+ if (!inner) return [];
131
+ const items = [];
132
+ let current = "";
133
+ let inQuote = false;
134
+ let quoteChar = "";
135
+ for (let i = 0; i < inner.length; i++) {
136
+ const ch = inner[i];
137
+ if (inQuote) {
138
+ if (ch === quoteChar) {
139
+ inQuote = false;
140
+ }
141
+ current += ch;
142
+ } else if (ch === '"' || ch === "'") {
143
+ inQuote = true;
144
+ quoteChar = ch;
145
+ current += ch;
146
+ } else if (ch === ",") {
147
+ items.push(parseYamlValue(current.trim()));
148
+ current = "";
149
+ } else {
150
+ current += ch;
151
+ }
152
+ }
153
+ if (current.trim()) {
154
+ items.push(parseYamlValue(current.trim()));
155
+ }
156
+ return items;
157
+ }
85
158
  function parseYamlValue(raw) {
86
159
  if (raw === "null") return null;
87
160
  if (raw === "true") return true;
@@ -99,7 +172,39 @@ function stringifyYaml(obj, indent = 0) {
99
172
  for (const [key, value] of Object.entries(obj)) {
100
173
  if (value === null || value === void 0) {
101
174
  parts.push(`${prefix}${key}: null`);
102
- } else if (typeof value === "object" && !Array.isArray(value)) {
175
+ } else if (Array.isArray(value)) {
176
+ if (value.length === 0) {
177
+ parts.push(`${prefix}${key}: []`);
178
+ } else if (value.every((v) => typeof v !== "object" || v === null)) {
179
+ parts.push(`${prefix}${key}:`);
180
+ for (const item of value) {
181
+ if (typeof item === "string") {
182
+ parts.push(`${prefix} - "${item}"`);
183
+ } else {
184
+ parts.push(`${prefix} - ${item}`);
185
+ }
186
+ }
187
+ } else {
188
+ parts.push(`${prefix}${key}:`);
189
+ for (const item of value) {
190
+ if (typeof item === "object" && item !== null) {
191
+ const entries = Object.entries(item);
192
+ if (entries.length > 0) {
193
+ const [firstKey, firstVal] = entries[0];
194
+ const formattedVal = typeof firstVal === "string" ? `"${firstVal}"` : firstVal;
195
+ parts.push(`${prefix} - ${firstKey}: ${formattedVal}`);
196
+ for (let j = 1; j < entries.length; j++) {
197
+ const [k, v] = entries[j];
198
+ const fv = typeof v === "string" ? `"${v}"` : v;
199
+ parts.push(`${prefix} ${k}: ${fv}`);
200
+ }
201
+ }
202
+ } else {
203
+ parts.push(`${prefix} - ${item}`);
204
+ }
205
+ }
206
+ }
207
+ } else if (typeof value === "object") {
103
208
  parts.push(`${prefix}${key}:`);
104
209
  parts.push(stringifyYaml(value, indent + 1));
105
210
  } else if (typeof value === "string") {
@@ -101,20 +101,55 @@ export async function updateSpecFrontmatter(
101
101
 
102
102
  /**
103
103
  * Simple YAML parser for spec frontmatter.
104
- * Handles nested objects, strings, numbers, booleans, null.
104
+ * Handles nested objects, strings, numbers, booleans, null, and arrays.
105
+ * Supports block arrays (`- item`) and flow arrays (`[a, b, c]`).
105
106
  */
106
107
  function parseYamlSimple(yaml: string): Record<string, unknown> {
107
108
  const result: Record<string, unknown> = {};
108
109
  const lines = yaml.split('\n');
109
- const stack: Array<{ obj: Record<string, unknown>; indent: number }> = [
110
+ const stack: Array<{ obj: Record<string, unknown>; indent: number; currentKey?: string }> = [
110
111
  { obj: result, indent: -1 },
111
112
  ];
112
113
 
113
- for (const line of lines) {
114
+ for (let i = 0; i < lines.length; i++) {
115
+ const line = lines[i];
114
116
  if (!line.trim() || line.trim().startsWith('#')) continue;
115
117
 
116
118
  const indent = line.search(/\S/);
117
119
  const trimmed = line.trim();
120
+
121
+ // Block array item: `- value` or `- key: value` (nested object in array)
122
+ if (trimmed.startsWith('- ')) {
123
+ // Find the parent that owns this array
124
+ while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
125
+ stack.pop();
126
+ }
127
+ const parent = stack[stack.length - 1];
128
+ const key = parent.currentKey;
129
+ if (key && parent.obj[key] !== undefined) {
130
+ const arr = parent.obj[key];
131
+ if (Array.isArray(arr)) {
132
+ const itemText = trimmed.slice(2).trim();
133
+ // Check if it's a nested object item (- key: value)
134
+ const nestedColonIdx = itemText.indexOf(':');
135
+ if (nestedColonIdx > 0 && !itemText.startsWith('"') && !itemText.startsWith("'")) {
136
+ const nestedKey = itemText.slice(0, nestedColonIdx).trim();
137
+ const nestedVal = itemText.slice(nestedColonIdx + 1).trim();
138
+ if (nestedVal) {
139
+ // Simple key:value object item
140
+ const obj: Record<string, unknown> = { [nestedKey]: parseYamlValue(nestedVal) };
141
+ arr.push(obj);
142
+ } else {
143
+ arr.push(parseYamlValue(itemText));
144
+ }
145
+ } else {
146
+ arr.push(parseYamlValue(itemText));
147
+ }
148
+ }
149
+ }
150
+ continue;
151
+ }
152
+
118
153
  const colonIdx = trimmed.indexOf(':');
119
154
  if (colonIdx === -1) continue;
120
155
 
@@ -129,10 +164,21 @@ function parseYamlSimple(yaml: string): Record<string, unknown> {
129
164
  const parent = stack[stack.length - 1].obj;
130
165
 
131
166
  if (rawValue === '' || rawValue === undefined) {
132
- // Nested object
133
- const child: Record<string, unknown> = {};
134
- parent[key] = child;
135
- stack.push({ obj: child, indent });
167
+ // Check if next non-empty line is a block array item
168
+ const nextIdx = findNextNonEmptyLine(lines, i + 1);
169
+ if (nextIdx !== -1 && lines[nextIdx].trim().startsWith('- ')) {
170
+ // This key holds an array
171
+ parent[key] = [] as unknown[];
172
+ stack.push({ obj: parent, indent, currentKey: key });
173
+ } else {
174
+ // Nested object
175
+ const child: Record<string, unknown> = {};
176
+ parent[key] = child;
177
+ stack.push({ obj: child, indent });
178
+ }
179
+ } else if (rawValue.startsWith('[') && rawValue.endsWith(']')) {
180
+ // Flow array: [a, b, c]
181
+ parent[key] = parseFlowArray(rawValue);
136
182
  } else {
137
183
  parent[key] = parseYamlValue(rawValue);
138
184
  }
@@ -141,6 +187,52 @@ function parseYamlSimple(yaml: string): Record<string, unknown> {
141
187
  return result;
142
188
  }
143
189
 
190
+ /**
191
+ * Find the next non-empty, non-comment line index.
192
+ */
193
+ function findNextNonEmptyLine(lines: string[], start: number): number {
194
+ for (let i = start; i < lines.length; i++) {
195
+ const trimmed = lines[i].trim();
196
+ if (trimmed && !trimmed.startsWith('#')) return i;
197
+ }
198
+ return -1;
199
+ }
200
+
201
+ /**
202
+ * Parse a YAML flow array like [a, b, c] or ["a", "b"]
203
+ */
204
+ function parseFlowArray(raw: string): unknown[] {
205
+ const inner = raw.slice(1, -1).trim();
206
+ if (!inner) return [];
207
+ // Split on commas, respecting quoted strings
208
+ const items: unknown[] = [];
209
+ let current = '';
210
+ let inQuote = false;
211
+ let quoteChar = '';
212
+ for (let i = 0; i < inner.length; i++) {
213
+ const ch = inner[i];
214
+ if (inQuote) {
215
+ if (ch === quoteChar) {
216
+ inQuote = false;
217
+ }
218
+ current += ch;
219
+ } else if (ch === '"' || ch === "'") {
220
+ inQuote = true;
221
+ quoteChar = ch;
222
+ current += ch;
223
+ } else if (ch === ',') {
224
+ items.push(parseYamlValue(current.trim()));
225
+ current = '';
226
+ } else {
227
+ current += ch;
228
+ }
229
+ }
230
+ if (current.trim()) {
231
+ items.push(parseYamlValue(current.trim()));
232
+ }
233
+ return items;
234
+ }
235
+
144
236
  function parseYamlValue(raw: string): unknown {
145
237
  if (raw === 'null') return null;
146
238
  if (raw === 'true') return true;
@@ -155,7 +247,7 @@ function parseYamlValue(raw: string): unknown {
155
247
  }
156
248
 
157
249
  /**
158
- * Simple YAML stringifier.
250
+ * Simple YAML stringifier with array support.
159
251
  */
160
252
  function stringifyYaml(obj: Record<string, unknown>, indent = 0): string {
161
253
  const prefix = ' '.repeat(indent);
@@ -164,7 +256,41 @@ function stringifyYaml(obj: Record<string, unknown>, indent = 0): string {
164
256
  for (const [key, value] of Object.entries(obj)) {
165
257
  if (value === null || value === undefined) {
166
258
  parts.push(`${prefix}${key}: null`);
167
- } else if (typeof value === 'object' && !Array.isArray(value)) {
259
+ } else if (Array.isArray(value)) {
260
+ if (value.length === 0) {
261
+ parts.push(`${prefix}${key}: []`);
262
+ } else if (value.every(v => typeof v !== 'object' || v === null)) {
263
+ // Simple array — use block style
264
+ parts.push(`${prefix}${key}:`);
265
+ for (const item of value) {
266
+ if (typeof item === 'string') {
267
+ parts.push(`${prefix} - "${item}"`);
268
+ } else {
269
+ parts.push(`${prefix} - ${item}`);
270
+ }
271
+ }
272
+ } else {
273
+ // Array of objects — use block style with nested keys
274
+ parts.push(`${prefix}${key}:`);
275
+ for (const item of value) {
276
+ if (typeof item === 'object' && item !== null) {
277
+ const entries = Object.entries(item as Record<string, unknown>);
278
+ if (entries.length > 0) {
279
+ const [firstKey, firstVal] = entries[0];
280
+ const formattedVal = typeof firstVal === 'string' ? `"${firstVal}"` : firstVal;
281
+ parts.push(`${prefix} - ${firstKey}: ${formattedVal}`);
282
+ for (let j = 1; j < entries.length; j++) {
283
+ const [k, v] = entries[j];
284
+ const fv = typeof v === 'string' ? `"${v}"` : v;
285
+ parts.push(`${prefix} ${k}: ${fv}`);
286
+ }
287
+ }
288
+ } else {
289
+ parts.push(`${prefix} - ${item}`);
290
+ }
291
+ }
292
+ }
293
+ } else if (typeof value === 'object') {
168
294
  parts.push(`${prefix}${key}:`);
169
295
  parts.push(stringifyYaml(value as Record<string, unknown>, indent + 1));
170
296
  } else if (typeof value === 'string') {