specweave 1.0.235 → 1.0.239
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/README.md +89 -193
- package/dist/plugins/specweave-github/lib/github-ac-comment-poster.d.ts +37 -0
- package/dist/plugins/specweave-github/lib/github-ac-comment-poster.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-ac-comment-poster.js +176 -0
- package/dist/plugins/specweave-github/lib/github-ac-comment-poster.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-batch-sync.d.ts +36 -0
- package/dist/plugins/specweave-github/lib/github-batch-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-batch-sync.js +115 -0
- package/dist/plugins/specweave-github/lib/github-batch-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-board-resolver-v2.d.ts +37 -0
- package/dist/plugins/specweave-github/lib/github-board-resolver-v2.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-board-resolver-v2.js +56 -0
- package/dist/plugins/specweave-github/lib/github-board-resolver-v2.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-conflict-resolver.d.ts +68 -0
- package/dist/plugins/specweave-github/lib/github-conflict-resolver.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-conflict-resolver.js +102 -0
- package/dist/plugins/specweave-github/lib/github-conflict-resolver.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-cross-repo-sync.d.ts +64 -0
- package/dist/plugins/specweave-github/lib/github-cross-repo-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-cross-repo-sync.js +162 -0
- package/dist/plugins/specweave-github/lib/github-cross-repo-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-field-sync.d.ts +50 -0
- package/dist/plugins/specweave-github/lib/github-field-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-field-sync.js +107 -0
- package/dist/plugins/specweave-github/lib/github-field-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-graphql-client.d.ts +53 -0
- package/dist/plugins/specweave-github/lib/github-graphql-client.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-graphql-client.js +138 -0
- package/dist/plugins/specweave-github/lib/github-graphql-client.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-issue-body-generator.d.ts +40 -0
- package/dist/plugins/specweave-github/lib/github-issue-body-generator.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-issue-body-generator.js +50 -0
- package/dist/plugins/specweave-github/lib/github-issue-body-generator.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-issue-body-parser.d.ts +30 -0
- package/dist/plugins/specweave-github/lib/github-issue-body-parser.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-issue-body-parser.js +75 -0
- package/dist/plugins/specweave-github/lib/github-issue-body-parser.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-pull-sync.d.ts +94 -0
- package/dist/plugins/specweave-github/lib/github-pull-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-pull-sync.js +232 -0
- package/dist/plugins/specweave-github/lib/github-pull-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-push-sync.d.ts +50 -0
- package/dist/plugins/specweave-github/lib/github-push-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-push-sync.js +114 -0
- package/dist/plugins/specweave-github/lib/github-push-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-rate-limiter.d.ts +53 -0
- package/dist/plugins/specweave-github/lib/github-rate-limiter.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-rate-limiter.js +109 -0
- package/dist/plugins/specweave-github/lib/github-rate-limiter.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-spec-frontmatter-updater.d.ts +21 -0
- package/dist/plugins/specweave-github/lib/github-spec-frontmatter-updater.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-spec-frontmatter-updater.js +161 -0
- package/dist/plugins/specweave-github/lib/github-spec-frontmatter-updater.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-sync-orchestrator.d.ts +46 -0
- package/dist/plugins/specweave-github/lib/github-sync-orchestrator.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-sync-orchestrator.js +99 -0
- package/dist/plugins/specweave-github/lib/github-sync-orchestrator.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-us-auto-closer.d.ts +43 -0
- package/dist/plugins/specweave-github/lib/github-us-auto-closer.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-us-auto-closer.js +153 -0
- package/dist/plugins/specweave-github/lib/github-us-auto-closer.js.map +1 -0
- package/dist/plugins/specweave-github/lib/index.d.ts +1 -4
- package/dist/plugins/specweave-github/lib/index.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/index.js +1 -4
- package/dist/plugins/specweave-github/lib/index.js.map +1 -1
- package/dist/plugins/specweave-testing/lib/playwright-ci-defaults.d.ts +7 -0
- package/dist/plugins/specweave-testing/lib/playwright-ci-defaults.d.ts.map +1 -0
- package/dist/plugins/specweave-testing/lib/playwright-ci-defaults.js +15 -0
- package/dist/plugins/specweave-testing/lib/playwright-ci-defaults.js.map +1 -0
- package/dist/plugins/specweave-testing/lib/playwright-cli-detector.d.ts +10 -0
- package/dist/plugins/specweave-testing/lib/playwright-cli-detector.d.ts.map +1 -0
- package/dist/plugins/specweave-testing/lib/playwright-cli-detector.js +36 -0
- package/dist/plugins/specweave-testing/lib/playwright-cli-detector.js.map +1 -0
- package/dist/plugins/specweave-testing/lib/playwright-cli-runner.d.ts +25 -0
- package/dist/plugins/specweave-testing/lib/playwright-cli-runner.d.ts.map +1 -0
- package/dist/plugins/specweave-testing/lib/playwright-cli-runner.js +57 -0
- package/dist/plugins/specweave-testing/lib/playwright-cli-runner.js.map +1 -0
- package/dist/plugins/specweave-testing/lib/playwright-routing.d.ts +7 -0
- package/dist/plugins/specweave-testing/lib/playwright-routing.d.ts.map +1 -0
- package/dist/plugins/specweave-testing/lib/playwright-routing.js +17 -0
- package/dist/plugins/specweave-testing/lib/playwright-routing.js.map +1 -0
- package/dist/src/cli/commands/auto.d.ts.map +1 -1
- package/dist/src/cli/commands/auto.js +1 -2
- package/dist/src/cli/commands/auto.js.map +1 -1
- package/dist/src/cli/commands/cancel-auto.js +1 -2
- package/dist/src/cli/commands/cancel-auto.js.map +1 -1
- package/dist/src/cli/commands/living-docs.js +2 -2
- package/dist/src/cli/commands/living-docs.js.map +1 -1
- package/dist/src/cli/commands/update.d.ts.map +1 -1
- package/dist/src/cli/commands/update.js +1 -2
- package/dist/src/cli/commands/update.js.map +1 -1
- package/dist/src/core/config/types.d.ts +8 -0
- package/dist/src/core/config/types.d.ts.map +1 -1
- package/dist/src/core/config/types.js +3 -0
- package/dist/src/core/config/types.js.map +1 -1
- package/dist/src/core/types/sync-profile.d.ts +72 -0
- package/dist/src/core/types/sync-profile.d.ts.map +1 -1
- package/dist/src/core/types/sync-profile.js +6 -0
- package/dist/src/core/types/sync-profile.js.map +1 -1
- package/package.json +2 -2
- package/plugins/specweave/hooks/hooks.json +2 -2
- package/plugins/specweave/hooks/startup-health-check.sh +1 -1
- package/plugins/specweave/hooks/stop-auto-v5.sh +166 -0
- package/plugins/specweave/hooks/user-prompt-submit.sh +10 -0
- package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +21 -1
- package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +1 -1
- package/plugins/specweave/skills/auto/SKILL.md +71 -251
- package/plugins/specweave/skills/team-build/SKILL.md +370 -0
- package/plugins/specweave/skills/team-merge/SKILL.md +123 -0
- package/plugins/specweave/skills/team-orchestrate/SKILL.md +800 -0
- package/plugins/specweave/skills/team-status/SKILL.md +89 -0
- package/plugins/specweave-github/MULTI-PROJECT-SYNC-ARCHITECTURE.md +94 -8
- package/plugins/specweave-github/commands/sync.md +17 -3
- package/plugins/specweave-github/hooks/github-ac-sync-handler.sh +255 -0
- package/plugins/specweave-github/hooks/github-auto-create-handler.sh +455 -0
- package/plugins/specweave-github/lib/github-ac-comment-poster.js +150 -0
- package/plugins/specweave-github/lib/github-ac-comment-poster.ts +245 -0
- package/plugins/specweave-github/lib/github-batch-sync.js +93 -0
- package/plugins/specweave-github/lib/github-batch-sync.ts +152 -0
- package/plugins/specweave-github/lib/github-board-resolver-v2.js +47 -0
- package/plugins/specweave-github/lib/github-board-resolver-v2.ts +73 -0
- package/plugins/specweave-github/lib/github-conflict-resolver.js +90 -0
- package/plugins/specweave-github/lib/github-conflict-resolver.ts +154 -0
- package/plugins/specweave-github/lib/github-cross-repo-sync.js +168 -0
- package/plugins/specweave-github/lib/github-cross-repo-sync.ts +252 -0
- package/plugins/specweave-github/lib/github-field-sync.js +116 -0
- package/plugins/specweave-github/lib/github-field-sync.ts +165 -0
- package/plugins/specweave-github/lib/github-graphql-client.js +129 -0
- package/plugins/specweave-github/lib/github-graphql-client.ts +181 -0
- package/plugins/specweave-github/lib/github-issue-body-generator.js +30 -0
- package/plugins/specweave-github/lib/github-issue-body-generator.ts +76 -0
- package/plugins/specweave-github/lib/github-issue-body-parser.js +55 -0
- package/plugins/specweave-github/lib/github-issue-body-parser.ts +92 -0
- package/plugins/specweave-github/lib/github-pull-sync.js +185 -0
- package/plugins/specweave-github/lib/github-pull-sync.ts +343 -0
- package/plugins/specweave-github/lib/github-push-sync.js +119 -0
- package/plugins/specweave-github/lib/github-push-sync.ts +174 -0
- package/plugins/specweave-github/lib/github-rate-limiter.js +96 -0
- package/plugins/specweave-github/lib/github-rate-limiter.ts +143 -0
- package/plugins/specweave-github/lib/github-spec-frontmatter-updater.js +117 -0
- package/plugins/specweave-github/lib/github-spec-frontmatter-updater.ts +180 -0
- package/plugins/specweave-github/lib/github-sync-orchestrator.js +84 -0
- package/plugins/specweave-github/lib/github-sync-orchestrator.ts +156 -0
- package/plugins/specweave-github/lib/github-us-auto-closer.js +134 -0
- package/plugins/specweave-github/lib/github-us-auto-closer.ts +226 -0
- package/plugins/specweave-github/lib/index.js +1 -7
- package/plugins/specweave-github/lib/index.ts +1 -4
- package/plugins/specweave-github/skills/github-sync/SKILL.md +76 -4
- package/plugins/specweave-testing/commands/e2e-setup.md +18 -0
- package/plugins/specweave-testing/commands/ui-automate.md +2 -0
- package/plugins/specweave-testing/commands/ui-inspect.md +8 -0
- package/plugins/specweave-testing/lib/playwright-ci-defaults.d.ts +6 -0
- package/plugins/specweave-testing/lib/playwright-ci-defaults.js +14 -0
- package/plugins/specweave-testing/lib/playwright-ci-defaults.ts +24 -0
- package/plugins/specweave-testing/lib/playwright-cli-detector.js +33 -0
- package/plugins/specweave-testing/lib/playwright-cli-detector.ts +48 -0
- package/plugins/specweave-testing/lib/playwright-cli-runner.js +58 -0
- package/plugins/specweave-testing/lib/playwright-cli-runner.ts +80 -0
- package/plugins/specweave-testing/lib/playwright-routing.js +16 -0
- package/plugins/specweave-testing/lib/playwright-routing.ts +38 -0
- package/plugins/specweave-testing/skills/e2e-testing/SKILL.md +38 -0
- package/src/templates/CLAUDE.md.template +7 -0
- package/src/templates/config.json.template +9 -1
- package/dist/plugins/specweave-github/lib/subtask-sync.d.ts +0 -51
- package/dist/plugins/specweave-github/lib/subtask-sync.d.ts.map +0 -1
- package/dist/plugins/specweave-github/lib/subtask-sync.js +0 -147
- package/dist/plugins/specweave-github/lib/subtask-sync.js.map +0 -1
- package/dist/plugins/specweave-github/lib/task-parser.d.ts +0 -37
- package/dist/plugins/specweave-github/lib/task-parser.d.ts.map +0 -1
- package/dist/plugins/specweave-github/lib/task-parser.js +0 -211
- package/dist/plugins/specweave-github/lib/task-parser.js.map +0 -1
- package/dist/plugins/specweave-github/lib/task-sync.d.ts +0 -56
- package/dist/plugins/specweave-github/lib/task-sync.d.ts.map +0 -1
- package/dist/plugins/specweave-github/lib/task-sync.js +0 -375
- package/dist/plugins/specweave-github/lib/task-sync.js.map +0 -1
- package/plugins/specweave/hooks/validate-completion-conditions.sh +0 -474
- package/plugins/specweave-github/lib/subtask-sync.d.ts +0 -51
- package/plugins/specweave-github/lib/subtask-sync.d.ts.map +0 -1
- package/plugins/specweave-github/lib/subtask-sync.js +0 -154
- package/plugins/specweave-github/lib/subtask-sync.js.map +0 -1
- package/plugins/specweave-github/lib/subtask-sync.ts +0 -225
- package/plugins/specweave-github/lib/task-parser.d.js +0 -0
- package/plugins/specweave-github/lib/task-parser.d.ts +0 -37
- package/plugins/specweave-github/lib/task-parser.d.ts.map +0 -1
- package/plugins/specweave-github/lib/task-parser.js +0 -195
- package/plugins/specweave-github/lib/task-parser.js.map +0 -1
- package/plugins/specweave-github/lib/task-parser.ts +0 -246
- package/plugins/specweave-github/lib/task-sync.d.js +0 -0
- package/plugins/specweave-github/lib/task-sync.d.ts +0 -51
- package/plugins/specweave-github/lib/task-sync.d.ts.map +0 -1
- package/plugins/specweave-github/lib/task-sync.js +0 -415
- package/plugins/specweave-github/lib/task-sync.js.map +0 -1
- package/plugins/specweave-github/lib/task-sync.ts +0 -451
- package/plugins/specweave-github/skills/github-issue-tracker/SKILL.md +0 -496
- /package/plugins/specweave/hooks/{stop-auto.sh → _archive/stop-auto-v4-legacy.sh} +0 -0
- /package/plugins/{specweave-github/lib/subtask-sync.d.js → specweave-testing/lib/playwright-ci-defaults.d.js} +0 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub AC Comment Poster — Posts progress comments to GitHub issues
|
|
3
|
+
* when acceptance criteria are completed in spec.md.
|
|
4
|
+
*
|
|
5
|
+
* Triggered by github-ac-sync-handler.sh after task-ac-sync-guard
|
|
6
|
+
* updates spec.md ACs.
|
|
7
|
+
*
|
|
8
|
+
* @module github-ac-comment-poster
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFile } from 'fs/promises';
|
|
12
|
+
import { execFileNoThrow } from '../../../src/utils/execFileNoThrow.js';
|
|
13
|
+
import { pushSyncUserStories } from './github-push-sync.js';
|
|
14
|
+
import type { UserStoryForSync } from './github-push-sync.js';
|
|
15
|
+
|
|
16
|
+
export interface CommentPostOptions {
|
|
17
|
+
owner: string;
|
|
18
|
+
repo: string;
|
|
19
|
+
token?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CommentPostResult {
|
|
23
|
+
posted: Array<{ usId: string; issueNumber: number }>;
|
|
24
|
+
errors: Array<{ usId: string; error: string }>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ParsedACState {
|
|
28
|
+
id: string;
|
|
29
|
+
description: string;
|
|
30
|
+
completed: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ParsedUSIssueLink {
|
|
34
|
+
issueNumber: number;
|
|
35
|
+
issueUrl: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Post AC progress comments to GitHub issues for affected user stories.
|
|
40
|
+
*
|
|
41
|
+
* For each affected US:
|
|
42
|
+
* 1. Look up GitHub issue link from spec.md frontmatter
|
|
43
|
+
* 2. Extract AC states for that US
|
|
44
|
+
* 3. Build progress comment
|
|
45
|
+
* 4. Post via `gh issue comment`
|
|
46
|
+
*
|
|
47
|
+
* Never throws — all errors are captured in result.errors.
|
|
48
|
+
*/
|
|
49
|
+
export async function postACProgressComments(
|
|
50
|
+
incrementId: string,
|
|
51
|
+
affectedUSIds: string[],
|
|
52
|
+
specPath: string,
|
|
53
|
+
options: CommentPostOptions,
|
|
54
|
+
): Promise<CommentPostResult> {
|
|
55
|
+
const result: CommentPostResult = { posted: [], errors: [] };
|
|
56
|
+
|
|
57
|
+
if (affectedUSIds.length === 0) {
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let content: string;
|
|
62
|
+
try {
|
|
63
|
+
content = await readFile(specPath, 'utf-8');
|
|
64
|
+
} catch (err) {
|
|
65
|
+
result.errors.push({
|
|
66
|
+
usId: affectedUSIds[0],
|
|
67
|
+
error: err instanceof Error ? err.message : String(err),
|
|
68
|
+
});
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const issueLinks = parseIssueLinks(content);
|
|
73
|
+
const repoSlug = `${options.owner}/${options.repo}`;
|
|
74
|
+
const env = options.token ? { GH_TOKEN: options.token } : undefined;
|
|
75
|
+
|
|
76
|
+
for (const usId of affectedUSIds) {
|
|
77
|
+
const link = issueLinks[usId];
|
|
78
|
+
if (!link) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const acStates = parseACStatesForUS(content, usId);
|
|
83
|
+
const commentBody = buildProgressCommentForUS(incrementId, usId, acStates);
|
|
84
|
+
|
|
85
|
+
const execResult = await execFileNoThrow(
|
|
86
|
+
'gh',
|
|
87
|
+
['issue', 'comment', String(link.issueNumber), '--body', commentBody, '-R', repoSlug],
|
|
88
|
+
env ? { env } : {},
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
if (execResult.success) {
|
|
92
|
+
result.posted.push({ usId, issueNumber: link.issueNumber });
|
|
93
|
+
} else {
|
|
94
|
+
result.errors.push({
|
|
95
|
+
usId,
|
|
96
|
+
error: execResult.stderr || 'Unknown error posting comment',
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Targeted push-sync: update issue body with current AC states
|
|
101
|
+
const usForSync = buildUserStoryForSync(content, usId, acStates, incrementId);
|
|
102
|
+
if (usForSync) {
|
|
103
|
+
try {
|
|
104
|
+
await pushSyncUserStories([usForSync], {
|
|
105
|
+
owner: options.owner,
|
|
106
|
+
repo: options.repo,
|
|
107
|
+
token: options.token,
|
|
108
|
+
});
|
|
109
|
+
} catch {
|
|
110
|
+
// Push-sync failure is non-blocking
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Parse issue links from spec.md YAML frontmatter.
|
|
120
|
+
* Extracts externalLinks.github.userStories entries.
|
|
121
|
+
*/
|
|
122
|
+
function parseIssueLinks(content: string): Record<string, ParsedUSIssueLink> {
|
|
123
|
+
const links: Record<string, ParsedUSIssueLink> = {};
|
|
124
|
+
|
|
125
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
126
|
+
if (!fmMatch) return links;
|
|
127
|
+
|
|
128
|
+
const frontmatter = fmMatch[1];
|
|
129
|
+
|
|
130
|
+
// Parse userStories section from YAML frontmatter
|
|
131
|
+
const usBlockMatch = frontmatter.match(/userStories:\s*\n((?:\s{6,}[\s\S]*?)(?=\n\s{0,3}\S|$))/);
|
|
132
|
+
if (!usBlockMatch) return links;
|
|
133
|
+
|
|
134
|
+
const usBlock = usBlockMatch[1];
|
|
135
|
+
const usEntries = usBlock.match(/^\s+(US-\d+):\s*\n((?:\s+\w[\s\S]*?)(?=\n\s+US-|\s*$))/gm);
|
|
136
|
+
|
|
137
|
+
if (!usEntries) return links;
|
|
138
|
+
|
|
139
|
+
for (const entry of usEntries) {
|
|
140
|
+
const idMatch = entry.match(/(US-\d+):/);
|
|
141
|
+
const numMatch = entry.match(/issueNumber:\s*(\d+)/);
|
|
142
|
+
const urlMatch = entry.match(/issueUrl:\s*"([^"]+)"/);
|
|
143
|
+
|
|
144
|
+
if (idMatch && numMatch) {
|
|
145
|
+
links[idMatch[1]] = {
|
|
146
|
+
issueNumber: parseInt(numMatch[1], 10),
|
|
147
|
+
issueUrl: urlMatch ? urlMatch[1] : '',
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return links;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Extract AC states for a specific user story from spec.md content.
|
|
157
|
+
*/
|
|
158
|
+
function parseACStatesForUS(content: string, usId: string): ParsedACState[] {
|
|
159
|
+
const states: ParsedACState[] = [];
|
|
160
|
+
// AC IDs use unpadded US number: US-001 → AC-US1-XX
|
|
161
|
+
const usNum = String(parseInt(usId.replace('US-', ''), 10));
|
|
162
|
+
|
|
163
|
+
const acPattern = new RegExp(
|
|
164
|
+
`- \\[([ x])\\] \\*\\*AC-US${usNum}-(\\d+)\\*\\*:\\s*(.+)`,
|
|
165
|
+
'g',
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
let match;
|
|
169
|
+
while ((match = acPattern.exec(content)) !== null) {
|
|
170
|
+
states.push({
|
|
171
|
+
id: `AC-US${usNum}-${match[2]}`,
|
|
172
|
+
description: match[3].trim(),
|
|
173
|
+
completed: match[1] === 'x',
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return states;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Build a UserStoryForSync object for targeted push-sync.
|
|
182
|
+
*/
|
|
183
|
+
function buildUserStoryForSync(
|
|
184
|
+
content: string,
|
|
185
|
+
usId: string,
|
|
186
|
+
acStates: ParsedACState[],
|
|
187
|
+
incrementId: string,
|
|
188
|
+
): UserStoryForSync | null {
|
|
189
|
+
// Extract US title from content
|
|
190
|
+
const usNum = String(parseInt(usId.replace('US-', ''), 10)).padStart(3, '0');
|
|
191
|
+
const titleMatch = content.match(new RegExp(`### US-${usNum}:\\s*(.+)`));
|
|
192
|
+
const title = titleMatch ? titleMatch[1].trim() : usId;
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
id: usId,
|
|
196
|
+
title,
|
|
197
|
+
description: '',
|
|
198
|
+
priority: 'P1',
|
|
199
|
+
status: 'in-progress',
|
|
200
|
+
acceptanceCriteria: acStates.map(ac => ({
|
|
201
|
+
id: ac.id,
|
|
202
|
+
description: ac.description,
|
|
203
|
+
completed: ac.completed,
|
|
204
|
+
})),
|
|
205
|
+
specId: incrementId,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Build a progress comment for a specific user story.
|
|
211
|
+
* Follows ProgressCommentBuilder format: percentage, AC checkboxes, timestamp.
|
|
212
|
+
*/
|
|
213
|
+
function buildProgressCommentForUS(
|
|
214
|
+
incrementId: string,
|
|
215
|
+
usId: string,
|
|
216
|
+
acStates: ParsedACState[],
|
|
217
|
+
): string {
|
|
218
|
+
const total = acStates.length;
|
|
219
|
+
const completed = acStates.filter(ac => ac.completed).length;
|
|
220
|
+
const percentage = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
221
|
+
|
|
222
|
+
let comment = `**Progress Update** — ${usId} (Increment ${incrementId})\n\n`;
|
|
223
|
+
comment += `**Status**: ${completed}/${total} ACs complete (${percentage}%)\n\n`;
|
|
224
|
+
|
|
225
|
+
if (completed > 0) {
|
|
226
|
+
comment += `**Completed**:\n`;
|
|
227
|
+
for (const ac of acStates.filter(a => a.completed)) {
|
|
228
|
+
comment += `- [x] **${ac.id}**: ${ac.description}\n`;
|
|
229
|
+
}
|
|
230
|
+
comment += '\n';
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (completed < total) {
|
|
234
|
+
comment += `**Remaining**:\n`;
|
|
235
|
+
for (const ac of acStates.filter(a => !a.completed)) {
|
|
236
|
+
comment += `- [ ] **${ac.id}**: ${ac.description}\n`;
|
|
237
|
+
}
|
|
238
|
+
comment += '\n';
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
comment += `---\n`;
|
|
242
|
+
comment += `Auto-synced by SpecWeave | ${new Date().toISOString().split('T')[0]}\n`;
|
|
243
|
+
|
|
244
|
+
return comment;
|
|
245
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { glob } from "glob";
|
|
2
|
+
import { readFile } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { GitHubSyncOrchestrator } from "./github-sync-orchestrator.js";
|
|
5
|
+
async function batchSyncAllSpecs(config) {
|
|
6
|
+
const specsDir = join(config.workspaceRoot, ".specweave/docs/internal/specs");
|
|
7
|
+
const specFiles = await glob("**/spec-*.md", { cwd: specsDir });
|
|
8
|
+
const result = {
|
|
9
|
+
specsProcessed: 0,
|
|
10
|
+
specsFailed: 0,
|
|
11
|
+
totalIssuesCreated: 0,
|
|
12
|
+
totalIssuesUpdated: 0,
|
|
13
|
+
errors: [],
|
|
14
|
+
summary: ""
|
|
15
|
+
};
|
|
16
|
+
if (specFiles.length === 0) {
|
|
17
|
+
result.summary = "No specs found";
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
const orchestrator = new GitHubSyncOrchestrator({
|
|
21
|
+
owner: config.owner,
|
|
22
|
+
repo: config.repo,
|
|
23
|
+
token: config.token,
|
|
24
|
+
dryRun: config.dryRun,
|
|
25
|
+
projectV2Enabled: config.projectV2Enabled,
|
|
26
|
+
projectV2Number: config.projectV2Number,
|
|
27
|
+
projectV2Id: config.projectV2Id,
|
|
28
|
+
statusFieldMapping: config.statusFieldMapping,
|
|
29
|
+
priorityFieldMapping: config.priorityFieldMapping
|
|
30
|
+
});
|
|
31
|
+
for (const specFile of specFiles) {
|
|
32
|
+
const specPath = join(specsDir, specFile);
|
|
33
|
+
result.specsProcessed++;
|
|
34
|
+
try {
|
|
35
|
+
const content = await readFile(specPath, "utf-8");
|
|
36
|
+
const userStories = parseUserStoriesFromSpec(content);
|
|
37
|
+
const syncResult = await orchestrator.syncSpec(specPath, userStories);
|
|
38
|
+
result.totalIssuesCreated += syncResult.pushResult.created.length;
|
|
39
|
+
result.totalIssuesUpdated += syncResult.pushResult.updated.length;
|
|
40
|
+
} catch (err) {
|
|
41
|
+
result.specsFailed++;
|
|
42
|
+
result.errors.push({
|
|
43
|
+
specPath,
|
|
44
|
+
error: err instanceof Error ? err.message : String(err)
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const synced = result.specsProcessed - result.specsFailed;
|
|
49
|
+
result.summary = `Synced ${synced}/${result.specsProcessed} specs, ${result.totalIssuesCreated} issues created, ${result.totalIssuesUpdated} updated`;
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
const US_HEADER_RE = /^###?\s+(US-\d{3,}E?):\s*(.+)$/;
|
|
53
|
+
const AC_CHECKBOX_RE = /^-\s+\[([ xX])\]\s+\*\*(?<acId>AC-US\d+E?-\d{2})\*\*:\s*(?<desc>.+)$/;
|
|
54
|
+
const PRIORITY_RE = /\*\*Priority\*\*:\s*(P[0-3])/;
|
|
55
|
+
function parseUserStoriesFromSpec(content) {
|
|
56
|
+
const lines = content.split("\n");
|
|
57
|
+
const userStories = [];
|
|
58
|
+
let current = null;
|
|
59
|
+
for (const line of lines) {
|
|
60
|
+
const usMatch = line.match(US_HEADER_RE);
|
|
61
|
+
if (usMatch) {
|
|
62
|
+
if (current) userStories.push(current);
|
|
63
|
+
current = {
|
|
64
|
+
id: usMatch[1],
|
|
65
|
+
title: usMatch[2],
|
|
66
|
+
description: "",
|
|
67
|
+
priority: "P3",
|
|
68
|
+
status: "planned",
|
|
69
|
+
acceptanceCriteria: []
|
|
70
|
+
};
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (!current) continue;
|
|
74
|
+
const acMatch = line.match(AC_CHECKBOX_RE);
|
|
75
|
+
if (acMatch?.groups) {
|
|
76
|
+
current.acceptanceCriteria.push({
|
|
77
|
+
id: acMatch.groups.acId,
|
|
78
|
+
description: acMatch.groups.desc.trim(),
|
|
79
|
+
completed: acMatch[1] !== " "
|
|
80
|
+
});
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const prioMatch = line.match(PRIORITY_RE);
|
|
84
|
+
if (prioMatch) {
|
|
85
|
+
current.priority = prioMatch[1];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (current) userStories.push(current);
|
|
89
|
+
return userStories;
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
batchSyncAllSpecs
|
|
93
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Batch Sync — Sync all specs in workspace
|
|
3
|
+
*
|
|
4
|
+
* Discovers all spec files and syncs them sequentially
|
|
5
|
+
* using the GitHubSyncOrchestrator.
|
|
6
|
+
*
|
|
7
|
+
* @module github-batch-sync
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { glob } from 'glob';
|
|
11
|
+
import { readFile } from 'fs/promises';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
import { GitHubSyncOrchestrator } from './github-sync-orchestrator.js';
|
|
14
|
+
import type { UserStoryForSync } from './github-push-sync.js';
|
|
15
|
+
|
|
16
|
+
export interface BatchSyncConfig {
|
|
17
|
+
owner: string;
|
|
18
|
+
repo: string;
|
|
19
|
+
workspaceRoot: string;
|
|
20
|
+
token?: string;
|
|
21
|
+
dryRun?: boolean;
|
|
22
|
+
projectV2Enabled?: boolean;
|
|
23
|
+
projectV2Number?: number;
|
|
24
|
+
projectV2Id?: string;
|
|
25
|
+
statusFieldMapping?: Record<string, string>;
|
|
26
|
+
priorityFieldMapping?: Record<string, string>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface BatchSyncResult {
|
|
30
|
+
specsProcessed: number;
|
|
31
|
+
specsFailed: number;
|
|
32
|
+
totalIssuesCreated: number;
|
|
33
|
+
totalIssuesUpdated: number;
|
|
34
|
+
errors: Array<{ specPath: string; error: string }>;
|
|
35
|
+
summary: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Discover all specs and sync them sequentially.
|
|
40
|
+
*/
|
|
41
|
+
export async function batchSyncAllSpecs(config: BatchSyncConfig): Promise<BatchSyncResult> {
|
|
42
|
+
const specsDir = join(config.workspaceRoot, '.specweave/docs/internal/specs');
|
|
43
|
+
|
|
44
|
+
// Discover specs
|
|
45
|
+
const specFiles = await glob('**/spec-*.md', { cwd: specsDir });
|
|
46
|
+
|
|
47
|
+
const result: BatchSyncResult = {
|
|
48
|
+
specsProcessed: 0,
|
|
49
|
+
specsFailed: 0,
|
|
50
|
+
totalIssuesCreated: 0,
|
|
51
|
+
totalIssuesUpdated: 0,
|
|
52
|
+
errors: [],
|
|
53
|
+
summary: '',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (specFiles.length === 0) {
|
|
57
|
+
result.summary = 'No specs found';
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Create orchestrator
|
|
62
|
+
const orchestrator = new GitHubSyncOrchestrator({
|
|
63
|
+
owner: config.owner,
|
|
64
|
+
repo: config.repo,
|
|
65
|
+
token: config.token,
|
|
66
|
+
dryRun: config.dryRun,
|
|
67
|
+
projectV2Enabled: config.projectV2Enabled,
|
|
68
|
+
projectV2Number: config.projectV2Number,
|
|
69
|
+
projectV2Id: config.projectV2Id,
|
|
70
|
+
statusFieldMapping: config.statusFieldMapping,
|
|
71
|
+
priorityFieldMapping: config.priorityFieldMapping,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Sync each spec sequentially
|
|
75
|
+
for (const specFile of specFiles) {
|
|
76
|
+
const specPath = join(specsDir, specFile);
|
|
77
|
+
result.specsProcessed++;
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const content = await readFile(specPath, 'utf-8');
|
|
81
|
+
const userStories = parseUserStoriesFromSpec(content);
|
|
82
|
+
const syncResult = await orchestrator.syncSpec(specPath, userStories);
|
|
83
|
+
|
|
84
|
+
result.totalIssuesCreated += syncResult.pushResult.created.length;
|
|
85
|
+
result.totalIssuesUpdated += syncResult.pushResult.updated.length;
|
|
86
|
+
} catch (err) {
|
|
87
|
+
result.specsFailed++;
|
|
88
|
+
result.errors.push({
|
|
89
|
+
specPath,
|
|
90
|
+
error: err instanceof Error ? err.message : String(err),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const synced = result.specsProcessed - result.specsFailed;
|
|
96
|
+
result.summary = `Synced ${synced}/${result.specsProcessed} specs, ${result.totalIssuesCreated} issues created, ${result.totalIssuesUpdated} updated`;
|
|
97
|
+
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// US header: ### US-001: Title
|
|
102
|
+
const US_HEADER_RE = /^###?\s+(US-\d{3,}E?):\s*(.+)$/;
|
|
103
|
+
// AC checkbox: - [x] **AC-US1-01**: Description
|
|
104
|
+
const AC_CHECKBOX_RE = /^-\s+\[([ xX])\]\s+\*\*(?<acId>AC-US\d+E?-\d{2})\*\*:\s*(?<desc>.+)$/;
|
|
105
|
+
// Priority: **Priority**: P1
|
|
106
|
+
const PRIORITY_RE = /\*\*Priority\*\*:\s*(P[0-3])/;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Parse user stories from spec.md content.
|
|
110
|
+
*/
|
|
111
|
+
function parseUserStoriesFromSpec(content: string): UserStoryForSync[] {
|
|
112
|
+
const lines = content.split('\n');
|
|
113
|
+
const userStories: UserStoryForSync[] = [];
|
|
114
|
+
let current: UserStoryForSync | null = null;
|
|
115
|
+
|
|
116
|
+
for (const line of lines) {
|
|
117
|
+
const usMatch = line.match(US_HEADER_RE);
|
|
118
|
+
if (usMatch) {
|
|
119
|
+
if (current) userStories.push(current);
|
|
120
|
+
current = {
|
|
121
|
+
id: usMatch[1],
|
|
122
|
+
title: usMatch[2],
|
|
123
|
+
description: '',
|
|
124
|
+
priority: 'P3',
|
|
125
|
+
status: 'planned',
|
|
126
|
+
acceptanceCriteria: [],
|
|
127
|
+
};
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!current) continue;
|
|
132
|
+
|
|
133
|
+
const acMatch = line.match(AC_CHECKBOX_RE);
|
|
134
|
+
if (acMatch?.groups) {
|
|
135
|
+
current.acceptanceCriteria.push({
|
|
136
|
+
id: acMatch.groups.acId,
|
|
137
|
+
description: acMatch.groups.desc.trim(),
|
|
138
|
+
completed: acMatch[1] !== ' ',
|
|
139
|
+
});
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const prioMatch = line.match(PRIORITY_RE);
|
|
144
|
+
if (prioMatch) {
|
|
145
|
+
current.priority = prioMatch[1];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (current) userStories.push(current);
|
|
150
|
+
|
|
151
|
+
return userStories;
|
|
152
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
class GitHubBoardResolverV2 {
|
|
2
|
+
constructor(client, config) {
|
|
3
|
+
this.client = client;
|
|
4
|
+
this.config = config;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Find an existing project or create a new one.
|
|
8
|
+
*
|
|
9
|
+
* Priority:
|
|
10
|
+
* 1. If projectV2Id is configured, use it directly (skip lookup)
|
|
11
|
+
* 2. If projectV2Number is configured, return with that number
|
|
12
|
+
* 3. Otherwise, create a new project
|
|
13
|
+
*/
|
|
14
|
+
async findOrCreateProject(title) {
|
|
15
|
+
if (this.config.projectV2Id) {
|
|
16
|
+
return {
|
|
17
|
+
id: this.config.projectV2Id,
|
|
18
|
+
number: this.config.projectV2Number || 0
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (this.config.projectV2Number) {
|
|
22
|
+
const ownerId2 = await this.client.getOwnerNodeId(this.config.owner);
|
|
23
|
+
return {
|
|
24
|
+
id: ownerId2,
|
|
25
|
+
// Will be resolved to actual project ID during operations
|
|
26
|
+
number: this.config.projectV2Number
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const ownerId = await this.client.getOwnerNodeId(this.config.owner);
|
|
30
|
+
return this.client.createProjectV2(ownerId, title);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Add issues (by node ID) to a Projects V2 board.
|
|
34
|
+
* Returns the project item IDs for each added issue.
|
|
35
|
+
*/
|
|
36
|
+
async addIssuesToProject(projectId, issueNodeIds) {
|
|
37
|
+
const itemIds = [];
|
|
38
|
+
for (const nodeId of issueNodeIds) {
|
|
39
|
+
const itemId = await this.client.addProjectV2Item(projectId, nodeId);
|
|
40
|
+
itemIds.push(itemId);
|
|
41
|
+
}
|
|
42
|
+
return itemIds;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export {
|
|
46
|
+
GitHubBoardResolverV2
|
|
47
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Board Resolver V2 — Projects V2 Integration
|
|
3
|
+
*
|
|
4
|
+
* Finds or creates GitHub Projects V2 boards and adds issues to them.
|
|
5
|
+
* Replaces the deprecated V1 Classic Projects board resolver.
|
|
6
|
+
*
|
|
7
|
+
* @module github-board-resolver-v2
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { GitHubGraphQLClient } from './github-graphql-client.js';
|
|
11
|
+
|
|
12
|
+
export interface BoardResolverConfig {
|
|
13
|
+
owner: string;
|
|
14
|
+
projectV2Number?: number;
|
|
15
|
+
projectV2Id?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class GitHubBoardResolverV2 {
|
|
19
|
+
private client: GitHubGraphQLClient;
|
|
20
|
+
private config: BoardResolverConfig;
|
|
21
|
+
|
|
22
|
+
constructor(client: GitHubGraphQLClient, config: BoardResolverConfig) {
|
|
23
|
+
this.client = client;
|
|
24
|
+
this.config = config;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Find an existing project or create a new one.
|
|
29
|
+
*
|
|
30
|
+
* Priority:
|
|
31
|
+
* 1. If projectV2Id is configured, use it directly (skip lookup)
|
|
32
|
+
* 2. If projectV2Number is configured, return with that number
|
|
33
|
+
* 3. Otherwise, create a new project
|
|
34
|
+
*/
|
|
35
|
+
async findOrCreateProject(title: string): Promise<{ id: string; number: number }> {
|
|
36
|
+
// If we have a direct project ID, use it
|
|
37
|
+
if (this.config.projectV2Id) {
|
|
38
|
+
return {
|
|
39
|
+
id: this.config.projectV2Id,
|
|
40
|
+
number: this.config.projectV2Number || 0,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// If we have a project number, look it up by querying the owner
|
|
45
|
+
if (this.config.projectV2Number) {
|
|
46
|
+
const ownerId = await this.client.getOwnerNodeId(this.config.owner);
|
|
47
|
+
// Return the project reference — the number is known, ID will be resolved on first use
|
|
48
|
+
return {
|
|
49
|
+
id: ownerId, // Will be resolved to actual project ID during operations
|
|
50
|
+
number: this.config.projectV2Number,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// No project configured — create a new one
|
|
55
|
+
const ownerId = await this.client.getOwnerNodeId(this.config.owner);
|
|
56
|
+
return this.client.createProjectV2(ownerId, title);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Add issues (by node ID) to a Projects V2 board.
|
|
61
|
+
* Returns the project item IDs for each added issue.
|
|
62
|
+
*/
|
|
63
|
+
async addIssuesToProject(projectId: string, issueNodeIds: string[]): Promise<string[]> {
|
|
64
|
+
const itemIds: string[] = [];
|
|
65
|
+
|
|
66
|
+
for (const nodeId of issueNodeIds) {
|
|
67
|
+
const itemId = await this.client.addProjectV2Item(projectId, nodeId);
|
|
68
|
+
itemIds.push(itemId);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return itemIds;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
class GitHubConflictResolver {
|
|
2
|
+
constructor(config) {
|
|
3
|
+
this.statusResolution = config?.defaultStatusResolution ?? "github-wins";
|
|
4
|
+
this.contentResolution = config?.defaultContentResolution ?? "prompt";
|
|
5
|
+
this.acResolution = config?.defaultACResolution ?? "github-wins";
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Detect conflicts between spec and GitHub states.
|
|
9
|
+
*/
|
|
10
|
+
detectConflicts(specState, githubState) {
|
|
11
|
+
const conflicts = [];
|
|
12
|
+
if (specState.title !== githubState.title) {
|
|
13
|
+
conflicts.push({
|
|
14
|
+
field: "title",
|
|
15
|
+
specValue: specState.title,
|
|
16
|
+
githubValue: githubState.title,
|
|
17
|
+
defaultResolution: this.contentResolution
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const githubStatus = githubState.state;
|
|
21
|
+
const specStatus = specState.status;
|
|
22
|
+
const statusMismatch = this.isStatusConflict(specStatus, githubStatus);
|
|
23
|
+
if (statusMismatch) {
|
|
24
|
+
conflicts.push({
|
|
25
|
+
field: "status",
|
|
26
|
+
specValue: specStatus,
|
|
27
|
+
githubValue: githubStatus,
|
|
28
|
+
defaultResolution: this.statusResolution
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const ghAcMap = new Map(
|
|
32
|
+
githubState.acceptanceCriteria.map((ac) => [ac.id, ac.completed])
|
|
33
|
+
);
|
|
34
|
+
for (const specAc of specState.acceptanceCriteria) {
|
|
35
|
+
const ghCompleted = ghAcMap.get(specAc.id);
|
|
36
|
+
if (ghCompleted !== void 0 && specAc.completed !== ghCompleted) {
|
|
37
|
+
conflicts.push({
|
|
38
|
+
field: `ac:${specAc.id}`,
|
|
39
|
+
specValue: String(specAc.completed),
|
|
40
|
+
githubValue: String(ghCompleted),
|
|
41
|
+
defaultResolution: this.acResolution
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return conflicts;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Resolve all conflicts using their default resolution strategy.
|
|
49
|
+
*/
|
|
50
|
+
resolveConflicts(conflicts) {
|
|
51
|
+
return conflicts.map((c) => this.resolveConflict(c));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolve a single conflict with optional override.
|
|
55
|
+
*/
|
|
56
|
+
resolveConflict(conflict, resolution) {
|
|
57
|
+
const effectiveResolution = resolution ?? conflict.defaultResolution;
|
|
58
|
+
let resolvedValue;
|
|
59
|
+
switch (effectiveResolution) {
|
|
60
|
+
case "github-wins":
|
|
61
|
+
resolvedValue = conflict.githubValue;
|
|
62
|
+
break;
|
|
63
|
+
case "spec-wins":
|
|
64
|
+
resolvedValue = conflict.specValue;
|
|
65
|
+
break;
|
|
66
|
+
case "prompt":
|
|
67
|
+
resolvedValue = conflict.specValue;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
field: conflict.field,
|
|
72
|
+
specValue: conflict.specValue,
|
|
73
|
+
githubValue: conflict.githubValue,
|
|
74
|
+
resolution: effectiveResolution,
|
|
75
|
+
resolvedValue,
|
|
76
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check if spec status and GitHub state represent a conflict.
|
|
81
|
+
*/
|
|
82
|
+
isStatusConflict(specStatus, githubState) {
|
|
83
|
+
if (specStatus === "completed" && githubState === "open") return true;
|
|
84
|
+
if (specStatus !== "completed" && githubState === "closed") return true;
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export {
|
|
89
|
+
GitHubConflictResolver
|
|
90
|
+
};
|