sdd-cli 0.1.22 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/cli.js +8 -0
- package/dist/commands/import-azure.d.ts +1 -0
- package/dist/commands/import-azure.js +113 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -171,6 +171,7 @@ Use `--questions` when you want the manual question-by-question flow.
|
|
|
171
171
|
- `sdd-cli import issue <github-issue-url>` -- import issue context and bootstrap autopilot
|
|
172
172
|
- `sdd-cli import jira <ticket-or-browse-url>` -- import Jira context and bootstrap autopilot
|
|
173
173
|
- `sdd-cli import linear <ticket-or-issue-url>` -- import Linear context and bootstrap autopilot
|
|
174
|
+
- `sdd-cli import azure <work-item-or-url>` -- import Azure Boards context and bootstrap autopilot
|
|
174
175
|
|
|
175
176
|
### Requirement lifecycle
|
|
176
177
|
- `sdd-cli req create`
|
package/dist/cli.js
CHANGED
|
@@ -49,6 +49,7 @@ const status_1 = require("./commands/status");
|
|
|
49
49
|
const import_issue_1 = require("./commands/import-issue");
|
|
50
50
|
const import_jira_1 = require("./commands/import-jira");
|
|
51
51
|
const import_linear_1 = require("./commands/import-linear");
|
|
52
|
+
const import_azure_1 = require("./commands/import-azure");
|
|
52
53
|
const paths_1 = require("./paths");
|
|
53
54
|
const flags_1 = require("./context/flags");
|
|
54
55
|
const prompt_1 = require("./ui/prompt");
|
|
@@ -406,4 +407,11 @@ importCmd
|
|
|
406
407
|
.action(async (ticket) => {
|
|
407
408
|
await (0, import_linear_1.runImportLinear)(ticket);
|
|
408
409
|
});
|
|
410
|
+
importCmd
|
|
411
|
+
.command("azure")
|
|
412
|
+
.description("Import an Azure Boards work item and bootstrap autopilot")
|
|
413
|
+
.argument("<work-item>", "Azure work item id, AB#id, or work item URL")
|
|
414
|
+
.action(async (workItem) => {
|
|
415
|
+
await (0, import_azure_1.runImportAzure)(workItem);
|
|
416
|
+
});
|
|
409
417
|
program.parse(process.argv);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runImportAzure(workItemInput: string): Promise<void>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runImportAzure = runImportAzure;
|
|
4
|
+
const hello_1 = require("./hello");
|
|
5
|
+
const errors_1 = require("../errors");
|
|
6
|
+
function parseAzureWorkItem(input) {
|
|
7
|
+
const trimmed = input.trim();
|
|
8
|
+
if (!trimmed) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const shorthand = trimmed.match(/^AB#(\d+)$/i);
|
|
12
|
+
if (shorthand) {
|
|
13
|
+
return { id: shorthand[1] };
|
|
14
|
+
}
|
|
15
|
+
const numeric = trimmed.match(/^(\d+)$/);
|
|
16
|
+
if (numeric) {
|
|
17
|
+
return { id: numeric[1] };
|
|
18
|
+
}
|
|
19
|
+
const azureUrl = trimmed.match(/^https?:\/\/dev\.azure\.com\/([^/]+)\/([^/]+)\/_workitems\/edit\/(\d+)(?:[/?#].*)?$/i);
|
|
20
|
+
if (azureUrl) {
|
|
21
|
+
const parsed = new URL(trimmed);
|
|
22
|
+
return {
|
|
23
|
+
id: azureUrl[3],
|
|
24
|
+
siteBase: `${parsed.protocol}//${parsed.host}/${azureUrl[1]}`,
|
|
25
|
+
project: azureUrl[2]
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
function stripHtml(value) {
|
|
31
|
+
return value
|
|
32
|
+
.replace(/<style[\s\S]*?<\/style>/gi, " ")
|
|
33
|
+
.replace(/<script[\s\S]*?<\/script>/gi, " ")
|
|
34
|
+
.replace(/<[^>]+>/g, " ")
|
|
35
|
+
.replace(/ /gi, " ")
|
|
36
|
+
.replace(/&/gi, "&")
|
|
37
|
+
.replace(/\s+/g, " ")
|
|
38
|
+
.trim();
|
|
39
|
+
}
|
|
40
|
+
function getAzureApiBase(ref) {
|
|
41
|
+
if (process.env.SDD_AZURE_API_BASE && process.env.SDD_AZURE_API_BASE.trim().length > 0) {
|
|
42
|
+
return process.env.SDD_AZURE_API_BASE.trim().replace(/\/$/, "");
|
|
43
|
+
}
|
|
44
|
+
if (ref.siteBase && ref.project) {
|
|
45
|
+
return `${ref.siteBase.replace(/\/$/, "")}/${ref.project}/_apis/wit`;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
function getAzureAuthHeader() {
|
|
50
|
+
const pat = (process.env.SDD_AZURE_PAT || "").trim();
|
|
51
|
+
if (pat.length === 0) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const basic = Buffer.from(`:${pat}`, "utf-8").toString("base64");
|
|
55
|
+
return `Basic ${basic}`;
|
|
56
|
+
}
|
|
57
|
+
async function fetchAzureWorkItem(ref) {
|
|
58
|
+
const apiBase = getAzureApiBase(ref);
|
|
59
|
+
if (!apiBase) {
|
|
60
|
+
throw new Error("Azure API base is required. Set SDD_AZURE_API_BASE or provide a full URL: https://dev.azure.com/<org>/<project>/_workitems/edit/<id>");
|
|
61
|
+
}
|
|
62
|
+
const endpoint = `${apiBase}/workitems/${encodeURIComponent(ref.id)}?api-version=7.1`;
|
|
63
|
+
const headers = {
|
|
64
|
+
Accept: "application/json",
|
|
65
|
+
"User-Agent": "sdd-cli"
|
|
66
|
+
};
|
|
67
|
+
const auth = getAzureAuthHeader();
|
|
68
|
+
if (auth) {
|
|
69
|
+
headers.Authorization = auth;
|
|
70
|
+
}
|
|
71
|
+
const response = await fetch(endpoint, { headers });
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
throw new Error(`Failed to fetch Azure work item (${response.status}).`);
|
|
74
|
+
}
|
|
75
|
+
const payload = (await response.json());
|
|
76
|
+
const resolvedId = String(payload.id ?? ref.id);
|
|
77
|
+
const titleRaw = payload.fields?.["System.Title"];
|
|
78
|
+
const descriptionRaw = payload.fields?.["System.Description"];
|
|
79
|
+
const sourceUrlRaw = payload._links?.html?.href;
|
|
80
|
+
const title = typeof titleRaw === "string" && titleRaw.trim().length > 0 ? titleRaw.trim() : `Work item ${resolvedId}`;
|
|
81
|
+
const description = typeof descriptionRaw === "string" && descriptionRaw.trim().length > 0 ? stripHtml(descriptionRaw) : "";
|
|
82
|
+
const sourceUrl = typeof sourceUrlRaw === "string" && sourceUrlRaw.trim().length > 0
|
|
83
|
+
? sourceUrlRaw.trim()
|
|
84
|
+
: ref.siteBase && ref.project
|
|
85
|
+
? `${ref.siteBase.replace(/\/$/, "")}/${ref.project}/_workitems/edit/${resolvedId}`
|
|
86
|
+
: `AB#${resolvedId}`;
|
|
87
|
+
return {
|
|
88
|
+
id: resolvedId,
|
|
89
|
+
title,
|
|
90
|
+
description,
|
|
91
|
+
sourceUrl
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function buildSeedText(item) {
|
|
95
|
+
const bodySnippet = item.description.trim().slice(0, 400).replace(/\s+/g, " ");
|
|
96
|
+
return `Resolve Azure work item: ${item.id} ${item.title}. Context: ${bodySnippet}. Source: ${item.sourceUrl}`;
|
|
97
|
+
}
|
|
98
|
+
async function runImportAzure(workItemInput) {
|
|
99
|
+
const ref = parseAzureWorkItem(workItemInput);
|
|
100
|
+
if (!ref) {
|
|
101
|
+
(0, errors_1.printError)("SDD-1131", "Invalid Azure work item. Expected AB#1234, 1234, or https://dev.azure.com/<org>/<project>/_workitems/edit/1234");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
console.log(`Importing Azure work item ${ref.id} ...`);
|
|
105
|
+
try {
|
|
106
|
+
const ticket = await fetchAzureWorkItem(ref);
|
|
107
|
+
console.log(`Imported: ${ticket.title}`);
|
|
108
|
+
await (0, hello_1.runHello)(buildSeedText(ticket), false);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
(0, errors_1.printError)("SDD-1132", error.message);
|
|
112
|
+
}
|
|
113
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sdd-cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.23",
|
|
4
|
+
"description": "CLI for turning ideas and tracker work items (GitHub, Jira, Linear, Azure) into actionable requirements, specs, test plans, and done-ready delivery artifacts with guided autopilot workflows.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
7
7
|
"specification-driven-development",
|