@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,250 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BranchCustodyViolation = exports.PROTECTED_BASE_BRANCHES = void 0;
4
+ exports.isProtectedBranch = isProtectedBranch;
5
+ exports.getRefSha = getRefSha;
6
+ exports.isCommitReachableFrom = isCommitReachableFrom;
7
+ exports.assertNotOnBaseBranch = assertNotOnBaseBranch;
8
+ exports.assertDeliveryBranchMatch = assertDeliveryBranchMatch;
9
+ exports.verifyChildCommitCustody = verifyChildCommitCustody;
10
+ exports.buildDeliveryBranchName = buildDeliveryBranchName;
11
+ exports.ensureDeliveryBranch = ensureDeliveryBranch;
12
+ exports.hasNonArtifactSourceChanges = hasNonArtifactSourceChanges;
13
+ exports.patternMatchesPath = patternMatchesPath;
14
+ exports.validateWorkerCommitScope = validateWorkerCommitScope;
15
+ exports.buildCustodyRecord = buildCustodyRecord;
16
+ const node_child_process_1 = require("node:child_process");
17
+ const artifact_policy_js_1 = require("../finalize/artifact-policy.js");
18
+ const node_path_1 = require("node:path");
19
+ exports.PROTECTED_BASE_BRANCHES = new Set([
20
+ "main",
21
+ "master",
22
+ "dev",
23
+ "develop",
24
+ "staging",
25
+ "production",
26
+ "release",
27
+ ]);
28
+ class BranchCustodyViolation extends Error {
29
+ constructor(message) {
30
+ super(message);
31
+ this.name = "BranchCustodyViolation";
32
+ }
33
+ }
34
+ exports.BranchCustodyViolation = BranchCustodyViolation;
35
+ /** Returns true when branchName is a known protected base branch. */
36
+ function isProtectedBranch(branchName) {
37
+ return exports.PROTECTED_BASE_BRANCHES.has(branchName.toLowerCase());
38
+ }
39
+ /** Returns the SHA for a ref, or null when the ref cannot be resolved. */
40
+ function getRefSha(repoRoot, ref) {
41
+ try {
42
+ return (0, node_child_process_1.execFileSync)("git", ["rev-parse", ref], {
43
+ cwd: repoRoot,
44
+ encoding: "utf-8",
45
+ }).trim();
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
51
+ /**
52
+ * Returns true when `commit` is an ancestor of (or equal to) `ref`.
53
+ * Uses `git merge-base --is-ancestor` which exits 0 on success.
54
+ */
55
+ function isCommitReachableFrom(repoRoot, commit, ref) {
56
+ try {
57
+ (0, node_child_process_1.execFileSync)("git", ["merge-base", "--is-ancestor", commit, ref], {
58
+ cwd: repoRoot,
59
+ stdio: "ignore",
60
+ });
61
+ return true;
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ }
67
+ /**
68
+ * Throws BranchCustodyViolation when currentBranch is a protected base branch.
69
+ * Implementation workers must run on a delivery branch, never on main/master/etc.
70
+ */
71
+ function assertNotOnBaseBranch(currentBranch) {
72
+ if (isProtectedBranch(currentBranch)) {
73
+ throw new BranchCustodyViolation(`Branch custody violation: workers must not run on base branch "${currentBranch}". ` +
74
+ `Create a delivery branch before dispatching implementation workers.`);
75
+ }
76
+ }
77
+ /**
78
+ * Throws BranchCustodyViolation when currentBranch differs from the
79
+ * delivery branch that was recorded for this cluster at first dispatch.
80
+ */
81
+ function assertDeliveryBranchMatch(currentBranch, deliveryBranch) {
82
+ if (currentBranch !== deliveryBranch) {
83
+ throw new BranchCustodyViolation(`Branch custody violation: current branch "${currentBranch}" does not match ` +
84
+ `recorded delivery branch "${deliveryBranch}". ` +
85
+ `Switch to the delivery branch before dispatching.`);
86
+ }
87
+ }
88
+ /**
89
+ * Verifies that a child commit belongs to the delivery branch and has not yet
90
+ * been merged into the base branch.
91
+ *
92
+ * Returns null on success, or a human-readable error string on failure.
93
+ */
94
+ function verifyChildCommitCustody(repoRoot, commit, deliveryBranch, baseBranch) {
95
+ const onDelivery = isCommitReachableFrom(repoRoot, commit, deliveryBranch);
96
+ if (!onDelivery) {
97
+ return (`commit ${commit} is not reachable from delivery branch "${deliveryBranch}"`);
98
+ }
99
+ const onBase = isCommitReachableFrom(repoRoot, commit, baseBranch);
100
+ if (onBase) {
101
+ return (`commit ${commit} is already reachable from base branch "${baseBranch}" ` +
102
+ `(branch custody violation: implementation committed directly to base)`);
103
+ }
104
+ return null;
105
+ }
106
+ /**
107
+ * Returns the canonical delivery branch name for a cluster.
108
+ * e.g. "POL-268" → "pol-268-delivery"
109
+ */
110
+ function buildDeliveryBranchName(clusterId) {
111
+ return clusterId.toLowerCase().replace(/_/g, "-") + "-delivery";
112
+ }
113
+ /**
114
+ * Creates and switches to `branchName`, or switches to it if it already exists.
115
+ * Checks for branch existence explicitly before choosing the git operation so
116
+ * that errors from unrelated failures are not silently swallowed.
117
+ * Throws with context when the checkout fails.
118
+ */
119
+ function ensureDeliveryBranch(repoRoot, branchName) {
120
+ const branchExists = (() => {
121
+ try {
122
+ (0, node_child_process_1.execFileSync)("git", ["rev-parse", "--verify", `refs/heads/${branchName}`], {
123
+ cwd: repoRoot,
124
+ stdio: "ignore",
125
+ });
126
+ return true;
127
+ }
128
+ catch {
129
+ return false;
130
+ }
131
+ })();
132
+ const args = branchExists ? ["checkout", branchName] : ["checkout", "-b", branchName];
133
+ try {
134
+ (0, node_child_process_1.execFileSync)("git", args, { cwd: repoRoot, stdio: "pipe" });
135
+ }
136
+ catch (err) {
137
+ const stderr = err instanceof Error && "stderr" in err ? String(err.stderr).trim() : "";
138
+ throw new Error(`ensureDeliveryBranch: git ${args.join(" ")} failed` +
139
+ (stderr ? `: ${stderr}` : "") +
140
+ ` (branch="${branchName}", repoRoot="${repoRoot}")`);
141
+ }
142
+ }
143
+ /**
144
+ * Returns true when the range baseBranch...deliveryBranch contains at least
145
+ * one non-artifact source file change (i.e. a real implementation change beyond
146
+ * Polaris artifacts).
147
+ *
148
+ * Pass the explicit delivery branch ref so the comparison is independent of
149
+ * the current HEAD position.
150
+ *
151
+ * Files under .polaris/ and .taskchain_artifacts/ are not counted as
152
+ * implementation evidence.
153
+ */
154
+ function hasNonArtifactSourceChanges(repoRoot, baseBranch, clusterId, deliveryBranch) {
155
+ try {
156
+ const output = (0, node_child_process_1.execFileSync)("git", ["diff", "--name-only", `${baseBranch}...${deliveryBranch}`], { cwd: repoRoot, encoding: "utf-8" }).trim();
157
+ if (!output)
158
+ return false;
159
+ const changedFiles = output.split("\n").filter(Boolean);
160
+ return changedFiles.some((f) => (0, artifact_policy_js_1.classifyArtifactPath)(f, clusterId) === "non-artifact");
161
+ }
162
+ catch (err) {
163
+ throw new Error(`hasNonArtifactSourceChanges: git diff failed for ${baseBranch}...${deliveryBranch}: ` +
164
+ (err instanceof Error ? err.message : String(err)));
165
+ }
166
+ }
167
+ function normalizeCommitPath(filePath) {
168
+ return node_path_1.posix.normalize(filePath.replace(/\\/g, "/")).replace(/^\.\//, "");
169
+ }
170
+ function escapeRegExp(value) {
171
+ return value.replace(/[\\^$+?.()|[\]{}]/g, "\\$&");
172
+ }
173
+ function globPatternToRegExp(pattern) {
174
+ let re = "^";
175
+ const normalized = normalizeCommitPath(pattern);
176
+ for (let i = 0; i < normalized.length; i++) {
177
+ const char = normalized[i];
178
+ if (char === "*") {
179
+ if (normalized[i + 1] === "*") {
180
+ re += ".*";
181
+ i++;
182
+ }
183
+ else {
184
+ re += "[^/]*";
185
+ }
186
+ continue;
187
+ }
188
+ if (char === "?") {
189
+ re += "[^/]";
190
+ continue;
191
+ }
192
+ re += escapeRegExp(char);
193
+ }
194
+ re += "$";
195
+ return new RegExp(re);
196
+ }
197
+ function patternMatchesPath(pattern, filePath) {
198
+ const normalizedPattern = normalizeCommitPath(pattern);
199
+ const normalizedPath = normalizeCommitPath(filePath);
200
+ if (/[\\*?\[\]]/.test(normalizedPattern)) {
201
+ return globPatternToRegExp(normalizedPattern).test(normalizedPath);
202
+ }
203
+ if (normalizedPattern.endsWith("/")) {
204
+ return normalizedPath.startsWith(normalizedPattern);
205
+ }
206
+ return normalizedPath === normalizedPattern || normalizedPath.startsWith(`${normalizedPattern}/`);
207
+ }
208
+ function getStagedFiles(repoRoot) {
209
+ const output = (0, node_child_process_1.execFileSync)("git", ["diff", "--cached", "--name-only", "--diff-filter=ACMR"], { cwd: repoRoot, encoding: "utf-8" }).trim();
210
+ if (!output) {
211
+ return [];
212
+ }
213
+ return output.split("\n").map((line) => normalizeCommitPath(line)).filter(Boolean);
214
+ }
215
+ function validateWorkerCommitScope(repoRoot, allowedScope, prohibitedWritePaths) {
216
+ const stagedFiles = getStagedFiles(repoRoot);
217
+ const violations = [];
218
+ for (const stagedFile of stagedFiles) {
219
+ const prohibitedPattern = prohibitedWritePaths.find((pattern) => patternMatchesPath(pattern, stagedFile));
220
+ if (prohibitedPattern) {
221
+ violations.push({
222
+ path: stagedFile,
223
+ kind: "prohibited",
224
+ pattern: prohibitedPattern,
225
+ });
226
+ continue;
227
+ }
228
+ const allowedPattern = allowedScope.find((pattern) => patternMatchesPath(pattern, stagedFile));
229
+ if (!allowedPattern) {
230
+ violations.push({
231
+ path: stagedFile,
232
+ kind: "out-of-scope",
233
+ pattern: allowedScope.length > 0 ? allowedScope[0] : "",
234
+ });
235
+ }
236
+ }
237
+ return { staged_files: stagedFiles, violations };
238
+ }
239
+ /**
240
+ * Builds a custody record from the current repo state.
241
+ * base_sha captures the tip of baseBranch at the moment custody is established.
242
+ */
243
+ function buildCustodyRecord(repoRoot, currentBranch, baseBranch = "main") {
244
+ const baseSha = getRefSha(repoRoot, baseBranch) ?? getRefSha(repoRoot, "HEAD") ?? "unknown";
245
+ return {
246
+ base_branch: baseBranch,
247
+ base_sha: baseSha,
248
+ delivery_branch: currentBranch,
249
+ };
250
+ }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveImplPacketInput = resolveImplPacketInput;
4
+ function toStringArray(value) {
5
+ if (!Array.isArray(value))
6
+ return [];
7
+ return value
8
+ .filter((item) => typeof item === "string")
9
+ .map((item) => item.trim())
10
+ .filter((item) => item.length > 0);
11
+ }
12
+ function resolveImplPacketInput(state, childId) {
13
+ const childMeta = state.open_children_meta?.[childId];
14
+ const title = childMeta?.["title"];
15
+ const keyRequirements = toStringArray(childMeta?.["key_requirements"]);
16
+ const allowedScope = toStringArray(childMeta?.["allowed_scope"]);
17
+ const validationCommands = toStringArray(childMeta?.["validation_commands"]);
18
+ if (allowedScope.length === 0) {
19
+ throw new Error(`cannot dispatch child ${childId}: impl packet generation refused because allowed_scope is empty`);
20
+ }
21
+ if (keyRequirements.length === 0) {
22
+ throw new Error(`cannot dispatch child ${childId}: impl packet generation refused because key_requirements is empty (would produce placeholder goal)`);
23
+ }
24
+ const validationWaiver = childMeta?.["validation_waiver"];
25
+ const hasValidationWaiver = typeof validationWaiver === "string" && validationWaiver.trim().length > 0;
26
+ if (validationCommands.length === 0 && !hasValidationWaiver) {
27
+ throw new Error(`cannot dispatch child ${childId}: impl packet generation refused because validation_commands is empty and no validation_waiver is set`);
28
+ }
29
+ return {
30
+ issueContext: {
31
+ id: childId,
32
+ title: typeof title === "string" && title.trim().length > 0 ? title : childId,
33
+ key_requirements: keyRequirements,
34
+ },
35
+ allowedScope,
36
+ validationCommands,
37
+ };
38
+ }
@@ -0,0 +1,270 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createLoopCommand = createLoopCommand;
4
+ const commander_1 = require("commander");
5
+ const node_path_1 = require("node:path");
6
+ const node_fs_1 = require("node:fs");
7
+ const continue_js_1 = require("./continue.js");
8
+ const resume_js_1 = require("./resume.js");
9
+ const status_js_1 = require("./status.js");
10
+ const abort_js_1 = require("./abort.js");
11
+ const dispatch_js_1 = require("./dispatch.js");
12
+ const parent_js_1 = require("./parent.js");
13
+ const run_bootstrap_js_1 = require("./run-bootstrap.js");
14
+ const run_preflight_js_1 = require("./run-preflight.js");
15
+ const evidence_backfill_js_1 = require("./evidence-backfill.js");
16
+ const wait_js_1 = require("./wait.js");
17
+ function defaultStateFile(repoRoot, stateFile) {
18
+ if (stateFile) {
19
+ return stateFile;
20
+ }
21
+ const newPath = (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run", "current-state.json");
22
+ const legacyPath = (0, node_path_1.join)(repoRoot, ".polaris", "runs", "current-state.json");
23
+ // If the new path exists, use it
24
+ if ((0, node_fs_1.existsSync)(newPath)) {
25
+ return newPath;
26
+ }
27
+ // If the legacy path exists, use it
28
+ if ((0, node_fs_1.existsSync)(legacyPath)) {
29
+ return legacyPath;
30
+ }
31
+ // Neither exists, return the new path so callers can create it
32
+ return newPath;
33
+ }
34
+ function failMissingSubcommand(command, commandName) {
35
+ const unknownSubcommand = command.args[0];
36
+ const message = unknownSubcommand
37
+ ? `error: unknown command '${unknownSubcommand}' for '${commandName}'. Run '${commandName} --help'.`
38
+ : `error: missing command for '${commandName}'. Run '${commandName} --help'.`;
39
+ command.error(message, {
40
+ code: "commander.missingCommand",
41
+ exitCode: 1,
42
+ });
43
+ }
44
+ function createLoopCommand(handlers = {}) {
45
+ const continueHandler = handlers.runLoopContinue ?? continue_js_1.runLoopContinue;
46
+ const resumeHandler = handlers.runLoopResume ?? resume_js_1.runLoopResume;
47
+ const statusHandler = handlers.runLoopStatus ?? status_js_1.runLoopStatus;
48
+ const abortHandler = handlers.runLoopAbort ?? abort_js_1.runLoopAbort;
49
+ const dispatchHandler = handlers.runLoopDispatch ?? dispatch_js_1.runLoopDispatch;
50
+ const parentHandler = handlers.runParentLoop ?? parent_js_1.runParentLoop;
51
+ const bootstrapHandler = handlers.runLoopBootstrapInit ?? run_bootstrap_js_1.runLoopBootstrapInit;
52
+ const repoRootDefault = handlers.repoRoot ?? process.cwd();
53
+ const loop = new commander_1.Command("loop")
54
+ .description("Polaris loop commands: status is safe/read-only; continue is mutating")
55
+ .showHelpAfterError()
56
+ .showSuggestionAfterError();
57
+ loop.action(() => failMissingSubcommand(loop, "polaris loop"));
58
+ loop
59
+ .command("run")
60
+ .description("mutating: run the automated parent loop for a cluster")
61
+ .argument("<cluster-id>", "Parent cluster ID")
62
+ .option("-r, --repo-root <path>", "Repository root", repoRootDefault)
63
+ .option("--state-file <path>", "Path to current-state.json")
64
+ .option("--adapter <mode>", "Execution adapter: agent-subtask, terminal-cli, ci, ssh, remote-worker, cross-agent")
65
+ .option("--provider <name>", "AI provider for worker dispatch (e.g. claude, openai, gemini)")
66
+ .option("--dry-run", "Log dispatches without executing workers")
67
+ .option("--allow-analyze-children", "Allow analyze-type children to be dispatched (overrides budget.allow_analyze_children)")
68
+ .action(async (clusterId, options) => {
69
+ const repoRoot = options.repoRoot;
70
+ const stateFile = defaultStateFile(repoRoot, options.stateFile);
71
+ await (0, run_preflight_js_1.ensureClusterRunState)({
72
+ clusterId,
73
+ stateFile,
74
+ repoRoot,
75
+ bootstrapHandler,
76
+ });
77
+ const result = await parentHandler({
78
+ stateFile,
79
+ repoRoot,
80
+ adapter: options.adapter,
81
+ provider: options.provider,
82
+ dryRun: options.dryRun,
83
+ allowAnalyzeChildren: options.allowAnalyzeChildren,
84
+ });
85
+ const summary = [
86
+ `Polaris parent loop halted: ${result.haltReason}`,
87
+ `Cluster: ${clusterId}`,
88
+ `Children dispatched: ${result.childrenDispatched}`,
89
+ result.haltingChild ? `Halting child: ${result.haltingChild}` : undefined,
90
+ result.message,
91
+ ].filter((line) => Boolean(line)).join("\n");
92
+ if (result.haltReason === "cluster-complete") {
93
+ process.stdout.write(`${summary}
94
+ `);
95
+ return;
96
+ }
97
+ process.stderr.write(`${summary}
98
+ `);
99
+ process.exit(1);
100
+ });
101
+ loop
102
+ .command("continue")
103
+ .description("mutating: checkpoint state and generate next-session bootstrap packet; not a smoke test")
104
+ .option("-r, --repo-root <path>", "Repository root", repoRootDefault)
105
+ .option("--state-file <path>", "Path to current-state.json")
106
+ .option("--adapter <mode>", "Execution adapter: agent-subtask, terminal-cli, ci, ssh, remote-worker, cross-agent")
107
+ .option("--provider <provider>", "AI provider for the next worker session (e.g. claude, openai, gemini)")
108
+ .option("--allow-analyze-children", "Allow analyze-type children to be dispatched (overrides budget.allow_analyze_children)")
109
+ .action((options) => {
110
+ const repoRoot = options.repoRoot;
111
+ const stateFile = defaultStateFile(repoRoot, options.stateFile);
112
+ continueHandler({ stateFile, repoRoot, adapter: options.adapter, provider: options.provider, allowAnalyzeChildren: options.allowAnalyzeChildren });
113
+ });
114
+ loop
115
+ .command("dispatch")
116
+ .description("mutating: claim the next open child and emit a compiled WorkerPacket")
117
+ .option("-r, --repo-root <path>", "Repository root", repoRootDefault)
118
+ .option("--state-file <path>", "Override path to current-state.json")
119
+ .option("--child <id>", "Open child issue ID to dispatch instead of the first open child")
120
+ .option("--result-file <path>", "Path to write the sealed result file")
121
+ .action((options) => {
122
+ dispatchHandler({
123
+ repoRoot: options.repoRoot,
124
+ stateFile: defaultStateFile(options.repoRoot, options.stateFile),
125
+ childId: options.child,
126
+ resultFile: options.resultFile,
127
+ });
128
+ });
129
+ loop
130
+ .command("resume [run_id]")
131
+ .description("mutating: resume from a bootstrap packet after branch/state integrity checks")
132
+ .option("-r, --repo-root <path>", "Repository root", repoRootDefault)
133
+ .option("--state-file <path>", "Override path to current-state.json")
134
+ .action((runId, options) => {
135
+ resumeHandler({
136
+ runId,
137
+ repoRoot: options.repoRoot,
138
+ stateFile: defaultStateFile(options.repoRoot, options.stateFile),
139
+ });
140
+ });
141
+ loop
142
+ .command("status")
143
+ .description("safe/read-only: print current loop run state summary")
144
+ .option("-r, --repo-root <path>", "Repository root", repoRootDefault)
145
+ .option("--state-file <path>", "Override path to current-state.json")
146
+ .option("--json", "Emit JSON output instead of human-readable text")
147
+ .action((options) => {
148
+ statusHandler({
149
+ repoRoot: options.repoRoot,
150
+ stateFile: defaultStateFile(options.repoRoot, options.stateFile),
151
+ json: options.json,
152
+ });
153
+ });
154
+ loop
155
+ .command("abort [reason]")
156
+ .description("mutating: record a blocker, set status to blocked, and halt cleanly")
157
+ .option("-r, --repo-root <path>", "Repository root", repoRootDefault)
158
+ .option("--state-file <path>", "Override path to current-state.json")
159
+ .option("--child <id>", "Child issue ID the blocker is associated with")
160
+ .action((reason, options) => {
161
+ if (!reason) {
162
+ process.stderr.write("Error: reason is required\n");
163
+ process.exit(1);
164
+ }
165
+ abortHandler({
166
+ reason,
167
+ childId: options.child,
168
+ repoRoot: options.repoRoot,
169
+ stateFile: defaultStateFile(options.repoRoot, options.stateFile),
170
+ });
171
+ });
172
+ loop
173
+ .command("bootstrap")
174
+ .description("mutating: initialize a new run — the ONLY valid way to create run state. " +
175
+ "Parent sessions may not hand-create current-state.json; this command issues " +
176
+ "a bootstrap seal that dispatch and run commands verify before proceeding.")
177
+ .requiredOption("--cluster-id <id>", "Parent cluster issue ID (e.g. POL-100)")
178
+ .requiredOption("--children <csv>", "Comma-separated ordered list of child issue IDs to execute (e.g. POL-101,POL-102)")
179
+ .option("-r, --repo-root <path>", "Repository root", repoRootDefault)
180
+ .option("--state-file <path>", "Override path to write current-state.json")
181
+ .option("--run-id <id>", "Override auto-generated run ID")
182
+ .option("--session-type <type>", "Session type: analyze | implement (default: implement)", "implement")
183
+ .option("--max-children <n>", "Max children per dispatch session (default: 1)", "1")
184
+ .option("--branch <name>", "Git branch for this run")
185
+ .option("--artifact-dir <path>", "Override artifact directory")
186
+ .option("--overwrite", "Archive existing state file and start fresh instead of failing")
187
+ .action((options) => {
188
+ const openChildren = options.children
189
+ .split(",")
190
+ .map((c) => c.trim())
191
+ .filter(Boolean);
192
+ const parsedMaxChildren = parseInt(options.maxChildren, 10);
193
+ const maxChildrenPerSession = Number.isInteger(parsedMaxChildren) && parsedMaxChildren > 0
194
+ ? parsedMaxChildren
195
+ : 1;
196
+ const bootstrapOptions = {
197
+ clusterId: options.clusterId,
198
+ runId: options.runId,
199
+ openChildren,
200
+ stateFile: defaultStateFile(options.repoRoot, options.stateFile),
201
+ repoRoot: options.repoRoot,
202
+ branch: options.branch,
203
+ sessionType: options.sessionType === "analyze" ? "analyze" : "implement",
204
+ maxChildrenPerSession,
205
+ artifactDir: options.artifactDir,
206
+ overwrite: options.overwrite,
207
+ };
208
+ bootstrapHandler(bootstrapOptions);
209
+ });
210
+ loop
211
+ .command("wait")
212
+ .description("safe/read-only: block until the active child's result file appears (replaces polling loop status)")
213
+ .option("-r, --repo-root <path>", "Repository root", repoRootDefault)
214
+ .option("--state-file <path>", "Override path to current-state.json")
215
+ .option("--child <id>", "Child ID to wait for (default: active_child from state)")
216
+ .option("--timeout <ms>", "Timeout in milliseconds (default: 1800000 = 30 min)", "1800000")
217
+ .option("--poll-interval <ms>", "Poll interval in milliseconds (default: 5000)", "5000")
218
+ .action((options) => {
219
+ const timeoutMs = parseInt(options.timeout, 10);
220
+ const pollIntervalMs = parseInt(options.pollInterval, 10);
221
+ if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
222
+ process.stderr.write(`Error: --timeout must be a positive integer, got "${options.timeout}"\n`);
223
+ process.exit(1);
224
+ }
225
+ if (!Number.isInteger(pollIntervalMs) || pollIntervalMs <= 0) {
226
+ process.stderr.write(`Error: --poll-interval must be a positive integer, got "${options.pollInterval}"\n`);
227
+ process.exit(1);
228
+ }
229
+ (0, wait_js_1.runLoopWait)({
230
+ stateFile: defaultStateFile(options.repoRoot, options.stateFile),
231
+ repoRoot: options.repoRoot,
232
+ childId: options.child,
233
+ timeoutMs,
234
+ pollIntervalMs,
235
+ });
236
+ });
237
+ loop
238
+ .command("backfill-evidence")
239
+ .description("mutating: backfill cluster-state commits/validation from sealed result files for completed children. " +
240
+ "Rejects placeholder commits. Does not weaken finalize.")
241
+ .option("-r, --repo-root <path>", "Repository root", repoRootDefault)
242
+ .option("--state-file <path>", "Override path to current-state.json")
243
+ .option("--dry-run", "Report what would be written without mutating cluster-state.json")
244
+ .action((options) => {
245
+ const repoRoot = options.repoRoot;
246
+ const stateFile = defaultStateFile(repoRoot, options.stateFile);
247
+ const report = (0, evidence_backfill_js_1.backfillClusterStateEvidence)({ repoRoot, stateFile, dryRun: options.dryRun });
248
+ const backfilledIds = report.backfilled.map((b) => b.childId);
249
+ const skippedLines = report.skipped.map((s) => ` SKIP ${s.childId}: ${s.reason}`);
250
+ if (backfilledIds.length > 0) {
251
+ process.stdout.write(`Backfilled ${backfilledIds.length} children: ${backfilledIds.join(", ")}\n`);
252
+ for (const b of report.backfilled) {
253
+ process.stdout.write(` OK ${b.childId}: commit=${b.commit}\n`);
254
+ }
255
+ }
256
+ else {
257
+ process.stdout.write("No children backfilled.\n");
258
+ }
259
+ if (skippedLines.length > 0) {
260
+ process.stdout.write(skippedLines.join("\n") + "\n");
261
+ }
262
+ if (options.dryRun) {
263
+ process.stdout.write("(dry-run: cluster-state.json not written)\n");
264
+ }
265
+ if (report.skipped.length > 0 && backfilledIds.length === 0) {
266
+ process.exit(1);
267
+ }
268
+ });
269
+ return loop;
270
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LedgerWriter = exports.DEFAULT_LEDGER_PATH = void 0;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ exports.DEFAULT_LEDGER_PATH = ".polaris/runs/ledger.jsonl";
7
+ const TERMINAL_OPEN_RUN_STATUSES = new Set(["complete", "finalized", "canceled"]);
8
+ class LedgerWriter {
9
+ ledgerPath;
10
+ constructor(ledgerPath = exports.DEFAULT_LEDGER_PATH) {
11
+ this.ledgerPath = ledgerPath;
12
+ }
13
+ append(event) {
14
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(this.ledgerPath), { recursive: true });
15
+ (0, node_fs_1.appendFileSync)(this.ledgerPath, `${JSON.stringify(event)}\n`, { encoding: "utf-8", flag: "a" });
16
+ }
17
+ readAll() {
18
+ if (!(0, node_fs_1.existsSync)(this.ledgerPath)) {
19
+ return [];
20
+ }
21
+ const content = (0, node_fs_1.readFileSync)(this.ledgerPath, "utf-8").trim();
22
+ if (content.length === 0) {
23
+ return [];
24
+ }
25
+ const lines = content.split("\n");
26
+ const events = [];
27
+ for (let i = 0; i < lines.length; i++) {
28
+ try {
29
+ events.push(JSON.parse(lines[i]));
30
+ }
31
+ catch (err) {
32
+ const msg = err instanceof Error ? err.message : String(err);
33
+ console.error(`Warning: skipping malformed ledger line ${i + 1}: ${msg}\nContent: ${lines[i]}`);
34
+ }
35
+ }
36
+ return events;
37
+ }
38
+ queryByIssue(issueId) {
39
+ return this.readAll().filter((event) => event.cluster_id === issueId);
40
+ }
41
+ queryOpenRuns() {
42
+ const latestByRun = new Map();
43
+ for (const event of this.readAll()) {
44
+ latestByRun.set(event.run_id, event);
45
+ }
46
+ return Array.from(latestByRun.values()).filter((event) => !TERMINAL_OPEN_RUN_STATUSES.has(event.status));
47
+ }
48
+ }
49
+ exports.LedgerWriter = LedgerWriter;