@specship/specship-win32-arm64 0.13.0 → 0.14.0
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/lib/dist/bin/specship.js +180 -1
- package/lib/dist/bin/specship.js.map +1 -1
- package/lib/dist/jira/auth.d.ts +17 -0
- package/lib/dist/jira/auth.d.ts.map +1 -0
- package/lib/dist/jira/auth.js +33 -0
- package/lib/dist/jira/auth.js.map +1 -0
- package/lib/dist/jira/client.d.ts +120 -0
- package/lib/dist/jira/client.d.ts.map +1 -0
- package/lib/dist/jira/client.js +316 -0
- package/lib/dist/jira/client.js.map +1 -0
- package/lib/dist/jira/config.d.ts +45 -0
- package/lib/dist/jira/config.d.ts.map +1 -0
- package/lib/dist/jira/config.js +178 -0
- package/lib/dist/jira/config.js.map +1 -0
- package/lib/dist/jira/index.d.ts +8 -0
- package/lib/dist/jira/index.d.ts.map +1 -0
- package/lib/dist/jira/index.js +24 -0
- package/lib/dist/jira/index.js.map +1 -0
- package/lib/dist/jira/pull-request.d.ts +72 -0
- package/lib/dist/jira/pull-request.d.ts.map +1 -0
- package/lib/dist/jira/pull-request.js +127 -0
- package/lib/dist/jira/pull-request.js.map +1 -0
- package/lib/dist/jira/spec-generator.d.ts +34 -0
- package/lib/dist/jira/spec-generator.d.ts.map +1 -0
- package/lib/dist/jira/spec-generator.js +138 -0
- package/lib/dist/jira/spec-generator.js.map +1 -0
- package/lib/dist/jira/spec-writer.d.ts +36 -0
- package/lib/dist/jira/spec-writer.d.ts.map +1 -0
- package/lib/dist/jira/spec-writer.js +143 -0
- package/lib/dist/jira/spec-writer.js.map +1 -0
- package/lib/dist/jira/types.d.ts +174 -0
- package/lib/dist/jira/types.d.ts.map +1 -0
- package/lib/dist/jira/types.js +60 -0
- package/lib/dist/jira/types.js.map +1 -0
- package/lib/dist/mcp/jira-tools.d.ts +234 -0
- package/lib/dist/mcp/jira-tools.d.ts.map +1 -0
- package/lib/dist/mcp/jira-tools.js +787 -0
- package/lib/dist/mcp/jira-tools.js.map +1 -0
- package/lib/dist/mcp/server-instructions.d.ts +1 -1
- package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
- package/lib/dist/mcp/server-instructions.js +10 -0
- package/lib/dist/mcp/server-instructions.js.map +1 -1
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +32 -0
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/dist/server/cli.js +1 -1
- package/lib/dist/server/routes/sse.js +1 -1
- package/lib/dist/server/server.js +1 -1
- package/lib/dist/statusline/index.d.ts +19 -0
- package/lib/dist/statusline/index.d.ts.map +1 -1
- package/lib/dist/statusline/index.js +96 -3
- package/lib/dist/statusline/index.js.map +1 -1
- package/lib/dist/statusline/render.d.ts +24 -0
- package/lib/dist/statusline/render.d.ts.map +1 -1
- package/lib/dist/statusline/render.js +26 -5
- package/lib/dist/statusline/render.js.map +1 -1
- package/lib/dist/workflows/executor.d.ts +25 -1
- package/lib/dist/workflows/executor.d.ts.map +1 -1
- package/lib/dist/workflows/executor.js +28 -3
- package/lib/dist/workflows/executor.js.map +1 -1
- package/lib/node_modules/.package-lock.json +11 -20
- package/lib/node_modules/ajv/node_modules/fast-uri/index.js +1 -1
- package/lib/node_modules/ajv/node_modules/fast-uri/package.json +1 -1
- package/lib/node_modules/ajv/node_modules/fast-uri/test/security.test.js +28 -0
- package/lib/node_modules/toad-cache/README.md +10 -9
- package/lib/node_modules/toad-cache/dist/toad-cache.cjs +139 -139
- package/lib/node_modules/toad-cache/dist/toad-cache.mjs +136 -140
- package/lib/node_modules/toad-cache/package.json +8 -8
- package/lib/node_modules/toad-cache/toad-cache.d.cts +20 -14
- package/lib/node_modules/toad-cache/toad-cache.d.ts +18 -14
- package/lib/node_modules/yaml/README.md +14 -31
- package/lib/node_modules/yaml/browser/dist/compose/compose-collection.js +3 -3
- package/lib/node_modules/yaml/browser/dist/compose/compose-doc.js +0 -1
- package/lib/node_modules/yaml/browser/dist/compose/compose-node.js +4 -21
- package/lib/node_modules/yaml/browser/dist/compose/compose-scalar.js +8 -14
- package/lib/node_modules/yaml/browser/dist/compose/composer.js +2 -4
- package/lib/node_modules/yaml/browser/dist/compose/resolve-block-map.js +0 -2
- package/lib/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +1 -3
- package/lib/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +2 -8
- package/lib/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +4 -6
- package/lib/node_modules/yaml/browser/dist/compose/resolve-props.js +5 -3
- package/lib/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +2 -1
- package/lib/node_modules/yaml/browser/dist/compose/util-map-includes.js +5 -1
- package/lib/node_modules/yaml/browser/dist/doc/Document.js +2 -3
- package/lib/node_modules/yaml/browser/dist/doc/anchors.js +2 -1
- package/lib/node_modules/yaml/browser/dist/doc/applyReviver.js +0 -1
- package/lib/node_modules/yaml/browser/dist/doc/createNode.js +2 -1
- package/lib/node_modules/yaml/browser/dist/errors.js +1 -1
- package/lib/node_modules/yaml/browser/dist/log.js +6 -1
- package/lib/node_modules/yaml/browser/dist/node_modules/tslib/tslib.es6.js +21 -0
- package/lib/node_modules/yaml/browser/dist/nodes/Alias.js +12 -27
- package/lib/node_modules/yaml/browser/dist/nodes/YAMLMap.js +1 -1
- package/lib/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +49 -8
- package/lib/node_modules/yaml/browser/dist/parse/lexer.js +24 -28
- package/lib/node_modules/yaml/browser/dist/parse/parser.js +8 -31
- package/lib/node_modules/yaml/browser/dist/public-api.js +0 -3
- package/lib/node_modules/yaml/browser/dist/schema/Schema.js +2 -1
- package/lib/node_modules/yaml/browser/dist/schema/json/schema.js +1 -1
- package/lib/node_modules/yaml/browser/dist/schema/tags.js +16 -29
- package/lib/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +13 -5
- package/lib/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +1 -1
- package/lib/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +0 -2
- package/lib/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +1 -1
- package/lib/node_modules/yaml/browser/dist/stringify/stringify.js +5 -10
- package/lib/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +3 -13
- package/lib/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +2 -3
- package/lib/node_modules/yaml/browser/dist/stringify/stringifyPair.js +1 -1
- package/lib/node_modules/yaml/browser/dist/stringify/stringifyString.js +16 -24
- package/lib/node_modules/yaml/browser/dist/util.js +1 -1
- package/lib/node_modules/yaml/dist/cli.d.ts +1 -1
- package/lib/node_modules/yaml/dist/cli.mjs +3 -5
- package/lib/node_modules/yaml/dist/compose/compose-collection.d.ts +4 -4
- package/lib/node_modules/yaml/dist/compose/compose-collection.js +3 -3
- package/lib/node_modules/yaml/dist/compose/compose-doc.d.ts +6 -6
- package/lib/node_modules/yaml/dist/compose/compose-doc.js +0 -1
- package/lib/node_modules/yaml/dist/compose/compose-node.d.ts +7 -8
- package/lib/node_modules/yaml/dist/compose/compose-node.js +4 -21
- package/lib/node_modules/yaml/dist/compose/compose-scalar.d.ts +4 -4
- package/lib/node_modules/yaml/dist/compose/compose-scalar.js +7 -13
- package/lib/node_modules/yaml/dist/compose/composer.d.ts +6 -7
- package/lib/node_modules/yaml/dist/compose/composer.js +3 -6
- package/lib/node_modules/yaml/dist/compose/resolve-block-map.d.ts +5 -5
- package/lib/node_modules/yaml/dist/compose/resolve-block-map.js +0 -2
- package/lib/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +5 -5
- package/lib/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +5 -5
- package/lib/node_modules/yaml/dist/compose/resolve-block-seq.js +1 -3
- package/lib/node_modules/yaml/dist/compose/resolve-end.d.ts +2 -2
- package/lib/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +7 -7
- package/lib/node_modules/yaml/dist/compose/resolve-flow-collection.js +2 -8
- package/lib/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +4 -4
- package/lib/node_modules/yaml/dist/compose/resolve-flow-scalar.js +4 -6
- package/lib/node_modules/yaml/dist/compose/resolve-props.d.ts +2 -2
- package/lib/node_modules/yaml/dist/compose/resolve-props.js +5 -3
- package/lib/node_modules/yaml/dist/compose/util-contains-newline.d.ts +1 -1
- package/lib/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +1 -1
- package/lib/node_modules/yaml/dist/compose/util-empty-scalar-position.js +2 -1
- package/lib/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +2 -2
- package/lib/node_modules/yaml/dist/compose/util-map-includes.d.ts +3 -3
- package/lib/node_modules/yaml/dist/compose/util-map-includes.js +5 -1
- package/lib/node_modules/yaml/dist/doc/Document.d.ts +11 -11
- package/lib/node_modules/yaml/dist/doc/Document.js +2 -3
- package/lib/node_modules/yaml/dist/doc/anchors.d.ts +2 -2
- package/lib/node_modules/yaml/dist/doc/anchors.js +2 -1
- package/lib/node_modules/yaml/dist/doc/applyReviver.js +0 -1
- package/lib/node_modules/yaml/dist/doc/createNode.d.ts +4 -4
- package/lib/node_modules/yaml/dist/doc/createNode.js +2 -1
- package/lib/node_modules/yaml/dist/doc/directives.d.ts +1 -1
- package/lib/node_modules/yaml/dist/errors.d.ts +1 -1
- package/lib/node_modules/yaml/dist/errors.js +1 -1
- package/lib/node_modules/yaml/dist/index.d.ts +18 -21
- package/lib/node_modules/yaml/dist/log.js +4 -4
- package/lib/node_modules/yaml/dist/nodes/Alias.d.ts +9 -10
- package/lib/node_modules/yaml/dist/nodes/Alias.js +11 -26
- package/lib/node_modules/yaml/dist/nodes/Collection.d.ts +4 -4
- package/lib/node_modules/yaml/dist/nodes/Node.d.ts +9 -16
- package/lib/node_modules/yaml/dist/nodes/Pair.d.ts +8 -9
- package/lib/node_modules/yaml/dist/nodes/Scalar.d.ts +4 -9
- package/lib/node_modules/yaml/dist/nodes/YAMLMap.d.ts +10 -10
- package/lib/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +10 -10
- package/lib/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +3 -3
- package/lib/node_modules/yaml/dist/nodes/addPairToJSMap.js +48 -7
- package/lib/node_modules/yaml/dist/nodes/identity.d.ts +7 -7
- package/lib/node_modules/yaml/dist/nodes/toJS.d.ts +2 -4
- package/lib/node_modules/yaml/dist/options.d.ts +10 -22
- package/lib/node_modules/yaml/dist/parse/cst-scalar.d.ts +4 -4
- package/lib/node_modules/yaml/dist/parse/cst-stringify.d.ts +1 -1
- package/lib/node_modules/yaml/dist/parse/cst-visit.d.ts +2 -2
- package/lib/node_modules/yaml/dist/parse/cst.d.ts +3 -4
- package/lib/node_modules/yaml/dist/parse/lexer.d.ts +1 -1
- package/lib/node_modules/yaml/dist/parse/lexer.js +24 -28
- package/lib/node_modules/yaml/dist/parse/parser.d.ts +4 -4
- package/lib/node_modules/yaml/dist/parse/parser.js +9 -33
- package/lib/node_modules/yaml/dist/public-api.d.ts +5 -6
- package/lib/node_modules/yaml/dist/public-api.js +0 -3
- package/lib/node_modules/yaml/dist/schema/Schema.d.ts +5 -4
- package/lib/node_modules/yaml/dist/schema/Schema.js +2 -1
- package/lib/node_modules/yaml/dist/schema/common/map.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/common/null.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/common/seq.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/common/string.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/core/bool.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/core/float.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/core/int.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/core/schema.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/json/schema.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/json/schema.js +1 -1
- package/lib/node_modules/yaml/dist/schema/tags.d.ts +3 -11
- package/lib/node_modules/yaml/dist/schema/tags.js +13 -26
- package/lib/node_modules/yaml/dist/schema/types.d.ts +12 -14
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/binary.js +7 -9
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +17 -11
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +7 -7
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/schema.js +0 -2
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +9 -9
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +1 -1
- package/lib/node_modules/yaml/dist/stringify/stringify.d.ts +3 -3
- package/lib/node_modules/yaml/dist/stringify/stringify.js +5 -10
- package/lib/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +2 -2
- package/lib/node_modules/yaml/dist/stringify/stringifyCollection.js +3 -13
- package/lib/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +3 -3
- package/lib/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +1 -1
- package/lib/node_modules/yaml/dist/stringify/stringifyNumber.js +2 -3
- package/lib/node_modules/yaml/dist/stringify/stringifyPair.d.ts +2 -2
- package/lib/node_modules/yaml/dist/stringify/stringifyPair.js +1 -1
- package/lib/node_modules/yaml/dist/stringify/stringifyString.d.ts +2 -2
- package/lib/node_modules/yaml/dist/stringify/stringifyString.js +15 -23
- package/lib/node_modules/yaml/dist/util.d.ts +12 -16
- package/lib/node_modules/yaml/dist/util.js +2 -2
- package/lib/node_modules/yaml/dist/visit.d.ts +7 -7
- package/lib/node_modules/yaml/package.json +14 -13
- package/lib/package.json +5 -6
- package/package.json +1 -1
- package/lib/node_modules/jsonc-parser/CHANGELOG.md +0 -76
- package/lib/node_modules/jsonc-parser/LICENSE.md +0 -21
- package/lib/node_modules/jsonc-parser/README.md +0 -364
- package/lib/node_modules/jsonc-parser/SECURITY.md +0 -41
- package/lib/node_modules/jsonc-parser/lib/esm/impl/edit.js +0 -185
- package/lib/node_modules/jsonc-parser/lib/esm/impl/format.js +0 -261
- package/lib/node_modules/jsonc-parser/lib/esm/impl/parser.js +0 -659
- package/lib/node_modules/jsonc-parser/lib/esm/impl/scanner.js +0 -443
- package/lib/node_modules/jsonc-parser/lib/esm/impl/string-intern.js +0 -29
- package/lib/node_modules/jsonc-parser/lib/esm/main.d.ts +0 -351
- package/lib/node_modules/jsonc-parser/lib/esm/main.js +0 -178
- package/lib/node_modules/jsonc-parser/lib/umd/impl/edit.js +0 -201
- package/lib/node_modules/jsonc-parser/lib/umd/impl/format.js +0 -275
- package/lib/node_modules/jsonc-parser/lib/umd/impl/parser.js +0 -682
- package/lib/node_modules/jsonc-parser/lib/umd/impl/scanner.js +0 -456
- package/lib/node_modules/jsonc-parser/lib/umd/impl/string-intern.js +0 -42
- package/lib/node_modules/jsonc-parser/lib/umd/main.d.ts +0 -351
- package/lib/node_modules/jsonc-parser/lib/umd/main.js +0 -194
- package/lib/node_modules/jsonc-parser/package.json +0 -37
- package/lib/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +0 -67
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +0 -9
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/merge.js +0 -71
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* JIRA config load / resolve / save (REQ-JIRA-001, A2).
|
|
4
|
+
*
|
|
5
|
+
* Config lives at `~/.specship/jira.json` — **user-level only, never in
|
|
6
|
+
* the project tree** (not even a gitignored project file), because it
|
|
7
|
+
* holds a secret. The path is overridable via `SPECSHIP_JIRA_CONFIG`
|
|
8
|
+
* (mirrors `SPECSHIP_SERVER_CONFIG`) so tests never touch the real home.
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.jiraConfigPath = jiraConfigPath;
|
|
45
|
+
exports.loadJiraConfig = loadJiraConfig;
|
|
46
|
+
exports.inferDeployment = inferDeployment;
|
|
47
|
+
exports.resolveJiraCredentials = resolveJiraCredentials;
|
|
48
|
+
exports.saveJiraConfig = saveJiraConfig;
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
50
|
+
const os = __importStar(require("os"));
|
|
51
|
+
const path = __importStar(require("path"));
|
|
52
|
+
const shared_1 = require("../installer/targets/shared");
|
|
53
|
+
const types_1 = require("./types");
|
|
54
|
+
/**
|
|
55
|
+
* Absolute path to the user-level JIRA config. Overridable via
|
|
56
|
+
* `SPECSHIP_JIRA_CONFIG` for tests. Never project-scoped.
|
|
57
|
+
*/
|
|
58
|
+
function jiraConfigPath() {
|
|
59
|
+
return (process.env.SPECSHIP_JIRA_CONFIG ??
|
|
60
|
+
path.join(os.homedir(), '.specship', 'jira.json'));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Read `~/.specship/jira.json`. Returns `null` when the file is absent.
|
|
64
|
+
* Throws `JiraConfigError` when it exists but is malformed — a corrupt
|
|
65
|
+
* secret file is a hard error, not a silent fall-through to `{}`.
|
|
66
|
+
*/
|
|
67
|
+
function loadJiraConfig() {
|
|
68
|
+
const file = jiraConfigPath();
|
|
69
|
+
let raw;
|
|
70
|
+
try {
|
|
71
|
+
raw = fs.readFileSync(file, 'utf-8');
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return null; // absent → not configured
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const parsed = JSON.parse(raw);
|
|
78
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
79
|
+
throw new types_1.JiraConfigError(`JIRA config at ${file} is not a JSON object. Re-run "specship jira configure".`);
|
|
80
|
+
}
|
|
81
|
+
return parsed;
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
if (err instanceof types_1.JiraConfigError)
|
|
85
|
+
throw err;
|
|
86
|
+
throw new types_1.JiraConfigError(`JIRA config at ${file} is not valid JSON. Re-run "specship jira configure".`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Infer the deployment kind from which credentials are present. An
|
|
91
|
+
* explicit `deployment` field always wins. Otherwise: a PAT (with no
|
|
92
|
+
* email+token pair) ⇒ Data Center; an email+token pair ⇒ Cloud.
|
|
93
|
+
*/
|
|
94
|
+
function inferDeployment(creds) {
|
|
95
|
+
if (creds.deployment)
|
|
96
|
+
return creds.deployment;
|
|
97
|
+
const hasBasic = Boolean(creds.email && creds.apiToken);
|
|
98
|
+
const hasPat = Boolean(creds.pat);
|
|
99
|
+
if (hasPat && !hasBasic)
|
|
100
|
+
return 'datacenter';
|
|
101
|
+
if (hasBasic)
|
|
102
|
+
return 'cloud';
|
|
103
|
+
if (hasPat)
|
|
104
|
+
return 'datacenter';
|
|
105
|
+
throw new types_1.JiraConfigError('Cannot infer JIRA deployment: provide either an email + API token (Cloud) ' +
|
|
106
|
+
'or a personal access token (Data Center).');
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Resolve effective credentials from environment (highest precedence,
|
|
110
|
+
* per-field) then the on-disk config. Throws `JiraConfigError` with an
|
|
111
|
+
* actionable message when required fields are missing or malformed.
|
|
112
|
+
*
|
|
113
|
+
* SECURITY: the thrown messages never include a token or PAT value.
|
|
114
|
+
*/
|
|
115
|
+
function resolveJiraCredentials() {
|
|
116
|
+
const file = loadJiraConfig(); // may throw on malformed
|
|
117
|
+
const baseUrl = process.env.SPECSHIP_JIRA_BASE_URL ?? file?.baseUrl;
|
|
118
|
+
const email = process.env.SPECSHIP_JIRA_EMAIL ?? file?.email;
|
|
119
|
+
const apiToken = process.env.SPECSHIP_JIRA_API_TOKEN ?? file?.apiToken;
|
|
120
|
+
const pat = process.env.SPECSHIP_JIRA_PAT ?? file?.pat;
|
|
121
|
+
const explicitDeployment = normalizeDeployment(process.env.SPECSHIP_JIRA_DEPLOYMENT ?? file?.deployment);
|
|
122
|
+
if (!baseUrl) {
|
|
123
|
+
throw new types_1.JiraConfigError('No JIRA base URL configured. Run "specship jira configure" or set ' +
|
|
124
|
+
'SPECSHIP_JIRA_BASE_URL.');
|
|
125
|
+
}
|
|
126
|
+
// Lifecycle transition names (REQ-JIRA-007): env overrides file, per field;
|
|
127
|
+
// an unconfigured project keeps the defaults so status-push still works.
|
|
128
|
+
const inProgress = process.env.SPECSHIP_JIRA_TRANSITION_IN_PROGRESS ??
|
|
129
|
+
file?.transitions?.inProgress ??
|
|
130
|
+
'In Progress';
|
|
131
|
+
const inReview = process.env.SPECSHIP_JIRA_TRANSITION_IN_REVIEW ??
|
|
132
|
+
file?.transitions?.inReview ??
|
|
133
|
+
'In Review';
|
|
134
|
+
const deployment = inferDeployment({
|
|
135
|
+
deployment: explicitDeployment,
|
|
136
|
+
email,
|
|
137
|
+
apiToken,
|
|
138
|
+
pat,
|
|
139
|
+
});
|
|
140
|
+
if (deployment === 'cloud' && !(email && apiToken)) {
|
|
141
|
+
throw new types_1.JiraConfigError('JIRA Cloud requires both an email and an API token. Run ' +
|
|
142
|
+
'"specship jira configure".');
|
|
143
|
+
}
|
|
144
|
+
if (deployment === 'datacenter' && !pat) {
|
|
145
|
+
throw new types_1.JiraConfigError('JIRA Data Center requires a personal access token. Run ' +
|
|
146
|
+
'"specship jira configure".');
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
baseUrl,
|
|
150
|
+
deployment,
|
|
151
|
+
email,
|
|
152
|
+
apiToken,
|
|
153
|
+
pat,
|
|
154
|
+
transitions: { inProgress, inReview },
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Persist config to `~/.specship/jira.json` with `0600` permissions —
|
|
159
|
+
* it holds a secret, so it must not be group/world readable.
|
|
160
|
+
*/
|
|
161
|
+
function saveJiraConfig(config) {
|
|
162
|
+
const file = jiraConfigPath();
|
|
163
|
+
(0, shared_1.writeJsonFile)(file, config);
|
|
164
|
+
try {
|
|
165
|
+
fs.chmodSync(file, 0o600);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
/* best-effort on platforms without POSIX perms (Windows) */
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function normalizeDeployment(value) {
|
|
172
|
+
if (value === undefined)
|
|
173
|
+
return undefined;
|
|
174
|
+
if (value === 'cloud' || value === 'datacenter')
|
|
175
|
+
return value;
|
|
176
|
+
throw new types_1.JiraConfigError(`Unknown JIRA deployment "${value}". Use "cloud" or "datacenter".`);
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/jira/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/jira/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* JIRA connection module (REQ-JIRA-001). Barrel re-export.
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
17
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
__exportStar(require("./types"), exports);
|
|
21
|
+
__exportStar(require("./config"), exports);
|
|
22
|
+
__exportStar(require("./auth"), exports);
|
|
23
|
+
__exportStar(require("./client"), exports);
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/jira/index.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;;;;;;;;;;;;AAEH,0CAAwB;AACxB,2CAAyB;AACzB,yCAAuB;AACvB,2CAAyB"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raise a pull request for a completed, verified JIRA implementation run
|
|
3
|
+
* (REQ-JIRA-006).
|
|
4
|
+
*
|
|
5
|
+
* The flow is driven entirely through the repo's EXISTING PR tooling — the
|
|
6
|
+
* GitHub CLI (`gh`) plus `git` — so SpecShip never re-implements auth or the
|
|
7
|
+
* host API. Every step goes through an injectable shell seam so the test suite
|
|
8
|
+
* can assert the command shapes WITHOUT ever running a real `git push` or
|
|
9
|
+
* `gh pr create` (which would push branches / open real PRs).
|
|
10
|
+
*
|
|
11
|
+
* Guarantees:
|
|
12
|
+
* - A3: if `gh` is missing or unauthenticated, or the push fails, we return
|
|
13
|
+
* the reason and DO NOTHING destructive — the caller leaves the branch +
|
|
14
|
+
* worktree fully intact for a manual PR. This helper never removes a
|
|
15
|
+
* worktree or deletes a branch.
|
|
16
|
+
* - A1/A2: the PR title and body embed the issue key (also carried by the
|
|
17
|
+
* branch name) so JIRA's development panel links the PR back to the ticket.
|
|
18
|
+
* - REQ-JIRA-009: no credential/secret is handled or echoed here — only the
|
|
19
|
+
* public issue key ever appears in the title/body.
|
|
20
|
+
*/
|
|
21
|
+
/** Result of a single shell invocation through the seam. */
|
|
22
|
+
export interface ShellResult {
|
|
23
|
+
status: number | null;
|
|
24
|
+
stdout: string;
|
|
25
|
+
stderr: string;
|
|
26
|
+
}
|
|
27
|
+
/** Injectable shell runner — stubbed in tests, real `spawnSync` in prod. */
|
|
28
|
+
export type ShellRunner = (command: string, args: string[], cwd?: string) => ShellResult;
|
|
29
|
+
/** Discriminated outcome of a PR-raise attempt. */
|
|
30
|
+
export type PullRequestOutcome = {
|
|
31
|
+
ok: true;
|
|
32
|
+
url: string;
|
|
33
|
+
} | {
|
|
34
|
+
ok: false;
|
|
35
|
+
reason: 'gh-missing' | 'gh-unauthenticated' | 'push-failed' | 'create-failed';
|
|
36
|
+
message: string;
|
|
37
|
+
};
|
|
38
|
+
export interface RaisePullRequestOptions {
|
|
39
|
+
/** Project root (the git repo the worktree branches from). */
|
|
40
|
+
repoRoot: string;
|
|
41
|
+
/** The worktree branch carrying the implementation (and the issue key). */
|
|
42
|
+
branchName: string;
|
|
43
|
+
/** Filesystem path of the worktree the push/PR is created from. */
|
|
44
|
+
worktreePath: string;
|
|
45
|
+
/** The source JIRA issue key (for the caller's title/body; embedded already). */
|
|
46
|
+
issueKey: string;
|
|
47
|
+
/** PR title (build with `buildPrTitle`). */
|
|
48
|
+
title: string;
|
|
49
|
+
/** PR body (build with `buildPrBody`). */
|
|
50
|
+
body: string;
|
|
51
|
+
/** Shell seam override (default: `spawnSync`). Tests always pass a stub. */
|
|
52
|
+
shell?: ShellRunner;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The PR title (A1): `<ISSUE-KEY>: <spec title>`. The leading key makes the PR
|
|
56
|
+
* traceable to the ticket at a glance and is one of the three places JIRA's dev
|
|
57
|
+
* panel matches (branch name + PR title + PR body).
|
|
58
|
+
*/
|
|
59
|
+
export declare function buildPrTitle(issueKey: string, specTitle: string): string;
|
|
60
|
+
/**
|
|
61
|
+
* The PR body (A2): the key sits on its OWN line (`Closes <KEY>` + `JIRA: <KEY>`)
|
|
62
|
+
* so JIRA's development panel links the PR back to the issue. No token/secret is
|
|
63
|
+
* ever embedded — only the public key (REQ-JIRA-009).
|
|
64
|
+
*/
|
|
65
|
+
export declare function buildPrBody(issueKey: string, specTitle: string): string;
|
|
66
|
+
/**
|
|
67
|
+
* Raise a PR for a verified run. Probes `gh`, pushes the branch, then creates
|
|
68
|
+
* the PR — returning early with a reason on the first failure and NEVER
|
|
69
|
+
* touching the branch or worktree so the work is recoverable (A3).
|
|
70
|
+
*/
|
|
71
|
+
export declare function raisePullRequest(opts: RaisePullRequestOptions): Promise<PullRequestOutcome>;
|
|
72
|
+
//# sourceMappingURL=pull-request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pull-request.d.ts","sourceRoot":"","sources":["../../src/jira/pull-request.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,4DAA4D;AAC5D,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,4EAA4E;AAC5E,MAAM,MAAM,WAAW,GAAG,CACxB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,CAAC,EAAE,MAAM,KACT,WAAW,CAAC;AAEjB,mDAAmD;AACnD,MAAM,MAAM,kBAAkB,GAC1B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACzB;IACE,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,YAAY,GAAG,oBAAoB,GAAG,aAAa,GAAG,eAAe,CAAC;IAC9E,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEN,MAAM,WAAW,uBAAuB;IACtC,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,UAAU,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,YAAY,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB;AAiBD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAGxE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAavE;AASD;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,uBAAuB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CA0D7B"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Raise a pull request for a completed, verified JIRA implementation run
|
|
4
|
+
* (REQ-JIRA-006).
|
|
5
|
+
*
|
|
6
|
+
* The flow is driven entirely through the repo's EXISTING PR tooling — the
|
|
7
|
+
* GitHub CLI (`gh`) plus `git` — so SpecShip never re-implements auth or the
|
|
8
|
+
* host API. Every step goes through an injectable shell seam so the test suite
|
|
9
|
+
* can assert the command shapes WITHOUT ever running a real `git push` or
|
|
10
|
+
* `gh pr create` (which would push branches / open real PRs).
|
|
11
|
+
*
|
|
12
|
+
* Guarantees:
|
|
13
|
+
* - A3: if `gh` is missing or unauthenticated, or the push fails, we return
|
|
14
|
+
* the reason and DO NOTHING destructive — the caller leaves the branch +
|
|
15
|
+
* worktree fully intact for a manual PR. This helper never removes a
|
|
16
|
+
* worktree or deletes a branch.
|
|
17
|
+
* - A1/A2: the PR title and body embed the issue key (also carried by the
|
|
18
|
+
* branch name) so JIRA's development panel links the PR back to the ticket.
|
|
19
|
+
* - REQ-JIRA-009: no credential/secret is handled or echoed here — only the
|
|
20
|
+
* public issue key ever appears in the title/body.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.buildPrTitle = buildPrTitle;
|
|
24
|
+
exports.buildPrBody = buildPrBody;
|
|
25
|
+
exports.raisePullRequest = raisePullRequest;
|
|
26
|
+
const child_process_1 = require("child_process");
|
|
27
|
+
/** The default shell runner — wraps `spawnSync`. Never used under test. */
|
|
28
|
+
function defaultShell(command, args, cwd) {
|
|
29
|
+
const r = (0, child_process_1.spawnSync)(command, args, {
|
|
30
|
+
cwd,
|
|
31
|
+
encoding: 'utf-8',
|
|
32
|
+
windowsHide: true,
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
// ENOENT (command not found) surfaces as a null status — treat as failure.
|
|
36
|
+
status: r.status,
|
|
37
|
+
stdout: (r.stdout ?? '').toString(),
|
|
38
|
+
stderr: (r.stderr ?? '').toString(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The PR title (A1): `<ISSUE-KEY>: <spec title>`. The leading key makes the PR
|
|
43
|
+
* traceable to the ticket at a glance and is one of the three places JIRA's dev
|
|
44
|
+
* panel matches (branch name + PR title + PR body).
|
|
45
|
+
*/
|
|
46
|
+
function buildPrTitle(issueKey, specTitle) {
|
|
47
|
+
const title = specTitle.trim() || issueKey;
|
|
48
|
+
return `${issueKey}: ${title}`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The PR body (A2): the key sits on its OWN line (`Closes <KEY>` + `JIRA: <KEY>`)
|
|
52
|
+
* so JIRA's development panel links the PR back to the issue. No token/secret is
|
|
53
|
+
* ever embedded — only the public key (REQ-JIRA-009).
|
|
54
|
+
*/
|
|
55
|
+
function buildPrBody(issueKey, specTitle) {
|
|
56
|
+
const title = specTitle.trim();
|
|
57
|
+
const lines = [
|
|
58
|
+
`Implements ${issueKey}${title ? `: ${title}` : ''}.`,
|
|
59
|
+
'',
|
|
60
|
+
'Raised automatically by SpecShip after the implementation completed and',
|
|
61
|
+
'its verification step passed.',
|
|
62
|
+
'',
|
|
63
|
+
// The key on its own line is what JIRA's dev panel matches on.
|
|
64
|
+
`Closes ${issueKey}`,
|
|
65
|
+
`JIRA: ${issueKey}`,
|
|
66
|
+
];
|
|
67
|
+
return lines.join('\n');
|
|
68
|
+
}
|
|
69
|
+
/** Pull the created PR URL out of `gh pr create` stdout (it prints the URL). */
|
|
70
|
+
function extractUrl(stdout) {
|
|
71
|
+
const match = stdout.match(/https?:\/\/\S+/);
|
|
72
|
+
if (match)
|
|
73
|
+
return match[0];
|
|
74
|
+
return stdout.trim();
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Raise a PR for a verified run. Probes `gh`, pushes the branch, then creates
|
|
78
|
+
* the PR — returning early with a reason on the first failure and NEVER
|
|
79
|
+
* touching the branch or worktree so the work is recoverable (A3).
|
|
80
|
+
*/
|
|
81
|
+
async function raisePullRequest(opts) {
|
|
82
|
+
const shell = opts.shell ?? defaultShell;
|
|
83
|
+
const { branchName, worktreePath, title, body } = opts;
|
|
84
|
+
// A3: probe the PR tooling before we change anything.
|
|
85
|
+
const version = shell('gh', ['--version'], worktreePath);
|
|
86
|
+
if (version.status !== 0) {
|
|
87
|
+
return {
|
|
88
|
+
ok: false,
|
|
89
|
+
reason: 'gh-missing',
|
|
90
|
+
message: 'GitHub CLI (gh) is not installed, so no pull request was raised. ' +
|
|
91
|
+
'Install gh (https://cli.github.com) and re-run, or open a PR manually — ' +
|
|
92
|
+
`the branch "${branchName}" and its worktree are left intact.`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const auth = shell('gh', ['auth', 'status'], worktreePath);
|
|
96
|
+
if (auth.status !== 0) {
|
|
97
|
+
return {
|
|
98
|
+
ok: false,
|
|
99
|
+
reason: 'gh-unauthenticated',
|
|
100
|
+
message: 'GitHub CLI (gh) is not authenticated, so no pull request was raised. ' +
|
|
101
|
+
'Run "gh auth login" and re-run, or open a PR manually — the branch ' +
|
|
102
|
+
`"${branchName}" and its worktree are left intact.`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const push = shell('git', ['push', '-u', 'origin', branchName], worktreePath);
|
|
106
|
+
if (push.status !== 0) {
|
|
107
|
+
return {
|
|
108
|
+
ok: false,
|
|
109
|
+
reason: 'push-failed',
|
|
110
|
+
message: `Pushing branch "${branchName}" failed, so no pull request was raised: ` +
|
|
111
|
+
`${push.stderr.trim() || 'unknown error'}. The branch and its worktree ` +
|
|
112
|
+
'are left intact for a manual push/PR.',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const create = shell('gh', ['pr', 'create', '--title', title, '--body', body, '--head', branchName], worktreePath);
|
|
116
|
+
if (create.status !== 0) {
|
|
117
|
+
return {
|
|
118
|
+
ok: false,
|
|
119
|
+
reason: 'create-failed',
|
|
120
|
+
message: `"gh pr create" failed, so no pull request was raised: ` +
|
|
121
|
+
`${create.stderr.trim() || 'unknown error'}. The branch "${branchName}" ` +
|
|
122
|
+
'was pushed and its worktree is intact — open the PR manually.',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return { ok: true, url: extractUrl(create.stdout) };
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=pull-request.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pull-request.js","sourceRoot":"","sources":["../../src/jira/pull-request.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JIRA issue → SpecShip spec markdown (REQ-JIRA-004).
|
|
3
|
+
*
|
|
4
|
+
* A "pick" turns a fetched issue into a well-formed, low-ceremony spec:
|
|
5
|
+
* the summary becomes the title, the description becomes an RFC-2119
|
|
6
|
+
* requirement body, and each subtask becomes an acceptance bullet. The
|
|
7
|
+
* generated markdown mirrors the `MarkdownSpecExtractor` contract exactly
|
|
8
|
+
* (frontmatter, `<!-- id: -->` markers, `## Acceptance` container, id-marked
|
|
9
|
+
* bullets) so it indexes with zero `spec_missing_id`/error results (A2).
|
|
10
|
+
*
|
|
11
|
+
* SECURITY: the issue summary / description / subtask summaries are UNTRUSTED
|
|
12
|
+
* content being embedded into a spec file. A description that contains a
|
|
13
|
+
* literal `<!-- id: REQ-EVIL-001 -->` marker or a `## Acceptance` / `# ` heading
|
|
14
|
+
* must NOT be able to inject spec structure that corrupts the generated spec or,
|
|
15
|
+
* on index, collides with another spec. `neutralizeLine` defuses those
|
|
16
|
+
* sequences so the injected markers index as inert prose. This module is pure —
|
|
17
|
+
* no I/O — and never handles or echoes any credential (REQ-JIRA-009).
|
|
18
|
+
*/
|
|
19
|
+
import type { JiraIssue } from './types';
|
|
20
|
+
/**
|
|
21
|
+
* Derive a stable, collision-safe requirement id from the FULL issue key
|
|
22
|
+
* (unique within the connected JIRA). `PROJ-123` → `REQ-PROJ-123`. The key is
|
|
23
|
+
* upper-cased and sanitized to `[A-Z0-9-]` so it is safe both as an id marker
|
|
24
|
+
* and as a filename slug. Drives the `<!-- id: -->` marker AND idempotency.
|
|
25
|
+
*/
|
|
26
|
+
export declare function reqIdForIssue(key: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Generate a full, well-formed spec document from a JIRA issue. The frontmatter
|
|
29
|
+
* records the source key (`jira_issue:` — the A1 link + the idempotency key);
|
|
30
|
+
* the H1 carries the requirement id marker; the `## Acceptance` container holds
|
|
31
|
+
* one id-marked bullet per subtask.
|
|
32
|
+
*/
|
|
33
|
+
export declare function generateSpecMarkdown(issue: JiraIssue): string;
|
|
34
|
+
//# sourceMappingURL=spec-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec-generator.d.ts","sourceRoot":"","sources":["../../src/jira/spec-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAUzC;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOjD;AAyED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAuB7D"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* JIRA issue → SpecShip spec markdown (REQ-JIRA-004).
|
|
4
|
+
*
|
|
5
|
+
* A "pick" turns a fetched issue into a well-formed, low-ceremony spec:
|
|
6
|
+
* the summary becomes the title, the description becomes an RFC-2119
|
|
7
|
+
* requirement body, and each subtask becomes an acceptance bullet. The
|
|
8
|
+
* generated markdown mirrors the `MarkdownSpecExtractor` contract exactly
|
|
9
|
+
* (frontmatter, `<!-- id: -->` markers, `## Acceptance` container, id-marked
|
|
10
|
+
* bullets) so it indexes with zero `spec_missing_id`/error results (A2).
|
|
11
|
+
*
|
|
12
|
+
* SECURITY: the issue summary / description / subtask summaries are UNTRUSTED
|
|
13
|
+
* content being embedded into a spec file. A description that contains a
|
|
14
|
+
* literal `<!-- id: REQ-EVIL-001 -->` marker or a `## Acceptance` / `# ` heading
|
|
15
|
+
* must NOT be able to inject spec structure that corrupts the generated spec or,
|
|
16
|
+
* on index, collides with another spec. `neutralizeLine` defuses those
|
|
17
|
+
* sequences so the injected markers index as inert prose. This module is pure —
|
|
18
|
+
* no I/O — and never handles or echoes any credential (REQ-JIRA-009).
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.reqIdForIssue = reqIdForIssue;
|
|
22
|
+
exports.generateSpecMarkdown = generateSpecMarkdown;
|
|
23
|
+
/**
|
|
24
|
+
* RFC-2119 keyword set. If the derived body already carries one of these, we
|
|
25
|
+
* keep it as-is; otherwise we prefix a MUST sentence so the requirement is
|
|
26
|
+
* always normative (A2 — the pipeline expects RFC-2119 keywords).
|
|
27
|
+
*/
|
|
28
|
+
const RFC2119 = /\b(MUST|MUST NOT|SHALL|SHALL NOT|SHOULD|SHOULD NOT|REQUIRED|MAY)\b/;
|
|
29
|
+
/**
|
|
30
|
+
* Derive a stable, collision-safe requirement id from the FULL issue key
|
|
31
|
+
* (unique within the connected JIRA). `PROJ-123` → `REQ-PROJ-123`. The key is
|
|
32
|
+
* upper-cased and sanitized to `[A-Z0-9-]` so it is safe both as an id marker
|
|
33
|
+
* and as a filename slug. Drives the `<!-- id: -->` marker AND idempotency.
|
|
34
|
+
*/
|
|
35
|
+
function reqIdForIssue(key) {
|
|
36
|
+
const sanitized = key
|
|
37
|
+
.toUpperCase()
|
|
38
|
+
.replace(/[^A-Z0-9-]/g, '-')
|
|
39
|
+
.replace(/-+/g, '-')
|
|
40
|
+
.replace(/^-|-$/g, '');
|
|
41
|
+
return `REQ-${sanitized || 'JIRA'}`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Neutralize a single line of UNTRUSTED issue text so it can't inject spec
|
|
45
|
+
* structure when embedded in the generated markdown:
|
|
46
|
+
* - break HTML-comment markers (`<!-- id: … -->`) so they never match the
|
|
47
|
+
* extractor's `ID_COMMENT`, which would otherwise strand or re-parent nodes;
|
|
48
|
+
* - escape a leading heading run (`#`..`######`) so it never opens a new
|
|
49
|
+
* id-less section (which raises `spec_missing_id`) or a second `## Acceptance`
|
|
50
|
+
* container.
|
|
51
|
+
* The line stays human-readable — the injected markers render as literal text.
|
|
52
|
+
*/
|
|
53
|
+
function neutralizeLine(line) {
|
|
54
|
+
let out = line.replace(/<!--/g, '<!--').replace(/-->/g, '-->');
|
|
55
|
+
// Escape a leading heading marker: `## Acceptance` → `\## Acceptance`.
|
|
56
|
+
out = out.replace(/^(\s*)(#{1,6})(\s+)/, '$1\\$2$3');
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
/** Neutralize a multi-line block line-by-line, preserving paragraph breaks. */
|
|
60
|
+
function neutralizeBlock(text) {
|
|
61
|
+
return text.split(/\r?\n/).map(neutralizeLine).join('\n').trim();
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Collapse UNTRUSTED text to a single neutralized line — for the H1 title and
|
|
65
|
+
* acceptance bullets, which must stay on one line. Newlines are folded to spaces
|
|
66
|
+
* (so a multi-line summary can't spill into the next markdown construct) and the
|
|
67
|
+
* result is neutralized. Returns `fallback` when the text is empty.
|
|
68
|
+
*/
|
|
69
|
+
function neutralizeInline(text, fallback) {
|
|
70
|
+
const collapsed = text.replace(/\s+/g, ' ').trim();
|
|
71
|
+
const safe = neutralizeLine(collapsed).trim();
|
|
72
|
+
return safe || fallback;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Derive the requirement body from the issue. If the (neutralized) description
|
|
76
|
+
* already carries an RFC-2119 keyword we use it verbatim; otherwise we prefix a
|
|
77
|
+
* MUST sentence built from the summary so the body is always normative and never
|
|
78
|
+
* empty (A2).
|
|
79
|
+
*/
|
|
80
|
+
function deriveBody(issue) {
|
|
81
|
+
const desc = neutralizeBlock(issue.description?.trim() ?? '');
|
|
82
|
+
if (desc && RFC2119.test(desc))
|
|
83
|
+
return desc;
|
|
84
|
+
const summary = neutralizeInline(issue.summary ?? '', issue.key);
|
|
85
|
+
const must = `The implementation MUST satisfy JIRA issue ${issue.key}: ${summary}.`;
|
|
86
|
+
return desc ? `${must}\n\n${desc}` : must;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Derive acceptance bullets — one per subtask, or a single derived bullet when
|
|
90
|
+
* the issue has none (never zero — A2). Each bullet id is `${reqId}.A<n>` so the
|
|
91
|
+
* extractor parents it to the requirement by id suffix.
|
|
92
|
+
*/
|
|
93
|
+
function deriveAcceptance(issue) {
|
|
94
|
+
const reqId = reqIdForIssue(issue.key);
|
|
95
|
+
const subs = issue.subtasks ?? [];
|
|
96
|
+
if (subs.length > 0) {
|
|
97
|
+
return subs.map((st, i) => ({
|
|
98
|
+
id: `${reqId}.A${i + 1}`,
|
|
99
|
+
text: neutralizeInline(st.summary ?? '', st.key || `Subtask ${i + 1}`),
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
return [
|
|
103
|
+
{
|
|
104
|
+
id: `${reqId}.A1`,
|
|
105
|
+
text: `The behaviour described by ${issue.key} is implemented and verified against the issue.`,
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Generate a full, well-formed spec document from a JIRA issue. The frontmatter
|
|
111
|
+
* records the source key (`jira_issue:` — the A1 link + the idempotency key);
|
|
112
|
+
* the H1 carries the requirement id marker; the `## Acceptance` container holds
|
|
113
|
+
* one id-marked bullet per subtask.
|
|
114
|
+
*/
|
|
115
|
+
function generateSpecMarkdown(issue) {
|
|
116
|
+
const reqId = reqIdForIssue(issue.key);
|
|
117
|
+
const title = neutralizeInline(issue.summary ?? '', issue.key);
|
|
118
|
+
const body = deriveBody(issue);
|
|
119
|
+
const acceptance = deriveAcceptance(issue);
|
|
120
|
+
const lines = [
|
|
121
|
+
'---',
|
|
122
|
+
'format: markdown',
|
|
123
|
+
`jira_issue: ${issue.key}`,
|
|
124
|
+
'---',
|
|
125
|
+
`<!-- id: ${reqId} -->`,
|
|
126
|
+
`# ${title}`,
|
|
127
|
+
'',
|
|
128
|
+
body,
|
|
129
|
+
'',
|
|
130
|
+
'## Acceptance',
|
|
131
|
+
];
|
|
132
|
+
for (const bullet of acceptance) {
|
|
133
|
+
lines.push(`<!-- id: ${bullet.id} -->`);
|
|
134
|
+
lines.push(`- ${bullet.text}`);
|
|
135
|
+
}
|
|
136
|
+
return lines.join('\n') + '\n';
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=spec-generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec-generator.js","sourceRoot":"","sources":["../../src/jira/spec-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JIRA issue → spec file on disk (REQ-JIRA-004).
|
|
3
|
+
*
|
|
4
|
+
* `writeSpecFromIssue` resolves the project's `specs/` directory, generates the
|
|
5
|
+
* spec markdown from the issue, and writes it idempotently (A3). Idempotency is
|
|
6
|
+
* keyed on the frontmatter `jira_issue:` value — parsed from a real frontmatter
|
|
7
|
+
* block, NOT grepped from the body (which could match the key in prose) — so a
|
|
8
|
+
* re-pick of the same issue overwrites the existing file even if it was renamed,
|
|
9
|
+
* and never leaves a duplicate. Pure filesystem side of the feature; it handles
|
|
10
|
+
* no credential (REQ-JIRA-009).
|
|
11
|
+
*/
|
|
12
|
+
import type { JiraIssue } from './types';
|
|
13
|
+
export interface WriteSpecResult {
|
|
14
|
+
/** Absolute path of the written spec file. */
|
|
15
|
+
path: string;
|
|
16
|
+
/** True when a new file was created; false when an existing one was updated. */
|
|
17
|
+
created: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Find the spec file (if any) whose frontmatter records `issueKey`, scanning
|
|
21
|
+
* `<projectRoot>/specs/`. Returns the absolute path, or `null` when no spec for
|
|
22
|
+
* the key exists. Keyed on the parsed `jira_issue:` frontmatter value — never a
|
|
23
|
+
* body match — so it locates the exact file a pick wrote even if it was renamed.
|
|
24
|
+
* Shared by `writeSpecFromIssue` (idempotent overwrite) and `specship_jira_start`
|
|
25
|
+
* (does a spec for this key exist yet?).
|
|
26
|
+
*/
|
|
27
|
+
export declare function findSpecForIssueKey(issueKey: string, projectRoot: string): string | null;
|
|
28
|
+
/**
|
|
29
|
+
* Generate and write a spec for `issue` under `<projectRoot>/specs/`, creating
|
|
30
|
+
* the directory if needed. If an existing spec already records this issue key in
|
|
31
|
+
* its frontmatter, that file is overwritten (keyed on the key, not the filename,
|
|
32
|
+
* so a renamed file still updates); otherwise a new `specs/jira-<slug>.md` is
|
|
33
|
+
* created.
|
|
34
|
+
*/
|
|
35
|
+
export declare function writeSpecFromIssue(issue: JiraIssue, projectRoot: string): WriteSpecResult;
|
|
36
|
+
//# sourceMappingURL=spec-writer.d.ts.map
|