@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.
Files changed (240) hide show
  1. package/lib/dist/bin/specship.js +180 -1
  2. package/lib/dist/bin/specship.js.map +1 -1
  3. package/lib/dist/jira/auth.d.ts +17 -0
  4. package/lib/dist/jira/auth.d.ts.map +1 -0
  5. package/lib/dist/jira/auth.js +33 -0
  6. package/lib/dist/jira/auth.js.map +1 -0
  7. package/lib/dist/jira/client.d.ts +120 -0
  8. package/lib/dist/jira/client.d.ts.map +1 -0
  9. package/lib/dist/jira/client.js +316 -0
  10. package/lib/dist/jira/client.js.map +1 -0
  11. package/lib/dist/jira/config.d.ts +45 -0
  12. package/lib/dist/jira/config.d.ts.map +1 -0
  13. package/lib/dist/jira/config.js +178 -0
  14. package/lib/dist/jira/config.js.map +1 -0
  15. package/lib/dist/jira/index.d.ts +8 -0
  16. package/lib/dist/jira/index.d.ts.map +1 -0
  17. package/lib/dist/jira/index.js +24 -0
  18. package/lib/dist/jira/index.js.map +1 -0
  19. package/lib/dist/jira/pull-request.d.ts +72 -0
  20. package/lib/dist/jira/pull-request.d.ts.map +1 -0
  21. package/lib/dist/jira/pull-request.js +127 -0
  22. package/lib/dist/jira/pull-request.js.map +1 -0
  23. package/lib/dist/jira/spec-generator.d.ts +34 -0
  24. package/lib/dist/jira/spec-generator.d.ts.map +1 -0
  25. package/lib/dist/jira/spec-generator.js +138 -0
  26. package/lib/dist/jira/spec-generator.js.map +1 -0
  27. package/lib/dist/jira/spec-writer.d.ts +36 -0
  28. package/lib/dist/jira/spec-writer.d.ts.map +1 -0
  29. package/lib/dist/jira/spec-writer.js +143 -0
  30. package/lib/dist/jira/spec-writer.js.map +1 -0
  31. package/lib/dist/jira/types.d.ts +174 -0
  32. package/lib/dist/jira/types.d.ts.map +1 -0
  33. package/lib/dist/jira/types.js +60 -0
  34. package/lib/dist/jira/types.js.map +1 -0
  35. package/lib/dist/mcp/jira-tools.d.ts +234 -0
  36. package/lib/dist/mcp/jira-tools.d.ts.map +1 -0
  37. package/lib/dist/mcp/jira-tools.js +787 -0
  38. package/lib/dist/mcp/jira-tools.js.map +1 -0
  39. package/lib/dist/mcp/server-instructions.d.ts +1 -1
  40. package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
  41. package/lib/dist/mcp/server-instructions.js +10 -0
  42. package/lib/dist/mcp/server-instructions.js.map +1 -1
  43. package/lib/dist/mcp/tools.d.ts.map +1 -1
  44. package/lib/dist/mcp/tools.js +32 -0
  45. package/lib/dist/mcp/tools.js.map +1 -1
  46. package/lib/dist/server/cli.js +1 -1
  47. package/lib/dist/server/routes/sse.js +1 -1
  48. package/lib/dist/server/server.js +1 -1
  49. package/lib/dist/statusline/index.d.ts +19 -0
  50. package/lib/dist/statusline/index.d.ts.map +1 -1
  51. package/lib/dist/statusline/index.js +96 -3
  52. package/lib/dist/statusline/index.js.map +1 -1
  53. package/lib/dist/statusline/render.d.ts +24 -0
  54. package/lib/dist/statusline/render.d.ts.map +1 -1
  55. package/lib/dist/statusline/render.js +26 -5
  56. package/lib/dist/statusline/render.js.map +1 -1
  57. package/lib/dist/workflows/executor.d.ts +25 -1
  58. package/lib/dist/workflows/executor.d.ts.map +1 -1
  59. package/lib/dist/workflows/executor.js +28 -3
  60. package/lib/dist/workflows/executor.js.map +1 -1
  61. package/lib/node_modules/.package-lock.json +11 -20
  62. package/lib/node_modules/ajv/node_modules/fast-uri/index.js +1 -1
  63. package/lib/node_modules/ajv/node_modules/fast-uri/package.json +1 -1
  64. package/lib/node_modules/ajv/node_modules/fast-uri/test/security.test.js +28 -0
  65. package/lib/node_modules/toad-cache/README.md +10 -9
  66. package/lib/node_modules/toad-cache/dist/toad-cache.cjs +139 -139
  67. package/lib/node_modules/toad-cache/dist/toad-cache.mjs +136 -140
  68. package/lib/node_modules/toad-cache/package.json +8 -8
  69. package/lib/node_modules/toad-cache/toad-cache.d.cts +20 -14
  70. package/lib/node_modules/toad-cache/toad-cache.d.ts +18 -14
  71. package/lib/node_modules/yaml/README.md +14 -31
  72. package/lib/node_modules/yaml/browser/dist/compose/compose-collection.js +3 -3
  73. package/lib/node_modules/yaml/browser/dist/compose/compose-doc.js +0 -1
  74. package/lib/node_modules/yaml/browser/dist/compose/compose-node.js +4 -21
  75. package/lib/node_modules/yaml/browser/dist/compose/compose-scalar.js +8 -14
  76. package/lib/node_modules/yaml/browser/dist/compose/composer.js +2 -4
  77. package/lib/node_modules/yaml/browser/dist/compose/resolve-block-map.js +0 -2
  78. package/lib/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +1 -3
  79. package/lib/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +2 -8
  80. package/lib/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +4 -6
  81. package/lib/node_modules/yaml/browser/dist/compose/resolve-props.js +5 -3
  82. package/lib/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +2 -1
  83. package/lib/node_modules/yaml/browser/dist/compose/util-map-includes.js +5 -1
  84. package/lib/node_modules/yaml/browser/dist/doc/Document.js +2 -3
  85. package/lib/node_modules/yaml/browser/dist/doc/anchors.js +2 -1
  86. package/lib/node_modules/yaml/browser/dist/doc/applyReviver.js +0 -1
  87. package/lib/node_modules/yaml/browser/dist/doc/createNode.js +2 -1
  88. package/lib/node_modules/yaml/browser/dist/errors.js +1 -1
  89. package/lib/node_modules/yaml/browser/dist/log.js +6 -1
  90. package/lib/node_modules/yaml/browser/dist/node_modules/tslib/tslib.es6.js +21 -0
  91. package/lib/node_modules/yaml/browser/dist/nodes/Alias.js +12 -27
  92. package/lib/node_modules/yaml/browser/dist/nodes/YAMLMap.js +1 -1
  93. package/lib/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +49 -8
  94. package/lib/node_modules/yaml/browser/dist/parse/lexer.js +24 -28
  95. package/lib/node_modules/yaml/browser/dist/parse/parser.js +8 -31
  96. package/lib/node_modules/yaml/browser/dist/public-api.js +0 -3
  97. package/lib/node_modules/yaml/browser/dist/schema/Schema.js +2 -1
  98. package/lib/node_modules/yaml/browser/dist/schema/json/schema.js +1 -1
  99. package/lib/node_modules/yaml/browser/dist/schema/tags.js +16 -29
  100. package/lib/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +13 -5
  101. package/lib/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +1 -1
  102. package/lib/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +0 -2
  103. package/lib/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +1 -1
  104. package/lib/node_modules/yaml/browser/dist/stringify/stringify.js +5 -10
  105. package/lib/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +3 -13
  106. package/lib/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +2 -3
  107. package/lib/node_modules/yaml/browser/dist/stringify/stringifyPair.js +1 -1
  108. package/lib/node_modules/yaml/browser/dist/stringify/stringifyString.js +16 -24
  109. package/lib/node_modules/yaml/browser/dist/util.js +1 -1
  110. package/lib/node_modules/yaml/dist/cli.d.ts +1 -1
  111. package/lib/node_modules/yaml/dist/cli.mjs +3 -5
  112. package/lib/node_modules/yaml/dist/compose/compose-collection.d.ts +4 -4
  113. package/lib/node_modules/yaml/dist/compose/compose-collection.js +3 -3
  114. package/lib/node_modules/yaml/dist/compose/compose-doc.d.ts +6 -6
  115. package/lib/node_modules/yaml/dist/compose/compose-doc.js +0 -1
  116. package/lib/node_modules/yaml/dist/compose/compose-node.d.ts +7 -8
  117. package/lib/node_modules/yaml/dist/compose/compose-node.js +4 -21
  118. package/lib/node_modules/yaml/dist/compose/compose-scalar.d.ts +4 -4
  119. package/lib/node_modules/yaml/dist/compose/compose-scalar.js +7 -13
  120. package/lib/node_modules/yaml/dist/compose/composer.d.ts +6 -7
  121. package/lib/node_modules/yaml/dist/compose/composer.js +3 -6
  122. package/lib/node_modules/yaml/dist/compose/resolve-block-map.d.ts +5 -5
  123. package/lib/node_modules/yaml/dist/compose/resolve-block-map.js +0 -2
  124. package/lib/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +5 -5
  125. package/lib/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +5 -5
  126. package/lib/node_modules/yaml/dist/compose/resolve-block-seq.js +1 -3
  127. package/lib/node_modules/yaml/dist/compose/resolve-end.d.ts +2 -2
  128. package/lib/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +7 -7
  129. package/lib/node_modules/yaml/dist/compose/resolve-flow-collection.js +2 -8
  130. package/lib/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +4 -4
  131. package/lib/node_modules/yaml/dist/compose/resolve-flow-scalar.js +4 -6
  132. package/lib/node_modules/yaml/dist/compose/resolve-props.d.ts +2 -2
  133. package/lib/node_modules/yaml/dist/compose/resolve-props.js +5 -3
  134. package/lib/node_modules/yaml/dist/compose/util-contains-newline.d.ts +1 -1
  135. package/lib/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +1 -1
  136. package/lib/node_modules/yaml/dist/compose/util-empty-scalar-position.js +2 -1
  137. package/lib/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +2 -2
  138. package/lib/node_modules/yaml/dist/compose/util-map-includes.d.ts +3 -3
  139. package/lib/node_modules/yaml/dist/compose/util-map-includes.js +5 -1
  140. package/lib/node_modules/yaml/dist/doc/Document.d.ts +11 -11
  141. package/lib/node_modules/yaml/dist/doc/Document.js +2 -3
  142. package/lib/node_modules/yaml/dist/doc/anchors.d.ts +2 -2
  143. package/lib/node_modules/yaml/dist/doc/anchors.js +2 -1
  144. package/lib/node_modules/yaml/dist/doc/applyReviver.js +0 -1
  145. package/lib/node_modules/yaml/dist/doc/createNode.d.ts +4 -4
  146. package/lib/node_modules/yaml/dist/doc/createNode.js +2 -1
  147. package/lib/node_modules/yaml/dist/doc/directives.d.ts +1 -1
  148. package/lib/node_modules/yaml/dist/errors.d.ts +1 -1
  149. package/lib/node_modules/yaml/dist/errors.js +1 -1
  150. package/lib/node_modules/yaml/dist/index.d.ts +18 -21
  151. package/lib/node_modules/yaml/dist/log.js +4 -4
  152. package/lib/node_modules/yaml/dist/nodes/Alias.d.ts +9 -10
  153. package/lib/node_modules/yaml/dist/nodes/Alias.js +11 -26
  154. package/lib/node_modules/yaml/dist/nodes/Collection.d.ts +4 -4
  155. package/lib/node_modules/yaml/dist/nodes/Node.d.ts +9 -16
  156. package/lib/node_modules/yaml/dist/nodes/Pair.d.ts +8 -9
  157. package/lib/node_modules/yaml/dist/nodes/Scalar.d.ts +4 -9
  158. package/lib/node_modules/yaml/dist/nodes/YAMLMap.d.ts +10 -10
  159. package/lib/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +10 -10
  160. package/lib/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +3 -3
  161. package/lib/node_modules/yaml/dist/nodes/addPairToJSMap.js +48 -7
  162. package/lib/node_modules/yaml/dist/nodes/identity.d.ts +7 -7
  163. package/lib/node_modules/yaml/dist/nodes/toJS.d.ts +2 -4
  164. package/lib/node_modules/yaml/dist/options.d.ts +10 -22
  165. package/lib/node_modules/yaml/dist/parse/cst-scalar.d.ts +4 -4
  166. package/lib/node_modules/yaml/dist/parse/cst-stringify.d.ts +1 -1
  167. package/lib/node_modules/yaml/dist/parse/cst-visit.d.ts +2 -2
  168. package/lib/node_modules/yaml/dist/parse/cst.d.ts +3 -4
  169. package/lib/node_modules/yaml/dist/parse/lexer.d.ts +1 -1
  170. package/lib/node_modules/yaml/dist/parse/lexer.js +24 -28
  171. package/lib/node_modules/yaml/dist/parse/parser.d.ts +4 -4
  172. package/lib/node_modules/yaml/dist/parse/parser.js +9 -33
  173. package/lib/node_modules/yaml/dist/public-api.d.ts +5 -6
  174. package/lib/node_modules/yaml/dist/public-api.js +0 -3
  175. package/lib/node_modules/yaml/dist/schema/Schema.d.ts +5 -4
  176. package/lib/node_modules/yaml/dist/schema/Schema.js +2 -1
  177. package/lib/node_modules/yaml/dist/schema/common/map.d.ts +1 -1
  178. package/lib/node_modules/yaml/dist/schema/common/null.d.ts +1 -1
  179. package/lib/node_modules/yaml/dist/schema/common/seq.d.ts +1 -1
  180. package/lib/node_modules/yaml/dist/schema/common/string.d.ts +1 -1
  181. package/lib/node_modules/yaml/dist/schema/core/bool.d.ts +1 -1
  182. package/lib/node_modules/yaml/dist/schema/core/float.d.ts +1 -1
  183. package/lib/node_modules/yaml/dist/schema/core/int.d.ts +1 -1
  184. package/lib/node_modules/yaml/dist/schema/core/schema.d.ts +1 -1
  185. package/lib/node_modules/yaml/dist/schema/json/schema.d.ts +1 -1
  186. package/lib/node_modules/yaml/dist/schema/json/schema.js +1 -1
  187. package/lib/node_modules/yaml/dist/schema/tags.d.ts +3 -11
  188. package/lib/node_modules/yaml/dist/schema/tags.js +13 -26
  189. package/lib/node_modules/yaml/dist/schema/types.d.ts +12 -14
  190. package/lib/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +1 -1
  191. package/lib/node_modules/yaml/dist/schema/yaml-1.1/binary.js +7 -9
  192. package/lib/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +1 -1
  193. package/lib/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +1 -1
  194. package/lib/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +1 -1
  195. package/lib/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +17 -11
  196. package/lib/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +7 -7
  197. package/lib/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +1 -1
  198. package/lib/node_modules/yaml/dist/schema/yaml-1.1/schema.js +0 -2
  199. package/lib/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +9 -9
  200. package/lib/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +1 -1
  201. package/lib/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +1 -1
  202. package/lib/node_modules/yaml/dist/stringify/stringify.d.ts +3 -3
  203. package/lib/node_modules/yaml/dist/stringify/stringify.js +5 -10
  204. package/lib/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +2 -2
  205. package/lib/node_modules/yaml/dist/stringify/stringifyCollection.js +3 -13
  206. package/lib/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +3 -3
  207. package/lib/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +1 -1
  208. package/lib/node_modules/yaml/dist/stringify/stringifyNumber.js +2 -3
  209. package/lib/node_modules/yaml/dist/stringify/stringifyPair.d.ts +2 -2
  210. package/lib/node_modules/yaml/dist/stringify/stringifyPair.js +1 -1
  211. package/lib/node_modules/yaml/dist/stringify/stringifyString.d.ts +2 -2
  212. package/lib/node_modules/yaml/dist/stringify/stringifyString.js +15 -23
  213. package/lib/node_modules/yaml/dist/util.d.ts +12 -16
  214. package/lib/node_modules/yaml/dist/util.js +2 -2
  215. package/lib/node_modules/yaml/dist/visit.d.ts +7 -7
  216. package/lib/node_modules/yaml/package.json +14 -13
  217. package/lib/package.json +5 -6
  218. package/package.json +1 -1
  219. package/lib/node_modules/jsonc-parser/CHANGELOG.md +0 -76
  220. package/lib/node_modules/jsonc-parser/LICENSE.md +0 -21
  221. package/lib/node_modules/jsonc-parser/README.md +0 -364
  222. package/lib/node_modules/jsonc-parser/SECURITY.md +0 -41
  223. package/lib/node_modules/jsonc-parser/lib/esm/impl/edit.js +0 -185
  224. package/lib/node_modules/jsonc-parser/lib/esm/impl/format.js +0 -261
  225. package/lib/node_modules/jsonc-parser/lib/esm/impl/parser.js +0 -659
  226. package/lib/node_modules/jsonc-parser/lib/esm/impl/scanner.js +0 -443
  227. package/lib/node_modules/jsonc-parser/lib/esm/impl/string-intern.js +0 -29
  228. package/lib/node_modules/jsonc-parser/lib/esm/main.d.ts +0 -351
  229. package/lib/node_modules/jsonc-parser/lib/esm/main.js +0 -178
  230. package/lib/node_modules/jsonc-parser/lib/umd/impl/edit.js +0 -201
  231. package/lib/node_modules/jsonc-parser/lib/umd/impl/format.js +0 -275
  232. package/lib/node_modules/jsonc-parser/lib/umd/impl/parser.js +0 -682
  233. package/lib/node_modules/jsonc-parser/lib/umd/impl/scanner.js +0 -456
  234. package/lib/node_modules/jsonc-parser/lib/umd/impl/string-intern.js +0 -42
  235. package/lib/node_modules/jsonc-parser/lib/umd/main.d.ts +0 -351
  236. package/lib/node_modules/jsonc-parser/lib/umd/main.js +0 -194
  237. package/lib/node_modules/jsonc-parser/package.json +0 -37
  238. package/lib/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +0 -67
  239. package/lib/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +0 -9
  240. package/lib/node_modules/yaml/dist/schema/yaml-1.1/merge.js +0 -71
@@ -0,0 +1,787 @@
1
+ "use strict";
2
+ /**
3
+ * JIRA MCP tools (REQ-JIRA-002, REQ-JIRA-003, REQ-JIRA-004).
4
+ *
5
+ * `specship_jira_issues` lists the issues assigned to the authenticated user
6
+ * (identity resolved from the token, never a typed name), optionally narrowed
7
+ * to a project. `specship_jira_issue` fetches a single issue by key with its
8
+ * full detail (description + subtasks). `specship_jira_pick` fetches an issue
9
+ * and authors a well-formed SpecShip spec from it under `specs/` (idempotent on
10
+ * the issue key). All read the user-level config, resolve credentials, and
11
+ * drive `JiraClient`.
12
+ *
13
+ * SECURITY (REQ-JIRA-009): the token is never surfaced. Every error path
14
+ * returns only the JiraError's own message — which by construction contains
15
+ * no credential — and the "not configured" path returns a plain pointer to
16
+ * `specship jira configure`, never a partial or fabricated result.
17
+ */
18
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ var desc = Object.getOwnPropertyDescriptor(m, k);
21
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
22
+ desc = { enumerable: true, get: function() { return m[k]; } };
23
+ }
24
+ Object.defineProperty(o, k2, desc);
25
+ }) : (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ o[k2] = m[k];
28
+ }));
29
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
30
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
31
+ }) : function(o, v) {
32
+ o["default"] = v;
33
+ });
34
+ var __importStar = (this && this.__importStar) || (function () {
35
+ var ownKeys = function(o) {
36
+ ownKeys = Object.getOwnPropertyNames || function (o) {
37
+ var ar = [];
38
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
39
+ return ar;
40
+ };
41
+ return ownKeys(o);
42
+ };
43
+ return function (mod) {
44
+ if (mod && mod.__esModule) return mod;
45
+ var result = {};
46
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
47
+ __setModuleDefault(result, mod);
48
+ return result;
49
+ };
50
+ })();
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.jiraToolDefinitions = void 0;
53
+ exports.handleSpecshipJiraIssues = handleSpecshipJiraIssues;
54
+ exports.handleSpecshipJiraIssue = handleSpecshipJiraIssue;
55
+ exports.handleSpecshipJiraPick = handleSpecshipJiraPick;
56
+ exports.handleJiraRunCompletion = handleJiraRunCompletion;
57
+ exports.handleSpecshipJiraStart = handleSpecshipJiraStart;
58
+ exports.deriveWorkState = deriveWorkState;
59
+ exports.handleSpecshipJiraTrack = handleSpecshipJiraTrack;
60
+ const fs = __importStar(require("fs"));
61
+ const path = __importStar(require("path"));
62
+ const crypto_1 = require("crypto");
63
+ const config_1 = require("../jira/config");
64
+ const client_1 = require("../jira/client");
65
+ const types_1 = require("../jira/types");
66
+ const spec_writer_1 = require("../jira/spec-writer");
67
+ const spec_generator_1 = require("../jira/spec-generator");
68
+ const pull_request_1 = require("../jira/pull-request");
69
+ function textResult(text) {
70
+ return { content: [{ type: 'text', text }] };
71
+ }
72
+ function errorResult(message) {
73
+ return { content: [{ type: 'text', text: message }], isError: true };
74
+ }
75
+ /**
76
+ * Factory seam that resolves credentials and builds a live `JiraClient` for the
77
+ * status-push paths. The default resolves the user-level config; tests pass a
78
+ * `makeJiraClient` dep returning a fake so no host is contacted. Throws only if
79
+ * credentials are unresolvable — callers wrap it and degrade to a note, never a
80
+ * blocked workflow (REQ-JIRA-007, degrade-gracefully).
81
+ */
82
+ function defaultMakeJiraClient() {
83
+ const creds = (0, config_1.resolveJiraCredentials)();
84
+ return {
85
+ client: new client_1.JiraClient(creds),
86
+ transitions: creds.transitions ?? {},
87
+ };
88
+ }
89
+ /**
90
+ * Push the "start" status to JIRA (REQ-JIRA-007.A1): assign the issue to the
91
+ * authenticated user and transition it toward "in progress". Returns a short
92
+ * human note describing what happened (assigned, moved, or skipped) — or `null`
93
+ * when there's nothing to say. NEVER throws: a JIRA hiccup (auth, network, a
94
+ * missing transition) must not block the local workflow from starting — the
95
+ * note simply reports the skip (reinforcement 4).
96
+ */
97
+ async function pushJiraStartStatus(key, make) {
98
+ let ctx;
99
+ try {
100
+ ctx = make();
101
+ }
102
+ catch (err) {
103
+ return `Note: couldn't push start status to JIRA — ${errMsg(err)}`;
104
+ }
105
+ const notes = [];
106
+ // Assign to the authenticated user (identity resolved from the token).
107
+ try {
108
+ const conn = await ctx.client.testConnection();
109
+ if (conn.accountId) {
110
+ await ctx.client.assignIssue(key, conn.accountId);
111
+ notes.push(`assigned ${key} to you`);
112
+ }
113
+ }
114
+ catch (err) {
115
+ notes.push(`couldn't assign ${key} (${errMsg(err)})`);
116
+ }
117
+ // Transition toward "in progress" — a missing transition is a skip, not a
118
+ // failure (A3), and never blocks the run.
119
+ try {
120
+ const target = ctx.transitions.inProgress ?? 'In Progress';
121
+ const res = await ctx.client.transitionIssue(key, target);
122
+ if ('transitioned' in res) {
123
+ notes.push(`moved ${key} to "${res.transitioned}"`);
124
+ }
125
+ else {
126
+ notes.push(`didn't transition ${key} — ${res.reason}`);
127
+ }
128
+ }
129
+ catch (err) {
130
+ notes.push(`couldn't transition ${key} (${errMsg(err)})`);
131
+ }
132
+ return notes.length ? `JIRA: ${notes.join('; ')}.` : null;
133
+ }
134
+ /**
135
+ * Push the "PR raised" status to JIRA (REQ-JIRA-007.A2): transition the issue
136
+ * toward "in review" and comment the PR link on it. When the configured
137
+ * transition doesn't exist for this workflow, it still comments the PR link and
138
+ * reports the skip (A3) rather than erroring. Returns a human note. NEVER
139
+ * throws — a JIRA-side failure must not undo the raised PR.
140
+ *
141
+ * SECURITY: only the public PR URL + issue key are sent (REQ-JIRA-009).
142
+ */
143
+ async function pushJiraReviewStatus(key, prUrl, make) {
144
+ let ctx;
145
+ try {
146
+ ctx = make();
147
+ }
148
+ catch (err) {
149
+ return `Note: couldn't push review status to JIRA — ${errMsg(err)}`;
150
+ }
151
+ const notes = [];
152
+ try {
153
+ const target = ctx.transitions.inReview ?? 'In Review';
154
+ const res = await ctx.client.transitionIssue(key, target);
155
+ if ('transitioned' in res) {
156
+ notes.push(`moved ${key} to "${res.transitioned}"`);
157
+ }
158
+ else {
159
+ // A3: the transition is unavailable — still comment, report the skip.
160
+ notes.push(`didn't transition ${key} — ${res.reason}`);
161
+ }
162
+ }
163
+ catch (err) {
164
+ notes.push(`couldn't transition ${key} (${errMsg(err)})`);
165
+ }
166
+ // The PR-link comment is posted regardless of the transition outcome (A3).
167
+ try {
168
+ await ctx.client.addComment(key, `SpecShip raised a pull request: ${prUrl}`);
169
+ notes.push(`commented the PR link on ${key}`);
170
+ }
171
+ catch (err) {
172
+ notes.push(`couldn't comment the PR link on ${key} (${errMsg(err)})`);
173
+ }
174
+ return notes.length ? `JIRA: ${notes.join('; ')}.` : null;
175
+ }
176
+ /** Extract a credential-free message from an unknown error (REQ-JIRA-009). */
177
+ function errMsg(err) {
178
+ return err instanceof Error ? err.message : String(err);
179
+ }
180
+ exports.jiraToolDefinitions = [
181
+ {
182
+ name: 'specship_jira_issues',
183
+ description: 'List the JIRA issues assigned to you (resolved from your configured ' +
184
+ 'token — you never type your own name). Optionally narrow to a project.',
185
+ inputSchema: {
186
+ type: 'object',
187
+ properties: {
188
+ project: {
189
+ type: 'string',
190
+ description: 'Optional project key to narrow the list (e.g., "PROJ"). Omit to list all your assigned issues.',
191
+ },
192
+ },
193
+ },
194
+ },
195
+ {
196
+ name: 'specship_jira_issue',
197
+ description: 'Fetch a single JIRA issue by key (e.g., "PROJ-123") with its full ' +
198
+ 'detail — summary, status, type, description, and any subtasks.',
199
+ inputSchema: {
200
+ type: 'object',
201
+ properties: {
202
+ key: {
203
+ type: 'string',
204
+ description: 'The issue key to fetch, e.g., "PROJ-123". Required.',
205
+ },
206
+ },
207
+ required: ['key'],
208
+ },
209
+ },
210
+ {
211
+ name: 'specship_jira_pick',
212
+ description: 'Pick a JIRA issue by key (e.g., "PROJ-123") and author a SpecShip spec ' +
213
+ 'from it under specs/ — summary becomes the title, description the ' +
214
+ 'requirement body, subtasks the acceptance criteria. Re-picking the same ' +
215
+ 'issue updates its spec in place rather than creating a duplicate.',
216
+ inputSchema: {
217
+ type: 'object',
218
+ properties: {
219
+ key: {
220
+ type: 'string',
221
+ description: 'The issue key to pick, e.g., "PROJ-123". Required.',
222
+ },
223
+ },
224
+ required: ['key'],
225
+ },
226
+ },
227
+ {
228
+ name: 'specship_jira_start',
229
+ description: 'Start implementing a picked JIRA issue: run the bundled spec-implement ' +
230
+ 'workflow on the spec that specship_jira_pick authored for the key, in an ' +
231
+ 'isolated worktree. Runs to the plan/approve gate and stops there — review ' +
232
+ 'the plan, then approve to proceed. Pick the issue first if no spec exists.',
233
+ inputSchema: {
234
+ type: 'object',
235
+ properties: {
236
+ key: {
237
+ type: 'string',
238
+ description: 'The issue key to start, e.g., "PROJ-123". Required. A spec for it ' +
239
+ 'must already exist (run specship_jira_pick first).',
240
+ },
241
+ },
242
+ required: ['key'],
243
+ },
244
+ },
245
+ {
246
+ name: 'specship_jira_track',
247
+ description: 'Show a read-only tracking view of every JIRA issue you have brought into ' +
248
+ "SpecShip: each row joins the issue's SpecShip work-state (spec authored → " +
249
+ 'implementing → PR raised → verified) with its LIVE JIRA status (a fresh ' +
250
+ 'read at track time, so an issue moved outside SpecShip reflects its current ' +
251
+ 'status). Read-only — it never re-picks or re-starts anything. Optionally ' +
252
+ 'narrow the JIRA read to a project.',
253
+ inputSchema: {
254
+ type: 'object',
255
+ properties: {
256
+ project: {
257
+ type: 'string',
258
+ description: 'Optional project key to narrow the live JIRA read (e.g., "PROJ"). Omit to read all your assigned issues.',
259
+ },
260
+ },
261
+ },
262
+ },
263
+ ];
264
+ /** Render the issue list as compact markdown, or an explicit empty line. */
265
+ function formatIssues(issues) {
266
+ if (issues.length === 0) {
267
+ return 'No issues assigned to you.';
268
+ }
269
+ const lines = issues.map(i => `- **${i.key}** — ${i.summary} — ${i.status} — ${i.issueType}`);
270
+ return `Issues assigned to you (${issues.length}):\n\n${lines.join('\n')}`;
271
+ }
272
+ /** Render a single issue's full detail as markdown. */
273
+ function formatIssue(issue) {
274
+ const parts = [
275
+ `## ${issue.key} — ${issue.summary}`,
276
+ '',
277
+ `- **Status:** ${issue.status}`,
278
+ `- **Type:** ${issue.issueType}`,
279
+ ];
280
+ const description = issue.description?.trim();
281
+ parts.push('', '### Description', '', description || '_No description._');
282
+ if (issue.subtasks && issue.subtasks.length > 0) {
283
+ parts.push('', `### Subtasks (${issue.subtasks.length})`, '');
284
+ for (const st of issue.subtasks) {
285
+ parts.push(`- **${st.key}** — ${st.summary} — ${st.status}`);
286
+ }
287
+ }
288
+ return parts.join('\n');
289
+ }
290
+ /**
291
+ * The shared "not configured → clear pointer" gate for the JIRA tools. Returns
292
+ * a pointer `ToolResult` when neither a config file nor env credentials are
293
+ * present, or `null` when configured (the caller proceeds). Never an error
294
+ * stack or a fabricated result.
295
+ */
296
+ function notConfiguredResult() {
297
+ let hasConfig = false;
298
+ try {
299
+ hasConfig = (0, config_1.loadJiraConfig)() !== null;
300
+ }
301
+ catch {
302
+ // A malformed config file still means "configured but broken" — fall
303
+ // through to resolveJiraCredentials, which surfaces the actionable message.
304
+ hasConfig = true;
305
+ }
306
+ const hasEnv = Boolean(process.env.SPECSHIP_JIRA_BASE_URL);
307
+ if (!hasConfig && !hasEnv) {
308
+ return textResult('JIRA is not configured. Run "specship jira configure" to connect a ' +
309
+ 'JIRA Cloud or Data Center instance, then try again.');
310
+ }
311
+ return null;
312
+ }
313
+ /**
314
+ * Handle `specship_jira_issues`. Independent of the code graph — talks only
315
+ * to the configured JIRA host through the stored credentials.
316
+ */
317
+ async function handleSpecshipJiraIssues(args) {
318
+ // Not configured → a clear pointer, never an error stack or a fabricated list.
319
+ const notConfigured = notConfiguredResult();
320
+ if (notConfigured)
321
+ return notConfigured;
322
+ const project = typeof args.project === 'string' && args.project.trim()
323
+ ? args.project.trim()
324
+ : undefined;
325
+ try {
326
+ const creds = (0, config_1.resolveJiraCredentials)();
327
+ const client = new client_1.JiraClient(creds);
328
+ const result = await client.listMyIssues({ project });
329
+ return textResult(formatIssues(result.issues));
330
+ }
331
+ catch (err) {
332
+ // JiraError messages are credential-free by construction (REQ-JIRA-009).
333
+ if (err instanceof types_1.JiraError) {
334
+ return errorResult(err.message);
335
+ }
336
+ // Defensive: never let an unexpected error leak internals.
337
+ return errorResult('Failed to list JIRA issues.');
338
+ }
339
+ }
340
+ /**
341
+ * Handle `specship_jira_issue` (REQ-JIRA-003). Fetches a single issue by key
342
+ * and renders its full detail. Independent of the code graph — talks only to
343
+ * the configured JIRA host. A missing/no-access key surfaces as an explicit
344
+ * tool error (A2), never a silent empty result.
345
+ */
346
+ async function handleSpecshipJiraIssue(args) {
347
+ // Not configured → a clear pointer, never an error stack or a fabricated issue.
348
+ const notConfigured = notConfiguredResult();
349
+ if (notConfigured)
350
+ return notConfigured;
351
+ // A missing/blank key is a user mistake, not an internal fault — a clear
352
+ // message, not a stack.
353
+ const key = typeof args.key === 'string' && args.key.trim() ? args.key.trim() : undefined;
354
+ if (!key) {
355
+ return textResult('An issue key is required (e.g., "PROJ-123"). Pass it as the "key" argument.');
356
+ }
357
+ try {
358
+ const creds = (0, config_1.resolveJiraCredentials)();
359
+ const client = new client_1.JiraClient(creds);
360
+ const result = await client.getIssue(key);
361
+ return textResult(formatIssue(result.issue));
362
+ }
363
+ catch (err) {
364
+ // JiraError messages (incl. JiraNotFoundError for a missing/no-access key)
365
+ // are credential-free by construction (REQ-JIRA-009).
366
+ if (err instanceof types_1.JiraError) {
367
+ return errorResult(err.message);
368
+ }
369
+ // Defensive: never let an unexpected error leak internals.
370
+ return errorResult('Failed to fetch the JIRA issue.');
371
+ }
372
+ }
373
+ /**
374
+ * Handle `specship_jira_pick` (REQ-JIRA-004). Fetches a single issue by key
375
+ * (reusing the REQ-JIRA-003 path) and authors a well-formed SpecShip spec from
376
+ * it under `specs/`, idempotent on the issue key (A3). Independent of the code
377
+ * graph — talks only to the configured JIRA host. A missing/no-access key
378
+ * surfaces as a `JiraNotFoundError` → tool error, never a written empty spec.
379
+ *
380
+ * SECURITY: the issue summary/description/subtasks are untrusted content; the
381
+ * generator neutralizes any injected spec structure before embedding. No token
382
+ * appears in any output or error (REQ-JIRA-009).
383
+ */
384
+ async function handleSpecshipJiraPick(args) {
385
+ // Not configured → a clear pointer, never an error stack or a written spec.
386
+ const notConfigured = notConfiguredResult();
387
+ if (notConfigured)
388
+ return notConfigured;
389
+ // A missing/blank key is a user mistake, not an internal fault.
390
+ const key = typeof args.key === 'string' && args.key.trim() ? args.key.trim() : undefined;
391
+ if (!key) {
392
+ return textResult('An issue key is required (e.g., "PROJ-123"). Pass it as the "key" argument.');
393
+ }
394
+ // The project whose specs/ directory receives the spec. Threaded through the
395
+ // handler args like the other tools' context, falling back to the cwd.
396
+ const projectRoot = typeof args.projectPath === 'string' && args.projectPath.trim()
397
+ ? args.projectPath.trim()
398
+ : process.cwd();
399
+ try {
400
+ const creds = (0, config_1.resolveJiraCredentials)();
401
+ const client = new client_1.JiraClient(creds);
402
+ const result = await client.getIssue(key);
403
+ const written = (0, spec_writer_1.writeSpecFromIssue)(result.issue, projectRoot);
404
+ const verb = written.created ? 'Created' : 'Updated';
405
+ return textResult(`${verb} spec for ${result.issue.key} at ${written.path}. ` +
406
+ 'Run "specship sync" (or let the watcher pick it up) to index it, then ' +
407
+ `run specship_jira_start for ${result.issue.key} to implement it.`);
408
+ }
409
+ catch (err) {
410
+ // JiraError messages (incl. JiraNotFoundError for a missing/no-access key)
411
+ // are credential-free by construction (REQ-JIRA-009).
412
+ if (err instanceof types_1.JiraError) {
413
+ return errorResult(err.message);
414
+ }
415
+ // Defensive: never let an unexpected error leak internals.
416
+ return errorResult('Failed to pick the JIRA issue.');
417
+ }
418
+ }
419
+ /** Read `metadata.approval.message` off a run, tolerating any metadata shape. */
420
+ function approvalMessageOf(run) {
421
+ const approval = run.metadata
422
+ ?.approval;
423
+ return typeof approval?.message === 'string' ? approval.message : undefined;
424
+ }
425
+ /**
426
+ * Re-read the persisted run so we see the settled status + approval metadata.
427
+ * `executor.start` returns the pre-finalize in-memory run whose status still
428
+ * reads "running" even after it paused at the approval gate; the DB row carries
429
+ * the real "paused" status + the approval message. Falls back to the returned
430
+ * run when the handle can't look it up (e.g. a stubbed executor in tests).
431
+ */
432
+ function reloadRun(specQueries, run) {
433
+ const getById = specQueries?.getWorkflowRunById;
434
+ if (typeof getById !== 'function')
435
+ return run;
436
+ try {
437
+ return getById.call(specQueries, run.id) ?? run;
438
+ }
439
+ catch {
440
+ return run;
441
+ }
442
+ }
443
+ /** Read the H1 title out of a spec file, falling back to `fallback`. */
444
+ function readSpecTitle(specPath, fallback) {
445
+ if (!specPath)
446
+ return fallback;
447
+ try {
448
+ const content = fs.readFileSync(specPath, 'utf8');
449
+ for (const raw of content.split(/\r?\n/)) {
450
+ const line = raw.trim();
451
+ const m = line.match(/^#\s+(.+)$/);
452
+ if (m)
453
+ return m[1].trim() || fallback;
454
+ }
455
+ }
456
+ catch {
457
+ /* fall through to the fallback */
458
+ }
459
+ return fallback;
460
+ }
461
+ /** Recover the `jira` metadata block off a run, tolerating any metadata shape. */
462
+ function jiraMetaOf(run) {
463
+ const jira = run.metadata?.jira;
464
+ return jira && typeof jira === 'object' ? jira : undefined;
465
+ }
466
+ /**
467
+ * True when the run's verify leg RAN AND PASSED — the only state that raises a
468
+ * PR (REQ-JIRA-006.A1; aligns with REQ-JIRA-005.A2). A verify that FAILED halts
469
+ * the run before completion (status `failed`), and a SKIPPED verify
470
+ * (`VERIFY_RESULT=skipped`) completes the run but must NOT raise a PR — tests
471
+ * that did not run are never treated as a pass.
472
+ */
473
+ function verifyPassed(run) {
474
+ const meta = run.metadata ?? {};
475
+ const states = meta.nodeStates;
476
+ if (states && states.verify && states.verify !== 'completed')
477
+ return false;
478
+ const outputs = meta.outputs;
479
+ const verifyText = outputs?.verify?.text ?? '';
480
+ return /VERIFY_RESULT=ran-and-passed/.test(verifyText);
481
+ }
482
+ /**
483
+ * Completion hook for a JIRA-started run (REQ-JIRA-006). Fires only once the
484
+ * run has settled to `completed`. Raises a PR — traceable to the issue via the
485
+ * key in the branch name, PR title, AND body — ONLY when the run's verify leg
486
+ * ran and passed. On any PR-tooling failure it surfaces the reason and leaves
487
+ * the branch + worktree fully intact for a manual PR (A3); it NEVER auto-merges
488
+ * or closes the PR (the human owns "done"). A non-JIRA run is a silent no-op.
489
+ *
490
+ * SECURITY: no JIRA token/secret appears in the PR title/body/logs — only the
491
+ * public issue key (REQ-JIRA-009).
492
+ */
493
+ async function handleJiraRunCompletion(run, deps) {
494
+ const jira = jiraMetaOf(run);
495
+ const issueKey = jira?.issueKey;
496
+ if (!issueKey)
497
+ return null; // not a JIRA-started run — nothing to do.
498
+ const log = deps.log ?? ((m) => {
499
+ // eslint-disable-next-line no-console
500
+ console.log(m);
501
+ });
502
+ // A1: a completed-but-unverified run (verify skipped) raises NO PR.
503
+ if (!verifyPassed(run)) {
504
+ log(`Run for ${issueKey} completed but its verification did not pass — no ` +
505
+ 'pull request was raised.');
506
+ return null;
507
+ }
508
+ if (!run.isolationEnvId) {
509
+ log(`Run for ${issueKey} completed but has no worktree on record — no pull ` +
510
+ 'request was raised.');
511
+ return null;
512
+ }
513
+ const env = deps.getIsolationEnvById(run.isolationEnvId);
514
+ if (!env) {
515
+ log(`Run for ${issueKey} completed but its worktree could not be resolved — ` +
516
+ 'no pull request was raised.');
517
+ return null;
518
+ }
519
+ const meta = env.metadata ?? {};
520
+ const repoRoot = (typeof meta.repoRoot === 'string' ? meta.repoRoot : undefined) ??
521
+ deps.projectRoot ??
522
+ process.cwd();
523
+ const specTitle = jira.title ?? jira.specId ?? issueKey;
524
+ const raise = deps.raisePullRequest ?? pull_request_1.raisePullRequest;
525
+ const outcome = await raise({
526
+ repoRoot,
527
+ branchName: env.branchName,
528
+ worktreePath: env.workingPath,
529
+ issueKey,
530
+ title: (0, pull_request_1.buildPrTitle)(issueKey, specTitle),
531
+ body: (0, pull_request_1.buildPrBody)(issueKey, specTitle),
532
+ });
533
+ if (outcome.ok) {
534
+ log(`Raised pull request for ${issueKey}: ${outcome.url}`);
535
+ // A2: PR raised → transition toward "in review" and comment the PR link.
536
+ // Only on a raised PR — a failed raise never advances the issue (below),
537
+ // matching REQ-JIRA-005.A2 (leave it in-progress). Never throws.
538
+ const make = deps.makeJiraClient ?? defaultMakeJiraClient;
539
+ const note = await pushJiraReviewStatus(issueKey, outcome.url, make);
540
+ if (note)
541
+ log(note);
542
+ }
543
+ else {
544
+ // A3 (of REQ-JIRA-006) / A2 (of REQ-JIRA-005): report the reason; the
545
+ // branch + worktree are left intact upstream and the issue is NOT
546
+ // transitioned (it stays in-progress).
547
+ log(outcome.message);
548
+ }
549
+ return outcome;
550
+ }
551
+ /**
552
+ * Handle `specship_jira_start` (REQ-JIRA-005). Drives the bundled
553
+ * `spec-implement` workflow on the spec `specship_jira_pick` authored for the
554
+ * issue key, in an isolated worktree, and runs it to the first pause — the
555
+ * plan/approve gate (A1). It does NOT block for the full implementation.
556
+ *
557
+ * On the non-failed path it pushes the "start" status back to JIRA
558
+ * (REQ-JIRA-007.A1): assigns the issue and transitions it toward "in progress".
559
+ * That push NEVER blocks the workflow — a JIRA hiccup (auth, network, a missing
560
+ * transition) is surfaced as a note, not an error. A failed/rejected run
561
+ * (REQ-JIRA-005.A2) raises no PR and is never advanced past "in progress"; the
562
+ * "in review" transition + PR comment (REQ-JIRA-007.A2) fire only once the run
563
+ * reaches `completed` with a verified, raised PR.
564
+ *
565
+ * SECURITY: the credential is handled only inside `JiraClient` and never
566
+ * echoed (REQ-JIRA-009); only the public issue key / PR URL are surfaced.
567
+ */
568
+ async function handleSpecshipJiraStart(args, deps) {
569
+ // Not configured → a clear pointer, never an error stack or a started run.
570
+ const notConfigured = notConfiguredResult();
571
+ if (notConfigured)
572
+ return notConfigured;
573
+ // A missing/blank key is a user mistake, not an internal fault.
574
+ const key = typeof args.key === 'string' && args.key.trim() ? args.key.trim() : undefined;
575
+ if (!key) {
576
+ return textResult('An issue key is required (e.g., "PROJ-123"). Pass it as the "key" argument.');
577
+ }
578
+ const projectRoot = deps.projectRoot;
579
+ const specId = (0, spec_generator_1.reqIdForIssue)(key);
580
+ // The spec must already exist — start never silently authors one. If it's
581
+ // absent, point the caller at specship_jira_pick (which writes the spec keyed
582
+ // on this exact issue key, so start then targets the same SPEC_ID).
583
+ const specPath = (0, spec_writer_1.findSpecForIssueKey)(key, projectRoot);
584
+ if (!specPath) {
585
+ return textResult(`No spec for ${key} was found under specs/. Run specship_jira_pick for ` +
586
+ `${key} first to author it, then run specship_jira_start again.`);
587
+ }
588
+ const specTitle = readSpecTitle(specPath, specId);
589
+ try {
590
+ const loadWorkflowByName = deps.loadWorkflowByName ??
591
+ (await Promise.resolve().then(() => __importStar(require('../workflows/discovery')))).loadWorkflowByName;
592
+ const WorktreeProvider = deps.WorktreeProvider ??
593
+ (await Promise.resolve().then(() => __importStar(require('../isolation/worktree')))).WorktreeProvider;
594
+ const WorkflowExecutor = deps.WorkflowExecutor ??
595
+ (await Promise.resolve().then(() => __importStar(require('../workflows/executor')))).WorkflowExecutor;
596
+ const getSpecShipDir = deps.getSpecShipDir ?? (await Promise.resolve().then(() => __importStar(require('../directory')))).getSpecShipDir;
597
+ const loaded = loadWorkflowByName(projectRoot, 'spec-implement');
598
+ if (!loaded) {
599
+ return errorResult('The bundled "spec-implement" workflow was not found. Reinstall ' +
600
+ 'SpecShip or check `specship workflow list`.');
601
+ }
602
+ // Carry the issue key in the branch name too (specship/<key>-<shortRunId>)
603
+ // so JIRA's dev panel matches on branch + PR title + PR body (REQ-JIRA-006).
604
+ const runId = (0, crypto_1.randomUUID)();
605
+ const shortRunId = runId.substring(0, 8);
606
+ const branchSlug = key.replace(/[^A-Za-z0-9._/-]/g, '-').replace(/-+/g, '-');
607
+ const branchName = `specship/${branchSlug}-${shortRunId}`;
608
+ // Completion deps: the PR is raised only when the run later resumes to
609
+ // `completed` with a passing verify. Non-JIRA runs no-op this hook.
610
+ const completionDeps = {
611
+ getIsolationEnvById: deps.getIsolationEnvById ??
612
+ ((id) => deps.specQueries.getIsolationEnvById?.(id)),
613
+ raisePullRequest: deps.raisePullRequest,
614
+ projectRoot,
615
+ makeJiraClient: deps.makeJiraClient,
616
+ };
617
+ const worktrees = new WorktreeProvider(deps.specQueries);
618
+ const executor = new WorkflowExecutor(deps.specQueries, worktrees, projectRoot, async (completedRun) => {
619
+ await handleJiraRunCompletion(completedRun, completionDeps);
620
+ });
621
+ const started = await executor.start(loaded.workflow, {
622
+ runId,
623
+ branchName,
624
+ projectRoot,
625
+ inputs: { SPEC_ID: specId },
626
+ variables: {
627
+ ARTIFACTS_DIR: path.join(getSpecShipDir(projectRoot), 'artifacts'),
628
+ CONTEXT: projectRoot,
629
+ },
630
+ runMetadata: { jira: { issueKey: key, specId, title: specTitle } },
631
+ });
632
+ const run = reloadRun(deps.specQueries, started.run);
633
+ if (run.status === 'failed') {
634
+ // A2: a failed run raises no PR and never advances the issue past
635
+ // "in progress" — the issue is not transitioned by this handler.
636
+ const why = run.errorMessage ? ` ${run.errorMessage}` : '';
637
+ return errorResult(`Implementation run for ${key} (${specId}) failed and no pull request ` +
638
+ `was raised.${why}`);
639
+ }
640
+ // A1 (REQ-JIRA-007): the run started, so push the "start" status —
641
+ // assign the issue + transition it toward "in progress". This runs only on
642
+ // the non-failed path (the run reached the gate); a JIRA hiccup here NEVER
643
+ // blocks the workflow — pushJiraStartStatus never throws and its note is
644
+ // surfaced in the returned text (reinforcement 4).
645
+ const startNote = await pushJiraStartStatus(key, deps.makeJiraClient ?? defaultMakeJiraClient);
646
+ // A1: the run reached the plan/approve gate and paused. Surface the
647
+ // approval message + runId; the caller approves (or rejects) to continue.
648
+ const gate = approvalMessageOf(run);
649
+ return textResult(`Started spec-implement for ${key} (${specId}). Run ${run.id} is paused ` +
650
+ `at the plan/approve gate — review the plan and approve to proceed.` +
651
+ (startNote ? `\n\n${startNote}` : '') +
652
+ (gate ? `\n\n${gate}` : '') +
653
+ `\n\nApprove with: specship workflow approve ${run.id}` +
654
+ `\nOr reject with feedback: specship workflow reject ${run.id} --comment "…"` +
655
+ `\n\nOnce approved and the implementation completes and verifies, a pull ` +
656
+ `request for ${key} is raised automatically (its key is carried on the ` +
657
+ `branch, PR title, and body so JIRA links it back).`);
658
+ }
659
+ catch (err) {
660
+ // Defensive: never let an unexpected error leak internals.
661
+ return errorResult(`Failed to start implementation for ${key}: ${err instanceof Error ? err.message : String(err)}`);
662
+ }
663
+ }
664
+ /**
665
+ * Map a workflow run to its SpecShip lifecycle work-state (REQ-JIRA-008). Pure
666
+ * — reads only the run's status + metadata, most-advanced stage first:
667
+ * - verify ran and passed → `verified` (the terminal success),
668
+ * - a raised-PR marker on the run → `PR raised`,
669
+ * - running/paused → `implementing`,
670
+ * - anything else (pending/failed/cancelled/completed-without-markers) → the
671
+ * spec exists but no implementation has landed → `spec authored`.
672
+ */
673
+ function deriveWorkState(run) {
674
+ if (verifyPassed(run))
675
+ return 'verified';
676
+ const jira = jiraMetaOf(run);
677
+ if (jira && typeof jira.prUrl === 'string' && jira.prUrl.trim()) {
678
+ return 'PR raised';
679
+ }
680
+ const status = run.status;
681
+ if (status === 'running' || status === 'paused')
682
+ return 'implementing';
683
+ return 'spec authored';
684
+ }
685
+ /** Factory seam that builds a live track client from the resolved credentials. */
686
+ function defaultMakeTrackClient() {
687
+ const creds = (0, config_1.resolveJiraCredentials)();
688
+ return new client_1.JiraClient(creds);
689
+ }
690
+ const JIRA_UNREACHABLE = '— (JIRA unreachable)';
691
+ /** Render the tracking rows as a compact markdown table (REQ-JIRA-008). */
692
+ function formatTrack(rows) {
693
+ if (rows.length === 0) {
694
+ return 'No picked issues yet — run specship_jira_pick first.';
695
+ }
696
+ const lines = [
697
+ '| Issue | SpecShip | JIRA |',
698
+ '| --- | --- | --- |',
699
+ ...rows.map((r) => `| **${r.issueKey}** — ${r.title} | ${r.workState} | ${r.jiraStatus} |`),
700
+ ];
701
+ return `Tracking ${rows.length} picked issue${rows.length === 1 ? '' : 's'}:\n\n${lines.join('\n')}`;
702
+ }
703
+ /**
704
+ * Handle `specship_jira_track` (REQ-JIRA-008). A read-only view joining each
705
+ * picked issue's SpecShip work-state (from its workflow run) with its LIVE JIRA
706
+ * status (a fresh read at track time — never the pick-time cached metadata, so
707
+ * an issue moved outside SpecShip shows its current status). It never re-picks
708
+ * or re-starts anything.
709
+ *
710
+ * Runs are enumerated newest-first and deduped by issue key keeping the
711
+ * most-recent run. A JIRA read failure degrades PER ROW to a clear "unreachable"
712
+ * marker and never fails the whole view (degrade-gracefully, matching the
713
+ * sibling status tools).
714
+ *
715
+ * SECURITY: no token appears in any output or error (REQ-JIRA-009) — only public
716
+ * issue keys, titles, and statuses.
717
+ */
718
+ async function handleSpecshipJiraTrack(args, deps) {
719
+ // Not configured → a clear pointer, never an error stack or a fabricated view.
720
+ const notConfigured = notConfiguredResult();
721
+ if (notConfigured)
722
+ return notConfigured;
723
+ const project = typeof args.project === 'string' && args.project.trim()
724
+ ? args.project.trim()
725
+ : undefined;
726
+ // Enumerate the runs that record which issues entered the SpecShip pipeline,
727
+ // keeping only JIRA-started runs and deduping by key (newest-first, so the
728
+ // first occurrence is the most-recent run — reinforcement 3).
729
+ const limit = typeof deps.limit === 'number' && deps.limit > 0 ? deps.limit : 200;
730
+ const getAll = deps.specQueries.getAllWorkflowRuns;
731
+ const runs = typeof getAll === 'function' ? getAll.call(deps.specQueries, limit) ?? [] : [];
732
+ const picked = [];
733
+ const seen = new Set();
734
+ for (const run of runs) {
735
+ const jira = jiraMetaOf(run);
736
+ const issueKey = jira?.issueKey;
737
+ if (!issueKey || seen.has(issueKey))
738
+ continue;
739
+ seen.add(issueKey);
740
+ picked.push({
741
+ issueKey,
742
+ title: jira?.title?.trim() || issueKey,
743
+ workState: deriveWorkState(run),
744
+ });
745
+ }
746
+ if (picked.length === 0) {
747
+ return textResult(formatTrack([]));
748
+ }
749
+ // Fresh JIRA read (reinforcement 1: the JIRA column is ALWAYS from this live
750
+ // read, never the pick-time cached metadata). A whole-read failure degrades
751
+ // every row; a per-key fallback failure degrades only that row.
752
+ const make = deps.makeJiraClient ?? defaultMakeTrackClient;
753
+ let client;
754
+ let listStatuses;
755
+ try {
756
+ client = make();
757
+ const listed = await client.listMyIssues({ project });
758
+ listStatuses = new Map(listed.issues.map((i) => [i.key, i.status]));
759
+ }
760
+ catch {
761
+ // The assigned-issues read failed entirely — every row degrades (per-row).
762
+ listStatuses = undefined;
763
+ }
764
+ const rows = [];
765
+ for (const p of picked) {
766
+ let jiraStatus = JIRA_UNREACHABLE;
767
+ if (listStatuses) {
768
+ const live = listStatuses.get(p.issueKey);
769
+ if (live !== undefined) {
770
+ jiraStatus = live;
771
+ }
772
+ else if (client) {
773
+ // No longer in the assigned list — a single live read still tracks it.
774
+ try {
775
+ const single = await client.getIssue(p.issueKey);
776
+ jiraStatus = single.issue.status;
777
+ }
778
+ catch {
779
+ jiraStatus = JIRA_UNREACHABLE;
780
+ }
781
+ }
782
+ }
783
+ rows.push({ ...p, jiraStatus });
784
+ }
785
+ return textResult(formatTrack(rows));
786
+ }
787
+ //# sourceMappingURL=jira-tools.js.map