@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,188 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runLoopAbort = runLoopAbort;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ const node_crypto_1 = require("node:crypto");
7
+ const checkpoint_js_1 = require("./checkpoint.js");
8
+ const ledger_js_1 = require("./ledger.js");
9
+ function normalizeRunType(sessionType) {
10
+ return sessionType === "analyze" ? "analyze" : "implement";
11
+ }
12
+ function ledgerLastCommit(state) {
13
+ return state.last_commit && state.last_commit.length > 0 ? state.last_commit : null;
14
+ }
15
+ function appendBlockedLedgerEvent(repoRoot, state, childId, reason) {
16
+ new ledger_js_1.LedgerWriter((0, node_path_1.join)(repoRoot, ledger_js_1.DEFAULT_LEDGER_PATH)).append({
17
+ schema_version: 1,
18
+ event_id: (0, node_crypto_1.randomUUID)(),
19
+ event: "run-blocked",
20
+ run_id: state.run_id,
21
+ run_type: normalizeRunType(state.session_type),
22
+ cluster_id: state.cluster_id,
23
+ issue_id: childId || null,
24
+ branch: state.branch ?? "unknown",
25
+ status: "blocked",
26
+ completed_children: state.completed_children,
27
+ open_children: state.open_children,
28
+ next_child: state.next_open_child,
29
+ last_commit: ledgerLastCommit(state),
30
+ pr_url: null,
31
+ timestamp: new Date().toISOString(),
32
+ blocker: {
33
+ summary: reason,
34
+ unblock_condition: `Resolve blocker then run: npm run polaris -- loop run ${state.cluster_id}`,
35
+ },
36
+ });
37
+ }
38
+ /**
39
+ * Detect whether the current state has a stuck dispatch with no worker activity.
40
+ *
41
+ * Intentionally does NOT call getMachineState(). When a prior `loop abort` set
42
+ * state.status to "blocked" but failed to clear active_child (the pre-fix
43
+ * behavior), getMachineState() would short-circuit to "blocked" before reaching
44
+ * the dispatch-boundary epoch logic, making re-abort appear non-stale. Instead
45
+ * we inspect the raw dispatch signals directly.
46
+ *
47
+ * A dispatch is eligible for stale reset when ALL of the following are true:
48
+ * • active_child is set
49
+ * • dispatch evidence exists (step_cursor is "dispatch", dispatch_boundary
50
+ * records the child, or the dispatch_record is still "dispatched")
51
+ * • the dispatch_record has not already reached a terminal state (failed/completed)
52
+ * • no heartbeat exists (worker never acknowledged)
53
+ * • the expected result file is absent (worker never wrote output)
54
+ */
55
+ function detectStaleDispatch(state, repoRoot) {
56
+ if (!state.active_child || state.active_child === "") {
57
+ return { isStale: false, hasHeartbeat: false, hasResultFile: false };
58
+ }
59
+ const activeChild = state.active_child;
60
+ const dr = state.open_children_meta?.[activeChild]?.dispatch_record;
61
+ // A dispatch record that already reached a terminal state has been handled.
62
+ if (dr && (dr.status === "failed" || dr.status === "completed")) {
63
+ return { isStale: false, hasHeartbeat: false, hasResultFile: false };
64
+ }
65
+ // Require at least one dispatch signal for this child.
66
+ const hasDispatchEvidence = state.step_cursor === "dispatch" ||
67
+ state.dispatch_boundary?.last_dispatched_child === activeChild ||
68
+ dr?.status === "dispatched";
69
+ if (!hasDispatchEvidence) {
70
+ return { isStale: false, hasHeartbeat: false, hasResultFile: false };
71
+ }
72
+ const hasHeartbeat = !!(dr?.last_heartbeat_at);
73
+ let hasResultFile = false;
74
+ if (dr?.expected_result_path) {
75
+ const resultPath = (0, node_path_1.isAbsolute)(dr.expected_result_path)
76
+ ? dr.expected_result_path
77
+ : (0, node_path_1.join)(repoRoot, dr.expected_result_path);
78
+ hasResultFile = (0, node_fs_1.existsSync)(resultPath);
79
+ }
80
+ // Only treat as stale when the worker left no evidence at all.
81
+ // If a heartbeat exists the worker may still be running; if a result file
82
+ // exists the worker completed and must be checkpointed before clearing.
83
+ return {
84
+ isStale: !hasHeartbeat && !hasResultFile,
85
+ hasHeartbeat,
86
+ hasResultFile,
87
+ abortedDispatchId: dr?.dispatch_id,
88
+ };
89
+ }
90
+ function runLoopAbort(options) {
91
+ const { reason, repoRoot } = options;
92
+ const stateFile = options.stateFile ?? (0, node_path_1.join)(repoRoot, ".polaris", "runs", "current-state.json");
93
+ let state;
94
+ try {
95
+ const raw = (0, checkpoint_js_1.readState)(stateFile);
96
+ const errors = (0, checkpoint_js_1.validateState)(raw);
97
+ if (errors.length > 0) {
98
+ process.stderr.write(`current-state.json invalid:\n${errors.join("\n")}\n`);
99
+ process.exit(1);
100
+ }
101
+ state = raw;
102
+ }
103
+ catch (err) {
104
+ process.stderr.write(`Error: cannot read state file ${stateFile}: ${err instanceof Error ? err.message : String(err)}\n`);
105
+ process.exit(1);
106
+ }
107
+ const timestamp = new Date().toISOString();
108
+ const staleInfo = detectStaleDispatch(state, repoRoot);
109
+ let updatedState;
110
+ let effectiveChildId;
111
+ if (staleInfo.isStale) {
112
+ // Stale dispatch: clear active_child, balance dispatch_boundary epochs, and
113
+ // mark the old dispatch record as failed so fresh dispatch can proceed after
114
+ // the operator runs `polaris loop resume` to exit the blocked state.
115
+ const stuckChildId = state.active_child;
116
+ effectiveChildId = options.childId ?? stuckChildId ?? "";
117
+ const existingMeta = state.open_children_meta ?? {};
118
+ const childMeta = existingMeta[stuckChildId] ?? {};
119
+ const oldDr = childMeta.dispatch_record;
120
+ updatedState = {
121
+ ...state,
122
+ active_child: "",
123
+ step_cursor: state.step_cursor === "dispatch" ? null : state.step_cursor,
124
+ dispatch_boundary: state.dispatch_boundary
125
+ ? {
126
+ ...state.dispatch_boundary,
127
+ // Balance epochs so the machine returns to checkpointed/idle,
128
+ // allowing a fresh dispatch once the blocked status is cleared.
129
+ continue_epoch: state.dispatch_boundary.dispatch_epoch,
130
+ }
131
+ : state.dispatch_boundary,
132
+ status: "blocked",
133
+ blocker: {
134
+ reason,
135
+ child_id: effectiveChildId,
136
+ timestamp,
137
+ resolved: false,
138
+ },
139
+ open_children_meta: {
140
+ ...existingMeta,
141
+ [stuckChildId]: {
142
+ ...childMeta,
143
+ ...(oldDr
144
+ ? { dispatch_record: { ...oldDr, status: "failed", runtime_state: "failed" } }
145
+ : {}),
146
+ },
147
+ },
148
+ };
149
+ }
150
+ else {
151
+ effectiveChildId = options.childId ?? state.active_child ?? "";
152
+ updatedState = {
153
+ ...state,
154
+ status: "blocked",
155
+ blocker: {
156
+ reason,
157
+ child_id: effectiveChildId,
158
+ timestamp,
159
+ resolved: false,
160
+ },
161
+ };
162
+ }
163
+ (0, checkpoint_js_1.writeStateAtomic)(stateFile, updatedState);
164
+ appendBlockedLedgerEvent(repoRoot, updatedState, effectiveChildId, reason);
165
+ const artifactDir = state.artifact_dir ?? (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run");
166
+ const telemetryFile = (0, node_path_1.join)(artifactDir, "runs", state.run_id, "telemetry.jsonl");
167
+ if (staleInfo.isStale) {
168
+ (0, checkpoint_js_1.appendStaleDispatchAbortedEvent)(telemetryFile, {
169
+ event: "stale-dispatch-aborted",
170
+ run_id: state.run_id,
171
+ child_id: state.active_child,
172
+ reason,
173
+ aborted_dispatch_id: staleInfo.abortedDispatchId,
174
+ had_heartbeat: staleInfo.hasHeartbeat,
175
+ had_result_file: staleInfo.hasResultFile,
176
+ timestamp,
177
+ });
178
+ }
179
+ (0, checkpoint_js_1.appendAbortEvent)(telemetryFile, {
180
+ event: "loop-aborted",
181
+ run_id: state.run_id,
182
+ child_id: effectiveChildId,
183
+ reason,
184
+ timestamp,
185
+ });
186
+ process.stderr.write(`Loop aborted. Reason: ${reason}. Resolve blocker then run: npm run polaris -- loop run ${updatedState.cluster_id}\n`);
187
+ process.exit(0);
188
+ }
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ const node_os_1 = require("node:os");
7
+ const node_child_process_1 = require("node:child_process");
8
+ const abort_js_1 = require("./abort.js");
9
+ function makeTestDir() {
10
+ const dir = (0, node_path_1.join)((0, node_os_1.tmpdir)(), `polaris-abort-test-${Date.now()}`);
11
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
12
+ (0, node_child_process_1.execFileSync)("git", ["init"], { cwd: dir });
13
+ (0, node_child_process_1.execFileSync)("git", ["config", "user.email", "test@test.com"], { cwd: dir });
14
+ (0, node_child_process_1.execFileSync)("git", ["config", "user.name", "Test"], { cwd: dir });
15
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, "README.md"), "test\n");
16
+ (0, node_child_process_1.execFileSync)("git", ["add", "."], { cwd: dir });
17
+ (0, node_child_process_1.execFileSync)("git", ["commit", "-m", "init"], { cwd: dir });
18
+ return dir;
19
+ }
20
+ function writeState(dir, state) {
21
+ const stateFile = (0, node_path_1.join)(dir, ".polaris", "runs", "current-state.json");
22
+ (0, node_fs_1.mkdirSync)((0, node_path_1.join)(dir, ".polaris", "runs"), { recursive: true });
23
+ (0, node_fs_1.writeFileSync)(stateFile, JSON.stringify(state, null, 2));
24
+ return stateFile;
25
+ }
26
+ const baseState = {
27
+ schema_version: "1.0",
28
+ run_id: "pol-5-session-1",
29
+ cluster_id: "POL-5",
30
+ session_type: "implement",
31
+ active_child: "POL-26",
32
+ completed_children: ["POL-23"],
33
+ open_children: ["POL-26", "POL-27"],
34
+ step_cursor: "implement-child",
35
+ context_budget: { children_completed: 1, max_children_per_session: 3 },
36
+ status: "running",
37
+ next_open_child: "POL-26",
38
+ };
39
+ (0, vitest_1.describe)("runLoopAbort", () => {
40
+ let testDir;
41
+ (0, vitest_1.beforeEach)(() => {
42
+ testDir = makeTestDir();
43
+ });
44
+ (0, vitest_1.afterEach)(() => {
45
+ (0, node_fs_1.rmSync)(testDir, { recursive: true, force: true });
46
+ });
47
+ (0, vitest_1.it)("writes blocked status and blocker record to current-state.json", () => {
48
+ const stateFile = writeState(testDir, baseState);
49
+ const exitSpy = vitest_1.vi.spyOn(process, "exit").mockImplementation(() => {
50
+ throw new Error("process.exit called");
51
+ });
52
+ vitest_1.vi.spyOn(process.stderr, "write").mockImplementation(() => true);
53
+ try {
54
+ (0, vitest_1.expect)(() => (0, abort_js_1.runLoopAbort)({ reason: "test blocker", repoRoot: testDir, stateFile })).toThrow("process.exit called");
55
+ }
56
+ finally {
57
+ exitSpy.mockRestore();
58
+ vitest_1.vi.restoreAllMocks();
59
+ }
60
+ const saved = JSON.parse((0, node_fs_1.readFileSync)(stateFile, "utf-8"));
61
+ (0, vitest_1.expect)(saved.status).toBe("blocked");
62
+ (0, vitest_1.expect)(saved.blocker.reason).toBe("test blocker");
63
+ (0, vitest_1.expect)(saved.blocker.child_id).toBe("POL-26");
64
+ (0, vitest_1.expect)(saved.blocker.resolved).toBe(false);
65
+ (0, vitest_1.expect)(saved.blocker.timestamp).toBeTruthy();
66
+ });
67
+ (0, vitest_1.it)("uses --child override for blocker child_id", () => {
68
+ const stateFile = writeState(testDir, baseState);
69
+ const exitSpy = vitest_1.vi.spyOn(process, "exit").mockImplementation(() => {
70
+ throw new Error("process.exit called");
71
+ });
72
+ vitest_1.vi.spyOn(process.stderr, "write").mockImplementation(() => true);
73
+ try {
74
+ (0, vitest_1.expect)(() => (0, abort_js_1.runLoopAbort)({
75
+ reason: "dependency not done",
76
+ childId: "POL-27",
77
+ repoRoot: testDir,
78
+ stateFile,
79
+ })).toThrow("process.exit called");
80
+ }
81
+ finally {
82
+ exitSpy.mockRestore();
83
+ vitest_1.vi.restoreAllMocks();
84
+ }
85
+ const saved = JSON.parse((0, node_fs_1.readFileSync)(stateFile, "utf-8"));
86
+ (0, vitest_1.expect)(saved.blocker.child_id).toBe("POL-27");
87
+ });
88
+ (0, vitest_1.it)("appends loop-aborted JSONL event to telemetry file", () => {
89
+ const artifactDir = (0, node_path_1.join)(testDir, ".taskchain_artifacts", "bootstrap-run");
90
+ const telemetryFile = (0, node_path_1.join)(artifactDir, "runs", "pol-5-session-1", "telemetry.jsonl");
91
+ const stateFile = writeState(testDir, baseState);
92
+ const exitSpy = vitest_1.vi.spyOn(process, "exit").mockImplementation(() => {
93
+ throw new Error("process.exit called");
94
+ });
95
+ vitest_1.vi.spyOn(process.stderr, "write").mockImplementation(() => true);
96
+ try {
97
+ (0, vitest_1.expect)(() => (0, abort_js_1.runLoopAbort)({ reason: "test blocker", repoRoot: testDir, stateFile })).toThrow("process.exit called");
98
+ }
99
+ finally {
100
+ exitSpy.mockRestore();
101
+ vitest_1.vi.restoreAllMocks();
102
+ }
103
+ const lines = (0, node_fs_1.readFileSync)(telemetryFile, "utf-8").trim().split("\n");
104
+ const event = JSON.parse(lines[lines.length - 1]);
105
+ (0, vitest_1.expect)(event.event).toBe("loop-aborted");
106
+ (0, vitest_1.expect)(event.run_id).toBe("pol-5-session-1");
107
+ (0, vitest_1.expect)(event.reason).toBe("test blocker");
108
+ });
109
+ (0, vitest_1.it)("prints abort message to stderr and exits 1", () => {
110
+ const stateFile = writeState(testDir, baseState);
111
+ const stderrMessages = [];
112
+ const exitSpy = vitest_1.vi.spyOn(process, "exit").mockImplementation(() => {
113
+ throw new Error("process.exit called");
114
+ });
115
+ const stderrSpy = vitest_1.vi
116
+ .spyOn(process.stderr, "write")
117
+ .mockImplementation((msg) => {
118
+ stderrMessages.push(String(msg));
119
+ return true;
120
+ });
121
+ try {
122
+ (0, vitest_1.expect)(() => (0, abort_js_1.runLoopAbort)({ reason: "blocked by missing dep", repoRoot: testDir, stateFile })).toThrow("process.exit called");
123
+ (0, vitest_1.expect)(exitSpy).toHaveBeenCalledWith(1);
124
+ (0, vitest_1.expect)(stderrMessages.some((m) => m.includes("Loop aborted"))).toBe(true);
125
+ (0, vitest_1.expect)(stderrMessages.some((m) => m.includes("blocked by missing dep"))).toBe(true);
126
+ (0, vitest_1.expect)(stderrMessages.some((m) => m.includes("polaris loop resume"))).toBe(true);
127
+ }
128
+ finally {
129
+ exitSpy.mockRestore();
130
+ stderrSpy.mockRestore();
131
+ }
132
+ });
133
+ (0, vitest_1.it)("exits with error when state file is missing", () => {
134
+ const exitSpy = vitest_1.vi.spyOn(process, "exit").mockImplementation(() => {
135
+ throw new Error("process.exit called");
136
+ });
137
+ const stderrSpy = vitest_1.vi.spyOn(process.stderr, "write").mockImplementation(() => true);
138
+ try {
139
+ (0, vitest_1.expect)(() => (0, abort_js_1.runLoopAbort)({
140
+ reason: "test",
141
+ repoRoot: testDir,
142
+ stateFile: (0, node_path_1.join)(testDir, "nonexistent.json"),
143
+ })).toThrow("process.exit called");
144
+ (0, vitest_1.expect)(stderrSpy).toHaveBeenCalledWith(vitest_1.expect.stringContaining("cannot read state file"));
145
+ }
146
+ finally {
147
+ exitSpy.mockRestore();
148
+ stderrSpy.mockRestore();
149
+ }
150
+ });
151
+ });
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const terminal_cli_js_1 = require("../terminal-cli.js");
5
+ const registry_js_1 = require("../registry.js");
6
+ const MOCK_PACKET = {
7
+ schema_version: "1.0",
8
+ run_id: "run-test-0001",
9
+ cluster_id: "POL-5",
10
+ active_child: "POL-14",
11
+ state_file: "/tmp/polaris-test/current-state.json",
12
+ telemetry_file: "/tmp/polaris-test/telemetry.jsonl",
13
+ };
14
+ (0, vitest_1.describe)("TerminalCliAdapter", () => {
15
+ (0, vitest_1.describe)("provider resolution", () => {
16
+ (0, vitest_1.it)("throws for unknown provider with list of available ones", async () => {
17
+ const adapter = new terminal_cli_js_1.TerminalCliAdapter({
18
+ adapter: "terminal-cli",
19
+ providers: {
20
+ codex: { command: "codex" },
21
+ gemini: { command: "gemini" },
22
+ },
23
+ });
24
+ await (0, vitest_1.expect)(adapter.dispatch(MOCK_PACKET, { provider: "windsurf", dryRun: true })).rejects.toThrow(/Unknown provider "windsurf".*codex.*gemini/);
25
+ });
26
+ (0, vitest_1.it)("includes helpful message when no providers are configured", async () => {
27
+ const adapter = new terminal_cli_js_1.TerminalCliAdapter({
28
+ adapter: "terminal-cli",
29
+ providers: {},
30
+ });
31
+ await (0, vitest_1.expect)(adapter.dispatch(MOCK_PACKET, { provider: "codex", dryRun: true })).rejects.toThrow(/No providers configured/);
32
+ });
33
+ });
34
+ (0, vitest_1.describe)("command expansion", () => {
35
+ (0, vitest_1.it)("expands $ENV_VAR references in command", async () => {
36
+ process.env.POLARIS_AGENT = "/usr/local/bin/myagent";
37
+ const adapter = new terminal_cli_js_1.TerminalCliAdapter({
38
+ adapter: "terminal-cli",
39
+ providers: { custom: { command: "$POLARIS_AGENT", args: ["{{active_child}}"] } },
40
+ });
41
+ const result = await adapter.dispatch(MOCK_PACKET, { provider: "custom", dryRun: true });
42
+ (0, vitest_1.expect)(result.command_run).toContain("/usr/local/bin/myagent");
43
+ (0, vitest_1.expect)(result.command_run).toContain("POL-14");
44
+ delete process.env.POLARIS_AGENT;
45
+ });
46
+ (0, vitest_1.it)("throws when command expands to an unset $VAR", async () => {
47
+ delete process.env.POLARIS_MISSING_VAR;
48
+ const adapter = new terminal_cli_js_1.TerminalCliAdapter({
49
+ adapter: "terminal-cli",
50
+ providers: { custom: { command: "$POLARIS_MISSING_VAR" } },
51
+ });
52
+ await (0, vitest_1.expect)(adapter.dispatch(MOCK_PACKET, { provider: "custom", dryRun: true })).rejects.toThrow(/unset environment variable|empty string/);
53
+ });
54
+ (0, vitest_1.it)("substitutes {{template}} variables in args", async () => {
55
+ const adapter = new terminal_cli_js_1.TerminalCliAdapter({
56
+ adapter: "terminal-cli",
57
+ providers: {
58
+ codex: {
59
+ command: "echo",
60
+ args: ["child={{active_child}}", "run={{run_id}}"],
61
+ },
62
+ },
63
+ });
64
+ const result = await adapter.dispatch(MOCK_PACKET, { provider: "codex", dryRun: true });
65
+ (0, vitest_1.expect)(result.command_run).toContain("child=POL-14");
66
+ (0, vitest_1.expect)(result.command_run).toContain("run=run-test-0001");
67
+ });
68
+ });
69
+ (0, vitest_1.describe)("dry-run", () => {
70
+ (0, vitest_1.it)("returns exit_code 0 and summary [dry-run] without spawning a process", async () => {
71
+ const adapter = new terminal_cli_js_1.TerminalCliAdapter({
72
+ adapter: "terminal-cli",
73
+ providers: {
74
+ codex: { command: "does-not-exist-binary", args: ["--child", "{{active_child}}"] },
75
+ },
76
+ });
77
+ const result = await adapter.dispatch(MOCK_PACKET, { provider: "codex", dryRun: true });
78
+ (0, vitest_1.expect)(result.exit_code).toBe(0);
79
+ (0, vitest_1.expect)(result.summary).toBe("[dry-run]");
80
+ (0, vitest_1.expect)(result.provider_used).toBe("codex");
81
+ });
82
+ });
83
+ });
84
+ (0, vitest_1.describe)("registry", () => {
85
+ (0, vitest_1.it)("createAdapter returns TerminalCliAdapter for terminal-cli", () => {
86
+ const adapter = (0, registry_js_1.createAdapter)("terminal-cli", { adapter: "terminal-cli", providers: {} });
87
+ (0, vitest_1.expect)(adapter.name).toBe("terminal-cli");
88
+ });
89
+ (0, vitest_1.it)("createAdapter throws for unknown adapter", () => {
90
+ (0, vitest_1.expect)(() => (0, registry_js_1.createAdapter)("agent-subtask", { adapter: "agent-subtask", providers: {} })).toThrow(/Unknown adapter/);
91
+ });
92
+ });
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ /**
3
+ * Agent-subtask adapter — dispatches a worker as a native agent subtask.
4
+ *
5
+ * When the packet is a compiled WorkerPacket (schema_version === '2.0') the
6
+ * adapter uses pre-compiled instructions from `packet.instructions` rather
7
+ * than generating generic instructions from packet metadata. This eliminates
8
+ * per-dispatch skill re-ingestion and reduces worker token burn.
9
+ *
10
+ * The adapter also includes the lifecycle teardown contract in every prompt so
11
+ * the worker session terminates immediately after returning compact JSON.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.AgentSubtaskAdapter = void 0;
15
+ const worker_packet_js_1 = require("../worker-packet.js");
16
+ const worker_instructions_js_1 = require("./worker-instructions.js");
17
+ function defaultDispatcher() {
18
+ const host = globalThis;
19
+ return host.__POLARIS_AGENT_SUBTASK_DISPATCH__;
20
+ }
21
+ function normalizeSummary(value) {
22
+ return typeof value === "string" ? value : JSON.stringify(value);
23
+ }
24
+ function validateSummary(summary, packet) {
25
+ try {
26
+ const parsed = JSON.parse(summary);
27
+ // For finalize/preflight workers active_child is "" — skip child_id check.
28
+ if (packet.active_child) {
29
+ const returnedChild = parsed.child_id ?? parsed.active_child;
30
+ if (returnedChild !== packet.active_child) {
31
+ return `Native subtask returned mismatched child_id: expected ${packet.active_child}, got ${String(returnedChild)}`;
32
+ }
33
+ }
34
+ if (!["done", "blocked", "error", "success", "failure"].includes(String(parsed.status))) {
35
+ return `Native subtask returned invalid status: ${String(parsed.status)}`;
36
+ }
37
+ return null;
38
+ }
39
+ catch {
40
+ return "Native subtask returned malformed compact JSON";
41
+ }
42
+ }
43
+ class AgentSubtaskAdapter {
44
+ dispatcher;
45
+ name = 'agent-subtask';
46
+ constructor(dispatcher = defaultDispatcher()) {
47
+ this.dispatcher = dispatcher;
48
+ }
49
+ async dispatch(packet, options) {
50
+ const label = packet.active_child || ((0, worker_packet_js_1.isWorkerPacket)(packet) ? packet.worker_role : 'worker');
51
+ const commandRun = `agent-subtask:${label}`;
52
+ const provider = options.provider || "agent-subtask";
53
+ if (!this.dispatcher) {
54
+ const error = "Native ephemeral agent subtask dispatch is unavailable in this host environment. " +
55
+ "Use manual handoff or a configured terminal-cli adapter.";
56
+ return {
57
+ exit_code: 1,
58
+ pre_dispatch_failure: true,
59
+ provider_used: provider,
60
+ command_run: commandRun,
61
+ summary: error,
62
+ stderr: error,
63
+ };
64
+ }
65
+ if ((0, worker_packet_js_1.isWorkerPacket)(packet) && packet.worker_role === 'impl') {
66
+ const allowed = Array.isArray(packet.instructions?.allowed_scope) ? packet.instructions.allowed_scope : [];
67
+ if (allowed.length === 0) {
68
+ const blockedMsg = `Worker blocked: impl packet for ${packet.active_child} has empty allowed_scope. Foreman must provide scope or approve override.`;
69
+ return {
70
+ exit_code: 1,
71
+ provider_used: provider,
72
+ command_run: commandRun,
73
+ summary: JSON.stringify({
74
+ child_id: packet.active_child,
75
+ status: "blocked",
76
+ validation_summary: blockedMsg,
77
+ next_action: "escalate",
78
+ warnings: ["empty-allowed-scope"],
79
+ }),
80
+ stderr: blockedMsg,
81
+ };
82
+ }
83
+ }
84
+ if (options.dryRun) {
85
+ const childId = packet.active_child || 'no-child';
86
+ return {
87
+ exit_code: 0,
88
+ provider_used: provider,
89
+ command_run: commandRun,
90
+ summary: JSON.stringify({
91
+ child_id: childId,
92
+ status: "done",
93
+ validation_summary: "dry-run: native ephemeral agent subtask dispatch not executed",
94
+ next_action: "resume-parent",
95
+ warnings: ["dry-run"],
96
+ }),
97
+ };
98
+ }
99
+ const instructions = (0, worker_instructions_js_1.buildWorkerInstructions)(packet);
100
+ const returnContract = (0, worker_instructions_js_1.returnContractFor)(packet);
101
+ try {
102
+ const rawSummary = await this.dispatcher({
103
+ packet,
104
+ instructions,
105
+ returnContract,
106
+ });
107
+ const summary = normalizeSummary(rawSummary);
108
+ const validationError = validateSummary(summary, packet);
109
+ if (validationError) {
110
+ return {
111
+ exit_code: 1,
112
+ provider_used: provider,
113
+ command_run: commandRun,
114
+ summary: validationError,
115
+ stderr: validationError,
116
+ };
117
+ }
118
+ return {
119
+ exit_code: 0,
120
+ provider_used: provider,
121
+ command_run: commandRun,
122
+ summary,
123
+ };
124
+ }
125
+ catch (err) {
126
+ const msg = err instanceof Error ? err.message : String(err);
127
+ return {
128
+ exit_code: 1,
129
+ provider_used: provider,
130
+ command_run: commandRun,
131
+ summary: `Native ephemeral agent subtask dispatch failed: ${msg}`,
132
+ stderr: msg,
133
+ };
134
+ }
135
+ }
136
+ }
137
+ exports.AgentSubtaskAdapter = AgentSubtaskAdapter;