@lsctech/polaris 0.1.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 (316) hide show
  1. package/README.md +87 -0
  2. package/dist/__mocks__/tool-server-linear.js +34 -0
  3. package/dist/cli/adopt-approve.js +60 -0
  4. package/dist/cli/adopt-cognition.js +199 -0
  5. package/dist/cli/adopt-instructions.js +143 -0
  6. package/dist/cli/adopt-rules.js +169 -0
  7. package/dist/cli/adopt-scan.js +25 -0
  8. package/dist/cli/adopt-smartdocs.js +192 -0
  9. package/dist/cli/adoption-inventory.js +350 -0
  10. package/dist/cli/adoption-plan.js +222 -0
  11. package/dist/cli/args.js +28 -0
  12. package/dist/cli/args.test.js +30 -0
  13. package/dist/cli/branding.js +41 -0
  14. package/dist/cli/graph.js +358 -0
  15. package/dist/cli/index.js +127 -0
  16. package/dist/cli/index.test.js +162 -0
  17. package/dist/cli/init-detect.js +61 -0
  18. package/dist/cli/init.js +492 -0
  19. package/dist/cli/init.test.js +259 -0
  20. package/dist/cli/librarian.js +32 -0
  21. package/dist/cli/medic.js +96 -0
  22. package/dist/cli/startup.test.js +23 -0
  23. package/dist/cli/tracker.js +130 -0
  24. package/dist/cli/tracker.test.js +126 -0
  25. package/dist/cli/version.js +10 -0
  26. package/dist/cli/version.test.js +11 -0
  27. package/dist/cli/worker.js +287 -0
  28. package/dist/cluster-state/fixtures/test-clusters.json +19 -0
  29. package/dist/cluster-state/store.js +302 -0
  30. package/dist/cluster-state/store.test.js +145 -0
  31. package/dist/cluster-state/types.js +2 -0
  32. package/dist/cognition/archive.js +158 -0
  33. package/dist/cognition/closeout-librarian-types.js +161 -0
  34. package/dist/cognition/cognition.test.js +623 -0
  35. package/dist/cognition/index.js +41 -0
  36. package/dist/cognition/librarian-dispatch.js +391 -0
  37. package/dist/cognition/librarian-dispatch.test.js +247 -0
  38. package/dist/cognition/librarian-packet.js +313 -0
  39. package/dist/cognition/librarian-types.js +57 -0
  40. package/dist/cognition/route-cognition-delta.js +350 -0
  41. package/dist/cognition/summary-delta.js +279 -0
  42. package/dist/cognition/validate.js +162 -0
  43. package/dist/config/defaults.js +98 -0
  44. package/dist/config/doctor.js +273 -0
  45. package/dist/config/index.js +14 -0
  46. package/dist/config/loader.js +58 -0
  47. package/dist/config/provider-detect.js +60 -0
  48. package/dist/config/provider-detect.test.js +119 -0
  49. package/dist/config/schema.js +2 -0
  50. package/dist/config/show.js +44 -0
  51. package/dist/config/show.test.js +42 -0
  52. package/dist/config/validator.js +675 -0
  53. package/dist/config/validator.test.js +77 -0
  54. package/dist/docs/canon-check.js +334 -0
  55. package/dist/docs/canon-check.test.js +207 -0
  56. package/dist/docs/doctrine.js +133 -0
  57. package/dist/docs/doctrine.test.js +139 -0
  58. package/dist/docs/index.js +168 -0
  59. package/dist/docs/index.test.js +83 -0
  60. package/dist/docs/ingest.js +295 -0
  61. package/dist/docs/ingest.test.js +71 -0
  62. package/dist/docs/migrate.js +165 -0
  63. package/dist/docs/migrate.test.js +136 -0
  64. package/dist/docs/seed-instructions.js +147 -0
  65. package/dist/docs/seed-instructions.test.js +137 -0
  66. package/dist/docs/validate-instructions.js +267 -0
  67. package/dist/docs/validate-instructions.test.js +238 -0
  68. package/dist/finalize/artifact-policy.js +214 -0
  69. package/dist/finalize/artifact-policy.test.js +84 -0
  70. package/dist/finalize/delivery-integrity.js +141 -0
  71. package/dist/finalize/finalize.test.js +210 -0
  72. package/dist/finalize/github.js +41 -0
  73. package/dist/finalize/index.js +474 -0
  74. package/dist/finalize/linear.js +213 -0
  75. package/dist/finalize/run-report.js +47 -0
  76. package/dist/finalize/steps/01-map-update.js +11 -0
  77. package/dist/finalize/steps/02-map-validate.js +11 -0
  78. package/dist/finalize/steps/03-schema-validate.js +11 -0
  79. package/dist/finalize/steps/04-run-checks.js +118 -0
  80. package/dist/finalize/steps/05-generate-report.js +14 -0
  81. package/dist/finalize/steps/06-commit.js +39 -0
  82. package/dist/finalize/steps/07-push.js +10 -0
  83. package/dist/finalize/steps/08-create-pr.js +9 -0
  84. package/dist/finalize/steps/09-update-state.js +9 -0
  85. package/dist/finalize/steps/10-append-jsonl.js +23 -0
  86. package/dist/finalize/steps/11-update-linear.js +22 -0
  87. package/dist/finalize/steps/12-archive.js +21 -0
  88. package/dist/graph/adapter/c/adapter.js +45 -0
  89. package/dist/graph/adapter/c/extract.js +135 -0
  90. package/dist/graph/adapter/c/index.js +10 -0
  91. package/dist/graph/adapter/c/runtime.js +67 -0
  92. package/dist/graph/adapter/cpp/adapter.js +45 -0
  93. package/dist/graph/adapter/cpp/extract.js +159 -0
  94. package/dist/graph/adapter/cpp/index.js +10 -0
  95. package/dist/graph/adapter/cpp/runtime.js +61 -0
  96. package/dist/graph/adapter/csharp/adapter.js +45 -0
  97. package/dist/graph/adapter/csharp/extract.js +127 -0
  98. package/dist/graph/adapter/csharp/index.js +10 -0
  99. package/dist/graph/adapter/csharp/runtime.js +61 -0
  100. package/dist/graph/adapter/dart/adapter.js +56 -0
  101. package/dist/graph/adapter/dart/extract.js +130 -0
  102. package/dist/graph/adapter/dart/index.js +10 -0
  103. package/dist/graph/adapter/dart/runtime.js +61 -0
  104. package/dist/graph/adapter/go/adapter.js +45 -0
  105. package/dist/graph/adapter/go/extract.js +112 -0
  106. package/dist/graph/adapter/go/index.js +10 -0
  107. package/dist/graph/adapter/go/runtime.js +58 -0
  108. package/dist/graph/adapter/kotlin-java/extract-common.js +83 -0
  109. package/dist/graph/adapter/kotlin-java/index.js +15 -0
  110. package/dist/graph/adapter/kotlin-java/java-adapter.js +45 -0
  111. package/dist/graph/adapter/kotlin-java/java-extract.js +42 -0
  112. package/dist/graph/adapter/kotlin-java/kotlin-adapter.js +48 -0
  113. package/dist/graph/adapter/kotlin-java/kotlin-extract.js +58 -0
  114. package/dist/graph/adapter/kotlin-java/runtime.js +75 -0
  115. package/dist/graph/adapter/python/adapter.js +45 -0
  116. package/dist/graph/adapter/python/extract.js +121 -0
  117. package/dist/graph/adapter/python/index.js +10 -0
  118. package/dist/graph/adapter/python/runtime.js +70 -0
  119. package/dist/graph/adapter/registry.js +82 -0
  120. package/dist/graph/adapter/rust/adapter.js +45 -0
  121. package/dist/graph/adapter/rust/extract.js +110 -0
  122. package/dist/graph/adapter/rust/index.js +10 -0
  123. package/dist/graph/adapter/rust/runtime.js +74 -0
  124. package/dist/graph/adapter/shell/adapter.js +34 -0
  125. package/dist/graph/adapter/shell/extract.js +98 -0
  126. package/dist/graph/adapter/shell/index.js +8 -0
  127. package/dist/graph/adapter/svelte/adapter.js +91 -0
  128. package/dist/graph/adapter/svelte/extract.js +41 -0
  129. package/dist/graph/adapter/svelte/index.js +8 -0
  130. package/dist/graph/adapter/swift/adapter.js +45 -0
  131. package/dist/graph/adapter/swift/extract.js +120 -0
  132. package/dist/graph/adapter/swift/index.js +10 -0
  133. package/dist/graph/adapter/swift/runtime.js +70 -0
  134. package/dist/graph/adapter/types.js +2 -0
  135. package/dist/graph/adapter/typescript-javascript/adapter.js +72 -0
  136. package/dist/graph/adapter/typescript-javascript/extract.js +115 -0
  137. package/dist/graph/adapter/typescript-javascript/index.js +10 -0
  138. package/dist/graph/adapter/typescript-javascript/runtime.js +97 -0
  139. package/dist/graph/capability/index.js +105 -0
  140. package/dist/graph/governance.js +104 -0
  141. package/dist/graph/parser/extract.js +5 -0
  142. package/dist/graph/parser/loader.js +2 -0
  143. package/dist/graph/parser/pipeline.js +194 -0
  144. package/dist/graph/query/index.js +277 -0
  145. package/dist/graph/query/types.js +5 -0
  146. package/dist/graph/resolver/build-edges.js +211 -0
  147. package/dist/graph/resolver/index.js +23 -0
  148. package/dist/graph/resolver/resolve-imports.js +168 -0
  149. package/dist/graph/store/adapter.js +132 -0
  150. package/dist/graph/store/queries.js +124 -0
  151. package/dist/graph/store/types.js +4 -0
  152. package/dist/ignore/defaults.js +41 -0
  153. package/dist/ignore/parser.js +39 -0
  154. package/dist/lint/map-reference-validator.js +72 -0
  155. package/dist/loop/abort.js +188 -0
  156. package/dist/loop/abort.test.js +151 -0
  157. package/dist/loop/adapters/__tests__/terminal-cli.test.js +92 -0
  158. package/dist/loop/adapters/agent-subtask.js +137 -0
  159. package/dist/loop/adapters/agent-subtask.test.js +151 -0
  160. package/dist/loop/adapters/cli-subtask-bridge.js +108 -0
  161. package/dist/loop/adapters/index.js +9 -0
  162. package/dist/loop/adapters/registry.js +17 -0
  163. package/dist/loop/adapters/terminal-cli.js +403 -0
  164. package/dist/loop/adapters/types.js +2 -0
  165. package/dist/loop/adapters/worker-instructions.js +85 -0
  166. package/dist/loop/body-parser.js +226 -0
  167. package/dist/loop/bootstrap-packet.js +97 -0
  168. package/dist/loop/bootstrap-packet.test.js +171 -0
  169. package/dist/loop/branch-governance.js +54 -0
  170. package/dist/loop/budget.js +63 -0
  171. package/dist/loop/budget.test.js +163 -0
  172. package/dist/loop/checkpoint.js +325 -0
  173. package/dist/loop/compact-return.js +59 -0
  174. package/dist/loop/continue.js +645 -0
  175. package/dist/loop/continue.test.js +337 -0
  176. package/dist/loop/dispatch-boundary.js +335 -0
  177. package/dist/loop/dispatch-state.js +223 -0
  178. package/dist/loop/dispatch-state.test.js +186 -0
  179. package/dist/loop/dispatch.js +1236 -0
  180. package/dist/loop/dispatch.test.js +499 -0
  181. package/dist/loop/ephemeral-smoke.test.js +160 -0
  182. package/dist/loop/evidence-backfill.js +216 -0
  183. package/dist/loop/execution-adapter.js +141 -0
  184. package/dist/loop/execution-adapter.test.js +66 -0
  185. package/dist/loop/finalize-evidence.js +169 -0
  186. package/dist/loop/git-custody.js +250 -0
  187. package/dist/loop/impl-packet-input.js +38 -0
  188. package/dist/loop/index.js +270 -0
  189. package/dist/loop/ledger.js +49 -0
  190. package/dist/loop/ledger.test.js +127 -0
  191. package/dist/loop/lifecycle-dispatch.js +348 -0
  192. package/dist/loop/lifecycle-dispatch.test.js +318 -0
  193. package/dist/loop/lifecycle.js +93 -0
  194. package/dist/loop/lifecycle.test.js +150 -0
  195. package/dist/loop/orphan-recovery.js +276 -0
  196. package/dist/loop/orphan-recovery.test.js +236 -0
  197. package/dist/loop/parent.js +1566 -0
  198. package/dist/loop/parent.test.js +738 -0
  199. package/dist/loop/provider-sealed-packet.test.js +164 -0
  200. package/dist/loop/resume.js +295 -0
  201. package/dist/loop/resume.test.js +172 -0
  202. package/dist/loop/run-bootstrap.js +234 -0
  203. package/dist/loop/run-preflight.js +203 -0
  204. package/dist/loop/status.js +665 -0
  205. package/dist/loop/status.test.js +235 -0
  206. package/dist/loop/wait.js +86 -0
  207. package/dist/loop/worker-packet.js +406 -0
  208. package/dist/loop/worker-packet.test.js +243 -0
  209. package/dist/loop/worker-prompt.js +260 -0
  210. package/dist/loop/worker-prompt.test.js +247 -0
  211. package/dist/loop/worker.js +498 -0
  212. package/dist/loop/worker.test.js +372 -0
  213. package/dist/map/atlas.js +77 -0
  214. package/dist/map/backfill.js +171 -0
  215. package/dist/map/backfill.test.js +120 -0
  216. package/dist/map/index.js +255 -0
  217. package/dist/map/inference.js +73 -0
  218. package/dist/map/inference.test.js +35 -0
  219. package/dist/map/query.js +157 -0
  220. package/dist/map/query.test.js +174 -0
  221. package/dist/map/update.js +355 -0
  222. package/dist/map/update.test.js +67 -0
  223. package/dist/map/validate.js +153 -0
  224. package/dist/map/validate.test.js +90 -0
  225. package/dist/map/welfare.js +86 -0
  226. package/dist/mcp/lib/invoke.js +35 -0
  227. package/dist/mcp/lib/redact.js +24 -0
  228. package/dist/mcp/lib/redact.test.js +112 -0
  229. package/dist/mcp/lib/root.js +41 -0
  230. package/dist/mcp/lib/root.test.js +114 -0
  231. package/dist/mcp/server.js +15 -0
  232. package/dist/mcp/tools/claim-child.js +119 -0
  233. package/dist/mcp/tools/claim-child.test.js +69 -0
  234. package/dist/mcp/tools/current-state.js +73 -0
  235. package/dist/mcp/tools/current-state.test.js +114 -0
  236. package/dist/mcp/tools/dispatch-result.js +186 -0
  237. package/dist/mcp/tools/dispatch-result.test.js +109 -0
  238. package/dist/mcp/tools/index.js +222 -0
  239. package/dist/mcp/tools/index.test.js +172 -0
  240. package/dist/mcp/tools/linear.js +74 -0
  241. package/dist/mcp/tools/loop-continue.js +159 -0
  242. package/dist/mcp/tools/loop-continue.test.js +186 -0
  243. package/dist/mcp/tools/loop-dry-run.js +15 -0
  244. package/dist/mcp/tools/status.js +65 -0
  245. package/dist/medic/chart-id.js +75 -0
  246. package/dist/medic/chart-schema.js +139 -0
  247. package/dist/runs/index.js +119 -0
  248. package/dist/runs/index.test.js +184 -0
  249. package/dist/runs/ledger-tail.js +15 -0
  250. package/dist/runs/list.js +29 -0
  251. package/dist/runs/reconcile.js +74 -0
  252. package/dist/runs/show.js +10 -0
  253. package/dist/runtime/audit/logger.js +15 -0
  254. package/dist/runtime/audit/reader.js +42 -0
  255. package/dist/runtime/checkpoint.js +89 -0
  256. package/dist/runtime/continuation/confirmed.js +225 -0
  257. package/dist/runtime/continuation/confirmed.test.js +421 -0
  258. package/dist/runtime/continuation/dry-run.js +74 -0
  259. package/dist/runtime/execution-window.js +106 -0
  260. package/dist/runtime/scheduling/child-selector.js +16 -0
  261. package/dist/runtime/state.js +43 -0
  262. package/dist/runtime/status.js +46 -0
  263. package/dist/runtime/verification/envelope.js +80 -0
  264. package/dist/runtime/verification/fingerprint.js +26 -0
  265. package/dist/skill-packet/cli.test.js +105 -0
  266. package/dist/skill-packet/generator.js +225 -0
  267. package/dist/skill-packet/generator.test.js +159 -0
  268. package/dist/skill-packet/index.js +49 -0
  269. package/dist/skill-packet/types.js +2 -0
  270. package/dist/smartdocs-engine/audit.js +194 -0
  271. package/dist/smartdocs-engine/audit.test.js +122 -0
  272. package/dist/smartdocs-engine/canon-check.js +340 -0
  273. package/dist/smartdocs-engine/canon-check.test.js +234 -0
  274. package/dist/smartdocs-engine/doctrine.js +633 -0
  275. package/dist/smartdocs-engine/doctrine.test.js +374 -0
  276. package/dist/smartdocs-engine/index.js +456 -0
  277. package/dist/smartdocs-engine/index.test.js +82 -0
  278. package/dist/smartdocs-engine/ingest.js +495 -0
  279. package/dist/smartdocs-engine/ingest.test.js +163 -0
  280. package/dist/smartdocs-engine/migrate.js +217 -0
  281. package/dist/smartdocs-engine/migrate.test.js +200 -0
  282. package/dist/smartdocs-engine/seed-instructions.js +335 -0
  283. package/dist/smartdocs-engine/seed-instructions.test.js +275 -0
  284. package/dist/smartdocs-engine/smartdoc-ignore.js +258 -0
  285. package/dist/smartdocs-engine/smartdoc-ignore.test.js +146 -0
  286. package/dist/smartdocs-engine/validate-instructions.js +366 -0
  287. package/dist/smartdocs-engine/validate-instructions.test.js +283 -0
  288. package/dist/tracker/adapters/github/index.js +305 -0
  289. package/dist/tracker/adapters/jira/index.js +293 -0
  290. package/dist/tracker/adapters/linear/http-errors.test.js +51 -0
  291. package/dist/tracker/adapters/linear/index.js +621 -0
  292. package/dist/tracker/adapters/linear/index.test.js +238 -0
  293. package/dist/tracker/adapters/linear.js +50 -0
  294. package/dist/tracker/adapters/local/index.js +10 -0
  295. package/dist/tracker/adapters/local-file/index.js +159 -0
  296. package/dist/tracker/adapters/mcp-bridge.js +158 -0
  297. package/dist/tracker/adapters/mcp-bridge.test.js +12 -0
  298. package/dist/tracker/adapters/spec/index.js +146 -0
  299. package/dist/tracker/capabilities.js +2 -0
  300. package/dist/tracker/index.js +49 -0
  301. package/dist/tracker/lifecycle-policy.js +139 -0
  302. package/dist/tracker/lifecycle-transition.js +164 -0
  303. package/dist/tracker/local-graph.js +98 -0
  304. package/dist/tracker/migration.js +56 -0
  305. package/dist/tracker/schema.js +86 -0
  306. package/dist/tracker/sync/index.js +404 -0
  307. package/dist/tracker/sync/queue-store.js +95 -0
  308. package/dist/tracker/sync/queue-store.test.js +104 -0
  309. package/dist/tracker/types.js +11 -0
  310. package/dist/types/linear.js +3 -0
  311. package/dist/types/result-packet.js +12 -0
  312. package/dist/types/runtime-state.js +2 -0
  313. package/dist/types/work-contract.js +2 -0
  314. package/dist/utils/monotonic-timestamp.js +13 -0
  315. package/dist/utils/monotonic-timestamp.test.js +19 -0
  316. package/package.json +64 -0
@@ -0,0 +1,305 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GitHubIssuesAdapter = void 0;
4
+ const node_https_1 = require("node:https");
5
+ // ──────────────────────────────────────────────────────────────────────────────
6
+ // Internal HTTP helper
7
+ // ──────────────────────────────────────────────────────────────────────────────
8
+ const GITHUB_API_HOSTNAME = "api.github.com";
9
+ const GITHUB_API_VERSION = "2022-11-28";
10
+ const GITHUB_REQUEST_TIMEOUT_MS = 15_000;
11
+ /**
12
+ * Makes a GitHub REST API request using Node.js built-in `https`.
13
+ *
14
+ * @param method - HTTP method.
15
+ * @param path - Request path (e.g. `/repos/owner/repo/issues/1`).
16
+ * @param token - GitHub PAT for Bearer authentication.
17
+ * @param body - Optional JSON-serializable request body.
18
+ * @returns Parsed JSON response body, or `null` for 204 No Content.
19
+ */
20
+ async function githubRequest(method, path, token, body) {
21
+ const payload = body !== undefined ? JSON.stringify(body) : undefined;
22
+ return new Promise((resolve, reject) => {
23
+ const req = (0, node_https_1.request)({
24
+ hostname: GITHUB_API_HOSTNAME,
25
+ path,
26
+ method,
27
+ headers: {
28
+ "Accept": "application/vnd.github+json",
29
+ "Authorization": `Bearer ${token}`,
30
+ "X-GitHub-Api-Version": GITHUB_API_VERSION,
31
+ "User-Agent": "polaris-github-adapter",
32
+ ...(payload !== undefined
33
+ ? {
34
+ "Content-Type": "application/json",
35
+ "Content-Length": Buffer.byteLength(payload),
36
+ }
37
+ : {}),
38
+ },
39
+ }, (res) => {
40
+ const chunks = [];
41
+ res.on("data", (chunk) => chunks.push(chunk));
42
+ res.on("end", () => {
43
+ const statusCode = res.statusCode ?? 0;
44
+ if (statusCode >= 400) {
45
+ const text = Buffer.concat(chunks).toString("utf-8");
46
+ reject(new Error(`GitHub API returned ${statusCode} for ${method} ${path}: ${text}`));
47
+ return;
48
+ }
49
+ // 204 No Content
50
+ if (statusCode === 204) {
51
+ resolve(null);
52
+ return;
53
+ }
54
+ try {
55
+ const text = Buffer.concat(chunks).toString("utf-8");
56
+ resolve(JSON.parse(text));
57
+ }
58
+ catch (err) {
59
+ reject(new Error(`Failed to parse GitHub API response: ${err instanceof Error ? err.message : String(err)}`));
60
+ }
61
+ });
62
+ });
63
+ req.setTimeout(GITHUB_REQUEST_TIMEOUT_MS, () => {
64
+ req.destroy(new Error(`GitHub API request timed out after ${GITHUB_REQUEST_TIMEOUT_MS}ms`));
65
+ });
66
+ req.on("error", reject);
67
+ if (payload !== undefined) {
68
+ req.write(payload);
69
+ }
70
+ req.end();
71
+ });
72
+ }
73
+ // ──────────────────────────────────────────────────────────────────────────────
74
+ // Lifecycle state helpers
75
+ // ──────────────────────────────────────────────────────────────────────────────
76
+ /**
77
+ * Maps a `NormalizedLifecycleState` to the hyphenated label suffix used in
78
+ * GitHub (e.g. `"in_progress"` → `"in-progress"`).
79
+ */
80
+ function lifecycleStateToLabelSuffix(state) {
81
+ return state.replace(/_/g, "-");
82
+ }
83
+ // ──────────────────────────────────────────────────────────────────────────────
84
+ // Adapter
85
+ // ──────────────────────────────────────────────────────────────────────────────
86
+ /**
87
+ * GitHub Issues tracker adapter.
88
+ *
89
+ * Implements {@link CapableTrackerAdapter} against the GitHub REST API v3,
90
+ * using PAT authentication. Lifecycle states are modelled as open/closed
91
+ * issue state plus `status:*` labels.
92
+ *
93
+ * **Capability summary:**
94
+ * - Status updates: yes (open/closed + labels)
95
+ * - Comments: yes
96
+ * - Lifecycle mapping: yes
97
+ * - Child relationships, links, dependencies: not supported
98
+ */
99
+ class GitHubIssuesAdapter {
100
+ owner;
101
+ repo;
102
+ token;
103
+ labelPrefix;
104
+ labelPrefixLower;
105
+ /**
106
+ * Creates a new `GitHubIssuesAdapter`.
107
+ *
108
+ * @param config - GitHub adapter configuration.
109
+ */
110
+ constructor(config) {
111
+ this.owner = config.owner;
112
+ this.repo = config.repo;
113
+ this.token = config.token;
114
+ this.labelPrefix = config.labelPrefix ?? "status:";
115
+ this.labelPrefixLower = this.labelPrefix.toLowerCase();
116
+ }
117
+ // ── CapableTrackerAdapter ──────────────────────────────────────────────────
118
+ /**
119
+ * Returns the capabilities supported by the GitHub Issues adapter.
120
+ */
121
+ getCapabilities() {
122
+ return {
123
+ supportsChildRelationships: false,
124
+ supportsStatusUpdates: true,
125
+ supportsComments: true,
126
+ supportsLinks: false,
127
+ supportsDependencies: false,
128
+ supportsLifecycleMapping: true,
129
+ supportsCreateChild: false,
130
+ };
131
+ }
132
+ /**
133
+ * Maps a GitHub native status string to a {@link NormalizedLifecycleState}.
134
+ *
135
+ * Accepted `nativeStatus` values:
136
+ * - `"open"` → `"in_progress"` (no status label present)
137
+ * - `"closed"` → `"done"`
138
+ * - `"status:in-progress"` → `"in_progress"`
139
+ * - `"status:in-review"` → `"in_review"`
140
+ * - `"status:blocked"` → `"blocked"`
141
+ *
142
+ * @param nativeStatus - GitHub issue state (`"open"` / `"closed"`) or a
143
+ * `status:*` label string.
144
+ */
145
+ mapNativeStatus(nativeStatus) {
146
+ const s = nativeStatus.trim().toLowerCase();
147
+ if (s === "closed") {
148
+ return { lifecycleState: "done", supported: true };
149
+ }
150
+ if (s === "open") {
151
+ return { lifecycleState: "in_progress", supported: true };
152
+ }
153
+ // Accept status label strings directly (e.g. from stored label names)
154
+ if (s.startsWith(this.labelPrefixLower)) {
155
+ const suffix = s.slice(this.labelPrefixLower.length); // e.g. "in-progress"
156
+ const normalized = suffix.replace(/-/g, "_");
157
+ const valid = [
158
+ "backlog",
159
+ "in_progress",
160
+ "in_review",
161
+ "done",
162
+ "blocked",
163
+ "cancelled",
164
+ "no_status_change",
165
+ ];
166
+ if (valid.includes(normalized)) {
167
+ return { lifecycleState: normalized, supported: true };
168
+ }
169
+ return {
170
+ lifecycleState: "no_status_change",
171
+ supported: false,
172
+ reason: `Unknown status label suffix '${suffix}' — cannot map to a normalized lifecycle state.`,
173
+ };
174
+ }
175
+ return {
176
+ lifecycleState: "no_status_change",
177
+ supported: false,
178
+ reason: `Unknown GitHub native status '${nativeStatus}'. Expected "open", "closed", or a "${this.labelPrefix}*" label.`,
179
+ };
180
+ }
181
+ /**
182
+ * Transitions a GitHub issue to a normalized lifecycle state.
183
+ *
184
+ * Steps performed:
185
+ * 1. Fetches the current issue to discover existing `status:*` labels.
186
+ * 2. Sets issue state to `"closed"` for `"done"` / `"cancelled"`, otherwise
187
+ * `"open"`.
188
+ * 3. Removes all existing `status:*` labels from the issue.
189
+ * 4. Adds a new `status:<lifecycle-state>` label (hyphens instead of
190
+ * underscores).
191
+ *
192
+ * Returns `{ applied: false, skipped: true }` when `lifecycleState` is
193
+ * `"no_status_change"`.
194
+ *
195
+ * @param taskId - GitHub issue number as a string (e.g. `"42"`).
196
+ * @param lifecycleState - Target normalized lifecycle state.
197
+ * @param _evidence - Unused; accepted for interface compatibility.
198
+ */
199
+ async transitionLifecycleState(taskId, lifecycleState, _evidence) {
200
+ if (lifecycleState === "no_status_change") {
201
+ return {
202
+ applied: false,
203
+ skipped: true,
204
+ skipReason: "Lifecycle state is 'no_status_change', skipping transition.",
205
+ };
206
+ }
207
+ const basePath = `/repos/${this.owner}/${this.repo}/issues/${taskId}`;
208
+ try {
209
+ // 1. Fetch current issue to find existing status labels
210
+ const issue = await githubRequest("GET", basePath, this.token);
211
+ const existingStatusLabels = issue.labels
212
+ .map((l) => l.name)
213
+ .filter((name) => name.toLowerCase().startsWith(this.labelPrefixLower));
214
+ // 2. Update open/closed state
215
+ const newState = lifecycleState === "done" || lifecycleState === "cancelled" ? "closed" : "open";
216
+ await githubRequest("PATCH", basePath, this.token, { state: newState });
217
+ // 3. Remove existing status:* labels
218
+ for (const labelName of existingStatusLabels) {
219
+ const encodedLabel = encodeURIComponent(labelName);
220
+ await githubRequest("DELETE", `${basePath}/labels/${encodedLabel}`, this.token);
221
+ }
222
+ // 4. Add new status label
223
+ const newLabel = `${this.labelPrefix}${lifecycleStateToLabelSuffix(lifecycleState)}`;
224
+ await githubRequest("POST", `${basePath}/labels`, this.token, {
225
+ labels: [newLabel],
226
+ });
227
+ return { applied: true, skipped: false };
228
+ }
229
+ catch (err) {
230
+ return {
231
+ applied: false,
232
+ skipped: false,
233
+ error: err instanceof Error ? err.message : String(err),
234
+ };
235
+ }
236
+ }
237
+ /**
238
+ * Adds a comment to a GitHub issue.
239
+ *
240
+ * @param taskId - GitHub issue number as a string (e.g. `"42"`).
241
+ * @param body - Markdown comment body.
242
+ */
243
+ async addComment(taskId, body) {
244
+ const path = `/repos/${this.owner}/${this.repo}/issues/${taskId}/comments`;
245
+ try {
246
+ await githubRequest("POST", path, this.token, { body });
247
+ return { added: true, unsupported: false };
248
+ }
249
+ catch (err) {
250
+ return {
251
+ added: false,
252
+ unsupported: false,
253
+ error: err instanceof Error ? err.message : String(err),
254
+ };
255
+ }
256
+ }
257
+ /**
258
+ * Link attachments are not supported by the GitHub Issues API.
259
+ *
260
+ * Always returns `{ attached: false, unsupported: true }`.
261
+ *
262
+ * @param _taskId - Unused.
263
+ * @param _url - Unused.
264
+ * @param _title - Unused.
265
+ */
266
+ async attachLink(_taskId, _url, _title) {
267
+ return {
268
+ attached: false,
269
+ unsupported: true,
270
+ reason: "GitHub Issues does not support native link attachments.",
271
+ };
272
+ }
273
+ /**
274
+ * Dependency relations are not supported by the GitHub Issues API.
275
+ *
276
+ * Always returns `{ added: false, unsupported: true }`.
277
+ *
278
+ * @param _taskId - Unused.
279
+ * @param _dependsOnTaskId - Unused.
280
+ */
281
+ async addDependency(_taskId, _dependsOnTaskId) {
282
+ return {
283
+ added: false,
284
+ unsupported: true,
285
+ reason: "GitHub Issues does not support native dependency relations.",
286
+ };
287
+ }
288
+ /**
289
+ * Child issue creation is not supported by the GitHub Issues adapter.
290
+ *
291
+ * Always returns `{ created: false, unsupported: true }`.
292
+ *
293
+ * @param _parentId - Unused.
294
+ * @param _title - Unused.
295
+ * @param _body - Unused.
296
+ */
297
+ async createChild(_parentId, _title, _body) {
298
+ return {
299
+ created: false,
300
+ unsupported: true,
301
+ reason: "GitHub Issues adapter does not support creating child issues.",
302
+ };
303
+ }
304
+ }
305
+ exports.GitHubIssuesAdapter = GitHubIssuesAdapter;
@@ -0,0 +1,293 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JiraCloudAdapter = void 0;
4
+ const node_https_1 = require("node:https");
5
+ const JIRA_REQUEST_TIMEOUT_MS = 30_000;
6
+ /**
7
+ * Performs an HTTPS request against the Jira Cloud REST API v3.
8
+ *
9
+ * @param config - Adapter config providing baseUrl and credentials.
10
+ * @param method - HTTP method ("GET" or "POST").
11
+ * @param path - API path, e.g. "/rest/api/3/issue/FOO-1/transitions".
12
+ * @param body - Optional request body. Will be JSON-serialized when provided.
13
+ * @returns Parsed JSON response body.
14
+ * @throws On HTTP 4xx/5xx or JSON parse failure.
15
+ */
16
+ function jiraRequest(config, method, path, body) {
17
+ const auth = Buffer.from(`${config.email}:${config.apiToken}`).toString("base64");
18
+ const url = new URL(config.baseUrl);
19
+ const payload = body !== undefined ? JSON.stringify(body) : undefined;
20
+ return new Promise((resolve, reject) => {
21
+ const options = {
22
+ hostname: url.hostname,
23
+ port: url.port || undefined,
24
+ path,
25
+ method,
26
+ headers: {
27
+ Authorization: `Basic ${auth}`,
28
+ Accept: "application/json",
29
+ ...(payload !== undefined
30
+ ? { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(payload) }
31
+ : {}),
32
+ },
33
+ };
34
+ const req = (0, node_https_1.request)(options, (res) => {
35
+ const chunks = [];
36
+ res.on("data", (chunk) => chunks.push(chunk));
37
+ res.on("end", () => {
38
+ const responseBody = Buffer.concat(chunks).toString("utf-8");
39
+ if ((res.statusCode ?? 0) >= 400) {
40
+ reject(new Error(`Jira API returned ${res.statusCode}: ${responseBody || "<empty response body>"}`));
41
+ return;
42
+ }
43
+ if (!responseBody) {
44
+ resolve(undefined);
45
+ return;
46
+ }
47
+ try {
48
+ resolve(JSON.parse(responseBody));
49
+ }
50
+ catch {
51
+ reject(new Error(`Jira API returned non-JSON response: ${responseBody}`));
52
+ }
53
+ });
54
+ });
55
+ req.setTimeout(JIRA_REQUEST_TIMEOUT_MS, () => {
56
+ req.destroy(new Error(`Jira API request timed out after ${JIRA_REQUEST_TIMEOUT_MS}ms`));
57
+ });
58
+ req.on("error", reject);
59
+ if (payload !== undefined) {
60
+ req.write(payload);
61
+ }
62
+ req.end();
63
+ });
64
+ }
65
+ /**
66
+ * Heuristic mapping from lowercased Jira status names to normalized lifecycle states.
67
+ * Returns `undefined` when no match is found.
68
+ */
69
+ function heuristicMap(normalizedName) {
70
+ if (["to do", "backlog", "new", "open"].includes(normalizedName)) {
71
+ return "backlog";
72
+ }
73
+ if (["in progress", "in development", "started", "doing"].includes(normalizedName)) {
74
+ return "in_progress";
75
+ }
76
+ if (["in review", "under review", "ready for review", "in qa", "in testing"].includes(normalizedName)) {
77
+ return "in_review";
78
+ }
79
+ if (["done", "completed", "resolved", "closed", "ready for deployment"].includes(normalizedName)) {
80
+ return "done";
81
+ }
82
+ if (["blocked", "on hold", "waiting", "paused"].includes(normalizedName)) {
83
+ return "blocked";
84
+ }
85
+ if (["cancelled", "canceled", "won't do", "duplicate", "invalid"].includes(normalizedName)) {
86
+ return "cancelled";
87
+ }
88
+ return undefined;
89
+ }
90
+ /**
91
+ * Tracker adapter for Jira Cloud using the REST API v3 with Basic Auth.
92
+ *
93
+ * Implements `CapableTrackerAdapter` to provide lifecycle state transitions,
94
+ * comment posting, and status mapping against a Jira Cloud project.
95
+ */
96
+ class JiraCloudAdapter {
97
+ config;
98
+ /**
99
+ * Creates a new JiraCloudAdapter.
100
+ *
101
+ * @param config - Jira Cloud connection and project configuration.
102
+ */
103
+ constructor(config) {
104
+ this.config = config;
105
+ }
106
+ /**
107
+ * Returns the capabilities supported by the Jira Cloud adapter.
108
+ */
109
+ getCapabilities() {
110
+ return {
111
+ supportsChildRelationships: false,
112
+ supportsStatusUpdates: true,
113
+ supportsComments: true,
114
+ supportsLinks: false,
115
+ supportsDependencies: false,
116
+ supportsLifecycleMapping: true,
117
+ supportsCreateChild: false,
118
+ };
119
+ }
120
+ /**
121
+ * Maps a Jira status name to a normalized lifecycle state, consulting
122
+ * user-configured `statusMappings` before falling back to the built-in
123
+ * heuristic.
124
+ *
125
+ * @param statusName - The native Jira status name (case-insensitive).
126
+ * @returns The matching normalized lifecycle state, or `undefined` if unknown.
127
+ */
128
+ mapStatusToLifecycle(statusName) {
129
+ const normalizedName = statusName.toLowerCase().trim();
130
+ if (this.config.statusMappings) {
131
+ for (const [key, value] of Object.entries(this.config.statusMappings)) {
132
+ if (key.toLowerCase().trim() === normalizedName) {
133
+ return value;
134
+ }
135
+ }
136
+ }
137
+ return heuristicMap(normalizedName);
138
+ }
139
+ /**
140
+ * Maps a native Jira status name to a normalized lifecycle state.
141
+ *
142
+ * User-provided `statusMappings` in the adapter config override the built-in
143
+ * heuristic for any matching status name (case-insensitive).
144
+ *
145
+ * @param nativeStatus - The native Jira status name (e.g. "In Progress").
146
+ * @returns A status mapping result with the normalized state and support status.
147
+ */
148
+ mapNativeStatus(nativeStatus) {
149
+ const mapped = this.mapStatusToLifecycle(nativeStatus);
150
+ if (mapped !== undefined) {
151
+ return { lifecycleState: mapped, supported: true };
152
+ }
153
+ return {
154
+ lifecycleState: "no_status_change",
155
+ supported: false,
156
+ reason: `Unknown Jira status '${nativeStatus}'. Consider adding an explicit statusMappings override.`,
157
+ };
158
+ }
159
+ /**
160
+ * Attempts to transition a Jira issue to the target lifecycle state.
161
+ *
162
+ * Fetches available transitions from the Jira API, finds one whose name
163
+ * heuristically maps to the requested lifecycle state, and applies it.
164
+ *
165
+ * @param taskId - The Jira issue key or ID (e.g. "POL-42").
166
+ * @param lifecycleState - The target normalized lifecycle state.
167
+ * @param _evidence - Unused. Present for interface compatibility.
168
+ * @returns A lifecycle transition result indicating success, skip, or failure.
169
+ */
170
+ async transitionLifecycleState(taskId, lifecycleState, _evidence) {
171
+ if (lifecycleState === "no_status_change") {
172
+ return {
173
+ applied: false,
174
+ skipped: true,
175
+ skipReason: "Lifecycle state is 'no_status_change', skipping transition",
176
+ };
177
+ }
178
+ let transitionsData;
179
+ try {
180
+ transitionsData = (await jiraRequest(this.config, "GET", `/rest/api/3/issue/${taskId}/transitions`));
181
+ }
182
+ catch (err) {
183
+ return {
184
+ applied: false,
185
+ skipped: false,
186
+ error: err instanceof Error ? err.message : String(err),
187
+ };
188
+ }
189
+ const transitions = transitionsData?.transitions ?? [];
190
+ // Find a transition whose name maps to the target lifecycle state (respects statusMappings).
191
+ const match = transitions.find((t) => this.mapStatusToLifecycle(t.name) === lifecycleState);
192
+ if (!match) {
193
+ return {
194
+ applied: false,
195
+ skipped: true,
196
+ skipReason: `No Jira transition found that maps to lifecycle state '${lifecycleState}' for issue '${taskId}'.`,
197
+ };
198
+ }
199
+ try {
200
+ await jiraRequest(this.config, "POST", `/rest/api/3/issue/${taskId}/transitions`, {
201
+ transition: { id: match.id },
202
+ });
203
+ }
204
+ catch (err) {
205
+ return {
206
+ applied: false,
207
+ skipped: false,
208
+ error: err instanceof Error ? err.message : String(err),
209
+ };
210
+ }
211
+ return { applied: true, skipped: false };
212
+ }
213
+ /**
214
+ * Adds a comment to a Jira issue using the Atlassian Document Format (ADF).
215
+ *
216
+ * @param taskId - The Jira issue key or ID (e.g. "POL-42").
217
+ * @param body - The plain-text comment body.
218
+ * @returns A comment result indicating success or failure.
219
+ */
220
+ async addComment(taskId, body) {
221
+ const adfBody = {
222
+ body: {
223
+ type: "doc",
224
+ version: 1,
225
+ content: [
226
+ {
227
+ type: "paragraph",
228
+ content: [{ type: "text", text: body }],
229
+ },
230
+ ],
231
+ },
232
+ };
233
+ try {
234
+ await jiraRequest(this.config, "POST", `/rest/api/3/issue/${taskId}/comment`, adfBody);
235
+ }
236
+ catch (err) {
237
+ return {
238
+ added: false,
239
+ unsupported: false,
240
+ error: err instanceof Error ? err.message : String(err),
241
+ };
242
+ }
243
+ return { added: true, unsupported: false };
244
+ }
245
+ /**
246
+ * Attaching arbitrary URL links is not supported by this adapter.
247
+ *
248
+ * Jira has issue links but not arbitrary URL attachments. Use `addComment`
249
+ * to include URLs in issue comments instead.
250
+ *
251
+ * @param _taskId - Unused.
252
+ * @param _url - Unused.
253
+ * @param _title - Unused.
254
+ * @returns A link result indicating the operation is unsupported.
255
+ */
256
+ async attachLink(_taskId, _url, _title) {
257
+ return {
258
+ attached: false,
259
+ unsupported: true,
260
+ reason: "Jira Cloud adapter does not support arbitrary URL link attachments. Use addComment to include URLs.",
261
+ };
262
+ }
263
+ /**
264
+ * Dependency relations are not supported by this adapter.
265
+ *
266
+ * @param _taskId - Unused.
267
+ * @param _dependsOnTaskId - Unused.
268
+ * @returns A dependency result indicating the operation is unsupported.
269
+ */
270
+ async addDependency(_taskId, _dependsOnTaskId) {
271
+ return {
272
+ added: false,
273
+ unsupported: true,
274
+ reason: "Jira Cloud adapter does not support dependency relations via this interface.",
275
+ };
276
+ }
277
+ /**
278
+ * Creating child tasks is not supported by this adapter.
279
+ *
280
+ * @param _parentId - Unused.
281
+ * @param _title - Unused.
282
+ * @param _body - Unused.
283
+ * @returns A create child result indicating the operation is unsupported.
284
+ */
285
+ async createChild(_parentId, _title, _body) {
286
+ return {
287
+ created: false,
288
+ unsupported: true,
289
+ reason: "Jira Cloud adapter does not support creating child issues via this interface.",
290
+ };
291
+ }
292
+ }
293
+ exports.JiraCloudAdapter = JiraCloudAdapter;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const node_events_1 = require("node:events");
4
+ const vitest_1 = require("vitest");
5
+ const requestMock = vitest_1.vi.hoisted(() => vitest_1.vi.fn());
6
+ vitest_1.vi.mock("node:https", () => ({
7
+ request: requestMock,
8
+ }));
9
+ (0, vitest_1.describe)("LinearAdapter HTTP error handling", () => {
10
+ const originalApiKey = process.env["LINEAR_API_KEY"];
11
+ (0, vitest_1.beforeEach)(() => {
12
+ requestMock.mockReset();
13
+ });
14
+ (0, vitest_1.it)("includes non-2xx response body in thrown error", async () => {
15
+ process.env["LINEAR_API_KEY"] = "linear-test-token";
16
+ requestMock.mockImplementation((_options, callback) => {
17
+ const req = new node_events_1.EventEmitter();
18
+ req.write = vitest_1.vi.fn();
19
+ req.end = vitest_1.vi.fn(() => {
20
+ const res = new node_events_1.EventEmitter();
21
+ res.statusCode = 400;
22
+ callback(res);
23
+ res.emit("data", Buffer.from(JSON.stringify({ errors: [{ message: "bad query" }] })));
24
+ res.emit("end");
25
+ });
26
+ req.on = req.addListener.bind(req);
27
+ return req;
28
+ });
29
+ try {
30
+ vitest_1.vi.resetModules();
31
+ const { LinearAdapter } = await import("./index.js");
32
+ const config = {
33
+ tracker: {
34
+ linear: {
35
+ enabled: true,
36
+ },
37
+ },
38
+ };
39
+ const adapter = new LinearAdapter(config);
40
+ await (0, vitest_1.expect)(adapter.syncIn("POL-198")).rejects.toThrow("Linear API returned 400: {\"errors\":[{\"message\":\"bad query\"}]}");
41
+ }
42
+ finally {
43
+ if (originalApiKey === undefined) {
44
+ delete process.env["LINEAR_API_KEY"];
45
+ }
46
+ else {
47
+ process.env["LINEAR_API_KEY"] = originalApiKey;
48
+ }
49
+ }
50
+ });
51
+ });