specweave 0.9.1 → 0.10.1

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 (75) hide show
  1. package/CLAUDE.md +153 -13
  2. package/README.md +97 -251
  3. package/bin/install-agents.sh +1 -1
  4. package/bin/install-commands.sh +1 -1
  5. package/bin/install-hooks.sh +1 -1
  6. package/bin/install-skills.sh +1 -1
  7. package/bin/specweave.js +32 -0
  8. package/dist/cli/commands/init.d.ts.map +1 -1
  9. package/dist/cli/commands/init.js +29 -1
  10. package/dist/cli/commands/init.js.map +1 -1
  11. package/dist/cli/commands/validate-jira.d.ts +35 -0
  12. package/dist/cli/commands/validate-jira.d.ts.map +1 -0
  13. package/dist/cli/commands/validate-jira.js +112 -0
  14. package/dist/cli/commands/validate-jira.js.map +1 -0
  15. package/dist/cli/commands/validate-plugins.d.ts +41 -0
  16. package/dist/cli/commands/validate-plugins.d.ts.map +1 -0
  17. package/dist/cli/commands/validate-plugins.js +171 -0
  18. package/dist/cli/commands/validate-plugins.js.map +1 -0
  19. package/dist/core/types/sync-profile.d.ts +177 -29
  20. package/dist/core/types/sync-profile.d.ts.map +1 -1
  21. package/dist/core/types/sync-profile.js +48 -1
  22. package/dist/core/types/sync-profile.js.map +1 -1
  23. package/dist/hooks/lib/translate-living-docs.d.ts.map +1 -1
  24. package/dist/hooks/lib/translate-living-docs.js +16 -7
  25. package/dist/hooks/lib/translate-living-docs.js.map +1 -1
  26. package/dist/metrics/dora-calculator.d.ts +7 -3
  27. package/dist/metrics/dora-calculator.d.ts.map +1 -1
  28. package/dist/metrics/dora-calculator.js +19 -6
  29. package/dist/metrics/dora-calculator.js.map +1 -1
  30. package/dist/metrics/report-generator.d.ts +17 -0
  31. package/dist/metrics/report-generator.d.ts.map +1 -0
  32. package/dist/metrics/report-generator.js +403 -0
  33. package/dist/metrics/report-generator.js.map +1 -0
  34. package/dist/utils/external-resource-validator.d.ts +102 -0
  35. package/dist/utils/external-resource-validator.d.ts.map +1 -0
  36. package/dist/utils/external-resource-validator.js +400 -0
  37. package/dist/utils/external-resource-validator.js.map +1 -0
  38. package/dist/utils/plugin-validator.d.ts +161 -0
  39. package/dist/utils/plugin-validator.d.ts.map +1 -0
  40. package/dist/utils/plugin-validator.js +565 -0
  41. package/dist/utils/plugin-validator.js.map +1 -0
  42. package/package.json +2 -1
  43. package/plugins/specweave/commands/specweave-do.md +47 -0
  44. package/plugins/specweave/commands/specweave-increment.md +82 -0
  45. package/plugins/specweave/commands/specweave-next.md +47 -0
  46. package/plugins/specweave/hooks/post-increment-planning.sh +117 -38
  47. package/plugins/specweave/hooks/pre-tool-use.sh +133 -0
  48. package/plugins/specweave/plugin.json +22 -0
  49. package/plugins/specweave/skills/SKILLS-INDEX.md +23 -2
  50. package/plugins/specweave/skills/plugin-installer/SKILL.md +340 -0
  51. package/plugins/specweave/skills/plugin-validator/SKILL.md +427 -0
  52. package/plugins/specweave-ado/.claude-plugin/plugin.json +2 -4
  53. package/plugins/specweave-ado/lib/ado-board-resolver.ts +328 -0
  54. package/plugins/specweave-ado/lib/ado-hierarchical-sync.ts +484 -0
  55. package/plugins/specweave-ado/plugin.json +20 -0
  56. package/plugins/specweave-alternatives/.claude-plugin/plugin.json +15 -2
  57. package/plugins/specweave-backend/.claude-plugin/plugin.json +15 -2
  58. package/plugins/specweave-cost-optimizer/.claude-plugin/plugin.json +14 -2
  59. package/plugins/specweave-diagrams/.claude-plugin/plugin.json +14 -2
  60. package/plugins/specweave-docs/.claude-plugin/plugin.json +13 -2
  61. package/plugins/specweave-figma/.claude-plugin/plugin.json +14 -2
  62. package/plugins/specweave-frontend/.claude-plugin/plugin.json +15 -2
  63. package/plugins/specweave-github/lib/github-board-resolver.ts +164 -0
  64. package/plugins/specweave-github/lib/github-hierarchical-sync.ts +344 -0
  65. package/plugins/specweave-github/plugin.json +19 -0
  66. package/plugins/specweave-infrastructure/.claude-plugin/plugin.json +15 -2
  67. package/plugins/specweave-jira/.claude-plugin/plugin.json +14 -2
  68. package/plugins/specweave-jira/lib/jira-board-resolver.ts +127 -0
  69. package/plugins/specweave-jira/lib/jira-hierarchical-sync.ts +283 -0
  70. package/plugins/specweave-jira/plugin.json +20 -0
  71. package/plugins/specweave-jira/skills/jira-resource-validator/SKILL.md +647 -0
  72. package/plugins/specweave-kubernetes/.claude-plugin/plugin.json +14 -2
  73. package/plugins/specweave-payments/.claude-plugin/plugin.json +14 -2
  74. package/plugins/specweave-testing/.claude-plugin/plugin.json +14 -2
  75. package/plugins/specweave-tooling/.claude-plugin/plugin.json +13 -2
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Jira Hierarchical Sync Implementation
3
+ *
4
+ * Supports three sync strategies:
5
+ * 1. Simple: One project, all boards (backward compatible)
6
+ * 2. Filtered: Multiple projects + specific boards + filters
7
+ * 3. Custom: Raw JQL query
8
+ */
9
+
10
+ import {
11
+ SyncProfile,
12
+ SyncContainer,
13
+ JiraConfig,
14
+ isSimpleStrategy,
15
+ isFilteredStrategy,
16
+ isCustomStrategy,
17
+ } from '../../../src/core/types/sync-profile.js';
18
+ import { JiraClient, JiraIssue } from '../../../src/integrations/jira/jira-client.js';
19
+ import { getBoardIds } from './jira-board-resolver.js';
20
+
21
+ /**
22
+ * Build hierarchical JQL query from containers
23
+ *
24
+ * Example output:
25
+ * (project=PROJECT-A AND board IN (123, 456) AND labels IN (feature))
26
+ * OR
27
+ * (project=PROJECT-B AND board IN (789))
28
+ *
29
+ * @param client JiraClient instance
30
+ * @param containers Array of containers with projects + boards + filters
31
+ * @returns JQL query string
32
+ */
33
+ export async function buildHierarchicalJQL(
34
+ client: JiraClient,
35
+ containers: SyncContainer[]
36
+ ): Promise<string> {
37
+ const clauses: string[] = [];
38
+
39
+ for (const container of containers) {
40
+ const parts: string[] = [];
41
+
42
+ // Project clause
43
+ parts.push(`project=${container.id}`);
44
+
45
+ // Board filtering (if sub-organizations specified)
46
+ if (container.subOrganizations && container.subOrganizations.length > 0) {
47
+ try {
48
+ const boardIds = await getBoardIds(
49
+ client,
50
+ container.id,
51
+ container.subOrganizations
52
+ );
53
+
54
+ if (boardIds.length > 0) {
55
+ parts.push(`board IN (${boardIds.join(',')})`);
56
+ } else {
57
+ console.warn(
58
+ `⚠️ No valid boards found for project ${container.id}, syncing all boards`
59
+ );
60
+ }
61
+ } catch (error) {
62
+ console.warn(
63
+ `⚠️ Failed to resolve boards for ${container.id}, syncing all boards:`,
64
+ (error as Error).message
65
+ );
66
+ }
67
+ }
68
+
69
+ // Filters
70
+ if (container.filters) {
71
+ const filterClauses = buildFilterClauses(container.filters);
72
+ parts.push(...filterClauses);
73
+ }
74
+
75
+ // Combine parts with AND
76
+ clauses.push(`(${parts.join(' AND ')})`);
77
+ }
78
+
79
+ // Combine clauses with OR
80
+ return clauses.join(' OR ');
81
+ }
82
+
83
+ /**
84
+ * Build filter clauses from container filters
85
+ *
86
+ * @param filters Container filters
87
+ * @returns Array of JQL filter clauses
88
+ */
89
+ function buildFilterClauses(filters: any): string[] {
90
+ const clauses: string[] = [];
91
+
92
+ // Include labels
93
+ if (filters.includeLabels && filters.includeLabels.length > 0) {
94
+ const labels = filters.includeLabels.map((l: string) => `"${l}"`).join(', ');
95
+ clauses.push(`labels IN (${labels})`);
96
+ }
97
+
98
+ // Exclude labels
99
+ if (filters.excludeLabels && filters.excludeLabels.length > 0) {
100
+ const labels = filters.excludeLabels.map((l: string) => `"${l}"`).join(', ');
101
+ clauses.push(`labels NOT IN (${labels})`);
102
+ }
103
+
104
+ // Assignees
105
+ if (filters.assignees && filters.assignees.length > 0) {
106
+ const assignees = filters.assignees.map((a: string) => `"${a}"`).join(', ');
107
+ clauses.push(`assignee IN (${assignees})`);
108
+ }
109
+
110
+ // Status categories
111
+ if (filters.statusCategories && filters.statusCategories.length > 0) {
112
+ const statuses = filters.statusCategories.map((s: string) => `"${s}"`).join(', ');
113
+ clauses.push(`status IN (${statuses})`);
114
+ }
115
+
116
+ // Components (Jira-specific)
117
+ if (filters.components && filters.components.length > 0) {
118
+ const components = filters.components.map((c: string) => `"${c}"`).join(', ');
119
+ clauses.push(`component IN (${components})`);
120
+ }
121
+
122
+ // Sprints (Jira-specific)
123
+ if (filters.sprints && filters.sprints.length > 0) {
124
+ const sprints = filters.sprints.map((s: string) => `"${s}"`).join(', ');
125
+ clauses.push(`sprint IN (${sprints})`);
126
+ }
127
+
128
+ // Issue types (Jira-specific)
129
+ if (filters.issueTypes && filters.issueTypes.length > 0) {
130
+ const types = filters.issueTypes.map((t: string) => `"${t}"`).join(', ');
131
+ clauses.push(`issuetype IN (${types})`);
132
+ }
133
+
134
+ return clauses;
135
+ }
136
+
137
+ /**
138
+ * Add time range filter to JQL query
139
+ *
140
+ * @param jql Base JQL query
141
+ * @param timeRange Time range preset (1W, 1M, 3M, 6M, ALL)
142
+ * @returns JQL with time range filter
143
+ */
144
+ function addTimeRangeFilter(jql: string, timeRange: string): string {
145
+ if (timeRange === 'ALL') {
146
+ return jql; // No time filter
147
+ }
148
+
149
+ // Convert time range to Jira format
150
+ const timeMap: Record<string, string> = {
151
+ '1W': '-1w',
152
+ '2W': '-2w',
153
+ '1M': '-1M',
154
+ '3M': '-3M',
155
+ '6M': '-6M',
156
+ '1Y': '-1y',
157
+ };
158
+
159
+ const jiraTime = timeMap[timeRange] || '-1M';
160
+
161
+ return `${jql} AND created >= ${jiraTime}`;
162
+ }
163
+
164
+ /**
165
+ * Fetch issues hierarchically based on sync strategy
166
+ *
167
+ * @param client JiraClient instance
168
+ * @param profile Sync profile with strategy
169
+ * @param timeRange Time range preset
170
+ * @returns Array of Jira issues
171
+ */
172
+ export async function fetchIssuesHierarchical(
173
+ client: JiraClient,
174
+ profile: SyncProfile,
175
+ timeRange: string = '1M'
176
+ ): Promise<JiraIssue[]> {
177
+ const config = profile.config as JiraConfig;
178
+
179
+ // Strategy 1: SIMPLE (backward compatible)
180
+ if (isSimpleStrategy(profile)) {
181
+ return fetchIssuesSimple(client, config, timeRange);
182
+ }
183
+
184
+ // Strategy 2: CUSTOM (raw JQL)
185
+ if (isCustomStrategy(profile)) {
186
+ return fetchIssuesCustom(client, config, timeRange);
187
+ }
188
+
189
+ // Strategy 3: FILTERED (hierarchical)
190
+ if (isFilteredStrategy(profile)) {
191
+ return fetchIssuesFiltered(client, config, timeRange);
192
+ }
193
+
194
+ // Default to simple if strategy not recognized
195
+ console.warn('⚠️ Unknown strategy, defaulting to simple');
196
+ return fetchIssuesSimple(client, config, timeRange);
197
+ }
198
+
199
+ /**
200
+ * Fetch issues using SIMPLE strategy (one project, all boards)
201
+ *
202
+ * @param client JiraClient instance
203
+ * @param config Jira configuration
204
+ * @param timeRange Time range preset
205
+ * @returns Array of Jira issues
206
+ */
207
+ async function fetchIssuesSimple(
208
+ client: JiraClient,
209
+ config: JiraConfig,
210
+ timeRange: string
211
+ ): Promise<JiraIssue[]> {
212
+ const projectKey = config.projectKey;
213
+
214
+ if (!projectKey) {
215
+ throw new Error('Simple strategy requires projectKey in config');
216
+ }
217
+
218
+ let jql = `project=${projectKey}`;
219
+
220
+ // Add time range
221
+ jql = addTimeRangeFilter(jql, timeRange);
222
+
223
+ console.log('🔍 Fetching issues (SIMPLE strategy):', jql);
224
+
225
+ return client.searchIssues({ jql });
226
+ }
227
+
228
+ /**
229
+ * Fetch issues using CUSTOM strategy (raw JQL)
230
+ *
231
+ * @param client JiraClient instance
232
+ * @param config Jira configuration
233
+ * @param timeRange Time range preset
234
+ * @returns Array of Jira issues
235
+ */
236
+ async function fetchIssuesCustom(
237
+ client: JiraClient,
238
+ config: JiraConfig,
239
+ timeRange: string
240
+ ): Promise<JiraIssue[]> {
241
+ const customQuery = config.customQuery;
242
+
243
+ if (!customQuery) {
244
+ throw new Error('Custom strategy requires customQuery in config');
245
+ }
246
+
247
+ // Add time range to custom query
248
+ const jql = addTimeRangeFilter(customQuery, timeRange);
249
+
250
+ console.log('🔍 Fetching issues (CUSTOM strategy):', jql);
251
+
252
+ return client.searchIssues({ jql });
253
+ }
254
+
255
+ /**
256
+ * Fetch issues using FILTERED strategy (multiple projects + boards + filters)
257
+ *
258
+ * @param client JiraClient instance
259
+ * @param config Jira configuration
260
+ * @param timeRange Time range preset
261
+ * @returns Array of Jira issues
262
+ */
263
+ async function fetchIssuesFiltered(
264
+ client: JiraClient,
265
+ config: JiraConfig,
266
+ timeRange: string
267
+ ): Promise<JiraIssue[]> {
268
+ const containers = config.containers;
269
+
270
+ if (!containers || containers.length === 0) {
271
+ throw new Error('Filtered strategy requires containers array in config');
272
+ }
273
+
274
+ // Build hierarchical JQL
275
+ const baseJql = await buildHierarchicalJQL(client, containers);
276
+
277
+ // Add time range
278
+ const jql = addTimeRangeFilter(baseJql, timeRange);
279
+
280
+ console.log('🔍 Fetching issues (FILTERED strategy):', jql);
281
+
282
+ return client.searchIssues({ jql });
283
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "specweave-jira",
3
+ "description": "JIRA integration for SpecWeave increments. Bidirectional sync between SpecWeave increments and JIRA epics/stories. Automatically creates JIRA issues from increments, tracks progress, and updates status.",
4
+ "version": "1.0.0",
5
+ "author": {
6
+ "name": "SpecWeave Team",
7
+ "url": "https://spec-weave.com"
8
+ },
9
+ "homepage": "https://spec-weave.com",
10
+ "repository": "https://github.com/anton-abyzov/specweave",
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "jira",
14
+ "atlassian",
15
+ "integration",
16
+ "sync",
17
+ "specweave",
18
+ "project-management"
19
+ ]
20
+ }