sdd-cli 0.1.21 → 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 CHANGED
@@ -14,6 +14,8 @@ Mission and vision live in `docs/MISSION.md` and `docs/VISION.md`.
14
14
 
15
15
  Start with `docs/INDEX.md` for a full documentation map and `docs/STYLE.md` for formatting guidance.
16
16
  Contributing guidelines live in `docs/CONTRIBUTING.md`.
17
+ Contributor quickstart lives in `docs/CONTRIBUTOR_QUICKSTART.md`.
18
+ Issue triage taxonomy lives in `docs/ISSUE_TRIAGE_PLAYBOOK.md`.
17
19
  Use the PR template in `.github/PULL_REQUEST_TEMPLATE.md`.
18
20
  Maintenance guidance lives in `docs/MAINTENANCE.md`.
19
21
  Install troubleshooting lives in `docs/TROUBLESHOOTING.md`.
@@ -168,6 +170,8 @@ Use `--questions` when you want the manual question-by-question flow.
168
170
  ### Imports
169
171
  - `sdd-cli import issue <github-issue-url>` -- import issue context and bootstrap autopilot
170
172
  - `sdd-cli import jira <ticket-or-browse-url>` -- import Jira context and bootstrap autopilot
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
171
175
 
172
176
  ### Requirement lifecycle
173
177
  - `sdd-cli req create`
@@ -253,6 +257,7 @@ For a full onboarding walkthrough, see:
253
257
  - 90-day roadmap: `docs/ADOPTION_ROADMAP_90D.md`
254
258
  - Value backlog: `docs/VALUE_BACKLOG.md`
255
259
  - Error codes and remediation guide: `docs/ERROR_CODES.md`
260
+ - Integration adapters roadmap and contract: `docs/INTEGRATION_ADAPTERS.md`
256
261
 
257
262
  ## Where files are stored (clean repos)
258
263
 
@@ -275,6 +280,10 @@ Optional:
275
280
  `npm run release:notes -- --write --version v0.1.20`
276
281
  - Generate post-release quality summary:
277
282
  `npm run release:metrics`
283
+ - Run fast contributor smoke checks:
284
+ `npm run dev:smoke`
285
+ - Run contributor pre-PR release checks:
286
+ `npm run dev:release-check`
278
287
  - Promote `Unreleased` changelog entries into a version:
279
288
  `npm run release:changelog -- --version v0.1.20`
280
289
  - Verify tag/version consistency:
package/dist/cli.js CHANGED
@@ -48,6 +48,8 @@ const quickstart_1 = require("./commands/quickstart");
48
48
  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
+ const import_linear_1 = require("./commands/import-linear");
52
+ const import_azure_1 = require("./commands/import-azure");
51
53
  const paths_1 = require("./paths");
52
54
  const flags_1 = require("./context/flags");
53
55
  const prompt_1 = require("./ui/prompt");
@@ -398,4 +400,18 @@ importCmd
398
400
  .action(async (ticket) => {
399
401
  await (0, import_jira_1.runImportJira)(ticket);
400
402
  });
403
+ importCmd
404
+ .command("linear")
405
+ .description("Import a Linear ticket and bootstrap autopilot")
406
+ .argument("<ticket>", "Linear ticket key or issue URL")
407
+ .action(async (ticket) => {
408
+ await (0, import_linear_1.runImportLinear)(ticket);
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
+ });
401
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(/&nbsp;/gi, " ")
36
+ .replace(/&amp;/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
+ }
@@ -0,0 +1 @@
1
+ export declare function runImportLinear(ticketInput: string): Promise<void>;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runImportLinear = runImportLinear;
4
+ const hello_1 = require("./hello");
5
+ const errors_1 = require("../errors");
6
+ function parseLinearTicket(input) {
7
+ const trimmed = input.trim();
8
+ if (!trimmed) {
9
+ return null;
10
+ }
11
+ const keyOnly = trimmed.match(/^([a-z][a-z0-9_]*-\d+)$/i);
12
+ if (keyOnly) {
13
+ return { identifier: keyOnly[1].toUpperCase() };
14
+ }
15
+ const linearUrl = trimmed.match(/^https?:\/\/linear\.app\/[^/]+\/issue\/([a-z][a-z0-9_]*-\d+)(?:[/?#].*)?$/i);
16
+ if (linearUrl) {
17
+ return { identifier: linearUrl[1].toUpperCase() };
18
+ }
19
+ return null;
20
+ }
21
+ async function fetchLinearTicket(ref) {
22
+ const endpoint = (process.env.SDD_LINEAR_API_BASE || "https://api.linear.app/graphql").trim();
23
+ const token = (process.env.SDD_LINEAR_API_KEY || "").trim();
24
+ const query = `
25
+ query IssueByIdentifier($identifier: String!) {
26
+ issue(identifier: $identifier) {
27
+ identifier
28
+ title
29
+ description
30
+ url
31
+ }
32
+ }
33
+ `;
34
+ const headers = {
35
+ "Content-Type": "application/json",
36
+ Accept: "application/json",
37
+ "User-Agent": "sdd-cli"
38
+ };
39
+ if (token.length > 0) {
40
+ headers.Authorization = token.startsWith("Bearer ") ? token : `Bearer ${token}`;
41
+ }
42
+ const response = await fetch(endpoint, {
43
+ method: "POST",
44
+ headers,
45
+ body: JSON.stringify({
46
+ query,
47
+ variables: { identifier: ref.identifier }
48
+ })
49
+ });
50
+ if (!response.ok) {
51
+ throw new Error(`Failed to fetch Linear ticket (${response.status}).`);
52
+ }
53
+ const payload = (await response.json());
54
+ if (payload.errors && payload.errors.length > 0) {
55
+ const message = payload.errors[0]?.message || "Unknown Linear API error.";
56
+ throw new Error(`Linear API error: ${message}`);
57
+ }
58
+ const issue = payload.data?.issue;
59
+ if (!issue) {
60
+ throw new Error(`Linear ticket not found: ${ref.identifier}`);
61
+ }
62
+ return {
63
+ identifier: (issue.identifier || ref.identifier).toUpperCase(),
64
+ title: issue.title?.trim() || `Linear ticket ${ref.identifier.toUpperCase()}`,
65
+ description: issue.description?.trim() || "",
66
+ sourceUrl: issue.url?.trim() || `https://linear.app/issue/${ref.identifier.toUpperCase()}`
67
+ };
68
+ }
69
+ function buildSeedText(ticket) {
70
+ const bodySnippet = ticket.description.trim().slice(0, 400).replace(/\s+/g, " ");
71
+ return `Resolve Linear ticket: ${ticket.identifier} ${ticket.title}. Context: ${bodySnippet}. Source: ${ticket.sourceUrl}`;
72
+ }
73
+ async function runImportLinear(ticketInput) {
74
+ const ref = parseLinearTicket(ticketInput);
75
+ if (!ref) {
76
+ (0, errors_1.printError)("SDD-1121", "Invalid Linear ticket. Expected LIN-123 or https://linear.app/<team>/issue/LIN-123/<slug>");
77
+ return;
78
+ }
79
+ console.log(`Importing Linear ticket ${ref.identifier} ...`);
80
+ try {
81
+ const ticket = await fetchLinearTicket(ref);
82
+ console.log(`Imported: ${ticket.title}`);
83
+ await (0, hello_1.runHello)(buildSeedText(ticket), false);
84
+ }
85
+ catch (error) {
86
+ (0, errors_1.printError)("SDD-1122", error.message);
87
+ }
88
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sdd-cli",
3
- "version": "0.1.21",
4
- "description": "Use sdd-cli to turn ideas, GitHub/Jira work items, and PR feedback into actionable requirements, specs, plans, and done-ready delivery records with guided workflows.",
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",
@@ -42,6 +42,8 @@
42
42
  "build": "tsc -p tsconfig.json",
43
43
  "start": "node dist/cli.js",
44
44
  "dev": "ts-node src/cli.ts",
45
+ "dev:smoke": "npm run build && npm run smoke:autopilot",
46
+ "dev:release-check": "npm run check:error-codes && npm run check:docs && npm test && npm run verify:publish",
45
47
  "check:docs": "node scripts/check-docs-flags.js",
46
48
  "check:error-codes": "node scripts/check-error-codes.js",
47
49
  "release:notes": "node scripts/generate-release-notes.js",