@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,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executionGraphV1Schema = exports.executionGraphV2Schema = void 0;
4
+ const zod_1 = require("zod");
5
+ /**
6
+ * The full v2 execution graph schema.
7
+ * Use this for validation when loading a graph from disk.
8
+ */
9
+ exports.executionGraphV2Schema = zod_1.z.object({
10
+ schemaVersion: zod_1.z.literal("v2"),
11
+ /** Source of truth metadata for the execution cluster. */
12
+ source: zod_1.z.object({
13
+ id: zod_1.z.string(),
14
+ type: zod_1.z.string(),
15
+ analysis: zod_1.z
16
+ .object({
17
+ id: zod_1.z.string(),
18
+ doc: zod_1.z.string().nullable().optional(),
19
+ })
20
+ .optional(),
21
+ }),
22
+ /** Normalized map of all nodes (issues) in the graph. */
23
+ nodes: zod_1.z.record(zod_1.z.object({
24
+ id: zod_1.z.string(),
25
+ title: zod_1.z.string(),
26
+ status: zod_1.z.string(),
27
+ sessionType: zod_1.z.string().optional(),
28
+ /** Markdown body / description of the issue. */
29
+ body: zod_1.z.string().optional(),
30
+ })),
31
+ /** Dependency map. Key is the node ID, value is an array of node IDs it is blocked by. */
32
+ dependencies: zod_1.z.record(zod_1.z.array(zod_1.z.string())),
33
+ /**
34
+ * Clusters are ordered groups of nodes.
35
+ * This is the primary execution structure.
36
+ */
37
+ clusters: zod_1.z.record(zod_1.z.object({
38
+ id: zod_1.z.string(),
39
+ title: zod_1.z.string(),
40
+ /**
41
+ * Runnable children only — issue IDs to be dispatched to workers.
42
+ * Does not include the cluster root or context/reference nodes.
43
+ * When the cluster root itself is the only work item (leaf cluster),
44
+ * it appears here and also as cluster_root.
45
+ */
46
+ children: zod_1.z.array(zod_1.z.string()),
47
+ /**
48
+ * The issue that was passed as the target to `polaris run`.
49
+ * This is a context/coordination node. It must not be dispatched
50
+ * as a worker child unless it is also the only entry in children
51
+ * (leaf cluster with no implementation sub-issues).
52
+ */
53
+ cluster_root: zod_1.z.string().optional(),
54
+ })),
55
+ /** The active cluster ID to be used for execution. */
56
+ activeCluster: zod_1.z.string(),
57
+ }).superRefine((data, ctx) => {
58
+ if (data.activeCluster && !data.clusters[data.activeCluster]) {
59
+ ctx.addIssue({
60
+ code: zod_1.z.ZodIssueCode.custom,
61
+ path: ["activeCluster"],
62
+ message: `activeCluster '${data.activeCluster}' does not exist in the clusters map`,
63
+ });
64
+ }
65
+ });
66
+ /**
67
+ * The v1 `clusters.json` schema.
68
+ * This is used for migration purposes.
69
+ */
70
+ exports.executionGraphV1Schema = zod_1.z.object({
71
+ source_id: zod_1.z.string(),
72
+ analyze_source_id: zod_1.z.string().optional(),
73
+ source_type: zod_1.z.string(),
74
+ created_at: zod_1.z.string(),
75
+ analysis_doc: zod_1.z.string().nullable().optional(),
76
+ clusters: zod_1.z.array(zod_1.z.object({
77
+ cluster_id: zod_1.z.string(),
78
+ description: zod_1.z.string(),
79
+ children: zod_1.z.array(zod_1.z.object({
80
+ id: zod_1.z.string(),
81
+ title: zod_1.z.string(),
82
+ session_type: zod_1.z.string(),
83
+ blockedBy: zod_1.z.array(zod_1.z.string()),
84
+ })),
85
+ })),
86
+ });
@@ -0,0 +1,404 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.TrackerSyncService = void 0;
7
+ const node_crypto_1 = require("node:crypto");
8
+ const promises_1 = require("node:fs/promises");
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const store_js_1 = require("../../cluster-state/store.js");
11
+ const queue_store_js_1 = require("./queue-store.js");
12
+ const lifecycle_policy_js_1 = require("../lifecycle-policy.js");
13
+ class TrackerSyncService {
14
+ adapter;
15
+ mutationQueue = [];
16
+ localGraphRef;
17
+ repoRoot;
18
+ clusterId;
19
+ queueFilePath;
20
+ lifecyclePolicy;
21
+ ready;
22
+ constructor(adapter, localGraph, options = {}) {
23
+ this.adapter = adapter;
24
+ this.localGraphRef = localGraph;
25
+ this.repoRoot = options.repoRoot ?? process.cwd();
26
+ this.clusterId = options.clusterId;
27
+ this.queueFilePath = options.queueFilePath ?? node_path_1.default.join(this.repoRoot, ".taskchain_artifacts", "polaris-run", "mutation-queue.json");
28
+ this.lifecyclePolicy = options.lifecyclePolicy;
29
+ this.ready = this.loadQueue();
30
+ }
31
+ async loadQueue() {
32
+ this.mutationQueue = await (0, queue_store_js_1.loadMutationQueue)(this.queueFilePath);
33
+ console.log(`Loaded ${this.mutationQueue.length} mutations from queue store.`);
34
+ }
35
+ async saveQueue() {
36
+ await (0, queue_store_js_1.saveMutationQueue)(this.mutationQueue, this.queueFilePath);
37
+ console.log(`Saved ${this.mutationQueue.length} mutations to queue store.`);
38
+ }
39
+ async syncIn(input) {
40
+ console.log(`Starting sync-in for tracker: ${input.trackerId}`);
41
+ const fetchedData = await this.adapter.fetchData(input);
42
+ let syncedInCount = 0;
43
+ if (Array.isArray(fetchedData) && fetchedData.length > 0) {
44
+ syncedInCount = fetchedData.length;
45
+ console.log(`Synced in ${syncedInCount} items.`);
46
+ }
47
+ return {
48
+ syncedInCount,
49
+ mutationsQueuedCount: this.mutationQueue.length,
50
+ mutationsAppliedCount: 0,
51
+ conflictsDetectedCount: 0,
52
+ failedMutationsCount: 0,
53
+ details: [`Sync-in completed. ${syncedInCount} items processed.`],
54
+ };
55
+ }
56
+ async addMutation(mutation) {
57
+ await this.ready;
58
+ const newMutation = {
59
+ ...mutation,
60
+ id: (0, node_crypto_1.randomUUID)(),
61
+ status: 'pending',
62
+ timestamp: new Date().toISOString(),
63
+ retries: 0,
64
+ };
65
+ this.mutationQueue.push(newMutation);
66
+ await this.saveQueue();
67
+ console.log(`Mutation added to queue: ${newMutation.id}`);
68
+ }
69
+ async reconcile(dryRun = false) {
70
+ await this.ready;
71
+ console.log(`Starting reconciliation (dryRun: ${dryRun})`);
72
+ const prepared = await this.prepareValidatedQueue(dryRun);
73
+ let mutationsAppliedCount = 0;
74
+ let conflictsDetectedCount = 0;
75
+ let failedMutationsCount = prepared.failureCount;
76
+ const details = [...prepared.details];
77
+ const mutationsToProcess = this.mutationQueue.filter((mutation) => mutation.status === 'pending' || mutation.status === 'failed');
78
+ for (const mutation of mutationsToProcess) {
79
+ if (dryRun) {
80
+ details.push(`Dry run: Would apply mutation ${mutation.id} (${mutation.type} ${mutation.entityType}:${mutation.entityId})`);
81
+ continue;
82
+ }
83
+ try {
84
+ const localEntity = this.localGraphRef.getNode(mutation.entityId);
85
+ if (localEntity) {
86
+ const remoteEntity = await this.adapter.fetchData({ trackerId: mutation.entityId });
87
+ if (remoteEntity && this.adapter.detectConflict(localEntity, remoteEntity)) {
88
+ mutation.status = 'conflicted';
89
+ conflictsDetectedCount++;
90
+ mutation.error = 'Remote tracker entity changed since local validation.';
91
+ await this.syncClusterMutationStatus(mutation, 'conflicted', mutation.error);
92
+ details.push(`Conflict detected for mutation ${mutation.id}. Remote entity changed.`);
93
+ continue;
94
+ }
95
+ }
96
+ mutation.status = 'sent';
97
+ await this.syncClusterMutationStatus(mutation, 'sent');
98
+ const updatedMutation = await this.adapter.applyMutation(mutation);
99
+ Object.assign(mutation, updatedMutation);
100
+ if (updatedMutation.status === 'succeeded') {
101
+ mutationsAppliedCount++;
102
+ await this.syncClusterMutationStatus(mutation, 'succeeded');
103
+ details.push(`Successfully applied mutation ${mutation.id}`);
104
+ }
105
+ else {
106
+ mutation.status = updatedMutation.status === 'conflicted' ? 'conflicted' : 'failed';
107
+ mutation.error = updatedMutation.error;
108
+ failedMutationsCount++;
109
+ await this.syncClusterMutationStatus(mutation, mutation.status, mutation.error);
110
+ details.push(`Failed to apply mutation ${mutation.id}: ${mutation.error ?? 'Unknown error'}`);
111
+ }
112
+ }
113
+ catch (error) {
114
+ mutation.retries += 1;
115
+ mutation.error = error instanceof Error ? error.message : String(error);
116
+ mutation.status = 'failed';
117
+ failedMutationsCount++;
118
+ await this.syncClusterMutationStatus(mutation, 'failed', mutation.error);
119
+ details.push(`Error applying mutation ${mutation.id} (retry ${mutation.retries}): ${mutation.error}`);
120
+ }
121
+ }
122
+ await this.saveQueue();
123
+ const report = {
124
+ syncedInCount: 0,
125
+ mutationsQueuedCount: this.mutationQueue.filter((mutation) => mutation.status === 'pending' || mutation.status === 'failed').length,
126
+ mutationsAppliedCount,
127
+ conflictsDetectedCount,
128
+ failedMutationsCount,
129
+ details,
130
+ };
131
+ console.log(`Reconciliation completed. Applied: ${mutationsAppliedCount}, Conflicts: ${conflictsDetectedCount}, Failed: ${failedMutationsCount}`);
132
+ return report;
133
+ }
134
+ getMutationQueue() {
135
+ return this.mutationQueue;
136
+ }
137
+ async prepareValidatedQueue(dryRun) {
138
+ if (!this.clusterId) {
139
+ return { preparedCount: 0, failureCount: 0, details: [] };
140
+ }
141
+ const clusterState = await (0, store_js_1.readClusterState)(this.clusterId, this.repoRoot);
142
+ if (!clusterState) {
143
+ return {
144
+ preparedCount: 0,
145
+ failureCount: 1,
146
+ details: [`Cluster state for ${this.clusterId} is missing; cannot prepare tracker reconciliation.`],
147
+ };
148
+ }
149
+ const nextQueue = [...this.mutationQueue];
150
+ const trackerMutations = { ...(clusterState.tracker_mutations ?? {}) };
151
+ const preparedDetails = [];
152
+ let preparedCount = 0;
153
+ let failureCount = 0;
154
+ let changed = false;
155
+ const sourceGeneration = clusterState.state_generation;
156
+ for (const childState of clusterState.child_states) {
157
+ if (childState.status !== 'done') {
158
+ continue;
159
+ }
160
+ const existingRef = trackerMutations[childState.id];
161
+ if (existingRef?.status === 'succeeded') {
162
+ continue;
163
+ }
164
+ const evidence = await this.validateCompletionEvidence(clusterState, childState.id);
165
+ if (!evidence.valid) {
166
+ failureCount += 1;
167
+ preparedDetails.push(`Skipped ${childState.id}: ${evidence.reason}`);
168
+ const blockedRef = this.buildTrackerReference({
169
+ existing: existingRef,
170
+ childId: childState.id,
171
+ idempotencyKey: existingRef?.idempotency_key ?? `blocked:${childState.id}`,
172
+ mutationIds: existingRef?.mutation_ids ?? [],
173
+ status: 'blocked',
174
+ resultFile: clusterState.result_pointers[childState.id] ?? '',
175
+ packetFile: clusterState.packet_pointers[childState.id],
176
+ commit: clusterState.commits[childState.id],
177
+ sourceStateGeneration: sourceGeneration,
178
+ error: evidence.reason,
179
+ });
180
+ if (!this.sameTrackerReference(existingRef, blockedRef)) {
181
+ trackerMutations[childState.id] = blockedRef;
182
+ changed = true;
183
+ }
184
+ continue;
185
+ }
186
+ const idempotencyKey = this.buildIdempotencyKey(evidence.value);
187
+ const existingQueueMutation = nextQueue.find((mutation) => mutation.operationId === idempotencyKey);
188
+ if (existingRef?.idempotency_key === idempotencyKey && existingQueueMutation) {
189
+ trackerMutations[childState.id] = this.buildTrackerReference({
190
+ existing: existingRef,
191
+ childId: childState.id,
192
+ idempotencyKey,
193
+ mutationIds: [existingQueueMutation.id],
194
+ status: this.normalizeMutationStatus(existingQueueMutation.status),
195
+ resultFile: evidence.value.resultFile,
196
+ packetFile: evidence.value.packetFile,
197
+ commit: evidence.value.commit,
198
+ sourceStateGeneration: evidence.value.sourceStateGeneration,
199
+ error: existingQueueMutation.error,
200
+ });
201
+ continue;
202
+ }
203
+ let queuedMutation = existingQueueMutation;
204
+ if (!queuedMutation) {
205
+ // Resolve the lifecycle state from policy for validation-passed children
206
+ const lifecycleTransition = (0, lifecycle_policy_js_1.resolveLifecycleTransition)("child-validation-passed", this.lifecyclePolicy);
207
+ // Skip creating a mutation if the policy says to skip or target is no_status_change
208
+ if (lifecycleTransition.skip || lifecycleTransition.targetState === "no_status_change") {
209
+ continue;
210
+ }
211
+ const targetState = lifecycleTransition.targetState;
212
+ const newMutation = {
213
+ id: (0, node_crypto_1.randomUUID)(),
214
+ operationId: idempotencyKey,
215
+ type: "update",
216
+ entityType: "issue",
217
+ entityId: childState.id,
218
+ payload: {
219
+ state: targetState,
220
+ },
221
+ status: "pending",
222
+ timestamp: new Date().toISOString(),
223
+ retries: 0,
224
+ evidence: {
225
+ ...evidence.value,
226
+ clusterId: this.clusterId,
227
+ childId: childState.id,
228
+ },
229
+ };
230
+ nextQueue.push(newMutation);
231
+ queuedMutation = newMutation;
232
+ preparedCount += 1;
233
+ changed = true;
234
+ preparedDetails.push(`Queued validated tracker mutation for ${childState.id} (target state: ${targetState}).`);
235
+ }
236
+ if (!queuedMutation) {
237
+ continue;
238
+ }
239
+ const pendingRef = this.buildTrackerReference({
240
+ existing: existingRef,
241
+ childId: childState.id,
242
+ idempotencyKey,
243
+ mutationIds: [queuedMutation.id],
244
+ status: this.normalizeMutationStatus(queuedMutation.status),
245
+ resultFile: evidence.value.resultFile,
246
+ packetFile: evidence.value.packetFile,
247
+ commit: evidence.value.commit,
248
+ sourceStateGeneration: evidence.value.sourceStateGeneration,
249
+ error: queuedMutation.error,
250
+ });
251
+ if (!this.sameTrackerReference(existingRef, pendingRef)) {
252
+ trackerMutations[childState.id] = pendingRef;
253
+ changed = true;
254
+ }
255
+ }
256
+ if (!dryRun && changed) {
257
+ this.mutationQueue = nextQueue;
258
+ await this.saveQueue();
259
+ await (0, store_js_1.writeClusterState)(this.clusterId, {
260
+ ...clusterState,
261
+ state_generation: clusterState.state_generation + 1,
262
+ tracker_mutations: trackerMutations,
263
+ }, this.repoRoot);
264
+ }
265
+ return { preparedCount, failureCount, details: preparedDetails };
266
+ }
267
+ async validateCompletionEvidence(clusterState, childId) {
268
+ const packetFile = clusterState.packet_pointers[childId];
269
+ if (!packetFile) {
270
+ return { valid: false, reason: 'missing packet pointer' };
271
+ }
272
+ const resultFile = clusterState.result_pointers[childId];
273
+ if (!resultFile) {
274
+ return { valid: false, reason: 'missing sealed result pointer' };
275
+ }
276
+ const validation = clusterState.validation_results[childId];
277
+ if (!validation?.passed) {
278
+ return { valid: false, reason: 'validation result is missing or failed' };
279
+ }
280
+ const commit = clusterState.commits[childId];
281
+ if (!commit) {
282
+ return { valid: false, reason: 'missing commit evidence' };
283
+ }
284
+ let packet;
285
+ let result;
286
+ try {
287
+ packet = JSON.parse(await (0, promises_1.readFile)(packetFile, 'utf-8'));
288
+ }
289
+ catch (error) {
290
+ return {
291
+ valid: false,
292
+ reason: `unable to read packet file (${error instanceof Error ? error.message : String(error)})`,
293
+ };
294
+ }
295
+ try {
296
+ result = JSON.parse(await (0, promises_1.readFile)(resultFile, 'utf-8'));
297
+ }
298
+ catch (error) {
299
+ return {
300
+ valid: false,
301
+ reason: `unable to read result file (${error instanceof Error ? error.message : String(error)})`,
302
+ };
303
+ }
304
+ if (packet.cluster_id !== this.clusterId || packet.active_child !== childId || !packet.run_id) {
305
+ return { valid: false, reason: 'packet metadata does not match the active cluster child' };
306
+ }
307
+ if (result.run_id !== packet.run_id || result.child_id !== childId) {
308
+ return { valid: false, reason: 'sealed result does not match packet run/child identifiers' };
309
+ }
310
+ if (result.status !== 'success') {
311
+ return { valid: false, reason: `sealed result status is ${result.status ?? 'missing'}` };
312
+ }
313
+ if (!result.commit || result.commit !== commit) {
314
+ return { valid: false, reason: 'sealed result commit does not match cluster-state commit evidence' };
315
+ }
316
+ return {
317
+ valid: true,
318
+ value: {
319
+ runId: packet.run_id,
320
+ packetFile,
321
+ resultFile,
322
+ commit,
323
+ sourceStateGeneration: clusterState.state_generation,
324
+ validatedAt: new Date().toISOString(),
325
+ },
326
+ };
327
+ }
328
+ buildIdempotencyKey(evidence) {
329
+ return (0, node_crypto_1.createHash)('sha256')
330
+ .update([
331
+ this.clusterId ?? '',
332
+ evidence.runId,
333
+ evidence.resultFile,
334
+ evidence.commit,
335
+ ].join(':'))
336
+ .digest('hex');
337
+ }
338
+ async syncClusterMutationStatus(mutation, status, error) {
339
+ if (!this.clusterId || !mutation.evidence) {
340
+ return;
341
+ }
342
+ const clusterState = await (0, store_js_1.readClusterState)(this.clusterId, this.repoRoot);
343
+ if (!clusterState) {
344
+ return;
345
+ }
346
+ const existing = clusterState.tracker_mutations[mutation.entityId];
347
+ const next = this.buildTrackerReference({
348
+ existing,
349
+ childId: mutation.entityId,
350
+ idempotencyKey: mutation.operationId,
351
+ mutationIds: [mutation.id],
352
+ status,
353
+ resultFile: mutation.evidence.resultFile,
354
+ packetFile: mutation.evidence.packetFile,
355
+ commit: mutation.evidence.commit,
356
+ sourceStateGeneration: mutation.evidence.sourceStateGeneration,
357
+ error,
358
+ attemptedAt: new Date().toISOString(),
359
+ });
360
+ if (this.sameTrackerReference(existing, next)) {
361
+ return;
362
+ }
363
+ await (0, store_js_1.writeClusterState)(this.clusterId, {
364
+ ...clusterState,
365
+ state_generation: clusterState.state_generation + 1,
366
+ tracker_mutations: {
367
+ ...clusterState.tracker_mutations,
368
+ [mutation.entityId]: next,
369
+ },
370
+ }, this.repoRoot);
371
+ }
372
+ buildTrackerReference(input) {
373
+ const updatedAt = new Date().toISOString();
374
+ return {
375
+ mutation_ids: input.mutationIds,
376
+ idempotency_key: input.idempotencyKey,
377
+ source_state_generation: input.sourceStateGeneration,
378
+ result_file: input.resultFile,
379
+ packet_file: input.packetFile,
380
+ commit: input.commit,
381
+ status: input.status,
382
+ updated_at: updatedAt,
383
+ last_attempted_at: input.attemptedAt ?? input.existing?.last_attempted_at,
384
+ last_error: input.error,
385
+ };
386
+ }
387
+ normalizeMutationStatus(status) {
388
+ if (status === 'sent') {
389
+ return 'sent';
390
+ }
391
+ return status;
392
+ }
393
+ sameTrackerReference(left, right) {
394
+ if (!left) {
395
+ return false;
396
+ }
397
+ const normalize = (value) => ({
398
+ ...value,
399
+ updated_at: undefined,
400
+ });
401
+ return JSON.stringify(normalize(left)) === JSON.stringify(normalize(right));
402
+ }
403
+ }
404
+ exports.TrackerSyncService = TrackerSyncService;
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.loadMutationQueue = loadMutationQueue;
37
+ exports.saveMutationQueue = saveMutationQueue;
38
+ const promises_1 = require("node:fs/promises");
39
+ const path = __importStar(require("node:path"));
40
+ /**
41
+ * Loads the mutation queue from a JSON file.
42
+ * If the file does not exist, an empty array is returned.
43
+ * @param filePath The path to the mutation queue JSON file. Defaults to .taskchain_artifacts/polaris-run/mutation-queue.json.
44
+ * @returns A promise that resolves to an array of MutationRecord.
45
+ */
46
+ async function loadMutationQueue(filePath = path.join(process.cwd(), '.taskchain_artifacts', 'polaris-run', 'mutation-queue.json')) {
47
+ try {
48
+ await (0, promises_1.mkdir)(path.dirname(filePath), { recursive: true });
49
+ const fileContent = await (0, promises_1.readFile)(filePath, 'utf-8');
50
+ const parsed = JSON.parse(fileContent);
51
+ // Validate that the parsed result is the expected queue shape
52
+ if (!Array.isArray(parsed)) {
53
+ throw new Error('Mutation queue file must contain an array');
54
+ }
55
+ for (const item of parsed) {
56
+ if (typeof item !== 'object' ||
57
+ item === null ||
58
+ typeof item.id !== 'string' ||
59
+ typeof item.operationId !== 'string' ||
60
+ typeof item.type !== 'string' ||
61
+ typeof item.entityType !== 'string' ||
62
+ typeof item.entityId !== 'string' ||
63
+ typeof item.payload !== 'object' ||
64
+ typeof item.status !== 'string' ||
65
+ typeof item.timestamp !== 'string' ||
66
+ typeof item.retries !== 'number') {
67
+ throw new Error('Invalid mutation record format in queue file');
68
+ }
69
+ }
70
+ return parsed;
71
+ }
72
+ catch (error) {
73
+ if (error.code === 'ENOENT') {
74
+ return [];
75
+ }
76
+ console.error('Error loading mutation queue:', error);
77
+ throw error;
78
+ }
79
+ }
80
+ /**
81
+ * Saves the mutation queue to a JSON file.
82
+ * @param queue The array of MutationRecord to save.
83
+ * @param filePath The path to the mutation queue JSON file. Defaults to .taskchain_artifacts/polaris-run/mutation-queue.json.
84
+ * @returns A promise that resolves when the queue has been saved.
85
+ */
86
+ async function saveMutationQueue(queue, filePath = path.join(process.cwd(), '.taskchain_artifacts', 'polaris-run', 'mutation-queue.json')) {
87
+ try {
88
+ await (0, promises_1.mkdir)(path.dirname(filePath), { recursive: true });
89
+ await (0, promises_1.writeFile)(filePath, JSON.stringify(queue, null, 2), 'utf-8');
90
+ }
91
+ catch (error) {
92
+ console.error('Error saving mutation queue:', error);
93
+ throw error;
94
+ }
95
+ }
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const node_path_1 = require("node:path");
5
+ const promises_1 = require("node:fs/promises");
6
+ const node_os_1 = require("node:os");
7
+ const promises_2 = require("node:fs/promises");
8
+ const queue_store_1 = require("./queue-store");
9
+ (0, vitest_1.describe)('queue-store', () => {
10
+ let testDir;
11
+ let testFilePath;
12
+ (0, vitest_1.beforeEach)(async () => {
13
+ testDir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'queue-store-test-'));
14
+ testFilePath = (0, node_path_1.join)(testDir, '.polaris', 'runs', 'mutation-queue.json');
15
+ });
16
+ (0, vitest_1.afterEach)(async () => {
17
+ await (0, promises_1.rm)(testDir, { recursive: true, force: true });
18
+ });
19
+ (0, vitest_1.it)('should load an empty array if the queue file does not exist', async () => {
20
+ const queue = await (0, queue_store_1.loadMutationQueue)(testFilePath);
21
+ (0, vitest_1.expect)(queue).toEqual([]);
22
+ });
23
+ (0, vitest_1.it)('should save and load a queue with multiple records', async () => {
24
+ const mockQueue = [
25
+ {
26
+ id: 'mut-1',
27
+ operationId: 'op-1',
28
+ type: 'create',
29
+ entityType: 'issue',
30
+ entityId: 'local-1',
31
+ payload: { title: 'Test Issue 1' },
32
+ status: 'pending',
33
+ timestamp: new Date().toISOString(),
34
+ retries: 0,
35
+ },
36
+ {
37
+ id: 'mut-2',
38
+ operationId: 'op-2',
39
+ type: 'update',
40
+ entityType: 'issue',
41
+ entityId: 'local-2',
42
+ payload: { description: 'Updated description' },
43
+ status: 'failed',
44
+ timestamp: new Date().toISOString(),
45
+ retries: 1,
46
+ error: 'Network error',
47
+ },
48
+ ];
49
+ await (0, queue_store_1.saveMutationQueue)(mockQueue, testFilePath);
50
+ const loadedQueue = await (0, queue_store_1.loadMutationQueue)(testFilePath);
51
+ (0, vitest_1.expect)(loadedQueue).toEqual(mockQueue);
52
+ // Verify file content directly
53
+ const fileContent = await (0, promises_2.readFile)(testFilePath, 'utf-8');
54
+ (0, vitest_1.expect)(JSON.parse(fileContent)).toEqual(mockQueue);
55
+ });
56
+ (0, vitest_1.it)('should handle saving an empty queue', async () => {
57
+ await (0, queue_store_1.saveMutationQueue)([], testFilePath);
58
+ const loadedQueue = await (0, queue_store_1.loadMutationQueue)(testFilePath);
59
+ (0, vitest_1.expect)(loadedQueue).toEqual([]);
60
+ });
61
+ (0, vitest_1.it)('should overwrite existing queue data', async () => {
62
+ const initialQueue = [
63
+ {
64
+ id: 'mut-initial',
65
+ operationId: 'op-initial',
66
+ type: 'create',
67
+ entityType: 'project',
68
+ entityId: 'proj-1',
69
+ payload: { name: 'Initial Project' },
70
+ status: 'succeeded',
71
+ timestamp: new Date().toISOString(),
72
+ retries: 0,
73
+ },
74
+ ];
75
+ await (0, queue_store_1.saveMutationQueue)(initialQueue, testFilePath);
76
+ const newQueue = [
77
+ {
78
+ id: 'mut-new',
79
+ operationId: 'op-new',
80
+ type: 'update',
81
+ entityType: 'project',
82
+ entityId: 'proj-1',
83
+ payload: { name: 'Updated Project' },
84
+ status: 'pending',
85
+ timestamp: new Date().toISOString(),
86
+ retries: 0,
87
+ },
88
+ ];
89
+ await (0, queue_store_1.saveMutationQueue)(newQueue, testFilePath);
90
+ const loadedQueue = await (0, queue_store_1.loadMutationQueue)(testFilePath);
91
+ (0, vitest_1.expect)(loadedQueue).toEqual(newQueue);
92
+ });
93
+ (0, vitest_1.it)('should return an empty array if the file exists but is empty', async () => {
94
+ await (0, queue_store_1.saveMutationQueue)([], testFilePath); // create an empty file
95
+ const queue = await (0, queue_store_1.loadMutationQueue)(testFilePath);
96
+ (0, vitest_1.expect)(queue).toEqual([]);
97
+ });
98
+ (0, vitest_1.it)('should throw an error for malformed JSON content', async () => {
99
+ // Manually create a malformed JSON file
100
+ await (0, promises_1.mkdir)((0, node_path_1.join)(testDir, '.polaris', 'runs'), { recursive: true });
101
+ await (0, promises_1.writeFile)(testFilePath, '{"id": "malformed",', 'utf-8');
102
+ await (0, vitest_1.expect)((0, queue_store_1.loadMutationQueue)(testFilePath)).rejects.toThrow(SyntaxError);
103
+ });
104
+ });