specweave 0.28.15 → 0.28.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/specweave.js +1 -1
- 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-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 +19 -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 +380 -0
- package/dist/plugins/specweave-github/lib/github-increment-sync-cli.js.map +1 -0
- package/dist/plugins/specweave-github/lib/increment-issue-builder.d.ts +92 -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 +349 -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/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 +138 -23
- package/dist/src/cli/helpers/init/external-import.js.map +1 -1
- 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/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/qa/qa-runner.js +7 -10
- package/dist/src/core/qa/qa-runner.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/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/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/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/AGENTS-INDEX.md +9 -7
- package/plugins/specweave/agents/pm/AGENT.md +202 -0
- package/plugins/specweave/commands/specweave-import-external.md +5 -3
- package/plugins/specweave/commands/specweave-qa.md +9 -9
- package/plugins/specweave/commands/specweave-save.md +531 -193
- package/plugins/specweave/commands/specweave-sync-docs.md +6 -2
- package/plugins/specweave/commands/specweave-validate.md +8 -7
- 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/skills/increment-quality-judge-v2/SKILL.md +18 -0
- package/plugins/specweave-ado/commands/specweave-ado-import-areas.md +358 -0
- 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 +86 -0
- package/plugins/specweave-github/lib/github-feature-sync.ts +6 -11
- package/plugins/specweave-github/lib/github-increment-sync-cli.js +343 -0
- package/plugins/specweave-github/lib/github-increment-sync-cli.ts +484 -0
- package/plugins/specweave-github/lib/increment-issue-builder.js +368 -0
- package/plugins/specweave-github/lib/increment-issue-builder.ts +471 -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-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 +129 -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/agents/increment-quality-judge-v2/AGENT.md +0 -705
- 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
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GitHub Feature Sync - Universal Hierarchy Implementation
|
|
3
3
|
*
|
|
4
|
-
* Architecture
|
|
5
|
-
* - Feature (FS-
|
|
6
|
-
* - User Story (US-
|
|
7
|
-
* - Tasks (T-
|
|
4
|
+
* Architecture:
|
|
5
|
+
* - Feature (FS-XXX) → GitHub Milestone (Container)
|
|
6
|
+
* - User Story (US-XXX) → GitHub Issue with format [FS-XXX][US-YYY] Title
|
|
7
|
+
* - Tasks (T-XXX) → Checkboxes in User Story issue body
|
|
8
8
|
*
|
|
9
|
-
* This implements the
|
|
10
|
-
*
|
|
11
|
-
* Key Differences from old github-epic-sync.ts:
|
|
12
|
-
* - ❌ OLD: Feature/Increment → GitHub Issue (WRONG!)
|
|
13
|
-
* - ✅ NEW: User Story → GitHub Issue (CORRECT!)
|
|
14
|
-
* - ✅ Creates ONE issue PER user story file (not one per increment)
|
|
15
|
-
* - ✅ Reads us-*.md files from specs/{project}/FS-XXX/
|
|
9
|
+
* This implements the Universal Hierarchy architecture for GitHub sync.
|
|
10
|
+
* Creates ONE issue PER user story file from specs/{project}/FS-XXX/us-*.md
|
|
16
11
|
*/
|
|
17
12
|
|
|
18
13
|
import { readdir, readFile, writeFile } from 'fs/promises';
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from "fs";
|
|
3
|
+
import * as fs from "fs/promises";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import { IncrementIssueBuilder } from "./increment-issue-builder.js";
|
|
6
|
+
import { execFileNoThrow } from "../../../src/utils/execFileNoThrow.js";
|
|
7
|
+
async function loadGitHubConfig() {
|
|
8
|
+
const projectRoot = process.cwd();
|
|
9
|
+
const configPath = path.join(projectRoot, ".specweave/config.json");
|
|
10
|
+
let owner = process.env.GITHUB_OWNER || "";
|
|
11
|
+
let repo = process.env.GITHUB_REPO || "";
|
|
12
|
+
const token = process.env.GITHUB_TOKEN || "";
|
|
13
|
+
if (existsSync(configPath)) {
|
|
14
|
+
try {
|
|
15
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
16
|
+
if (config.sync?.github?.owner && config.sync?.github?.repo) {
|
|
17
|
+
owner = config.sync.github.owner;
|
|
18
|
+
repo = config.sync.github.repo;
|
|
19
|
+
} else if (config.multiProject?.enabled && config.multiProject?.activeProject) {
|
|
20
|
+
const activeProject = config.multiProject.activeProject;
|
|
21
|
+
const projectConfig = config.multiProject.projects?.[activeProject];
|
|
22
|
+
if (projectConfig?.externalTools?.github?.repository) {
|
|
23
|
+
const parts = projectConfig.externalTools.github.repository.split("/");
|
|
24
|
+
if (parts.length === 2) {
|
|
25
|
+
owner = parts[0];
|
|
26
|
+
repo = parts[1];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
} else if (config.sync?.activeProfile && config.sync?.profiles) {
|
|
30
|
+
const profile = config.sync.profiles[config.sync.activeProfile];
|
|
31
|
+
if (profile?.config?.owner && profile?.config?.repo) {
|
|
32
|
+
owner = profile.config.owner;
|
|
33
|
+
repo = profile.config.repo;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error("\u26A0\uFE0F Failed to parse config.json:", error);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (!owner || !repo) {
|
|
41
|
+
const result = await execFileNoThrow("git", ["remote", "get-url", "origin"]);
|
|
42
|
+
if (result.exitCode === 0 && result.stdout) {
|
|
43
|
+
const remoteUrl = result.stdout.trim();
|
|
44
|
+
const match = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
|
|
45
|
+
if (match) {
|
|
46
|
+
owner = owner || match[1];
|
|
47
|
+
repo = repo || match[2];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!token) {
|
|
52
|
+
console.error("\u274C GITHUB_TOKEN not set");
|
|
53
|
+
console.error(" Set it in .env file or export GITHUB_TOKEN=ghp_xxx");
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
if (!owner || !repo) {
|
|
57
|
+
console.error("\u274C Could not detect GitHub owner/repo");
|
|
58
|
+
console.error(" Set sync.github.owner and sync.github.repo in .specweave/config.json");
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return { owner, repo, token };
|
|
62
|
+
}
|
|
63
|
+
async function findIncrementFolder(incrementId) {
|
|
64
|
+
const projectRoot = process.cwd();
|
|
65
|
+
const incrementsDir = path.join(projectRoot, ".specweave/increments");
|
|
66
|
+
if (!existsSync(incrementsDir)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const entries = await fs.readdir(incrementsDir, { withFileTypes: true });
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
if (!entry.isDirectory()) continue;
|
|
72
|
+
if (entry.name.startsWith("_")) continue;
|
|
73
|
+
if (entry.name === incrementId) {
|
|
74
|
+
return path.join(incrementsDir, entry.name);
|
|
75
|
+
}
|
|
76
|
+
if (entry.name.startsWith(incrementId + "-")) {
|
|
77
|
+
return path.join(incrementsDir, entry.name);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
async function findExistingIssue(owner, repo, featureId) {
|
|
83
|
+
const result = await execFileNoThrow("gh", [
|
|
84
|
+
"search",
|
|
85
|
+
"issues",
|
|
86
|
+
`repo:${owner}/${repo}`,
|
|
87
|
+
`"[${featureId}]" in:title`,
|
|
88
|
+
"is:open",
|
|
89
|
+
"--json",
|
|
90
|
+
"number,title",
|
|
91
|
+
"--limit",
|
|
92
|
+
"5"
|
|
93
|
+
]);
|
|
94
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const issues = JSON.parse(result.stdout);
|
|
99
|
+
if (issues.length > 0) {
|
|
100
|
+
return issues[0].number;
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
async function createGitHubIssue(owner, repo, title, body, labels) {
|
|
107
|
+
const args = [
|
|
108
|
+
"issue",
|
|
109
|
+
"create",
|
|
110
|
+
"--repo",
|
|
111
|
+
`${owner}/${repo}`,
|
|
112
|
+
"--title",
|
|
113
|
+
title,
|
|
114
|
+
"--body",
|
|
115
|
+
body
|
|
116
|
+
];
|
|
117
|
+
if (labels.length > 0) {
|
|
118
|
+
args.push("--label", labels.join(","));
|
|
119
|
+
}
|
|
120
|
+
const result = await execFileNoThrow("gh", args);
|
|
121
|
+
if (result.exitCode !== 0) {
|
|
122
|
+
throw new Error(`Failed to create issue: ${result.stderr || result.stdout}`);
|
|
123
|
+
}
|
|
124
|
+
const urlMatch = result.stdout.match(/https:\/\/github\.com\/[^\s]+\/issues\/(\d+)/);
|
|
125
|
+
if (!urlMatch) {
|
|
126
|
+
throw new Error("Could not parse issue URL from gh output");
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
number: parseInt(urlMatch[1], 10),
|
|
130
|
+
url: urlMatch[0]
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
async function updateGitHubIssue(owner, repo, issueNumber, body) {
|
|
134
|
+
const result = await execFileNoThrow("gh", [
|
|
135
|
+
"issue",
|
|
136
|
+
"edit",
|
|
137
|
+
String(issueNumber),
|
|
138
|
+
"--repo",
|
|
139
|
+
`${owner}/${repo}`,
|
|
140
|
+
"--body",
|
|
141
|
+
body
|
|
142
|
+
]);
|
|
143
|
+
if (result.exitCode !== 0) {
|
|
144
|
+
throw new Error(`Failed to update issue: ${result.stderr || result.stdout}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function loadExistingGitHubLink(incrementPath) {
|
|
148
|
+
const metadataPath = path.join(incrementPath, "metadata.json");
|
|
149
|
+
if (!existsSync(metadataPath)) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const metadata = JSON.parse(readFileSync(metadataPath, "utf-8"));
|
|
154
|
+
if (metadata.github?.issue) {
|
|
155
|
+
return {
|
|
156
|
+
issue: metadata.github.issue,
|
|
157
|
+
url: metadata.github.url
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (metadata.sync?.issueNumber) {
|
|
161
|
+
return {
|
|
162
|
+
issue: metadata.sync.issueNumber,
|
|
163
|
+
url: metadata.sync.issueUrl
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
} catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function updateIncrementMetadata(incrementPath, issueNumber, issueUrl) {
|
|
172
|
+
const metadataPath = path.join(incrementPath, "metadata.json");
|
|
173
|
+
let metadata = {};
|
|
174
|
+
if (existsSync(metadataPath)) {
|
|
175
|
+
try {
|
|
176
|
+
metadata = JSON.parse(readFileSync(metadataPath, "utf-8"));
|
|
177
|
+
} catch {
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
metadata.github = {
|
|
181
|
+
issue: issueNumber,
|
|
182
|
+
url: issueUrl,
|
|
183
|
+
lastSync: (/* @__PURE__ */ new Date()).toISOString()
|
|
184
|
+
};
|
|
185
|
+
await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2) + "\n");
|
|
186
|
+
}
|
|
187
|
+
async function main() {
|
|
188
|
+
const args = process.argv.slice(2);
|
|
189
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
190
|
+
console.log("Usage: node github-increment-sync-cli.js <increment-id> [options]");
|
|
191
|
+
console.log("");
|
|
192
|
+
console.log("Arguments:");
|
|
193
|
+
console.log(" increment-id Increment ID (e.g., 0063 or 0063-fix-external-import)");
|
|
194
|
+
console.log("");
|
|
195
|
+
console.log("Options:");
|
|
196
|
+
console.log(" --force Force create even if issue exists");
|
|
197
|
+
console.log(" --dry-run Preview issue without creating");
|
|
198
|
+
console.log("");
|
|
199
|
+
console.log("Environment:");
|
|
200
|
+
console.log(" GITHUB_TOKEN Required - GitHub personal access token");
|
|
201
|
+
console.log("");
|
|
202
|
+
console.log("Example:");
|
|
203
|
+
console.log(" GITHUB_TOKEN=ghp_xxx node github-increment-sync-cli.js 0063");
|
|
204
|
+
process.exit(args.length === 0 ? 1 : 0);
|
|
205
|
+
}
|
|
206
|
+
const incrementId = args[0];
|
|
207
|
+
const force = args.includes("--force");
|
|
208
|
+
const dryRun = args.includes("--dry-run");
|
|
209
|
+
console.log(`
|
|
210
|
+
\u{1F419} GitHub Increment Sync CLI`);
|
|
211
|
+
console.log(` Increment: ${incrementId}`);
|
|
212
|
+
const incrementPath = await findIncrementFolder(incrementId);
|
|
213
|
+
if (!incrementPath) {
|
|
214
|
+
console.error(`\u274C Increment not found: ${incrementId}`);
|
|
215
|
+
console.error(" Check: ls .specweave/increments/");
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
const fullIncrementId = path.basename(incrementPath);
|
|
219
|
+
console.log(` Found: ${fullIncrementId}`);
|
|
220
|
+
let config = null;
|
|
221
|
+
if (!dryRun) {
|
|
222
|
+
config = await loadGitHubConfig();
|
|
223
|
+
if (!config) {
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
console.log(` Repository: ${config.owner}/${config.repo}`);
|
|
227
|
+
} else {
|
|
228
|
+
config = await loadGitHubConfig().catch(() => null);
|
|
229
|
+
if (config) {
|
|
230
|
+
console.log(` Repository: ${config.owner}/${config.repo}`);
|
|
231
|
+
} else {
|
|
232
|
+
console.log(` Repository: (not detected - dry run mode)`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const projectRoot = process.cwd();
|
|
236
|
+
const builder = new IncrementIssueBuilder(incrementPath, projectRoot);
|
|
237
|
+
try {
|
|
238
|
+
console.log(`
|
|
239
|
+
\u{1F504} Parsing increment spec.md...`);
|
|
240
|
+
const incrementData = await builder.parse();
|
|
241
|
+
console.log(` \u{1F4E6} Title: ${incrementData.title}`);
|
|
242
|
+
console.log(` \u{1F4DD} User Stories: ${incrementData.userStories.length}`);
|
|
243
|
+
const totalACs = incrementData.userStories.reduce(
|
|
244
|
+
(sum, us) => sum + us.acceptanceCriteria.length,
|
|
245
|
+
0
|
|
246
|
+
);
|
|
247
|
+
console.log(` \u2713 Acceptance Criteria: ${totalACs}`);
|
|
248
|
+
const githubRepo = config ? `${config.owner}/${config.repo}` : void 0;
|
|
249
|
+
const issue = builder.buildIncrementIssue(incrementData, githubRepo);
|
|
250
|
+
console.log(`
|
|
251
|
+
\u{1F4CB} Issue Preview:`);
|
|
252
|
+
console.log(` Title: ${issue.title}`);
|
|
253
|
+
console.log(` Labels: ${issue.labels.join(", ")}`);
|
|
254
|
+
if (dryRun) {
|
|
255
|
+
console.log(`
|
|
256
|
+
\u{1F4C4} Issue Body Preview:
|
|
257
|
+
`);
|
|
258
|
+
console.log(issue.body);
|
|
259
|
+
console.log(`
|
|
260
|
+
\u2705 Dry run complete (no issue created)`);
|
|
261
|
+
process.exit(0);
|
|
262
|
+
}
|
|
263
|
+
if (!config) {
|
|
264
|
+
console.error("\u274C GitHub config not available");
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
const metadataLink = loadExistingGitHubLink(incrementPath);
|
|
268
|
+
if (metadataLink) {
|
|
269
|
+
console.log(`
|
|
270
|
+
\u{1F4CE} Found existing issue link in metadata: #${metadataLink.issue}`);
|
|
271
|
+
console.log(`\u{1F504} Updating issue #${metadataLink.issue} with new format...`);
|
|
272
|
+
await updateGitHubIssue(config.owner, config.repo, metadataLink.issue, issue.body);
|
|
273
|
+
console.log(` \u2705 Body updated with User Stories and ACs`);
|
|
274
|
+
const updateTitleResult = await execFileNoThrow("gh", [
|
|
275
|
+
"issue",
|
|
276
|
+
"edit",
|
|
277
|
+
String(metadataLink.issue),
|
|
278
|
+
"--repo",
|
|
279
|
+
`${config.owner}/${config.repo}`,
|
|
280
|
+
"--title",
|
|
281
|
+
issue.title
|
|
282
|
+
]);
|
|
283
|
+
if (updateTitleResult.exitCode === 0) {
|
|
284
|
+
console.log(` \u2705 Title updated to: ${issue.title}`);
|
|
285
|
+
} else {
|
|
286
|
+
console.log(` \u26A0\uFE0F Could not update title (may need permissions)`);
|
|
287
|
+
}
|
|
288
|
+
await updateIncrementMetadata(
|
|
289
|
+
incrementPath,
|
|
290
|
+
metadataLink.issue,
|
|
291
|
+
`https://github.com/${config.owner}/${config.repo}/issues/${metadataLink.issue}`
|
|
292
|
+
);
|
|
293
|
+
console.log(`
|
|
294
|
+
\u2705 Sync complete!`);
|
|
295
|
+
console.log(` \u{1F517} https://github.com/${config.owner}/${config.repo}/issues/${metadataLink.issue}`);
|
|
296
|
+
process.exit(0);
|
|
297
|
+
}
|
|
298
|
+
const featureId = incrementData.frontmatter.feature_id || `FS-${fullIncrementId.match(/^(\d+)/)?.[1]?.padStart(3, "0") || "UNKNOWN"}`;
|
|
299
|
+
console.log(`
|
|
300
|
+
\u{1F50D} Searching GitHub for existing issue [${featureId}]...`);
|
|
301
|
+
const existingIssue = await findExistingIssue(config.owner, config.repo, featureId);
|
|
302
|
+
if (existingIssue) {
|
|
303
|
+
console.log(` Found existing issue: #${existingIssue}`);
|
|
304
|
+
console.log(`\u{1F504} Updating issue #${existingIssue}...`);
|
|
305
|
+
await updateGitHubIssue(config.owner, config.repo, existingIssue, issue.body);
|
|
306
|
+
console.log(` \u2705 Issue #${existingIssue} updated`);
|
|
307
|
+
await updateIncrementMetadata(
|
|
308
|
+
incrementPath,
|
|
309
|
+
existingIssue,
|
|
310
|
+
`https://github.com/${config.owner}/${config.repo}/issues/${existingIssue}`
|
|
311
|
+
);
|
|
312
|
+
console.log(`
|
|
313
|
+
\u2705 Sync complete!`);
|
|
314
|
+
console.log(` \u{1F517} https://github.com/${config.owner}/${config.repo}/issues/${existingIssue}`);
|
|
315
|
+
process.exit(0);
|
|
316
|
+
}
|
|
317
|
+
console.log(` No existing issue found`);
|
|
318
|
+
console.log(`
|
|
319
|
+
\u{1F680} Creating GitHub issue...`);
|
|
320
|
+
const created = await createGitHubIssue(
|
|
321
|
+
config.owner,
|
|
322
|
+
config.repo,
|
|
323
|
+
issue.title,
|
|
324
|
+
issue.body,
|
|
325
|
+
issue.labels
|
|
326
|
+
);
|
|
327
|
+
console.log(` \u2705 Issue #${created.number} created`);
|
|
328
|
+
await updateIncrementMetadata(incrementPath, created.number, created.url);
|
|
329
|
+
console.log(` \u{1F4DD} Metadata updated`);
|
|
330
|
+
console.log(`
|
|
331
|
+
\u2705 Sync complete!`);
|
|
332
|
+
console.log(` \u{1F517} ${created.url}`);
|
|
333
|
+
process.exit(0);
|
|
334
|
+
} catch (error) {
|
|
335
|
+
console.error(`
|
|
336
|
+
\u274C Sync failed:`, error);
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
main().catch((error) => {
|
|
341
|
+
console.error("Fatal error:", error);
|
|
342
|
+
process.exit(1);
|
|
343
|
+
});
|