specweave 0.28.17 → 0.28.20
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/dist/plugins/specweave-ado/lib/ado-board-resolver.d.ts +94 -0
- package/dist/plugins/specweave-ado/lib/ado-board-resolver.d.ts.map +1 -0
- package/dist/plugins/specweave-ado/lib/ado-board-resolver.js +219 -0
- package/dist/plugins/specweave-ado/lib/ado-board-resolver.js.map +1 -0
- package/dist/plugins/specweave-ado/lib/ado-spec-sync.d.ts +16 -0
- package/dist/plugins/specweave-ado/lib/ado-spec-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-sync.js +63 -3
- package/dist/plugins/specweave-ado/lib/ado-spec-sync.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts +12 -3
- package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-status-sync.js +37 -3
- package/dist/plugins/specweave-ado/lib/ado-status-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts +6 -11
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.js +6 -11
- package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-increment-sync-cli.d.ts +21 -0
- package/dist/plugins/specweave-github/lib/github-increment-sync-cli.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-increment-sync-cli.js +445 -0
- package/dist/plugins/specweave-github/lib/github-increment-sync-cli.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-status-sync.d.ts +10 -0
- package/dist/plugins/specweave-github/lib/github-status-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-status-sync.js +40 -2
- package/dist/plugins/specweave-github/lib/github-status-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/increment-issue-builder.d.ts +94 -0
- package/dist/plugins/specweave-github/lib/increment-issue-builder.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/increment-issue-builder.js +369 -0
- package/dist/plugins/specweave-github/lib/increment-issue-builder.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-board-resolver.d.ts +50 -0
- package/dist/plugins/specweave-jira/lib/jira-board-resolver.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-board-resolver.js +84 -0
- package/dist/plugins/specweave-jira/lib/jira-board-resolver.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-spec-sync.d.ts +12 -0
- package/dist/plugins/specweave-jira/lib/jira-spec-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-spec-sync.js +57 -5
- package/dist/plugins/specweave-jira/lib/jira-spec-sync.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts +5 -1
- package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-status-sync.js +12 -4
- package/dist/plugins/specweave-jira/lib/jira-status-sync.js.map +1 -1
- package/dist/src/cli/commands/import-external.d.ts.map +1 -1
- package/dist/src/cli/commands/import-external.js +12 -7
- package/dist/src/cli/commands/import-external.js.map +1 -1
- package/dist/src/cli/helpers/init/external-import.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/external-import.js +308 -36
- package/dist/src/cli/helpers/init/external-import.js.map +1 -1
- package/dist/src/cli/helpers/init/jira-ado-auto-detect.d.ts +115 -0
- package/dist/src/cli/helpers/init/jira-ado-auto-detect.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/jira-ado-auto-detect.js +590 -0
- package/dist/src/cli/helpers/init/jira-ado-auto-detect.js.map +1 -0
- package/dist/src/cli/helpers/issue-tracker/ado-area-selection.d.ts +65 -0
- package/dist/src/cli/helpers/issue-tracker/ado-area-selection.d.ts.map +1 -0
- package/dist/src/cli/helpers/issue-tracker/ado-area-selection.js +278 -0
- package/dist/src/cli/helpers/issue-tracker/ado-area-selection.js.map +1 -0
- package/dist/src/cli/helpers/issue-tracker/jira-board-selection.d.ts +64 -0
- package/dist/src/cli/helpers/issue-tracker/jira-board-selection.d.ts.map +1 -0
- package/dist/src/cli/helpers/issue-tracker/jira-board-selection.js +251 -0
- package/dist/src/cli/helpers/issue-tracker/jira-board-selection.js.map +1 -0
- package/dist/src/config/types.d.ts +6 -6
- package/dist/src/core/ac-test-validator-cli.js +4 -1
- package/dist/src/core/ac-test-validator-cli.js.map +1 -1
- package/dist/src/core/ac-test-validator.d.ts.map +1 -1
- package/dist/src/core/ac-test-validator.js +4 -1
- package/dist/src/core/ac-test-validator.js.map +1 -1
- package/dist/src/core/background/index.d.ts +11 -0
- package/dist/src/core/background/index.d.ts.map +1 -0
- package/dist/src/core/background/index.js +11 -0
- package/dist/src/core/background/index.js.map +1 -0
- package/dist/src/core/background/job-manager.d.ts +65 -0
- package/dist/src/core/background/job-manager.d.ts.map +1 -0
- package/dist/src/core/background/job-manager.js +192 -0
- package/dist/src/core/background/job-manager.js.map +1 -0
- package/dist/src/core/background/types.d.ts +59 -0
- package/dist/src/core/background/types.d.ts.map +1 -0
- package/dist/src/core/background/types.js +8 -0
- package/dist/src/core/background/types.js.map +1 -0
- package/dist/src/core/repo-structure/multi-repo-configurator.d.ts +25 -0
- package/dist/src/core/repo-structure/multi-repo-configurator.d.ts.map +1 -0
- package/dist/src/core/repo-structure/multi-repo-configurator.js +614 -0
- package/dist/src/core/repo-structure/multi-repo-configurator.js.map +1 -0
- package/dist/src/core/repo-structure/repo-initializer.d.ts +40 -0
- package/dist/src/core/repo-structure/repo-initializer.d.ts.map +1 -0
- package/dist/src/core/repo-structure/repo-initializer.js +252 -0
- package/dist/src/core/repo-structure/repo-initializer.js.map +1 -0
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts +3 -37
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.js +23 -803
- package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
- package/dist/src/core/types/increment-metadata.d.ts +75 -0
- package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
- package/dist/src/core/types/spec-metadata.d.ts +2 -0
- package/dist/src/core/types/spec-metadata.d.ts.map +1 -1
- package/dist/src/core/types/sync-profile.d.ts +137 -5
- package/dist/src/core/types/sync-profile.d.ts.map +1 -1
- package/dist/src/core/types/sync-profile.js +63 -0
- package/dist/src/core/types/sync-profile.js.map +1 -1
- package/dist/src/importers/external-importer.d.ts +25 -0
- package/dist/src/importers/external-importer.d.ts.map +1 -1
- package/dist/src/importers/github-importer.d.ts.map +1 -1
- package/dist/src/importers/github-importer.js +5 -3
- package/dist/src/importers/github-importer.js.map +1 -1
- package/dist/src/importers/import-coordinator.d.ts +20 -0
- package/dist/src/importers/import-coordinator.d.ts.map +1 -1
- package/dist/src/importers/import-coordinator.js.map +1 -1
- package/dist/src/importers/item-converter.d.ts +51 -0
- package/dist/src/importers/item-converter.d.ts.map +1 -1
- package/dist/src/importers/item-converter.js +39 -12
- package/dist/src/importers/item-converter.js.map +1 -1
- package/dist/src/init/architecture/types.d.ts +2 -2
- package/dist/src/init/compliance/types.d.ts +1 -1
- package/dist/src/init/repo/types.d.ts +1 -1
- package/dist/src/living-docs/fs-id-allocator.d.ts +72 -3
- package/dist/src/living-docs/fs-id-allocator.d.ts.map +1 -1
- package/dist/src/living-docs/fs-id-allocator.js +142 -16
- package/dist/src/living-docs/fs-id-allocator.js.map +1 -1
- package/dist/src/locales/de/cli.json +14 -0
- package/dist/src/locales/es/cli.json +14 -0
- package/dist/src/locales/fr/cli.json +14 -0
- package/dist/src/locales/ja/cli.json +14 -0
- package/dist/src/locales/ko/cli.json +14 -0
- package/dist/src/locales/pt/cli.json +14 -0
- package/dist/src/locales/ru/cli.json +14 -0
- package/dist/src/locales/zh/cli.json +14 -0
- package/dist/src/utils/chalk-fallback.d.ts +38 -0
- package/dist/src/utils/chalk-fallback.d.ts.map +1 -0
- package/dist/src/utils/chalk-fallback.js +118 -0
- package/dist/src/utils/chalk-fallback.js.map +1 -0
- package/dist/src/utils/project-id-generator.d.ts +127 -0
- package/dist/src/utils/project-id-generator.d.ts.map +1 -0
- package/dist/src/utils/project-id-generator.js +228 -0
- package/dist/src/utils/project-id-generator.js.map +1 -0
- package/package.json +1 -1
- package/plugins/specweave/agents/pm/AGENT.md +202 -0
- package/plugins/specweave/commands/specweave-import-external.md +5 -3
- package/plugins/specweave/commands/specweave-jobs.md +160 -0
- package/plugins/specweave/commands/specweave-sync-docs.md +6 -2
- package/plugins/specweave/hooks/pre-task-completion.sh +35 -17
- package/plugins/specweave/lib/vendor/core/ac-test-validator-cli.d.ts +16 -0
- package/plugins/specweave/lib/vendor/core/ac-test-validator-cli.js +121 -0
- package/plugins/specweave/lib/vendor/core/ac-test-validator-cli.js.map +1 -0
- package/plugins/specweave/lib/vendor/core/ac-test-validator.d.ts +111 -0
- package/plugins/specweave/lib/vendor/core/ac-test-validator.js +295 -0
- package/plugins/specweave/lib/vendor/core/ac-test-validator.js.map +1 -0
- package/plugins/specweave/lib/vendor/core/types/increment-metadata.d.ts +75 -0
- package/plugins/specweave/lib/vendor/utils/chalk-fallback.d.ts +38 -0
- package/plugins/specweave/lib/vendor/utils/chalk-fallback.js +118 -0
- package/plugins/specweave/lib/vendor/utils/chalk-fallback.js.map +1 -0
- package/plugins/specweave/lib/vendor/utils/fs-native.d.ts +179 -0
- package/plugins/specweave/lib/vendor/utils/fs-native.js +319 -0
- package/plugins/specweave/lib/vendor/utils/fs-native.js.map +1 -0
- package/plugins/specweave/skills/code-reviewer/SKILL.md +1 -1
- package/plugins/specweave/skills/docs-updater/SKILL.md +61 -0
- package/plugins/specweave/skills/increment-planner/SKILL.md +10 -335
- package/plugins/specweave/skills/increment-planner/templates/metadata.json +13 -0
- package/plugins/specweave/skills/increment-planner/templates/plan.md +50 -0
- package/plugins/specweave/skills/increment-planner/templates/spec-multi-project.md +86 -0
- package/plugins/specweave/skills/increment-planner/templates/spec-single-project.md +50 -0
- package/plugins/specweave/skills/increment-planner/templates/tasks-multi-project.md +86 -0
- package/plugins/specweave/skills/increment-planner/templates/tasks-single-project.md +48 -0
- package/plugins/specweave-ado/commands/specweave-ado-import-areas.md +358 -0
- package/plugins/specweave-ado/lib/ado-spec-sync.js +59 -3
- package/plugins/specweave-ado/lib/ado-spec-sync.ts +72 -3
- package/plugins/specweave-ado/lib/ado-status-sync.js +35 -3
- package/plugins/specweave-ado/lib/ado-status-sync.ts +48 -4
- package/plugins/specweave-alternatives/skills/architecture-alternatives/SKILL.md +1 -0
- package/plugins/specweave-alternatives/skills/bmad-method/SKILL.md +1 -0
- package/plugins/specweave-core/skills/code-quality/SKILL.md +1 -0
- package/plugins/specweave-core/skills/design-patterns/SKILL.md +1 -0
- package/plugins/specweave-core/skills/software-architecture/SKILL.md +1 -0
- package/plugins/specweave-github/commands/specweave-github-cleanup-duplicates.md +14 -10
- package/plugins/specweave-github/commands/specweave-github-sync.md +57 -0
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +74 -0
- package/plugins/specweave-github/lib/github-feature-sync.ts +6 -11
- package/plugins/specweave-github/lib/github-increment-sync-cli.js +456 -0
- package/plugins/specweave-github/lib/github-increment-sync-cli.ts +588 -0
- package/plugins/specweave-github/lib/github-status-sync.js +37 -1
- package/plugins/specweave-github/lib/github-status-sync.ts +60 -4
- package/plugins/specweave-github/lib/increment-issue-builder.js +389 -0
- package/plugins/specweave-github/lib/increment-issue-builder.ts +502 -0
- package/plugins/specweave-github/skills/github-issue-standard/SKILL.md +19 -24
- package/plugins/specweave-infrastructure/agents/observability-engineer/AGENT.md +15 -23
- package/plugins/specweave-jira/commands/specweave-jira-import-boards.md +331 -0
- package/plugins/specweave-jira/lib/jira-spec-sync.js +53 -5
- package/plugins/specweave-jira/lib/jira-spec-sync.ts +87 -7
- package/plugins/specweave-jira/lib/jira-status-sync.js +9 -3
- package/plugins/specweave-jira/lib/jira-status-sync.ts +15 -6
- package/plugins/specweave-ml/agents/data-scientist/AGENT.md +16 -20
- package/plugins/specweave-ml/agents/ml-engineer/AGENT.md +18 -19
- package/plugins/specweave-ml/skills/{ml-pipeline-workflow → mlops-dag-builder}/SKILL.md +18 -14
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +111 -0
- package/plugins/specweave-ui/skills/browser-automation/SKILL.md +1 -1
- package/plugins/specweave-ui/skills/ui-testing/SKILL.md +10 -122
- package/dist/plugins/specweave-github/lib/epic-content-builder.d.ts +0 -70
- package/dist/plugins/specweave-github/lib/epic-content-builder.d.ts.map +0 -1
- package/dist/plugins/specweave-github/lib/epic-content-builder.js +0 -258
- package/dist/plugins/specweave-github/lib/epic-content-builder.js.map +0 -1
- package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts +0 -83
- package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts.map +0 -1
- package/dist/plugins/specweave-github/lib/github-epic-sync.js +0 -466
- package/dist/plugins/specweave-github/lib/github-epic-sync.js.map +0 -1
- package/plugins/specweave-github/lib/epic-content-builder.js +0 -265
- package/plugins/specweave-github/lib/epic-content-builder.ts +0 -376
- package/plugins/specweave-github/lib/github-epic-sync.js +0 -488
- package/plugins/specweave-github/lib/github-epic-sync.ts +0 -715
|
@@ -53,6 +53,9 @@ export class GitHubStatusSync {
|
|
|
53
53
|
/**
|
|
54
54
|
* Update GitHub issue status
|
|
55
55
|
*
|
|
56
|
+
* Preserves existing labels that are not status-related.
|
|
57
|
+
* Only replaces labels that start with "status:" prefix.
|
|
58
|
+
*
|
|
56
59
|
* @param issueNumber - GitHub issue number
|
|
57
60
|
* @param status - New status (state and labels)
|
|
58
61
|
*/
|
|
@@ -60,21 +63,74 @@ export class GitHubStatusSync {
|
|
|
60
63
|
issueNumber: number,
|
|
61
64
|
status: ExternalStatus
|
|
62
65
|
): Promise<void> {
|
|
63
|
-
const updateData:
|
|
66
|
+
const updateData: {
|
|
67
|
+
owner: string;
|
|
68
|
+
repo: string;
|
|
69
|
+
issue_number: number;
|
|
70
|
+
state: 'open' | 'closed';
|
|
71
|
+
labels?: string[];
|
|
72
|
+
} = {
|
|
64
73
|
owner: this.owner,
|
|
65
74
|
repo: this.repo,
|
|
66
75
|
issue_number: issueNumber,
|
|
67
|
-
state: status.state
|
|
76
|
+
state: status.state as 'open' | 'closed'
|
|
68
77
|
};
|
|
69
78
|
|
|
70
|
-
//
|
|
79
|
+
// Merge labels - preserve non-status labels, replace status labels
|
|
71
80
|
if (status.labels && status.labels.length > 0) {
|
|
72
|
-
|
|
81
|
+
// Fetch current labels to preserve non-status ones
|
|
82
|
+
const currentLabels = await this.getCurrentLabels(issueNumber);
|
|
83
|
+
|
|
84
|
+
// Filter out status-related labels (start with "status:")
|
|
85
|
+
const preservedLabels = currentLabels.filter(
|
|
86
|
+
label => !label.startsWith('status:')
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Get new status labels from the provided status
|
|
90
|
+
const newStatusLabels = status.labels.filter(
|
|
91
|
+
label => label.startsWith('status:')
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Get non-status labels from the provided status (e.g., priority, type)
|
|
95
|
+
const newOtherLabels = status.labels.filter(
|
|
96
|
+
label => !label.startsWith('status:')
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Combine: preserved non-status + new status + new other labels
|
|
100
|
+
const mergedLabels = [...new Set([
|
|
101
|
+
...preservedLabels,
|
|
102
|
+
...newStatusLabels,
|
|
103
|
+
...newOtherLabels
|
|
104
|
+
])];
|
|
105
|
+
|
|
106
|
+
updateData.labels = mergedLabels;
|
|
73
107
|
}
|
|
74
108
|
|
|
75
109
|
await this.octokit.rest.issues.update(updateData);
|
|
76
110
|
}
|
|
77
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Get current labels from GitHub issue
|
|
114
|
+
*
|
|
115
|
+
* @param issueNumber - GitHub issue number
|
|
116
|
+
* @returns Array of current label names
|
|
117
|
+
*/
|
|
118
|
+
private async getCurrentLabels(issueNumber: number): Promise<string[]> {
|
|
119
|
+
try {
|
|
120
|
+
const response = await this.octokit.rest.issues.get({
|
|
121
|
+
owner: this.owner,
|
|
122
|
+
repo: this.repo,
|
|
123
|
+
issue_number: issueNumber
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return response.data.labels
|
|
127
|
+
.map((label: any) => (typeof label === 'string' ? label : label.name))
|
|
128
|
+
.filter(Boolean) as string[];
|
|
129
|
+
} catch {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
78
134
|
/**
|
|
79
135
|
* Post status change comment to GitHub issue
|
|
80
136
|
*
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as yaml from "yaml";
|
|
5
|
+
class IncrementIssueBuilder {
|
|
6
|
+
constructor(incrementPath, projectRoot) {
|
|
7
|
+
this.incrementPath = incrementPath;
|
|
8
|
+
this.projectRoot = projectRoot;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Parse increment spec.md and extract all data
|
|
12
|
+
*/
|
|
13
|
+
async parse() {
|
|
14
|
+
const specPath = path.join(this.incrementPath, "spec.md");
|
|
15
|
+
if (!existsSync(specPath)) {
|
|
16
|
+
throw new Error(`spec.md not found at ${specPath}`);
|
|
17
|
+
}
|
|
18
|
+
const content = await readFile(specPath, "utf-8");
|
|
19
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
20
|
+
if (!frontmatterMatch) {
|
|
21
|
+
throw new Error("spec.md missing YAML frontmatter");
|
|
22
|
+
}
|
|
23
|
+
const frontmatter = yaml.parse(frontmatterMatch[1]);
|
|
24
|
+
const bodyContent = content.slice(frontmatterMatch[0].length).trim();
|
|
25
|
+
const titleMatch = bodyContent.match(/^#\s+(.+)$/m);
|
|
26
|
+
const title = titleMatch ? titleMatch[1].trim() : frontmatter.increment;
|
|
27
|
+
const problemStatement = this.extractSection(bodyContent, "Problem Statement");
|
|
28
|
+
const userStories = this.extractUserStories(bodyContent);
|
|
29
|
+
const outOfScopeSection = this.extractSection(bodyContent, "Out of Scope");
|
|
30
|
+
const outOfScope = outOfScopeSection.split("\n").filter((line) => line.startsWith("-")).map((line) => line.replace(/^-\s*/, "").trim());
|
|
31
|
+
const tasks = await this.extractTasks();
|
|
32
|
+
return {
|
|
33
|
+
frontmatter,
|
|
34
|
+
title,
|
|
35
|
+
problemStatement,
|
|
36
|
+
userStories,
|
|
37
|
+
tasks,
|
|
38
|
+
outOfScope
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Extract tasks from tasks.md
|
|
43
|
+
*/
|
|
44
|
+
async extractTasks() {
|
|
45
|
+
const tasksPath = path.join(this.incrementPath, "tasks.md");
|
|
46
|
+
if (!existsSync(tasksPath)) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const content = await readFile(tasksPath, "utf-8");
|
|
51
|
+
const tasks = [];
|
|
52
|
+
const taskBlocks = content.split(/(?=###\s+T-\d+)/);
|
|
53
|
+
for (const block of taskBlocks) {
|
|
54
|
+
const headerMatch = block.match(/###\s+(T-\d+):\s*(.+)/);
|
|
55
|
+
if (!headerMatch) continue;
|
|
56
|
+
const id = headerMatch[1];
|
|
57
|
+
const title = headerMatch[2].trim();
|
|
58
|
+
const statusMatch = block.match(/\*\*Status\*\*:\s*\[([x\s])\]/i);
|
|
59
|
+
const completed = statusMatch ? statusMatch[1].toLowerCase() === "x" : false;
|
|
60
|
+
const userStoryMatch = block.match(/\*\*User Story\*\*:\s*([^\n]+)/i);
|
|
61
|
+
const satisfiesMatch = block.match(/\*\*Satisfies ACs?\*\*:\s*([^\n]+)/i);
|
|
62
|
+
let userStories = [];
|
|
63
|
+
if (userStoryMatch) {
|
|
64
|
+
userStories = userStoryMatch[1].split(",").map((s) => s.trim());
|
|
65
|
+
} else if (satisfiesMatch) {
|
|
66
|
+
const acIds = satisfiesMatch[1].split(",").map((s) => s.trim());
|
|
67
|
+
const usIds = /* @__PURE__ */ new Set();
|
|
68
|
+
for (const ac of acIds) {
|
|
69
|
+
const usMatch = ac.match(/AC-(US\d+)-/i);
|
|
70
|
+
if (usMatch) {
|
|
71
|
+
usIds.add(usMatch[1]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
userStories = Array.from(usIds);
|
|
75
|
+
}
|
|
76
|
+
const priorityMatch = block.match(/\*\*Priority\*\*:\s*(P\d)/i);
|
|
77
|
+
const priority = priorityMatch ? priorityMatch[1] : void 0;
|
|
78
|
+
tasks.push({
|
|
79
|
+
id,
|
|
80
|
+
title,
|
|
81
|
+
completed,
|
|
82
|
+
userStories,
|
|
83
|
+
priority
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return tasks;
|
|
87
|
+
} catch {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Extract a section by heading
|
|
93
|
+
*/
|
|
94
|
+
extractSection(content, heading) {
|
|
95
|
+
const regex = new RegExp(`##\\s+${heading}\\s*\\n([\\s\\S]*?)(?=\\n##\\s|$)`, "i");
|
|
96
|
+
const match = content.match(regex);
|
|
97
|
+
return match ? match[1].trim() : "";
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Extract user stories from spec.md
|
|
101
|
+
*/
|
|
102
|
+
extractUserStories(content) {
|
|
103
|
+
const stories = [];
|
|
104
|
+
const userStoriesSection = this.extractSection(content, "User Stories");
|
|
105
|
+
if (!userStoriesSection) {
|
|
106
|
+
return stories;
|
|
107
|
+
}
|
|
108
|
+
const storyBlocks = userStoriesSection.split(/(?=###\s+US-\d+)/);
|
|
109
|
+
for (const block of storyBlocks) {
|
|
110
|
+
const headerMatch = block.match(/###\s+(US-\d+):\s*(.+)/);
|
|
111
|
+
if (!headerMatch) continue;
|
|
112
|
+
const id = headerMatch[1];
|
|
113
|
+
const title = headerMatch[2].trim();
|
|
114
|
+
const asAMatch = block.match(/\*\*As a\*\*\s+([^\n]+)/i);
|
|
115
|
+
const iWantMatch = block.match(/\*\*I want\*\*\s+([^\n]+)/i);
|
|
116
|
+
const soThatMatch = block.match(/\*\*So that\*\*\s+([^\n]+)/i);
|
|
117
|
+
const acceptanceCriteria = this.extractAcceptanceCriteria(block, id);
|
|
118
|
+
stories.push({
|
|
119
|
+
id,
|
|
120
|
+
title,
|
|
121
|
+
asA: asAMatch ? asAMatch[1].trim().replace(/,$/, "") : "",
|
|
122
|
+
iWant: iWantMatch ? iWantMatch[1].trim().replace(/,$/, "") : "",
|
|
123
|
+
soThat: soThatMatch ? soThatMatch[1].trim().replace(/\.$/, "") : "",
|
|
124
|
+
acceptanceCriteria
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return stories;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Extract acceptance criteria from a user story block
|
|
131
|
+
*/
|
|
132
|
+
extractAcceptanceCriteria(block, userStoryId) {
|
|
133
|
+
const criteria = [];
|
|
134
|
+
const acPattern = /-\s*\[([ x])\]\s*\*\*AC-(US\d+)-(\d+)\*\*:\s*(.+)/gi;
|
|
135
|
+
let match;
|
|
136
|
+
while ((match = acPattern.exec(block)) !== null) {
|
|
137
|
+
const completed = match[1].toLowerCase() === "x";
|
|
138
|
+
const usNum = match[2];
|
|
139
|
+
const acNum = match[3];
|
|
140
|
+
const description = match[4].trim();
|
|
141
|
+
criteria.push({
|
|
142
|
+
id: `AC-${usNum}-${acNum}`,
|
|
143
|
+
description,
|
|
144
|
+
completed
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return criteria;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Build GitHub issue for a single user story
|
|
151
|
+
*/
|
|
152
|
+
buildUserStoryIssue(story, incrementData, githubRepo) {
|
|
153
|
+
const featureId = incrementData.frontmatter.feature_id || this.generateFeatureId(incrementData);
|
|
154
|
+
const incrementId = incrementData.frontmatter.increment;
|
|
155
|
+
const title = `[${featureId}][${story.id}] ${story.title}`;
|
|
156
|
+
let body = "";
|
|
157
|
+
body += `**Feature**: ${featureId}
|
|
158
|
+
`;
|
|
159
|
+
body += `**Status**: ${incrementData.frontmatter.status || "planning"}
|
|
160
|
+
`;
|
|
161
|
+
body += `**Priority**: P1
|
|
162
|
+
`;
|
|
163
|
+
body += `
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
`;
|
|
167
|
+
body += `## User Story
|
|
168
|
+
|
|
169
|
+
`;
|
|
170
|
+
if (story.asA && story.iWant && story.soThat) {
|
|
171
|
+
body += `**As a** ${story.asA}
|
|
172
|
+
`;
|
|
173
|
+
body += `**I want** ${story.iWant}
|
|
174
|
+
`;
|
|
175
|
+
body += `**So that** ${story.soThat}
|
|
176
|
+
|
|
177
|
+
`;
|
|
178
|
+
} else {
|
|
179
|
+
body += `${story.title}
|
|
180
|
+
|
|
181
|
+
`;
|
|
182
|
+
}
|
|
183
|
+
body += `---
|
|
184
|
+
|
|
185
|
+
`;
|
|
186
|
+
body += `## Acceptance Criteria
|
|
187
|
+
|
|
188
|
+
`;
|
|
189
|
+
if (story.acceptanceCriteria.length > 0) {
|
|
190
|
+
const completed = story.acceptanceCriteria.filter((ac) => ac.completed).length;
|
|
191
|
+
const total = story.acceptanceCriteria.length;
|
|
192
|
+
const percentage = total > 0 ? Math.round(completed / total * 100) : 0;
|
|
193
|
+
body += `Progress: ${completed}/${total} criteria met (${percentage}%)
|
|
194
|
+
|
|
195
|
+
`;
|
|
196
|
+
for (const ac of story.acceptanceCriteria) {
|
|
197
|
+
const checkbox = ac.completed ? "[x]" : "[ ]";
|
|
198
|
+
body += `- ${checkbox} **${ac.id}**: ${ac.description}
|
|
199
|
+
`;
|
|
200
|
+
}
|
|
201
|
+
body += "\n";
|
|
202
|
+
} else {
|
|
203
|
+
body += `*No acceptance criteria defined*
|
|
204
|
+
|
|
205
|
+
`;
|
|
206
|
+
}
|
|
207
|
+
body += `---
|
|
208
|
+
|
|
209
|
+
`;
|
|
210
|
+
const storyTasks = incrementData.tasks.filter(
|
|
211
|
+
(t) => t.userStories.includes(story.id) || t.userStories.some((us) => us.toUpperCase() === story.id.toUpperCase())
|
|
212
|
+
);
|
|
213
|
+
if (storyTasks.length > 0) {
|
|
214
|
+
body += `## Tasks
|
|
215
|
+
|
|
216
|
+
`;
|
|
217
|
+
const completedTasks = storyTasks.filter((t) => t.completed).length;
|
|
218
|
+
body += `Progress: ${completedTasks}/${storyTasks.length} tasks
|
|
219
|
+
|
|
220
|
+
`;
|
|
221
|
+
for (const task of storyTasks) {
|
|
222
|
+
const checkbox = task.completed ? "[x]" : "[ ]";
|
|
223
|
+
body += `- ${checkbox} **${task.id}**: ${task.title}
|
|
224
|
+
`;
|
|
225
|
+
}
|
|
226
|
+
body += "\n---\n\n";
|
|
227
|
+
}
|
|
228
|
+
body += `## Implementation
|
|
229
|
+
|
|
230
|
+
`;
|
|
231
|
+
if (githubRepo) {
|
|
232
|
+
body += `**Increment**: [${incrementId}](https://github.com/${githubRepo}/tree/develop/.specweave/increments/${incrementId})
|
|
233
|
+
|
|
234
|
+
`;
|
|
235
|
+
} else {
|
|
236
|
+
body += `**Increment**: ${incrementId}
|
|
237
|
+
|
|
238
|
+
`;
|
|
239
|
+
}
|
|
240
|
+
body += `---
|
|
241
|
+
|
|
242
|
+
`;
|
|
243
|
+
body += `\u{1F916} Auto-synced by SpecWeave Increment Sync`;
|
|
244
|
+
const labels = ["specweave", "user-story"];
|
|
245
|
+
if (incrementData.frontmatter.type) {
|
|
246
|
+
labels.push(incrementData.frontmatter.type.toLowerCase());
|
|
247
|
+
}
|
|
248
|
+
const priority = story.priority?.toLowerCase() || incrementData.frontmatter.priority?.toLowerCase() || "p2";
|
|
249
|
+
labels.push(priority);
|
|
250
|
+
return { title, body, labels };
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Build GitHub issue for the entire increment (epic-style)
|
|
254
|
+
*/
|
|
255
|
+
buildIncrementIssue(incrementData, githubRepo) {
|
|
256
|
+
const featureId = incrementData.frontmatter.feature_id || this.generateFeatureId(incrementData);
|
|
257
|
+
const incrementId = incrementData.frontmatter.increment;
|
|
258
|
+
const title = `[${featureId}] ${incrementData.title}`;
|
|
259
|
+
let body = "";
|
|
260
|
+
body += `**Increment**: ${incrementId}
|
|
261
|
+
`;
|
|
262
|
+
body += `**Status**: ${incrementData.frontmatter.status || "planning"}
|
|
263
|
+
`;
|
|
264
|
+
body += `**Priority**: P0 (Critical)
|
|
265
|
+
`;
|
|
266
|
+
const totalACs = incrementData.userStories.reduce((sum, us) => sum + us.acceptanceCriteria.length, 0);
|
|
267
|
+
const completedACs = incrementData.userStories.reduce(
|
|
268
|
+
(sum, us) => sum + us.acceptanceCriteria.filter((ac) => ac.completed).length,
|
|
269
|
+
0
|
|
270
|
+
);
|
|
271
|
+
const percentage = totalACs > 0 ? Math.round(completedACs / totalACs * 100) : 0;
|
|
272
|
+
body += `**Progress**: ${completedACs}/${totalACs} ACs (${percentage}%)
|
|
273
|
+
`;
|
|
274
|
+
body += `
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
`;
|
|
278
|
+
body += `## Overview
|
|
279
|
+
|
|
280
|
+
`;
|
|
281
|
+
body += incrementData.problemStatement || incrementData.title;
|
|
282
|
+
body += `
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
`;
|
|
287
|
+
body += `## User Stories
|
|
288
|
+
|
|
289
|
+
`;
|
|
290
|
+
for (const story of incrementData.userStories) {
|
|
291
|
+
const usCompleted = story.acceptanceCriteria.filter((ac) => ac.completed).length;
|
|
292
|
+
const usTotal = story.acceptanceCriteria.length;
|
|
293
|
+
const usCheckbox = usCompleted === usTotal && usTotal > 0 ? "[x]" : "[ ]";
|
|
294
|
+
body += `### ${usCheckbox} ${story.id}: ${story.title}
|
|
295
|
+
|
|
296
|
+
`;
|
|
297
|
+
if (story.asA && story.iWant && story.soThat) {
|
|
298
|
+
body += `> **As a** ${story.asA}, **I want** ${story.iWant}, **So that** ${story.soThat}
|
|
299
|
+
|
|
300
|
+
`;
|
|
301
|
+
}
|
|
302
|
+
body += `**Acceptance Criteria:**
|
|
303
|
+
`;
|
|
304
|
+
for (const ac of story.acceptanceCriteria) {
|
|
305
|
+
const checkbox = ac.completed ? "[x]" : "[ ]";
|
|
306
|
+
body += `- ${checkbox} **${ac.id}**: ${ac.description}
|
|
307
|
+
`;
|
|
308
|
+
}
|
|
309
|
+
body += "\n";
|
|
310
|
+
}
|
|
311
|
+
body += `---
|
|
312
|
+
|
|
313
|
+
`;
|
|
314
|
+
if (incrementData.tasks.length > 0) {
|
|
315
|
+
body += `## Tasks
|
|
316
|
+
|
|
317
|
+
`;
|
|
318
|
+
const completedTasks = incrementData.tasks.filter((t) => t.completed).length;
|
|
319
|
+
const totalTasks = incrementData.tasks.length;
|
|
320
|
+
const taskPercentage = totalTasks > 0 ? Math.round(completedTasks / totalTasks * 100) : 0;
|
|
321
|
+
body += `Progress: ${completedTasks}/${totalTasks} tasks (${taskPercentage}%)
|
|
322
|
+
|
|
323
|
+
`;
|
|
324
|
+
for (const task of incrementData.tasks) {
|
|
325
|
+
const checkbox = task.completed ? "[x]" : "[ ]";
|
|
326
|
+
body += `- ${checkbox} **${task.id}**: ${task.title}
|
|
327
|
+
`;
|
|
328
|
+
if (task.priority || task.userStories.length > 0) {
|
|
329
|
+
const parts = [];
|
|
330
|
+
if (task.priority) parts.push(`Priority: ${task.priority}`);
|
|
331
|
+
if (task.userStories.length > 0) parts.push(`User ${task.userStories.length === 1 ? "Story" : "Stories"}: ${task.userStories.join(", ")}`);
|
|
332
|
+
body += ` - ${parts.join(" | ")}
|
|
333
|
+
`;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
body += "\n---\n\n";
|
|
337
|
+
}
|
|
338
|
+
body += `## SpecWeave Increment
|
|
339
|
+
|
|
340
|
+
`;
|
|
341
|
+
if (githubRepo) {
|
|
342
|
+
body += `- **Spec**: [\`spec.md\`](https://github.com/${githubRepo}/blob/develop/.specweave/increments/${incrementId}/spec.md)
|
|
343
|
+
`;
|
|
344
|
+
body += `- **Plan**: [\`plan.md\`](https://github.com/${githubRepo}/blob/develop/.specweave/increments/${incrementId}/plan.md)
|
|
345
|
+
`;
|
|
346
|
+
body += `- **Tasks**: [\`tasks.md\`](https://github.com/${githubRepo}/blob/develop/.specweave/increments/${incrementId}/tasks.md)
|
|
347
|
+
`;
|
|
348
|
+
} else {
|
|
349
|
+
body += `- **Spec**: \`spec.md\`
|
|
350
|
+
`;
|
|
351
|
+
body += `- **Plan**: \`plan.md\`
|
|
352
|
+
`;
|
|
353
|
+
body += `- **Tasks**: \`tasks.md\`
|
|
354
|
+
`;
|
|
355
|
+
}
|
|
356
|
+
body += `
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
`;
|
|
360
|
+
body += `\u{1F916} Auto-synced by SpecWeave Increment Sync`;
|
|
361
|
+
const labels = ["specweave", "increment"];
|
|
362
|
+
const typeLabel = incrementData.frontmatter.type?.toLowerCase() || "enhancement";
|
|
363
|
+
labels.push(typeLabel);
|
|
364
|
+
const priority = incrementData.frontmatter.priority?.toLowerCase() || "p2";
|
|
365
|
+
labels.push(priority);
|
|
366
|
+
return { title, body, labels };
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Generate a feature ID if not present in frontmatter
|
|
370
|
+
* Uses date-based format: FS-YY-MM-DD
|
|
371
|
+
*/
|
|
372
|
+
generateFeatureId(incrementData) {
|
|
373
|
+
const incrementNum = incrementData.frontmatter.increment.match(/^(\d+)/)?.[1];
|
|
374
|
+
if (incrementNum) {
|
|
375
|
+
return `FS-${incrementNum.padStart(3, "0")}`;
|
|
376
|
+
}
|
|
377
|
+
const created = incrementData.frontmatter.created;
|
|
378
|
+
if (created) {
|
|
379
|
+
const match = created.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
380
|
+
if (match) {
|
|
381
|
+
return `FS-${match[1].slice(2)}-${match[2]}-${match[3]}`;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return "FS-UNKNOWN";
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
export {
|
|
388
|
+
IncrementIssueBuilder
|
|
389
|
+
};
|