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 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
  */
@@ -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":"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"}
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