specweave 0.29.0 → 0.29.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 (87) hide show
  1. package/package.json +3 -1
  2. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +12 -0
  3. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +18 -0
  4. package/dist/plugins/specweave-github/lib/enhanced-github-sync.d.ts +0 -26
  5. package/dist/plugins/specweave-github/lib/enhanced-github-sync.d.ts.map +0 -1
  6. package/dist/plugins/specweave-github/lib/enhanced-github-sync.js +0 -249
  7. package/dist/plugins/specweave-github/lib/enhanced-github-sync.js.map +0 -1
  8. package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts +0 -28
  9. package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts.map +0 -1
  10. package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js +0 -156
  11. package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js.map +0 -1
  12. package/dist/src/core/sync/bidirectional-engine.d.ts +0 -119
  13. package/dist/src/core/sync/bidirectional-engine.d.ts.map +0 -1
  14. package/dist/src/core/sync/bidirectional-engine.js +0 -359
  15. package/dist/src/core/sync/bidirectional-engine.js.map +0 -1
  16. package/dist/src/core/sync/conflict-resolver.d.ts +0 -66
  17. package/dist/src/core/sync/conflict-resolver.d.ts.map +0 -1
  18. package/dist/src/core/sync/conflict-resolver.js +0 -108
  19. package/dist/src/core/sync/conflict-resolver.js.map +0 -1
  20. package/dist/src/core/sync/enhanced-content-builder.d.ts +0 -55
  21. package/dist/src/core/sync/enhanced-content-builder.d.ts.map +0 -1
  22. package/dist/src/core/sync/enhanced-content-builder.js +0 -203
  23. package/dist/src/core/sync/enhanced-content-builder.js.map +0 -1
  24. package/dist/src/core/sync/folder-mapper.d.ts +0 -71
  25. package/dist/src/core/sync/folder-mapper.d.ts.map +0 -1
  26. package/dist/src/core/sync/folder-mapper.js +0 -203
  27. package/dist/src/core/sync/folder-mapper.js.map +0 -1
  28. package/dist/src/core/sync/label-detector.d.ts +0 -66
  29. package/dist/src/core/sync/label-detector.d.ts.map +0 -1
  30. package/dist/src/core/sync/label-detector.js +0 -224
  31. package/dist/src/core/sync/label-detector.js.map +0 -1
  32. package/dist/src/core/sync/performance-optimizer.d.ts +0 -153
  33. package/dist/src/core/sync/performance-optimizer.d.ts.map +0 -1
  34. package/dist/src/core/sync/performance-optimizer.js +0 -220
  35. package/dist/src/core/sync/performance-optimizer.js.map +0 -1
  36. package/dist/src/core/sync/profile-selector.d.ts +0 -52
  37. package/dist/src/core/sync/profile-selector.d.ts.map +0 -1
  38. package/dist/src/core/sync/profile-selector.js +0 -179
  39. package/dist/src/core/sync/profile-selector.js.map +0 -1
  40. package/dist/src/core/sync/profile-validator.d.ts +0 -52
  41. package/dist/src/core/sync/profile-validator.d.ts.map +0 -1
  42. package/dist/src/core/sync/profile-validator.js +0 -170
  43. package/dist/src/core/sync/profile-validator.js.map +0 -1
  44. package/dist/src/core/sync/rate-limiter.d.ts +0 -116
  45. package/dist/src/core/sync/rate-limiter.d.ts.map +0 -1
  46. package/dist/src/core/sync/rate-limiter.js +0 -308
  47. package/dist/src/core/sync/rate-limiter.js.map +0 -1
  48. package/dist/src/core/sync/retry-handler.d.ts +0 -98
  49. package/dist/src/core/sync/retry-handler.d.ts.map +0 -1
  50. package/dist/src/core/sync/retry-handler.js +0 -196
  51. package/dist/src/core/sync/retry-handler.js.map +0 -1
  52. package/dist/src/core/sync/retry-logic.d.ts +0 -64
  53. package/dist/src/core/sync/retry-logic.d.ts.map +0 -1
  54. package/dist/src/core/sync/retry-logic.js +0 -165
  55. package/dist/src/core/sync/retry-logic.js.map +0 -1
  56. package/dist/src/core/sync/status-cache.d.ts +0 -91
  57. package/dist/src/core/sync/status-cache.d.ts.map +0 -1
  58. package/dist/src/core/sync/status-cache.js +0 -140
  59. package/dist/src/core/sync/status-cache.js.map +0 -1
  60. package/dist/src/core/sync/status-mapper.d.ts +0 -69
  61. package/dist/src/core/sync/status-mapper.d.ts.map +0 -1
  62. package/dist/src/core/sync/status-mapper.js +0 -90
  63. package/dist/src/core/sync/status-mapper.js.map +0 -1
  64. package/dist/src/core/sync/status-sync-engine.d.ts +0 -162
  65. package/dist/src/core/sync/status-sync-engine.d.ts.map +0 -1
  66. package/dist/src/core/sync/status-sync-engine.js +0 -347
  67. package/dist/src/core/sync/status-sync-engine.js.map +0 -1
  68. package/dist/src/core/sync/sync-event-logger.d.ts +0 -113
  69. package/dist/src/core/sync/sync-event-logger.d.ts.map +0 -1
  70. package/dist/src/core/sync/sync-event-logger.js +0 -141
  71. package/dist/src/core/sync/sync-event-logger.js.map +0 -1
  72. package/dist/src/core/sync/time-range-selector.d.ts +0 -48
  73. package/dist/src/core/sync/time-range-selector.d.ts.map +0 -1
  74. package/dist/src/core/sync/time-range-selector.js +0 -224
  75. package/dist/src/core/sync/time-range-selector.js.map +0 -1
  76. package/dist/src/core/sync/types.d.ts +0 -52
  77. package/dist/src/core/sync/types.d.ts.map +0 -1
  78. package/dist/src/core/sync/types.js +0 -5
  79. package/dist/src/core/sync/types.js.map +0 -1
  80. package/dist/src/core/sync/workflow-detector.d.ts +0 -95
  81. package/dist/src/core/sync/workflow-detector.d.ts.map +0 -1
  82. package/dist/src/core/sync/workflow-detector.js +0 -175
  83. package/dist/src/core/sync/workflow-detector.js.map +0 -1
  84. package/plugins/specweave-github/lib/enhanced-github-sync.js +0 -220
  85. package/plugins/specweave-github/lib/enhanced-github-sync.ts +0 -322
  86. package/plugins/specweave-jira/lib/enhanced-jira-sync.js +0 -134
  87. package/plugins/specweave-jira/lib/enhanced-jira-sync.ts +0 -196
@@ -1,220 +0,0 @@
1
- import { GitHubClientV2 } from "./github-client-v2.js";
2
- import { EnhancedContentBuilder } from "../../../src/core/sync/enhanced-content-builder.js";
3
- import { SpecIncrementMapper } from "../../../src/core/sync/spec-increment-mapper.js";
4
- import { parseSpecContent } from "../../../src/core/spec-content-sync.js";
5
- import { LabelDetector } from "../../../src/core/sync/label-detector.js";
6
- import path from "path";
7
- import fs from "fs/promises";
8
- async function syncSpecWithEnhancedContent(options) {
9
- const { specPath, owner, repo, dryRun = false, verbose = false } = options;
10
- try {
11
- const baseSpec = await parseSpecContent(specPath);
12
- if (!baseSpec) {
13
- return {
14
- success: false,
15
- action: "error",
16
- error: "Failed to parse spec content"
17
- };
18
- }
19
- if (verbose) {
20
- console.log(`\u{1F4C4} Parsed spec: ${baseSpec.identifier.compact}`);
21
- }
22
- const specId = baseSpec.identifier.full || baseSpec.identifier.compact;
23
- const rootDir = await findSpecWeaveRoot(specPath);
24
- const mapper = new SpecIncrementMapper(rootDir);
25
- const mapping = await mapper.mapSpecToIncrements(specId);
26
- if (verbose) {
27
- console.log(`\u{1F517} Found ${mapping.increments.length} related increments`);
28
- console.log(`\u{1F4CB} Mapped ${Object.keys(mapping.userStoryMappings).length} user stories to tasks`);
29
- }
30
- const taskMapping = buildTaskMapping(mapping.increments, owner, repo);
31
- const architectureDocs = await findArchitectureDocs(rootDir, specId);
32
- const sourceLinks = buildSourceLinks(mapping.increments[0]?.id, owner, repo);
33
- const enhancedSpec = {
34
- ...baseSpec,
35
- summary: baseSpec.description,
36
- taskMapping,
37
- architectureDocs,
38
- sourceLinks
39
- };
40
- const builder = new EnhancedContentBuilder();
41
- const originalBuildExternal = builder.buildExternalDescription.bind(builder);
42
- const description = (() => {
43
- const sections = [];
44
- sections.push(builder.buildSummarySection(enhancedSpec));
45
- if (enhancedSpec.userStories && enhancedSpec.userStories.length > 0) {
46
- sections.push(builder.buildUserStoriesSection(enhancedSpec.userStories));
47
- }
48
- if (enhancedSpec.taskMapping) {
49
- sections.push(builder.buildTasksSection(enhancedSpec.taskMapping, {
50
- showCheckboxes: true,
51
- showProgressBar: true,
52
- showCompletionStatus: true,
53
- provider: "github"
54
- }));
55
- }
56
- if (enhancedSpec.architectureDocs && enhancedSpec.architectureDocs.length > 0) {
57
- sections.push(builder.buildArchitectureSection(enhancedSpec.architectureDocs));
58
- }
59
- if (enhancedSpec.sourceLinks) {
60
- sections.push(builder.buildSourceLinksSection(enhancedSpec.sourceLinks));
61
- }
62
- return sections.filter((s) => s.length > 0).join("\n\n---\n\n");
63
- })();
64
- if (verbose) {
65
- console.log(`\u{1F4DD} Generated description: ${description.length} characters`);
66
- }
67
- if (dryRun) {
68
- console.log("\u{1F50D} DRY RUN - Would create/update issue with:");
69
- console.log(` Title: ${baseSpec.title}`);
70
- console.log(` Description length: ${description.length}`);
71
- console.log(` Tasks linked: ${taskMapping?.tasks.length || 0}`);
72
- return {
73
- success: true,
74
- action: "no-change",
75
- tasksLinked: taskMapping?.tasks.length || 0
76
- };
77
- }
78
- if (!owner || !repo) {
79
- return {
80
- success: false,
81
- action: "error",
82
- error: "GitHub owner/repo not specified"
83
- };
84
- }
85
- const client = GitHubClientV2.fromRepo(owner, repo);
86
- const labelDetector = new LabelDetector(void 0, false);
87
- const detection = labelDetector.detectType(
88
- await fs.readFile(specPath, "utf-8"),
89
- mapping.increments[0]?.id
90
- );
91
- const githubLabels = labelDetector.getGitHubLabels(detection.type);
92
- const allLabels = ["spec", ...githubLabels];
93
- if (verbose) {
94
- console.log(`\u{1F3F7}\uFE0F Detected type: ${detection.type} (${detection.confidence}% confidence)`);
95
- console.log(` Labels: ${allLabels.join(", ")}`);
96
- }
97
- const existingIssue = await findExistingIssue(client, baseSpec.identifier.compact);
98
- let result;
99
- if (existingIssue) {
100
- await client.updateIssueBody(existingIssue.number, description);
101
- await client.addLabels(existingIssue.number, allLabels);
102
- result = {
103
- success: true,
104
- action: "updated",
105
- issueNumber: existingIssue.number,
106
- issueUrl: existingIssue.html_url,
107
- tasksLinked: taskMapping?.tasks.length || 0
108
- };
109
- } else {
110
- const issue = await client.createEpicIssue(
111
- `[${baseSpec.project === "_features" ? baseSpec.identifier.display : baseSpec.identifier.compact}] ${baseSpec.title}`,
112
- description,
113
- void 0,
114
- allLabels
115
- // Apply labels at creation
116
- );
117
- result = {
118
- success: true,
119
- action: "created",
120
- issueNumber: issue.number,
121
- issueUrl: issue.html_url,
122
- tasksLinked: taskMapping?.tasks.length || 0
123
- };
124
- await mapper.updateSpecWithIncrementLinks(specId, mapping.increments[0]?.id);
125
- }
126
- if (verbose) {
127
- console.log(`\u2705 ${result.action === "created" ? "Created" : "Updated"} issue #${result.issueNumber}`);
128
- console.log(` URL: ${result.issueUrl}`);
129
- console.log(` Tasks linked: ${result.tasksLinked}`);
130
- }
131
- return result;
132
- } catch (error) {
133
- return {
134
- success: false,
135
- action: "error",
136
- error: error.message
137
- };
138
- }
139
- }
140
- async function findSpecWeaveRoot(specPath) {
141
- let currentDir = path.dirname(specPath);
142
- while (true) {
143
- const specweaveDir = path.join(currentDir, ".specweave");
144
- try {
145
- await fs.access(specweaveDir);
146
- return currentDir;
147
- } catch {
148
- const parentDir = path.dirname(currentDir);
149
- if (parentDir === currentDir) {
150
- throw new Error(".specweave directory not found");
151
- }
152
- currentDir = parentDir;
153
- }
154
- }
155
- }
156
- function buildTaskMapping(increments, owner, repo) {
157
- if (increments.length === 0) return void 0;
158
- const firstIncrement = increments[0];
159
- const tasks = firstIncrement.tasks.map((task) => ({
160
- id: task.id,
161
- title: task.title,
162
- userStories: task.userStories,
163
- githubIssue: task.githubIssue
164
- }));
165
- return {
166
- incrementId: firstIncrement.id,
167
- tasks,
168
- tasksUrl: `https://github.com/${owner}/${repo}/blob/develop/.specweave/increments/${firstIncrement.id}/tasks.md`
169
- };
170
- }
171
- async function findArchitectureDocs(rootDir, specId) {
172
- const docs = [];
173
- const archDir = path.join(rootDir, ".specweave/docs/internal/architecture");
174
- try {
175
- const adrDir = path.join(archDir, "adr");
176
- try {
177
- const adrs = await fs.readdir(adrDir);
178
- const relatedAdrs = adrs.filter((file) => file.includes(specId.replace("spec-", "")));
179
- for (const adr of relatedAdrs) {
180
- docs.push({
181
- type: "adr",
182
- path: path.join(adrDir, adr),
183
- title: adr.replace(".md", "").replace(/-/g, " ")
184
- });
185
- }
186
- } catch {
187
- }
188
- const hlds = await fs.readdir(archDir);
189
- const relatedHlds = hlds.filter((file) => file.includes("hld") && file.includes(specId.replace("spec-", "")));
190
- for (const hld of relatedHlds) {
191
- docs.push({
192
- type: "hld",
193
- path: path.join(archDir, hld),
194
- title: hld.replace(".md", "").replace(/-/g, " ")
195
- });
196
- }
197
- } catch {
198
- }
199
- return docs;
200
- }
201
- function buildSourceLinks(incrementId, owner, repo) {
202
- if (!incrementId) return void 0;
203
- const baseUrl = `https://github.com/${owner}/${repo}/blob/develop/.specweave`;
204
- return {
205
- spec: `${baseUrl}/docs/internal/specs/default/spec-${incrementId.replace(/^\d+-/, "")}.md`,
206
- plan: `${baseUrl}/increments/${incrementId}/plan.md`,
207
- tasks: `${baseUrl}/increments/${incrementId}/tasks.md`
208
- };
209
- }
210
- async function findExistingIssue(client, specId) {
211
- try {
212
- const issues = await client.listIssuesInTimeRange("ALL");
213
- return issues.find((issue) => issue.title.includes(`[${specId}]`) && issue.labels?.some((l) => l.name === "spec")) || null;
214
- } catch {
215
- return null;
216
- }
217
- }
218
- export {
219
- syncSpecWithEnhancedContent
220
- };
@@ -1,322 +0,0 @@
1
- /**
2
- * Enhanced GitHub Spec Content Sync
3
- *
4
- * Uses EnhancedContentBuilder and SpecIncrementMapper for rich external descriptions.
5
- * NEW (v0.21.0): Supports task checkboxes and automatic labeling.
6
- */
7
-
8
- import { GitHubClientV2 } from './github-client-v2.js';
9
- import { EnhancedContentBuilder, EnhancedSpecContent } from '../../../src/core/sync/enhanced-content-builder.js';
10
- import { SpecIncrementMapper, TaskInfo } from '../../../src/core/sync/spec-increment-mapper.js';
11
- import { parseSpecContent, SpecContent } from '../../../src/core/spec-content-sync.js';
12
- import { LabelDetector } from '../../../src/core/sync/label-detector.js';
13
- import path from 'path';
14
- import fs from 'fs/promises';
15
-
16
- export interface EnhancedGitHubSyncOptions {
17
- specPath: string;
18
- owner?: string;
19
- repo?: string;
20
- dryRun?: boolean;
21
- verbose?: boolean;
22
- }
23
-
24
- export interface EnhancedSyncResult {
25
- success: boolean;
26
- action: 'created' | 'updated' | 'no-change' | 'error';
27
- issueNumber?: number;
28
- issueUrl?: string;
29
- error?: string;
30
- tasksLinked?: number;
31
- }
32
-
33
- /**
34
- * Enhanced sync with rich content including task mappings
35
- */
36
- export async function syncSpecWithEnhancedContent(
37
- options: EnhancedGitHubSyncOptions
38
- ): Promise<EnhancedSyncResult> {
39
- const { specPath, owner, repo, dryRun = false, verbose = false } = options;
40
-
41
- try {
42
- // 1. Parse spec content
43
- const baseSpec = await parseSpecContent(specPath);
44
- if (!baseSpec) {
45
- return {
46
- success: false,
47
- action: 'error',
48
- error: 'Failed to parse spec content',
49
- };
50
- }
51
-
52
- if (verbose) {
53
- console.log(`📄 Parsed spec: ${baseSpec.identifier.compact}`);
54
- }
55
-
56
- // 2. Build enhanced spec with task mappings
57
- const specId = baseSpec.identifier.full || baseSpec.identifier.compact;
58
- const rootDir = await findSpecWeaveRoot(specPath);
59
- const mapper = new SpecIncrementMapper(rootDir);
60
- const mapping = await mapper.mapSpecToIncrements(specId);
61
-
62
- if (verbose) {
63
- console.log(`🔗 Found ${mapping.increments.length} related increments`);
64
- console.log(`📋 Mapped ${Object.keys(mapping.userStoryMappings).length} user stories to tasks`);
65
- }
66
-
67
- // 3. Build task mapping for EnhancedSpecContent
68
- const taskMapping = buildTaskMapping(mapping.increments, owner!, repo!);
69
-
70
- // 4. Build architecture docs references
71
- const architectureDocs = await findArchitectureDocs(rootDir, specId);
72
-
73
- // 5. Build source links
74
- const sourceLinks = buildSourceLinks(mapping.increments[0]?.id, owner!, repo!);
75
-
76
- // 6. Create enhanced spec content
77
- const enhancedSpec: EnhancedSpecContent = {
78
- ...baseSpec,
79
- summary: baseSpec.description,
80
- taskMapping,
81
- architectureDocs,
82
- sourceLinks
83
- };
84
-
85
- // 7. Build external description with task checkboxes
86
- const builder = new EnhancedContentBuilder();
87
-
88
- // NEW: Override buildTasksSection call to include checkboxes
89
- const originalBuildExternal = builder.buildExternalDescription.bind(builder);
90
- const description = (() => {
91
- const sections: string[] = [];
92
-
93
- // Summary
94
- sections.push(builder.buildSummarySection(enhancedSpec));
95
-
96
- // User stories
97
- if (enhancedSpec.userStories && enhancedSpec.userStories.length > 0) {
98
- sections.push(builder.buildUserStoriesSection(enhancedSpec.userStories));
99
- }
100
-
101
- // Tasks with checkboxes (NEW!)
102
- if (enhancedSpec.taskMapping) {
103
- sections.push(builder.buildTasksSection(enhancedSpec.taskMapping, {
104
- showCheckboxes: true,
105
- showProgressBar: true,
106
- showCompletionStatus: true,
107
- provider: 'github'
108
- }));
109
- }
110
-
111
- // Architecture
112
- if (enhancedSpec.architectureDocs && enhancedSpec.architectureDocs.length > 0) {
113
- sections.push(builder.buildArchitectureSection(enhancedSpec.architectureDocs));
114
- }
115
-
116
- // Source links
117
- if (enhancedSpec.sourceLinks) {
118
- sections.push(builder.buildSourceLinksSection(enhancedSpec.sourceLinks));
119
- }
120
-
121
- return sections.filter(s => s.length > 0).join('\n\n---\n\n');
122
- })();
123
-
124
- if (verbose) {
125
- console.log(`📝 Generated description: ${description.length} characters`);
126
- }
127
-
128
- if (dryRun) {
129
- console.log('🔍 DRY RUN - Would create/update issue with:');
130
- console.log(` Title: ${baseSpec.title}`);
131
- console.log(` Description length: ${description.length}`);
132
- console.log(` Tasks linked: ${taskMapping?.tasks.length || 0}`);
133
- return {
134
- success: true,
135
- action: 'no-change',
136
- tasksLinked: taskMapping?.tasks.length || 0
137
- };
138
- }
139
-
140
- // 8. Create or update GitHub issue
141
- if (!owner || !repo) {
142
- return {
143
- success: false,
144
- action: 'error',
145
- error: 'GitHub owner/repo not specified',
146
- };
147
- }
148
-
149
- const client = GitHubClientV2.fromRepo(owner, repo);
150
-
151
- // NEW: Detect increment type and apply labels
152
- const labelDetector = new LabelDetector(undefined, false); // Use GitHub format
153
- const detection = labelDetector.detectType(
154
- await fs.readFile(specPath, 'utf-8'),
155
- mapping.increments[0]?.id
156
- );
157
- const githubLabels = labelDetector.getGitHubLabels(detection.type);
158
- const allLabels = ['spec', ...githubLabels]; // Include both 'spec' and type labels
159
-
160
- if (verbose) {
161
- console.log(`🏷️ Detected type: ${detection.type} (${detection.confidence}% confidence)`);
162
- console.log(` Labels: ${allLabels.join(', ')}`);
163
- }
164
-
165
- // Check if issue already exists
166
- const existingIssue = await findExistingIssue(client, baseSpec.identifier.compact);
167
-
168
- let result: EnhancedSyncResult;
169
-
170
- if (existingIssue) {
171
- // Update existing issue (body + labels)
172
- await client.updateIssueBody(existingIssue.number, description);
173
-
174
- // Update labels if autoApplyLabels is enabled
175
- // TODO: Read from config, for now always apply
176
- await client.addLabels(existingIssue.number, allLabels);
177
-
178
- result = {
179
- success: true,
180
- action: 'updated',
181
- issueNumber: existingIssue.number,
182
- issueUrl: existingIssue.html_url,
183
- tasksLinked: taskMapping?.tasks.length || 0
184
- };
185
- } else {
186
- // Create new issue with labels
187
- const issue = await client.createEpicIssue(
188
- `[${baseSpec.project === '_features' ? baseSpec.identifier.display : baseSpec.identifier.compact}] ${baseSpec.title}`,
189
- description,
190
- undefined,
191
- allLabels // Apply labels at creation
192
- );
193
-
194
- result = {
195
- success: true,
196
- action: 'created',
197
- issueNumber: issue.number,
198
- issueUrl: issue.html_url,
199
- tasksLinked: taskMapping?.tasks.length || 0
200
- };
201
-
202
- // Update spec with GitHub link
203
- await mapper.updateSpecWithIncrementLinks(specId, mapping.increments[0]?.id);
204
- }
205
-
206
- if (verbose) {
207
- console.log(`✅ ${result.action === 'created' ? 'Created' : 'Updated'} issue #${result.issueNumber}`);
208
- console.log(` URL: ${result.issueUrl}`);
209
- console.log(` Tasks linked: ${result.tasksLinked}`);
210
- }
211
-
212
- return result;
213
- } catch (error: any) {
214
- return {
215
- success: false,
216
- action: 'error',
217
- error: error.message
218
- };
219
- }
220
- }
221
-
222
- // Helper functions
223
-
224
- async function findSpecWeaveRoot(specPath: string): Promise<string> {
225
- let currentDir = path.dirname(specPath);
226
-
227
- while (true) {
228
- const specweaveDir = path.join(currentDir, '.specweave');
229
- try {
230
- await fs.access(specweaveDir);
231
- return currentDir;
232
- } catch {
233
- const parentDir = path.dirname(currentDir);
234
- if (parentDir === currentDir) {
235
- throw new Error('.specweave directory not found');
236
- }
237
- currentDir = parentDir;
238
- }
239
- }
240
- }
241
-
242
- function buildTaskMapping(
243
- increments: any[],
244
- owner: string,
245
- repo: string
246
- ): any {
247
- if (increments.length === 0) return undefined;
248
-
249
- const firstIncrement = increments[0];
250
- const tasks = firstIncrement.tasks.map((task: TaskInfo) => ({
251
- id: task.id,
252
- title: task.title,
253
- userStories: task.userStories,
254
- githubIssue: task.githubIssue
255
- }));
256
-
257
- return {
258
- incrementId: firstIncrement.id,
259
- tasks,
260
- tasksUrl: `https://github.com/${owner}/${repo}/blob/develop/.specweave/increments/${firstIncrement.id}/tasks.md`
261
- };
262
- }
263
-
264
- async function findArchitectureDocs(
265
- rootDir: string,
266
- specId: string
267
- ): Promise<any[]> {
268
- const docs: any[] = [];
269
- const archDir = path.join(rootDir, '.specweave/docs/internal/architecture');
270
-
271
- try {
272
- // Check for ADRs
273
- const adrDir = path.join(archDir, 'adr');
274
- try {
275
- const adrs = await fs.readdir(adrDir);
276
- const relatedAdrs = adrs.filter(file => file.includes(specId.replace('spec-', '')));
277
-
278
- for (const adr of relatedAdrs) {
279
- docs.push({
280
- type: 'adr',
281
- path: path.join(adrDir, adr),
282
- title: adr.replace('.md', '').replace(/-/g, ' ')
283
- });
284
- }
285
- } catch {}
286
-
287
- // Check for HLD
288
- const hlds = await fs.readdir(archDir);
289
- const relatedHlds = hlds.filter(file => file.includes('hld') && file.includes(specId.replace('spec-', '')));
290
-
291
- for (const hld of relatedHlds) {
292
- docs.push({
293
- type: 'hld',
294
- path: path.join(archDir, hld),
295
- title: hld.replace('.md', '').replace(/-/g, ' ')
296
- });
297
- }
298
- } catch {}
299
-
300
- return docs;
301
- }
302
-
303
- function buildSourceLinks(incrementId: string | undefined, owner: string, repo: string): any {
304
- if (!incrementId) return undefined;
305
-
306
- const baseUrl = `https://github.com/${owner}/${repo}/blob/develop/.specweave`;
307
-
308
- return {
309
- spec: `${baseUrl}/docs/internal/specs/default/spec-${incrementId.replace(/^\d+-/, '')}.md`,
310
- plan: `${baseUrl}/increments/${incrementId}/plan.md`,
311
- tasks: `${baseUrl}/increments/${incrementId}/tasks.md`
312
- };
313
- }
314
-
315
- async function findExistingIssue(client: GitHubClientV2, specId: string): Promise<any | null> {
316
- try {
317
- const issues = await client.listIssuesInTimeRange('ALL');
318
- return issues.find((issue: any) => issue.title.includes(`[${specId}]`) && issue.labels?.some((l: any) => l.name === 'spec')) || null;
319
- } catch {
320
- return null;
321
- }
322
- }
@@ -1,134 +0,0 @@
1
- import { EnhancedContentBuilder } from "../../../src/core/sync/enhanced-content-builder.js";
2
- import { SpecIncrementMapper } from "../../../src/core/sync/spec-increment-mapper.js";
3
- import { parseSpecContent } from "../../../src/core/spec-content-sync.js";
4
- import * as path from "path";
5
- import * as fs from "fs/promises";
6
- async function syncSpecToJiraWithEnhancedContent(options) {
7
- const { specPath, domain, project, dryRun = false, verbose = false } = options;
8
- try {
9
- const baseSpec = await parseSpecContent(specPath);
10
- if (!baseSpec) {
11
- return {
12
- success: false,
13
- action: "error",
14
- error: "Failed to parse spec content"
15
- };
16
- }
17
- if (verbose) {
18
- console.log(`\u{1F4C4} Parsed spec: ${baseSpec.identifier.compact}`);
19
- }
20
- const specId = baseSpec.identifier.full || baseSpec.identifier.compact;
21
- const rootDir = await findSpecWeaveRoot(specPath);
22
- const mapper = new SpecIncrementMapper(rootDir);
23
- const mapping = await mapper.mapSpecToIncrements(specId);
24
- if (verbose) {
25
- console.log(`\u{1F517} Found ${mapping.increments.length} related increments`);
26
- }
27
- const taskMapping = buildTaskMapping(mapping.increments);
28
- const architectureDocs = await findArchitectureDocs(rootDir, specId);
29
- const enhancedSpec = {
30
- ...baseSpec,
31
- summary: baseSpec.description,
32
- taskMapping,
33
- architectureDocs
34
- };
35
- const builder = new EnhancedContentBuilder();
36
- const description = builder.buildExternalDescription(enhancedSpec);
37
- if (verbose) {
38
- console.log(`\u{1F4DD} Generated description: ${description.length} characters`);
39
- }
40
- if (dryRun) {
41
- console.log("\u{1F50D} DRY RUN - Would create/update epic with:");
42
- console.log(` Summary: ${baseSpec.title}`);
43
- console.log(` Description length: ${description.length}`);
44
- return {
45
- success: true,
46
- action: "no-change",
47
- tasksLinked: taskMapping?.tasks.length || 0
48
- };
49
- }
50
- if (!dryRun && (!domain || !project)) {
51
- return {
52
- success: false,
53
- action: "error",
54
- error: "JIRA domain/project not specified (required for actual sync)"
55
- };
56
- }
57
- const result = {
58
- success: true,
59
- action: dryRun ? "no-change" : "created",
60
- // Assume create if not dry run
61
- tasksLinked: taskMapping?.tasks.length || 0
62
- };
63
- if (domain && project && !dryRun) {
64
- result.epicKey = `SPEC-001`;
65
- result.epicUrl = `https://${domain}/browse/SPEC-001`;
66
- if (verbose) {
67
- console.log(`\u26A0\uFE0F JIRA API integration not implemented in this file`);
68
- console.log(` Use jira-spec-sync.ts for actual JIRA synchronization`);
69
- }
70
- }
71
- return result;
72
- } catch (error) {
73
- return {
74
- success: false,
75
- action: "error",
76
- error: error.message
77
- };
78
- }
79
- }
80
- async function findSpecWeaveRoot(specPath) {
81
- let currentDir = path.dirname(specPath);
82
- while (true) {
83
- const specweaveDir = path.join(currentDir, ".specweave");
84
- try {
85
- await fs.access(specweaveDir);
86
- return currentDir;
87
- } catch {
88
- const parentDir = path.dirname(currentDir);
89
- if (parentDir === currentDir) {
90
- throw new Error(".specweave directory not found");
91
- }
92
- currentDir = parentDir;
93
- }
94
- }
95
- }
96
- function buildTaskMapping(increments) {
97
- if (increments.length === 0) return void 0;
98
- const firstIncrement = increments[0];
99
- const tasks = firstIncrement.tasks.map((task) => ({
100
- id: task.id,
101
- title: task.title,
102
- userStories: task.userStories
103
- }));
104
- return {
105
- incrementId: firstIncrement.id,
106
- tasks,
107
- tasksUrl: `tasks.md`
108
- // JIRA doesn't support external links in same way
109
- };
110
- }
111
- async function findArchitectureDocs(rootDir, specId) {
112
- const docs = [];
113
- const archDir = path.join(rootDir, ".specweave/docs/internal/architecture");
114
- try {
115
- const adrDir = path.join(archDir, "adr");
116
- try {
117
- const adrs = await fs.readdir(adrDir);
118
- const relatedAdrs = adrs.filter((file) => file.includes(specId.replace("spec-", "")));
119
- for (const adr of relatedAdrs) {
120
- docs.push({
121
- type: "adr",
122
- path: path.join(adrDir, adr),
123
- title: adr.replace(".md", "").replace(/-/g, " ")
124
- });
125
- }
126
- } catch {
127
- }
128
- } catch {
129
- }
130
- return docs;
131
- }
132
- export {
133
- syncSpecToJiraWithEnhancedContent
134
- };