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.
- package/bin/specweave.js +18 -0
- package/dist/plugins/specweave-ado/lib/ado-client.d.ts +6 -0
- package/dist/plugins/specweave-ado/lib/ado-client.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-client.js +22 -0
- package/dist/plugins/specweave-ado/lib/ado-client.js.map +1 -1
- package/dist/src/cli/commands/get.d.ts +17 -1
- package/dist/src/cli/commands/get.d.ts.map +1 -1
- package/dist/src/cli/commands/get.js +51 -1
- package/dist/src/cli/commands/get.js.map +1 -1
- package/dist/src/cli/commands/link-pr.d.ts +17 -0
- package/dist/src/cli/commands/link-pr.d.ts.map +1 -0
- package/dist/src/cli/commands/link-pr.js +45 -0
- package/dist/src/cli/commands/link-pr.js.map +1 -0
- package/dist/src/cli/helpers/get/bulk-get.d.ts +41 -0
- package/dist/src/cli/helpers/get/bulk-get.d.ts.map +1 -0
- package/dist/src/cli/helpers/get/bulk-get.js +88 -0
- package/dist/src/cli/helpers/get/bulk-get.js.map +1 -0
- package/dist/src/cli/helpers/init/github-repo-cloning.d.ts +50 -0
- package/dist/src/cli/helpers/init/github-repo-cloning.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/github-repo-cloning.js +1 -1
- package/dist/src/cli/helpers/init/github-repo-cloning.js.map +1 -1
- package/dist/src/integrations/jira/jira-client.d.ts +12 -0
- package/dist/src/integrations/jira/jira-client.d.ts.map +1 -1
- package/dist/src/integrations/jira/jira-client.js +37 -0
- package/dist/src/integrations/jira/jira-client.js.map +1 -1
- package/dist/src/sync/format-preservation-sync.d.ts +3 -0
- package/dist/src/sync/format-preservation-sync.d.ts.map +1 -1
- package/dist/src/sync/format-preservation-sync.js +9 -0
- package/dist/src/sync/format-preservation-sync.js.map +1 -1
- package/dist/src/sync/integration-health-check.d.ts +45 -0
- package/dist/src/sync/integration-health-check.d.ts.map +1 -0
- package/dist/src/sync/integration-health-check.js +186 -0
- package/dist/src/sync/integration-health-check.js.map +1 -0
- package/dist/src/sync/pr-linker.d.ts +38 -0
- package/dist/src/sync/pr-linker.d.ts.map +1 -0
- package/dist/src/sync/pr-linker.js +157 -0
- package/dist/src/sync/pr-linker.js.map +1 -0
- package/package.json +1 -1
- package/plugins/specweave/commands/sync-setup.md +24 -1
- package/plugins/specweave/skills/get/SKILL.md +38 -6
- package/plugins/specweave/skills/pr/SKILL.md +39 -2
- package/plugins/specweave-ado/lib/ado-client.js +22 -0
- 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.
|
|
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:
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
44
|
-
|
|
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
|
*
|