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
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* GitHub Increment Sync CLI
|
|
4
|
+
*
|
|
5
|
+
* For brownfield projects without living docs structure.
|
|
6
|
+
* Creates GitHub issues directly from increment spec.md with CORRECT format.
|
|
7
|
+
*
|
|
8
|
+
* CORRECT FORMAT (SpecWeave Universal Hierarchy):
|
|
9
|
+
* - Feature (FS-XXX) ā GitHub Milestone
|
|
10
|
+
* - User Story (US-XXX) ā GitHub Issue with [FS-XXX][US-YYY] Title
|
|
11
|
+
* - Tasks (T-XXX) ā Checkboxes in User Story issue
|
|
12
|
+
* - ACs ā Checkboxes in User Story issue
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* node github-increment-sync-cli.js <increment-id>
|
|
16
|
+
* node github-increment-sync-cli.js 0002-thumbnail-optimizer-mvp
|
|
17
|
+
*
|
|
18
|
+
* @see CLAUDE.md (GitHub Issue Format rules)
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { existsSync, readFileSync } from 'fs';
|
|
22
|
+
import * as fs from 'fs/promises';
|
|
23
|
+
import * as path from 'path';
|
|
24
|
+
import { IncrementIssueBuilder, UserStory, Task, IncrementData } from './increment-issue-builder.js';
|
|
25
|
+
import { execFileNoThrow } from '../../../src/utils/execFileNoThrow.js';
|
|
26
|
+
|
|
27
|
+
interface GitHubConfig {
|
|
28
|
+
owner: string;
|
|
29
|
+
repo: string;
|
|
30
|
+
token: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface SyncResult {
|
|
34
|
+
milestoneNumber: number;
|
|
35
|
+
milestoneUrl: string;
|
|
36
|
+
issues: Array<{
|
|
37
|
+
userStoryId: string;
|
|
38
|
+
issueNumber: number;
|
|
39
|
+
issueUrl: string;
|
|
40
|
+
title: string;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function loadGitHubConfig(): Promise<GitHubConfig | null> {
|
|
45
|
+
const projectRoot = process.cwd();
|
|
46
|
+
const configPath = path.join(projectRoot, '.specweave/config.json');
|
|
47
|
+
|
|
48
|
+
let owner = process.env.GITHUB_OWNER || '';
|
|
49
|
+
let repo = process.env.GITHUB_REPO || '';
|
|
50
|
+
const token = process.env.GITHUB_TOKEN || '';
|
|
51
|
+
|
|
52
|
+
// Try to load from config.json
|
|
53
|
+
if (existsSync(configPath)) {
|
|
54
|
+
try {
|
|
55
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
56
|
+
|
|
57
|
+
// Method 1: sync.github
|
|
58
|
+
if (config.sync?.github?.owner && config.sync?.github?.repo) {
|
|
59
|
+
owner = config.sync.github.owner;
|
|
60
|
+
repo = config.sync.github.repo;
|
|
61
|
+
}
|
|
62
|
+
// Method 2: multiProject.projects[activeProject].externalTools.github
|
|
63
|
+
else if (config.multiProject?.enabled && config.multiProject?.activeProject) {
|
|
64
|
+
const activeProject = config.multiProject.activeProject;
|
|
65
|
+
const projectConfig = config.multiProject.projects?.[activeProject];
|
|
66
|
+
if (projectConfig?.externalTools?.github?.repository) {
|
|
67
|
+
const parts = projectConfig.externalTools.github.repository.split('/');
|
|
68
|
+
if (parts.length === 2) {
|
|
69
|
+
owner = parts[0];
|
|
70
|
+
repo = parts[1];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Method 3: sync.profiles[activeProfile]
|
|
75
|
+
else if (config.sync?.activeProfile && config.sync?.profiles) {
|
|
76
|
+
const profile = config.sync.profiles[config.sync.activeProfile];
|
|
77
|
+
if (profile?.config?.owner && profile?.config?.repo) {
|
|
78
|
+
owner = profile.config.owner;
|
|
79
|
+
repo = profile.config.repo;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error('ā ļø Failed to parse config.json:', error);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Fallback: detect from git remote
|
|
88
|
+
if (!owner || !repo) {
|
|
89
|
+
const result = await execFileNoThrow('git', ['remote', 'get-url', 'origin']);
|
|
90
|
+
if (result.exitCode === 0 && result.stdout) {
|
|
91
|
+
const remoteUrl = result.stdout.trim();
|
|
92
|
+
const match = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
|
|
93
|
+
if (match) {
|
|
94
|
+
owner = owner || match[1];
|
|
95
|
+
repo = repo || match[2];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!token) {
|
|
101
|
+
console.error('ā GITHUB_TOKEN not set');
|
|
102
|
+
console.error(' Set it in .env file or export GITHUB_TOKEN=ghp_xxx');
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!owner || !repo) {
|
|
107
|
+
console.error('ā Could not detect GitHub owner/repo');
|
|
108
|
+
console.error(' Set sync.github.owner and sync.github.repo in .specweave/config.json');
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return { owner, repo, token };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Find increment folder by ID (supports partial matching)
|
|
117
|
+
*/
|
|
118
|
+
async function findIncrementFolder(incrementId: string): Promise<string | null> {
|
|
119
|
+
const projectRoot = process.cwd();
|
|
120
|
+
const incrementsDir = path.join(projectRoot, '.specweave/increments');
|
|
121
|
+
|
|
122
|
+
if (!existsSync(incrementsDir)) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const entries = await fs.readdir(incrementsDir, { withFileTypes: true });
|
|
127
|
+
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
if (!entry.isDirectory()) continue;
|
|
130
|
+
if (entry.name.startsWith('_')) continue; // Skip _archive, _backlog
|
|
131
|
+
|
|
132
|
+
// Exact match
|
|
133
|
+
if (entry.name === incrementId) {
|
|
134
|
+
return path.join(incrementsDir, entry.name);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Partial match (e.g., "0063" matches "0063-fix-external-import")
|
|
138
|
+
if (entry.name.startsWith(incrementId + '-')) {
|
|
139
|
+
return path.join(incrementsDir, entry.name);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Load existing GitHub links from metadata.json
|
|
148
|
+
*/
|
|
149
|
+
function loadExistingGitHubLinks(incrementPath: string): {
|
|
150
|
+
milestone?: number;
|
|
151
|
+
userStoryIssues: Record<string, number>;
|
|
152
|
+
} {
|
|
153
|
+
const metadataPath = path.join(incrementPath, 'metadata.json');
|
|
154
|
+
|
|
155
|
+
if (!existsSync(metadataPath)) {
|
|
156
|
+
return { userStoryIssues: {} };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const metadata = JSON.parse(readFileSync(metadataPath, 'utf-8'));
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
milestone: metadata.github?.milestone,
|
|
164
|
+
userStoryIssues: metadata.github?.userStoryIssues || {}
|
|
165
|
+
};
|
|
166
|
+
} catch {
|
|
167
|
+
return { userStoryIssues: {} };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Update increment metadata with GitHub links
|
|
173
|
+
*/
|
|
174
|
+
async function updateIncrementMetadata(
|
|
175
|
+
incrementPath: string,
|
|
176
|
+
milestoneNumber: number,
|
|
177
|
+
userStoryIssues: Record<string, number>
|
|
178
|
+
): Promise<void> {
|
|
179
|
+
const metadataPath = path.join(incrementPath, 'metadata.json');
|
|
180
|
+
|
|
181
|
+
let metadata: Record<string, unknown> = {};
|
|
182
|
+
|
|
183
|
+
if (existsSync(metadataPath)) {
|
|
184
|
+
try {
|
|
185
|
+
metadata = JSON.parse(readFileSync(metadataPath, 'utf-8'));
|
|
186
|
+
} catch {
|
|
187
|
+
// Start fresh
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Update github section
|
|
192
|
+
metadata.github = {
|
|
193
|
+
milestone: milestoneNumber,
|
|
194
|
+
userStoryIssues,
|
|
195
|
+
lastSync: new Date().toISOString()
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2) + '\n');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Create or get GitHub milestone for the feature
|
|
203
|
+
*/
|
|
204
|
+
async function createOrGetMilestone(
|
|
205
|
+
owner: string,
|
|
206
|
+
repo: string,
|
|
207
|
+
featureId: string,
|
|
208
|
+
title: string,
|
|
209
|
+
existingMilestone?: number
|
|
210
|
+
): Promise<{ number: number; url: string }> {
|
|
211
|
+
// If we have an existing milestone, verify it exists
|
|
212
|
+
if (existingMilestone) {
|
|
213
|
+
const verifyResult = await execFileNoThrow('gh', [
|
|
214
|
+
'api', `repos/${owner}/${repo}/milestones/${existingMilestone}`,
|
|
215
|
+
'--jq', '.number'
|
|
216
|
+
]);
|
|
217
|
+
if (verifyResult.exitCode === 0) {
|
|
218
|
+
return {
|
|
219
|
+
number: existingMilestone,
|
|
220
|
+
url: `https://github.com/${owner}/${repo}/milestone/${existingMilestone}`
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Search for existing milestone by title
|
|
226
|
+
const searchResult = await execFileNoThrow('gh', [
|
|
227
|
+
'api', `repos/${owner}/${repo}/milestones`,
|
|
228
|
+
'--jq', `.[] | select(.title | contains("${featureId}")) | .number`
|
|
229
|
+
]);
|
|
230
|
+
|
|
231
|
+
if (searchResult.exitCode === 0 && searchResult.stdout.trim()) {
|
|
232
|
+
const milestoneNumber = parseInt(searchResult.stdout.trim().split('\n')[0], 10);
|
|
233
|
+
return {
|
|
234
|
+
number: milestoneNumber,
|
|
235
|
+
url: `https://github.com/${owner}/${repo}/milestone/${milestoneNumber}`
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Create new milestone
|
|
240
|
+
const milestoneTitle = `[${featureId}] ${title}`;
|
|
241
|
+
const createResult = await execFileNoThrow('gh', [
|
|
242
|
+
'api', `repos/${owner}/${repo}/milestones`,
|
|
243
|
+
'-X', 'POST',
|
|
244
|
+
'-f', `title=${milestoneTitle}`,
|
|
245
|
+
'-f', `description=Feature milestone for ${featureId}`,
|
|
246
|
+
'--jq', '.number'
|
|
247
|
+
]);
|
|
248
|
+
|
|
249
|
+
if (createResult.exitCode !== 0) {
|
|
250
|
+
throw new Error(`Failed to create milestone: ${createResult.stderr || createResult.stdout}`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const milestoneNumber = parseInt(createResult.stdout.trim(), 10);
|
|
254
|
+
return {
|
|
255
|
+
number: milestoneNumber,
|
|
256
|
+
url: `https://github.com/${owner}/${repo}/milestone/${milestoneNumber}`
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Build issue body for a single user story
|
|
262
|
+
*/
|
|
263
|
+
function buildUserStoryIssueBody(
|
|
264
|
+
story: UserStory,
|
|
265
|
+
tasks: Task[],
|
|
266
|
+
incrementData: IncrementData,
|
|
267
|
+
githubRepo: string
|
|
268
|
+
): string {
|
|
269
|
+
const incrementId = incrementData.frontmatter.increment;
|
|
270
|
+
let body = '';
|
|
271
|
+
|
|
272
|
+
// Header with metadata
|
|
273
|
+
body += `**Feature**: ${incrementData.frontmatter.feature_id || 'N/A'}\n`;
|
|
274
|
+
body += `**Status**: ${story.acceptanceCriteria.every(ac => ac.completed) ? 'complete' : 'in-progress'}\n`;
|
|
275
|
+
body += `**Priority**: ${story.priority || incrementData.frontmatter.priority || 'P2'}\n`;
|
|
276
|
+
|
|
277
|
+
body += `\n---\n\n`;
|
|
278
|
+
|
|
279
|
+
// User Story description
|
|
280
|
+
body += `## User Story\n\n`;
|
|
281
|
+
if (story.asA && story.iWant && story.soThat) {
|
|
282
|
+
body += `**As a** ${story.asA}\n`;
|
|
283
|
+
body += `**I want** ${story.iWant}\n`;
|
|
284
|
+
body += `**So that** ${story.soThat}\n\n`;
|
|
285
|
+
} else {
|
|
286
|
+
body += `${story.title}\n\n`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
body += `---\n\n`;
|
|
290
|
+
|
|
291
|
+
// Acceptance Criteria
|
|
292
|
+
body += `## Acceptance Criteria\n\n`;
|
|
293
|
+
if (story.acceptanceCriteria.length > 0) {
|
|
294
|
+
const completed = story.acceptanceCriteria.filter(ac => ac.completed).length;
|
|
295
|
+
const total = story.acceptanceCriteria.length;
|
|
296
|
+
const percentage = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
297
|
+
body += `Progress: ${completed}/${total} criteria met (${percentage}%)\n\n`;
|
|
298
|
+
|
|
299
|
+
for (const ac of story.acceptanceCriteria) {
|
|
300
|
+
const checkbox = ac.completed ? '[x]' : '[ ]';
|
|
301
|
+
body += `- ${checkbox} **${ac.id}**: ${ac.description}\n`;
|
|
302
|
+
}
|
|
303
|
+
body += '\n';
|
|
304
|
+
} else {
|
|
305
|
+
body += `*No acceptance criteria defined*\n\n`;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
body += `---\n\n`;
|
|
309
|
+
|
|
310
|
+
// Tasks for this user story
|
|
311
|
+
const storyTasks = tasks.filter(t =>
|
|
312
|
+
t.userStories.includes(story.id) ||
|
|
313
|
+
t.userStories.some(us => us.includes(story.id.replace('US-', '')))
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
if (storyTasks.length > 0) {
|
|
317
|
+
body += `## Implementation Tasks\n\n`;
|
|
318
|
+
const completedTasks = storyTasks.filter(t => t.completed).length;
|
|
319
|
+
const totalTasks = storyTasks.length;
|
|
320
|
+
const taskPercentage = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;
|
|
321
|
+
body += `Progress: ${completedTasks}/${totalTasks} tasks (${taskPercentage}%)\n\n`;
|
|
322
|
+
|
|
323
|
+
for (const task of storyTasks) {
|
|
324
|
+
const checkbox = task.completed ? '[x]' : '[ ]';
|
|
325
|
+
body += `- ${checkbox} **${task.id}**: ${task.title}\n`;
|
|
326
|
+
}
|
|
327
|
+
body += '\n';
|
|
328
|
+
|
|
329
|
+
body += `---\n\n`;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Link to increment
|
|
333
|
+
body += `## SpecWeave Increment\n\n`;
|
|
334
|
+
body += `**Increment**: [${incrementId}](https://github.com/${githubRepo}/tree/develop/.specweave/increments/${incrementId})\n\n`;
|
|
335
|
+
|
|
336
|
+
body += `---\n\n`;
|
|
337
|
+
body += `š¤ Auto-synced by SpecWeave`;
|
|
338
|
+
|
|
339
|
+
return body;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Create or update GitHub issue for a user story
|
|
344
|
+
*/
|
|
345
|
+
async function syncUserStoryIssue(
|
|
346
|
+
owner: string,
|
|
347
|
+
repo: string,
|
|
348
|
+
featureId: string,
|
|
349
|
+
story: UserStory,
|
|
350
|
+
tasks: Task[],
|
|
351
|
+
incrementData: IncrementData,
|
|
352
|
+
milestoneNumber: number,
|
|
353
|
+
existingIssueNumber?: number
|
|
354
|
+
): Promise<{ number: number; url: string }> {
|
|
355
|
+
// CORRECT FORMAT: [FS-XXX][US-YYY] User Story Title
|
|
356
|
+
const title = `[${featureId}][${story.id}] ${story.title}`;
|
|
357
|
+
const body = buildUserStoryIssueBody(story, tasks, incrementData, `${owner}/${repo}`);
|
|
358
|
+
|
|
359
|
+
// Labels
|
|
360
|
+
const labels = ['specweave', 'user-story'];
|
|
361
|
+
const priority = story.priority?.toLowerCase() || incrementData.frontmatter.priority?.toLowerCase() || 'p2';
|
|
362
|
+
labels.push(priority);
|
|
363
|
+
|
|
364
|
+
if (existingIssueNumber) {
|
|
365
|
+
// Update existing issue
|
|
366
|
+
const updateResult = await execFileNoThrow('gh', [
|
|
367
|
+
'issue', 'edit',
|
|
368
|
+
String(existingIssueNumber),
|
|
369
|
+
'--repo', `${owner}/${repo}`,
|
|
370
|
+
'--title', title,
|
|
371
|
+
'--body', body
|
|
372
|
+
]);
|
|
373
|
+
|
|
374
|
+
if (updateResult.exitCode !== 0) {
|
|
375
|
+
throw new Error(`Failed to update issue #${existingIssueNumber}: ${updateResult.stderr}`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
number: existingIssueNumber,
|
|
380
|
+
url: `https://github.com/${owner}/${repo}/issues/${existingIssueNumber}`
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Create new issue
|
|
385
|
+
const createArgs = [
|
|
386
|
+
'issue', 'create',
|
|
387
|
+
'--repo', `${owner}/${repo}`,
|
|
388
|
+
'--title', title,
|
|
389
|
+
'--body', body,
|
|
390
|
+
'--milestone', String(milestoneNumber)
|
|
391
|
+
];
|
|
392
|
+
|
|
393
|
+
if (labels.length > 0) {
|
|
394
|
+
createArgs.push('--label', labels.join(','));
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const createResult = await execFileNoThrow('gh', createArgs);
|
|
398
|
+
|
|
399
|
+
if (createResult.exitCode !== 0) {
|
|
400
|
+
throw new Error(`Failed to create issue: ${createResult.stderr || createResult.stdout}`);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Parse issue URL from output
|
|
404
|
+
const urlMatch = createResult.stdout.match(/https:\/\/github\.com\/[^\s]+\/issues\/(\d+)/);
|
|
405
|
+
if (!urlMatch) {
|
|
406
|
+
throw new Error('Could not parse issue URL from gh output');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
number: parseInt(urlMatch[1], 10),
|
|
411
|
+
url: urlMatch[0]
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async function main() {
|
|
416
|
+
const args = process.argv.slice(2);
|
|
417
|
+
|
|
418
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
419
|
+
console.log('Usage: node github-increment-sync-cli.js <increment-id> [options]');
|
|
420
|
+
console.log('');
|
|
421
|
+
console.log('Creates GitHub issues with CORRECT format:');
|
|
422
|
+
console.log(' - Milestone: [FS-XXX] Feature Title');
|
|
423
|
+
console.log(' - Issues: [FS-XXX][US-YYY] User Story Title (one per US)');
|
|
424
|
+
console.log('');
|
|
425
|
+
console.log('Arguments:');
|
|
426
|
+
console.log(' increment-id Increment ID (e.g., 0002 or 0002-thumbnail-mvp)');
|
|
427
|
+
console.log('');
|
|
428
|
+
console.log('Options:');
|
|
429
|
+
console.log(' --dry-run Preview issues without creating');
|
|
430
|
+
console.log('');
|
|
431
|
+
console.log('Environment:');
|
|
432
|
+
console.log(' GITHUB_TOKEN Required - GitHub personal access token');
|
|
433
|
+
console.log('');
|
|
434
|
+
console.log('Example:');
|
|
435
|
+
console.log(' GITHUB_TOKEN=ghp_xxx node github-increment-sync-cli.js 0002');
|
|
436
|
+
process.exit(args.length === 0 ? 1 : 0);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const incrementId = args[0];
|
|
440
|
+
const dryRun = args.includes('--dry-run');
|
|
441
|
+
|
|
442
|
+
console.log(`\nš GitHub Increment Sync CLI (Per-User-Story Mode)`);
|
|
443
|
+
console.log(` Increment: ${incrementId}`);
|
|
444
|
+
|
|
445
|
+
// Find increment folder
|
|
446
|
+
const incrementPath = await findIncrementFolder(incrementId);
|
|
447
|
+
if (!incrementPath) {
|
|
448
|
+
console.error(`ā Increment not found: ${incrementId}`);
|
|
449
|
+
console.error(' Check: ls .specweave/increments/');
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const fullIncrementId = path.basename(incrementPath);
|
|
454
|
+
console.log(` Found: ${fullIncrementId}`);
|
|
455
|
+
|
|
456
|
+
// For dry-run, we can skip config validation
|
|
457
|
+
let config: GitHubConfig | null = null;
|
|
458
|
+
if (!dryRun) {
|
|
459
|
+
config = await loadGitHubConfig();
|
|
460
|
+
if (!config) {
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
console.log(` Repository: ${config.owner}/${config.repo}`);
|
|
464
|
+
} else {
|
|
465
|
+
config = await loadGitHubConfig().catch((): null => null);
|
|
466
|
+
if (config) {
|
|
467
|
+
console.log(` Repository: ${config.owner}/${config.repo}`);
|
|
468
|
+
} else {
|
|
469
|
+
console.log(` Repository: (not detected - dry run mode)`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Parse increment
|
|
474
|
+
const projectRoot = process.cwd();
|
|
475
|
+
const builder = new IncrementIssueBuilder(incrementPath, projectRoot);
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
console.log(`\nš Parsing increment spec.md...`);
|
|
479
|
+
const incrementData = await builder.parse();
|
|
480
|
+
|
|
481
|
+
const featureId = incrementData.frontmatter.feature_id ||
|
|
482
|
+
`FS-${fullIncrementId.match(/^(\d+)/)?.[1]?.padStart(3, '0') || 'UNKNOWN'}`;
|
|
483
|
+
|
|
484
|
+
console.log(` š¦ Feature: ${featureId}`);
|
|
485
|
+
console.log(` š¦ Title: ${incrementData.title}`);
|
|
486
|
+
console.log(` š User Stories: ${incrementData.userStories.length}`);
|
|
487
|
+
|
|
488
|
+
const totalACs = incrementData.userStories.reduce(
|
|
489
|
+
(sum, us) => sum + us.acceptanceCriteria.length, 0
|
|
490
|
+
);
|
|
491
|
+
console.log(` ā Acceptance Criteria: ${totalACs}`);
|
|
492
|
+
console.log(` š§ Tasks: ${incrementData.tasks.length}`);
|
|
493
|
+
|
|
494
|
+
if (incrementData.userStories.length === 0) {
|
|
495
|
+
console.error(`\nā No user stories found in spec.md`);
|
|
496
|
+
console.error(' Ensure spec.md has ### US-XXX: Title sections');
|
|
497
|
+
process.exit(1);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Preview issues
|
|
501
|
+
console.log(`\nš Issues to Create/Update:`);
|
|
502
|
+
console.log(` šÆ Milestone: [${featureId}] ${incrementData.title}`);
|
|
503
|
+
for (const story of incrementData.userStories) {
|
|
504
|
+
const storyTasks = incrementData.tasks.filter(t =>
|
|
505
|
+
t.userStories.includes(story.id) ||
|
|
506
|
+
t.userStories.some(us => us.includes(story.id.replace('US-', '')))
|
|
507
|
+
);
|
|
508
|
+
console.log(` š [${featureId}][${story.id}] ${story.title}`);
|
|
509
|
+
console.log(` āā ${story.acceptanceCriteria.length} ACs, ${storyTasks.length} tasks`);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (dryRun) {
|
|
513
|
+
console.log(`\nš Sample Issue Body (${incrementData.userStories[0].id}):\n`);
|
|
514
|
+
const sampleBody = buildUserStoryIssueBody(
|
|
515
|
+
incrementData.userStories[0],
|
|
516
|
+
incrementData.tasks,
|
|
517
|
+
incrementData,
|
|
518
|
+
config ? `${config.owner}/${config.repo}` : 'owner/repo'
|
|
519
|
+
);
|
|
520
|
+
console.log(sampleBody);
|
|
521
|
+
console.log(`\nā
Dry run complete (no issues created)`);
|
|
522
|
+
process.exit(0);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// After dry-run check, config is guaranteed to be non-null
|
|
526
|
+
if (!config) {
|
|
527
|
+
console.error('ā GitHub config not available');
|
|
528
|
+
process.exit(1);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Load existing links
|
|
532
|
+
const existingLinks = loadExistingGitHubLinks(incrementPath);
|
|
533
|
+
|
|
534
|
+
// Create or get milestone
|
|
535
|
+
console.log(`\nšÆ Creating/updating milestone...`);
|
|
536
|
+
const milestone = await createOrGetMilestone(
|
|
537
|
+
config.owner,
|
|
538
|
+
config.repo,
|
|
539
|
+
featureId,
|
|
540
|
+
incrementData.title,
|
|
541
|
+
existingLinks.milestone
|
|
542
|
+
);
|
|
543
|
+
console.log(` ā
Milestone #${milestone.number}: [${featureId}] ${incrementData.title}`);
|
|
544
|
+
|
|
545
|
+
// Create/update issues for each user story
|
|
546
|
+
console.log(`\nš Creating/updating user story issues...`);
|
|
547
|
+
const userStoryIssues: Record<string, number> = {};
|
|
548
|
+
|
|
549
|
+
for (const story of incrementData.userStories) {
|
|
550
|
+
const existingIssue = existingLinks.userStoryIssues[story.id];
|
|
551
|
+
|
|
552
|
+
const issue = await syncUserStoryIssue(
|
|
553
|
+
config.owner,
|
|
554
|
+
config.repo,
|
|
555
|
+
featureId,
|
|
556
|
+
story,
|
|
557
|
+
incrementData.tasks,
|
|
558
|
+
incrementData,
|
|
559
|
+
milestone.number,
|
|
560
|
+
existingIssue
|
|
561
|
+
);
|
|
562
|
+
|
|
563
|
+
userStoryIssues[story.id] = issue.number;
|
|
564
|
+
|
|
565
|
+
const action = existingIssue ? 'ā»ļø Updated' : 'ā
Created';
|
|
566
|
+
console.log(` ${action} #${issue.number}: [${featureId}][${story.id}] ${story.title}`);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Update metadata
|
|
570
|
+
await updateIncrementMetadata(incrementPath, milestone.number, userStoryIssues);
|
|
571
|
+
console.log(`\nš Metadata updated`);
|
|
572
|
+
|
|
573
|
+
console.log(`\nā
Sync complete!`);
|
|
574
|
+
console.log(` šÆ Milestone: ${milestone.url}`);
|
|
575
|
+
console.log(` š Issues: ${Object.keys(userStoryIssues).length} user stories synced`);
|
|
576
|
+
|
|
577
|
+
process.exit(0);
|
|
578
|
+
} catch (error) {
|
|
579
|
+
console.error(`\nā Sync failed:`, error);
|
|
580
|
+
process.exit(1);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Run CLI
|
|
585
|
+
main().catch(error => {
|
|
586
|
+
console.error('Fatal error:', error);
|
|
587
|
+
process.exit(1);
|
|
588
|
+
});
|
|
@@ -26,6 +26,9 @@ class GitHubStatusSync {
|
|
|
26
26
|
/**
|
|
27
27
|
* Update GitHub issue status
|
|
28
28
|
*
|
|
29
|
+
* Preserves existing labels that are not status-related.
|
|
30
|
+
* Only replaces labels that start with "status:" prefix.
|
|
31
|
+
*
|
|
29
32
|
* @param issueNumber - GitHub issue number
|
|
30
33
|
* @param status - New status (state and labels)
|
|
31
34
|
*/
|
|
@@ -37,10 +40,43 @@ class GitHubStatusSync {
|
|
|
37
40
|
state: status.state
|
|
38
41
|
};
|
|
39
42
|
if (status.labels && status.labels.length > 0) {
|
|
40
|
-
|
|
43
|
+
const currentLabels = await this.getCurrentLabels(issueNumber);
|
|
44
|
+
const preservedLabels = currentLabels.filter(
|
|
45
|
+
(label) => !label.startsWith("status:")
|
|
46
|
+
);
|
|
47
|
+
const newStatusLabels = status.labels.filter(
|
|
48
|
+
(label) => label.startsWith("status:")
|
|
49
|
+
);
|
|
50
|
+
const newOtherLabels = status.labels.filter(
|
|
51
|
+
(label) => !label.startsWith("status:")
|
|
52
|
+
);
|
|
53
|
+
const mergedLabels = [.../* @__PURE__ */ new Set([
|
|
54
|
+
...preservedLabels,
|
|
55
|
+
...newStatusLabels,
|
|
56
|
+
...newOtherLabels
|
|
57
|
+
])];
|
|
58
|
+
updateData.labels = mergedLabels;
|
|
41
59
|
}
|
|
42
60
|
await this.octokit.rest.issues.update(updateData);
|
|
43
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Get current labels from GitHub issue
|
|
64
|
+
*
|
|
65
|
+
* @param issueNumber - GitHub issue number
|
|
66
|
+
* @returns Array of current label names
|
|
67
|
+
*/
|
|
68
|
+
async getCurrentLabels(issueNumber) {
|
|
69
|
+
try {
|
|
70
|
+
const response = await this.octokit.rest.issues.get({
|
|
71
|
+
owner: this.owner,
|
|
72
|
+
repo: this.repo,
|
|
73
|
+
issue_number: issueNumber
|
|
74
|
+
});
|
|
75
|
+
return response.data.labels.map((label) => typeof label === "string" ? label : label.name).filter(Boolean);
|
|
76
|
+
} catch {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
44
80
|
/**
|
|
45
81
|
* Post status change comment to GitHub issue
|
|
46
82
|
*
|