specweave 0.17.16 → 0.17.19
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/CLAUDE.md +405 -2495
- package/README.md +92 -2
- package/dist/locales/de/.gitkeep +0 -0
- package/dist/locales/de/cli.json +108 -0
- package/dist/locales/en/cli.json +287 -0
- package/dist/locales/en/errors.json +7 -0
- package/dist/locales/en/templates.json +6 -0
- package/dist/locales/es/.gitkeep +0 -0
- package/dist/locales/es/cli.json +41 -0
- package/dist/locales/fr/.gitkeep +0 -0
- package/dist/locales/fr/cli.json +108 -0
- package/dist/locales/ja/.gitkeep +0 -0
- package/dist/locales/ja/cli.json +108 -0
- package/dist/locales/ko/.gitkeep +0 -0
- package/dist/locales/ko/cli.json +108 -0
- package/dist/locales/pt/.gitkeep +0 -0
- package/dist/locales/pt/cli.json +108 -0
- package/dist/locales/ru/.gitkeep +0 -0
- package/dist/locales/ru/cli.json +269 -0
- package/dist/locales/zh/.gitkeep +0 -0
- package/dist/locales/zh/cli.json +108 -0
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.d.ts.map +1 -1
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.js +188 -36
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.js +65 -6
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts +54 -0
- package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-ado/lib/ado-status-sync.js +86 -0
- package/dist/plugins/specweave-ado/lib/ado-status-sync.js.map +1 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.d.ts +25 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.js +191 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/duplicate-detector.d.ts +139 -0
- package/dist/plugins/specweave-github/lib/duplicate-detector.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/duplicate-detector.js +389 -0
- package/dist/plugins/specweave-github/lib/duplicate-detector.js.map +1 -0
- package/dist/plugins/specweave-github/lib/enhanced-github-sync.d.ts +26 -0
- package/dist/plugins/specweave-github/lib/enhanced-github-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/enhanced-github-sync.js +249 -0
- package/dist/plugins/specweave-github/lib/enhanced-github-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.d.ts +63 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.js +216 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-client.d.ts +1 -1
- package/dist/plugins/specweave-github/lib/github-client.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client.js +25 -13
- package/dist/plugins/specweave-github/lib/github-client.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts +83 -0
- package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-epic-sync.js +466 -0
- package/dist/plugins/specweave-github/lib/github-epic-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-status-sync.d.ts +43 -0
- package/dist/plugins/specweave-github/lib/github-status-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-status-sync.js +82 -0
- package/dist/plugins/specweave-github/lib/github-status-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/task-sync.d.ts +5 -0
- package/dist/plugins/specweave-github/lib/task-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/task-sync.js +38 -2
- package/dist/plugins/specweave-github/lib/task-sync.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts +28 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js +156 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts +66 -0
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.js +274 -0
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts +56 -0
- package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-status-sync.js +93 -0
- package/dist/plugins/specweave-jira/lib/jira-status-sync.js.map +1 -0
- package/dist/spec-parser.js +629 -0
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +107 -3
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.js +48 -3
- package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
- package/dist/src/core/deduplication/command-deduplicator.d.ts +166 -0
- package/dist/src/core/deduplication/command-deduplicator.d.ts.map +1 -0
- package/dist/src/core/deduplication/command-deduplicator.js +254 -0
- package/dist/src/core/deduplication/command-deduplicator.js.map +1 -0
- package/dist/src/core/living-docs/hierarchy-mapper.d.ts +142 -0
- package/dist/src/core/living-docs/hierarchy-mapper.d.ts.map +1 -0
- package/dist/src/core/living-docs/hierarchy-mapper.js +453 -0
- package/dist/src/core/living-docs/hierarchy-mapper.js.map +1 -0
- package/dist/src/core/living-docs/index.d.ts +10 -84
- package/dist/src/core/living-docs/index.d.ts.map +1 -1
- package/dist/src/core/living-docs/index.js +10 -164
- package/dist/src/core/living-docs/index.js.map +1 -1
- package/dist/src/core/living-docs/spec-distributor.d.ts +106 -0
- package/dist/src/core/living-docs/spec-distributor.d.ts.map +1 -0
- package/dist/src/core/living-docs/spec-distributor.js +823 -0
- package/dist/src/core/living-docs/spec-distributor.js.map +1 -0
- package/dist/src/core/living-docs/types.d.ts +201 -0
- package/dist/src/core/living-docs/types.d.ts.map +1 -0
- package/dist/src/core/living-docs/types.js +15 -0
- package/dist/src/core/living-docs/types.js.map +1 -0
- package/dist/src/core/logging/prompt-logger.d.ts +70 -0
- package/dist/src/core/logging/prompt-logger.d.ts.map +1 -0
- package/dist/src/core/logging/prompt-logger.js +247 -0
- package/dist/src/core/logging/prompt-logger.js.map +1 -0
- package/dist/src/core/status-line/status-line-manager.d.ts +15 -24
- package/dist/src/core/status-line/status-line-manager.d.ts.map +1 -1
- package/dist/src/core/status-line/status-line-manager.js +33 -70
- package/dist/src/core/status-line/status-line-manager.js.map +1 -1
- package/dist/src/core/status-line/types.d.ts +19 -31
- package/dist/src/core/status-line/types.d.ts.map +1 -1
- package/dist/src/core/status-line/types.js +5 -9
- package/dist/src/core/status-line/types.js.map +1 -1
- package/dist/src/core/sync/conflict-resolver.d.ts +66 -0
- package/dist/src/core/sync/conflict-resolver.d.ts.map +1 -0
- package/dist/src/core/sync/conflict-resolver.js +108 -0
- package/dist/src/core/sync/conflict-resolver.js.map +1 -0
- package/dist/src/core/sync/enhanced-content-builder.d.ts +55 -0
- package/dist/src/core/sync/enhanced-content-builder.d.ts.map +1 -0
- package/dist/src/core/sync/enhanced-content-builder.js +202 -0
- package/dist/src/core/sync/enhanced-content-builder.js.map +1 -0
- package/dist/src/core/sync/label-detector.d.ts +66 -0
- package/dist/src/core/sync/label-detector.d.ts.map +1 -0
- package/dist/src/core/sync/label-detector.js +211 -0
- package/dist/src/core/sync/label-detector.js.map +1 -0
- package/dist/src/core/sync/retry-logic.d.ts +64 -0
- package/dist/src/core/sync/retry-logic.d.ts.map +1 -0
- package/dist/src/core/sync/retry-logic.js +165 -0
- package/dist/src/core/sync/retry-logic.js.map +1 -0
- package/dist/src/core/sync/spec-content-sync.d.ts +88 -0
- package/dist/src/core/sync/spec-content-sync.d.ts.map +1 -0
- package/dist/src/core/sync/spec-content-sync.js +5 -0
- package/dist/src/core/sync/spec-content-sync.js.map +1 -0
- package/dist/src/core/sync/spec-increment-mapper.d.ts +100 -0
- package/dist/src/core/sync/spec-increment-mapper.d.ts.map +1 -0
- package/dist/src/core/sync/spec-increment-mapper.js +424 -0
- package/dist/src/core/sync/spec-increment-mapper.js.map +1 -0
- package/dist/src/core/sync/status-cache.d.ts +91 -0
- package/dist/src/core/sync/status-cache.d.ts.map +1 -0
- package/dist/src/core/sync/status-cache.js +140 -0
- package/dist/src/core/sync/status-cache.js.map +1 -0
- package/dist/src/core/sync/status-mapper.d.ts +69 -0
- package/dist/src/core/sync/status-mapper.d.ts.map +1 -0
- package/dist/src/core/sync/status-mapper.js +90 -0
- package/dist/src/core/sync/status-mapper.js.map +1 -0
- package/dist/src/core/sync/status-sync-engine.d.ts +162 -0
- package/dist/src/core/sync/status-sync-engine.d.ts.map +1 -0
- package/dist/src/core/sync/status-sync-engine.js +347 -0
- package/dist/src/core/sync/status-sync-engine.js.map +1 -0
- package/dist/src/core/sync/sync-event-logger.d.ts +99 -0
- package/dist/src/core/sync/sync-event-logger.d.ts.map +1 -0
- package/dist/src/core/sync/sync-event-logger.js +103 -0
- package/dist/src/core/sync/sync-event-logger.js.map +1 -0
- package/dist/src/core/sync/types.d.ts +52 -0
- package/dist/src/core/sync/types.d.ts.map +1 -0
- package/dist/src/core/sync/types.js +5 -0
- package/dist/src/core/sync/types.js.map +1 -0
- package/dist/src/core/sync/workflow-detector.d.ts +95 -0
- package/dist/src/core/sync/workflow-detector.d.ts.map +1 -0
- package/dist/src/core/sync/workflow-detector.js +175 -0
- package/dist/src/core/sync/workflow-detector.js.map +1 -0
- package/dist/src/core/types/config.d.ts +51 -0
- package/dist/src/core/types/config.d.ts.map +1 -1
- package/dist/src/core/types/config.js +47 -0
- package/dist/src/core/types/config.js.map +1 -1
- package/dist/src/core/types/increment-metadata.d.ts +4 -0
- package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
- package/dist/src/core/types/increment-metadata.js.map +1 -1
- package/dist/src/utils/github-url.d.ts +53 -0
- package/dist/src/utils/github-url.d.ts.map +1 -0
- package/dist/src/utils/github-url.js +90 -0
- package/dist/src/utils/github-url.js.map +1 -0
- package/dist/src/utils/spec-parser.d.ts +145 -0
- package/dist/src/utils/spec-parser.d.ts.map +1 -0
- package/dist/src/utils/spec-parser.js +640 -0
- package/dist/src/utils/spec-parser.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +1 -1
- package/plugins/specweave/agents/pm/AGENT.md +160 -13
- package/plugins/specweave/agents/pm/templates/increment-spec.md +158 -0
- package/plugins/specweave/agents/pm/templates/living-docs-spec.md +113 -0
- package/plugins/specweave/commands/specweave-done.md +163 -0
- package/plugins/specweave/commands/specweave.md +70 -405
- package/plugins/specweave/hooks/hooks.json +4 -0
- package/plugins/specweave/hooks/lib/sync-spec-content.sh +2 -2
- package/plugins/specweave/hooks/lib/update-status-line.sh +79 -111
- package/plugins/specweave/hooks/post-increment-planning.sh +133 -37
- package/plugins/specweave/hooks/pre-command-deduplication.sh +86 -0
- package/plugins/specweave/lib/hooks/sync-living-docs.js +139 -34
- package/plugins/specweave/lib/hooks/sync-living-docs.ts +234 -38
- package/plugins/specweave/skills/SKILLS-INDEX.md +4 -24
- package/plugins/specweave/skills/increment-planner/SKILL.md +94 -0
- package/plugins/specweave/skills/increment-work-router/SKILL.md +466 -0
- package/plugins/specweave-ado/commands/specweave-ado-sync-spec.md +1 -1
- package/plugins/specweave-ado/lib/ado-spec-content-sync.js +49 -5
- package/plugins/specweave-ado/lib/ado-spec-content-sync.ts +72 -6
- package/plugins/specweave-ado/lib/ado-status-sync.js +80 -0
- package/plugins/specweave-ado/lib/ado-status-sync.ts +121 -0
- package/plugins/specweave-ado/lib/enhanced-ado-sync.js +170 -0
- package/plugins/specweave-github/commands/specweave-github-cleanup-duplicates.md +205 -0
- package/plugins/specweave-github/commands/specweave-github-sync-epic.md +248 -0
- package/plugins/specweave-github/commands/specweave-github-sync-spec.md +1 -1
- package/plugins/specweave-github/hooks/post-task-completion.sh +32 -0
- package/plugins/specweave-github/lib/duplicate-detector.js +370 -0
- package/plugins/specweave-github/lib/duplicate-detector.ts +525 -0
- package/plugins/specweave-github/lib/enhanced-github-sync.js +220 -0
- package/plugins/specweave-github/lib/enhanced-github-sync.ts +322 -0
- package/plugins/specweave-github/lib/epic-content-builder.js +227 -0
- package/plugins/specweave-github/lib/epic-content-builder.ts +317 -0
- package/plugins/specweave-github/lib/github-client.js +21 -10
- package/plugins/specweave-github/lib/github-client.ts +27 -16
- package/plugins/specweave-github/lib/github-epic-sync.js +488 -0
- package/plugins/specweave-github/lib/github-epic-sync.ts +715 -0
- package/plugins/specweave-github/lib/github-status-sync.js +71 -0
- package/plugins/specweave-github/lib/github-status-sync.ts +107 -0
- package/plugins/specweave-github/lib/task-sync.js +33 -2
- package/plugins/specweave-github/lib/task-sync.ts +44 -2
- package/plugins/specweave-jira/commands/specweave-jira-sync-epic.md +267 -0
- package/plugins/specweave-jira/commands/specweave-jira-sync-spec.md +1 -1
- package/plugins/specweave-jira/lib/enhanced-jira-sync.js +134 -0
- package/plugins/specweave-jira/lib/enhanced-jira-sync.ts +196 -0
- package/plugins/specweave-jira/lib/jira-epic-sync.js +304 -0
- package/plugins/specweave-jira/lib/jira-epic-sync.ts +459 -0
- package/plugins/specweave-jira/lib/jira-status-sync.js +79 -0
- package/plugins/specweave-jira/lib/jira-status-sync.ts +139 -0
- package/plugins/specweave-release/commands/specweave-release-platform.md +1 -1
- package/plugins/specweave-release/hooks/post-task-completion.sh +2 -2
- package/src/templates/AGENTS.md.template +88 -1
- package/src/templates/CLAUDE.md.template +49 -0
- package/plugins/specweave/skills/increment-quality-judge/SKILL.md +0 -524
- package/plugins/specweave/skills/plugin-installer/SKILL.md +0 -353
|
@@ -0,0 +1,134 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced JIRA Spec Content Sync
|
|
3
|
+
*
|
|
4
|
+
* Uses EnhancedContentBuilder and SpecIncrementMapper for rich epic descriptions.
|
|
5
|
+
*
|
|
6
|
+
* NOTE: This version focuses on enhanced content building.
|
|
7
|
+
* Actual JIRA API integration requires jira-spec-sync.ts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { EnhancedContentBuilder, EnhancedSpecContent } from '../../../src/core/sync/enhanced-content-builder.js';
|
|
11
|
+
import { SpecIncrementMapper, TaskInfo } from '../../../src/core/sync/spec-increment-mapper.js';
|
|
12
|
+
import { parseSpecContent } from '../../../src/core/spec-content-sync.js';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import * as fs from 'fs/promises';
|
|
15
|
+
|
|
16
|
+
export interface EnhancedJiraSyncOptions {
|
|
17
|
+
specPath: string;
|
|
18
|
+
domain?: string;
|
|
19
|
+
project?: string;
|
|
20
|
+
dryRun?: boolean;
|
|
21
|
+
verbose?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface EnhancedJiraSyncResult {
|
|
25
|
+
success: boolean;
|
|
26
|
+
action: 'created' | 'updated' | 'no-change' | 'error';
|
|
27
|
+
epicKey?: string;
|
|
28
|
+
epicUrl?: string;
|
|
29
|
+
error?: string;
|
|
30
|
+
tasksLinked?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Enhanced sync with rich content including task mappings
|
|
35
|
+
*/
|
|
36
|
+
export async function syncSpecToJiraWithEnhancedContent(
|
|
37
|
+
options: EnhancedJiraSyncOptions
|
|
38
|
+
): Promise<EnhancedJiraSyncResult> {
|
|
39
|
+
const { specPath, domain, project, 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
|
+
}
|
|
65
|
+
|
|
66
|
+
// 3. Build enhanced spec content
|
|
67
|
+
const taskMapping = buildTaskMapping(mapping.increments);
|
|
68
|
+
const architectureDocs = await findArchitectureDocs(rootDir, specId);
|
|
69
|
+
|
|
70
|
+
const enhancedSpec: EnhancedSpecContent = {
|
|
71
|
+
...baseSpec,
|
|
72
|
+
summary: baseSpec.description,
|
|
73
|
+
taskMapping,
|
|
74
|
+
architectureDocs
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// 4. Build external description
|
|
78
|
+
const builder = new EnhancedContentBuilder();
|
|
79
|
+
const description = builder.buildExternalDescription(enhancedSpec);
|
|
80
|
+
|
|
81
|
+
if (verbose) {
|
|
82
|
+
console.log(`📝 Generated description: ${description.length} characters`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (dryRun) {
|
|
86
|
+
console.log('🔍 DRY RUN - Would create/update epic with:');
|
|
87
|
+
console.log(` Summary: ${baseSpec.title}`);
|
|
88
|
+
console.log(` Description length: ${description.length}`);
|
|
89
|
+
return {
|
|
90
|
+
success: true,
|
|
91
|
+
action: 'no-change',
|
|
92
|
+
tasksLinked: taskMapping?.tasks.length || 0
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 5. Validate domain/project (if not dry run)
|
|
97
|
+
if (!dryRun && (!domain || !project)) {
|
|
98
|
+
return {
|
|
99
|
+
success: false,
|
|
100
|
+
action: 'error',
|
|
101
|
+
error: 'JIRA domain/project not specified (required for actual sync)',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// For now, we focus on content building
|
|
106
|
+
// Actual JIRA API integration is in jira-spec-sync.ts
|
|
107
|
+
const result: EnhancedJiraSyncResult = {
|
|
108
|
+
success: true,
|
|
109
|
+
action: dryRun ? 'no-change' : 'created', // Assume create if not dry run
|
|
110
|
+
tasksLinked: taskMapping?.tasks.length || 0
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (domain && project && !dryRun) {
|
|
114
|
+
// In a real implementation, this would use JIRA API
|
|
115
|
+
// For now, just simulate success
|
|
116
|
+
result.epicKey = `SPEC-001`; // Placeholder
|
|
117
|
+
result.epicUrl = `https://${domain}/browse/SPEC-001`;
|
|
118
|
+
|
|
119
|
+
if (verbose) {
|
|
120
|
+
console.log(`⚠️ JIRA API integration not implemented in this file`);
|
|
121
|
+
console.log(` Use jira-spec-sync.ts for actual JIRA synchronization`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return result;
|
|
126
|
+
} catch (error: any) {
|
|
127
|
+
return {
|
|
128
|
+
success: false,
|
|
129
|
+
action: 'error',
|
|
130
|
+
error: error.message
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Helper functions (similar to GitHub sync)
|
|
136
|
+
|
|
137
|
+
async function findSpecWeaveRoot(specPath: string): Promise<string> {
|
|
138
|
+
let currentDir = path.dirname(specPath);
|
|
139
|
+
|
|
140
|
+
while (true) {
|
|
141
|
+
const specweaveDir = path.join(currentDir, '.specweave');
|
|
142
|
+
try {
|
|
143
|
+
await fs.access(specweaveDir);
|
|
144
|
+
return currentDir;
|
|
145
|
+
} catch {
|
|
146
|
+
const parentDir = path.dirname(currentDir);
|
|
147
|
+
if (parentDir === currentDir) {
|
|
148
|
+
throw new Error('.specweave directory not found');
|
|
149
|
+
}
|
|
150
|
+
currentDir = parentDir;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function buildTaskMapping(increments: any[]): any {
|
|
156
|
+
if (increments.length === 0) return undefined;
|
|
157
|
+
|
|
158
|
+
const firstIncrement = increments[0];
|
|
159
|
+
const tasks = firstIncrement.tasks.map((task: TaskInfo) => ({
|
|
160
|
+
id: task.id,
|
|
161
|
+
title: task.title,
|
|
162
|
+
userStories: task.userStories
|
|
163
|
+
}));
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
incrementId: firstIncrement.id,
|
|
167
|
+
tasks,
|
|
168
|
+
tasksUrl: `tasks.md` // JIRA doesn't support external links in same way
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function findArchitectureDocs(rootDir: string, specId: string): Promise<any[]> {
|
|
173
|
+
const docs: any[] = [];
|
|
174
|
+
const archDir = path.join(rootDir, '.specweave/docs/internal/architecture');
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const adrDir = path.join(archDir, 'adr');
|
|
178
|
+
try {
|
|
179
|
+
const adrs = await fs.readdir(adrDir);
|
|
180
|
+
const relatedAdrs = adrs.filter(file => file.includes(specId.replace('spec-', '')));
|
|
181
|
+
|
|
182
|
+
for (const adr of relatedAdrs) {
|
|
183
|
+
docs.push({
|
|
184
|
+
type: 'adr',
|
|
185
|
+
path: path.join(adrDir, adr),
|
|
186
|
+
title: adr.replace('.md', '').replace(/-/g, ' ')
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
} catch {}
|
|
190
|
+
} catch {}
|
|
191
|
+
|
|
192
|
+
return docs;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// NOTE: findExistingEpic not needed in this simplified version
|
|
196
|
+
// Real JIRA API integration is in jira-spec-sync.ts
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import * as fs from "fs-extra";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as yaml from "yaml";
|
|
4
|
+
class JiraEpicSync {
|
|
5
|
+
constructor(client, specsDir, projectKey) {
|
|
6
|
+
this.client = client;
|
|
7
|
+
this.specsDir = specsDir;
|
|
8
|
+
this.projectKey = projectKey;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Sync Epic folder to JIRA (Epic + Stories)
|
|
12
|
+
*/
|
|
13
|
+
async syncEpicToJira(epicId) {
|
|
14
|
+
console.log(`
|
|
15
|
+
\u{1F504} Syncing Epic ${epicId} to JIRA...`);
|
|
16
|
+
const epicFolder = await this.findEpicFolder(epicId);
|
|
17
|
+
if (!epicFolder) {
|
|
18
|
+
throw new Error(`Epic ${epicId} not found in ${this.specsDir}`);
|
|
19
|
+
}
|
|
20
|
+
const readmePath = path.join(epicFolder, "README.md");
|
|
21
|
+
const epicData = await this.parseEpicReadme(readmePath);
|
|
22
|
+
console.log(` \u{1F4E6} Epic: ${epicData.title}`);
|
|
23
|
+
console.log(` \u{1F4CA} Increments: ${epicData.total_increments}`);
|
|
24
|
+
let epicKey = epicData.external_tools.jira.key;
|
|
25
|
+
let epicUrl = epicData.external_tools.jira.url;
|
|
26
|
+
if (!epicKey) {
|
|
27
|
+
console.log(` \u{1F680} Creating JIRA Epic...`);
|
|
28
|
+
const epic = await this.createEpic(epicData);
|
|
29
|
+
epicKey = epic.key;
|
|
30
|
+
epicUrl = epic.url;
|
|
31
|
+
console.log(` \u2705 Created Epic ${epicKey}`);
|
|
32
|
+
await this.updateEpicReadme(readmePath, {
|
|
33
|
+
type: "epic",
|
|
34
|
+
key: epicKey,
|
|
35
|
+
url: epicUrl
|
|
36
|
+
});
|
|
37
|
+
} else {
|
|
38
|
+
console.log(` \u267B\uFE0F Updating existing Epic ${epicKey}...`);
|
|
39
|
+
await this.updateEpic(epicKey, epicData);
|
|
40
|
+
console.log(` \u2705 Updated Epic ${epicKey}`);
|
|
41
|
+
}
|
|
42
|
+
let storiesCreated = 0;
|
|
43
|
+
let storiesUpdated = 0;
|
|
44
|
+
console.log(`
|
|
45
|
+
\u{1F4DD} Syncing ${epicData.increments.length} increments...`);
|
|
46
|
+
for (const increment of epicData.increments) {
|
|
47
|
+
const incrementFile = path.join(epicFolder, `${increment.id}.md`);
|
|
48
|
+
if (!await fs.pathExists(incrementFile)) {
|
|
49
|
+
console.log(` \u26A0\uFE0F Increment file not found: ${increment.id}.md`);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const incrementData = await this.parseIncrementFile(incrementFile);
|
|
53
|
+
const existingStory = increment.external.jira;
|
|
54
|
+
if (!existingStory) {
|
|
55
|
+
const storyKey = await this.createStory(
|
|
56
|
+
epicData.id,
|
|
57
|
+
incrementData,
|
|
58
|
+
epicKey
|
|
59
|
+
);
|
|
60
|
+
storiesCreated++;
|
|
61
|
+
console.log(` \u2705 Created Story ${storyKey} for ${increment.id}`);
|
|
62
|
+
await this.updateIncrementExternalLink(
|
|
63
|
+
readmePath,
|
|
64
|
+
incrementFile,
|
|
65
|
+
increment.id,
|
|
66
|
+
storyKey
|
|
67
|
+
);
|
|
68
|
+
} else {
|
|
69
|
+
await this.updateStory(
|
|
70
|
+
epicData.id,
|
|
71
|
+
existingStory,
|
|
72
|
+
incrementData,
|
|
73
|
+
epicKey
|
|
74
|
+
);
|
|
75
|
+
storiesUpdated++;
|
|
76
|
+
console.log(` \u267B\uFE0F Updated Story ${existingStory} for ${increment.id}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
console.log(`
|
|
80
|
+
\u2705 Epic sync complete!`);
|
|
81
|
+
console.log(` Epic: ${epicUrl}`);
|
|
82
|
+
console.log(` Stories created: ${storiesCreated}`);
|
|
83
|
+
console.log(` Stories updated: ${storiesUpdated}`);
|
|
84
|
+
return {
|
|
85
|
+
epicKey,
|
|
86
|
+
epicUrl,
|
|
87
|
+
storiesCreated,
|
|
88
|
+
storiesUpdated
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Find Epic folder by ID (FS-001 or just 001)
|
|
93
|
+
*/
|
|
94
|
+
async findEpicFolder(epicId) {
|
|
95
|
+
const normalizedId = epicId.startsWith("FS-") ? epicId : `FS-${epicId.padStart(3, "0")}`;
|
|
96
|
+
const folders = await fs.readdir(this.specsDir);
|
|
97
|
+
for (const folder of folders) {
|
|
98
|
+
if (folder.startsWith(normalizedId)) {
|
|
99
|
+
return path.join(this.specsDir, folder);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Parse Epic README.md to extract frontmatter
|
|
106
|
+
*/
|
|
107
|
+
async parseEpicReadme(readmePath) {
|
|
108
|
+
const content = await fs.readFile(readmePath, "utf-8");
|
|
109
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
110
|
+
if (!match) {
|
|
111
|
+
throw new Error("Epic README.md missing YAML frontmatter");
|
|
112
|
+
}
|
|
113
|
+
const frontmatter = yaml.parse(match[1]);
|
|
114
|
+
return frontmatter;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Parse increment file to extract title and overview
|
|
118
|
+
*/
|
|
119
|
+
async parseIncrementFile(filePath) {
|
|
120
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
121
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
122
|
+
let frontmatter = { id: "", epic: "" };
|
|
123
|
+
let bodyContent = content;
|
|
124
|
+
if (match) {
|
|
125
|
+
frontmatter = yaml.parse(match[1]);
|
|
126
|
+
bodyContent = content.slice(match[0].length).trim();
|
|
127
|
+
}
|
|
128
|
+
const titleMatch = bodyContent.match(/^#\s+(.+)$/m);
|
|
129
|
+
const title = titleMatch ? titleMatch[1].trim() : frontmatter.id || path.basename(filePath, ".md");
|
|
130
|
+
const overviewMatch = bodyContent.match(/^#[^\n]+\n+([^\n]+)/);
|
|
131
|
+
const overview = overviewMatch ? overviewMatch[1].trim() : "No overview available";
|
|
132
|
+
return {
|
|
133
|
+
id: frontmatter.id,
|
|
134
|
+
title,
|
|
135
|
+
overview,
|
|
136
|
+
content: bodyContent,
|
|
137
|
+
frontmatter
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Create JIRA Epic
|
|
142
|
+
*/
|
|
143
|
+
async createEpic(epic) {
|
|
144
|
+
const summary = `[${epic.id}] ${epic.title}`;
|
|
145
|
+
const description = `Epic: ${epic.title}
|
|
146
|
+
|
|
147
|
+
Progress: ${epic.completed_increments}/${epic.total_increments} increments (${epic.progress})
|
|
148
|
+
|
|
149
|
+
Priority: ${epic.priority}
|
|
150
|
+
Status: ${epic.status}`;
|
|
151
|
+
const issueData = {
|
|
152
|
+
issueType: "Epic",
|
|
153
|
+
summary,
|
|
154
|
+
description,
|
|
155
|
+
priority: this.mapPriorityToJira(epic.priority),
|
|
156
|
+
labels: ["epic-sync", epic.id.toLowerCase()]
|
|
157
|
+
};
|
|
158
|
+
const issue = await this.client.createIssue(issueData, this.projectKey);
|
|
159
|
+
return {
|
|
160
|
+
key: issue.key,
|
|
161
|
+
url: issue.self.replace("/rest/api/3/issue/", "/browse/")
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Update JIRA Epic
|
|
166
|
+
*/
|
|
167
|
+
async updateEpic(epicKey, epic) {
|
|
168
|
+
const summary = `[${epic.id}] ${epic.title}`;
|
|
169
|
+
const description = `Epic: ${epic.title}
|
|
170
|
+
|
|
171
|
+
Progress: ${epic.completed_increments}/${epic.total_increments} increments (${epic.progress})
|
|
172
|
+
|
|
173
|
+
Priority: ${epic.priority}
|
|
174
|
+
Status: ${epic.status}`;
|
|
175
|
+
await this.client.updateIssue({
|
|
176
|
+
key: epicKey,
|
|
177
|
+
summary,
|
|
178
|
+
description,
|
|
179
|
+
priority: this.mapPriorityToJira(epic.priority),
|
|
180
|
+
labels: ["epic-sync", epic.id.toLowerCase()]
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Create JIRA Story for increment
|
|
185
|
+
*/
|
|
186
|
+
async createStory(epicId, increment, epicKey) {
|
|
187
|
+
const summary = `[${epicId}] ${increment.title}`;
|
|
188
|
+
const description = `${increment.overview}
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
**Increment**: ${increment.id}
|
|
193
|
+
**Epic**: ${epicId} (${epicKey})
|
|
194
|
+
|
|
195
|
+
\u{1F916} Auto-created by SpecWeave Epic Sync`;
|
|
196
|
+
const issueData = {
|
|
197
|
+
issueType: "Story",
|
|
198
|
+
summary,
|
|
199
|
+
description,
|
|
200
|
+
epicKey,
|
|
201
|
+
// Link to Epic via Epic Link field
|
|
202
|
+
labels: ["increment", "epic-sync"]
|
|
203
|
+
};
|
|
204
|
+
const issue = await this.client.createIssue(issueData, this.projectKey);
|
|
205
|
+
return issue.key;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Update JIRA Story for increment
|
|
209
|
+
*/
|
|
210
|
+
async updateStory(epicId, storyKey, increment, epicKey) {
|
|
211
|
+
const summary = `[${epicId}] ${increment.title}`;
|
|
212
|
+
const description = `${increment.overview}
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
**Increment**: ${increment.id}
|
|
217
|
+
**Epic**: ${epicId} (${epicKey})
|
|
218
|
+
|
|
219
|
+
\u{1F916} Auto-updated by SpecWeave Epic Sync`;
|
|
220
|
+
await this.client.updateIssue({
|
|
221
|
+
key: storyKey,
|
|
222
|
+
summary,
|
|
223
|
+
description,
|
|
224
|
+
labels: ["increment", "epic-sync"]
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Map SpecWeave priority to JIRA priority
|
|
229
|
+
*/
|
|
230
|
+
mapPriorityToJira(priority) {
|
|
231
|
+
const map = {
|
|
232
|
+
P0: "Highest",
|
|
233
|
+
P1: "High",
|
|
234
|
+
P2: "Medium",
|
|
235
|
+
P3: "Low"
|
|
236
|
+
};
|
|
237
|
+
return map[priority] || "Medium";
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Update Epic README.md with JIRA Epic key
|
|
241
|
+
*/
|
|
242
|
+
async updateEpicReadme(readmePath, jira) {
|
|
243
|
+
const content = await fs.readFile(readmePath, "utf-8");
|
|
244
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
245
|
+
if (!match) {
|
|
246
|
+
throw new Error("Epic README.md missing YAML frontmatter");
|
|
247
|
+
}
|
|
248
|
+
const frontmatter = yaml.parse(match[1]);
|
|
249
|
+
frontmatter.external_tools.jira = jira;
|
|
250
|
+
const newFrontmatter = yaml.stringify(frontmatter);
|
|
251
|
+
const newContent = content.replace(
|
|
252
|
+
/^---\n[\s\S]*?\n---/,
|
|
253
|
+
`---
|
|
254
|
+
${newFrontmatter}---`
|
|
255
|
+
);
|
|
256
|
+
await fs.writeFile(readmePath, newContent, "utf-8");
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Update increment external link in both Epic README and increment file
|
|
260
|
+
*/
|
|
261
|
+
async updateIncrementExternalLink(readmePath, incrementFile, incrementId, storyKey) {
|
|
262
|
+
const storyUrl = `https://${this.client["credentials"].domain}/browse/${storyKey}`;
|
|
263
|
+
const readmeContent = await fs.readFile(readmePath, "utf-8");
|
|
264
|
+
const readmeMatch = readmeContent.match(/^---\n([\s\S]*?)\n---/);
|
|
265
|
+
if (readmeMatch) {
|
|
266
|
+
const frontmatter = yaml.parse(readmeMatch[1]);
|
|
267
|
+
const increment = frontmatter.increments.find(
|
|
268
|
+
(inc) => inc.id === incrementId
|
|
269
|
+
);
|
|
270
|
+
if (increment) {
|
|
271
|
+
increment.external.jira = storyKey;
|
|
272
|
+
const newFrontmatter = yaml.stringify(frontmatter);
|
|
273
|
+
const newContent = readmeContent.replace(
|
|
274
|
+
/^---\n[\s\S]*?\n---/,
|
|
275
|
+
`---
|
|
276
|
+
${newFrontmatter}---`
|
|
277
|
+
);
|
|
278
|
+
await fs.writeFile(readmePath, newContent, "utf-8");
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const incrementContent = await fs.readFile(incrementFile, "utf-8");
|
|
282
|
+
const incrementMatch = incrementContent.match(/^---\n([\s\S]*?)\n---/);
|
|
283
|
+
if (incrementMatch) {
|
|
284
|
+
const frontmatter = yaml.parse(incrementMatch[1]);
|
|
285
|
+
if (!frontmatter.external) {
|
|
286
|
+
frontmatter.external = {};
|
|
287
|
+
}
|
|
288
|
+
frontmatter.external.jira = {
|
|
289
|
+
story: storyKey,
|
|
290
|
+
url: storyUrl
|
|
291
|
+
};
|
|
292
|
+
const newFrontmatter = yaml.stringify(frontmatter);
|
|
293
|
+
const newContent = incrementContent.replace(
|
|
294
|
+
/^---\n[\s\S]*?\n---/,
|
|
295
|
+
`---
|
|
296
|
+
${newFrontmatter}---`
|
|
297
|
+
);
|
|
298
|
+
await fs.writeFile(incrementFile, newContent, "utf-8");
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
export {
|
|
303
|
+
JiraEpicSync
|
|
304
|
+
};
|