@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,499 @@
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 dispatch_js_1 = require("./dispatch.js");
8
+ const checkpoint_js_1 = require("./checkpoint.js");
9
+ const run_bootstrap_js_1 = require("./run-bootstrap.js");
10
+ function makeTestDir() {
11
+ const dir = (0, node_path_1.join)((0, node_os_1.tmpdir)(), `polaris-dispatch-test-${Date.now()}-${Math.random().toString(16).slice(2)}`);
12
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
13
+ (0, node_fs_1.mkdirSync)((0, node_path_1.join)(dir, ".git"), { recursive: true });
14
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, ".git", "HEAD"), "ref: refs/heads/test-branch\n");
15
+ return dir;
16
+ }
17
+ function baseState(overrides = {}) {
18
+ return {
19
+ schema_version: "1.0",
20
+ run_id: "pol-142-session-1",
21
+ cluster_id: "POL-142",
22
+ session_type: "implement",
23
+ branch: "test-branch",
24
+ active_child: "",
25
+ completed_children: ["POL-144"],
26
+ open_children: ["POL-145", "POL-146"],
27
+ open_children_meta: {
28
+ "POL-145": { title: "Add dispatch command", labels: ["implement"] },
29
+ "POL-146": { title: "Parent delivery", labels: ["implement"] },
30
+ },
31
+ step_cursor: "checkpoint",
32
+ context_budget: { children_completed: 1, max_children_per_session: 3 },
33
+ status: "running",
34
+ next_open_child: "POL-145",
35
+ dispatch_boundary: { dispatch_epoch: 1, continue_epoch: 1, last_dispatched_child: "POL-144" },
36
+ run_bootstrap_seal: (0, run_bootstrap_js_1.createBootstrapSeal)("pol-142-session-1", "POL-142", ["POL-145", "POL-146"]),
37
+ ...overrides,
38
+ };
39
+ }
40
+ function writeState(dir, state) {
41
+ const stateFile = (0, node_path_1.join)(dir, ".taskchain_artifacts", "polaris-run", "current-state.json");
42
+ (0, node_fs_1.mkdirSync)((0, node_path_1.join)(dir, ".taskchain_artifacts", "polaris-run"), { recursive: true });
43
+ (0, node_fs_1.writeFileSync)(stateFile, JSON.stringify(state, null, 2), "utf-8");
44
+ return stateFile;
45
+ }
46
+ function captureStdout(fn) {
47
+ const chunks = [];
48
+ const originalWrite = process.stdout.write.bind(process.stdout);
49
+ process.stdout.write = (chunk) => {
50
+ chunks.push(chunk.toString());
51
+ return true;
52
+ };
53
+ try {
54
+ fn();
55
+ }
56
+ finally {
57
+ process.stdout.write = originalWrite;
58
+ }
59
+ return chunks.join("");
60
+ }
61
+ function expectDispatchError(fn) {
62
+ const chunks = [];
63
+ const exitSpy = vitest_1.vi.spyOn(process, "exit").mockImplementation(() => {
64
+ throw new Error("process.exit called");
65
+ });
66
+ const originalWrite = process.stderr.write.bind(process.stderr);
67
+ process.stderr.write = (chunk) => {
68
+ chunks.push(chunk.toString());
69
+ return true;
70
+ };
71
+ try {
72
+ (0, vitest_1.expect)(fn).toThrow("process.exit called");
73
+ (0, vitest_1.expect)(exitSpy).toHaveBeenCalledWith(1);
74
+ }
75
+ finally {
76
+ process.stderr.write = originalWrite;
77
+ exitSpy.mockRestore();
78
+ }
79
+ return chunks.join("");
80
+ }
81
+ (0, vitest_1.describe)("runLoopDispatch", () => {
82
+ let testDir;
83
+ (0, vitest_1.beforeEach)(() => {
84
+ testDir = makeTestDir();
85
+ });
86
+ (0, vitest_1.afterEach)(() => {
87
+ (0, node_fs_1.rmSync)(testDir, { recursive: true, force: true });
88
+ });
89
+ (0, vitest_1.it)("dispatches first open child when no --child is specified", () => {
90
+ const stateFile = writeState(testDir, baseState());
91
+ const output = captureStdout(() => (0, dispatch_js_1.runLoopDispatch)({ repoRoot: testDir, stateFile }));
92
+ const packet = JSON.parse(output);
93
+ const updated = (0, checkpoint_js_1.readState)(stateFile);
94
+ (0, vitest_1.expect)(packet.schema_version).toBe("2.1");
95
+ (0, vitest_1.expect)(packet.active_child).toBe("POL-145");
96
+ (0, vitest_1.expect)(updated.active_child).toBe("POL-145");
97
+ (0, vitest_1.expect)(updated.open_children).toEqual(["POL-145", "POL-146"]);
98
+ });
99
+ (0, vitest_1.it)("dispatches named child when --child is specified", () => {
100
+ const stateFile = writeState(testDir, baseState());
101
+ const output = captureStdout(() => (0, dispatch_js_1.runLoopDispatch)({ repoRoot: testDir, stateFile, childId: "POL-146" }));
102
+ const packet = JSON.parse(output);
103
+ const updated = (0, checkpoint_js_1.readState)(stateFile);
104
+ (0, vitest_1.expect)(packet.active_child).toBe("POL-146");
105
+ (0, vitest_1.expect)(packet.instructions.issue_context).toMatchObject({ id: "POL-146" });
106
+ (0, vitest_1.expect)(updated.active_child).toBe("POL-146");
107
+ });
108
+ (0, vitest_1.it)("errors when active_child is already set", () => {
109
+ const stateFile = writeState(testDir, baseState({ active_child: "POL-145" }));
110
+ const stderr = expectDispatchError(() => (0, dispatch_js_1.runLoopDispatch)({ repoRoot: testDir, stateFile }));
111
+ (0, vitest_1.expect)(stderr).toContain("active_child is already set");
112
+ });
113
+ (0, vitest_1.it)("errors when open_children is empty", () => {
114
+ const stateFile = writeState(testDir, baseState({ completed_children: ["POL-144", "POL-145"], open_children: [] }));
115
+ const stderr = expectDispatchError(() => (0, dispatch_js_1.runLoopDispatch)({ repoRoot: testDir, stateFile }));
116
+ (0, vitest_1.expect)(stderr).toContain("no open children");
117
+ });
118
+ (0, vitest_1.it)("claims active_child without advancing completion state", () => {
119
+ const stateFile = writeState(testDir, baseState());
120
+ captureStdout(() => (0, dispatch_js_1.runLoopDispatch)({ repoRoot: testDir, stateFile }));
121
+ const updated = (0, checkpoint_js_1.readState)(stateFile);
122
+ (0, vitest_1.expect)(updated.active_child).toBe("POL-145");
123
+ (0, vitest_1.expect)(updated.completed_children).toEqual(["POL-144"]);
124
+ (0, vitest_1.expect)(updated.open_children).toEqual(["POL-145", "POL-146"]);
125
+ (0, vitest_1.expect)(updated.status).toBe("running");
126
+ });
127
+ (0, vitest_1.it)("emits exactly one child-dispatched JSONL event", () => {
128
+ const artifactDir = (0, node_path_1.join)(testDir, ".taskchain_artifacts", "polaris-run");
129
+ const stateFile = writeState(testDir, baseState({ artifact_dir: artifactDir }));
130
+ captureStdout(() => (0, dispatch_js_1.runLoopDispatch)({ repoRoot: testDir, stateFile }));
131
+ const telemetryFile = (0, node_path_1.join)(artifactDir, "runs", "pol-142-session-1", "telemetry.jsonl");
132
+ (0, vitest_1.expect)((0, node_fs_1.existsSync)(telemetryFile)).toBe(true);
133
+ const events = (0, node_fs_1.readFileSync)(telemetryFile, "utf-8")
134
+ .trim()
135
+ .split("\n")
136
+ .map((line) => JSON.parse(line))
137
+ .filter((event) => event.event === "child-dispatched");
138
+ (0, vitest_1.expect)(events).toHaveLength(1);
139
+ (0, vitest_1.expect)(events[0]).toMatchObject({
140
+ run_id: "pol-142-session-1",
141
+ child_id: "POL-145",
142
+ });
143
+ });
144
+ (0, vitest_1.it)("appends child-dispatched to the global ledger and creates it when absent", () => {
145
+ const artifactDir = (0, node_path_1.join)(testDir, ".taskchain_artifacts", "polaris-run");
146
+ const stateFile = writeState(testDir, baseState({ artifact_dir: artifactDir }));
147
+ captureStdout(() => (0, dispatch_js_1.runLoopDispatch)({ repoRoot: testDir, stateFile }));
148
+ const ledgerFile = (0, node_path_1.join)(testDir, ".polaris", "runs", "ledger.jsonl");
149
+ (0, vitest_1.expect)((0, node_fs_1.existsSync)(ledgerFile)).toBe(true);
150
+ const events = (0, node_fs_1.readFileSync)(ledgerFile, "utf-8")
151
+ .trim()
152
+ .split("\n")
153
+ .map((line) => JSON.parse(line));
154
+ (0, vitest_1.expect)(events).toHaveLength(1);
155
+ (0, vitest_1.expect)(events[0]).toMatchObject({
156
+ event: "child-dispatched",
157
+ run_id: "pol-142-session-1",
158
+ run_type: "implement",
159
+ cluster_id: "POL-142",
160
+ issue_id: "POL-145",
161
+ branch: "test-branch",
162
+ status: "child-dispatched",
163
+ completed_children: ["POL-144"],
164
+ open_children: ["POL-145", "POL-146"],
165
+ next_child: "POL-145",
166
+ last_commit: null,
167
+ pr_url: null,
168
+ dispatch_epoch: 2,
169
+ });
170
+ });
171
+ // ── REGRESSION TESTS: Dispatch durable evidence ─────────────────────────────
172
+ (0, vitest_1.it)("writes packet artifact to cluster-scoped layout", () => {
173
+ const stateFile = writeState(testDir, baseState());
174
+ captureStdout(() => (0, dispatch_js_1.runLoopDispatch)({ repoRoot: testDir, stateFile }));
175
+ // Check that packet was written to cluster-scoped directory
176
+ const clusterPacketDir = (0, node_path_1.join)(testDir, ".polaris", "clusters", "POL-142", "packets");
177
+ (0, vitest_1.expect)((0, node_fs_1.existsSync)(clusterPacketDir)).toBe(true);
178
+ // Get the actual dispatched state to find the packet path
179
+ const updated = (0, checkpoint_js_1.readState)(stateFile);
180
+ const dispatchRecord = updated.open_children_meta?.["POL-145"]?.dispatch_record;
181
+ (0, vitest_1.expect)(dispatchRecord).toBeDefined();
182
+ (0, vitest_1.expect)(dispatchRecord?.packet_path).toBeDefined();
183
+ const absolutePacketPath = (0, node_path_1.resolve)(testDir, dispatchRecord?.packet_path);
184
+ (0, vitest_1.expect)((0, node_fs_1.existsSync)(absolutePacketPath)).toBe(true);
185
+ // Read the packet and verify it contains expected fields
186
+ const packet = JSON.parse((0, node_fs_1.readFileSync)(absolutePacketPath, "utf-8"));
187
+ (0, vitest_1.expect)(packet.schema_version).toBe("2.1");
188
+ (0, vitest_1.expect)(packet.active_child).toBe("POL-145");
189
+ (0, vitest_1.expect)(packet.run_id).toBe("pol-142-session-1");
190
+ (0, vitest_1.expect)(packet.cluster_id).toBe("POL-142");
191
+ });
192
+ (0, vitest_1.it)("records dispatch_record with packet and result paths in open_children_meta", () => {
193
+ const stateFile = writeState(testDir, baseState());
194
+ captureStdout(() => (0, dispatch_js_1.runLoopDispatch)({ repoRoot: testDir, stateFile }));
195
+ const updated = (0, checkpoint_js_1.readState)(stateFile);
196
+ const childMeta = updated.open_children_meta?.["POL-145"];
197
+ (0, vitest_1.expect)(childMeta).toBeDefined();
198
+ (0, vitest_1.expect)(childMeta?.dispatch_record).toBeDefined();
199
+ const dispatchRecord = childMeta?.dispatch_record;
200
+ (0, vitest_1.expect)(dispatchRecord?.child_id).toBe("POL-145");
201
+ (0, vitest_1.expect)(dispatchRecord?.run_id).toBe("pol-142-session-1");
202
+ (0, vitest_1.expect)(dispatchRecord?.cluster_id).toBe("POL-142");
203
+ (0, vitest_1.expect)(dispatchRecord?.status).toBe("dispatched");
204
+ // Verify paths are set and exist
205
+ (0, vitest_1.expect)(dispatchRecord?.packet_path).toBeDefined();
206
+ (0, vitest_1.expect)((0, node_fs_1.existsSync)((0, node_path_1.resolve)(testDir, dispatchRecord?.packet_path))).toBe(true);
207
+ // Verify expected result path is set (won't exist yet - worker creates it)
208
+ (0, vitest_1.expect)(dispatchRecord?.expected_result_path).toBeDefined();
209
+ (0, vitest_1.expect)(dispatchRecord?.expected_result_path).toContain("results");
210
+ (0, vitest_1.expect)(dispatchRecord?.expected_result_path).toContain("POL-145");
211
+ });
212
+ (0, vitest_1.it)("telemetry includes packet_path and expected_result_path", () => {
213
+ const artifactDir = (0, node_path_1.join)(testDir, ".taskchain_artifacts", "polaris-run");
214
+ const stateFile = writeState(testDir, baseState({ artifact_dir: artifactDir }));
215
+ captureStdout(() => (0, dispatch_js_1.runLoopDispatch)({ repoRoot: testDir, stateFile }));
216
+ const telemetryFile = (0, node_path_1.join)(artifactDir, "runs", "pol-142-session-1", "telemetry.jsonl");
217
+ (0, vitest_1.expect)((0, node_fs_1.existsSync)(telemetryFile)).toBe(true);
218
+ const events = (0, node_fs_1.readFileSync)(telemetryFile, "utf-8")
219
+ .trim()
220
+ .split("\n")
221
+ .map((line) => JSON.parse(line))
222
+ .filter((event) => event.event === "child-dispatched");
223
+ (0, vitest_1.expect)(events).toHaveLength(1);
224
+ (0, vitest_1.expect)(events[0].packet_path).toBeDefined();
225
+ (0, vitest_1.expect)(events[0].expected_result_path).toBeDefined();
226
+ (0, vitest_1.expect)(events[0].packet_path).toContain("POL-142");
227
+ (0, vitest_1.expect)(events[0].packet_path).toContain("packets");
228
+ });
229
+ // ── DISPATCH MODE TESTS ────────────────────────────────────────────────────
230
+ (0, vitest_1.it)("records delegated dispatch mode when no provider specified", () => {
231
+ const stateFile = writeState(testDir, baseState());
232
+ captureStdout(() => (0, dispatch_js_1.runLoopDispatch)({ repoRoot: testDir, stateFile }));
233
+ const updated = (0, checkpoint_js_1.readState)(stateFile);
234
+ const dispatchRecord = updated.open_children_meta?.["POL-145"]?.dispatch_record;
235
+ (0, vitest_1.expect)(dispatchRecord).toBeDefined();
236
+ (0, vitest_1.expect)(dispatchRecord?.dispatch_mode).toBe("delegated");
237
+ (0, vitest_1.expect)(dispatchRecord?.runtime_state).toBe("delegated");
238
+ (0, vitest_1.expect)(dispatchRecord?.provider).toBeUndefined();
239
+ });
240
+ (0, vitest_1.it)("records direct-worker dispatch mode when provider is specified", () => {
241
+ const stateFile = writeState(testDir, baseState());
242
+ captureStdout(() => (0, dispatch_js_1.runLoopDispatch)({
243
+ repoRoot: testDir,
244
+ stateFile,
245
+ provider: "copilot"
246
+ }));
247
+ const updated = (0, checkpoint_js_1.readState)(stateFile);
248
+ const dispatchRecord = updated.open_children_meta?.["POL-145"]?.dispatch_record;
249
+ (0, vitest_1.expect)(dispatchRecord).toBeDefined();
250
+ (0, vitest_1.expect)(dispatchRecord?.dispatch_mode).toBe("direct-worker");
251
+ (0, vitest_1.expect)(dispatchRecord?.runtime_state).toBe("packet-created");
252
+ (0, vitest_1.expect)(dispatchRecord?.provider).toBe("copilot");
253
+ });
254
+ (0, vitest_1.it)("emits dispatch_mode and runtime_state in telemetry", () => {
255
+ const artifactDir = (0, node_path_1.join)(testDir, ".taskchain_artifacts", "polaris-run");
256
+ const stateFile = writeState(testDir, baseState({ artifact_dir: artifactDir }));
257
+ captureStdout(() => (0, dispatch_js_1.runLoopDispatch)({
258
+ repoRoot: testDir,
259
+ stateFile,
260
+ provider: "gemini"
261
+ }));
262
+ const telemetryFile = (0, node_path_1.join)(artifactDir, "runs", "pol-142-session-1", "telemetry.jsonl");
263
+ const events = (0, node_fs_1.readFileSync)(telemetryFile, "utf-8")
264
+ .trim()
265
+ .split("\n")
266
+ .map((line) => JSON.parse(line))
267
+ .filter((event) => event.event === "child-dispatched");
268
+ (0, vitest_1.expect)(events).toHaveLength(1);
269
+ (0, vitest_1.expect)(events[0].dispatch_mode).toBe("direct-worker");
270
+ (0, vitest_1.expect)(events[0].runtime_state).toBe("packet-created");
271
+ (0, vitest_1.expect)(events[0].provider).toBe("gemini");
272
+ });
273
+ // ── DELEGATED ASSIGNMENT TESTS (POL-220) ──────────────────────────────────
274
+ (0, vitest_1.it)("escalation path: emits all fallback telemetry events and sets pending-escalation when no subagent available", () => {
275
+ const artifactDir = (0, node_path_1.join)(testDir, ".taskchain_artifacts", "polaris-run");
276
+ const stateFile = writeState(testDir, baseState({ artifact_dir: artifactDir }));
277
+ // No subagent dispatcher registered — escalation path
278
+ captureStdout(() => (0, dispatch_js_1.runLoopDispatch)({ repoRoot: testDir, stateFile }));
279
+ const telemetryFile = (0, node_path_1.join)(artifactDir, "runs", "pol-142-session-1", "telemetry.jsonl");
280
+ const allEvents = (0, node_fs_1.readFileSync)(telemetryFile, "utf-8")
281
+ .trim()
282
+ .split("\n")
283
+ .map((line) => JSON.parse(line));
284
+ const attemptedEvents = allEvents.filter((e) => e.event === "worker-assignment-attempted");
285
+ const failedEvents = allEvents.filter((e) => e.event === "worker-assignment-failed");
286
+ const escalationEvents = allEvents.filter((e) => e.event === "escalation-initiated");
287
+ // Three attempts: subagent, external-process, human-handoff
288
+ (0, vitest_1.expect)(attemptedEvents).toHaveLength(3);
289
+ (0, vitest_1.expect)(attemptedEvents[0].assignment_type).toBe("subagent");
290
+ (0, vitest_1.expect)(attemptedEvents[1].assignment_type).toBe("external-process");
291
+ (0, vitest_1.expect)(attemptedEvents[2].assignment_type).toBe("human-handoff");
292
+ // Three failures
293
+ (0, vitest_1.expect)(failedEvents).toHaveLength(3);
294
+ (0, vitest_1.expect)(failedEvents[0].reason).toBe("no-subagent-support");
295
+ (0, vitest_1.expect)(failedEvents[1].reason).toBe("provider-unavailable");
296
+ (0, vitest_1.expect)(failedEvents[2].reason).toBe("provider-unavailable");
297
+ // One escalation-initiated
298
+ (0, vitest_1.expect)(escalationEvents).toHaveLength(1);
299
+ (0, vitest_1.expect)(escalationEvents[0].reason).toBeDefined();
300
+ (0, vitest_1.expect)(escalationEvents[0].recommended_action).toBe("manual-dispatch");
301
+ // All events have required base fields
302
+ for (const ev of [...attemptedEvents, ...failedEvents, ...escalationEvents]) {
303
+ (0, vitest_1.expect)(ev.event_id).toBeDefined();
304
+ (0, vitest_1.expect)(ev.dispatch_id).toBeDefined();
305
+ (0, vitest_1.expect)(ev.run_id).toBe("pol-142-session-1");
306
+ (0, vitest_1.expect)(ev.child_id).toBe("POL-145");
307
+ (0, vitest_1.expect)(ev.timestamp).toBeDefined();
308
+ }
309
+ });
310
+ (0, vitest_1.it)("escalation path: sets pending-escalation in worker_assignment on dispatch_record", () => {
311
+ const stateFile = writeState(testDir, baseState());
312
+ // No subagent dispatcher registered
313
+ captureStdout(() => (0, dispatch_js_1.runLoopDispatch)({ repoRoot: testDir, stateFile }));
314
+ const updated = (0, checkpoint_js_1.readState)(stateFile);
315
+ const dispatchRecord = updated.open_children_meta?.["POL-145"]?.dispatch_record;
316
+ (0, vitest_1.expect)(dispatchRecord?.worker_assignment).toBeDefined();
317
+ (0, vitest_1.expect)(dispatchRecord?.worker_assignment?.assignment_type).toBe("pending-escalation");
318
+ (0, vitest_1.expect)(dispatchRecord?.worker_assignment?.assigned_at).toBeDefined();
319
+ (0, vitest_1.expect)(dispatchRecord?.worker_assignment?.escalation_reason).toBeDefined();
320
+ (0, vitest_1.expect)(dispatchRecord?.session_id).toBeNull();
321
+ (0, vitest_1.expect)(dispatchRecord?.attachment_capable).toBe(false);
322
+ (0, vitest_1.expect)(dispatchRecord?.runtime_state).toBe("delegated");
323
+ });
324
+ (0, vitest_1.it)("successful subagent assignment: sets worker_assignment with subagent session_id", () => {
325
+ const stateFile = writeState(testDir, baseState());
326
+ // Register a mock subagent dispatcher
327
+ const mockDispatcher = vitest_1.vi.fn().mockResolvedValue(JSON.stringify({ child_id: "POL-145", status: "done", validation_summary: "ok", next_action: "continue" }));
328
+ globalThis.__POLARIS_AGENT_SUBTASK_DISPATCH__ = mockDispatcher;
329
+ try {
330
+ captureStdout(() => (0, dispatch_js_1.runLoopDispatch)({ repoRoot: testDir, stateFile }));
331
+ const updated = (0, checkpoint_js_1.readState)(stateFile);
332
+ const dispatchRecord = updated.open_children_meta?.["POL-145"]?.dispatch_record;
333
+ (0, vitest_1.expect)(dispatchRecord?.worker_assignment).toBeDefined();
334
+ (0, vitest_1.expect)(dispatchRecord?.worker_assignment?.assignment_type).toBe("subagent");
335
+ (0, vitest_1.expect)(dispatchRecord?.worker_assignment?.assigned_at).toBeDefined();
336
+ (0, vitest_1.expect)(dispatchRecord?.worker_assignment?.subagent_session_id).toBeDefined();
337
+ (0, vitest_1.expect)(typeof dispatchRecord?.worker_assignment?.subagent_session_id).toBe("string");
338
+ (0, vitest_1.expect)(dispatchRecord?.session_id).toBe(dispatchRecord?.worker_assignment?.subagent_session_id);
339
+ (0, vitest_1.expect)(dispatchRecord?.attachment_capable).toBe(false);
340
+ (0, vitest_1.expect)(dispatchRecord?.runtime_state).toBe("delegated");
341
+ }
342
+ finally {
343
+ delete globalThis.__POLARIS_AGENT_SUBTASK_DISPATCH__;
344
+ }
345
+ });
346
+ (0, vitest_1.it)("successful subagent assignment: emits worker-assignment-attempted and worker-assigned events", () => {
347
+ const artifactDir = (0, node_path_1.join)(testDir, ".taskchain_artifacts", "polaris-run");
348
+ const stateFile = writeState(testDir, baseState({ artifact_dir: artifactDir }));
349
+ const mockDispatcher = vitest_1.vi.fn().mockResolvedValue(JSON.stringify({ child_id: "POL-145", status: "done", validation_summary: "ok", next_action: "continue" }));
350
+ globalThis.__POLARIS_AGENT_SUBTASK_DISPATCH__ = mockDispatcher;
351
+ try {
352
+ captureStdout(() => (0, dispatch_js_1.runLoopDispatch)({ repoRoot: testDir, stateFile }));
353
+ const telemetryFile = (0, node_path_1.join)(artifactDir, "runs", "pol-142-session-1", "telemetry.jsonl");
354
+ const allEvents = (0, node_fs_1.readFileSync)(telemetryFile, "utf-8")
355
+ .trim()
356
+ .split("\n")
357
+ .map((line) => JSON.parse(line));
358
+ const attemptedEvents = allEvents.filter((e) => e.event === "worker-assignment-attempted");
359
+ const assignedEvents = allEvents.filter((e) => e.event === "worker-assigned");
360
+ const failedEvents = allEvents.filter((e) => e.event === "worker-assignment-failed");
361
+ const escalationEvents = allEvents.filter((e) => e.event === "escalation-initiated");
362
+ // Only one attempt (subagent succeeds)
363
+ (0, vitest_1.expect)(attemptedEvents).toHaveLength(1);
364
+ (0, vitest_1.expect)(attemptedEvents[0].assignment_type).toBe("subagent");
365
+ // One assigned event
366
+ (0, vitest_1.expect)(assignedEvents).toHaveLength(1);
367
+ (0, vitest_1.expect)(assignedEvents[0].assignment_type).toBe("subagent");
368
+ (0, vitest_1.expect)(assignedEvents[0].subagent_session_id).toBeDefined();
369
+ // No failures or escalations
370
+ (0, vitest_1.expect)(failedEvents).toHaveLength(0);
371
+ (0, vitest_1.expect)(escalationEvents).toHaveLength(0);
372
+ }
373
+ finally {
374
+ delete globalThis.__POLARIS_AGENT_SUBTASK_DISPATCH__;
375
+ }
376
+ });
377
+ (0, vitest_1.it)("direct-worker mode: no assignment events emitted (provider specified)", () => {
378
+ const artifactDir = (0, node_path_1.join)(testDir, ".taskchain_artifacts", "polaris-run");
379
+ const stateFile = writeState(testDir, baseState({ artifact_dir: artifactDir }));
380
+ captureStdout(() => (0, dispatch_js_1.runLoopDispatch)({ repoRoot: testDir, stateFile, provider: "copilot" }));
381
+ const telemetryFile = (0, node_path_1.join)(artifactDir, "runs", "pol-142-session-1", "telemetry.jsonl");
382
+ const allEvents = (0, node_fs_1.readFileSync)(telemetryFile, "utf-8")
383
+ .trim()
384
+ .split("\n")
385
+ .map((line) => JSON.parse(line));
386
+ const assignmentEvents = allEvents.filter((e) => ["worker-assignment-attempted", "worker-assigned", "worker-assignment-failed", "escalation-initiated"].includes(e.event));
387
+ (0, vitest_1.expect)(assignmentEvents).toHaveLength(0);
388
+ });
389
+ (0, vitest_1.it)("direct-worker mode: no worker_assignment on dispatch_record", () => {
390
+ const stateFile = writeState(testDir, baseState());
391
+ captureStdout(() => (0, dispatch_js_1.runLoopDispatch)({ repoRoot: testDir, stateFile, provider: "copilot" }));
392
+ const updated = (0, checkpoint_js_1.readState)(stateFile);
393
+ const dispatchRecord = updated.open_children_meta?.["POL-145"]?.dispatch_record;
394
+ (0, vitest_1.expect)(dispatchRecord?.worker_assignment).toBeUndefined();
395
+ });
396
+ // ── Role context tests (POL-227 / POL-230) ──────────────────────────────────
397
+ (0, vitest_1.it)("impl packet includes worker role_context", () => {
398
+ const stateFile = writeState(testDir, baseState());
399
+ const output = captureStdout(() => (0, dispatch_js_1.runLoopDispatch)({ repoRoot: testDir, stateFile }));
400
+ const packet = JSON.parse(output);
401
+ (0, vitest_1.expect)(packet.role_context).toBeDefined();
402
+ (0, vitest_1.expect)(packet.role_context.role).toBe("worker");
403
+ (0, vitest_1.expect)(packet.role_context.role_authority).toBe("implementation");
404
+ (0, vitest_1.expect)(packet.role_context.may_implement).toBe(true);
405
+ (0, vitest_1.expect)(packet.role_context.may_assign_workers).toBe(false);
406
+ (0, vitest_1.expect)(Array.isArray(packet.role_context.prohibited_actions)).toBe(true);
407
+ });
408
+ (0, vitest_1.it)("dispatch record includes role fields from packet role_context", () => {
409
+ const stateFile = writeState(testDir, baseState());
410
+ captureStdout(() => (0, dispatch_js_1.runLoopDispatch)({ repoRoot: testDir, stateFile }));
411
+ const updated = (0, checkpoint_js_1.readState)(stateFile);
412
+ const dr = updated.open_children_meta?.["POL-145"]?.dispatch_record;
413
+ (0, vitest_1.expect)(dr?.role).toBe("worker");
414
+ (0, vitest_1.expect)(dr?.role_authority).toBe("implementation");
415
+ (0, vitest_1.expect)(dr?.may_implement).toBe(true);
416
+ (0, vitest_1.expect)(dr?.session_type).toBe("implementation");
417
+ });
418
+ // ── Config provider routing (POL-228 scenario 2) ────────────────────────────
419
+ (0, vitest_1.it)("uses config provider as direct-worker when no --provider flag", () => {
420
+ // Write a polaris config with a configured provider
421
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(testDir, "polaris.config.json"), JSON.stringify({ execution: { adapter: "terminal-cli", providers: { codex: { command: "codex" } }, rotation: ["codex"] } }), "utf-8");
422
+ const stateFile = writeState(testDir, baseState());
423
+ const output = captureStdout(() => (0, dispatch_js_1.runLoopDispatch)({ repoRoot: testDir, stateFile }));
424
+ const packet = JSON.parse(output);
425
+ const updated = (0, checkpoint_js_1.readState)(stateFile);
426
+ const dr = updated.open_children_meta?.["POL-145"]?.dispatch_record;
427
+ (0, vitest_1.expect)(dr?.dispatch_mode).toBe("direct-worker");
428
+ (0, vitest_1.expect)(dr?.provider).toBe("codex");
429
+ (0, vitest_1.expect)(packet.role_context.role).toBe("worker");
430
+ });
431
+ // ── Acknowledgment timeout detection (POL-228 scenario B + E) ───────────────
432
+ (0, vitest_1.it)("checkAcknowledgmentTimeout returns null when no active child", () => {
433
+ const stateFile = writeState(testDir, baseState());
434
+ const result = (0, dispatch_js_1.checkAcknowledgmentTimeout)({ stateFile, repoRoot: testDir });
435
+ (0, vitest_1.expect)(result).toBeNull();
436
+ });
437
+ (0, vitest_1.it)("checkAcknowledgmentTimeout detects no-acknowledgment timeout", () => {
438
+ const pastTime = new Date(Date.now() - 60_000).toISOString(); // 60s ago
439
+ const stateFile = writeState(testDir, baseState({
440
+ active_child: "POL-145",
441
+ open_children_meta: {
442
+ "POL-145": {
443
+ title: "Test child",
444
+ dispatch_record: {
445
+ dispatch_id: "test-dispatch-001",
446
+ child_id: "POL-145",
447
+ run_id: "pol-142-session-1",
448
+ cluster_id: "POL-142",
449
+ packet_path: "/tmp/packet.json",
450
+ expected_result_path: "/tmp/result.json",
451
+ dispatched_at: pastTime,
452
+ status: "dispatched",
453
+ dispatch_mode: "direct-worker",
454
+ runtime_state: "launching",
455
+ worker_id: "worker-001", // Has worker_id but no first_heartbeat_at
456
+ },
457
+ },
458
+ },
459
+ }));
460
+ const result = (0, dispatch_js_1.checkAcknowledgmentTimeout)({
461
+ stateFile,
462
+ repoRoot: testDir,
463
+ launchToFirstHeartbeatMs: 30_000,
464
+ });
465
+ (0, vitest_1.expect)(result).not.toBeNull();
466
+ (0, vitest_1.expect)(result?.orphaned).toBe(true);
467
+ (0, vitest_1.expect)(result?.reason).toBe("no-acknowledgment");
468
+ (0, vitest_1.expect)(result?.childId).toBe("POL-145");
469
+ });
470
+ (0, vitest_1.it)("checkAcknowledgmentTimeout returns not-orphaned for fresh dispatch", () => {
471
+ const stateFile = writeState(testDir, baseState({
472
+ active_child: "POL-145",
473
+ open_children_meta: {
474
+ "POL-145": {
475
+ title: "Test child",
476
+ dispatch_record: {
477
+ dispatch_id: "test-dispatch-001",
478
+ child_id: "POL-145",
479
+ run_id: "pol-142-session-1",
480
+ cluster_id: "POL-142",
481
+ packet_path: "/tmp/packet.json",
482
+ expected_result_path: "/tmp/result.json",
483
+ dispatched_at: new Date().toISOString(), // Just dispatched
484
+ status: "dispatched",
485
+ dispatch_mode: "direct-worker",
486
+ runtime_state: "launching",
487
+ worker_id: "worker-001",
488
+ },
489
+ },
490
+ },
491
+ }));
492
+ const result = (0, dispatch_js_1.checkAcknowledgmentTimeout)({
493
+ stateFile,
494
+ repoRoot: testDir,
495
+ launchToFirstHeartbeatMs: 30_000,
496
+ });
497
+ (0, vitest_1.expect)(result?.orphaned).toBe(false);
498
+ });
499
+ });
@@ -0,0 +1,160 @@
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 parent_js_1 = require("./parent.js");
8
+ const run_bootstrap_js_1 = require("./run-bootstrap.js");
9
+ vitest_1.vi.mock("./adapters/registry.js", () => ({
10
+ createAdapter: vitest_1.vi.fn(),
11
+ }));
12
+ vitest_1.vi.mock("../config/loader.js", () => ({
13
+ loadConfig: vitest_1.vi.fn(() => ({
14
+ execution: {
15
+ adapter: "terminal-cli",
16
+ providers: { "agent-subtask": { command: "native-subtask" } },
17
+ rotation: ["terminal-cli"],
18
+ },
19
+ budget: {
20
+ max_children_per_session: 5,
21
+ allow_analyze_children: false,
22
+ },
23
+ })),
24
+ }));
25
+ const registry_js_1 = require("./adapters/registry.js");
26
+ function writeState(dir, overrides) {
27
+ const stateFile = (0, node_path_1.join)(dir, "current-state.json");
28
+ const runId = "ephemeral-smoke-run";
29
+ const clusterId = overrides["cluster_id"] ?? "POL-105";
30
+ const openChildren = overrides["open_children"] ?? [];
31
+ const state = {
32
+ schema_version: "1.0",
33
+ run_id: runId,
34
+ cluster_id: clusterId,
35
+ skill: "polaris-run",
36
+ artifact_dir: dir,
37
+ branch: "feature/pol-105",
38
+ current_step_id: "03-execute-child",
39
+ step_cursor: "dispatching",
40
+ status: "executing",
41
+ session_type: "implementation",
42
+ active_child: "",
43
+ last_commit: "",
44
+ next_open_child: null,
45
+ completed_children: [],
46
+ open_children: [],
47
+ open_children_meta: {},
48
+ context_budget: {
49
+ children_completed: 0,
50
+ files_touched_total: 0,
51
+ max_children_per_session: 5,
52
+ },
53
+ dispatch_boundary: { dispatch_epoch: 0, continue_epoch: 0, last_dispatched_child: null },
54
+ run_bootstrap_seal: (0, run_bootstrap_js_1.createBootstrapSeal)(runId, clusterId, openChildren),
55
+ updated_at: "2026-05-26T00:00:00.000Z",
56
+ ...overrides,
57
+ };
58
+ (0, node_fs_1.writeFileSync)(stateFile, JSON.stringify(state, null, 2), "utf-8");
59
+ return stateFile;
60
+ }
61
+ function readJsonLines(path) {
62
+ return (0, node_fs_1.readFileSync)(path, "utf-8")
63
+ .trim()
64
+ .split("\n")
65
+ .filter(Boolean)
66
+ .map((line) => JSON.parse(line));
67
+ }
68
+ function makeAdapter(calls) {
69
+ const result = {
70
+ exit_code: 0,
71
+ provider_used: "agent-subtask",
72
+ command_run: "agent-subtask:POL-112",
73
+ summary: JSON.stringify({
74
+ child_id: "POL-112",
75
+ status: "done",
76
+ commit_hash: "abc1120",
77
+ validation_summary: "ephemeral smoke passed",
78
+ next_action: "resume-parent",
79
+ }),
80
+ };
81
+ return {
82
+ name: "agent-subtask",
83
+ async dispatch(packet, options) {
84
+ calls.push({ packet, options });
85
+ return result;
86
+ },
87
+ };
88
+ }
89
+ (0, vitest_1.describe)("ephemeral execution smoke", () => {
90
+ let tmpDir;
91
+ (0, vitest_1.beforeEach)(() => {
92
+ tmpDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "polaris-ephemeral-smoke-"));
93
+ (0, node_fs_1.mkdirSync)((0, node_path_1.join)(tmpDir, "runs", "ephemeral-smoke-run"), { recursive: true });
94
+ vitest_1.vi.clearAllMocks();
95
+ });
96
+ (0, vitest_1.afterEach)(() => {
97
+ (0, node_fs_1.rmSync)(tmpDir, { recursive: true, force: true });
98
+ });
99
+ (0, vitest_1.it)("rejects an ANALYZE parent before dispatch", async () => {
100
+ const calls = [];
101
+ vitest_1.vi.mocked(registry_js_1.createAdapter).mockReturnValue(makeAdapter(calls));
102
+ const stateFile = writeState(tmpDir, {
103
+ cluster_id: "POL-200",
104
+ open_children: ["POL-201"],
105
+ next_open_child: "POL-201",
106
+ open_children_meta: {
107
+ "POL-200": { title: "ANALYZE: Ephemeral execution split" },
108
+ "POL-201": { title: "IMPLEMENT: unreachable child" },
109
+ },
110
+ });
111
+ const result = await (0, parent_js_1.runParentLoop)({ stateFile, repoRoot: tmpDir });
112
+ (0, vitest_1.expect)(result.haltReason).toBe("analyze-parent");
113
+ (0, vitest_1.expect)(result.childrenDispatched).toBe(0);
114
+ (0, vitest_1.expect)(result.message).toBe("polaris-run targets IMPLEMENT parents, not ANALYZE issues. Run polaris-analyze first to create an IMPLEMENT parent.");
115
+ (0, vitest_1.expect)(calls).toHaveLength(0);
116
+ (0, vitest_1.expect)(registry_js_1.createAdapter).not.toHaveBeenCalled();
117
+ });
118
+ (0, vitest_1.it)("dispatches one child through the ephemeral adapter and records state plus telemetry", async () => {
119
+ const calls = [];
120
+ vitest_1.vi.mocked(registry_js_1.createAdapter).mockReturnValue(makeAdapter(calls));
121
+ const stateFile = writeState(tmpDir, {
122
+ orchestration_mode: "ephemeral",
123
+ open_children: ["POL-112"],
124
+ next_open_child: "POL-112",
125
+ open_children_meta: {
126
+ "POL-105": { title: "IMPLEMENT: Issue hierarchy and ephemeral execution refactor" },
127
+ "POL-112": { title: "IMPLEMENT: Write Polaris ephemeral execution smoke test and validation harness" },
128
+ },
129
+ });
130
+ const result = await (0, parent_js_1.runParentLoop)({ stateFile, repoRoot: tmpDir });
131
+ (0, vitest_1.expect)(result.haltReason).toBe("cluster-complete");
132
+ (0, vitest_1.expect)(result.childrenDispatched).toBe(1);
133
+ (0, vitest_1.expect)(registry_js_1.createAdapter).toHaveBeenCalledWith("agent-subtask", vitest_1.expect.objectContaining({ adapter: "agent-subtask" }));
134
+ (0, vitest_1.expect)(calls).toHaveLength(1);
135
+ (0, vitest_1.expect)(calls[0].packet.active_child).toBe("POL-112");
136
+ (0, vitest_1.expect)(calls[0].options.provider).toBe("agent-subtask");
137
+ const updatedState = JSON.parse((0, node_fs_1.readFileSync)(stateFile, "utf-8"));
138
+ (0, vitest_1.expect)(updatedState.status).toBe("cluster-complete");
139
+ (0, vitest_1.expect)(updatedState.completed_children).toEqual(["POL-112"]);
140
+ (0, vitest_1.expect)(updatedState.open_children).toEqual([]);
141
+ (0, vitest_1.expect)(updatedState.next_open_child).toBeNull();
142
+ (0, vitest_1.expect)(updatedState.last_commit).toBe("abc1120");
143
+ (0, vitest_1.expect)(updatedState.context_budget).toMatchObject({ children_completed: 1 });
144
+ const telemetry = readJsonLines((0, node_path_1.join)(tmpDir, "runs", "ephemeral-smoke-run", "telemetry.jsonl"));
145
+ (0, vitest_1.expect)(telemetry).toEqual(vitest_1.expect.arrayContaining([
146
+ vitest_1.expect.objectContaining({
147
+ event: "child-dispatched",
148
+ child_id: "POL-112",
149
+ adapter: "agent-subtask",
150
+ orchestration_mode: "ephemeral",
151
+ provider: "agent-subtask",
152
+ }),
153
+ vitest_1.expect.objectContaining({
154
+ event: "child-complete",
155
+ child_id: "POL-112",
156
+ children_completed: 1,
157
+ }),
158
+ ]));
159
+ });
160
+ });