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,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub US Auto-Closer — Closes GitHub issues when all ACs are complete
|
|
3
|
+
*
|
|
4
|
+
* For each affected user story, checks if ALL acceptance criteria are
|
|
5
|
+
* marked complete in spec.md. If so, posts a completion comment and
|
|
6
|
+
* closes the associated GitHub issue.
|
|
7
|
+
*
|
|
8
|
+
* @module github-us-auto-closer
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFile } from 'fs/promises';
|
|
12
|
+
import { execFileNoThrow } from '../../../src/utils/execFileNoThrow.js';
|
|
13
|
+
|
|
14
|
+
export interface AutoCloseOptions {
|
|
15
|
+
owner: string;
|
|
16
|
+
repo: string;
|
|
17
|
+
token?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface AutoCloseResult {
|
|
21
|
+
closed: Array<{ usId: string; issueNumber: number }>;
|
|
22
|
+
skipped: Array<{ usId: string; reason: string }>;
|
|
23
|
+
errors: Array<{ usId: string; error: string }>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface ParsedACState {
|
|
27
|
+
id: string;
|
|
28
|
+
description: string;
|
|
29
|
+
completed: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface ParsedUSIssueLink {
|
|
33
|
+
issueNumber: number;
|
|
34
|
+
issueUrl: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Auto-close GitHub issues for user stories with all ACs complete.
|
|
39
|
+
*
|
|
40
|
+
* For each affected US:
|
|
41
|
+
* 1. Parse AC states from spec.md
|
|
42
|
+
* 2. Skip if not all ACs complete
|
|
43
|
+
* 3. Look up GitHub issue link from frontmatter
|
|
44
|
+
* 4. Check if issue already closed (idempotent)
|
|
45
|
+
* 5. Post completion comment
|
|
46
|
+
* 6. Close issue via `gh issue close`
|
|
47
|
+
*
|
|
48
|
+
* Never throws — all errors captured in result.errors.
|
|
49
|
+
*/
|
|
50
|
+
export async function autoCloseCompletedUserStories(
|
|
51
|
+
incrementId: string,
|
|
52
|
+
affectedUSIds: string[],
|
|
53
|
+
specPath: string,
|
|
54
|
+
options: AutoCloseOptions,
|
|
55
|
+
): Promise<AutoCloseResult> {
|
|
56
|
+
const result: AutoCloseResult = { closed: [], skipped: [], errors: [] };
|
|
57
|
+
|
|
58
|
+
if (affectedUSIds.length === 0) {
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let content: string;
|
|
63
|
+
try {
|
|
64
|
+
content = await readFile(specPath, 'utf-8');
|
|
65
|
+
} catch (err) {
|
|
66
|
+
result.errors.push({
|
|
67
|
+
usId: affectedUSIds[0],
|
|
68
|
+
error: err instanceof Error ? err.message : String(err),
|
|
69
|
+
});
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const issueLinks = parseIssueLinks(content);
|
|
74
|
+
const repoSlug = `${options.owner}/${options.repo}`;
|
|
75
|
+
const env = options.token ? { GH_TOKEN: options.token } : undefined;
|
|
76
|
+
const execOpts = env ? { env } : {};
|
|
77
|
+
|
|
78
|
+
for (const usId of affectedUSIds) {
|
|
79
|
+
const acStates = parseACStatesForUS(content, usId);
|
|
80
|
+
|
|
81
|
+
// Skip if not all ACs complete
|
|
82
|
+
if (acStates.length === 0 || acStates.some(ac => !ac.completed)) {
|
|
83
|
+
result.skipped.push({ usId, reason: 'incomplete-acs' });
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Skip if no issue link
|
|
88
|
+
const link = issueLinks[usId];
|
|
89
|
+
if (!link) {
|
|
90
|
+
result.skipped.push({ usId, reason: 'no-issue-link' });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const issueNum = String(link.issueNumber);
|
|
95
|
+
|
|
96
|
+
// Check if already closed (idempotent)
|
|
97
|
+
const viewResult = await execFileNoThrow(
|
|
98
|
+
'gh',
|
|
99
|
+
['issue', 'view', issueNum, '--json', 'state', '-R', repoSlug],
|
|
100
|
+
execOpts,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (viewResult.success) {
|
|
104
|
+
try {
|
|
105
|
+
const issueState = JSON.parse(viewResult.stdout);
|
|
106
|
+
if (issueState.state === 'CLOSED') {
|
|
107
|
+
result.skipped.push({ usId, reason: 'already-closed' });
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
} catch {
|
|
111
|
+
// Parse error — proceed with close attempt
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Post completion comment
|
|
116
|
+
const commentBody = buildCompletionComment(incrementId, usId, acStates);
|
|
117
|
+
await execFileNoThrow(
|
|
118
|
+
'gh',
|
|
119
|
+
['issue', 'comment', issueNum, '--body', commentBody, '-R', repoSlug],
|
|
120
|
+
execOpts,
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Close the issue
|
|
124
|
+
const closeResult = await execFileNoThrow(
|
|
125
|
+
'gh',
|
|
126
|
+
['issue', 'close', issueNum, '-R', repoSlug],
|
|
127
|
+
execOpts,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
if (closeResult.success) {
|
|
131
|
+
result.closed.push({ usId, issueNumber: link.issueNumber });
|
|
132
|
+
} else {
|
|
133
|
+
result.errors.push({
|
|
134
|
+
usId,
|
|
135
|
+
error: closeResult.stderr || 'Unknown error closing issue',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Build a completion comment for a fully-complete user story.
|
|
145
|
+
*/
|
|
146
|
+
function buildCompletionComment(
|
|
147
|
+
incrementId: string,
|
|
148
|
+
usId: string,
|
|
149
|
+
acStates: ParsedACState[],
|
|
150
|
+
): string {
|
|
151
|
+
const total = acStates.length;
|
|
152
|
+
|
|
153
|
+
let comment = `**All acceptance criteria completed** — ${usId} (Increment ${incrementId})\n\n`;
|
|
154
|
+
comment += `**Status**: ${total}/${total} ACs complete (100%)\n\n`;
|
|
155
|
+
|
|
156
|
+
comment += `**Completed**:\n`;
|
|
157
|
+
for (const ac of acStates) {
|
|
158
|
+
comment += `- [x] **${ac.id}**: ${ac.description}\n`;
|
|
159
|
+
}
|
|
160
|
+
comment += '\n';
|
|
161
|
+
|
|
162
|
+
comment += `---\n`;
|
|
163
|
+
comment += `Auto-closed by SpecWeave | ${new Date().toISOString().split('T')[0]}\n`;
|
|
164
|
+
|
|
165
|
+
return comment;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Parse issue links from spec.md YAML frontmatter.
|
|
170
|
+
*/
|
|
171
|
+
function parseIssueLinks(content: string): Record<string, ParsedUSIssueLink> {
|
|
172
|
+
const links: Record<string, ParsedUSIssueLink> = {};
|
|
173
|
+
|
|
174
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
175
|
+
if (!fmMatch) return links;
|
|
176
|
+
|
|
177
|
+
const frontmatter = fmMatch[1];
|
|
178
|
+
|
|
179
|
+
const usBlockMatch = frontmatter.match(/userStories:\s*\n((?:\s{6,}[\s\S]*?)(?=\n\s{0,3}\S|$))/);
|
|
180
|
+
if (!usBlockMatch) return links;
|
|
181
|
+
|
|
182
|
+
const usBlock = usBlockMatch[1];
|
|
183
|
+
const usEntries = usBlock.match(/^\s+(US-\d+):\s*\n((?:\s+\w[\s\S]*?)(?=\n\s+US-|\s*$))/gm);
|
|
184
|
+
|
|
185
|
+
if (!usEntries) return links;
|
|
186
|
+
|
|
187
|
+
for (const entry of usEntries) {
|
|
188
|
+
const idMatch = entry.match(/(US-\d+):/);
|
|
189
|
+
const numMatch = entry.match(/issueNumber:\s*(\d+)/);
|
|
190
|
+
const urlMatch = entry.match(/issueUrl:\s*"([^"]+)"/);
|
|
191
|
+
|
|
192
|
+
if (idMatch && numMatch) {
|
|
193
|
+
links[idMatch[1]] = {
|
|
194
|
+
issueNumber: parseInt(numMatch[1], 10),
|
|
195
|
+
issueUrl: urlMatch ? urlMatch[1] : '',
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return links;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Extract AC states for a specific user story from spec.md content.
|
|
205
|
+
*/
|
|
206
|
+
function parseACStatesForUS(content: string, usId: string): ParsedACState[] {
|
|
207
|
+
const states: ParsedACState[] = [];
|
|
208
|
+
// AC IDs use unpadded US number: US-001 → AC-US1-XX
|
|
209
|
+
const usNum = String(parseInt(usId.replace('US-', ''), 10));
|
|
210
|
+
|
|
211
|
+
const acPattern = new RegExp(
|
|
212
|
+
`- \\[([ x])\\] \\*\\*AC-US${usNum}-(\\d+)\\*\\*:\\s*(.+)`,
|
|
213
|
+
'g',
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
let match;
|
|
217
|
+
while ((match = acPattern.exec(content)) !== null) {
|
|
218
|
+
states.push({
|
|
219
|
+
id: `AC-US${usNum}-${match[2]}`,
|
|
220
|
+
description: match[3].trim(),
|
|
221
|
+
completed: match[1] === 'x',
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return states;
|
|
226
|
+
}
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import { GitHubClient } from "./github-client";
|
|
2
|
-
import { TaskParser } from "./task-parser";
|
|
3
|
-
import { TaskSync } from "./task-sync";
|
|
4
|
-
import { SubtaskSync } from "./subtask-sync";
|
|
5
2
|
export * from "./types";
|
|
6
3
|
export {
|
|
7
|
-
GitHubClient
|
|
8
|
-
SubtaskSync,
|
|
9
|
-
TaskParser,
|
|
10
|
-
TaskSync
|
|
4
|
+
GitHubClient
|
|
11
5
|
};
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SpecWeave GitHub Plugin - Library Entry Point
|
|
3
|
-
*
|
|
3
|
+
* Spec-based GitHub synchronization
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export { GitHubClient } from './github-client';
|
|
7
|
-
export { TaskParser } from './task-parser';
|
|
8
|
-
export { TaskSync } from './task-sync';
|
|
9
|
-
export { SubtaskSync } from './subtask-sync';
|
|
10
7
|
export * from './types';
|
|
@@ -457,14 +457,86 @@ For monorepos with multiple GitHub repositories:
|
|
|
457
457
|
|
|
458
458
|
---
|
|
459
459
|
|
|
460
|
+
## Projects V2 Integration (v3.0.0+)
|
|
461
|
+
|
|
462
|
+
### Enabling Projects V2
|
|
463
|
+
|
|
464
|
+
Add `projectV2Enabled: true` to your sync config:
|
|
465
|
+
|
|
466
|
+
```json
|
|
467
|
+
{
|
|
468
|
+
"sync": {
|
|
469
|
+
"profiles": {
|
|
470
|
+
"myproject": {
|
|
471
|
+
"provider": "github",
|
|
472
|
+
"config": {
|
|
473
|
+
"owner": "myorg",
|
|
474
|
+
"repo": "myrepo",
|
|
475
|
+
"projectV2Enabled": true,
|
|
476
|
+
"projectV2Number": 5
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### What Happens with V2 Enabled
|
|
485
|
+
|
|
486
|
+
1. **Push sync** creates/updates GitHub issues (same as before)
|
|
487
|
+
2. **Issues are added to Projects V2 board** automatically
|
|
488
|
+
3. **Status field** is set based on user story status (planned/in-progress/completed)
|
|
489
|
+
4. **Priority field** is set based on user story priority (P1-P4)
|
|
490
|
+
|
|
491
|
+
### Custom Field Mappings
|
|
492
|
+
|
|
493
|
+
```json
|
|
494
|
+
{
|
|
495
|
+
"statusFieldMapping": {
|
|
496
|
+
"planned": "Todo",
|
|
497
|
+
"in-progress": "In Progress",
|
|
498
|
+
"completed": "Done"
|
|
499
|
+
},
|
|
500
|
+
"priorityFieldMapping": {
|
|
501
|
+
"P1": "Urgent",
|
|
502
|
+
"P2": "High",
|
|
503
|
+
"P3": "Medium",
|
|
504
|
+
"P4": "Low"
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Pull Sync (GitHub to Spec)
|
|
510
|
+
|
|
511
|
+
Pull sync fetches issue state and compares with spec ACs:
|
|
512
|
+
- AC checkbox toggles on GitHub are detected and applied to spec
|
|
513
|
+
- Issue close/reopen is detected as status change
|
|
514
|
+
- Conflicts are detected when both spec and GitHub changed the same field
|
|
515
|
+
|
|
516
|
+
### Batch Sync
|
|
517
|
+
|
|
518
|
+
Sync all specs at once:
|
|
519
|
+
- Discovers all `spec-*.md` files in `.specweave/docs/internal/specs/`
|
|
520
|
+
- Syncs each sequentially
|
|
521
|
+
- Reports summary with created/updated/failed counts
|
|
522
|
+
|
|
523
|
+
### Cross-Repo Sync
|
|
524
|
+
|
|
525
|
+
For distributed strategies, issues can be created in different repos:
|
|
526
|
+
- User stories specify target repos via `targetRepos` field
|
|
527
|
+
- Cross-references added: "Also tracked in: org/other-repo#XX"
|
|
528
|
+
- All cross-repo issues added to shared org-level Projects V2 board
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
460
532
|
## Related
|
|
461
533
|
|
|
462
|
-
- **github-issue-tracker**:
|
|
463
|
-
- **github-manager agent**: AI agent for GitHub operations
|
|
534
|
+
- **github-issue-tracker**: DEPRECATED - use spec sync instead
|
|
464
535
|
- **Commands**: `/sw-github:sync-spec`, `/sw-github:import-project`, `/sw-github:status`
|
|
536
|
+
- **Team Skills**: `/sw:team-orchestrate`, `/sw:team-status`, `/sw:team-merge`
|
|
465
537
|
|
|
466
538
|
---
|
|
467
539
|
|
|
468
|
-
**Version**:
|
|
540
|
+
**Version**: 3.0.0 (Projects V2 + Pull Sync + Cross-Repo)
|
|
469
541
|
**Plugin**: specweave-github
|
|
470
|
-
**Last Updated**:
|
|
542
|
+
**Last Updated**: 2026-02-06
|
|
@@ -8,6 +8,24 @@ Set up comprehensive Playwright E2E testing with best practices, page objects, a
|
|
|
8
8
|
|
|
9
9
|
You are an expert E2E testing engineer who implements production-ready Playwright test suites.
|
|
10
10
|
|
|
11
|
+
## CLI vs MCP Mode
|
|
12
|
+
|
|
13
|
+
SpecWeave supports two modes for Playwright browser automation:
|
|
14
|
+
|
|
15
|
+
- **@playwright/cli** (recommended for test execution): Token-efficient, file-based output, CI-friendly
|
|
16
|
+
- **@playwright/mcp** (for interactive exploration): Rich inline snapshots, good for debugging
|
|
17
|
+
|
|
18
|
+
Install CLI mode: `npm install -g @playwright/cli@latest`
|
|
19
|
+
|
|
20
|
+
Configure preference in `.specweave/config.json`:
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"testing": {
|
|
24
|
+
"playwright": { "preferCli": true }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
11
29
|
## Your Task
|
|
12
30
|
|
|
13
31
|
Set up a complete Playwright E2E testing framework with page objects, fixtures, and testing patterns.
|
|
@@ -21,6 +21,8 @@ Create and execute automated browser workflows using Playwright. Generate script
|
|
|
21
21
|
|
|
22
22
|
> **Why Code-First?** Anthropic research shows [code execution beats MCP tool calls](https://www.anthropic.com/engineering/code-execution-with-mcp) with 98% token reduction. Playwright code is reusable, committable, CI-runnable, and deterministic.
|
|
23
23
|
|
|
24
|
+
> **CLI Mode Available**: When `@playwright/cli` is installed, this command routes to the CLI by default for maximum token efficiency (~250 chars per interaction vs ~5K+ via MCP). The CLI keeps browser state external and returns file references instead of inline DOM trees. Install: `npm install -g @playwright/cli@latest`
|
|
25
|
+
|
|
24
26
|
## Workflow Types
|
|
25
27
|
|
|
26
28
|
### 1. Form Automation
|
|
@@ -63,6 +63,14 @@ Provides:
|
|
|
63
63
|
- **Accessibility Audit**: Check element accessibility
|
|
64
64
|
- **Bug Investigation**: Debug element behavior and properties
|
|
65
65
|
|
|
66
|
+
## Browser Mode
|
|
67
|
+
|
|
68
|
+
This command **prefers MCP mode** for rich DOM introspection. When the Playwright MCP plugin provides inline accessibility tree snapshots, the AI can reason about page structure and find optimal selectors.
|
|
69
|
+
|
|
70
|
+
If MCP is unavailable, falls back to `@playwright/cli snapshot` which saves the accessibility tree to a file (`.playwright-cli/*.yml`). The AI can then read the file, but loses the ability to iterate on snapshots without re-reading.
|
|
71
|
+
|
|
72
|
+
**Recommended**: Keep the Playwright MCP plugin installed for best `ui-inspect` experience.
|
|
73
|
+
|
|
66
74
|
## Requirements
|
|
67
75
|
|
|
68
76
|
- Playwright browser binaries (auto-installed on first use)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
function isRunningInCI() {
|
|
2
|
+
return !!(process.env.CI === 'true' ||
|
|
3
|
+
process.env.GITHUB_ACTIONS === 'true' ||
|
|
4
|
+
process.env.GITLAB_CI === 'true' ||
|
|
5
|
+
process.env.JENKINS_URL);
|
|
6
|
+
}
|
|
7
|
+
export function getCiDefaults(options) {
|
|
8
|
+
const isCI = isRunningInCI();
|
|
9
|
+
return {
|
|
10
|
+
isCI,
|
|
11
|
+
headed: isCI ? false : (options?.headed ?? false),
|
|
12
|
+
outputDir: options?.outputDir ?? '.playwright-cli',
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type CiConfig = {
|
|
2
|
+
headed: boolean;
|
|
3
|
+
isCI: boolean;
|
|
4
|
+
outputDir: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
function isRunningInCI(): boolean {
|
|
8
|
+
return !!(
|
|
9
|
+
process.env.CI === 'true' ||
|
|
10
|
+
process.env.GITHUB_ACTIONS === 'true' ||
|
|
11
|
+
process.env.GITLAB_CI === 'true' ||
|
|
12
|
+
process.env.JENKINS_URL
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getCiDefaults(options?: Partial<CiConfig>): CiConfig {
|
|
17
|
+
const isCI = isRunningInCI();
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
isCI,
|
|
21
|
+
headed: isCI ? false : (options?.headed ?? false),
|
|
22
|
+
outputDir: options?.outputDir ?? '.playwright-cli',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
let cachedResult = null;
|
|
3
|
+
function detectPlaywrightCli(options = {}) {
|
|
4
|
+
const { useCache = false } = options;
|
|
5
|
+
if (useCache && cachedResult) {
|
|
6
|
+
return cachedResult;
|
|
7
|
+
}
|
|
8
|
+
let path;
|
|
9
|
+
let version;
|
|
10
|
+
try {
|
|
11
|
+
path = execSync("which playwright-cli", {
|
|
12
|
+
encoding: "utf-8",
|
|
13
|
+
timeout: 5e3
|
|
14
|
+
}).trim();
|
|
15
|
+
} catch {
|
|
16
|
+
const result2 = { installed: false };
|
|
17
|
+
if (useCache) cachedResult = result2;
|
|
18
|
+
return result2;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
version = execSync("playwright-cli --version", {
|
|
22
|
+
encoding: "utf-8",
|
|
23
|
+
timeout: 5e3
|
|
24
|
+
}).trim();
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
const result = { installed: true, path, version };
|
|
28
|
+
if (useCache) cachedResult = result;
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
detectPlaywrightCli
|
|
33
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
|
|
3
|
+
export interface CliDetectionResult {
|
|
4
|
+
installed: boolean;
|
|
5
|
+
version?: string;
|
|
6
|
+
path?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface DetectOptions {
|
|
10
|
+
useCache?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let cachedResult: CliDetectionResult | null = null;
|
|
14
|
+
|
|
15
|
+
export function detectPlaywrightCli(options: DetectOptions = {}): CliDetectionResult {
|
|
16
|
+
const { useCache = false } = options;
|
|
17
|
+
|
|
18
|
+
if (useCache && cachedResult) {
|
|
19
|
+
return cachedResult;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let path: string | undefined;
|
|
23
|
+
let version: string | undefined;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
path = execSync('which playwright-cli', {
|
|
27
|
+
encoding: 'utf-8',
|
|
28
|
+
timeout: 5_000,
|
|
29
|
+
}).trim();
|
|
30
|
+
} catch {
|
|
31
|
+
const result: CliDetectionResult = { installed: false };
|
|
32
|
+
if (useCache) cachedResult = result;
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
version = execSync('playwright-cli --version', {
|
|
38
|
+
encoding: 'utf-8',
|
|
39
|
+
timeout: 5_000,
|
|
40
|
+
}).trim();
|
|
41
|
+
} catch {
|
|
42
|
+
// Version check failed but binary exists
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const result: CliDetectionResult = { installed: true, path, version };
|
|
46
|
+
if (useCache) cachedResult = result;
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
class PlaywrightCliRunner {
|
|
3
|
+
constructor(config = {}) {
|
|
4
|
+
this.config = {
|
|
5
|
+
headed: config.headed ?? false,
|
|
6
|
+
browser: config.browser ?? "chrome",
|
|
7
|
+
session: config.session,
|
|
8
|
+
timeout: config.timeout ?? 3e4,
|
|
9
|
+
...config
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
exec(command) {
|
|
13
|
+
const sessionFlag = this.config.session ? `-s=${this.config.session} ` : "";
|
|
14
|
+
const fullCommand = `playwright-cli ${sessionFlag}${command}`;
|
|
15
|
+
try {
|
|
16
|
+
const output = execSync(fullCommand, {
|
|
17
|
+
encoding: "utf-8",
|
|
18
|
+
timeout: this.config.timeout
|
|
19
|
+
}).trim();
|
|
20
|
+
return { ok: true, output };
|
|
21
|
+
} catch (e) {
|
|
22
|
+
return { ok: false, output: e.message };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
open(url) {
|
|
26
|
+
const headedFlag = this.config.headed ? " --headed" : "";
|
|
27
|
+
const urlPart = url ? ` ${url}` : "";
|
|
28
|
+
return this.exec(`open${urlPart}${headedFlag}`);
|
|
29
|
+
}
|
|
30
|
+
navigate(url) {
|
|
31
|
+
return this.exec(`goto ${url}`);
|
|
32
|
+
}
|
|
33
|
+
snapshot() {
|
|
34
|
+
return this.exec("snapshot");
|
|
35
|
+
}
|
|
36
|
+
screenshot(filename) {
|
|
37
|
+
const flag = filename ? ` --filename ${filename}` : "";
|
|
38
|
+
return this.exec(`screenshot${flag}`);
|
|
39
|
+
}
|
|
40
|
+
close() {
|
|
41
|
+
return this.exec("close");
|
|
42
|
+
}
|
|
43
|
+
click(ref) {
|
|
44
|
+
return this.exec(`click ${ref}`);
|
|
45
|
+
}
|
|
46
|
+
type(text) {
|
|
47
|
+
return this.exec(`type "${text}"`);
|
|
48
|
+
}
|
|
49
|
+
fill(ref, text) {
|
|
50
|
+
return this.exec(`fill ${ref} "${text}"`);
|
|
51
|
+
}
|
|
52
|
+
evaluate(fn) {
|
|
53
|
+
return this.exec(`eval "${fn}"`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
PlaywrightCliRunner
|
|
58
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
|
|
3
|
+
export interface CliRunnerConfig {
|
|
4
|
+
headed?: boolean;
|
|
5
|
+
browser?: string;
|
|
6
|
+
session?: string;
|
|
7
|
+
timeout?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CliResult {
|
|
11
|
+
ok: boolean;
|
|
12
|
+
output: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class PlaywrightCliRunner {
|
|
16
|
+
readonly config: Required<Pick<CliRunnerConfig, 'headed' | 'browser'>> & CliRunnerConfig;
|
|
17
|
+
|
|
18
|
+
constructor(config: CliRunnerConfig = {}) {
|
|
19
|
+
this.config = {
|
|
20
|
+
headed: config.headed ?? false,
|
|
21
|
+
browser: config.browser ?? 'chrome',
|
|
22
|
+
session: config.session,
|
|
23
|
+
timeout: config.timeout ?? 30_000,
|
|
24
|
+
...config,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private exec(command: string): CliResult {
|
|
29
|
+
const sessionFlag = this.config.session ? `-s=${this.config.session} ` : '';
|
|
30
|
+
const fullCommand = `playwright-cli ${sessionFlag}${command}`;
|
|
31
|
+
try {
|
|
32
|
+
const output = execSync(fullCommand, {
|
|
33
|
+
encoding: 'utf-8',
|
|
34
|
+
timeout: this.config.timeout,
|
|
35
|
+
}).trim();
|
|
36
|
+
return { ok: true, output };
|
|
37
|
+
} catch (e: unknown) {
|
|
38
|
+
return { ok: false, output: (e as Error).message };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
open(url?: string): CliResult {
|
|
43
|
+
const headedFlag = this.config.headed ? ' --headed' : '';
|
|
44
|
+
const urlPart = url ? ` ${url}` : '';
|
|
45
|
+
return this.exec(`open${urlPart}${headedFlag}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
navigate(url: string): CliResult {
|
|
49
|
+
return this.exec(`goto ${url}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
snapshot(): CliResult {
|
|
53
|
+
return this.exec('snapshot');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
screenshot(filename?: string): CliResult {
|
|
57
|
+
const flag = filename ? ` --filename ${filename}` : '';
|
|
58
|
+
return this.exec(`screenshot${flag}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
close(): CliResult {
|
|
62
|
+
return this.exec('close');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
click(ref: string): CliResult {
|
|
66
|
+
return this.exec(`click ${ref}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type(text: string): CliResult {
|
|
70
|
+
return this.exec(`type "${text}"`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fill(ref: string, text: string): CliResult {
|
|
74
|
+
return this.exec(`fill ${ref} "${text}"`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
evaluate(fn: string): CliResult {
|
|
78
|
+
return this.exec(`eval "${fn}"`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { detectPlaywrightCli } from "./playwright-cli-detector.js";
|
|
2
|
+
const MCP_PREFERRED_TASKS = /* @__PURE__ */ new Set([
|
|
3
|
+
"ui-inspect",
|
|
4
|
+
"page-exploration",
|
|
5
|
+
"self-healing-test"
|
|
6
|
+
]);
|
|
7
|
+
function resolvePlaywrightMode(task, config = {}) {
|
|
8
|
+
const { preferCli = true } = config;
|
|
9
|
+
if (!preferCli) return "mcp";
|
|
10
|
+
const detection = detectPlaywrightCli({ useCache: true });
|
|
11
|
+
if (!detection.installed) return "mcp";
|
|
12
|
+
return MCP_PREFERRED_TASKS.has(task) ? "mcp" : "cli";
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
resolvePlaywrightMode
|
|
16
|
+
};
|