@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,621 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LinearAdapter = void 0;
4
+ const local_graph_js_1 = require("../../local-graph.js");
5
+ const schema_js_1 = require("../../schema.js");
6
+ const node_https_1 = require("node:https");
7
+ class LinearGraphqlClient {
8
+ apiKey;
9
+ constructor(apiKey) {
10
+ this.apiKey = apiKey;
11
+ }
12
+ async graphql(query, variables) {
13
+ if (!this.apiKey) {
14
+ throw new Error("LINEAR_API_KEY is required for the 'linear' tracker adapter.");
15
+ }
16
+ const payload = JSON.stringify({ query, variables });
17
+ const raw = await new Promise((resolve, reject) => {
18
+ const req = (0, node_https_1.request)({
19
+ hostname: "api.linear.app",
20
+ path: "/graphql",
21
+ method: "POST",
22
+ headers: {
23
+ "Content-Type": "application/json",
24
+ Authorization: this.apiKey,
25
+ },
26
+ }, (res) => {
27
+ const chunks = [];
28
+ res.on("data", (chunk) => chunks.push(chunk));
29
+ res.on("end", () => {
30
+ const responseBody = Buffer.concat(chunks).toString("utf-8");
31
+ if ((res.statusCode ?? 0) >= 400) {
32
+ reject(new Error(`Linear API returned ${res.statusCode}: ${responseBody || "<empty response body>"}`));
33
+ return;
34
+ }
35
+ resolve(responseBody);
36
+ });
37
+ });
38
+ req.on("error", reject);
39
+ req.write(payload);
40
+ req.end();
41
+ });
42
+ const parsed = JSON.parse(raw);
43
+ if (parsed.errors && parsed.errors.length > 0) {
44
+ throw new Error(`Linear API GraphQL errors: ${JSON.stringify(parsed.errors)}`);
45
+ }
46
+ if (!parsed.data) {
47
+ throw new Error("Linear API returned no data.");
48
+ }
49
+ return parsed.data;
50
+ }
51
+ async listTeams() {
52
+ const data = await this.graphql("query PolarisLinearTeams { teams(first: 250) { nodes { id name } } }", {});
53
+ return data.teams.nodes;
54
+ }
55
+ async listProjects(teamId) {
56
+ const data = await this.graphql(`
57
+ query PolarisLinearProjects($teamId: String!) {
58
+ projects(filter: { teams: { some: { id: { eq: $teamId } } } }, first: 250) {
59
+ nodes { id name }
60
+ }
61
+ }
62
+ `, { teamId });
63
+ return data.projects.nodes;
64
+ }
65
+ async listIssues(filters) {
66
+ const data = await this.graphql(`
67
+ query PolarisLinearIssues($teamId: String, $projectId: String) {
68
+ issues(
69
+ first: 250
70
+ filter: {
71
+ team: { id: { eq: $teamId } }
72
+ project: { id: { eq: $projectId } }
73
+ }
74
+ ) {
75
+ nodes {
76
+ id
77
+ identifier
78
+ title
79
+ description
80
+ state { id name }
81
+ children {
82
+ nodes {
83
+ id
84
+ identifier
85
+ title
86
+ description
87
+ state { id name }
88
+ }
89
+ }
90
+ relations {
91
+ nodes {
92
+ id
93
+ type
94
+ issue {
95
+ id
96
+ identifier
97
+ title
98
+ state { id name }
99
+ }
100
+ relatedIssue {
101
+ id
102
+ identifier
103
+ title
104
+ state { id name }
105
+ }
106
+ }
107
+ }
108
+ inverseRelations {
109
+ nodes {
110
+ id
111
+ type
112
+ issue {
113
+ id
114
+ identifier
115
+ title
116
+ state { id name }
117
+ }
118
+ relatedIssue {
119
+ id
120
+ identifier
121
+ title
122
+ state { id name }
123
+ }
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ `, {
130
+ teamId: filters.teamId ?? null,
131
+ projectId: filters.projectId ?? null,
132
+ });
133
+ return data.issues.nodes;
134
+ }
135
+ async getIssueById(id) {
136
+ const data = await this.graphql(`
137
+ query PolarisLinearIssueById($id: String!) {
138
+ issue(id: $id) {
139
+ id
140
+ identifier
141
+ title
142
+ description
143
+ state { id name }
144
+ children {
145
+ nodes {
146
+ id
147
+ identifier
148
+ title
149
+ state { id name }
150
+ }
151
+ }
152
+ relations {
153
+ nodes {
154
+ id
155
+ type
156
+ issue {
157
+ id
158
+ identifier
159
+ title
160
+ state { id name }
161
+ }
162
+ relatedIssue {
163
+ id
164
+ identifier
165
+ title
166
+ state { id name }
167
+ }
168
+ }
169
+ }
170
+ inverseRelations {
171
+ nodes {
172
+ id
173
+ type
174
+ issue {
175
+ id
176
+ identifier
177
+ title
178
+ state { id name }
179
+ }
180
+ relatedIssue {
181
+ id
182
+ identifier
183
+ title
184
+ state { id name }
185
+ }
186
+ }
187
+ }
188
+ }
189
+ }
190
+ `, { id });
191
+ return data.issue;
192
+ }
193
+ }
194
+ /**
195
+ * Implements the Linear direct adapter for synchronizing issues.
196
+ */
197
+ class LinearAdapter {
198
+ config;
199
+ linearClient;
200
+ constructor(config, linearClient) {
201
+ this.config = config;
202
+ this.linearClient = linearClient ?? new LinearGraphqlClient(process.env["LINEAR_API_KEY"]);
203
+ }
204
+ issueNodeId(issue) {
205
+ return issue.identifier ?? issue.id;
206
+ }
207
+ addDependencyToMap(dependencies, nodeId, dependsOnNodeId) {
208
+ if (!dependencies[nodeId]) {
209
+ dependencies[nodeId] = [];
210
+ }
211
+ if (!dependencies[nodeId].includes(dependsOnNodeId)) {
212
+ dependencies[nodeId].push(dependsOnNodeId);
213
+ }
214
+ }
215
+ upsertNode(nodes, issue) {
216
+ const nodeId = this.issueNodeId(issue);
217
+ const statusName = issue.state?.name ?? issue.state?.id ?? "Unknown";
218
+ if (!nodes[nodeId]) {
219
+ nodes[nodeId] = {
220
+ id: nodeId,
221
+ title: issue.title ?? issue.identifier ?? issue.id,
222
+ status: statusName,
223
+ ...(issue.description ? { body: issue.description } : {}),
224
+ };
225
+ return nodeId;
226
+ }
227
+ if (issue.title &&
228
+ (!nodes[nodeId].title ||
229
+ nodes[nodeId].title === issue.identifier ||
230
+ nodes[nodeId].title === issue.id)) {
231
+ nodes[nodeId].title = issue.title;
232
+ }
233
+ if ((nodes[nodeId].status === "Unknown" || !nodes[nodeId].status) && issue.state) {
234
+ nodes[nodeId].status = statusName;
235
+ }
236
+ if (issue.description && !nodes[nodeId].body) {
237
+ nodes[nodeId].body = issue.description;
238
+ }
239
+ return nodeId;
240
+ }
241
+ relationDependency(relation, issueNodeId) {
242
+ if (!relation.issue || !relation.relatedIssue || !relation.type) {
243
+ return null;
244
+ }
245
+ const relationType = this.normalizeRelationType(relation.type);
246
+ const sourceNodeId = this.issueNodeId(relation.issue);
247
+ const relatedNodeId = this.issueNodeId(relation.relatedIssue);
248
+ if (sourceNodeId !== issueNodeId && relatedNodeId !== issueNodeId) {
249
+ return null;
250
+ }
251
+ if (relationType === "BLOCKS") {
252
+ return {
253
+ fromId: relatedNodeId,
254
+ dependsOnId: sourceNodeId,
255
+ };
256
+ }
257
+ if (relationType === "BLOCKED_BY" || relationType === "DEPENDS_ON") {
258
+ return {
259
+ fromId: sourceNodeId,
260
+ dependsOnId: relatedNodeId,
261
+ };
262
+ }
263
+ console.warn(`Linear relation type '${relation.type}' is not mapped to dependencies (supported types: blocks, blocked_by, depends_on); ignoring.`);
264
+ return null;
265
+ }
266
+ normalizeRelationType(type) {
267
+ return type.trim().toUpperCase().replace(/[^A-Z0-9]+/g, "_");
268
+ }
269
+ mapIssueRelations(issue, nodes, dependencies) {
270
+ const issueNodeId = this.upsertNode(nodes, issue);
271
+ const relationNodes = [
272
+ ...(issue.relations?.nodes ?? []),
273
+ ...(issue.inverseRelations?.nodes ?? []),
274
+ ];
275
+ for (const relation of relationNodes) {
276
+ if (relation.issue) {
277
+ this.upsertNode(nodes, relation.issue);
278
+ }
279
+ if (relation.relatedIssue) {
280
+ this.upsertNode(nodes, relation.relatedIssue);
281
+ }
282
+ const dependency = this.relationDependency(relation, issueNodeId);
283
+ if (!dependency) {
284
+ continue;
285
+ }
286
+ this.addDependencyToMap(dependencies, dependency.fromId, dependency.dependsOnId);
287
+ }
288
+ for (const childIssue of issue.children?.nodes ?? []) {
289
+ this.upsertNode(nodes, childIssue);
290
+ }
291
+ }
292
+ async collectIssueAndChildren(issueId) {
293
+ const rootIssue = await this.linearClient.getIssueById(issueId);
294
+ if (!rootIssue) {
295
+ throw new Error(`Linear issue ID '${issueId}' not found.`);
296
+ }
297
+ const issueMap = new Map([[rootIssue.id, rootIssue]]);
298
+ const pendingChildIds = (rootIssue.children?.nodes ?? []).map((child) => child.id);
299
+ const fetchedChildIds = new Set();
300
+ let queueIndex = 0;
301
+ while (queueIndex < pendingChildIds.length) {
302
+ const childId = pendingChildIds[queueIndex];
303
+ queueIndex += 1;
304
+ if (!childId || fetchedChildIds.has(childId)) {
305
+ continue;
306
+ }
307
+ fetchedChildIds.add(childId);
308
+ const childIssue = await this.linearClient.getIssueById(childId);
309
+ if (!childIssue) {
310
+ continue;
311
+ }
312
+ issueMap.set(childIssue.id, childIssue);
313
+ for (const grandchild of childIssue.children?.nodes ?? []) {
314
+ if (!fetchedChildIds.has(grandchild.id)) {
315
+ pendingChildIds.push(grandchild.id);
316
+ }
317
+ }
318
+ }
319
+ return Array.from(issueMap.values());
320
+ }
321
+ /**
322
+ * Synchronizes issues from Linear into a LocalGraph format.
323
+ * @returns A promise that resolves to a LocalGraph instance.
324
+ */
325
+ async syncIn(issueIdentifier) {
326
+ const linearConfig = this.config.tracker?.linear;
327
+ if (!linearConfig || !linearConfig.enabled) {
328
+ throw new Error("Linear tracker not enabled in config.");
329
+ }
330
+ const teamId = linearConfig.teamId;
331
+ const projectId = linearConfig.projectId;
332
+ let issues = [];
333
+ let teamName;
334
+ let resolvedTeamId;
335
+ let sourceId;
336
+ let sourceDoc;
337
+ let activeClusterId;
338
+ let clusterTitle;
339
+ if (issueIdentifier) {
340
+ issues = await this.collectIssueAndChildren(issueIdentifier);
341
+ sourceId = issueIdentifier;
342
+ sourceDoc = `Synchronized from Linear issue identifier: ${issueIdentifier}`;
343
+ activeClusterId = issueIdentifier;
344
+ clusterTitle = `Linear Issue: ${issueIdentifier}`;
345
+ }
346
+ else if (teamId) {
347
+ const teamsResponse = await this.linearClient.listTeams();
348
+ const team = teamsResponse.find(t => t.id === teamId || t.name === teamId);
349
+ if (!team) {
350
+ throw new Error(`Linear team with ID or name '${teamId}' not found.`);
351
+ }
352
+ teamName = team.name;
353
+ resolvedTeamId = team.id;
354
+ const listIssuesArgs = { teamId: team.id };
355
+ if (projectId) {
356
+ const projectsResponse = await this.linearClient.listProjects(team.id);
357
+ const project = projectsResponse.find(p => p.id === projectId || p.name === projectId);
358
+ if (!project) {
359
+ throw new Error(`Linear project with ID or name '${projectId}' not found in team '${team.name}'.`);
360
+ }
361
+ listIssuesArgs.projectId = project.id;
362
+ }
363
+ issues = await this.linearClient.listIssues(listIssuesArgs);
364
+ sourceId = resolvedTeamId ?? teamId;
365
+ sourceDoc = `Synchronized from Linear team: ${teamName}${projectId ? `, project: ${projectId}` : ''}`;
366
+ if (projectId && teamName) {
367
+ activeClusterId = `${teamName}-${projectId}`;
368
+ clusterTitle = `Linear Project: ${projectId} (${teamName})`;
369
+ }
370
+ else {
371
+ activeClusterId = teamName;
372
+ clusterTitle = `Linear Team: ${teamName}`;
373
+ }
374
+ }
375
+ else {
376
+ console.warn("No Linear teamId specified in config. Fetching all accessible Linear issues. Consider specifying 'teamId' for better scope.");
377
+ issues = await this.linearClient.listIssues({});
378
+ sourceId = "all-linear-issues";
379
+ sourceDoc = "Synchronized from Linear team: all";
380
+ activeClusterId = "default";
381
+ clusterTitle = "All Linear Issues";
382
+ }
383
+ const nodes = {};
384
+ const dependencies = {};
385
+ const clusters = {};
386
+ clusters[activeClusterId] = {
387
+ id: activeClusterId,
388
+ title: clusterTitle,
389
+ children: [],
390
+ };
391
+ const clusterChildren = new Set();
392
+ // In single-target mode the first issue is the cluster root — a context node,
393
+ // not a dispatch target. Only its fetched descendants are runnable children.
394
+ // Relation-referenced issues go into the nodes map for graph reference but
395
+ // must not be promoted to runnable children.
396
+ const clusterRootNodeId = issueIdentifier && issues.length > 0 ? this.issueNodeId(issues[0]) : null;
397
+ for (const issue of issues) {
398
+ this.mapIssueRelations(issue, nodes, dependencies);
399
+ if (clusterRootNodeId !== null) {
400
+ // Single-target mode: every fetched descendant except the root is runnable.
401
+ const nodeId = this.issueNodeId(issue);
402
+ if (nodeId !== clusterRootNodeId) {
403
+ clusterChildren.add(nodeId);
404
+ }
405
+ // Relation targets are added to nodes by mapIssueRelations above but
406
+ // are intentionally excluded from runnable children here.
407
+ }
408
+ else {
409
+ // Team/project mode: all fetched issues plus their relations are runnable.
410
+ clusterChildren.add(this.issueNodeId(issue));
411
+ for (const relation of issue.relations?.nodes ?? []) {
412
+ if (relation.issue) {
413
+ clusterChildren.add(this.issueNodeId(relation.issue));
414
+ }
415
+ if (relation.relatedIssue) {
416
+ clusterChildren.add(this.issueNodeId(relation.relatedIssue));
417
+ }
418
+ }
419
+ for (const relation of issue.inverseRelations?.nodes ?? []) {
420
+ if (relation.issue) {
421
+ clusterChildren.add(this.issueNodeId(relation.issue));
422
+ }
423
+ if (relation.relatedIssue) {
424
+ clusterChildren.add(this.issueNodeId(relation.relatedIssue));
425
+ }
426
+ }
427
+ for (const childIssue of issue.children?.nodes ?? []) {
428
+ clusterChildren.add(this.issueNodeId(childIssue));
429
+ }
430
+ }
431
+ }
432
+ // Single-target leaf: root has no implementation children → root is itself runnable.
433
+ if (clusterRootNodeId !== null && clusterChildren.size === 0) {
434
+ clusterChildren.add(clusterRootNodeId);
435
+ }
436
+ clusters[activeClusterId] = {
437
+ ...clusters[activeClusterId],
438
+ children: Array.from(clusterChildren),
439
+ ...(clusterRootNodeId !== null ? { cluster_root: clusterRootNodeId } : {}),
440
+ };
441
+ const graph = {
442
+ schemaVersion: "v2",
443
+ source: {
444
+ id: sourceId,
445
+ type: "Linear",
446
+ analysis: {
447
+ id: "initial-sync",
448
+ doc: sourceDoc,
449
+ },
450
+ },
451
+ nodes: nodes,
452
+ dependencies: dependencies,
453
+ clusters: clusters,
454
+ activeCluster: activeClusterId,
455
+ };
456
+ const validatedGraph = schema_js_1.executionGraphV2Schema.parse(graph);
457
+ return local_graph_js_1.LocalGraph.fromGraph(validatedGraph);
458
+ }
459
+ /**
460
+ * Synchronizes local changes back to Linear.
461
+ * This includes status updates, comments, and link mutations.
462
+ */
463
+ async syncOut() {
464
+ console.log("LinearAdapter: syncOut not yet fully implemented.");
465
+ // TODO: Implement export/apply behavior for queued status/comment/link mutations.
466
+ // This will involve using mcp_linear_save_issue and mcp_linear_save_comment.
467
+ }
468
+ /**
469
+ * Returns the capabilities supported by the Linear adapter.
470
+ */
471
+ getCapabilities() {
472
+ return {
473
+ supportsChildRelationships: true,
474
+ supportsStatusUpdates: true,
475
+ supportsComments: true,
476
+ supportsLinks: true,
477
+ supportsDependencies: true,
478
+ supportsLifecycleMapping: true,
479
+ supportsCreateChild: true,
480
+ };
481
+ }
482
+ /**
483
+ * Maps a Linear state to a normalized lifecycle state.
484
+ *
485
+ * This mapping is heuristic and team-agnostic. It uses common patterns
486
+ * from Linear workflows but does not hardcode specific team state names.
487
+ * Teams with custom state names may need to configure explicit mappings.
488
+ *
489
+ * @param nativeStatus - The native Linear state name.
490
+ * @returns A status mapping result with the normalized state and support status.
491
+ */
492
+ mapNativeStatus(nativeStatus) {
493
+ const normalizedStatus = nativeStatus.toLowerCase().trim();
494
+ // Common backlog state patterns
495
+ if (["backlog", "todo", "idea", "suggestion", "unstarted"].includes(normalizedStatus)) {
496
+ return { lifecycleState: "backlog", supported: true };
497
+ }
498
+ // Common in-progress state patterns
499
+ if (["in progress", "in-progress", "started", "doing", "active", "wip"].includes(normalizedStatus)) {
500
+ return { lifecycleState: "in_progress", supported: true };
501
+ }
502
+ // Common in-review state patterns
503
+ if (["in review", "in-review", "review", "under review", "pending review", "ready for review"].includes(normalizedStatus)) {
504
+ return { lifecycleState: "in_review", supported: true };
505
+ }
506
+ // Common done state patterns
507
+ if (["done", "completed", "finished", "closed", "resolved", "shipped", "released"].includes(normalizedStatus)) {
508
+ return { lifecycleState: "done", supported: true };
509
+ }
510
+ // Common blocked state patterns
511
+ if (["blocked", "blocked by", "waiting", "on hold", "paused", "stuck"].includes(normalizedStatus)) {
512
+ return { lifecycleState: "blocked", supported: true };
513
+ }
514
+ // Common cancelled state patterns
515
+ if (["cancelled", "canceled", "declined", "won't do", "wont do", "duplicate", "invalid"].includes(normalizedStatus)) {
516
+ return { lifecycleState: "cancelled", supported: true };
517
+ }
518
+ // Unknown state - treat as unsupported but don't fail
519
+ return {
520
+ lifecycleState: "no_status_change",
521
+ supported: false,
522
+ reason: `Unknown Linear state '${nativeStatus}'. Consider adding explicit state mapping configuration.`,
523
+ };
524
+ }
525
+ /**
526
+ * Attempts to transition a Linear issue to a lifecycle state.
527
+ *
528
+ * @param taskId - The Linear issue ID or identifier.
529
+ * @param lifecycleState - The target normalized lifecycle state.
530
+ * @param evidence - Optional evidence for the transition (e.g., commit hash).
531
+ * @returns A lifecycle transition result indicating success, skip, or failure.
532
+ */
533
+ async transitionLifecycleState(taskId, lifecycleState, evidence) {
534
+ if (lifecycleState === "no_status_change") {
535
+ return {
536
+ applied: false,
537
+ skipped: true,
538
+ skipReason: "Lifecycle state is 'no_status_change', skipping transition",
539
+ };
540
+ }
541
+ // Linear requires team-specific state IDs for state transitions.
542
+ // This implementation is a placeholder - full implementation would need:
543
+ // 1. Fetch available states for the issue's team
544
+ // 2. Map normalized lifecycle state to a specific Linear state ID
545
+ // 3. Use GraphQL mutation to update the issue state
546
+ console.warn(`LinearAdapter: transitionLifecycleState not fully implemented. Would transition ${taskId} to ${lifecycleState}.`);
547
+ return {
548
+ applied: false,
549
+ skipped: true,
550
+ skipReason: "Lifecycle state transitions are not yet implemented for Linear adapter",
551
+ };
552
+ }
553
+ /**
554
+ * Adds a comment to a Linear issue.
555
+ *
556
+ * @param taskId - The Linear issue ID or identifier.
557
+ * @param body - The comment body text.
558
+ * @returns A comment result indicating success, skip, or failure.
559
+ */
560
+ async addComment(taskId, body) {
561
+ // Placeholder implementation - would use GraphQL mutation to create comment
562
+ console.warn(`LinearAdapter: addComment not fully implemented. Would add comment to ${taskId}.`);
563
+ return {
564
+ added: false,
565
+ unsupported: false,
566
+ error: "Adding comments is not yet implemented for Linear adapter",
567
+ };
568
+ }
569
+ /**
570
+ * Attaches a link to a Linear issue.
571
+ *
572
+ * @param taskId - The Linear issue ID or identifier.
573
+ * @param url - The URL to attach.
574
+ * @param title - Optional title for the link.
575
+ * @returns A link result indicating success, skip, or failure.
576
+ */
577
+ async attachLink(taskId, url, title) {
578
+ // Linear doesn't have a native "link" attachment concept.
579
+ // Links are typically added as comments or in the description.
580
+ console.warn(`LinearAdapter: attachLink not supported. Linear uses comments for links.`);
581
+ return {
582
+ attached: false,
583
+ unsupported: true,
584
+ reason: "Linear does not have native link attachments. Use addComment to include URLs.",
585
+ };
586
+ }
587
+ /**
588
+ * Adds a dependency relation between Linear issues.
589
+ *
590
+ * @param taskId - The ID of the issue that depends on another.
591
+ * @param dependsOnTaskId - The ID of the issue that is depended on.
592
+ * @returns A dependency result indicating success, skip, or failure.
593
+ */
594
+ async addDependency(taskId, dependsOnTaskId) {
595
+ // Placeholder implementation - would use GraphQL mutation to create relation
596
+ console.warn(`LinearAdapter: addDependency not fully implemented. Would add dependency from ${taskId} to ${dependsOnTaskId}.`);
597
+ return {
598
+ added: false,
599
+ unsupported: false,
600
+ error: "Adding dependencies is not yet implemented for Linear adapter",
601
+ };
602
+ }
603
+ /**
604
+ * Creates a child Linear issue under a parent issue.
605
+ *
606
+ * @param parentId - The ID of the parent issue.
607
+ * @param title - The title of the child issue.
608
+ * @param body - Optional body/description of the child issue.
609
+ * @returns A create child result indicating success, skip, or failure.
610
+ */
611
+ async createChild(parentId, title, body) {
612
+ // Placeholder implementation - would use GraphQL mutation to create issue with parent
613
+ console.warn(`LinearAdapter: createChild not fully implemented. Would create child '${title}' under ${parentId}.`);
614
+ return {
615
+ created: false,
616
+ unsupported: false,
617
+ error: "Creating child issues is not yet implemented for Linear adapter",
618
+ };
619
+ }
620
+ }
621
+ exports.LinearAdapter = LinearAdapter;