ado-sync 0.1.47 → 0.1.48
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/.next-steps.md +179 -0
- package/dist/azure/work-items.d.ts +28 -0
- package/dist/azure/work-items.js +96 -0
- package/dist/azure/work-items.js.map +1 -1
- package/dist/cli.js +124 -7
- package/dist/cli.js.map +1 -1
- package/dist/issues/ado-bugs.d.ts +23 -0
- package/dist/issues/ado-bugs.js +59 -0
- package/dist/issues/ado-bugs.js.map +1 -0
- package/dist/issues/create-issues.d.ts +32 -0
- package/dist/issues/create-issues.js +236 -0
- package/dist/issues/create-issues.js.map +1 -0
- package/dist/issues/github.d.ts +22 -0
- package/dist/issues/github.js +95 -0
- package/dist/issues/github.js.map +1 -0
- package/dist/mcp-server.d.ts +3 -0
- package/dist/mcp-server.js +137 -2
- package/dist/mcp-server.js.map +1 -1
- package/dist/sync/manifest.d.ts +69 -0
- package/dist/sync/manifest.js +197 -0
- package/dist/sync/manifest.js.map +1 -0
- package/dist/sync/publish-results.d.ts +8 -1
- package/dist/sync/publish-results.js +139 -2
- package/dist/sync/publish-results.js.map +1 -1
- package/dist/types.d.ts +46 -0
- package/docs/mcp-server.md +131 -29
- package/docs/publish-test-results.md +136 -2
- package/llms.txt +4 -3
- package/package.json +1 -1
- package/.cucumber/.ado-sync-state.json +0 -282
- package/.cucumber/ado-sync.yaml +0 -21
- package/.cucumber/features/cart.feature +0 -62
- package/.cucumber/features/checkout.feature +0 -100
- package/.cucumber/features/homepage.feature +0 -7
- package/.cucumber/features/inventory.feature +0 -59
- package/.cucumber/features/login.feature +0 -74
package/.next-steps.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
What I'd build next (priority order)
|
|
2
|
+
|
|
3
|
+
1. create_issue_on_failure — GitHub Issues + ADO Bugs (dual provider) ✅ DONE
|
|
4
|
+
|
|
5
|
+
When publish-test-results finds failures, automatically file issues so agents can pick them up
|
|
6
|
+
and propose fix PRs. Supports both GitHub Issues and ADO Bugs as providers under one config key.
|
|
7
|
+
|
|
8
|
+
--- Why GitHub Issues is the better default ---
|
|
9
|
+
|
|
10
|
+
GitHub Issues is preferred over ADO Bugs for most teams because:
|
|
11
|
+
- 5,000 req/hour API limit vs ADO's stricter rate limits
|
|
12
|
+
- The healer agent lives in GitHub Actions — Issue → PR → auto-close on merge is native
|
|
13
|
+
- Only requires repo `issues: write` permission, not ADO Work Items (Write)
|
|
14
|
+
- Dedup by label search is simpler than WiQL queries
|
|
15
|
+
- Issue body can embed the ADO TC URL, so full traceability is preserved
|
|
16
|
+
|
|
17
|
+
ADO Bugs remain available for teams fully inside ADO (no GitHub, or policy requires bugs in ADO).
|
|
18
|
+
|
|
19
|
+
--- The bulk-failure problem ---
|
|
20
|
+
|
|
21
|
+
A full environment outage (app down, auth broken) can fail 10,000 tests and would create 10,000
|
|
22
|
+
issues, hitting API limits and flooding the tracker. Guards are applied in order before any issue
|
|
23
|
+
is filed:
|
|
24
|
+
|
|
25
|
+
1. Failure rate threshold (default 20%)
|
|
26
|
+
If >20% of tests fail → skip per-test issues entirely.
|
|
27
|
+
Create ONE environment-failure issue with a summary of all failures.
|
|
28
|
+
|
|
29
|
+
2. Error signature clustering
|
|
30
|
+
Group failures by hashed error message.
|
|
31
|
+
If 500 tests all fail with ERR_CONNECTION_REFUSED → ONE cluster issue listing affected tests
|
|
32
|
+
(up to 20 names, then "+ N more").
|
|
33
|
+
|
|
34
|
+
3. Hard cap (default 50)
|
|
35
|
+
Ceiling regardless of strategy. After the cap, one "overflow" summary issue is created:
|
|
36
|
+
"50 issues filed; X additional failures suppressed — possible environment issue."
|
|
37
|
+
|
|
38
|
+
4. Dedup against open issues
|
|
39
|
+
Skip creation if an open issue already exists for that TC (matched by label tc:ID or title).
|
|
40
|
+
|
|
41
|
+
Decision tree at runtime:
|
|
42
|
+
|
|
43
|
+
failures > threshold% of total?
|
|
44
|
+
└─ YES → 1 environment issue, stop
|
|
45
|
+
└─ NO
|
|
46
|
+
└─ cluster by error signature
|
|
47
|
+
└─ cluster size > 10?
|
|
48
|
+
└─ YES → 1 issue per cluster (lists affected TCs)
|
|
49
|
+
└─ NO → 1 issue per TC (up to maxIssues cap)
|
|
50
|
+
└─ cap hit? → 1 overflow summary issue
|
|
51
|
+
|
|
52
|
+
--- CLI ---
|
|
53
|
+
|
|
54
|
+
ado-sync publish-test-results \
|
|
55
|
+
--testResult results/ctrf.json \
|
|
56
|
+
--create-issues-on-failure \
|
|
57
|
+
--github-repo owner/repo \
|
|
58
|
+
--github-token $GITHUB_TOKEN
|
|
59
|
+
|
|
60
|
+
# ADO Bugs instead:
|
|
61
|
+
ado-sync publish-test-results \
|
|
62
|
+
--testResult results/ctrf.json \
|
|
63
|
+
--create-issues-on-failure \
|
|
64
|
+
--issue-provider ado
|
|
65
|
+
|
|
66
|
+
--- Config (ado-sync.json) ---
|
|
67
|
+
|
|
68
|
+
{
|
|
69
|
+
"publishTestResults": {
|
|
70
|
+
"createIssuesOnFailure": {
|
|
71
|
+
"provider": "github", // "github" | "ado"
|
|
72
|
+
"repo": "owner/repo", // GitHub only
|
|
73
|
+
"labels": ["test-failure", "automated"],
|
|
74
|
+
"threshold": 20, // % of failures that triggers env-failure mode
|
|
75
|
+
"maxIssues": 50, // hard cap per run
|
|
76
|
+
"clusterByError": true, // group same-error failures into one issue
|
|
77
|
+
"dedupByTestCase": true, // skip if open issue for TC already exists
|
|
78
|
+
"areaPath": "MyProject\\QA", // ADO only
|
|
79
|
+
"assignTo": "$AZURE_DEVOPS_USER"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
--- MCP tools ---
|
|
85
|
+
|
|
86
|
+
create_issue({ title, failureDetails, testCaseId, buildId, provider })
|
|
87
|
+
→ files one issue (GitHub or ADO) and returns the issue/bug URL
|
|
88
|
+
|
|
89
|
+
create_issues_from_run({ runId, provider, ...guardOptions })
|
|
90
|
+
→ applies full guard logic against a completed test run, returns summary
|
|
91
|
+
|
|
92
|
+
--- GitHub Issue body template ---
|
|
93
|
+
|
|
94
|
+
## Test failure: {testName}
|
|
95
|
+
|
|
96
|
+
| Field | Value |
|
|
97
|
+
|---|---|
|
|
98
|
+
| ADO Test Case | [#{testCaseId}]({adoTcUrl}) |
|
|
99
|
+
| Build | #{buildId} |
|
|
100
|
+
| Run | [View in Azure DevOps]({runUrl}) |
|
|
101
|
+
| File | {filePath}:{line} |
|
|
102
|
+
|
|
103
|
+
### Error
|
|
104
|
+
```
|
|
105
|
+
{errorMessage}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Stack trace
|
|
109
|
+
```
|
|
110
|
+
{stackTrace}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
/cc @healer-agent
|
|
114
|
+
|
|
115
|
+
This unblocks Use Case 4 — the GitHub Agentic Workflow where the Playwright Healer Agent reads
|
|
116
|
+
the issue, understands the failure from errorMessage + stackTrace + ADO TC context, then proposes
|
|
117
|
+
a fix PR. The issue is the handoff between CI and the healer.
|
|
118
|
+
|
|
119
|
+
2. CTRF format support in publish-test-results ✅ DONE
|
|
120
|
+
The scope doc and materia article both use CTRF. It's becoming the standard cross-framework report format. Teams using the materia pattern will arrive with CTRF output expecting ado-sync to accept it.
|
|
121
|
+
Auto-detected from results.tests array structure. TC IDs from tags[] or test name. Attachments,
|
|
122
|
+
stdout/stderr uploaded automatically. Documented in docs/publish-test-results.md.
|
|
123
|
+
|
|
124
|
+
3. get_story_context MCP tool (Planner agent feed) ✅ DONE
|
|
125
|
+
Returns AC items (bullet list), inferred tags (@smoke, @auth…), extracted actors, and linked TC IDs.
|
|
126
|
+
CLI: ado-sync story-context --story-id 1234
|
|
127
|
+
MCP: get_story_context({ storyId: 1234 })
|
|
128
|
+
Implemented in src/azure/work-items.ts — getStoryContext() with tag inference + actor extraction.
|
|
129
|
+
|
|
130
|
+
4. ai-workflow-manifest.json generator ✅ DONE
|
|
131
|
+
CLI: ado-sync generate --manifest --story-ids 1234
|
|
132
|
+
MCP: generate_manifest({ storyIds: [1234], outputFolder: "e2e/bdd", format: "gherkin" })
|
|
133
|
+
Writes .ai-workflow-manifest-{id}.json with 8-step workflow, AC items, required docs checklist,
|
|
134
|
+
validation steps, and output paths. Implemented in src/sync/manifest.ts.
|
|
135
|
+
|
|
136
|
+
5. GitHub Actions reusable workflow
|
|
137
|
+
The scope doc explicitly asks for this:
|
|
138
|
+
|
|
139
|
+
"There is need to write GitHub Actions templates in TR Org Workflows repo"
|
|
140
|
+
|
|
141
|
+
A reusable workflow file:
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# .github/workflows/ado-sync.yml (reusable)
|
|
145
|
+
on:
|
|
146
|
+
workflow_call:
|
|
147
|
+
inputs:
|
|
148
|
+
test-result-path: ...
|
|
149
|
+
create-issues-on-failure: ...
|
|
150
|
+
Teams subscribe to it — one-line adoption in their own pipelines.
|
|
151
|
+
|
|
152
|
+
6. Flaky test detection in publish-test-results
|
|
153
|
+
Materia's 4-method flaky detection:
|
|
154
|
+
|
|
155
|
+
Summary-level flaky count
|
|
156
|
+
Retry pattern analysis
|
|
157
|
+
Explicit marking
|
|
158
|
+
Passed-after-retry
|
|
159
|
+
Add a --detect-flaky flag that outputs a flaky test report alongside the ADO publish. Tag flaky TCs in ADO with ado-sync:flaky so you can track trends across runs.
|
|
160
|
+
|
|
161
|
+
The full picture
|
|
162
|
+
|
|
163
|
+
User Story (ADO)
|
|
164
|
+
↓ get_story_context (MCP)
|
|
165
|
+
Planner Agent → Markdown spec
|
|
166
|
+
↓ generate --manifest
|
|
167
|
+
.ai-workflow-manifest.json + spec file
|
|
168
|
+
↓ generate (existing)
|
|
169
|
+
Playwright test skeleton
|
|
170
|
+
↓ push (existing)
|
|
171
|
+
ADO Test Case with @tc:ID writeback
|
|
172
|
+
↓ CI runs tests
|
|
173
|
+
CTRF / Playwright JSON results
|
|
174
|
+
↓ publish-test-results (existing + flaky detection)
|
|
175
|
+
ADO Test Run results
|
|
176
|
+
↓ failures → create_issue_on_failure (new, GitHub Issues or ADO Bugs)
|
|
177
|
+
GitHub Issue / ADO Bug → Healer Agent → fix PR
|
|
178
|
+
ado-sync becomes the connective tissue across all 4 use cases in that scope document. Right now you cover the right half. Items 1–3 above close the loop on Use Cases 3 and 4.
|
|
179
|
+
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Fetch Azure DevOps User Stories (or any work item type) with their
|
|
3
3
|
* Acceptance Criteria and Description fields, for use by the `generate` command.
|
|
4
|
+
*
|
|
5
|
+
* Also exposes getStoryContext() — a richer, planner-agent-optimised view that
|
|
6
|
+
* adds related Test Case IDs, bullet-formatted AC items, suggested tags, and
|
|
7
|
+
* extracted actors (user roles) parsed from the Acceptance Criteria text.
|
|
4
8
|
*/
|
|
5
9
|
import { AzureClient } from './client';
|
|
6
10
|
export interface AdoStory {
|
|
@@ -26,6 +30,30 @@ export declare function getWorkItemsByIds(client: AzureClient, _project: string,
|
|
|
26
30
|
* "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @project AND [System.WorkItemType] = 'User Story'"
|
|
27
31
|
*/
|
|
28
32
|
export declare function getWorkItemsByQuery(client: AzureClient, project: string, wiql: string): Promise<AdoStory[]>;
|
|
33
|
+
export interface StoryContext {
|
|
34
|
+
storyId: number;
|
|
35
|
+
title: string;
|
|
36
|
+
description?: string;
|
|
37
|
+
/** Raw AC text — HTML stripped */
|
|
38
|
+
acceptanceCriteria?: string;
|
|
39
|
+
/** AC split into individual bullet items, stripped of list markers */
|
|
40
|
+
acItems: string[];
|
|
41
|
+
state?: string;
|
|
42
|
+
adoTags?: string[];
|
|
43
|
+
/** Inferred test tags from AC/title keywords: @smoke, @regression, @auth, etc. */
|
|
44
|
+
suggestedTags: string[];
|
|
45
|
+
/** User roles / actors extracted from AC text (e.g. "admin", "guest user") */
|
|
46
|
+
suggestedActors: string[];
|
|
47
|
+
/** ADO Test Case IDs already linked to this story via TestedBy relation */
|
|
48
|
+
relatedTestCases: number[];
|
|
49
|
+
/** Direct URL to the work item in Azure DevOps */
|
|
50
|
+
url: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Return a planner-agent-optimised view of a single User Story.
|
|
54
|
+
* Includes related TC IDs (already written), suggested tags, and extracted actors.
|
|
55
|
+
*/
|
|
56
|
+
export declare function getStoryContext(client: AzureClient, project: string, storyId: number, orgUrl: string): Promise<StoryContext>;
|
|
29
57
|
/**
|
|
30
58
|
* Fetch User Stories under a specific area path.
|
|
31
59
|
*/
|
package/dist/azure/work-items.js
CHANGED
|
@@ -2,10 +2,15 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Fetch Azure DevOps User Stories (or any work item type) with their
|
|
4
4
|
* Acceptance Criteria and Description fields, for use by the `generate` command.
|
|
5
|
+
*
|
|
6
|
+
* Also exposes getStoryContext() — a richer, planner-agent-optimised view that
|
|
7
|
+
* adds related Test Case IDs, bullet-formatted AC items, suggested tags, and
|
|
8
|
+
* extracted actors (user roles) parsed from the Acceptance Criteria text.
|
|
5
9
|
*/
|
|
6
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
11
|
exports.getWorkItemsByIds = getWorkItemsByIds;
|
|
8
12
|
exports.getWorkItemsByQuery = getWorkItemsByQuery;
|
|
13
|
+
exports.getStoryContext = getStoryContext;
|
|
9
14
|
exports.getWorkItemsByAreaPath = getWorkItemsByAreaPath;
|
|
10
15
|
const FIELDS = [
|
|
11
16
|
'System.Id',
|
|
@@ -84,6 +89,97 @@ async function getWorkItemsByQuery(client, project, wiql) {
|
|
|
84
89
|
const ids = (queryResult.workItems ?? []).map((ref) => ref.id).filter(Boolean);
|
|
85
90
|
return getWorkItemsByIds(client, project, ids);
|
|
86
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Return a planner-agent-optimised view of a single User Story.
|
|
94
|
+
* Includes related TC IDs (already written), suggested tags, and extracted actors.
|
|
95
|
+
*/
|
|
96
|
+
async function getStoryContext(client, project, storyId, orgUrl) {
|
|
97
|
+
const witApi = await client.getWitApi();
|
|
98
|
+
// Fetch with relations expanded (4 = WorkItemExpand.Relations)
|
|
99
|
+
const wi = await witApi.getWorkItem(storyId, undefined, undefined, 4);
|
|
100
|
+
const f = wi?.fields ?? {};
|
|
101
|
+
const rawTitle = f['System.Title'] ?? `Work Item ${storyId}`;
|
|
102
|
+
const rawDesc = f['System.Description'];
|
|
103
|
+
const rawAc = f['Microsoft.VSTS.Common.AcceptanceCriteria'];
|
|
104
|
+
const rawTags = f['System.Tags'] ?? '';
|
|
105
|
+
const description = stripHtml(rawDesc);
|
|
106
|
+
const acceptanceCriteria = stripHtml(rawAc);
|
|
107
|
+
// AC bullet items — split on newlines, strip list markers, drop blank lines
|
|
108
|
+
const acItems = (acceptanceCriteria ?? '')
|
|
109
|
+
.split('\n')
|
|
110
|
+
.map((l) => l.replace(/^[-*•\d.)\s]+/, '').trim())
|
|
111
|
+
.filter((l) => l.length > 0);
|
|
112
|
+
const adoTags = rawTags
|
|
113
|
+
? rawTags.split(';').map((t) => t.trim()).filter(Boolean)
|
|
114
|
+
: [];
|
|
115
|
+
// Extract related TC IDs from relations
|
|
116
|
+
const relations = wi?.relations ?? [];
|
|
117
|
+
const relatedTestCases = relations
|
|
118
|
+
.filter((r) => typeof r.rel === 'string' && r.rel.includes('TestedBy'))
|
|
119
|
+
.map((r) => {
|
|
120
|
+
const match = (r.url ?? '').match(/\/(\d+)$/);
|
|
121
|
+
return match ? parseInt(match[1], 10) : NaN;
|
|
122
|
+
})
|
|
123
|
+
.filter((id) => !isNaN(id));
|
|
124
|
+
const searchText = [rawTitle, acceptanceCriteria ?? '', description ?? ''].join(' ').toLowerCase();
|
|
125
|
+
const suggestedTags = inferTags(searchText);
|
|
126
|
+
const suggestedActors = extractActors(acceptanceCriteria ?? '');
|
|
127
|
+
const url = `${orgUrl.replace(/\/$/, '')}/${project}/_workitems/edit/${storyId}`;
|
|
128
|
+
return {
|
|
129
|
+
storyId,
|
|
130
|
+
title: rawTitle,
|
|
131
|
+
description,
|
|
132
|
+
acceptanceCriteria,
|
|
133
|
+
acItems,
|
|
134
|
+
state: f['System.State'],
|
|
135
|
+
adoTags: adoTags.length ? adoTags : undefined,
|
|
136
|
+
suggestedTags,
|
|
137
|
+
suggestedActors,
|
|
138
|
+
relatedTestCases,
|
|
139
|
+
url,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
// ─── Tag inference ────────────────────────────────────────────────────────────
|
|
143
|
+
const TAG_RULES = [
|
|
144
|
+
{ patterns: /\bsmoke\b/, tag: '@smoke' },
|
|
145
|
+
{ patterns: /\bregression\b/, tag: '@regression' },
|
|
146
|
+
{ patterns: /\b(login|sign[- ]?in|sign[- ]?out|log[- ]?out|auth(entication|oriz)?)\b/, tag: '@auth' },
|
|
147
|
+
{ patterns: /\b(payment|checkout|billing|invoice|refund)\b/, tag: '@payment' },
|
|
148
|
+
{ patterns: /\b(performance|load test|speed|latency)\b/, tag: '@performance' },
|
|
149
|
+
{ patterns: /\bmobile\b/, tag: '@mobile' },
|
|
150
|
+
{ patterns: /\b(api|endpoint|rest|graphql|webhook)\b/, tag: '@api' },
|
|
151
|
+
{ patterns: /\b(security|permission|role|access control)\b/, tag: '@security' },
|
|
152
|
+
{ patterns: /\b(search|filter|sort)\b/, tag: '@search' },
|
|
153
|
+
{ patterns: /\b(upload|download|file|attachment)\b/, tag: '@files' },
|
|
154
|
+
{ patterns: /\b(notification|email|sms|alert)\b/, tag: '@notifications' },
|
|
155
|
+
{ patterns: /\b(report|export|csv|excel|pdf)\b/, tag: '@reporting' },
|
|
156
|
+
{ patterns: /\bcritical\b/, tag: '@critical' },
|
|
157
|
+
{ patterns: /\bwip\b/, tag: '@wip' },
|
|
158
|
+
];
|
|
159
|
+
function inferTags(text) {
|
|
160
|
+
return TAG_RULES
|
|
161
|
+
.filter((r) => r.patterns.test(text))
|
|
162
|
+
.map((r) => r.tag);
|
|
163
|
+
}
|
|
164
|
+
// ─── Actor extraction ─────────────────────────────────────────────────────────
|
|
165
|
+
function extractActors(ac) {
|
|
166
|
+
const found = new Set();
|
|
167
|
+
// "As a <actor>" / "As an <actor>"
|
|
168
|
+
for (const m of ac.matchAll(/\bas an?\s+([a-z][a-z\s]{1,25}?)(?:,|\.|I |can |should |must )/gi)) {
|
|
169
|
+
const actor = m[1].trim().replace(/\s+/g, ' ');
|
|
170
|
+
if (actor)
|
|
171
|
+
found.add(actor);
|
|
172
|
+
}
|
|
173
|
+
// "the <actor> can/should/must"
|
|
174
|
+
for (const m of ac.matchAll(/\bthe\s+([a-z][a-z\s]{1,20}?)\s+(?:can|should|must|is able to)\b/gi)) {
|
|
175
|
+
const actor = m[1].trim().replace(/\s+/g, ' ');
|
|
176
|
+
if (actor && actor.split(' ').length <= 3)
|
|
177
|
+
found.add(actor);
|
|
178
|
+
}
|
|
179
|
+
// Deduplicate substrings (e.g. "admin" vs "admin user" — keep longer)
|
|
180
|
+
const actors = [...found];
|
|
181
|
+
return actors.filter((a) => !actors.some((b) => b !== a && b.includes(a)));
|
|
182
|
+
}
|
|
87
183
|
/**
|
|
88
184
|
* Fetch User Stories under a specific area path.
|
|
89
185
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-items.js","sourceRoot":"","sources":["../../src/azure/work-items.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"work-items.js","sourceRoot":"","sources":["../../src/azure/work-items.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -58,6 +58,7 @@ const client_1 = require("./azure/client");
|
|
|
58
58
|
const config_1 = require("./config");
|
|
59
59
|
const engine_1 = require("./sync/engine");
|
|
60
60
|
const generate_1 = require("./sync/generate");
|
|
61
|
+
const manifest_1 = require("./sync/manifest");
|
|
61
62
|
const publish_results_1 = require("./sync/publish-results");
|
|
62
63
|
// ─── CLI definition ───────────────────────────────────────────────────────────
|
|
63
64
|
const program = new commander_1.Command();
|
|
@@ -337,13 +338,19 @@ program
|
|
|
337
338
|
// ─── publish-test-results ─────────────────────────────────────────────────────
|
|
338
339
|
program
|
|
339
340
|
.command('publish-test-results')
|
|
340
|
-
.description('Publish test results from result files (TRX, JUnit, Cucumber JSON) to Azure DevOps')
|
|
341
|
+
.description('Publish test results from result files (TRX, JUnit, Cucumber JSON, CTRF) to Azure DevOps')
|
|
341
342
|
.option('--testResult <path>', 'Path to a test result file (repeatable)', collect, [])
|
|
342
|
-
.option('--testResultFormat <format>', 'Result file format: trx, nunitXml, junit, cucumberJson, playwrightJson')
|
|
343
|
+
.option('--testResultFormat <format>', 'Result file format: trx, nunitXml, junit, cucumberJson, playwrightJson, ctrfJson')
|
|
343
344
|
.option('--attachmentsFolder <path>', 'Folder with screenshots/videos/logs to attach to test results')
|
|
344
345
|
.option('--runName <name>', 'Name for the test run in Azure DevOps')
|
|
345
346
|
.option('--buildId <id>', 'Build ID to associate with the test run')
|
|
346
347
|
.option('--dry-run', 'Parse results and show summary without publishing')
|
|
348
|
+
.option('--create-issues-on-failure', 'File GitHub Issues or ADO Bugs for each failed test')
|
|
349
|
+
.option('--issue-provider <provider>', 'Issue provider: github (default) or ado')
|
|
350
|
+
.option('--github-repo <owner/repo>', 'GitHub repository to file issues in (e.g. "myorg/myrepo")')
|
|
351
|
+
.option('--github-token <token>', 'GitHub token ($ENV_VAR reference supported)')
|
|
352
|
+
.option('--bug-threshold <percent>', 'Failure % above which one env-failure issue is filed instead of per-test (default: 20)')
|
|
353
|
+
.option('--max-issues <n>', 'Hard cap on issues filed per run (default: 50)')
|
|
347
354
|
.option('--config-override <path=value>', 'Override a config value (repeatable)', collect, [])
|
|
348
355
|
.action(async (opts) => {
|
|
349
356
|
const globalOpts = program.opts();
|
|
@@ -365,6 +372,14 @@ program
|
|
|
365
372
|
attachmentsFolder: opts.attachmentsFolder,
|
|
366
373
|
runName: opts.runName,
|
|
367
374
|
buildId: opts.buildId ? parseInt(opts.buildId) : undefined,
|
|
375
|
+
createIssuesOnFailure: opts.createIssuesOnFailure,
|
|
376
|
+
issueOverrides: {
|
|
377
|
+
...(opts.issueProvider && { provider: opts.issueProvider }),
|
|
378
|
+
...(opts.githubRepo && { repo: opts.githubRepo }),
|
|
379
|
+
...(opts.githubToken && { token: opts.githubToken }),
|
|
380
|
+
...(opts.bugThreshold && { threshold: parseInt(opts.bugThreshold) }),
|
|
381
|
+
...(opts.maxIssues && { maxIssues: parseInt(opts.maxIssues) }),
|
|
382
|
+
},
|
|
368
383
|
});
|
|
369
384
|
console.log(chalk_1.default.green(`Total results: ${result.totalResults}`));
|
|
370
385
|
console.log(` ${chalk_1.default.green(`${result.passed} passed`)} ${chalk_1.default.red(`${result.failed} failed`)} ${chalk_1.default.dim(`${result.other} other`)}`);
|
|
@@ -372,6 +387,21 @@ program
|
|
|
372
387
|
console.log(chalk_1.default.dim(`Run ID: ${result.runId}`));
|
|
373
388
|
console.log(chalk_1.default.dim(`URL: ${result.runUrl}`));
|
|
374
389
|
}
|
|
390
|
+
if (result.issuesSummary) {
|
|
391
|
+
const s = result.issuesSummary;
|
|
392
|
+
console.log('');
|
|
393
|
+
console.log(chalk_1.default.bold('Issues filed:'));
|
|
394
|
+
console.log(chalk_1.default.dim(` Mode: ${s.mode} | Total failed: ${s.totalFailed} | Suppressed: ${s.suppressed}`));
|
|
395
|
+
for (const issue of s.issued) {
|
|
396
|
+
const prefix = issue.action === 'created' ? chalk_1.default.green('+')
|
|
397
|
+
: issue.action === 'skipped' ? chalk_1.default.dim('=')
|
|
398
|
+
: chalk_1.default.yellow('~');
|
|
399
|
+
const detail = issue.url ? chalk_1.default.dim(` → ${issue.url}`)
|
|
400
|
+
: issue.reason ? chalk_1.default.dim(` (${issue.reason})`)
|
|
401
|
+
: '';
|
|
402
|
+
console.log(` ${prefix} ${issue.title}${detail}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
375
405
|
}
|
|
376
406
|
catch (err) {
|
|
377
407
|
handleError(err);
|
|
@@ -609,12 +639,13 @@ program
|
|
|
609
639
|
// ─── generate ────────────────────────────────────────────────────────────────
|
|
610
640
|
program
|
|
611
641
|
.command('generate')
|
|
612
|
-
.description('Generate local spec files from Azure DevOps User Stories')
|
|
642
|
+
.description('Generate local spec files (or AI workflow manifests) from Azure DevOps User Stories')
|
|
613
643
|
.option('--story-ids <ids>', 'Comma-separated ADO work item IDs (e.g. 1234,5678)')
|
|
614
644
|
.option('--query <wiql>', 'WIQL query string to select stories')
|
|
615
645
|
.option('--area-path <path>', 'Filter by area path')
|
|
616
646
|
.option('--format <fmt>', 'Output format: gherkin or markdown (default: markdown)')
|
|
617
647
|
.option('--output-folder <dir>', 'Where to write files (default: config pull.targetFolder or .)')
|
|
648
|
+
.option('--manifest', 'Generate .ai-workflow-manifest-{id}.json instead of spec files')
|
|
618
649
|
.option('--force', 'Overwrite existing files')
|
|
619
650
|
.option('--dry-run', 'Show what would be created without writing files')
|
|
620
651
|
.option('--config-override <path=value>', 'Override a config value (repeatable)', collect, [])
|
|
@@ -626,15 +657,55 @@ program
|
|
|
626
657
|
if (opts.configOverride?.length)
|
|
627
658
|
(0, config_1.applyOverrides)(config, opts.configOverride);
|
|
628
659
|
const configDir = path.dirname(configPath);
|
|
660
|
+
const outputFormat = globalOpts.output;
|
|
661
|
+
const storyIds = opts.storyIds
|
|
662
|
+
? opts.storyIds.split(',').map((s) => parseInt(s.trim(), 10)).filter(Boolean)
|
|
663
|
+
: undefined;
|
|
664
|
+
if (opts.manifest) {
|
|
665
|
+
// ── Manifest mode ──────────────────────────────────────────────────────
|
|
666
|
+
if (!storyIds?.length) {
|
|
667
|
+
console.error(chalk_1.default.red('--story-ids is required with --manifest'));
|
|
668
|
+
process.exit(1);
|
|
669
|
+
}
|
|
670
|
+
console.log(chalk_1.default.bold('ado-sync generate --manifest'));
|
|
671
|
+
console.log(chalk_1.default.dim(`Config: ${configPath}`));
|
|
672
|
+
if (opts.dryRun)
|
|
673
|
+
console.log(chalk_1.default.yellow('Dry run — no files will be written'));
|
|
674
|
+
console.log('');
|
|
675
|
+
const results = await (0, manifest_1.generateManifests)(config, configDir, {
|
|
676
|
+
storyIds,
|
|
677
|
+
outputFolder: opts.outputFolder,
|
|
678
|
+
format: opts.format,
|
|
679
|
+
force: opts.force ?? false,
|
|
680
|
+
dryRun: opts.dryRun ?? false,
|
|
681
|
+
});
|
|
682
|
+
if (outputFormat === 'json') {
|
|
683
|
+
process.stdout.write(JSON.stringify(results, null, 2) + '\n');
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
for (const r of results) {
|
|
687
|
+
const symbol = r.action === 'created' ? chalk_1.default.green('+') : chalk_1.default.dim('=');
|
|
688
|
+
const relPath = path.relative(process.cwd(), r.filePath);
|
|
689
|
+
console.log(`${symbol} [#${r.storyId}] ${relPath} — ${r.title}`);
|
|
690
|
+
if (r.manifest && r.action === 'created') {
|
|
691
|
+
console.log(chalk_1.default.dim(` ${r.manifest.context.acceptanceCriteria.length} AC items · ${r.manifest.context.suggestedTags.join(' ')} · ${r.manifest.context.relatedTestCases.length} linked TCs`));
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
console.log('');
|
|
695
|
+
const created = results.filter((r) => r.action === 'created').length;
|
|
696
|
+
const skipped = results.filter((r) => r.action === 'skipped').length;
|
|
697
|
+
console.log([
|
|
698
|
+
created && chalk_1.default.green(`${created} manifest${created !== 1 ? 's' : ''} created`),
|
|
699
|
+
skipped && chalk_1.default.dim(`${skipped} skipped`),
|
|
700
|
+
].filter(Boolean).join(chalk_1.default.dim(' ')) || chalk_1.default.dim('Nothing to generate.'));
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
// ── Spec file mode ─────────────────────────────────────────────────────
|
|
629
704
|
console.log(chalk_1.default.bold('ado-sync generate'));
|
|
630
705
|
console.log(chalk_1.default.dim(`Config: ${configPath}`));
|
|
631
706
|
if (opts.dryRun)
|
|
632
707
|
console.log(chalk_1.default.yellow('Dry run — no files will be written'));
|
|
633
708
|
console.log('');
|
|
634
|
-
const storyIds = opts.storyIds
|
|
635
|
-
? opts.storyIds.split(',').map((s) => parseInt(s.trim(), 10)).filter(Boolean)
|
|
636
|
-
: undefined;
|
|
637
|
-
const outputFormat = program.opts().output;
|
|
638
709
|
const results = await (0, generate_1.generateSpecs)(config, configDir, {
|
|
639
710
|
storyIds,
|
|
640
711
|
query: opts.query,
|
|
@@ -665,6 +736,52 @@ program
|
|
|
665
736
|
handleError(err);
|
|
666
737
|
}
|
|
667
738
|
});
|
|
739
|
+
// ─── story-context ────────────────────────────────────────────────────────────
|
|
740
|
+
program
|
|
741
|
+
.command('story-context')
|
|
742
|
+
.description('Show planner-agent context for an ADO User Story (AC items, suggested tags, linked TCs)')
|
|
743
|
+
.requiredOption('--story-id <id>', 'ADO work item ID')
|
|
744
|
+
.option('--config-override <path=value>', 'Override a config value (repeatable)', collect, [])
|
|
745
|
+
.action(async (opts) => {
|
|
746
|
+
const globalOpts = program.opts();
|
|
747
|
+
try {
|
|
748
|
+
const configPath = (0, config_1.resolveConfigPath)(globalOpts.config);
|
|
749
|
+
const config = (0, config_1.loadConfig)(configPath);
|
|
750
|
+
if (opts.configOverride?.length)
|
|
751
|
+
(0, config_1.applyOverrides)(config, opts.configOverride);
|
|
752
|
+
const { AzureClient } = await Promise.resolve().then(() => __importStar(require('./azure/client')));
|
|
753
|
+
const { getStoryContext } = await Promise.resolve().then(() => __importStar(require('./azure/work-items')));
|
|
754
|
+
const client = await AzureClient.create(config);
|
|
755
|
+
const ctx = await getStoryContext(client, config.project, parseInt(opts.storyId, 10), config.orgUrl);
|
|
756
|
+
if (globalOpts.output === 'json') {
|
|
757
|
+
process.stdout.write(JSON.stringify(ctx, null, 2) + '\n');
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
console.log(chalk_1.default.bold(`Story #${ctx.storyId}: ${ctx.title}`));
|
|
761
|
+
console.log(chalk_1.default.dim(`State: ${ctx.state ?? 'unknown'} | ${ctx.url}`));
|
|
762
|
+
console.log('');
|
|
763
|
+
if (ctx.acItems.length) {
|
|
764
|
+
console.log(chalk_1.default.bold('Acceptance Criteria:'));
|
|
765
|
+
ctx.acItems.forEach((item, i) => console.log(` ${chalk_1.default.dim(`${i + 1}.`)} ${item}`));
|
|
766
|
+
console.log('');
|
|
767
|
+
}
|
|
768
|
+
if (ctx.suggestedTags.length) {
|
|
769
|
+
console.log(`${chalk_1.default.bold('Suggested tags:')} ${ctx.suggestedTags.join(' ')}`);
|
|
770
|
+
}
|
|
771
|
+
if (ctx.suggestedActors.length) {
|
|
772
|
+
console.log(`${chalk_1.default.bold('Actors:')} ${ctx.suggestedActors.join(', ')}`);
|
|
773
|
+
}
|
|
774
|
+
if (ctx.relatedTestCases.length) {
|
|
775
|
+
console.log(`${chalk_1.default.bold('Linked TCs:')} ${ctx.relatedTestCases.map((id) => `#${id}`).join(', ')}`);
|
|
776
|
+
}
|
|
777
|
+
else {
|
|
778
|
+
console.log(chalk_1.default.dim('No linked test cases yet.'));
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
catch (err) {
|
|
782
|
+
handleError(err);
|
|
783
|
+
}
|
|
784
|
+
});
|
|
668
785
|
// ─── Run ─────────────────────────────────────────────────────────────────────
|
|
669
786
|
program.parse(process.argv);
|
|
670
787
|
//# sourceMappingURL=cli.js.map
|