@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,234 @@
1
+ "use strict";
2
+ /**
3
+ * Polaris Run Bootstrap Delegator
4
+ *
5
+ * The run bootstrap delegator is the runtime gate to the dispatch machinery.
6
+ * Parent sessions may NOT hand-create run state. The runtime MUST initialize
7
+ * the loop before any child can be selected or dispatched.
8
+ *
9
+ * Enforcement:
10
+ * - `polaris loop bootstrap` is the ONLY command that may create initial
11
+ * run state. It writes a RunBootstrapSeal into current-state.json.
12
+ * - `polaris loop dispatch` and `polaris loop run` verify the seal before
13
+ * allowing any child to be selected. If the seal is absent or invalid,
14
+ * dispatch is refused with a hard exit(1).
15
+ *
16
+ * The seal binds:
17
+ * - The run_id and cluster_id (must match state fields)
18
+ * - A SHA-256 of the initial open_children list (forensic tamper record)
19
+ * - The sealer identity ("polaris-loop-bootstrap" — only this value is valid)
20
+ *
21
+ * This module also exports the CLI handler runLoopBootstrapInit(), which
22
+ * is registered as `polaris loop bootstrap` in src/loop/index.ts.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.BOOTSTRAP_REQUIRED_ERROR = void 0;
26
+ exports.computeChildrenSha = computeChildrenSha;
27
+ exports.createBootstrapSeal = createBootstrapSeal;
28
+ exports.deriveRunId = deriveRunId;
29
+ exports.assertBootstrapSeal = assertBootstrapSeal;
30
+ exports.runLoopBootstrapInit = runLoopBootstrapInit;
31
+ const node_crypto_1 = require("node:crypto");
32
+ const node_fs_1 = require("node:fs");
33
+ const node_path_1 = require("node:path");
34
+ const checkpoint_js_1 = require("./checkpoint.js");
35
+ const dispatch_boundary_js_1 = require("./dispatch-boundary.js");
36
+ const dispatch_boundary_js_2 = require("./dispatch-boundary.js");
37
+ const store_js_1 = require("../cluster-state/store.js");
38
+ const git_custody_js_1 = require("./git-custody.js");
39
+ // ──────────────────────────────────────────────────────────────────────────────
40
+ // Error constants
41
+ // ──────────────────────────────────────────────────────────────────────────────
42
+ exports.BOOTSTRAP_REQUIRED_ERROR = "Run state must be initialized through 'polaris loop bootstrap' before dispatch. " +
43
+ "Parent sessions may not hand-create run state. " +
44
+ "Use: npm run polaris -- loop bootstrap --cluster-id <ID> --children <CSV>";
45
+ // ──────────────────────────────────────────────────────────────────────────────
46
+ // Seal helpers
47
+ // ──────────────────────────────────────────────────────────────────────────────
48
+ /**
49
+ * Compute a deterministic SHA-256 of an open_children list.
50
+ * Sorts the list before hashing to ensure stability.
51
+ */
52
+ function computeChildrenSha(openChildren) {
53
+ const sorted = [...openChildren].sort();
54
+ return (0, node_crypto_1.createHash)("sha256").update(sorted.join(",")).digest("hex");
55
+ }
56
+ /**
57
+ * Create a bootstrap seal for the given run/cluster/children.
58
+ */
59
+ function createBootstrapSeal(runId, clusterId, openChildren) {
60
+ return {
61
+ sealer: "polaris-loop-bootstrap",
62
+ run_id: runId,
63
+ cluster_id: clusterId,
64
+ open_children_sha: computeChildrenSha(openChildren),
65
+ sealed_at: new Date().toISOString(),
66
+ };
67
+ }
68
+ /**
69
+ * Derive a run_id from a cluster ID and the current date.
70
+ * Format: polaris-run-<cluster-slug>-<YYYY-MM-DD>-001
71
+ */
72
+ function deriveRunId(clusterId) {
73
+ const slug = clusterId.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
74
+ const date = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
75
+ return `polaris-run-${slug}-${date}-001`;
76
+ }
77
+ // ──────────────────────────────────────────────────────────────────────────────
78
+ // Enforcement guard
79
+ // ──────────────────────────────────────────────────────────────────────────────
80
+ /**
81
+ * Assert that the current state was initialized through `polaris loop bootstrap`.
82
+ *
83
+ * Verifies:
84
+ * 1. `run_bootstrap_seal` is present in state
85
+ * 2. `seal.sealer === "polaris-loop-bootstrap"`
86
+ * 3. `seal.run_id === state.run_id`
87
+ * 4. `seal.cluster_id === state.cluster_id`
88
+ *
89
+ * Emits a `dispatch-required` telemetry event on failure and throws.
90
+ * The caller must NOT mutate state after this throws.
91
+ *
92
+ * @throws Error with BOOTSTRAP_REQUIRED_ERROR if seal is absent or invalid
93
+ */
94
+ function assertBootstrapSeal(state, telemetryFile) {
95
+ const seal = state.run_bootstrap_seal;
96
+ let reason = null;
97
+ if (!seal) {
98
+ reason = `No run_bootstrap_seal found in state. ${exports.BOOTSTRAP_REQUIRED_ERROR}`;
99
+ }
100
+ else if (seal.sealer !== "polaris-loop-bootstrap") {
101
+ reason = `Invalid run_bootstrap_seal.sealer: "${seal.sealer}". Only "polaris-loop-bootstrap" is accepted.`;
102
+ }
103
+ else if (seal.run_id !== state.run_id) {
104
+ reason = `run_bootstrap_seal.run_id "${seal.run_id}" does not match state.run_id "${state.run_id}".`;
105
+ }
106
+ else if (seal.cluster_id !== state.cluster_id) {
107
+ reason = `run_bootstrap_seal.cluster_id "${seal.cluster_id}" does not match state.cluster_id "${state.cluster_id}".`;
108
+ }
109
+ if (reason) {
110
+ (0, dispatch_boundary_js_1.appendDispatchViolationEvent)(telemetryFile, {
111
+ event: "dispatch-required",
112
+ run_id: state.run_id,
113
+ from_state: "unbootstrapped",
114
+ to_state: "dispatched",
115
+ reason,
116
+ timestamp: new Date().toISOString(),
117
+ });
118
+ throw new Error(reason);
119
+ }
120
+ }
121
+ // ──────────────────────────────────────────────────────────────────────────────
122
+ // Bootstrap init command handler
123
+ // ──────────────────────────────────────────────────────────────────────────────
124
+ /**
125
+ * Create the initial run state and write it to the state file.
126
+ *
127
+ * This is the CLI handler for `polaris loop bootstrap`. It is the ONLY
128
+ * legitimate path to creating a new current-state.json. Any state file
129
+ * that was not written by this function will be refused by dispatch.
130
+ */
131
+ function runLoopBootstrapInit(options) {
132
+ const { clusterId, openChildren, openChildrenMeta, stateFile, repoRoot, branch, sessionType, maxChildrenPerSession, artifactDir, overwrite, } = options;
133
+ if (!clusterId) {
134
+ process.stderr.write("Error: --cluster-id is required\n");
135
+ process.exit(1);
136
+ }
137
+ if (!openChildren || openChildren.length === 0) {
138
+ process.stderr.write("Error: --children is required and must be non-empty\n");
139
+ process.exit(1);
140
+ }
141
+ const runId = options.runId ?? deriveRunId(clusterId);
142
+ const seal = createBootstrapSeal(runId, clusterId, openChildren);
143
+ const deliveryBranch = branch ?? (0, git_custody_js_1.buildDeliveryBranchName)(clusterId);
144
+ const initialState = {
145
+ schema_version: "1.0",
146
+ run_id: runId,
147
+ cluster_id: clusterId,
148
+ skill: "polaris-run",
149
+ branch: deliveryBranch,
150
+ session_type: sessionType ?? "implement",
151
+ active_child: "",
152
+ completed_children: [],
153
+ open_children: openChildren,
154
+ open_children_meta: openChildrenMeta,
155
+ step_cursor: null,
156
+ context_budget: {
157
+ children_completed: 0,
158
+ max_children_per_session: maxChildrenPerSession ?? 1,
159
+ },
160
+ status: "running",
161
+ next_open_child: openChildren[0] ?? null,
162
+ artifact_dir: artifactDir ?? (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run"),
163
+ dispatch_boundary: (0, dispatch_boundary_js_2.initialDispatchBoundary)(),
164
+ run_bootstrap_seal: seal,
165
+ };
166
+ // Check if current-state.json already exists before making any modifications
167
+ if ((0, node_fs_1.existsSync)(stateFile)) {
168
+ if (!overwrite) {
169
+ process.stderr.write(`Error: State file already exists at ${stateFile}\n` +
170
+ `Cannot overwrite existing run state. Either:\n` +
171
+ ` 1. Use --overwrite to archive the existing state and start fresh, or\n` +
172
+ ` 2. Use a different --state-file path\n`);
173
+ process.exit(1);
174
+ }
175
+ // Archive the existing state before overwriting
176
+ const backupPath = `${stateFile}.${Date.now()}.bak`;
177
+ (0, node_fs_1.renameSync)(stateFile, backupPath);
178
+ process.stderr.write(`Warning: Archived existing state to ${backupPath}\n`);
179
+ }
180
+ // Initialize cluster-state.json if it doesn't exist.
181
+ // This is best-effort and must not block bootstrap state creation.
182
+ const clusterStateInitPromise = (async () => {
183
+ try {
184
+ const existingClusterState = await (0, store_js_1.readClusterState)(clusterId, repoRoot);
185
+ if (!existingClusterState) {
186
+ process.stderr.write(`Initializing new cluster-state.json for ${clusterId}...\n`);
187
+ await (0, store_js_1.initializeClusterState)(clusterId, repoRoot);
188
+ // Bind the delivery branch immediately so finalize doesn't inherit stale metadata
189
+ const freshState = await (0, store_js_1.readClusterState)(clusterId, repoRoot);
190
+ if (freshState && !freshState.delivery_branch) {
191
+ await (0, store_js_1.writeClusterState)(clusterId, {
192
+ ...freshState,
193
+ state_generation: (freshState.state_generation ?? 0) + 1,
194
+ delivery_branch: deliveryBranch,
195
+ base_branch: "main",
196
+ }, repoRoot);
197
+ }
198
+ }
199
+ else if (!existingClusterState.delivery_branch) {
200
+ // Existing cluster state without delivery branch — bind it now
201
+ await (0, store_js_1.writeClusterState)(clusterId, {
202
+ ...existingClusterState,
203
+ state_generation: (existingClusterState.state_generation ?? 0) + 1,
204
+ delivery_branch: deliveryBranch,
205
+ base_branch: existingClusterState.base_branch ?? "main",
206
+ }, repoRoot);
207
+ }
208
+ }
209
+ catch (error) {
210
+ process.stderr.write(`Warning: Failed to initialize cluster-state.json for ${clusterId}: ${error instanceof Error ? error.message : String(error)}\n`);
211
+ // Continue without hard-failing bootstrap.
212
+ }
213
+ })();
214
+ // Validate the state we're about to write
215
+ // (uses validateState from checkpoint.ts — but we don't import it here to
216
+ // avoid circular deps; validateState is called by dispatch which is downstream)
217
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(stateFile), { recursive: true });
218
+ const sha = (0, checkpoint_js_1.writeStateAtomic)(stateFile, initialState);
219
+ const summary = {
220
+ run_id: runId,
221
+ cluster_id: clusterId,
222
+ children: openChildren.length,
223
+ first_child: openChildren[0],
224
+ state_file: stateFile,
225
+ seal_sha: sha.slice(0, 12),
226
+ sealed_at: seal.sealed_at,
227
+ };
228
+ process.stdout.write(JSON.stringify(summary, null, 2) + "\n");
229
+ process.stderr.write(`Bootstrap complete. State initialized at: ${stateFile}\n` +
230
+ `Run ID: ${runId}\n` +
231
+ `Children: ${openChildren.join(", ")}\n` +
232
+ `Next: npm run polaris -- loop dispatch\n`);
233
+ return clusterStateInitPromise;
234
+ }
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensureClusterRunState = ensureClusterRunState;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ const store_js_1 = require("../cluster-state/store.js");
7
+ const local_graph_js_1 = require("../tracker/local-graph.js");
8
+ const index_js_1 = require("../tracker/adapters/linear/index.js");
9
+ const loader_js_1 = require("../config/loader.js");
10
+ const checkpoint_js_1 = require("./checkpoint.js");
11
+ function getClusterStatePath(clusterId, repoRoot) {
12
+ return (0, node_path_1.join)(repoRoot, ".polaris", "clusters", clusterId, "cluster-state.json");
13
+ }
14
+ function isEnoent(error) {
15
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
16
+ }
17
+ async function loadOrSyncGraph(clusterId, repoRoot) {
18
+ try {
19
+ return await local_graph_js_1.LocalGraph.load(clusterId, repoRoot);
20
+ }
21
+ catch (error) {
22
+ if (!isEnoent(error)) {
23
+ throw error;
24
+ }
25
+ const config = (0, loader_js_1.loadConfig)(repoRoot);
26
+ if (!config.tracker?.linear?.enabled) {
27
+ throw new Error(`No local cluster graph found for ${clusterId}. Run 'npm run polaris -- tracker sync-in ${clusterId}' or bootstrap with --children.`);
28
+ }
29
+ const graph = await new index_js_1.LinearAdapter(config).syncIn(clusterId);
30
+ await graph.save(clusterId, repoRoot);
31
+ return graph;
32
+ }
33
+ }
34
+ function topoSort(children, getDeps) {
35
+ // Kahn's algorithm — only sorts within the given children set
36
+ const childSet = new Set(children);
37
+ const inDegree = new Map();
38
+ const dependents = new Map(); // dep → [children that need dep]
39
+ for (const child of children) {
40
+ inDegree.set(child, 0);
41
+ dependents.set(child, []);
42
+ }
43
+ for (const child of children) {
44
+ for (const dep of getDeps(child)) {
45
+ if (!childSet.has(dep))
46
+ continue; // external dep — ignore for ordering
47
+ inDegree.set(child, (inDegree.get(child) ?? 0) + 1);
48
+ dependents.get(dep).push(child);
49
+ }
50
+ }
51
+ const queue = [];
52
+ for (const [child, deg] of inDegree) {
53
+ if (deg === 0)
54
+ queue.push(child);
55
+ }
56
+ const sorted = [];
57
+ while (queue.length > 0) {
58
+ const node = queue.shift();
59
+ sorted.push(node);
60
+ for (const dependent of dependents.get(node) ?? []) {
61
+ const newDeg = (inDegree.get(dependent) ?? 1) - 1;
62
+ inDegree.set(dependent, newDeg);
63
+ if (newDeg === 0)
64
+ queue.push(dependent);
65
+ }
66
+ }
67
+ // If sort is incomplete (cycle), fall back to original order for remaining nodes
68
+ if (sorted.length < children.length) {
69
+ const sortedSet = new Set(sorted);
70
+ for (const child of children) {
71
+ if (!sortedSet.has(child))
72
+ sorted.push(child);
73
+ }
74
+ }
75
+ return sorted;
76
+ }
77
+ function buildBootstrapPlan(clusterId, graph) {
78
+ const activeCluster = graph.getActiveCluster();
79
+ // Exclude the cluster root from runnable children when it has implementation
80
+ // children. The root is a context/coordination node. If after filtering nothing
81
+ // remains (leaf cluster), fall back to [clusterId] so the root runs as the sole
82
+ // work item — matching single-target leaf semantics.
83
+ const clusterRoot = activeCluster.cluster_root;
84
+ const runnableChildren = clusterRoot
85
+ ? activeCluster.children.filter((id) => id !== clusterRoot)
86
+ : activeCluster.children;
87
+ const unsortedChildren = runnableChildren.length > 0 ? runnableChildren : [clusterId];
88
+ const openChildren = topoSort(unsortedChildren, (id) => graph.getDependencies(id));
89
+ const openChildrenMeta = {};
90
+ const clusterNode = graph.getNode(clusterId);
91
+ openChildrenMeta[clusterId] = {
92
+ title: clusterNode?.title ?? activeCluster.title,
93
+ ...(clusterNode?.body ? { body: clusterNode.body } : {}),
94
+ };
95
+ for (const childId of openChildren) {
96
+ const node = graph.getNode(childId);
97
+ openChildrenMeta[childId] = {
98
+ ...openChildrenMeta[childId],
99
+ title: node?.title ?? openChildrenMeta[childId]?.title ?? childId,
100
+ ...(node?.body ? { body: node.body } : {}),
101
+ };
102
+ }
103
+ return { openChildren, openChildrenMeta };
104
+ }
105
+ function preserveMismatchedState(stateFile) {
106
+ const suffix = (() => {
107
+ try {
108
+ const state = (0, checkpoint_js_1.readState)(stateFile);
109
+ return `${state.run_id || "state"}-${Date.now()}`;
110
+ }
111
+ catch {
112
+ return `state-${Date.now()}`;
113
+ }
114
+ })();
115
+ (0, node_fs_1.renameSync)(stateFile, `${stateFile}.${suffix}.bak`);
116
+ }
117
+ async function isGhostCompleteState(clusterId, repoRoot, completedChildren, status) {
118
+ if (status !== "cluster-complete" && completedChildren.length === 0) {
119
+ return false;
120
+ }
121
+ const clusterState = await (0, store_js_1.readClusterState)(clusterId, repoRoot);
122
+ if (!clusterState) {
123
+ return false;
124
+ }
125
+ const unfinishedStatuses = new Set(["ready", "claimed", "dispatched", "running", "failed", "blocked"]);
126
+ return clusterState.child_states.some((child) => {
127
+ if (!completedChildren.includes(child.id) && status !== "cluster-complete") {
128
+ return false;
129
+ }
130
+ return unfinishedStatuses.has(child.status);
131
+ });
132
+ }
133
+ async function assertCanonicalClusterState(clusterId, repoRoot, state) {
134
+ const clusterStatePath = getClusterStatePath(clusterId, repoRoot);
135
+ const clusterState = await (0, store_js_1.readClusterState)(clusterId, repoRoot);
136
+ if (!clusterState) {
137
+ throw new Error(`run-preflight: missing canonical cluster-state for ${clusterId} at ${clusterStatePath}. ` +
138
+ "The live execution state must exist before Polaris reuses current-state.json.");
139
+ }
140
+ const clusterChildIds = new Set(clusterState.child_states.map((child) => child.id));
141
+ const referencedChildren = new Set([
142
+ ...state.completed_children ?? [],
143
+ ...state.open_children ?? [],
144
+ ...(state.active_child && state.active_child.length > 0
145
+ ? [state.active_child]
146
+ : []),
147
+ ]);
148
+ const missingChildren = [...referencedChildren].filter((childId) => !clusterChildIds.has(childId));
149
+ if (missingChildren.length > 0) {
150
+ throw new Error(`run-preflight: cluster-state for ${clusterId} is missing children referenced by current-state.json: ${missingChildren.join(", ")}`);
151
+ }
152
+ }
153
+ async function ensureClusterRunState(options) {
154
+ const { clusterId, stateFile, repoRoot, bootstrapHandler } = options;
155
+ if ((0, node_fs_1.existsSync)(stateFile)) {
156
+ let existingState;
157
+ try {
158
+ existingState = (0, checkpoint_js_1.readState)(stateFile);
159
+ }
160
+ catch (err) {
161
+ if (!isEnoent(err)) {
162
+ console.warn(`run-preflight: failed to read state file ${stateFile}: ${err instanceof Error ? err.message : String(err)}`);
163
+ }
164
+ // ENOENT (race condition) or parse/IO error — proceed to bootstrap
165
+ }
166
+ if (existingState !== undefined) {
167
+ const validationErrors = (0, checkpoint_js_1.validateState)(existingState);
168
+ if (existingState.cluster_id === clusterId) {
169
+ if (await isGhostCompleteState(clusterId, repoRoot, existingState.completed_children ?? [], existingState.status)) {
170
+ preserveMismatchedState(stateFile);
171
+ }
172
+ else {
173
+ // Valid state for this cluster — no bootstrap needed
174
+ if (validationErrors.length === 0) {
175
+ await assertCanonicalClusterState(clusterId, repoRoot, existingState);
176
+ return;
177
+ }
178
+ // Leave invalid in-place so downstream validation reports the real state error.
179
+ return;
180
+ }
181
+ }
182
+ else {
183
+ if (!existingState.cluster_id && validationErrors.length > 0) {
184
+ return;
185
+ }
186
+ // Mismatched cluster_id — always preserve, with a warning if also invalid
187
+ if (validationErrors.length > 0) {
188
+ console.warn(`run-preflight: state file ${stateFile} has cluster_id "${existingState.cluster_id}" (expected "${clusterId}") and failed validation; preserving for inspection.`);
189
+ }
190
+ preserveMismatchedState(stateFile);
191
+ }
192
+ }
193
+ }
194
+ const { openChildren, openChildrenMeta } = buildBootstrapPlan(clusterId, await loadOrSyncGraph(clusterId, repoRoot));
195
+ await bootstrapHandler({
196
+ clusterId,
197
+ openChildren,
198
+ openChildrenMeta,
199
+ stateFile,
200
+ repoRoot,
201
+ artifactDir: (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run"),
202
+ });
203
+ }