specweave 1.0.362 → 1.0.367
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.
- package/bin/specweave.js +1 -1
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +33 -0
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/sync-progress.d.ts.map +1 -1
- package/dist/src/cli/commands/sync-progress.js +1 -0
- package/dist/src/cli/commands/sync-progress.js.map +1 -1
- package/dist/src/core/config/config-manager.d.ts.map +1 -1
- package/dist/src/core/config/config-manager.js +6 -0
- package/dist/src/core/config/config-manager.js.map +1 -1
- package/dist/src/core/config/types.d.ts +2 -2
- package/dist/src/core/config/types.d.ts.map +1 -1
- package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts.map +1 -1
- package/dist/src/core/lazy-loading/llm-plugin-detector.js +69 -0
- package/dist/src/core/lazy-loading/llm-plugin-detector.js.map +1 -1
- package/dist/src/core/migration/umbrella-migrator.d.ts.map +1 -1
- package/dist/src/core/migration/umbrella-migrator.js +17 -1
- package/dist/src/core/migration/umbrella-migrator.js.map +1 -1
- package/dist/src/sync/external-issue-auto-creator.d.ts.map +1 -1
- package/dist/src/sync/external-issue-auto-creator.js +13 -1
- package/dist/src/sync/external-issue-auto-creator.js.map +1 -1
- package/dist/src/sync/sync-target-resolver.d.ts +2 -2
- package/dist/src/sync/sync-target-resolver.d.ts.map +1 -1
- package/dist/src/sync/sync-target-resolver.js +17 -4
- package/dist/src/sync/sync-target-resolver.js.map +1 -1
- package/package.json +1 -1
- package/plugins/SKILLS-VS-AGENTS.md +212 -119
- package/plugins/specweave/agents/sw-architect.md +8 -67
- package/plugins/specweave/agents/sw-planner.md +10 -82
- package/plugins/specweave/agents/sw-pm.md +9 -103
- package/plugins/specweave/hooks/.specweave/logs/auto-iterations.log +1 -0
- package/plugins/specweave/hooks/.specweave/logs/auto-stop-reasons.log +1 -0
- package/plugins/specweave/skills/.specweave/logs/reflect/auto-reflect.log +15 -0
- package/plugins/specweave/skills/.specweave/logs/reflect/reflect.log +3 -0
- package/plugins/specweave/skills/.specweave/logs/stop-auto.log +1 -0
- package/plugins/specweave/skills/brainstorm/SKILL.md +128 -26
- package/plugins/specweave/skills/increment/SKILL.md +50 -32
- package/plugins/specweave/skills/pm/SKILL.md +28 -1
- package/plugins/specweave/skills/pm/phases/02-spec-creation.md +17 -0
- package/plugins/specweave/skills/team-lead/agents/database.md +12 -12
- package/plugins/specweave/skills/team-lead/agents/security.md +12 -12
- package/plugins/specweave/skills/team-lead/agents/testing.md +12 -12
- package/plugins/specweave/skills/team-merge/SKILL.md +11 -0
- package/plugins/specweave/skills/test-aware-planner/SKILL.md +1 -1
- package/plugins/specweave-ado/lib/ado-multi-project-sync.js +0 -1
- package/plugins/specweave-ado/lib/enhanced-ado-sync.js +180 -0
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +1266 -0
- package/plugins/specweave-github/lib/enhanced-github-sync.js +249 -0
- package/plugins/specweave-jira/lib/enhanced-jira-sync.js +150 -0
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +1260 -0
|
@@ -0,0 +1,249 @@
|
|
|
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 defaultBranch = (owner && repo) ? await getDefaultBranch(owner, repo) : null;
|
|
31
|
+
const taskMapping = buildTaskMapping(mapping.increments, owner, repo, defaultBranch);
|
|
32
|
+
const architectureDocs = await findArchitectureDocs(rootDir, specId);
|
|
33
|
+
const sourceLinks = buildSourceLinks(mapping.increments[0]?.id, owner, repo, defaultBranch);
|
|
34
|
+
const enhancedSpec = {
|
|
35
|
+
...baseSpec,
|
|
36
|
+
summary: baseSpec.description,
|
|
37
|
+
taskMapping,
|
|
38
|
+
architectureDocs,
|
|
39
|
+
sourceLinks
|
|
40
|
+
};
|
|
41
|
+
const builder = new EnhancedContentBuilder();
|
|
42
|
+
const originalBuildExternal = builder.buildExternalDescription.bind(builder);
|
|
43
|
+
const description = (() => {
|
|
44
|
+
const sections = [];
|
|
45
|
+
sections.push(builder.buildSummarySection(enhancedSpec));
|
|
46
|
+
if (enhancedSpec.userStories && enhancedSpec.userStories.length > 0) {
|
|
47
|
+
sections.push(builder.buildUserStoriesSection(enhancedSpec.userStories));
|
|
48
|
+
}
|
|
49
|
+
if (enhancedSpec.taskMapping) {
|
|
50
|
+
sections.push(builder.buildTasksSection(enhancedSpec.taskMapping, {
|
|
51
|
+
showCheckboxes: true,
|
|
52
|
+
showProgressBar: true,
|
|
53
|
+
showCompletionStatus: true,
|
|
54
|
+
provider: "github"
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
if (enhancedSpec.architectureDocs && enhancedSpec.architectureDocs.length > 0) {
|
|
58
|
+
sections.push(builder.buildArchitectureSection(enhancedSpec.architectureDocs));
|
|
59
|
+
}
|
|
60
|
+
if (enhancedSpec.sourceLinks) {
|
|
61
|
+
sections.push(builder.buildSourceLinksSection(enhancedSpec.sourceLinks));
|
|
62
|
+
}
|
|
63
|
+
return sections.filter((s) => s.length > 0).join("\n\n---\n\n");
|
|
64
|
+
})();
|
|
65
|
+
if (verbose) {
|
|
66
|
+
console.log(`\u{1F4DD} Generated description: ${description.length} characters`);
|
|
67
|
+
}
|
|
68
|
+
if (dryRun) {
|
|
69
|
+
console.log("\u{1F50D} DRY RUN - Would create/update issue with:");
|
|
70
|
+
console.log(` Title: ${baseSpec.title}`);
|
|
71
|
+
console.log(` Description length: ${description.length}`);
|
|
72
|
+
console.log(` Tasks linked: ${taskMapping?.tasks.length || 0}`);
|
|
73
|
+
return {
|
|
74
|
+
success: true,
|
|
75
|
+
action: "no-change",
|
|
76
|
+
tasksLinked: taskMapping?.tasks.length || 0
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (!owner || !repo) {
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
action: "error",
|
|
83
|
+
error: "GitHub owner/repo not specified"
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const client = GitHubClientV2.fromRepo(owner, repo);
|
|
87
|
+
const labelDetector = new LabelDetector(void 0, false);
|
|
88
|
+
const detection = labelDetector.detectType(
|
|
89
|
+
await fs.readFile(specPath, "utf-8"),
|
|
90
|
+
mapping.increments[0]?.id
|
|
91
|
+
);
|
|
92
|
+
const githubLabels = labelDetector.getGitHubLabels(detection.type);
|
|
93
|
+
const allLabels = ["spec", ...githubLabels];
|
|
94
|
+
if (verbose) {
|
|
95
|
+
console.log(`\u{1F3F7}\uFE0F Detected type: ${detection.type} (${detection.confidence}% confidence)`);
|
|
96
|
+
console.log(` Labels: ${allLabels.join(", ")}`);
|
|
97
|
+
}
|
|
98
|
+
const existingIssue = await findExistingIssue(client, baseSpec.identifier.compact);
|
|
99
|
+
let result;
|
|
100
|
+
if (existingIssue) {
|
|
101
|
+
await client.updateIssueBody(existingIssue.number, description);
|
|
102
|
+
await client.addLabels(existingIssue.number, allLabels);
|
|
103
|
+
result = {
|
|
104
|
+
success: true,
|
|
105
|
+
action: "updated",
|
|
106
|
+
issueNumber: existingIssue.number,
|
|
107
|
+
issueUrl: existingIssue.html_url,
|
|
108
|
+
tasksLinked: taskMapping?.tasks.length || 0
|
|
109
|
+
};
|
|
110
|
+
} else {
|
|
111
|
+
const issue = await client.createEpicIssue(
|
|
112
|
+
`[${baseSpec.project === "_features" ? baseSpec.identifier.display : baseSpec.identifier.compact}] ${baseSpec.title}`,
|
|
113
|
+
description,
|
|
114
|
+
void 0,
|
|
115
|
+
allLabels
|
|
116
|
+
// Apply labels at creation
|
|
117
|
+
);
|
|
118
|
+
result = {
|
|
119
|
+
success: true,
|
|
120
|
+
action: "created",
|
|
121
|
+
issueNumber: issue.number,
|
|
122
|
+
issueUrl: issue.html_url,
|
|
123
|
+
tasksLinked: taskMapping?.tasks.length || 0
|
|
124
|
+
};
|
|
125
|
+
await mapper.updateSpecWithIncrementLinks(specId, mapping.increments[0]?.id);
|
|
126
|
+
}
|
|
127
|
+
if (verbose) {
|
|
128
|
+
console.log(`\u2705 ${result.action === "created" ? "Created" : "Updated"} issue #${result.issueNumber}`);
|
|
129
|
+
console.log(` URL: ${result.issueUrl}`);
|
|
130
|
+
console.log(` Tasks linked: ${result.tasksLinked}`);
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
action: "error",
|
|
137
|
+
error: error.message
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function findSpecWeaveRoot(specPath) {
|
|
142
|
+
let currentDir = path.dirname(specPath);
|
|
143
|
+
while (true) {
|
|
144
|
+
const specweaveDir = path.join(currentDir, ".specweave");
|
|
145
|
+
try {
|
|
146
|
+
await fs.access(specweaveDir);
|
|
147
|
+
return currentDir;
|
|
148
|
+
} catch {
|
|
149
|
+
const parentDir = path.dirname(currentDir);
|
|
150
|
+
if (parentDir === currentDir) {
|
|
151
|
+
throw new Error(".specweave directory not found");
|
|
152
|
+
}
|
|
153
|
+
currentDir = parentDir;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Cache for default branch per repo per session
|
|
158
|
+
const defaultBranchCache = new Map();
|
|
159
|
+
|
|
160
|
+
async function getDefaultBranch(owner, repo) {
|
|
161
|
+
const cacheKey = `${owner}/${repo}`;
|
|
162
|
+
if (defaultBranchCache.has(cacheKey)) {
|
|
163
|
+
return defaultBranchCache.get(cacheKey);
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
const { execFileSync } = await import("child_process");
|
|
167
|
+
const result = execFileSync("gh", [
|
|
168
|
+
"repo", "view", `${owner}/${repo}`,
|
|
169
|
+
"--json", "defaultBranchRef",
|
|
170
|
+
"--jq", ".defaultBranchRef.name"
|
|
171
|
+
], { encoding: "utf-8", timeout: 10000 }).trim();
|
|
172
|
+
if (result) {
|
|
173
|
+
defaultBranchCache.set(cacheKey, result);
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
// On failure, return null — callers omit branch segment
|
|
178
|
+
}
|
|
179
|
+
defaultBranchCache.set(cacheKey, null);
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function buildTaskMapping(increments, owner, repo, defaultBranch) {
|
|
184
|
+
if (increments.length === 0) return void 0;
|
|
185
|
+
const firstIncrement = increments[0];
|
|
186
|
+
const tasks = firstIncrement.tasks.map((task) => ({
|
|
187
|
+
id: task.id,
|
|
188
|
+
title: task.title,
|
|
189
|
+
userStories: task.userStories,
|
|
190
|
+
githubIssue: task.githubIssue
|
|
191
|
+
}));
|
|
192
|
+
const branchSegment = defaultBranch ? `/blob/${defaultBranch}` : "";
|
|
193
|
+
return {
|
|
194
|
+
incrementId: firstIncrement.id,
|
|
195
|
+
tasks,
|
|
196
|
+
tasksUrl: `https://github.com/${owner}/${repo}${branchSegment}/.specweave/increments/${firstIncrement.id}/tasks.md`
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
async function findArchitectureDocs(rootDir, specId) {
|
|
200
|
+
const docs = [];
|
|
201
|
+
const archDir = path.join(rootDir, ".specweave/docs/internal/architecture");
|
|
202
|
+
try {
|
|
203
|
+
const adrDir = path.join(archDir, "adr");
|
|
204
|
+
try {
|
|
205
|
+
const adrs = await fs.readdir(adrDir);
|
|
206
|
+
const relatedAdrs = adrs.filter((file) => file.includes(specId.replace("spec-", "")));
|
|
207
|
+
for (const adr of relatedAdrs) {
|
|
208
|
+
docs.push({
|
|
209
|
+
type: "adr",
|
|
210
|
+
path: path.join(adrDir, adr),
|
|
211
|
+
title: adr.replace(".md", "").replace(/-/g, " ")
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
} catch {
|
|
215
|
+
}
|
|
216
|
+
const hlds = await fs.readdir(archDir);
|
|
217
|
+
const relatedHlds = hlds.filter((file) => file.includes("hld") && file.includes(specId.replace("spec-", "")));
|
|
218
|
+
for (const hld of relatedHlds) {
|
|
219
|
+
docs.push({
|
|
220
|
+
type: "hld",
|
|
221
|
+
path: path.join(archDir, hld),
|
|
222
|
+
title: hld.replace(".md", "").replace(/-/g, " ")
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
}
|
|
227
|
+
return docs;
|
|
228
|
+
}
|
|
229
|
+
function buildSourceLinks(incrementId, owner, repo, defaultBranch) {
|
|
230
|
+
if (!incrementId) return void 0;
|
|
231
|
+
const branchSegment = defaultBranch ? `/blob/${defaultBranch}` : "";
|
|
232
|
+
const baseUrl = `https://github.com/${owner}/${repo}${branchSegment}/.specweave`;
|
|
233
|
+
return {
|
|
234
|
+
spec: `${baseUrl}/docs/internal/specs/default/spec-${incrementId.replace(/^\d+-/, "")}.md`,
|
|
235
|
+
plan: `${baseUrl}/increments/${incrementId}/plan.md`,
|
|
236
|
+
tasks: `${baseUrl}/increments/${incrementId}/tasks.md`
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
async function findExistingIssue(client, specId) {
|
|
240
|
+
try {
|
|
241
|
+
const issues = await client.listIssuesInTimeRange("ALL");
|
|
242
|
+
return issues.find((issue) => issue.title.includes(`[${specId}]`) && issue.labels?.some((l) => l.name === "spec")) || null;
|
|
243
|
+
} catch {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
export {
|
|
248
|
+
syncSpecWithEnhancedContent
|
|
249
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { EnhancedContentBuilder } from "../../../dist/src/core/sync/enhanced-content-builder.js";
|
|
2
|
+
import { SpecIncrementMapper } from "../../../dist/src/core/sync/spec-increment-mapper.js";
|
|
3
|
+
import { parseSpecContent } from "../../../dist/src/core/spec-content-sync.js";
|
|
4
|
+
import { readIssueKey } from "./metadata-paths.js";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import * as fs from "fs/promises";
|
|
7
|
+
async function syncSpecToJiraWithEnhancedContent(options) {
|
|
8
|
+
const { specPath, domain, project, dryRun = false, verbose = false } = options;
|
|
9
|
+
try {
|
|
10
|
+
const baseSpec = await parseSpecContent(specPath);
|
|
11
|
+
if (!baseSpec) {
|
|
12
|
+
return {
|
|
13
|
+
success: false,
|
|
14
|
+
action: "error",
|
|
15
|
+
error: "Failed to parse spec content"
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (verbose) {
|
|
19
|
+
console.log(`\u{1F4C4} Parsed spec: ${baseSpec.identifier.compact}`);
|
|
20
|
+
}
|
|
21
|
+
const specId = baseSpec.identifier.full || baseSpec.identifier.compact;
|
|
22
|
+
const rootDir = await findSpecWeaveRoot(specPath);
|
|
23
|
+
const mapper = new SpecIncrementMapper(rootDir);
|
|
24
|
+
const mapping = await mapper.mapSpecToIncrements(specId);
|
|
25
|
+
if (verbose) {
|
|
26
|
+
console.log(`\u{1F517} Found ${mapping.increments.length} related increments`);
|
|
27
|
+
}
|
|
28
|
+
const taskMapping = buildTaskMapping(mapping.increments);
|
|
29
|
+
const architectureDocs = await findArchitectureDocs(rootDir, specId);
|
|
30
|
+
const enhancedSpec = {
|
|
31
|
+
...baseSpec,
|
|
32
|
+
summary: baseSpec.description,
|
|
33
|
+
taskMapping,
|
|
34
|
+
architectureDocs
|
|
35
|
+
};
|
|
36
|
+
const builder = new EnhancedContentBuilder();
|
|
37
|
+
const description = builder.buildExternalDescription(enhancedSpec);
|
|
38
|
+
if (verbose) {
|
|
39
|
+
console.log(`\u{1F4DD} Generated description: ${description.length} characters`);
|
|
40
|
+
}
|
|
41
|
+
if (dryRun) {
|
|
42
|
+
console.log("\u{1F50D} DRY RUN - Would create/update epic with:");
|
|
43
|
+
console.log(` Summary: ${baseSpec.title}`);
|
|
44
|
+
console.log(` Description length: ${description.length}`);
|
|
45
|
+
return {
|
|
46
|
+
success: true,
|
|
47
|
+
action: "no-change",
|
|
48
|
+
tasksLinked: taskMapping?.tasks.length || 0
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (!dryRun && (!domain || !project)) {
|
|
52
|
+
return {
|
|
53
|
+
success: false,
|
|
54
|
+
action: "error",
|
|
55
|
+
error: "JIRA domain/project not specified (required for actual sync)"
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const result = {
|
|
59
|
+
success: true,
|
|
60
|
+
action: dryRun ? "no-change" : "created",
|
|
61
|
+
// Assume create if not dry run
|
|
62
|
+
tasksLinked: taskMapping?.tasks.length || 0
|
|
63
|
+
};
|
|
64
|
+
if (domain && project && !dryRun) {
|
|
65
|
+
// Read real JIRA key from metadata if available
|
|
66
|
+
const rootDir = await findSpecWeaveRoot(specPath);
|
|
67
|
+
const metadataPath = path.join(rootDir, '.specweave', 'increments', specId, 'metadata.json');
|
|
68
|
+
let realKey = null;
|
|
69
|
+
try {
|
|
70
|
+
const meta = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
|
|
71
|
+
realKey = readIssueKey(meta);
|
|
72
|
+
} catch { /* no metadata */ }
|
|
73
|
+
|
|
74
|
+
if (realKey) {
|
|
75
|
+
result.epicKey = realKey;
|
|
76
|
+
result.epicUrl = `https://${domain}/browse/${realKey}`;
|
|
77
|
+
} else {
|
|
78
|
+
// No real JIRA key — return null instead of placeholder
|
|
79
|
+
result.epicKey = null;
|
|
80
|
+
result.epicUrl = null;
|
|
81
|
+
if (verbose) {
|
|
82
|
+
console.log(`\u26A0\uFE0F JIRA API integration not implemented in this file`);
|
|
83
|
+
console.log(` Use jira-spec-sync.ts for actual JIRA synchronization`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
return {
|
|
90
|
+
success: false,
|
|
91
|
+
action: "error",
|
|
92
|
+
error: error.message
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function findSpecWeaveRoot(specPath) {
|
|
97
|
+
let currentDir = path.dirname(specPath);
|
|
98
|
+
while (true) {
|
|
99
|
+
const specweaveDir = path.join(currentDir, ".specweave");
|
|
100
|
+
try {
|
|
101
|
+
await fs.access(specweaveDir);
|
|
102
|
+
return currentDir;
|
|
103
|
+
} catch {
|
|
104
|
+
const parentDir = path.dirname(currentDir);
|
|
105
|
+
if (parentDir === currentDir) {
|
|
106
|
+
throw new Error(".specweave directory not found");
|
|
107
|
+
}
|
|
108
|
+
currentDir = parentDir;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function buildTaskMapping(increments) {
|
|
113
|
+
if (increments.length === 0) return void 0;
|
|
114
|
+
const firstIncrement = increments[0];
|
|
115
|
+
const tasks = firstIncrement.tasks.map((task) => ({
|
|
116
|
+
id: task.id,
|
|
117
|
+
title: task.title,
|
|
118
|
+
userStories: task.userStories
|
|
119
|
+
}));
|
|
120
|
+
return {
|
|
121
|
+
incrementId: firstIncrement.id,
|
|
122
|
+
tasks,
|
|
123
|
+
tasksUrl: `tasks.md`
|
|
124
|
+
// JIRA doesn't support external links in same way
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
async function findArchitectureDocs(rootDir, specId) {
|
|
128
|
+
const docs = [];
|
|
129
|
+
const archDir = path.join(rootDir, ".specweave/docs/internal/architecture");
|
|
130
|
+
try {
|
|
131
|
+
const adrDir = path.join(archDir, "adr");
|
|
132
|
+
try {
|
|
133
|
+
const adrs = await fs.readdir(adrDir);
|
|
134
|
+
const relatedAdrs = adrs.filter((file) => file.includes(specId.replace("spec-", "")));
|
|
135
|
+
for (const adr of relatedAdrs) {
|
|
136
|
+
docs.push({
|
|
137
|
+
type: "adr",
|
|
138
|
+
path: path.join(adrDir, adr),
|
|
139
|
+
title: adr.replace(".md", "").replace(/-/g, " ")
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
return docs;
|
|
147
|
+
}
|
|
148
|
+
export {
|
|
149
|
+
syncSpecToJiraWithEnhancedContent
|
|
150
|
+
};
|