specweave 0.17.15 → 0.18.0
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/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-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-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/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 +451 -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/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/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/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 +77 -0
- package/dist/src/core/sync/enhanced-content-builder.d.ts.map +1 -0
- package/dist/src/core/sync/enhanced-content-builder.js +199 -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-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/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.map +1 -1
- package/dist/src/core/types/config.js +31 -0
- package/dist/src/core/types/config.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/plugin-validator.d.ts +9 -0
- package/dist/src/utils/plugin-validator.d.ts.map +1 -1
- package/dist/src/utils/plugin-validator.js +86 -19
- package/dist/src/utils/plugin-validator.js.map +1 -1
- 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/package.json +1 -1
- package/plugins/specweave/agents/pm/AGENT.md +1 -1
- 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/hooks/lib/update-status-line.sh +79 -111
- package/plugins/specweave/hooks/post-increment-planning.sh +107 -35
- 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/skills/plugin-validator/SKILL.md +16 -13
- 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-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/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/github-client.js +21 -10
- package/plugins/specweave-github/lib/github-client.ts +27 -16
- package/plugins/specweave-github/lib/github-epic-sync.js +489 -0
- package/plugins/specweave-github/lib/github-epic-sync.ts +690 -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/lib/enhanced-jira-sync.ts.disabled +222 -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/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,71 @@
|
|
|
1
|
+
import { Octokit } from "@octokit/rest";
|
|
2
|
+
class GitHubStatusSync {
|
|
3
|
+
constructor(token, owner, repo) {
|
|
4
|
+
this.octokit = new Octokit({ auth: token });
|
|
5
|
+
this.owner = owner;
|
|
6
|
+
this.repo = repo;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Get current status from GitHub issue
|
|
10
|
+
*
|
|
11
|
+
* @param issueNumber - GitHub issue number
|
|
12
|
+
* @returns External status with state and labels
|
|
13
|
+
*/
|
|
14
|
+
async getStatus(issueNumber) {
|
|
15
|
+
const response = await this.octokit.rest.issues.get({
|
|
16
|
+
owner: this.owner,
|
|
17
|
+
repo: this.repo,
|
|
18
|
+
issue_number: issueNumber
|
|
19
|
+
});
|
|
20
|
+
const labels = response.data.labels.map((label) => typeof label === "string" ? label : label.name).filter(Boolean);
|
|
21
|
+
return {
|
|
22
|
+
state: response.data.state,
|
|
23
|
+
labels
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Update GitHub issue status
|
|
28
|
+
*
|
|
29
|
+
* @param issueNumber - GitHub issue number
|
|
30
|
+
* @param status - New status (state and labels)
|
|
31
|
+
*/
|
|
32
|
+
async updateStatus(issueNumber, status) {
|
|
33
|
+
const updateData = {
|
|
34
|
+
owner: this.owner,
|
|
35
|
+
repo: this.repo,
|
|
36
|
+
issue_number: issueNumber,
|
|
37
|
+
state: status.state
|
|
38
|
+
};
|
|
39
|
+
if (status.labels && status.labels.length > 0) {
|
|
40
|
+
updateData.labels = status.labels;
|
|
41
|
+
}
|
|
42
|
+
await this.octokit.rest.issues.update(updateData);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Post status change comment to GitHub issue
|
|
46
|
+
*
|
|
47
|
+
* @param issueNumber - GitHub issue number
|
|
48
|
+
* @param oldStatus - Previous SpecWeave status
|
|
49
|
+
* @param newStatus - New SpecWeave status
|
|
50
|
+
*/
|
|
51
|
+
async postStatusComment(issueNumber, oldStatus, newStatus) {
|
|
52
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
53
|
+
const body = `\u{1F504} **Status Update**
|
|
54
|
+
|
|
55
|
+
SpecWeave increment status changed:
|
|
56
|
+
- **From**: ${oldStatus}
|
|
57
|
+
- **To**: ${newStatus}
|
|
58
|
+
- **When**: ${timestamp}
|
|
59
|
+
|
|
60
|
+
This update was automatically synchronized by SpecWeave.`;
|
|
61
|
+
await this.octokit.rest.issues.createComment({
|
|
62
|
+
owner: this.owner,
|
|
63
|
+
repo: this.repo,
|
|
64
|
+
issue_number: issueNumber,
|
|
65
|
+
body
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export {
|
|
70
|
+
GitHubStatusSync
|
|
71
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Status Sync
|
|
3
|
+
*
|
|
4
|
+
* Synchronizes SpecWeave increment statuses with GitHub issues.
|
|
5
|
+
*
|
|
6
|
+
* Responsibilities:
|
|
7
|
+
* - Get current status from GitHub issue
|
|
8
|
+
* - Update GitHub issue state and labels
|
|
9
|
+
* - Post status change comments
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Octokit } from '@octokit/rest';
|
|
13
|
+
|
|
14
|
+
export interface ExternalStatus {
|
|
15
|
+
state: string;
|
|
16
|
+
labels?: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class GitHubStatusSync {
|
|
20
|
+
private octokit: Octokit;
|
|
21
|
+
private owner: string;
|
|
22
|
+
private repo: string;
|
|
23
|
+
|
|
24
|
+
constructor(token: string, owner: string, repo: string) {
|
|
25
|
+
this.octokit = new Octokit({ auth: token });
|
|
26
|
+
this.owner = owner;
|
|
27
|
+
this.repo = repo;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get current status from GitHub issue
|
|
32
|
+
*
|
|
33
|
+
* @param issueNumber - GitHub issue number
|
|
34
|
+
* @returns External status with state and labels
|
|
35
|
+
*/
|
|
36
|
+
public async getStatus(issueNumber: number): Promise<ExternalStatus> {
|
|
37
|
+
const response = await this.octokit.rest.issues.get({
|
|
38
|
+
owner: this.owner,
|
|
39
|
+
repo: this.repo,
|
|
40
|
+
issue_number: issueNumber
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const labels = response.data.labels
|
|
44
|
+
.map((label: any) => (typeof label === 'string' ? label : label.name))
|
|
45
|
+
.filter(Boolean);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
state: response.data.state,
|
|
49
|
+
labels
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Update GitHub issue status
|
|
55
|
+
*
|
|
56
|
+
* @param issueNumber - GitHub issue number
|
|
57
|
+
* @param status - New status (state and labels)
|
|
58
|
+
*/
|
|
59
|
+
public async updateStatus(
|
|
60
|
+
issueNumber: number,
|
|
61
|
+
status: ExternalStatus
|
|
62
|
+
): Promise<void> {
|
|
63
|
+
const updateData: any = {
|
|
64
|
+
owner: this.owner,
|
|
65
|
+
repo: this.repo,
|
|
66
|
+
issue_number: issueNumber,
|
|
67
|
+
state: status.state
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Add labels if provided
|
|
71
|
+
if (status.labels && status.labels.length > 0) {
|
|
72
|
+
updateData.labels = status.labels;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
await this.octokit.rest.issues.update(updateData);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Post status change comment to GitHub issue
|
|
80
|
+
*
|
|
81
|
+
* @param issueNumber - GitHub issue number
|
|
82
|
+
* @param oldStatus - Previous SpecWeave status
|
|
83
|
+
* @param newStatus - New SpecWeave status
|
|
84
|
+
*/
|
|
85
|
+
public async postStatusComment(
|
|
86
|
+
issueNumber: number,
|
|
87
|
+
oldStatus: string,
|
|
88
|
+
newStatus: string
|
|
89
|
+
): Promise<void> {
|
|
90
|
+
const timestamp = new Date().toISOString();
|
|
91
|
+
const body = `🔄 **Status Update**
|
|
92
|
+
|
|
93
|
+
SpecWeave increment status changed:
|
|
94
|
+
- **From**: ${oldStatus}
|
|
95
|
+
- **To**: ${newStatus}
|
|
96
|
+
- **When**: ${timestamp}
|
|
97
|
+
|
|
98
|
+
This update was automatically synchronized by SpecWeave.`;
|
|
99
|
+
|
|
100
|
+
await this.octokit.rest.issues.createComment({
|
|
101
|
+
owner: this.owner,
|
|
102
|
+
repo: this.repo,
|
|
103
|
+
issue_number: issueNumber,
|
|
104
|
+
body
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -61,9 +61,10 @@ class TaskSync {
|
|
|
61
61
|
console.log(` \u2705 Milestone #${milestone.number}: ${milestone.title}`);
|
|
62
62
|
console.log(`
|
|
63
63
|
\u{1F3AF} Creating epic issue for increment ${metadata.id}...`);
|
|
64
|
+
const issuePrefix = this.getIssuePrefix(metadata.id);
|
|
64
65
|
const epicBody = this.generateEpicBody(metadata, tasks);
|
|
65
66
|
const epic = await this.client.createEpicIssue(
|
|
66
|
-
`[
|
|
67
|
+
`[${issuePrefix}] ${metadata.title}`,
|
|
67
68
|
epicBody,
|
|
68
69
|
milestone.title,
|
|
69
70
|
["increment", "specweave", metadata.priority.toLowerCase()]
|
|
@@ -328,6 +329,35 @@ This task blocks:
|
|
|
328
329
|
const metadataPath = path.join(this.incrementPath, ".metadata.yaml");
|
|
329
330
|
fs.writeFileSync(metadataPath, yaml.dump(metadata), "utf-8");
|
|
330
331
|
}
|
|
332
|
+
/**
|
|
333
|
+
* Get issue prefix from metadata.json (with created date)
|
|
334
|
+
* Returns FS-YY-MM-DD format or fallback to FS-UNKNOWN
|
|
335
|
+
*/
|
|
336
|
+
getIssuePrefix(incrementId) {
|
|
337
|
+
const metadataJsonPath = path.join(this.incrementPath, "metadata.json");
|
|
338
|
+
if (fs.existsSync(metadataJsonPath)) {
|
|
339
|
+
try {
|
|
340
|
+
const metadataContent = fs.readFileSync(metadataJsonPath, "utf-8");
|
|
341
|
+
const metadata = JSON.parse(metadataContent);
|
|
342
|
+
if (metadata.created) {
|
|
343
|
+
const dateMatch = metadata.created.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
344
|
+
if (dateMatch) {
|
|
345
|
+
const year = dateMatch[1].slice(2);
|
|
346
|
+
const month = dateMatch[2];
|
|
347
|
+
const day = dateMatch[3];
|
|
348
|
+
return `FS-${year}-${month}-${day}`;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
} catch (error) {
|
|
352
|
+
console.warn(`\u26A0\uFE0F Could not parse metadata.json: ${error}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const incrementNumber = incrementId.match(/^(\d+)/)?.[1];
|
|
356
|
+
if (incrementNumber) {
|
|
357
|
+
return `FS-${incrementNumber}`;
|
|
358
|
+
}
|
|
359
|
+
return "FS-UNKNOWN";
|
|
360
|
+
}
|
|
331
361
|
/**
|
|
332
362
|
* Save sync mapping
|
|
333
363
|
*/
|
|
@@ -363,10 +393,11 @@ This task blocks:
|
|
|
363
393
|
* Print dry run summary
|
|
364
394
|
*/
|
|
365
395
|
printDryRunSummary(tasks, metadata) {
|
|
396
|
+
const issuePrefix = this.getIssuePrefix(metadata.id);
|
|
366
397
|
console.log(`
|
|
367
398
|
\u{1F4CA} Dry Run Summary:`);
|
|
368
399
|
console.log(` Milestone: ${this.getMilestoneTitle(metadata)}`);
|
|
369
|
-
console.log(` Epic: [
|
|
400
|
+
console.log(` Epic: [${issuePrefix}] ${metadata.title}`);
|
|
370
401
|
console.log(` Task Issues: ${tasks.length}`);
|
|
371
402
|
console.log(`
|
|
372
403
|
\u{1F4DD} Would create:`);
|
|
@@ -81,9 +81,13 @@ export class TaskSync {
|
|
|
81
81
|
|
|
82
82
|
// 6. Create epic issue
|
|
83
83
|
console.log(`\n🎯 Creating epic issue for increment ${metadata.id}...`);
|
|
84
|
+
|
|
85
|
+
// Get issue prefix from metadata.json (with created date)
|
|
86
|
+
const issuePrefix = this.getIssuePrefix(metadata.id);
|
|
87
|
+
|
|
84
88
|
const epicBody = this.generateEpicBody(metadata, tasks);
|
|
85
89
|
const epic = await this.client.createEpicIssue(
|
|
86
|
-
`[
|
|
90
|
+
`[${issuePrefix}] ${metadata.title}`,
|
|
87
91
|
epicBody,
|
|
88
92
|
milestone.title,
|
|
89
93
|
['increment', 'specweave', metadata.priority.toLowerCase()]
|
|
@@ -348,6 +352,43 @@ ${task.description}
|
|
|
348
352
|
fs.writeFileSync(metadataPath, yaml.dump(metadata), 'utf-8');
|
|
349
353
|
}
|
|
350
354
|
|
|
355
|
+
/**
|
|
356
|
+
* Get issue prefix from metadata.json (with created date)
|
|
357
|
+
* Returns FS-YY-MM-DD format or fallback to FS-UNKNOWN
|
|
358
|
+
*/
|
|
359
|
+
private getIssuePrefix(incrementId: string): string {
|
|
360
|
+
const metadataJsonPath = path.join(this.incrementPath, 'metadata.json');
|
|
361
|
+
|
|
362
|
+
// Try to read metadata.json for created date
|
|
363
|
+
if (fs.existsSync(metadataJsonPath)) {
|
|
364
|
+
try {
|
|
365
|
+
const metadataContent = fs.readFileSync(metadataJsonPath, 'utf-8');
|
|
366
|
+
const metadata = JSON.parse(metadataContent);
|
|
367
|
+
|
|
368
|
+
if (metadata.created) {
|
|
369
|
+
// Extract YY-MM-DD from date (e.g., "2025-11-12T12:46:00Z" -> "25-11-12")
|
|
370
|
+
const dateMatch = metadata.created.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
371
|
+
if (dateMatch) {
|
|
372
|
+
const year = dateMatch[1].slice(2); // "2025" -> "25"
|
|
373
|
+
const month = dateMatch[2]; // "11"
|
|
374
|
+
const day = dateMatch[3]; // "12"
|
|
375
|
+
return `FS-${year}-${month}-${day}`;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
} catch (error) {
|
|
379
|
+
console.warn(`⚠️ Could not parse metadata.json: ${error}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Fallback: use increment number if available
|
|
384
|
+
const incrementNumber = incrementId.match(/^(\d+)/)?.[1];
|
|
385
|
+
if (incrementNumber) {
|
|
386
|
+
return `FS-${incrementNumber}`;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return 'FS-UNKNOWN';
|
|
390
|
+
}
|
|
391
|
+
|
|
351
392
|
/**
|
|
352
393
|
* Save sync mapping
|
|
353
394
|
*/
|
|
@@ -390,9 +431,10 @@ ${task.description}
|
|
|
390
431
|
* Print dry run summary
|
|
391
432
|
*/
|
|
392
433
|
private printDryRunSummary(tasks: Task[], metadata: IncrementMetadata): void {
|
|
434
|
+
const issuePrefix = this.getIssuePrefix(metadata.id);
|
|
393
435
|
console.log(`\n📊 Dry Run Summary:`);
|
|
394
436
|
console.log(` Milestone: ${this.getMilestoneTitle(metadata)}`);
|
|
395
|
-
console.log(` Epic: [
|
|
437
|
+
console.log(` Epic: [${issuePrefix}] ${metadata.title}`);
|
|
396
438
|
console.log(` Task Issues: ${tasks.length}`);
|
|
397
439
|
console.log(`\n📝 Would create:`);
|
|
398
440
|
tasks.forEach((task, i) => {
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: specweave-jira-sync-epic
|
|
3
|
+
description: Sync SpecWeave Epic folder to JIRA (Epic + Stories with Epic Link). Implements Universal Hierarchy architecture - Epic → JIRA Epic, Increments → Stories.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Sync Epic to JIRA (Universal Hierarchy)
|
|
7
|
+
|
|
8
|
+
**Architecture**: Hierarchical sync using Epic folder structure
|
|
9
|
+
|
|
10
|
+
- **Epic (FS-001)** → **JIRA Epic**
|
|
11
|
+
- **Increment (0001-core-framework)** → **JIRA Story** (Epic Link field)
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
/specweave-jira:sync-epic <epic-id> [--project <project-key>]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## What It Does
|
|
20
|
+
|
|
21
|
+
**Hierarchical Sync Process**:
|
|
22
|
+
|
|
23
|
+
1. **Load Epic folder** from `.specweave/docs/internal/specs/FS-XXX-name/`
|
|
24
|
+
2. **Parse Epic README.md** to get Epic metadata (title, increments, status)
|
|
25
|
+
3. **Create or update JIRA Epic**:
|
|
26
|
+
- Summary: `[FS-001] Epic Title`
|
|
27
|
+
- Description: Epic overview + progress stats
|
|
28
|
+
- Priority: Mapped from P0→Highest, P1→High, etc.
|
|
29
|
+
- Labels: `epic-sync`, `fs-001`
|
|
30
|
+
4. **Sync each increment as JIRA Story**:
|
|
31
|
+
- Summary: `[INC-0001-core-framework] Title`
|
|
32
|
+
- Description: Increment overview
|
|
33
|
+
- Epic Link: Links to Epic via `epicKey` field
|
|
34
|
+
- Labels: `increment`, `epic-sync`
|
|
35
|
+
5. **Update frontmatter** in Epic README.md and increment files
|
|
36
|
+
|
|
37
|
+
## Examples
|
|
38
|
+
|
|
39
|
+
### Sync Epic FS-001 (Core Framework Architecture)
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
/specweave-jira:sync-epic FS-001 --project SPEC
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Output**:
|
|
46
|
+
```
|
|
47
|
+
🔄 Syncing Epic FS-001 to JIRA...
|
|
48
|
+
📦 Epic: Core Framework Architecture
|
|
49
|
+
📊 Increments: 4
|
|
50
|
+
🚀 Creating JIRA Epic...
|
|
51
|
+
✅ Created Epic SPEC-100
|
|
52
|
+
|
|
53
|
+
📝 Syncing 4 increments...
|
|
54
|
+
✅ Created Story SPEC-101 for 0001-core-framework
|
|
55
|
+
✅ Created Story SPEC-102 for 0002-core-enhancements
|
|
56
|
+
✅ Created Story SPEC-103 for 0004-plugin-architecture
|
|
57
|
+
✅ Created Story SPEC-104 for 0005-cross-platform-cli
|
|
58
|
+
|
|
59
|
+
✅ Epic sync complete!
|
|
60
|
+
Epic: https://mycompany.atlassian.net/browse/SPEC-100
|
|
61
|
+
Stories created: 4
|
|
62
|
+
Stories updated: 0
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Sync Epic with short ID
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
/specweave-jira:sync-epic 031 --project SPEC
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Re-sync Epic (updates existing)
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
/specweave-jira:sync-epic FS-001 --project SPEC
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Output**:
|
|
78
|
+
```
|
|
79
|
+
🔄 Syncing Epic FS-001 to JIRA...
|
|
80
|
+
♻️ Updating existing Epic SPEC-100...
|
|
81
|
+
✅ Updated Epic SPEC-100
|
|
82
|
+
|
|
83
|
+
📝 Syncing 4 increments...
|
|
84
|
+
♻️ Updated Story SPEC-101 for 0001-core-framework
|
|
85
|
+
♻️ Updated Story SPEC-102 for 0002-core-enhancements
|
|
86
|
+
♻️ Updated Story SPEC-103 for 0004-plugin-architecture
|
|
87
|
+
♻️ Updated Story SPEC-104 for 0005-cross-platform-cli
|
|
88
|
+
|
|
89
|
+
✅ Epic sync complete!
|
|
90
|
+
Epic: https://mycompany.atlassian.net/browse/SPEC-100
|
|
91
|
+
Stories created: 0
|
|
92
|
+
Stories updated: 4
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Arguments
|
|
96
|
+
|
|
97
|
+
- `<epic-id>` - Epic ID (e.g., `FS-001` or just `001`)
|
|
98
|
+
- `--project <key>` - JIRA project key (e.g., `SPEC`, `PROJ`, `DEV`)
|
|
99
|
+
|
|
100
|
+
## What Gets Created
|
|
101
|
+
|
|
102
|
+
### JIRA Epic (Epic-level)
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
Key: SPEC-100
|
|
106
|
+
Summary: [FS-001] Core Framework Architecture
|
|
107
|
+
|
|
108
|
+
Description:
|
|
109
|
+
Epic: Core Framework Architecture
|
|
110
|
+
|
|
111
|
+
Progress: 4/4 increments (100%)
|
|
112
|
+
|
|
113
|
+
Priority: P0
|
|
114
|
+
Status: complete
|
|
115
|
+
|
|
116
|
+
Labels: epic-sync, fs-001
|
|
117
|
+
Priority: Highest (P0 → Highest)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### JIRA Story (Increment-level)
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
Key: SPEC-101
|
|
124
|
+
Summary: [INC-0001-core-framework] Core Framework
|
|
125
|
+
|
|
126
|
+
Description:
|
|
127
|
+
Foundation framework with CLI, plugin system, and agent architecture...
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
**Increment**: 0001-core-framework
|
|
132
|
+
**Epic**: SPEC-100
|
|
133
|
+
|
|
134
|
+
🤖 Auto-created by SpecWeave Epic Sync
|
|
135
|
+
|
|
136
|
+
Epic Link: SPEC-100 (linked via Epic Link field)
|
|
137
|
+
Labels: increment, epic-sync
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Frontmatter Updates
|
|
141
|
+
|
|
142
|
+
### Epic README.md (after sync)
|
|
143
|
+
|
|
144
|
+
```yaml
|
|
145
|
+
---
|
|
146
|
+
id: FS-001
|
|
147
|
+
title: "Core Framework Architecture"
|
|
148
|
+
external_tools:
|
|
149
|
+
jira:
|
|
150
|
+
type: epic
|
|
151
|
+
key: SPEC-100 # ← Added
|
|
152
|
+
url: https://mycompany.atlassian.net/browse/SPEC-100 # ← Added
|
|
153
|
+
increments:
|
|
154
|
+
- id: 0001-core-framework
|
|
155
|
+
external:
|
|
156
|
+
jira: SPEC-101 # ← Added
|
|
157
|
+
- id: 0002-core-enhancements
|
|
158
|
+
external:
|
|
159
|
+
jira: SPEC-102 # ← Added
|
|
160
|
+
---
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Increment file (0001-core-framework.md)
|
|
164
|
+
|
|
165
|
+
```yaml
|
|
166
|
+
---
|
|
167
|
+
id: 0001-core-framework
|
|
168
|
+
epic: FS-001
|
|
169
|
+
external:
|
|
170
|
+
jira:
|
|
171
|
+
story: SPEC-101 # ← Added
|
|
172
|
+
url: https://mycompany.atlassian.net/browse/SPEC-101 # ← Added
|
|
173
|
+
---
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Benefits
|
|
177
|
+
|
|
178
|
+
✅ **Hierarchical tracking**: JIRA Epics group related Stories
|
|
179
|
+
✅ **Epic-level progress**: See completion in Epic details
|
|
180
|
+
✅ **Automatic linking**: Epic Link field links Stories to Epic
|
|
181
|
+
✅ **Idempotent**: Safe to re-run (updates existing Epic/Stories)
|
|
182
|
+
✅ **Brownfield-ready**: Links existing JIRA Epics/Stories
|
|
183
|
+
|
|
184
|
+
## Requirements
|
|
185
|
+
|
|
186
|
+
1. **JIRA credentials** in `.env`:
|
|
187
|
+
```
|
|
188
|
+
JIRA_DOMAIN=mycompany.atlassian.net
|
|
189
|
+
JIRA_EMAIL=user@mycompany.com
|
|
190
|
+
JIRA_API_TOKEN=your-api-token
|
|
191
|
+
```
|
|
192
|
+
2. **Project exists** in JIRA
|
|
193
|
+
3. **Write access** to project (for creating Epics/Stories)
|
|
194
|
+
4. **Epic folder exists** at `.specweave/docs/internal/specs/FS-XXX-name/`
|
|
195
|
+
|
|
196
|
+
## Architecture: Why Epic Link?
|
|
197
|
+
|
|
198
|
+
**JIRA's Hierarchy**:
|
|
199
|
+
- JIRA Epics = Top-level grouping
|
|
200
|
+
- JIRA Stories = Implementation work
|
|
201
|
+
- Epic Link field = Links Stories to Epic
|
|
202
|
+
|
|
203
|
+
**Comparison with GitHub/ADO**:
|
|
204
|
+
- GitHub: Epic → Milestone, Increment → Issue (Milestone link)
|
|
205
|
+
- JIRA: Epic → Epic, Increment → Story (Epic Link field)
|
|
206
|
+
- ADO: Epic → Feature, Increment → User Story (Parent link)
|
|
207
|
+
|
|
208
|
+
All three implement the same Universal Hierarchy, just with different fields/terminology.
|
|
209
|
+
|
|
210
|
+
## Priority Mapping
|
|
211
|
+
|
|
212
|
+
| SpecWeave | JIRA |
|
|
213
|
+
|-----------|------|
|
|
214
|
+
| P0 | Highest |
|
|
215
|
+
| P1 | High |
|
|
216
|
+
| P2 | Medium |
|
|
217
|
+
| P3 | Low |
|
|
218
|
+
|
|
219
|
+
## Troubleshooting
|
|
220
|
+
|
|
221
|
+
**"Epic FS-001 not found"**:
|
|
222
|
+
- Check Epic folder exists: `ls .specweave/docs/internal/specs/`
|
|
223
|
+
- Verify Epic ID format: `FS-001-epic-name/`
|
|
224
|
+
|
|
225
|
+
**"Epic README.md missing YAML frontmatter"**:
|
|
226
|
+
- Ensure Epic was migrated with `migrate-to-epic-folders.ts`
|
|
227
|
+
- Frontmatter must start with `---` on line 1
|
|
228
|
+
|
|
229
|
+
**"Failed to create JIRA Epic"**:
|
|
230
|
+
- Check credentials: `echo $JIRA_API_TOKEN`
|
|
231
|
+
- Verify project exists in JIRA
|
|
232
|
+
- Check write permissions
|
|
233
|
+
|
|
234
|
+
**"Epic Link field not found"**:
|
|
235
|
+
- Some JIRA instances use custom field for Epic Link
|
|
236
|
+
- Check with JIRA admin for custom field ID
|
|
237
|
+
- May need configuration update
|
|
238
|
+
|
|
239
|
+
## Related Commands
|
|
240
|
+
|
|
241
|
+
- `/specweave-github:sync-epic` - Sync to GitHub Milestone + Issues
|
|
242
|
+
- `/specweave-ado:sync-epic` - Sync to ADO Feature + User Stories
|
|
243
|
+
- `/specweave-jira:sync-spec` - OLD (flat spec → epic) - DEPRECATED
|
|
244
|
+
|
|
245
|
+
## Implementation
|
|
246
|
+
|
|
247
|
+
**File**: `plugins/specweave-jira/lib/jira-epic-sync.ts`
|
|
248
|
+
|
|
249
|
+
**Core Class**: `JiraEpicSync`
|
|
250
|
+
|
|
251
|
+
**Methods**:
|
|
252
|
+
- `syncEpicToJira(epicId)` - Main sync logic
|
|
253
|
+
- `createEpic(epic)` - Create JIRA Epic
|
|
254
|
+
- `updateEpic(key, epic)` - Update existing Epic
|
|
255
|
+
- `createStory(increment, epicKey)` - Create JIRA Story
|
|
256
|
+
- `updateStory(key, increment, epicKey)` - Update existing Story
|
|
257
|
+
- `updateEpicReadme(path, jira)` - Update frontmatter
|
|
258
|
+
- `updateIncrementExternalLink(...)` - Update increment frontmatter
|
|
259
|
+
|
|
260
|
+
## Next Steps
|
|
261
|
+
|
|
262
|
+
After syncing Epic to JIRA:
|
|
263
|
+
|
|
264
|
+
1. **View Epic**: Open in JIRA browser
|
|
265
|
+
2. **List Stories**: Filter by Epic Link field
|
|
266
|
+
3. **Track completion**: JIRA shows Epic progress automatically
|
|
267
|
+
4. **Link to Sprint**: Drag Stories into Sprint planning
|