specweave 1.0.447 → 1.0.448

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.
Files changed (43) hide show
  1. package/bin/specweave.js +18 -0
  2. package/dist/plugins/specweave-ado/lib/ado-client.d.ts +6 -0
  3. package/dist/plugins/specweave-ado/lib/ado-client.d.ts.map +1 -1
  4. package/dist/plugins/specweave-ado/lib/ado-client.js +22 -0
  5. package/dist/plugins/specweave-ado/lib/ado-client.js.map +1 -1
  6. package/dist/src/cli/commands/get.d.ts +17 -1
  7. package/dist/src/cli/commands/get.d.ts.map +1 -1
  8. package/dist/src/cli/commands/get.js +51 -1
  9. package/dist/src/cli/commands/get.js.map +1 -1
  10. package/dist/src/cli/commands/link-pr.d.ts +17 -0
  11. package/dist/src/cli/commands/link-pr.d.ts.map +1 -0
  12. package/dist/src/cli/commands/link-pr.js +45 -0
  13. package/dist/src/cli/commands/link-pr.js.map +1 -0
  14. package/dist/src/cli/helpers/get/bulk-get.d.ts +41 -0
  15. package/dist/src/cli/helpers/get/bulk-get.d.ts.map +1 -0
  16. package/dist/src/cli/helpers/get/bulk-get.js +88 -0
  17. package/dist/src/cli/helpers/get/bulk-get.js.map +1 -0
  18. package/dist/src/cli/helpers/init/github-repo-cloning.d.ts +50 -0
  19. package/dist/src/cli/helpers/init/github-repo-cloning.d.ts.map +1 -1
  20. package/dist/src/cli/helpers/init/github-repo-cloning.js +1 -1
  21. package/dist/src/cli/helpers/init/github-repo-cloning.js.map +1 -1
  22. package/dist/src/integrations/jira/jira-client.d.ts +12 -0
  23. package/dist/src/integrations/jira/jira-client.d.ts.map +1 -1
  24. package/dist/src/integrations/jira/jira-client.js +37 -0
  25. package/dist/src/integrations/jira/jira-client.js.map +1 -1
  26. package/dist/src/sync/format-preservation-sync.d.ts +3 -0
  27. package/dist/src/sync/format-preservation-sync.d.ts.map +1 -1
  28. package/dist/src/sync/format-preservation-sync.js +9 -0
  29. package/dist/src/sync/format-preservation-sync.js.map +1 -1
  30. package/dist/src/sync/integration-health-check.d.ts +45 -0
  31. package/dist/src/sync/integration-health-check.d.ts.map +1 -0
  32. package/dist/src/sync/integration-health-check.js +186 -0
  33. package/dist/src/sync/integration-health-check.js.map +1 -0
  34. package/dist/src/sync/pr-linker.d.ts +38 -0
  35. package/dist/src/sync/pr-linker.d.ts.map +1 -0
  36. package/dist/src/sync/pr-linker.js +157 -0
  37. package/dist/src/sync/pr-linker.js.map +1 -0
  38. package/package.json +1 -1
  39. package/plugins/specweave/commands/sync-setup.md +24 -1
  40. package/plugins/specweave/skills/get/SKILL.md +38 -6
  41. package/plugins/specweave/skills/pr/SKILL.md +39 -2
  42. package/plugins/specweave-ado/lib/ado-client.js +22 -0
  43. package/plugins/specweave-ado/lib/ado-client.ts +25 -0
@@ -0,0 +1,38 @@
1
+ /**
2
+ * PR Linker — Links pull requests to external tickets (JIRA, ADO)
3
+ *
4
+ * Called after PR creation to create remote links / hyperlinks
5
+ * so external tools show the PR in the issue's UI.
6
+ *
7
+ * @module sync/pr-linker
8
+ */
9
+ import { Logger } from '../utils/logger.js';
10
+ export interface PrLinkRequest {
11
+ projectRoot: string;
12
+ incrementId: string;
13
+ prUrl: string;
14
+ prNumber: number;
15
+ branch: string;
16
+ }
17
+ export interface PrLinkResult {
18
+ linked: {
19
+ provider: string;
20
+ key: string;
21
+ }[];
22
+ errors: {
23
+ provider: string;
24
+ key: string;
25
+ error: string;
26
+ }[];
27
+ }
28
+ /**
29
+ * Link a PR to all external tickets associated with this increment.
30
+ * Non-blocking: errors are collected, never thrown.
31
+ */
32
+ export declare function linkPrToExternalTickets(request: PrLinkRequest, logger?: Logger): Promise<PrLinkResult>;
33
+ /**
34
+ * Resolve the external ticket key for branch naming.
35
+ * Returns the JIRA key or ADO ID prefix if available.
36
+ */
37
+ export declare function resolveExternalBranchPrefix(metadata: any): string | null;
38
+ //# sourceMappingURL=pr-linker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pr-linker.d.ts","sourceRoot":"","sources":["../../../src/sync/pr-linker.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAiB,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE3D,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5C,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC5D;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAqC5G;AAwHD;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,CAUxE"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * PR Linker — Links pull requests to external tickets (JIRA, ADO)
3
+ *
4
+ * Called after PR creation to create remote links / hyperlinks
5
+ * so external tools show the PR in the issue's UI.
6
+ *
7
+ * @module sync/pr-linker
8
+ */
9
+ import { readFileSync, existsSync } from 'fs';
10
+ import { join } from 'path';
11
+ import { consoleLogger } from '../utils/logger.js';
12
+ /**
13
+ * Link a PR to all external tickets associated with this increment.
14
+ * Non-blocking: errors are collected, never thrown.
15
+ */
16
+ export async function linkPrToExternalTickets(request, logger) {
17
+ const log = logger || consoleLogger;
18
+ const result = { linked: [], errors: [] };
19
+ const configPath = join(request.projectRoot, '.specweave', 'config.json');
20
+ if (!existsSync(configPath)) {
21
+ log.warn('No .specweave/config.json found — skipping PR linking');
22
+ return result;
23
+ }
24
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
25
+ const metadataPath = join(request.projectRoot, '.specweave', 'increments', request.incrementId, 'metadata.json');
26
+ if (!existsSync(metadataPath)) {
27
+ log.warn(`No metadata.json for increment ${request.incrementId}`);
28
+ return result;
29
+ }
30
+ const metadata = JSON.parse(readFileSync(metadataPath, 'utf-8'));
31
+ // Collect JIRA issue keys from metadata
32
+ const jiraKeys = collectJiraKeys(metadata, request.projectRoot, request.incrementId);
33
+ // Collect ADO work item IDs from metadata
34
+ const adoIds = collectAdoIds(metadata, request.projectRoot, request.incrementId);
35
+ // Link to JIRA
36
+ if (jiraKeys.length > 0 && config.sync?.jira?.enabled !== false) {
37
+ await linkToJira(jiraKeys, request, config, result, log);
38
+ }
39
+ // Link to ADO
40
+ if (adoIds.length > 0 && config.sync?.ado?.enabled !== false) {
41
+ await linkToAdo(adoIds, request, config, result, log);
42
+ }
43
+ return result;
44
+ }
45
+ function collectJiraKeys(metadata, projectRoot, incrementId) {
46
+ const keys = [];
47
+ // From metadata.json direct jira reference
48
+ if (metadata.jira?.issue)
49
+ keys.push(metadata.jira.issue);
50
+ if (metadata.jira?.issueKey)
51
+ keys.push(metadata.jira.issueKey);
52
+ // From externalLinks
53
+ if (metadata.externalLinks?.jira?.epicKey)
54
+ keys.push(metadata.externalLinks.jira.epicKey);
55
+ if (metadata.externalLinks?.jira?.issueKey)
56
+ keys.push(metadata.externalLinks.jira.issueKey);
57
+ // From per-user-story links
58
+ const usLinks = metadata.externalLinks?.jira?.userStories;
59
+ if (usLinks && typeof usLinks === 'object') {
60
+ for (const us of Object.values(usLinks)) {
61
+ if (us?.issueKey)
62
+ keys.push(us.issueKey);
63
+ }
64
+ }
65
+ // Deduplicate
66
+ return [...new Set(keys)];
67
+ }
68
+ function collectAdoIds(metadata, projectRoot, incrementId) {
69
+ const ids = [];
70
+ if (metadata.externalLinks?.ado?.featureId)
71
+ ids.push(Number(metadata.externalLinks.ado.featureId));
72
+ if (metadata.externalLinks?.ado?.workItemId)
73
+ ids.push(Number(metadata.externalLinks.ado.workItemId));
74
+ const usLinks = metadata.externalLinks?.ado?.userStories;
75
+ if (usLinks && typeof usLinks === 'object') {
76
+ for (const us of Object.values(usLinks)) {
77
+ if (us?.workItemId)
78
+ ids.push(Number(us.workItemId));
79
+ }
80
+ }
81
+ return [...new Set(ids.filter(id => !isNaN(id)))];
82
+ }
83
+ async function linkToJira(keys, request, config, result, log) {
84
+ try {
85
+ const { JiraClient } = await import('../integrations/jira/jira-client.js');
86
+ const jiraClient = new JiraClient();
87
+ for (const issueKey of keys) {
88
+ try {
89
+ await jiraClient.addRemoteLink(issueKey, {
90
+ url: request.prUrl,
91
+ title: `PR #${request.prNumber}: ${request.branch}`,
92
+ globalId: `specweave-pr-${request.prNumber}`,
93
+ });
94
+ result.linked.push({ provider: 'jira', key: issueKey });
95
+ log.info(`Linked PR #${request.prNumber} to JIRA ${issueKey}`);
96
+ }
97
+ catch (err) {
98
+ result.errors.push({ provider: 'jira', key: issueKey, error: err.message });
99
+ log.warn(`Failed to link PR to JIRA ${issueKey}: ${err.message}`);
100
+ }
101
+ }
102
+ }
103
+ catch (err) {
104
+ log.warn(`JIRA client initialization failed: ${err.message}`);
105
+ for (const key of keys) {
106
+ result.errors.push({ provider: 'jira', key, error: err.message });
107
+ }
108
+ }
109
+ }
110
+ async function linkToAdo(ids, request, config, result, log) {
111
+ try {
112
+ const { AdoClient } = await import('../../plugins/specweave-ado/lib/ado-client.js');
113
+ const adoConfig = {
114
+ organization: config.sync?.ado?.organization || '',
115
+ project: config.sync?.ado?.project || '',
116
+ personalAccessToken: process.env.ADO_PAT || process.env.AZURE_DEVOPS_PAT || '',
117
+ };
118
+ if (!adoConfig.organization || !adoConfig.personalAccessToken) {
119
+ log.warn('ADO not configured — skipping PR linking');
120
+ return;
121
+ }
122
+ const adoClient = new AdoClient(adoConfig);
123
+ for (const workItemId of ids) {
124
+ try {
125
+ await adoClient.addHyperlink(workItemId, request.prUrl, `PR #${request.prNumber}: ${request.branch}`);
126
+ result.linked.push({ provider: 'ado', key: String(workItemId) });
127
+ log.info(`Linked PR #${request.prNumber} to ADO #${workItemId}`);
128
+ }
129
+ catch (err) {
130
+ result.errors.push({ provider: 'ado', key: String(workItemId), error: err.message });
131
+ log.warn(`Failed to link PR to ADO #${workItemId}: ${err.message}`);
132
+ }
133
+ }
134
+ }
135
+ catch (err) {
136
+ log.warn(`ADO client initialization failed: ${err.message}`);
137
+ for (const id of ids) {
138
+ result.errors.push({ provider: 'ado', key: String(id), error: err.message });
139
+ }
140
+ }
141
+ }
142
+ /**
143
+ * Resolve the external ticket key for branch naming.
144
+ * Returns the JIRA key or ADO ID prefix if available.
145
+ */
146
+ export function resolveExternalBranchPrefix(metadata) {
147
+ // JIRA key takes priority
148
+ const jiraKeys = collectJiraKeys(metadata, '', '');
149
+ if (jiraKeys.length > 0)
150
+ return jiraKeys[0];
151
+ // ADO work item ID
152
+ const adoIds = collectAdoIds(metadata, '', '');
153
+ if (adoIds.length > 0)
154
+ return `AB#${adoIds[0]}`;
155
+ return null;
156
+ }
157
+ //# sourceMappingURL=pr-linker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pr-linker.js","sourceRoot":"","sources":["../../../src/sync/pr-linker.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specweave",
3
- "version": "1.0.447",
3
+ "version": "1.0.448",
4
4
  "description": "100+ domain-expert AI skills — PM, Architect, Frontend, QA, Security and more. Skills learn your team's patterns permanently. Spec-first planning, autonomous execution, multi-agent teams, synced to GitHub/JIRA. Claude Code, Cursor, Copilot & more.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -119,7 +119,30 @@ Write the following:
119
119
  }
120
120
  ```
121
121
 
122
- ### Step 7: Dry Run
122
+ ### Step 7: Integration Health Check
123
+
124
+ After credentials are validated, run a deeper health check to catch misconfigurations early (e.g., wrong issue types, missing permissions, unavailable integrations):
125
+
126
+ ```typescript
127
+ // Import and run health checks for each enabled provider
128
+ import { checkJiraIntegration, checkAdoIntegration, checkGitHubIntegration, formatHealthCheckResults } from '../../src/sync/integration-health-check.js';
129
+
130
+ const results = [];
131
+ if (jiraEnabled) results.push(await checkJiraIntegration({ domain, projectKey, email, apiToken }));
132
+ if (adoEnabled) results.push(await checkAdoIntegration({ organization, project, pat }));
133
+ if (githubEnabled) results.push(await checkGitHubIntegration());
134
+
135
+ console.log(formatHealthCheckResults(results));
136
+ ```
137
+
138
+ This checks:
139
+ - **JIRA**: API auth, project access, issue type availability (catches "The issue type selected is invalid"), edit permissions for remote links
140
+ - **ADO**: API auth, work item type availability
141
+ - **GitHub**: gh CLI authentication
142
+
143
+ Warnings are advisory — they don't block setup but alert the user to fix issues before they hit them at increment closure.
144
+
145
+ ### Step 8: Dry Run
123
146
 
124
147
  Run a test sync (read-only) to verify configuration:
125
148
 
@@ -1,12 +1,13 @@
1
1
  ---
2
- version: 1.0.0
2
+ version: 1.1.0
3
3
  description: >
4
- Clone and register an existing repository into the SpecWeave workspace.
4
+ Clone and register an existing repository (or bulk-clone an entire org) into the SpecWeave workspace.
5
5
  Activate when the user wants to: add a repo, get a repo, clone a repo,
6
6
  register a repo, bring in a repo, pull in a repo, add a github repo to umbrella,
7
7
  clone and register, "get owner/repo", "add this github repo", "clone this repo
8
- into my workspace", "register this local repo". Also activate for "restore repos"
9
- or "clone all child repos" on a new machine.
8
+ into my workspace", "register this local repo". Also activate for "restore repos",
9
+ "clone all child repos" on a new machine, "clone all service-* repos", "bulk clone",
10
+ "clone entire org", "clone all repos matching pattern", "get all repos from org".
10
11
  Do NOT activate for: add a feature, add a task, add a story, add an increment,
11
12
  add a user story (those route to sw:increment).
12
13
  triggers:
@@ -20,6 +21,10 @@ triggers:
20
21
  - "pull in repo"
21
22
  - "restore repos"
22
23
  - "clone all child repos"
24
+ - "bulk clone"
25
+ - "clone entire org"
26
+ - "clone all repos"
27
+ - "clone all matching"
23
28
  negative_triggers:
24
29
  - "add a feature"
25
30
  - "add a task"
@@ -30,16 +35,21 @@ negative_triggers:
30
35
 
31
36
  # sw:get — Clone & Register Repository
32
37
 
33
- Use this skill when the user wants to add an existing repository to their SpecWeave workspace.
38
+ Use this skill when the user wants to add an existing repository (or many repositories) to their SpecWeave workspace.
34
39
 
35
40
  ## What it does
36
41
 
37
42
  Runs `specweave get <source>` which:
38
- 1. Parses the source (GitHub shorthand, full URL, SSH URL, or local path)
43
+ 1. Parses the source (GitHub shorthand, full URL, SSH URL, local path, or org glob pattern)
39
44
  2. Clones the repo into `repositories/{org}/{repo}/` (in umbrella context)
40
45
  3. Registers it in `.specweave/config.json` under `umbrella.childRepos`
41
46
  4. Runs `specweave init` inside the cloned repo
42
47
 
48
+ For **bulk mode** (glob pattern or `--all`):
49
+ - Fetches matching repos from the GitHub org via API
50
+ - Launches a background clone job (survives terminal close)
51
+ - Reports job ID; user monitors with `specweave jobs`
52
+
43
53
  ## Instructions
44
54
 
45
55
  1. Extract the repository source from the user's message:
@@ -47,16 +57,26 @@ Runs `specweave get <source>` which:
47
57
  - Full URL: `https://github.com/org/repo`
48
58
  - SSH: `git@github.com:org/repo`
49
59
  - Local path: `./path/to/repo`
60
+ - Glob pattern: `org/service-*` or `org/*`
61
+ - All repos in org: `--all org`
50
62
 
51
63
  2. Check if the user mentioned optional flags:
52
64
  - `--prefix` — US prefix (e.g., "use prefix FE")
53
65
  - `--role` — repo role (e.g., "it's a backend service")
54
66
  - `--branch` — specific branch (e.g., "clone the develop branch")
55
67
  - `--no-init` — skip specweave init
68
+ - `--all` — clone all repos in org
69
+ - `--pattern` — glob filter when used with `--all`
70
+ - `--no-archived` — skip archived repos
71
+ - `--no-forks` — skip forks
72
+ - `--limit` — max repos to fetch (default 1000)
56
73
 
57
74
  3. Run the command:
58
75
  ```bash
59
76
  specweave get <source> [--prefix X] [--role Y] [--branch Z] [--no-init]
77
+ # or bulk:
78
+ specweave get "org/service-*"
79
+ specweave get --all org [--pattern "svc-*"] [--no-archived] [--no-forks]
60
80
  ```
61
81
 
62
82
  4. Report the result to the user.
@@ -70,8 +90,20 @@ Runs `specweave get <source>` which:
70
90
  | "get this repo: git@github.com:org/repo" | `specweave get git@github.com:org/repo` |
71
91
  | "add ./my-local-service to the umbrella" | `specweave get ./my-local-service` |
72
92
  | "clone my-service with prefix MSV" | `specweave get owner/my-service --prefix MSV` |
93
+ | "clone all service-* repos from acme-corp" | `specweave get "acme-corp/service-*"` |
94
+ | "clone all repos in acme-corp org" | `specweave get --all acme-corp` |
95
+ | "clone all acme-corp repos, skip archived and forks" | `specweave get --all acme-corp --no-archived --no-forks` |
96
+ | "clone all api-* repos from myorg, max 200" | `specweave get "myorg/api-*" --limit 200` |
73
97
  | "restore all repos on this machine" | See note below |
74
98
 
99
+ ## Bulk Clone Notes
100
+
101
+ - Requires GitHub auth: `GH_TOKEN` env var or `gh auth login`
102
+ - Cloning runs in the **background** — terminal can be closed safely
103
+ - Monitor progress: `specweave jobs`
104
+ - Already-cloned repos are skipped automatically (idempotent)
105
+ - Failed repos are tracked and can be resumed by re-running the same command
106
+
75
107
  ## New Machine Restore
76
108
 
77
109
  If the user says "restore repos", "clone all child repos", or "set up on new machine":
@@ -39,9 +39,26 @@ If `prRefs` already has an entry with `state: "open"`, skip PR creation and repo
39
39
 
40
40
  ## Step 2: Determine Branch Name
41
41
 
42
+ **External ticket key prefix** — If the increment was imported from JIRA/ADO, prefix the branch with the external ticket key for native integration linking:
43
+
42
44
  ```bash
43
- BRANCH_NAME="${BRANCH_PREFIX}${INCREMENT_ID}"
44
- # e.g., sw/0520-pr-based-increment-closure
45
+ # Check metadata for external ticket key (JIRA or ADO)
46
+ JIRA_KEY=$(jq -r '.jira.issue // .jira.issueKey // .externalLinks.jira.epicKey // .externalLinks.jira.issueKey // empty' \
47
+ .specweave/increments/${INCREMENT_ID}/metadata.json 2>/dev/null)
48
+ ADO_ID=$(jq -r '.externalLinks.ado.featureId // .externalLinks.ado.workItemId // empty' \
49
+ .specweave/increments/${INCREMENT_ID}/metadata.json 2>/dev/null)
50
+ EXTERNAL_KEY_BRANCHING=$(jq -r '.cicd.git.externalKeyBranching // true' .specweave/config.json 2>/dev/null)
51
+
52
+ if [ "$EXTERNAL_KEY_BRANCHING" = "true" ] && [ -n "$JIRA_KEY" ]; then
53
+ BRANCH_NAME="${JIRA_KEY}/${INCREMENT_ID}"
54
+ # e.g., ID-300/0520-auth-gateway-otel
55
+ elif [ "$EXTERNAL_KEY_BRANCHING" = "true" ] && [ -n "$ADO_ID" ]; then
56
+ BRANCH_NAME="AB#${ADO_ID}/${INCREMENT_ID}"
57
+ # e.g., AB#4567/0520-auth-gateway-otel
58
+ else
59
+ BRANCH_NAME="${BRANCH_PREFIX}${INCREMENT_ID}"
60
+ # e.g., sw/0520-pr-based-increment-closure
61
+ fi
45
62
  ```
46
63
 
47
64
  Check current branch:
@@ -147,6 +164,26 @@ Update increment metadata with PR reference. Edit `metadata.json` to add/update
147
164
 
148
165
  Use `jq` or direct JSON edit to update metadata.json.
149
166
 
167
+ ## Step 7b: Link PR to External Tickets
168
+
169
+ After PR creation, link the PR to any associated JIRA/ADO tickets. This creates remote links (JIRA) or hyperlinks (ADO) so the PR appears in the external tool's UI.
170
+
171
+ ```bash
172
+ specweave link-pr \
173
+ --increment "${INCREMENT_ID}" \
174
+ --pr-url "${PR_URL}" \
175
+ --pr-number "${PR_NUMBER}" \
176
+ --branch "${BRANCH_NAME}"
177
+ ```
178
+
179
+ This is **non-blocking** — if linking fails, the PR is still created successfully. Errors are logged as warnings.
180
+
181
+ The link-pr command:
182
+ - Reads metadata.json for JIRA issue keys and ADO work item IDs
183
+ - Creates JIRA remote links via `/rest/api/3/issue/{key}/remotelink` (idempotent via `globalId`)
184
+ - Creates ADO work item hyperlinks via JSON Patch on work item relations
185
+ - Reports linked tickets and any errors
186
+
150
187
  ## Step 8: Multi-Repo / Umbrella Mode
151
188
 
152
189
  Check if umbrella mode is enabled:
@@ -127,6 +127,28 @@ class AdoClient {
127
127
  const response = await this.request("POST", url, { text });
128
128
  return response;
129
129
  }
130
+ /**
131
+ * Add hyperlink relation to work item (for PR linking)
132
+ *
133
+ * @see https://learn.microsoft.com/en-us/rest/api/azure/devops/wit/work-items/update
134
+ */
135
+ async addHyperlink(workItemId, url, comment) {
136
+ const apiUrl = `${this.baseUrl}/_apis/wit/workitems/${workItemId}?api-version=7.1`;
137
+ const operations = [{
138
+ op: "add",
139
+ path: "/relations/-",
140
+ value: {
141
+ rel: "Hyperlink",
142
+ url,
143
+ attributes: {
144
+ comment
145
+ }
146
+ }
147
+ }];
148
+ await this.request("PATCH", apiUrl, operations, {
149
+ "Content-Type": "application/json-patch+json"
150
+ });
151
+ }
130
152
  /**
131
153
  * Get comments for work item
132
154
  *
@@ -224,6 +224,31 @@ export class AdoClient {
224
224
  return response;
225
225
  }
226
226
 
227
+ /**
228
+ * Add hyperlink relation to work item (for PR linking)
229
+ *
230
+ * @see https://learn.microsoft.com/en-us/rest/api/azure/devops/wit/work-items/update
231
+ */
232
+ async addHyperlink(workItemId: number, url: string, comment: string): Promise<void> {
233
+ const apiUrl = `${this.baseUrl}/_apis/wit/workitems/${workItemId}?api-version=7.1`;
234
+
235
+ const operations = [{
236
+ op: 'add',
237
+ path: '/relations/-',
238
+ value: {
239
+ rel: 'Hyperlink',
240
+ url: url,
241
+ attributes: {
242
+ comment: comment
243
+ }
244
+ }
245
+ }];
246
+
247
+ await this.request('PATCH', apiUrl, operations, {
248
+ 'Content-Type': 'application/json-patch+json',
249
+ });
250
+ }
251
+
227
252
  /**
228
253
  * Get comments for work item
229
254
  *