@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,318 @@
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 lifecycle_dispatch_js_1 = require("./lifecycle-dispatch.js");
8
+ function makeDir() {
9
+ const dir = (0, node_path_1.join)((0, node_os_1.tmpdir)(), `polaris-lifecycle-dispatch-${Date.now()}-${Math.random().toString(36).slice(2)}`);
10
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
11
+ return dir;
12
+ }
13
+ function baseConfig() {
14
+ return {
15
+ version: "1.0",
16
+ repo: {
17
+ name: "",
18
+ sourceRoots: ["src"],
19
+ docsRoots: [],
20
+ taskchainRoots: [],
21
+ generatedRoots: [],
22
+ sidecarOutputPath: ".polaris/map",
23
+ },
24
+ map: {
25
+ confidenceThreshold: 0.75,
26
+ autoWriteAbove: 0.85,
27
+ reviewRequiredBelow: 0.75,
28
+ inferenceRules: [],
29
+ onLowConfidence: "warn",
30
+ },
31
+ loop: {
32
+ bootstrapOutputPath: ".polaris/bootstrap",
33
+ analyzeImplBoundaryEnforced: true,
34
+ sessionTerminationMode: "emit-marker",
35
+ allowBranchDivergence: false,
36
+ },
37
+ orchestration: {
38
+ mode: "auto",
39
+ auto_finalize: false,
40
+ notification_format: "terse",
41
+ },
42
+ execution: {
43
+ adapter: "terminal-cli",
44
+ providers: {
45
+ worker: { command: "worker-cli" },
46
+ startup: { command: "startup-cli" },
47
+ finalizer: { command: "finalizer-cli" },
48
+ },
49
+ rotation: ["worker"],
50
+ allowCrossAgentFallback: false,
51
+ roles: {
52
+ worker: { provider: "worker" },
53
+ startup: { provider: "startup", model: "gpt-startup" },
54
+ finalizer: { provider: "finalizer", model: "gpt-finalizer" },
55
+ },
56
+ },
57
+ finalize: {
58
+ targetBranch: "main",
59
+ prDraft: true,
60
+ runChecks: [],
61
+ requireMapValidation: true,
62
+ requireSchemaValidation: true,
63
+ archiveRunSnapshot: true,
64
+ },
65
+ tracker: {
66
+ linear: {
67
+ enabled: false,
68
+ teamId: "",
69
+ projectId: "",
70
+ },
71
+ },
72
+ integrations: {
73
+ github: {
74
+ owner: "",
75
+ repo: "",
76
+ },
77
+ },
78
+ canon: {
79
+ checkOnContinue: true,
80
+ checkOnFinalize: true,
81
+ },
82
+ providers: {
83
+ repoAnalysis: {
84
+ preferred: undefined,
85
+ fallback: ["polaris-map", "ripgrep"],
86
+ },
87
+ compactionProviders: [],
88
+ },
89
+ budget: {
90
+ mode: "fixed-cap",
91
+ max_children: 3,
92
+ stop_on_fail: false,
93
+ allow_analyze_children: false,
94
+ },
95
+ compact: {
96
+ orchestratorMode: "standard",
97
+ workerMode: "standard",
98
+ level: "standard",
99
+ },
100
+ skill_packet: {
101
+ analysis_confidence_threshold: 85,
102
+ auto_deep_analysis: false,
103
+ allow_cross_provider_delegation: false,
104
+ },
105
+ };
106
+ }
107
+ function makeAdapter(writeResult) {
108
+ return {
109
+ name: "mock",
110
+ async dispatch(packet, options) {
111
+ const resultFile = packet.result_file_contract?.result_file;
112
+ const result = writeResult(packet);
113
+ if (resultFile && result !== undefined) {
114
+ (0, node_fs_1.writeFileSync)(resultFile, typeof result === "string" ? result : JSON.stringify(result, null, 2));
115
+ }
116
+ return {
117
+ exit_code: 0,
118
+ provider_used: options.provider,
119
+ command_run: `mock:${options.provider}`,
120
+ summary: JSON.stringify({ status: "done" }),
121
+ };
122
+ },
123
+ };
124
+ }
125
+ (0, vitest_1.describe)("resolveLifecycleProvider", () => {
126
+ (0, vitest_1.it)("selects role-specific provider and model before default worker rotation", () => {
127
+ const resolved = (0, lifecycle_dispatch_js_1.resolveLifecycleProvider)(baseConfig(), "startup");
128
+ (0, vitest_1.expect)(resolved).toEqual({
129
+ adapter: "terminal-cli",
130
+ provider: "startup",
131
+ model: "gpt-startup",
132
+ });
133
+ });
134
+ });
135
+ (0, vitest_1.describe)("dispatchLifecyclePhase", () => {
136
+ (0, vitest_1.it)("executes startup as a sealed dispatch phase and records role telemetry", async () => {
137
+ const dir = makeDir();
138
+ try {
139
+ const telemetryFile = (0, node_path_1.join)(dir, "telemetry.jsonl");
140
+ const result = await (0, lifecycle_dispatch_js_1.dispatchLifecyclePhase)({
141
+ phase: "startup",
142
+ runId: "run-1",
143
+ clusterId: "POL-188",
144
+ branch: "polaris/POL-188",
145
+ stateFile: (0, node_path_1.join)(dir, "current-state.json"),
146
+ telemetryFile,
147
+ config: baseConfig(),
148
+ adapter: makeAdapter((packet) => ({
149
+ run_id: packet.run_id,
150
+ role: "startup",
151
+ status: "success",
152
+ execution_plan: ["POL-198"],
153
+ first_child: "POL-198",
154
+ })),
155
+ });
156
+ (0, vitest_1.expect)(result.ok).toBe(true);
157
+ if (result.ok) {
158
+ (0, vitest_1.expect)(result.role).toBe("startup");
159
+ (0, vitest_1.expect)(result.provider).toBe("startup");
160
+ (0, vitest_1.expect)(result.result.first_child).toBe("POL-198");
161
+ }
162
+ const telemetry = (0, node_fs_1.readFileSync)(telemetryFile, "utf-8");
163
+ (0, vitest_1.expect)(telemetry).toContain("\"event\":\"lifecycle-dispatched\"");
164
+ (0, vitest_1.expect)(telemetry).toContain("\"role\":\"startup\"");
165
+ (0, vitest_1.expect)(telemetry).toContain("\"event\":\"lifecycle-result-accepted\"");
166
+ }
167
+ finally {
168
+ (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
169
+ }
170
+ });
171
+ (0, vitest_1.it)("executes finalization as a sealed dispatch phase", async () => {
172
+ const dir = makeDir();
173
+ try {
174
+ const result = await (0, lifecycle_dispatch_js_1.dispatchLifecyclePhase)({
175
+ phase: "finalize",
176
+ runId: "run-1",
177
+ clusterId: "POL-188",
178
+ branch: "polaris/POL-188",
179
+ stateFile: (0, node_path_1.join)(dir, "current-state.json"),
180
+ telemetryFile: (0, node_path_1.join)(dir, "telemetry.jsonl"),
181
+ config: baseConfig(),
182
+ adapter: makeAdapter((packet) => ({
183
+ run_id: packet.run_id,
184
+ role: "finalize",
185
+ status: "success",
186
+ branch_validated: true,
187
+ commits_validated: true,
188
+ tests_validated: true,
189
+ tracker_reconciliation_ready: true,
190
+ })),
191
+ });
192
+ (0, vitest_1.expect)(result.ok).toBe(true);
193
+ if (result.ok) {
194
+ (0, vitest_1.expect)(result.role).toBe("finalize");
195
+ (0, vitest_1.expect)(result.provider).toBe("finalizer");
196
+ (0, vitest_1.expect)(result.result.tracker_reconciliation_ready).toBe(true);
197
+ }
198
+ }
199
+ finally {
200
+ (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
201
+ }
202
+ });
203
+ (0, vitest_1.it)("archives cognition notes after accepting a sealed lifecycle result", async () => {
204
+ const dir = makeDir();
205
+ try {
206
+ (0, node_fs_1.mkdirSync)((0, node_path_1.join)(dir, ".git"), { recursive: true });
207
+ (0, node_fs_1.mkdirSync)((0, node_path_1.join)(dir, ".polaris", "cognition", "pending", "src", "loop"), { recursive: true });
208
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, ".polaris", "cognition", "pending", "src", "loop", "note.md"), "pending note", "utf-8");
209
+ const result = await (0, lifecycle_dispatch_js_1.dispatchLifecyclePhase)({
210
+ phase: "finalize",
211
+ runId: "run-1",
212
+ clusterId: "POL-188",
213
+ branch: "polaris/POL-188",
214
+ repoRoot: dir,
215
+ stateFile: (0, node_path_1.join)(dir, "current-state.json"),
216
+ telemetryFile: (0, node_path_1.join)(dir, "telemetry.jsonl"),
217
+ config: baseConfig(),
218
+ adapter: makeAdapter((packet) => ({
219
+ run_id: packet.run_id,
220
+ role: "finalize",
221
+ status: "success",
222
+ cognition_archive: {
223
+ reconcile_id: "reconcile-1",
224
+ notes_consumed: ["src/loop/note.md"],
225
+ polaris_md_updated: true,
226
+ summary_md_updated: false,
227
+ },
228
+ })),
229
+ });
230
+ (0, vitest_1.expect)(result.ok).toBe(true);
231
+ (0, vitest_1.expect)((0, node_fs_1.readFileSync)((0, node_path_1.join)(dir, ".polaris", "cognition", "archive", "src", "loop", "note.md"), "utf-8")).toBe("pending note");
232
+ const index = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(dir, ".polaris", "cognition", "archive", "src", "loop", "cognition-index.json"), "utf-8"));
233
+ (0, vitest_1.expect)(index.entries).toEqual([
234
+ {
235
+ reconcile_id: "reconcile-1",
236
+ run_id: "run-1",
237
+ reconciled_at: vitest_1.expect.any(String),
238
+ notes_consumed: ["note.md"],
239
+ polaris_md_updated: true,
240
+ summary_md_updated: false,
241
+ },
242
+ ]);
243
+ }
244
+ finally {
245
+ (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
246
+ }
247
+ });
248
+ (0, vitest_1.it)("halts safely when the sealed result file is missing", async () => {
249
+ const dir = makeDir();
250
+ try {
251
+ const result = await (0, lifecycle_dispatch_js_1.dispatchLifecyclePhase)({
252
+ phase: "finalize",
253
+ runId: "run-1",
254
+ clusterId: "POL-188",
255
+ branch: "polaris/POL-188",
256
+ stateFile: (0, node_path_1.join)(dir, "current-state.json"),
257
+ telemetryFile: (0, node_path_1.join)(dir, "telemetry.jsonl"),
258
+ config: baseConfig(),
259
+ adapter: makeAdapter(() => undefined),
260
+ });
261
+ (0, vitest_1.expect)(result.ok).toBe(false);
262
+ if (!result.ok) {
263
+ (0, vitest_1.expect)(result.error).toBe("missing_result");
264
+ }
265
+ }
266
+ finally {
267
+ (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
268
+ }
269
+ });
270
+ (0, vitest_1.it)("halts safely on malformed sealed result JSON", async () => {
271
+ const dir = makeDir();
272
+ try {
273
+ const result = await (0, lifecycle_dispatch_js_1.dispatchLifecyclePhase)({
274
+ phase: "startup",
275
+ runId: "run-1",
276
+ clusterId: "POL-188",
277
+ branch: "polaris/POL-188",
278
+ stateFile: (0, node_path_1.join)(dir, "current-state.json"),
279
+ telemetryFile: (0, node_path_1.join)(dir, "telemetry.jsonl"),
280
+ config: baseConfig(),
281
+ adapter: makeAdapter(() => "{not json"),
282
+ });
283
+ (0, vitest_1.expect)(result.ok).toBe(false);
284
+ if (!result.ok) {
285
+ (0, vitest_1.expect)(result.error).toBe("malformed_result");
286
+ }
287
+ }
288
+ finally {
289
+ (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
290
+ }
291
+ });
292
+ (0, vitest_1.it)("halts safely on mismatched result role", async () => {
293
+ const dir = makeDir();
294
+ try {
295
+ const result = await (0, lifecycle_dispatch_js_1.dispatchLifecyclePhase)({
296
+ phase: "startup",
297
+ runId: "run-1",
298
+ clusterId: "POL-188",
299
+ branch: "polaris/POL-188",
300
+ stateFile: (0, node_path_1.join)(dir, "current-state.json"),
301
+ telemetryFile: (0, node_path_1.join)(dir, "telemetry.jsonl"),
302
+ config: baseConfig(),
303
+ adapter: makeAdapter((packet) => ({
304
+ run_id: packet.run_id,
305
+ role: "finalize",
306
+ status: "success",
307
+ })),
308
+ });
309
+ (0, vitest_1.expect)(result.ok).toBe(false);
310
+ if (!result.ok) {
311
+ (0, vitest_1.expect)(result.error).toBe("mismatched_result");
312
+ }
313
+ }
314
+ finally {
315
+ (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
316
+ }
317
+ });
318
+ });
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ /**
3
+ * Worker lifecycle manager for delegated-chain execution.
4
+ *
5
+ * Enforces the one-active-worker policy: by default the parent may have at
6
+ * most one live worker at a time. The parent calls register() immediately
7
+ * before dispatch and release() immediately after the worker returns a result.
8
+ *
9
+ * On session startup the parent should call forceReleaseAll() to recover from
10
+ * any orphaned registrations left by a previous crashed session.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.WorkerLifecycleManager = void 0;
14
+ // ── WorkerLifecycleManager ────────────────────────────────────────────────────
15
+ class WorkerLifecycleManager {
16
+ workers = new Map();
17
+ maxConcurrentWorkers;
18
+ constructor(maxConcurrentWorkers = 1) {
19
+ if (maxConcurrentWorkers < 1) {
20
+ throw new RangeError(`maxConcurrentWorkers must be >= 1, got ${maxConcurrentWorkers}`);
21
+ }
22
+ this.maxConcurrentWorkers = maxConcurrentWorkers;
23
+ }
24
+ // ── Slot management ─────────────────────────────────────────────────────────
25
+ /** Returns true when a new worker can be dispatched without exceeding the limit. */
26
+ canDispatch() {
27
+ return this.workers.size < this.maxConcurrentWorkers;
28
+ }
29
+ /**
30
+ * Register a worker before dispatch.
31
+ * Throws `Error` if the active-worker limit would be exceeded.
32
+ */
33
+ register(workerId, childId, role) {
34
+ if (this.workers.has(workerId)) {
35
+ throw new Error(`Worker "${workerId}" is already registered`);
36
+ }
37
+ if (!this.canDispatch()) {
38
+ const active = this.describeActive();
39
+ throw new Error(`Cannot dispatch worker "${workerId}": ` +
40
+ `${this.workers.size}/${this.maxConcurrentWorkers} worker slot(s) in use. ` +
41
+ `Active: ${active}. ` +
42
+ `Release the active worker before dispatching a new one.`);
43
+ }
44
+ const record = {
45
+ worker_id: workerId,
46
+ child_id: childId,
47
+ role,
48
+ dispatched_at: new Date().toISOString(),
49
+ };
50
+ this.workers.set(workerId, record);
51
+ return record;
52
+ }
53
+ /**
54
+ * Release a worker after it returns.
55
+ * No-op if the worker ID is not registered (idempotent).
56
+ */
57
+ release(workerId) {
58
+ this.workers.delete(workerId);
59
+ }
60
+ // ── Inspection ──────────────────────────────────────────────────────────────
61
+ /** Returns snapshot of currently registered workers. */
62
+ getActiveWorkers() {
63
+ return Array.from(this.workers.values());
64
+ }
65
+ /** Active worker count. */
66
+ get activeCount() {
67
+ return this.workers.size;
68
+ }
69
+ /** Returns true when any workers remain registered (orphan indicator). */
70
+ hasOrphanedWorkers() {
71
+ return this.workers.size > 0;
72
+ }
73
+ // ── Recovery ────────────────────────────────────────────────────────────────
74
+ /**
75
+ * Force-release all active registrations without waiting for workers.
76
+ * Use at session startup to recover from orphaned state.
77
+ * Returns the IDs that were released.
78
+ */
79
+ forceReleaseAll() {
80
+ const ids = Array.from(this.workers.keys());
81
+ this.workers.clear();
82
+ return ids;
83
+ }
84
+ // ── Helpers ──────────────────────────────────────────────────────────────────
85
+ describeActive() {
86
+ if (this.workers.size === 0)
87
+ return '(none)';
88
+ return Array.from(this.workers.values())
89
+ .map((w) => `${w.worker_id}[${w.role}:${w.child_id ?? 'no-child'}]`)
90
+ .join(', ');
91
+ }
92
+ }
93
+ exports.WorkerLifecycleManager = WorkerLifecycleManager;
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ /**
3
+ * Unit tests for src/loop/lifecycle.ts
4
+ *
5
+ * Verifies one-active-worker enforcement, register/release semantics,
6
+ * and orphan recovery via forceReleaseAll().
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ const vitest_1 = require("vitest");
10
+ const lifecycle_js_1 = require("./lifecycle.js");
11
+ // ── Constructor ───────────────────────────────────────────────────────────────
12
+ (0, vitest_1.describe)("WorkerLifecycleManager constructor", () => {
13
+ (0, vitest_1.it)("defaults to maxConcurrentWorkers = 1", () => {
14
+ const m = new lifecycle_js_1.WorkerLifecycleManager();
15
+ (0, vitest_1.expect)(m.maxConcurrentWorkers).toBe(1);
16
+ });
17
+ (0, vitest_1.it)("accepts a custom maxConcurrentWorkers", () => {
18
+ const m = new lifecycle_js_1.WorkerLifecycleManager(3);
19
+ (0, vitest_1.expect)(m.maxConcurrentWorkers).toBe(3);
20
+ });
21
+ (0, vitest_1.it)("throws RangeError for maxConcurrentWorkers < 1", () => {
22
+ (0, vitest_1.expect)(() => new lifecycle_js_1.WorkerLifecycleManager(0)).toThrow(RangeError);
23
+ (0, vitest_1.expect)(() => new lifecycle_js_1.WorkerLifecycleManager(-1)).toThrow(RangeError);
24
+ });
25
+ });
26
+ // ── canDispatch ───────────────────────────────────────────────────────────────
27
+ (0, vitest_1.describe)("canDispatch", () => {
28
+ (0, vitest_1.it)("returns true when no workers are registered", () => {
29
+ const m = new lifecycle_js_1.WorkerLifecycleManager(1);
30
+ (0, vitest_1.expect)(m.canDispatch()).toBe(true);
31
+ });
32
+ (0, vitest_1.it)("returns false when the slot is full", () => {
33
+ const m = new lifecycle_js_1.WorkerLifecycleManager(1);
34
+ m.register("w1", "POL-121", "impl");
35
+ (0, vitest_1.expect)(m.canDispatch()).toBe(false);
36
+ });
37
+ (0, vitest_1.it)("returns true after all slots are released", () => {
38
+ const m = new lifecycle_js_1.WorkerLifecycleManager(1);
39
+ m.register("w1", "POL-121", "impl");
40
+ m.release("w1");
41
+ (0, vitest_1.expect)(m.canDispatch()).toBe(true);
42
+ });
43
+ });
44
+ // ── register ──────────────────────────────────────────────────────────────────
45
+ (0, vitest_1.describe)("register", () => {
46
+ (0, vitest_1.it)("returns the worker record", () => {
47
+ const m = new lifecycle_js_1.WorkerLifecycleManager(1);
48
+ const rec = m.register("w1", "POL-121", "impl");
49
+ (0, vitest_1.expect)(rec.worker_id).toBe("w1");
50
+ (0, vitest_1.expect)(rec.child_id).toBe("POL-121");
51
+ (0, vitest_1.expect)(rec.role).toBe("impl");
52
+ (0, vitest_1.expect)(typeof rec.dispatched_at).toBe("string");
53
+ });
54
+ (0, vitest_1.it)("throws when slot limit is already reached", () => {
55
+ const m = new lifecycle_js_1.WorkerLifecycleManager(1);
56
+ m.register("w1", "POL-121", "impl");
57
+ (0, vitest_1.expect)(() => m.register("w2", "POL-122", "impl")).toThrow(/slot/i);
58
+ });
59
+ (0, vitest_1.it)("allows null child_id for finalize workers", () => {
60
+ const m = new lifecycle_js_1.WorkerLifecycleManager(1);
61
+ const rec = m.register("w1", null, "finalize");
62
+ (0, vitest_1.expect)(rec.child_id).toBeNull();
63
+ });
64
+ (0, vitest_1.it)("increments activeCount after register", () => {
65
+ const m = new lifecycle_js_1.WorkerLifecycleManager(2);
66
+ (0, vitest_1.expect)(m.activeCount).toBe(0);
67
+ m.register("w1", "POL-121", "impl");
68
+ (0, vitest_1.expect)(m.activeCount).toBe(1);
69
+ m.register("w2", "POL-122", "impl");
70
+ (0, vitest_1.expect)(m.activeCount).toBe(2);
71
+ });
72
+ });
73
+ // ── release ───────────────────────────────────────────────────────────────────
74
+ (0, vitest_1.describe)("release", () => {
75
+ (0, vitest_1.it)("removes the worker from active set", () => {
76
+ const m = new lifecycle_js_1.WorkerLifecycleManager(1);
77
+ m.register("w1", "POL-121", "impl");
78
+ m.release("w1");
79
+ (0, vitest_1.expect)(m.activeCount).toBe(0);
80
+ });
81
+ (0, vitest_1.it)("is idempotent — releasing an unknown ID is a no-op", () => {
82
+ const m = new lifecycle_js_1.WorkerLifecycleManager(1);
83
+ (0, vitest_1.expect)(() => m.release("unknown-worker")).not.toThrow();
84
+ });
85
+ (0, vitest_1.it)("frees the slot for the next dispatch", () => {
86
+ const m = new lifecycle_js_1.WorkerLifecycleManager(1);
87
+ m.register("w1", "POL-121", "impl");
88
+ m.release("w1");
89
+ (0, vitest_1.expect)(() => m.register("w2", "POL-122", "impl")).not.toThrow();
90
+ });
91
+ });
92
+ // ── getActiveWorkers ──────────────────────────────────────────────────────────
93
+ (0, vitest_1.describe)("getActiveWorkers", () => {
94
+ (0, vitest_1.it)("returns empty array when no workers registered", () => {
95
+ const m = new lifecycle_js_1.WorkerLifecycleManager(1);
96
+ (0, vitest_1.expect)(m.getActiveWorkers()).toEqual([]);
97
+ });
98
+ (0, vitest_1.it)("returns all registered worker records", () => {
99
+ const m = new lifecycle_js_1.WorkerLifecycleManager(3);
100
+ m.register("w1", "POL-121", "impl");
101
+ m.register("w2", null, "finalize");
102
+ const workers = m.getActiveWorkers();
103
+ (0, vitest_1.expect)(workers).toHaveLength(2);
104
+ (0, vitest_1.expect)(workers.map((w) => w.worker_id).sort()).toEqual(["w1", "w2"]);
105
+ });
106
+ });
107
+ // ── hasOrphanedWorkers ────────────────────────────────────────────────────────
108
+ (0, vitest_1.describe)("hasOrphanedWorkers", () => {
109
+ (0, vitest_1.it)("returns false when no workers registered", () => {
110
+ const m = new lifecycle_js_1.WorkerLifecycleManager(1);
111
+ (0, vitest_1.expect)(m.hasOrphanedWorkers()).toBe(false);
112
+ });
113
+ (0, vitest_1.it)("returns true when workers are registered", () => {
114
+ const m = new lifecycle_js_1.WorkerLifecycleManager(1);
115
+ m.register("w1", "POL-121", "impl");
116
+ (0, vitest_1.expect)(m.hasOrphanedWorkers()).toBe(true);
117
+ });
118
+ });
119
+ // ── forceReleaseAll ───────────────────────────────────────────────────────────
120
+ (0, vitest_1.describe)("forceReleaseAll", () => {
121
+ (0, vitest_1.it)("returns empty array when nothing registered", () => {
122
+ const m = new lifecycle_js_1.WorkerLifecycleManager(1);
123
+ (0, vitest_1.expect)(m.forceReleaseAll()).toEqual([]);
124
+ });
125
+ (0, vitest_1.it)("clears all workers and returns their IDs", () => {
126
+ const m = new lifecycle_js_1.WorkerLifecycleManager(3);
127
+ m.register("w1", "POL-121", "impl");
128
+ m.register("w2", "POL-122", "impl");
129
+ const released = m.forceReleaseAll();
130
+ (0, vitest_1.expect)(released.sort()).toEqual(["w1", "w2"]);
131
+ (0, vitest_1.expect)(m.activeCount).toBe(0);
132
+ });
133
+ (0, vitest_1.it)("allows new registrations after force release", () => {
134
+ const m = new lifecycle_js_1.WorkerLifecycleManager(1);
135
+ m.register("w1", "POL-121", "impl");
136
+ m.forceReleaseAll();
137
+ (0, vitest_1.expect)(() => m.register("w2", "POL-122", "impl")).not.toThrow();
138
+ });
139
+ });
140
+ // ── One-active-worker enforcement (integration) ───────────────────────────────
141
+ (0, vitest_1.describe)("one-active-worker policy (default)", () => {
142
+ (0, vitest_1.it)("enforces serial dispatch: second register fails until first is released", () => {
143
+ const m = new lifecycle_js_1.WorkerLifecycleManager();
144
+ m.register("worker-POL-121", "POL-121", "impl");
145
+ (0, vitest_1.expect)(() => m.register("worker-POL-122", "POL-122", "impl")).toThrow();
146
+ m.release("worker-POL-121");
147
+ // Now should succeed
148
+ (0, vitest_1.expect)(() => m.register("worker-POL-122", "POL-122", "impl")).not.toThrow();
149
+ });
150
+ });