@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,665 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hasWorkerAcknowledged = hasWorkerAcknowledged;
4
+ exports.canSessionBeAttached = canSessionBeAttached;
5
+ exports.runLoopStatus = runLoopStatus;
6
+ const node_fs_1 = require("node:fs");
7
+ const node_path_1 = require("node:path");
8
+ const node_crypto_1 = require("node:crypto");
9
+ const node_child_process_1 = require("node:child_process");
10
+ const checkpoint_js_1 = require("./checkpoint.js");
11
+ const loader_js_1 = require("../config/loader.js");
12
+ /**
13
+ * Find dispatch evidence for the active child.
14
+ * Searches cluster-scoped layout and open_children_meta.
15
+ */
16
+ function findDispatchEvidence(repoRoot, clusterId, activeChild, openChildrenMeta) {
17
+ if (!activeChild)
18
+ return null;
19
+ // First check open_children_meta for dispatch_record
20
+ const childMeta = openChildrenMeta?.[activeChild];
21
+ const dispatchRecord = childMeta?.dispatch_record;
22
+ if (dispatchRecord) {
23
+ // Check if result file exists
24
+ const resultPresent = (0, node_fs_1.existsSync)(dispatchRecord.expected_result_path);
25
+ return {
26
+ child_id: dispatchRecord.child_id,
27
+ dispatch_id: dispatchRecord.dispatch_id,
28
+ packet_path: dispatchRecord.packet_path,
29
+ expected_result_path: dispatchRecord.expected_result_path,
30
+ result_present: resultPresent,
31
+ dispatch_status: dispatchRecord.status,
32
+ provider: dispatchRecord.provider,
33
+ dispatched_at: dispatchRecord.dispatched_at,
34
+ dispatch_mode: dispatchRecord.dispatch_mode,
35
+ runtime_state: dispatchRecord.runtime_state,
36
+ last_heartbeat_at: dispatchRecord.last_heartbeat_at,
37
+ last_heartbeat_step: dispatchRecord.last_heartbeat_step,
38
+ worker_assignment: dispatchRecord.worker_assignment,
39
+ };
40
+ }
41
+ // Fallback: scan cluster packets directory for matching files
42
+ const packetDir = (0, node_path_1.join)(repoRoot, ".polaris", "clusters", clusterId, "packets");
43
+ if (!(0, node_fs_1.existsSync)(packetDir))
44
+ return null;
45
+ // Look for files matching <child-id>-*.json pattern
46
+ const files = (0, node_fs_1.readdirSync)(packetDir).filter((f) => f.startsWith(`${activeChild}-`) && f.endsWith(".json"));
47
+ if (files.length === 0)
48
+ return null;
49
+ // Use the most recent file (sorted by name, which includes timestamp/UUID)
50
+ const latestFile = files.sort().at(-1);
51
+ const packetPath = (0, node_path_1.join)(packetDir, latestFile);
52
+ // Derive result path from packet path
53
+ const resultDir = (0, node_path_1.join)(repoRoot, ".polaris", "clusters", clusterId, "results");
54
+ const resultPath = (0, node_path_1.join)(resultDir, latestFile);
55
+ // Check if result exists
56
+ const resultPresent = (0, node_fs_1.existsSync)(resultPath);
57
+ // Extract dispatch ID from filename (format: <child-id>-<dispatch-id>.json)
58
+ const dispatchId = latestFile.replace(`${activeChild}-`, "").replace(".json", "");
59
+ return {
60
+ child_id: activeChild,
61
+ dispatch_id: dispatchId,
62
+ packet_path: packetPath,
63
+ expected_result_path: resultPath,
64
+ result_present: resultPresent,
65
+ dispatch_status: resultPresent ? "completed" : "dispatched",
66
+ dispatched_at: "unknown", // Cannot determine from file alone
67
+ };
68
+ }
69
+ /**
70
+ * Get relative path from repo root if path is inside repo.
71
+ */
72
+ function getRelativePath(repoRoot, absolutePath) {
73
+ if (absolutePath.startsWith(repoRoot + "/")) {
74
+ return absolutePath.slice(repoRoot.length + 1);
75
+ }
76
+ return absolutePath;
77
+ }
78
+ function classifyCurrentStateSurface(repoRoot, stateFile) {
79
+ const relativePath = getRelativePath(repoRoot, stateFile);
80
+ const normalizedPath = relativePath.split(node_path_1.sep).join("/");
81
+ const targetPath = (0, node_path_1.join)(".polaris", "runs", "current-state.json").split(node_path_1.sep).join("/");
82
+ if (normalizedPath === targetPath) {
83
+ return {
84
+ path: relativePath,
85
+ role: "legacy/transitional current-state snapshot",
86
+ classification: "legacy",
87
+ exists: (0, node_fs_1.existsSync)(stateFile),
88
+ };
89
+ }
90
+ return {
91
+ path: relativePath,
92
+ role: "compatibility/debug current-state surface",
93
+ classification: "compatibility",
94
+ exists: (0, node_fs_1.existsSync)(stateFile),
95
+ };
96
+ }
97
+ function buildRuntimeArtifactSurfaces(repoRoot, clusterId, stateFile, bootstrapDir, telemetryFile) {
98
+ const clusterStatePath = (0, node_path_1.join)(repoRoot, ".polaris", "clusters", clusterId, "cluster-state.json");
99
+ return {
100
+ cluster_state: {
101
+ path: getRelativePath(repoRoot, clusterStatePath),
102
+ role: "live execution authority",
103
+ classification: "canonical",
104
+ exists: (0, node_fs_1.existsSync)(clusterStatePath),
105
+ },
106
+ current_state: classifyCurrentStateSurface(repoRoot, stateFile),
107
+ bootstrap_packets: {
108
+ path: getRelativePath(repoRoot, bootstrapDir),
109
+ role: "sealed handoff snapshots",
110
+ classification: "derived",
111
+ exists: (0, node_fs_1.existsSync)(bootstrapDir),
112
+ },
113
+ telemetry: {
114
+ path: getRelativePath(repoRoot, telemetryFile),
115
+ role: "append-only audit/debug stream",
116
+ classification: "debug",
117
+ exists: (0, node_fs_1.existsSync)(telemetryFile),
118
+ },
119
+ };
120
+ }
121
+ /**
122
+ * Find latest worker heartbeat for active child from telemetry.
123
+ */
124
+ function findWorkerHeartbeat(telemetryFile, activeChild) {
125
+ if (!activeChild || !(0, node_fs_1.existsSync)(telemetryFile))
126
+ return null;
127
+ try {
128
+ const content = (0, node_fs_1.readFileSync)(telemetryFile, "utf-8");
129
+ const lines = content.trim().split("\n").filter(Boolean);
130
+ // Parse from most recent to find latest heartbeat for this child
131
+ for (let i = lines.length - 1; i >= 0; i--) {
132
+ try {
133
+ const event = JSON.parse(lines[i]);
134
+ if (event.event === "worker-heartbeat" && event.child_id === activeChild) {
135
+ return event;
136
+ }
137
+ }
138
+ catch {
139
+ continue; // Skip malformed lines
140
+ }
141
+ }
142
+ }
143
+ catch {
144
+ return null;
145
+ }
146
+ return null;
147
+ }
148
+ /**
149
+ * Format heartbeat for display.
150
+ */
151
+ function formatHeartbeat(heartbeat) {
152
+ const time = new Date(heartbeat.timestamp).toLocaleTimeString();
153
+ let status = heartbeat.step_cursor;
154
+ if (heartbeat.progress_pct !== undefined) {
155
+ status += ` (${heartbeat.progress_pct}%)`;
156
+ }
157
+ if (heartbeat.current_file) {
158
+ status += ` - ${heartbeat.current_file}`;
159
+ }
160
+ return `${time}: ${status}`;
161
+ }
162
+ /**
163
+ * Find latest worker-blocked event for active child.
164
+ */
165
+ function findWorkerBlocked(telemetryFile, activeChild) {
166
+ if (!activeChild || !(0, node_fs_1.existsSync)(telemetryFile))
167
+ return null;
168
+ try {
169
+ const content = (0, node_fs_1.readFileSync)(telemetryFile, "utf-8");
170
+ const lines = content.trim().split("\n").filter(Boolean);
171
+ for (let i = lines.length - 1; i >= 0; i--) {
172
+ try {
173
+ const event = JSON.parse(lines[i]);
174
+ if (event.event === "worker-blocked" && event.child_id === activeChild) {
175
+ return event;
176
+ }
177
+ }
178
+ catch {
179
+ continue;
180
+ }
181
+ }
182
+ }
183
+ catch {
184
+ return null;
185
+ }
186
+ return null;
187
+ }
188
+ /**
189
+ * Format blocked event for display.
190
+ */
191
+ function formatBlockedEvent(event) {
192
+ const time = new Date(event.timestamp).toLocaleTimeString();
193
+ let msg = `${time}: ${event.reason}`;
194
+ if (event.approval_type) {
195
+ msg += ` (${event.approval_type})`;
196
+ }
197
+ if (event.description) {
198
+ msg += ` - ${event.description.slice(0, 60)}${event.description.length > 60 ? "..." : ""}`;
199
+ }
200
+ return msg;
201
+ }
202
+ /**
203
+ * Find all auto-approved events for active child.
204
+ */
205
+ function findAutoApprovedEvents(telemetryFile, activeChild) {
206
+ if (!activeChild || !(0, node_fs_1.existsSync)(telemetryFile))
207
+ return [];
208
+ const events = [];
209
+ try {
210
+ const content = (0, node_fs_1.readFileSync)(telemetryFile, "utf-8");
211
+ const lines = content.trim().split("\n").filter(Boolean);
212
+ for (const line of lines) {
213
+ try {
214
+ const event = JSON.parse(line);
215
+ if (event.event === "worker-auto-approved" && event.child_id === activeChild) {
216
+ events.push(event);
217
+ }
218
+ }
219
+ catch {
220
+ continue;
221
+ }
222
+ }
223
+ }
224
+ catch {
225
+ return [];
226
+ }
227
+ return events;
228
+ }
229
+ /**
230
+ * States that indicate the worker has acknowledged the dispatch packet.
231
+ * These are states at or after the "acknowledged" lifecycle stage.
232
+ */
233
+ const ACKNOWLEDGED_OR_LATER_STATES = new Set([
234
+ "acknowledged",
235
+ "running",
236
+ "waiting-for-approval",
237
+ "blocked",
238
+ "completed",
239
+ "failed",
240
+ "orphaned",
241
+ ]);
242
+ /**
243
+ * Check whether the worker for the given dispatch has acknowledged the packet.
244
+ *
245
+ * Primary: checks runtime_state on the ChildDispatchRecord against the set of
246
+ * states at or after "acknowledged".
247
+ * Fallback: scans telemetry log for a worker-acknowledged event with a matching
248
+ * dispatch_id.
249
+ *
250
+ * Returns false for unknown dispatch_id (does not throw).
251
+ */
252
+ function hasWorkerAcknowledged(dispatch_id, state, telemetryFile) {
253
+ // Search all open_children_meta for a matching dispatch record
254
+ const meta = state.open_children_meta ?? {};
255
+ for (const childKey of Object.keys(meta)) {
256
+ const record = meta[childKey]?.dispatch_record;
257
+ if (record && record.dispatch_id === dispatch_id) {
258
+ // Primary check: runtime_state
259
+ if (record.runtime_state && ACKNOWLEDGED_OR_LATER_STATES.has(record.runtime_state)) {
260
+ return true;
261
+ }
262
+ break; // Found the record but not acknowledged by state; try telemetry fallback
263
+ }
264
+ }
265
+ // Fallback: scan telemetry log for worker-acknowledged event
266
+ if (telemetryFile && (0, node_fs_1.existsSync)(telemetryFile)) {
267
+ try {
268
+ const content = (0, node_fs_1.readFileSync)(telemetryFile, "utf-8");
269
+ const lines = content.trim().split("\n").filter(Boolean);
270
+ for (const line of lines) {
271
+ try {
272
+ const event = JSON.parse(line);
273
+ if (event.event === "worker-acknowledged" && event.dispatch_id === dispatch_id) {
274
+ return true;
275
+ }
276
+ }
277
+ catch {
278
+ continue;
279
+ }
280
+ }
281
+ }
282
+ catch {
283
+ // Ignore read errors
284
+ }
285
+ }
286
+ return false;
287
+ }
288
+ /**
289
+ * Check whether a session can be attached to the worker for the given dispatch.
290
+ *
291
+ * Returns true only when attachment_capable === true AND session_id is non-null/undefined
292
+ * on the ChildDispatchRecord. Does NOT scan telemetry.
293
+ *
294
+ * Returns false for unknown dispatch_id (does not throw).
295
+ */
296
+ function canSessionBeAttached(dispatch_id, state) {
297
+ const meta = state.open_children_meta ?? {};
298
+ for (const childKey of Object.keys(meta)) {
299
+ const record = meta[childKey]?.dispatch_record;
300
+ if (record && record.dispatch_id === dispatch_id) {
301
+ return record.attachment_capable === true &&
302
+ record.session_id !== null &&
303
+ record.session_id !== undefined;
304
+ }
305
+ }
306
+ return false;
307
+ }
308
+ function getCurrentBranch(repoRoot) {
309
+ try {
310
+ return (0, node_child_process_1.execFileSync)("git", ["branch", "--show-current"], {
311
+ cwd: repoRoot,
312
+ encoding: "utf-8",
313
+ }).trim();
314
+ }
315
+ catch {
316
+ return "unknown";
317
+ }
318
+ }
319
+ function computeStateSha(stateFile) {
320
+ const content = (0, node_fs_1.readFileSync)(stateFile, "utf-8");
321
+ return (0, node_crypto_1.createHash)("sha256").update(content).digest("hex");
322
+ }
323
+ function findLatestPacket(bootstrapDir, runId) {
324
+ let entries;
325
+ try {
326
+ entries = (0, node_fs_1.readdirSync)(bootstrapDir).filter((f) => f.endsWith(".json"));
327
+ }
328
+ catch {
329
+ return null;
330
+ }
331
+ if (entries.length === 0)
332
+ return null;
333
+ const candidates = runId
334
+ ? entries.filter((f) => f.startsWith(`${runId}-`)).sort()
335
+ : entries.sort();
336
+ if (candidates.length === 0)
337
+ return null;
338
+ const latest = candidates.at(-1);
339
+ const fullPath = (0, node_path_1.join)(bootstrapDir, latest);
340
+ try {
341
+ const raw = (0, node_fs_1.readFileSync)(fullPath, "utf-8");
342
+ return { path: fullPath, packet: JSON.parse(raw) };
343
+ }
344
+ catch {
345
+ return null;
346
+ }
347
+ }
348
+ function runLoopStatus(options) {
349
+ const { repoRoot } = options;
350
+ const config = (0, loader_js_1.loadConfig)(repoRoot);
351
+ const bootstrapDir = (0, node_path_1.resolve)(repoRoot, config.loop.bootstrapOutputPath ?? ".polaris/bootstrap");
352
+ const stateFile = options.stateFile ?? (0, node_path_1.join)(repoRoot, ".polaris", "runs", "current-state.json");
353
+ let state;
354
+ try {
355
+ const raw = (0, checkpoint_js_1.readState)(stateFile);
356
+ const errors = (0, checkpoint_js_1.validateState)(raw);
357
+ if (errors.length > 0) {
358
+ console.error(`current-state.json invalid:\n${errors.join("\n")}`);
359
+ process.exit(1);
360
+ }
361
+ state = raw;
362
+ }
363
+ catch (err) {
364
+ console.error(`Error: cannot read state file ${stateFile}: ${err instanceof Error ? err.message : String(err)}`);
365
+ process.exit(1);
366
+ }
367
+ const branch = getCurrentBranch(repoRoot);
368
+ const openChildren = state.open_children ?? [];
369
+ const blockedChildren = state["blocked_children"] ?? [];
370
+ const packetResult = findLatestPacket(bootstrapDir, state.run_id);
371
+ let packetFresh = null;
372
+ let packetPathDisplay = null;
373
+ let stateSha = null;
374
+ if (packetResult) {
375
+ try {
376
+ stateSha = computeStateSha(stateFile);
377
+ packetFresh = packetResult.packet.current_state_sha === stateSha;
378
+ packetPathDisplay = packetResult.path.startsWith(repoRoot + "/")
379
+ ? packetResult.path.slice(repoRoot.length + 1)
380
+ : packetResult.path;
381
+ }
382
+ catch {
383
+ packetFresh = false;
384
+ }
385
+ }
386
+ const isDeadlock = openChildren.length > 0 &&
387
+ blockedChildren.length > 0 &&
388
+ openChildren.every((c) => blockedChildren.includes(c));
389
+ // ── Find dispatch evidence for active child ────────────────────────────────
390
+ const dispatchEvidence = findDispatchEvidence(repoRoot, state.cluster_id, state.active_child || null, state.open_children_meta);
391
+ // ── Find worker heartbeat for active child ──────────────────────────────────
392
+ const artifactDir = state.artifact_dir ?? (0, node_path_1.join)(repoRoot, ".taskchain_artifacts", "polaris-run");
393
+ const telemetryFile = (0, node_path_1.join)(artifactDir, "runs", state.run_id, "telemetry.jsonl");
394
+ const runtimeArtifacts = buildRuntimeArtifactSurfaces(repoRoot, state.cluster_id, stateFile, bootstrapDir, telemetryFile);
395
+ const workerHeartbeat = findWorkerHeartbeat(telemetryFile, state.active_child || null);
396
+ const workerBlocked = findWorkerBlocked(telemetryFile, state.active_child || null);
397
+ // ── Role context from active dispatch record ───────────────────────────────
398
+ const activeDispatch = state.open_children_meta?.[state.active_child]?.dispatch_record;
399
+ if (options.json) {
400
+ console.log(JSON.stringify({
401
+ run_id: state.run_id,
402
+ cluster_id: state.cluster_id,
403
+ branch,
404
+ session_type: state.session_type ?? null,
405
+ active_child: state.active_child || null,
406
+ step_cursor: state.step_cursor,
407
+ status: state.status,
408
+ context_budget: state.context_budget,
409
+ completed_children: state.completed_children,
410
+ open_children: openChildren,
411
+ blocked_children: blockedChildren,
412
+ deadlock: isDeadlock,
413
+ bootstrap_packet: packetPathDisplay
414
+ ? { path: packetPathDisplay, fresh: packetFresh }
415
+ : null,
416
+ runtime_artifacts: runtimeArtifacts,
417
+ state_sha: stateSha ? stateSha.slice(0, 12) : null,
418
+ role_context: activeDispatch?.role ? {
419
+ role: activeDispatch.role,
420
+ role_authority: activeDispatch.role_authority ?? null,
421
+ may_implement: activeDispatch.may_implement ?? null,
422
+ session_type: activeDispatch.session_type ?? null,
423
+ } : null,
424
+ dispatch: dispatchEvidence
425
+ ? {
426
+ child_id: dispatchEvidence.child_id,
427
+ dispatch_status: dispatchEvidence.dispatch_status,
428
+ packet_path: getRelativePath(repoRoot, dispatchEvidence.packet_path),
429
+ expected_result_path: getRelativePath(repoRoot, dispatchEvidence.expected_result_path),
430
+ result_present: dispatchEvidence.result_present,
431
+ provider: dispatchEvidence.provider ?? null,
432
+ dispatched_at: dispatchEvidence.dispatched_at,
433
+ dispatch_mode: dispatchEvidence.dispatch_mode ?? null,
434
+ // Derive runtime state: delegated mode defaults to "delegated", else "unknown"
435
+ runtime_state: dispatchEvidence.runtime_state ??
436
+ ((dispatchEvidence.dispatch_mode ?? "delegated") === "delegated" ? "delegated" : "unknown"),
437
+ last_heartbeat_at: dispatchEvidence.last_heartbeat_at ?? null,
438
+ last_heartbeat_step: dispatchEvidence.last_heartbeat_step ?? null,
439
+ worker_assignment: dispatchEvidence.worker_assignment ?? null,
440
+ worker_acknowledged: hasWorkerAcknowledged(dispatchEvidence.dispatch_id, state, telemetryFile),
441
+ session_attachable: canSessionBeAttached(dispatchEvidence.dispatch_id, state),
442
+ }
443
+ : null,
444
+ worker: workerHeartbeat
445
+ ? {
446
+ child_id: workerHeartbeat.child_id,
447
+ step_cursor: workerHeartbeat.step_cursor,
448
+ last_seen: workerHeartbeat.timestamp,
449
+ progress_pct: workerHeartbeat.progress_pct ?? null,
450
+ files_changed: workerHeartbeat.files_changed ?? null,
451
+ current_file: workerHeartbeat.current_file ?? null,
452
+ }
453
+ : null,
454
+ blocked: workerBlocked
455
+ ? {
456
+ reason: workerBlocked.reason,
457
+ approval_type: workerBlocked.approval_type ?? null,
458
+ description: workerBlocked.description ?? null,
459
+ blocker_id: workerBlocked.blocker_id ?? null,
460
+ timestamp: workerBlocked.timestamp,
461
+ }
462
+ : null,
463
+ }, null, 2));
464
+ return;
465
+ }
466
+ const maxChildren = state.context_budget.max_children_per_session ?? 3;
467
+ const completed = state.context_budget.children_completed;
468
+ const remaining = Math.max(0, maxChildren - completed);
469
+ // ── Role block ────────────────────────────────────────────────────────────
470
+ const roleLines = [];
471
+ if (activeDispatch?.role) {
472
+ const roleName = activeDispatch.role.charAt(0).toUpperCase() + activeDispatch.role.slice(1);
473
+ const authorityLabel = activeDispatch.role_authority ?? "(unknown)";
474
+ const mayImpl = activeDispatch.may_implement === true ? "Yes" : activeDispatch.may_implement === false ? "No" : "(unknown)";
475
+ const mayAssign = activeDispatch.role === "foreman" ? "Yes" : activeDispatch.role === "worker" ? "No" : "(unknown)";
476
+ roleLines.push("", "Role Context:");
477
+ roleLines.push(` Role: ${roleName}`);
478
+ roleLines.push(` Authority: ${authorityLabel}`);
479
+ roleLines.push(` May Implement: ${mayImpl}`);
480
+ roleLines.push(` May Assign Workers: ${mayAssign}`);
481
+ }
482
+ const lines = [
483
+ "Polaris Loop Status",
484
+ "───────────────────",
485
+ `Run ID: ${state.run_id}`,
486
+ `Cluster: ${state.cluster_id}`,
487
+ `Branch: ${branch}`,
488
+ `Session type: ${state.session_type ?? "(not set)"}`,
489
+ `Active child: ${state.active_child || "(none)"}`,
490
+ `Step cursor: ${state.step_cursor ?? "(none)"}`,
491
+ `Context budget: ${completed}/${maxChildren} children completed (${remaining} remaining)`,
492
+ "",
493
+ `Completed: ${state.completed_children.length > 0 ? state.completed_children.join(", ") + ` (${state.completed_children.length})` : "none"}`,
494
+ `Open: ${openChildren.length > 0 ? openChildren.join(", ") + ` (${openChildren.length})` : "none"}`,
495
+ `Blocked: ${blockedChildren.length > 0 ? blockedChildren.join(", ") : "none"}`,
496
+ ];
497
+ lines.push(...roleLines);
498
+ lines.push("");
499
+ lines.push("Runtime Artifacts:");
500
+ lines.push(` Cluster state: ${runtimeArtifacts.cluster_state.path} (${runtimeArtifacts.cluster_state.classification}; ${runtimeArtifacts.cluster_state.role}; ${runtimeArtifacts.cluster_state.exists ? "exists" : "missing"})`);
501
+ lines.push(` Current state: ${runtimeArtifacts.current_state.path} (${runtimeArtifacts.current_state.classification}; ${runtimeArtifacts.current_state.role}; ${runtimeArtifacts.current_state.exists ? "exists" : "missing"})`);
502
+ lines.push(` Bootstrap dir: ${runtimeArtifacts.bootstrap_packets.path} (${runtimeArtifacts.bootstrap_packets.classification}; ${runtimeArtifacts.bootstrap_packets.role}; ${runtimeArtifacts.bootstrap_packets.exists ? "exists" : "missing"})`);
503
+ lines.push(` Telemetry: ${runtimeArtifacts.telemetry.path} (${runtimeArtifacts.telemetry.classification}; ${runtimeArtifacts.telemetry.role}; ${runtimeArtifacts.telemetry.exists ? "exists" : "missing"})`);
504
+ if (packetPathDisplay) {
505
+ const freshLabel = packetFresh
506
+ ? "(fresh)"
507
+ : "(stale — re-run `polaris loop continue`)";
508
+ lines.push("");
509
+ lines.push(`Bootstrap packet: ${packetPathDisplay} ${freshLabel}`);
510
+ if (stateSha) {
511
+ const matchLabel = packetFresh
512
+ ? "matches current-state.json ✓"
513
+ : "MISMATCH — state has changed";
514
+ lines.push(`State SHA: ${stateSha.slice(0, 12)}... (${matchLabel})`);
515
+ }
516
+ }
517
+ else {
518
+ lines.push("");
519
+ lines.push("Bootstrap packet: (none found)");
520
+ }
521
+ // ── Dispatch evidence ─────────────────────────────────────────────────────
522
+ if (dispatchEvidence) {
523
+ lines.push("");
524
+ lines.push("Dispatch Evidence:");
525
+ lines.push(` Child: ${dispatchEvidence.child_id}`);
526
+ // Show dispatch mode and runtime state
527
+ const mode = dispatchEvidence.dispatch_mode ?? "delegated";
528
+ // Derive runtime state: for delegated mode without explicit state, default to "delegated"
529
+ // for direct-worker mode without explicit state, default to "unknown"
530
+ const runtimeState = dispatchEvidence.runtime_state ??
531
+ (mode === "delegated" ? "delegated" : "unknown");
532
+ lines.push(` Mode: ${mode}`);
533
+ lines.push(` Runtime state: ${runtimeState}`);
534
+ if (dispatchEvidence.provider) {
535
+ lines.push(` Provider: ${dispatchEvidence.provider}`);
536
+ }
537
+ // Show mode-specific messaging
538
+ if (mode === "delegated") {
539
+ lines.push(` Visibility: limited (orchestrator-owned)`);
540
+ // Show worker assignment info if available
541
+ if (dispatchEvidence.worker_assignment) {
542
+ const wa = dispatchEvidence.worker_assignment;
543
+ lines.push(` Assignment: ${wa.assignment_type}`);
544
+ if (wa.assignment_type === "subagent" && wa.subagent_session_id) {
545
+ lines.push(` Subagent session: ${wa.subagent_session_id}`);
546
+ }
547
+ else if (wa.assignment_type === "external-process" && wa.process_pid) {
548
+ lines.push(` Process PID: ${wa.process_pid}`);
549
+ }
550
+ else if (wa.assignment_type === "pending-escalation" && wa.escalation_reason) {
551
+ lines.push(` Escalation: ${wa.escalation_reason}`);
552
+ }
553
+ lines.push(` Assigned at: ${wa.assigned_at}`);
554
+ }
555
+ else {
556
+ // No assignment yet - Foreman seal compliance notice
557
+ lines.push(` Assignment: (none yet)`);
558
+ }
559
+ }
560
+ lines.push(` Packet: ${getRelativePath(repoRoot, dispatchEvidence.packet_path)}`);
561
+ lines.push(` Expected result: ${getRelativePath(repoRoot, dispatchEvidence.expected_result_path)}`);
562
+ lines.push(` Result present: ${dispatchEvidence.result_present ? "✓ yes" : "✗ no"}`);
563
+ // Show heartbeat info if available
564
+ if (dispatchEvidence.last_heartbeat_at) {
565
+ const heartbeatAge = Math.round((Date.now() - new Date(dispatchEvidence.last_heartbeat_at).getTime()) / 1000);
566
+ lines.push(` Last heartbeat: ${heartbeatAge}s ago${dispatchEvidence.last_heartbeat_step ? ` (${dispatchEvidence.last_heartbeat_step})` : ""}`);
567
+ }
568
+ else if (mode === "direct-worker" && !dispatchEvidence.result_present) {
569
+ lines.push(` Last heartbeat: (none yet)`);
570
+ }
571
+ lines.push(` Dispatched at: ${dispatchEvidence.dispatched_at}`);
572
+ // Show state-specific warnings
573
+ if (runtimeState === "waiting-for-approval") {
574
+ lines.push("");
575
+ lines.push(" ⏳ Waiting for approval - worker is blocked");
576
+ }
577
+ else if (runtimeState === "orphaned") {
578
+ lines.push("");
579
+ lines.push(" ⚠ Worker appears orphaned - no recent heartbeats");
580
+ }
581
+ else if (runtimeState === "blocked") {
582
+ lines.push("");
583
+ lines.push(" ⚠ Worker blocked - no heartbeat within expected interval");
584
+ }
585
+ // Foreman seal compliance notice for delegated mode without assignment
586
+ if (mode === "delegated" && !dispatchEvidence.worker_assignment && !dispatchEvidence.result_present) {
587
+ lines.push("");
588
+ lines.push(" 📋 Foreman Seal Compliance:");
589
+ lines.push(" Foreman coordinates; Foreman does NOT implement.");
590
+ lines.push(" A worker must be assigned or escalated.");
591
+ if (!dispatchEvidence.worker_assignment) {
592
+ lines.push("");
593
+ lines.push(" ⚠️ No worker assigned - implementation would violate seal");
594
+ lines.push(" Action: Assign worker or escalate to manual dispatch");
595
+ }
596
+ }
597
+ }
598
+ else if (state.active_child) {
599
+ lines.push("");
600
+ lines.push("⚠ No dispatch evidence found for active child");
601
+ lines.push(` Child ${state.active_child} is active but no packet/result artifacts exist.`);
602
+ lines.push(" This may indicate a dispatch failure or orphaned state.");
603
+ }
604
+ // ── Worker blocked (needs approval) ───────────────────────────────────────
605
+ if (workerBlocked) {
606
+ lines.push("");
607
+ lines.push("🛑 WORKER BLOCKED - AWAITING APPROVAL");
608
+ lines.push(` Reason: ${workerBlocked.reason}`);
609
+ if (workerBlocked.approval_type) {
610
+ lines.push(` Type: ${workerBlocked.approval_type}`);
611
+ }
612
+ if (workerBlocked.description) {
613
+ lines.push(` Details: ${workerBlocked.description}`);
614
+ }
615
+ if (workerBlocked.blocker_id) {
616
+ lines.push(` Blocker ID: ${workerBlocked.blocker_id}`);
617
+ }
618
+ lines.push(` Blocked at: ${formatBlockedEvent(workerBlocked)}`);
619
+ lines.push("");
620
+ lines.push("To resolve:");
621
+ lines.push(` 1. Read the packet: cat ${dispatchEvidence?.packet_path ?? "<packet path>"}`);
622
+ lines.push(` 2. Check worker context in the packet instructions`);
623
+ lines.push(` 3. Either:`);
624
+ lines.push(` a) Approve: Append approval response to telemetry and resume`);
625
+ lines.push(` b) Abort: Run 'polaris loop abort --child ${state.active_child}' to unblock`);
626
+ lines.push("");
627
+ lines.push("⚠️ DO NOT dispatch another worker until this block is resolved!");
628
+ }
629
+ // ── Worker heartbeat (progress) ───────────────────────────────────────────
630
+ else if (workerHeartbeat) {
631
+ lines.push("");
632
+ lines.push("Worker Progress (last heartbeat):");
633
+ lines.push(` Step: ${workerHeartbeat.step_cursor}`);
634
+ if (workerHeartbeat.progress_pct !== undefined) {
635
+ lines.push(` Progress: ${workerHeartbeat.progress_pct}%`);
636
+ }
637
+ if (workerHeartbeat.files_changed !== undefined) {
638
+ lines.push(` Files: ${workerHeartbeat.files_changed} changed`);
639
+ }
640
+ if (workerHeartbeat.current_file) {
641
+ lines.push(` Current: ${workerHeartbeat.current_file}`);
642
+ }
643
+ lines.push(` Last seen: ${formatHeartbeat(workerHeartbeat)}`);
644
+ }
645
+ else if (dispatchEvidence && !dispatchEvidence.result_present) {
646
+ lines.push("");
647
+ lines.push("⚠ No worker heartbeats detected");
648
+ lines.push(` Worker for ${dispatchEvidence.child_id} has not emitted any progress telemetry.`);
649
+ lines.push(" Worker may be:");
650
+ lines.push(" - Starting up (heartbeats start after packet read)");
651
+ lines.push(" - Stuck or crashed (no heartbeats in telemetry.jsonl)");
652
+ lines.push(" - Blocked on approval (old worker version without block telemetry)");
653
+ lines.push(" - Running without heartbeat compliance (old worker version)");
654
+ }
655
+ if (isDeadlock) {
656
+ lines.push("");
657
+ lines.push("⚠ DEADLOCK DETECTED");
658
+ lines.push("Blocked children:");
659
+ for (const c of blockedChildren) {
660
+ lines.push(` ${c} — blocked`);
661
+ }
662
+ lines.push("Resolve blockers in Linear, then run: polaris loop resume");
663
+ }
664
+ console.log(lines.join("\n"));
665
+ }