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,456 @@
|
|
|
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
|
+
function loadExistingGitHubLinks(incrementPath) {
|
|
83
|
+
const metadataPath = path.join(incrementPath, "metadata.json");
|
|
84
|
+
if (!existsSync(metadataPath)) {
|
|
85
|
+
return { userStoryIssues: {} };
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const metadata = JSON.parse(readFileSync(metadataPath, "utf-8"));
|
|
89
|
+
return {
|
|
90
|
+
milestone: metadata.github?.milestone,
|
|
91
|
+
userStoryIssues: metadata.github?.userStoryIssues || {}
|
|
92
|
+
};
|
|
93
|
+
} catch {
|
|
94
|
+
return { userStoryIssues: {} };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function updateIncrementMetadata(incrementPath, milestoneNumber, userStoryIssues) {
|
|
98
|
+
const metadataPath = path.join(incrementPath, "metadata.json");
|
|
99
|
+
let metadata = {};
|
|
100
|
+
if (existsSync(metadataPath)) {
|
|
101
|
+
try {
|
|
102
|
+
metadata = JSON.parse(readFileSync(metadataPath, "utf-8"));
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
metadata.github = {
|
|
107
|
+
milestone: milestoneNumber,
|
|
108
|
+
userStoryIssues,
|
|
109
|
+
lastSync: (/* @__PURE__ */ new Date()).toISOString()
|
|
110
|
+
};
|
|
111
|
+
await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2) + "\n");
|
|
112
|
+
}
|
|
113
|
+
async function createOrGetMilestone(owner, repo, featureId, title, existingMilestone) {
|
|
114
|
+
if (existingMilestone) {
|
|
115
|
+
const verifyResult = await execFileNoThrow("gh", [
|
|
116
|
+
"api",
|
|
117
|
+
`repos/${owner}/${repo}/milestones/${existingMilestone}`,
|
|
118
|
+
"--jq",
|
|
119
|
+
".number"
|
|
120
|
+
]);
|
|
121
|
+
if (verifyResult.exitCode === 0) {
|
|
122
|
+
return {
|
|
123
|
+
number: existingMilestone,
|
|
124
|
+
url: `https://github.com/${owner}/${repo}/milestone/${existingMilestone}`
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const searchResult = await execFileNoThrow("gh", [
|
|
129
|
+
"api",
|
|
130
|
+
`repos/${owner}/${repo}/milestones`,
|
|
131
|
+
"--jq",
|
|
132
|
+
`.[] | select(.title | contains("${featureId}")) | .number`
|
|
133
|
+
]);
|
|
134
|
+
if (searchResult.exitCode === 0 && searchResult.stdout.trim()) {
|
|
135
|
+
const milestoneNumber2 = parseInt(searchResult.stdout.trim().split("\n")[0], 10);
|
|
136
|
+
return {
|
|
137
|
+
number: milestoneNumber2,
|
|
138
|
+
url: `https://github.com/${owner}/${repo}/milestone/${milestoneNumber2}`
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const milestoneTitle = `[${featureId}] ${title}`;
|
|
142
|
+
const createResult = await execFileNoThrow("gh", [
|
|
143
|
+
"api",
|
|
144
|
+
`repos/${owner}/${repo}/milestones`,
|
|
145
|
+
"-X",
|
|
146
|
+
"POST",
|
|
147
|
+
"-f",
|
|
148
|
+
`title=${milestoneTitle}`,
|
|
149
|
+
"-f",
|
|
150
|
+
`description=Feature milestone for ${featureId}`,
|
|
151
|
+
"--jq",
|
|
152
|
+
".number"
|
|
153
|
+
]);
|
|
154
|
+
if (createResult.exitCode !== 0) {
|
|
155
|
+
throw new Error(`Failed to create milestone: ${createResult.stderr || createResult.stdout}`);
|
|
156
|
+
}
|
|
157
|
+
const milestoneNumber = parseInt(createResult.stdout.trim(), 10);
|
|
158
|
+
return {
|
|
159
|
+
number: milestoneNumber,
|
|
160
|
+
url: `https://github.com/${owner}/${repo}/milestone/${milestoneNumber}`
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function buildUserStoryIssueBody(story, tasks, incrementData, githubRepo) {
|
|
164
|
+
const incrementId = incrementData.frontmatter.increment;
|
|
165
|
+
let body = "";
|
|
166
|
+
body += `**Feature**: ${incrementData.frontmatter.feature_id || "N/A"}
|
|
167
|
+
`;
|
|
168
|
+
body += `**Status**: ${story.acceptanceCriteria.every((ac) => ac.completed) ? "complete" : "in-progress"}
|
|
169
|
+
`;
|
|
170
|
+
body += `**Priority**: ${story.priority || incrementData.frontmatter.priority || "P2"}
|
|
171
|
+
`;
|
|
172
|
+
body += `
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
`;
|
|
176
|
+
body += `## User Story
|
|
177
|
+
|
|
178
|
+
`;
|
|
179
|
+
if (story.asA && story.iWant && story.soThat) {
|
|
180
|
+
body += `**As a** ${story.asA}
|
|
181
|
+
`;
|
|
182
|
+
body += `**I want** ${story.iWant}
|
|
183
|
+
`;
|
|
184
|
+
body += `**So that** ${story.soThat}
|
|
185
|
+
|
|
186
|
+
`;
|
|
187
|
+
} else {
|
|
188
|
+
body += `${story.title}
|
|
189
|
+
|
|
190
|
+
`;
|
|
191
|
+
}
|
|
192
|
+
body += `---
|
|
193
|
+
|
|
194
|
+
`;
|
|
195
|
+
body += `## Acceptance Criteria
|
|
196
|
+
|
|
197
|
+
`;
|
|
198
|
+
if (story.acceptanceCriteria.length > 0) {
|
|
199
|
+
const completed = story.acceptanceCriteria.filter((ac) => ac.completed).length;
|
|
200
|
+
const total = story.acceptanceCriteria.length;
|
|
201
|
+
const percentage = total > 0 ? Math.round(completed / total * 100) : 0;
|
|
202
|
+
body += `Progress: ${completed}/${total} criteria met (${percentage}%)
|
|
203
|
+
|
|
204
|
+
`;
|
|
205
|
+
for (const ac of story.acceptanceCriteria) {
|
|
206
|
+
const checkbox = ac.completed ? "[x]" : "[ ]";
|
|
207
|
+
body += `- ${checkbox} **${ac.id}**: ${ac.description}
|
|
208
|
+
`;
|
|
209
|
+
}
|
|
210
|
+
body += "\n";
|
|
211
|
+
} else {
|
|
212
|
+
body += `*No acceptance criteria defined*
|
|
213
|
+
|
|
214
|
+
`;
|
|
215
|
+
}
|
|
216
|
+
body += `---
|
|
217
|
+
|
|
218
|
+
`;
|
|
219
|
+
const storyTasks = tasks.filter(
|
|
220
|
+
(t) => t.userStories.includes(story.id) || t.userStories.some((us) => us.includes(story.id.replace("US-", "")))
|
|
221
|
+
);
|
|
222
|
+
if (storyTasks.length > 0) {
|
|
223
|
+
body += `## Implementation Tasks
|
|
224
|
+
|
|
225
|
+
`;
|
|
226
|
+
const completedTasks = storyTasks.filter((t) => t.completed).length;
|
|
227
|
+
const totalTasks = storyTasks.length;
|
|
228
|
+
const taskPercentage = totalTasks > 0 ? Math.round(completedTasks / totalTasks * 100) : 0;
|
|
229
|
+
body += `Progress: ${completedTasks}/${totalTasks} tasks (${taskPercentage}%)
|
|
230
|
+
|
|
231
|
+
`;
|
|
232
|
+
for (const task of storyTasks) {
|
|
233
|
+
const checkbox = task.completed ? "[x]" : "[ ]";
|
|
234
|
+
body += `- ${checkbox} **${task.id}**: ${task.title}
|
|
235
|
+
`;
|
|
236
|
+
}
|
|
237
|
+
body += "\n";
|
|
238
|
+
body += `---
|
|
239
|
+
|
|
240
|
+
`;
|
|
241
|
+
}
|
|
242
|
+
body += `## SpecWeave Increment
|
|
243
|
+
|
|
244
|
+
`;
|
|
245
|
+
body += `**Increment**: [${incrementId}](https://github.com/${githubRepo}/tree/develop/.specweave/increments/${incrementId})
|
|
246
|
+
|
|
247
|
+
`;
|
|
248
|
+
body += `---
|
|
249
|
+
|
|
250
|
+
`;
|
|
251
|
+
body += `\u{1F916} Auto-synced by SpecWeave`;
|
|
252
|
+
return body;
|
|
253
|
+
}
|
|
254
|
+
async function syncUserStoryIssue(owner, repo, featureId, story, tasks, incrementData, milestoneNumber, existingIssueNumber) {
|
|
255
|
+
const title = `[${featureId}][${story.id}] ${story.title}`;
|
|
256
|
+
const body = buildUserStoryIssueBody(story, tasks, incrementData, `${owner}/${repo}`);
|
|
257
|
+
const labels = ["specweave", "user-story"];
|
|
258
|
+
const priority = story.priority?.toLowerCase() || incrementData.frontmatter.priority?.toLowerCase() || "p2";
|
|
259
|
+
labels.push(priority);
|
|
260
|
+
if (existingIssueNumber) {
|
|
261
|
+
const updateResult = await execFileNoThrow("gh", [
|
|
262
|
+
"issue",
|
|
263
|
+
"edit",
|
|
264
|
+
String(existingIssueNumber),
|
|
265
|
+
"--repo",
|
|
266
|
+
`${owner}/${repo}`,
|
|
267
|
+
"--title",
|
|
268
|
+
title,
|
|
269
|
+
"--body",
|
|
270
|
+
body
|
|
271
|
+
]);
|
|
272
|
+
if (updateResult.exitCode !== 0) {
|
|
273
|
+
throw new Error(`Failed to update issue #${existingIssueNumber}: ${updateResult.stderr}`);
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
number: existingIssueNumber,
|
|
277
|
+
url: `https://github.com/${owner}/${repo}/issues/${existingIssueNumber}`
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const createArgs = [
|
|
281
|
+
"issue",
|
|
282
|
+
"create",
|
|
283
|
+
"--repo",
|
|
284
|
+
`${owner}/${repo}`,
|
|
285
|
+
"--title",
|
|
286
|
+
title,
|
|
287
|
+
"--body",
|
|
288
|
+
body,
|
|
289
|
+
"--milestone",
|
|
290
|
+
String(milestoneNumber)
|
|
291
|
+
];
|
|
292
|
+
if (labels.length > 0) {
|
|
293
|
+
createArgs.push("--label", labels.join(","));
|
|
294
|
+
}
|
|
295
|
+
const createResult = await execFileNoThrow("gh", createArgs);
|
|
296
|
+
if (createResult.exitCode !== 0) {
|
|
297
|
+
throw new Error(`Failed to create issue: ${createResult.stderr || createResult.stdout}`);
|
|
298
|
+
}
|
|
299
|
+
const urlMatch = createResult.stdout.match(/https:\/\/github\.com\/[^\s]+\/issues\/(\d+)/);
|
|
300
|
+
if (!urlMatch) {
|
|
301
|
+
throw new Error("Could not parse issue URL from gh output");
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
number: parseInt(urlMatch[1], 10),
|
|
305
|
+
url: urlMatch[0]
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
async function main() {
|
|
309
|
+
const args = process.argv.slice(2);
|
|
310
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
311
|
+
console.log("Usage: node github-increment-sync-cli.js <increment-id> [options]");
|
|
312
|
+
console.log("");
|
|
313
|
+
console.log("Creates GitHub issues with CORRECT format:");
|
|
314
|
+
console.log(" - Milestone: [FS-XXX] Feature Title");
|
|
315
|
+
console.log(" - Issues: [FS-XXX][US-YYY] User Story Title (one per US)");
|
|
316
|
+
console.log("");
|
|
317
|
+
console.log("Arguments:");
|
|
318
|
+
console.log(" increment-id Increment ID (e.g., 0002 or 0002-thumbnail-mvp)");
|
|
319
|
+
console.log("");
|
|
320
|
+
console.log("Options:");
|
|
321
|
+
console.log(" --dry-run Preview issues without creating");
|
|
322
|
+
console.log("");
|
|
323
|
+
console.log("Environment:");
|
|
324
|
+
console.log(" GITHUB_TOKEN Required - GitHub personal access token");
|
|
325
|
+
console.log("");
|
|
326
|
+
console.log("Example:");
|
|
327
|
+
console.log(" GITHUB_TOKEN=ghp_xxx node github-increment-sync-cli.js 0002");
|
|
328
|
+
process.exit(args.length === 0 ? 1 : 0);
|
|
329
|
+
}
|
|
330
|
+
const incrementId = args[0];
|
|
331
|
+
const dryRun = args.includes("--dry-run");
|
|
332
|
+
console.log(`
|
|
333
|
+
\u{1F419} GitHub Increment Sync CLI (Per-User-Story Mode)`);
|
|
334
|
+
console.log(` Increment: ${incrementId}`);
|
|
335
|
+
const incrementPath = await findIncrementFolder(incrementId);
|
|
336
|
+
if (!incrementPath) {
|
|
337
|
+
console.error(`\u274C Increment not found: ${incrementId}`);
|
|
338
|
+
console.error(" Check: ls .specweave/increments/");
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
const fullIncrementId = path.basename(incrementPath);
|
|
342
|
+
console.log(` Found: ${fullIncrementId}`);
|
|
343
|
+
let config = null;
|
|
344
|
+
if (!dryRun) {
|
|
345
|
+
config = await loadGitHubConfig();
|
|
346
|
+
if (!config) {
|
|
347
|
+
process.exit(1);
|
|
348
|
+
}
|
|
349
|
+
console.log(` Repository: ${config.owner}/${config.repo}`);
|
|
350
|
+
} else {
|
|
351
|
+
config = await loadGitHubConfig().catch(() => null);
|
|
352
|
+
if (config) {
|
|
353
|
+
console.log(` Repository: ${config.owner}/${config.repo}`);
|
|
354
|
+
} else {
|
|
355
|
+
console.log(` Repository: (not detected - dry run mode)`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const projectRoot = process.cwd();
|
|
359
|
+
const builder = new IncrementIssueBuilder(incrementPath, projectRoot);
|
|
360
|
+
try {
|
|
361
|
+
console.log(`
|
|
362
|
+
\u{1F504} Parsing increment spec.md...`);
|
|
363
|
+
const incrementData = await builder.parse();
|
|
364
|
+
const featureId = incrementData.frontmatter.feature_id || `FS-${fullIncrementId.match(/^(\d+)/)?.[1]?.padStart(3, "0") || "UNKNOWN"}`;
|
|
365
|
+
console.log(` \u{1F4E6} Feature: ${featureId}`);
|
|
366
|
+
console.log(` \u{1F4E6} Title: ${incrementData.title}`);
|
|
367
|
+
console.log(` \u{1F4DD} User Stories: ${incrementData.userStories.length}`);
|
|
368
|
+
const totalACs = incrementData.userStories.reduce(
|
|
369
|
+
(sum, us) => sum + us.acceptanceCriteria.length,
|
|
370
|
+
0
|
|
371
|
+
);
|
|
372
|
+
console.log(` \u2713 Acceptance Criteria: ${totalACs}`);
|
|
373
|
+
console.log(` \u{1F527} Tasks: ${incrementData.tasks.length}`);
|
|
374
|
+
if (incrementData.userStories.length === 0) {
|
|
375
|
+
console.error(`
|
|
376
|
+
\u274C No user stories found in spec.md`);
|
|
377
|
+
console.error(" Ensure spec.md has ### US-XXX: Title sections");
|
|
378
|
+
process.exit(1);
|
|
379
|
+
}
|
|
380
|
+
console.log(`
|
|
381
|
+
\u{1F4CB} Issues to Create/Update:`);
|
|
382
|
+
console.log(` \u{1F3AF} Milestone: [${featureId}] ${incrementData.title}`);
|
|
383
|
+
for (const story of incrementData.userStories) {
|
|
384
|
+
const storyTasks = incrementData.tasks.filter(
|
|
385
|
+
(t) => t.userStories.includes(story.id) || t.userStories.some((us) => us.includes(story.id.replace("US-", "")))
|
|
386
|
+
);
|
|
387
|
+
console.log(` \u{1F4DD} [${featureId}][${story.id}] ${story.title}`);
|
|
388
|
+
console.log(` \u2514\u2500 ${story.acceptanceCriteria.length} ACs, ${storyTasks.length} tasks`);
|
|
389
|
+
}
|
|
390
|
+
if (dryRun) {
|
|
391
|
+
console.log(`
|
|
392
|
+
\u{1F4C4} Sample Issue Body (${incrementData.userStories[0].id}):
|
|
393
|
+
`);
|
|
394
|
+
const sampleBody = buildUserStoryIssueBody(
|
|
395
|
+
incrementData.userStories[0],
|
|
396
|
+
incrementData.tasks,
|
|
397
|
+
incrementData,
|
|
398
|
+
config ? `${config.owner}/${config.repo}` : "owner/repo"
|
|
399
|
+
);
|
|
400
|
+
console.log(sampleBody);
|
|
401
|
+
console.log(`
|
|
402
|
+
\u2705 Dry run complete (no issues created)`);
|
|
403
|
+
process.exit(0);
|
|
404
|
+
}
|
|
405
|
+
if (!config) {
|
|
406
|
+
console.error("\u274C GitHub config not available");
|
|
407
|
+
process.exit(1);
|
|
408
|
+
}
|
|
409
|
+
const existingLinks = loadExistingGitHubLinks(incrementPath);
|
|
410
|
+
console.log(`
|
|
411
|
+
\u{1F3AF} Creating/updating milestone...`);
|
|
412
|
+
const milestone = await createOrGetMilestone(
|
|
413
|
+
config.owner,
|
|
414
|
+
config.repo,
|
|
415
|
+
featureId,
|
|
416
|
+
incrementData.title,
|
|
417
|
+
existingLinks.milestone
|
|
418
|
+
);
|
|
419
|
+
console.log(` \u2705 Milestone #${milestone.number}: [${featureId}] ${incrementData.title}`);
|
|
420
|
+
console.log(`
|
|
421
|
+
\u{1F4DD} Creating/updating user story issues...`);
|
|
422
|
+
const userStoryIssues = {};
|
|
423
|
+
for (const story of incrementData.userStories) {
|
|
424
|
+
const existingIssue = existingLinks.userStoryIssues[story.id];
|
|
425
|
+
const issue = await syncUserStoryIssue(
|
|
426
|
+
config.owner,
|
|
427
|
+
config.repo,
|
|
428
|
+
featureId,
|
|
429
|
+
story,
|
|
430
|
+
incrementData.tasks,
|
|
431
|
+
incrementData,
|
|
432
|
+
milestone.number,
|
|
433
|
+
existingIssue
|
|
434
|
+
);
|
|
435
|
+
userStoryIssues[story.id] = issue.number;
|
|
436
|
+
const action = existingIssue ? "\u267B\uFE0F Updated" : "\u2705 Created";
|
|
437
|
+
console.log(` ${action} #${issue.number}: [${featureId}][${story.id}] ${story.title}`);
|
|
438
|
+
}
|
|
439
|
+
await updateIncrementMetadata(incrementPath, milestone.number, userStoryIssues);
|
|
440
|
+
console.log(`
|
|
441
|
+
\u{1F4DD} Metadata updated`);
|
|
442
|
+
console.log(`
|
|
443
|
+
\u2705 Sync complete!`);
|
|
444
|
+
console.log(` \u{1F3AF} Milestone: ${milestone.url}`);
|
|
445
|
+
console.log(` \u{1F4DD} Issues: ${Object.keys(userStoryIssues).length} user stories synced`);
|
|
446
|
+
process.exit(0);
|
|
447
|
+
} catch (error) {
|
|
448
|
+
console.error(`
|
|
449
|
+
\u274C Sync failed:`, error);
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
main().catch((error) => {
|
|
454
|
+
console.error("Fatal error:", error);
|
|
455
|
+
process.exit(1);
|
|
456
|
+
});
|