@geraldmaron/construct 1.1.0 → 1.2.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 (425) hide show
  1. package/README.md +30 -18
  2. package/bin/construct +392 -91
  3. package/bin/construct-postinstall.mjs +30 -2
  4. package/examples/distribution/README.md +42 -0
  5. package/examples/distribution/manifest.json +53 -0
  6. package/examples/distribution/sources/adr.md +84 -0
  7. package/examples/distribution/sources/deck-one-pager.md +65 -0
  8. package/examples/distribution/sources/prd-platform.md +161 -0
  9. package/examples/distribution/sources/research-brief.md +80 -0
  10. package/examples/distribution/sources/rfc-platform.md +82 -0
  11. package/examples/distribution/sources/runbook.md +103 -0
  12. package/examples/distribution/sources/strategy.md +88 -0
  13. package/lib/adapters-sync.mjs +67 -0
  14. package/lib/artifact-gate-notice.mjs +38 -0
  15. package/lib/artifact-manifest.mjs +112 -0
  16. package/lib/artifact-release-gate.mjs +171 -0
  17. package/lib/artifact-reviewers.mjs +68 -0
  18. package/lib/artifact-type-from-path.mjs +79 -0
  19. package/lib/audit-skills.mjs +4 -0
  20. package/lib/audit-specialists.mjs +285 -0
  21. package/lib/audit-trail.mjs +77 -10
  22. package/lib/auto-docs.mjs +87 -18
  23. package/lib/beads/drift.mjs +27 -6
  24. package/lib/beads-client.mjs +49 -121
  25. package/lib/beads-optimistic.mjs +5 -12
  26. package/lib/brand-fonts.mjs +93 -0
  27. package/lib/brand-prose.mjs +214 -0
  28. package/lib/brand-tokens.mjs +92 -0
  29. package/lib/bridges/copilot-proxy.mjs +13 -26
  30. package/lib/capability-ledger.mjs +156 -0
  31. package/lib/certification/artifact-fixtures.mjs +132 -0
  32. package/lib/certification/artifact-gates.mjs +63 -0
  33. package/lib/certification/artifact-provenance.mjs +97 -0
  34. package/lib/certification/canonical-scenarios.mjs +78 -0
  35. package/lib/certification/cli.mjs +191 -0
  36. package/lib/certification/dashboard-api.mjs +71 -0
  37. package/lib/certification/demo-parity.mjs +116 -0
  38. package/lib/certification/document-io-fixtures.mjs +246 -0
  39. package/lib/certification/document-workflow.mjs +97 -0
  40. package/lib/certification/eval-bridge.mjs +77 -0
  41. package/lib/certification/model-routing.mjs +149 -0
  42. package/lib/certification/prompt-budget.mjs +119 -0
  43. package/lib/certification/rc-gate.mjs +206 -0
  44. package/lib/certification/real-llm-scenarios.mjs +303 -0
  45. package/lib/certification/role-cards.mjs +113 -0
  46. package/lib/certification/role-overlays.mjs +117 -0
  47. package/lib/certification/run.mjs +122 -0
  48. package/lib/certification/runner.mjs +323 -0
  49. package/lib/certification/scenarios.mjs +51 -0
  50. package/lib/certification/skill-inventory.mjs +289 -0
  51. package/lib/certification/skill-scenarios.mjs +147 -0
  52. package/lib/certification/specialist-contracts.mjs +85 -0
  53. package/lib/certification/specialist-scenarios.mjs +175 -0
  54. package/lib/certification/stale-impact.mjs +146 -0
  55. package/lib/certification/status.mjs +252 -0
  56. package/lib/certification/store.mjs +77 -0
  57. package/lib/chat/cli.mjs +333 -0
  58. package/lib/chat/command-suggest.mjs +161 -0
  59. package/lib/chat/commands.mjs +215 -0
  60. package/lib/chat/config.mjs +142 -0
  61. package/lib/chat/context-compactor.mjs +250 -0
  62. package/lib/chat/context-continuation.mjs +253 -0
  63. package/lib/chat/continuation-source.mjs +58 -0
  64. package/lib/chat/demo-guide.mjs +61 -0
  65. package/lib/chat/design-tokens.mjs +91 -0
  66. package/lib/chat/desktop-binary.mjs +79 -0
  67. package/lib/chat/desktop-build.mjs +130 -0
  68. package/lib/chat/desktop-launcher.mjs +133 -0
  69. package/lib/chat/evidence.mjs +145 -0
  70. package/lib/chat/export.mjs +74 -0
  71. package/lib/chat/harness/driver.mjs +91 -0
  72. package/lib/chat/list-picker.mjs +112 -0
  73. package/lib/chat/model-picker.mjs +356 -0
  74. package/lib/chat/openrouter-fallback.mjs +151 -0
  75. package/lib/chat/permission-prompt.mjs +33 -0
  76. package/lib/chat/picker-catalog.mjs +45 -0
  77. package/lib/chat/policy-telemetry.mjs +34 -0
  78. package/lib/chat/present.mjs +245 -0
  79. package/lib/chat/session-context.mjs +39 -0
  80. package/lib/chat/session-persist.mjs +73 -0
  81. package/lib/chat/session-restore.mjs +71 -0
  82. package/lib/chat/session-settings.mjs +53 -0
  83. package/lib/chat/system-prompt.mjs +52 -0
  84. package/lib/chat/transparency.mjs +93 -0
  85. package/lib/chat/tui/color-scheme.mjs +42 -0
  86. package/lib/chat/tui/markdown.mjs +123 -0
  87. package/lib/chat/tui/presentation.mjs +100 -0
  88. package/lib/chat/tui/render.mjs +500 -0
  89. package/lib/chat/tui/turn-block.mjs +284 -0
  90. package/lib/chat/tui/turn-present.mjs +18 -0
  91. package/lib/chat/tui/turn-state.mjs +88 -0
  92. package/lib/chat/tui/usage.mjs +122 -0
  93. package/lib/chat/web-commands.mjs +146 -0
  94. package/lib/chat/web-launcher.mjs +63 -0
  95. package/lib/chat/web-picker-keys.mjs +46 -0
  96. package/lib/chat/web-session.mjs +159 -0
  97. package/lib/cli-commands.mjs +232 -11
  98. package/lib/comment-lint.mjs +6 -4
  99. package/lib/config/schema.mjs +36 -5
  100. package/lib/contract-schemas/decision.json +50 -0
  101. package/lib/contract-schemas/implementation.json +51 -0
  102. package/lib/contract-schemas/review-report.json +32 -0
  103. package/lib/contract-schemas/test-report.json +43 -0
  104. package/lib/contracts/construct-handoff.mjs +60 -0
  105. package/lib/contracts/validate.mjs +32 -3
  106. package/lib/contracts/violation-log.mjs +58 -5
  107. package/lib/dashboard-demo.mjs +71 -0
  108. package/lib/dashboard-static.mjs +19 -5
  109. package/lib/decisions/registry.mjs +5 -3
  110. package/lib/deck-export-pptx.mjs +1152 -0
  111. package/lib/demo-recording.mjs +142 -0
  112. package/lib/demo-script.mjs +114 -0
  113. package/lib/demo-surface.mjs +249 -0
  114. package/lib/demo.mjs +703 -0
  115. package/lib/diagram-export.mjs +192 -0
  116. package/lib/diagram.mjs +302 -0
  117. package/lib/docs-verify.mjs +25 -7
  118. package/lib/doctor/index.mjs +4 -2
  119. package/lib/doctor/project-adapters.mjs +44 -0
  120. package/lib/doctor/watchers/cx-budget.mjs +98 -0
  121. package/lib/doctor/watchers/graph-staleness.mjs +52 -0
  122. package/lib/doctor/watchers/process-pressure.mjs +4 -3
  123. package/lib/doctor/watchers/service-health.mjs +1 -1
  124. package/lib/document-export.mjs +360 -36
  125. package/lib/document-extract.mjs +88 -0
  126. package/lib/document-ingest.mjs +32 -18
  127. package/lib/embed/cli.mjs +2 -2
  128. package/lib/embed/daemon.mjs +18 -46
  129. package/lib/embed/semantic.mjs +5 -2
  130. package/lib/embed/worker.mjs +2 -2
  131. package/lib/embedded-contract/model-resolve.mjs +10 -9
  132. package/lib/env-config.mjs +26 -0
  133. package/lib/evals/dataset.mjs +137 -0
  134. package/lib/evals/gates.mjs +175 -0
  135. package/lib/graph/build-co-change.mjs +54 -0
  136. package/lib/graph/build-from-registry.mjs +136 -0
  137. package/lib/graph/build-import-graph.mjs +153 -0
  138. package/lib/graph/cli.mjs +110 -0
  139. package/lib/graph/impact-cli.mjs +89 -0
  140. package/lib/graph/impact.mjs +108 -0
  141. package/lib/graph/staleness.mjs +44 -0
  142. package/lib/graph/store.mjs +172 -0
  143. package/lib/handoffs/cleanup.mjs +1 -1
  144. package/lib/health-check.mjs +90 -76
  145. package/lib/hooks/artifact-release-gate.mjs +43 -0
  146. package/lib/hooks/audit-trail.mjs +14 -28
  147. package/lib/hooks/brand-prose-lint.mjs +38 -0
  148. package/lib/hooks/graph-impact-advisory.mjs +62 -0
  149. package/lib/hooks/session-optimize.mjs +84 -207
  150. package/lib/hooks/session-start.mjs +4 -5
  151. package/lib/host-disposition.mjs +7 -0
  152. package/lib/improvement/cli.mjs +189 -0
  153. package/lib/improvement/controller.mjs +137 -0
  154. package/lib/improvement/proposal.mjs +120 -0
  155. package/lib/improvement/specialist-loop.mjs +192 -0
  156. package/lib/improvement/store.mjs +89 -0
  157. package/lib/improvement/surface.mjs +219 -0
  158. package/lib/ingest-tooling.mjs +97 -0
  159. package/lib/init/doc-lanes.mjs +165 -0
  160. package/lib/init-docs.mjs +31 -178
  161. package/lib/init-unified.mjs +27 -97
  162. package/lib/init-update-guide.mjs +102 -0
  163. package/lib/init-update.mjs +32 -1
  164. package/lib/init.mjs +8 -0
  165. package/lib/install/desktop-binary-download.mjs +85 -0
  166. package/lib/install/legacy-global-cleanup.mjs +189 -0
  167. package/lib/intake/daemon.mjs +2 -0
  168. package/lib/intake/git-queue.mjs +9 -8
  169. package/lib/intake/queue.mjs +2 -1
  170. package/lib/intake/session-prelude.mjs +90 -1
  171. package/lib/libreoffice-export.mjs +97 -0
  172. package/lib/logging/rotate.mjs +9 -0
  173. package/lib/maintenance/docker-reclaim.mjs +206 -0
  174. package/lib/mcp/external-schema-cost.mjs +74 -0
  175. package/lib/mcp/server.mjs +84 -9
  176. package/lib/mcp/stdio-mcp-probe.mjs +188 -0
  177. package/lib/mcp/tool-budget.mjs +55 -4
  178. package/lib/mcp/tools/document.mjs +18 -4
  179. package/lib/mcp/tools/skills.mjs +32 -3
  180. package/lib/mcp/tools/workflow.mjs +13 -1
  181. package/lib/model-free-selector.mjs +18 -0
  182. package/lib/model-registry.mjs +13 -229
  183. package/lib/model-router.mjs +425 -93
  184. package/lib/models/behavior-matrix.mjs +289 -0
  185. package/lib/models/catalog.mjs +209 -0
  186. package/lib/models/execution-capability-profile.mjs +196 -0
  187. package/lib/models/execution-policy.mjs +307 -0
  188. package/lib/models/provider-poll.mjs +383 -0
  189. package/lib/npm-spawn-env.mjs +17 -0
  190. package/lib/ollama/installed-models.mjs +129 -0
  191. package/lib/oracle/actions.mjs +187 -0
  192. package/lib/oracle/artifact-gate.mjs +99 -0
  193. package/lib/oracle/cli.mjs +204 -0
  194. package/lib/oracle/daemon-entry.mjs +14 -0
  195. package/lib/oracle/dispatch.mjs +81 -0
  196. package/lib/oracle/execute.mjs +143 -0
  197. package/lib/oracle/gaps.mjs +76 -0
  198. package/lib/oracle/index.mjs +84 -0
  199. package/lib/oracle/issues.mjs +164 -0
  200. package/lib/oracle/org-graph.mjs +170 -0
  201. package/lib/oracle/policy.mjs +80 -0
  202. package/lib/oracle/read-model.mjs +398 -0
  203. package/lib/oracle/reconcile.mjs +191 -0
  204. package/lib/oracle/routing.mjs +89 -0
  205. package/lib/oracle/synthesize.mjs +384 -0
  206. package/lib/oracle/verdicts.mjs +51 -0
  207. package/lib/orchestration/worker.mjs +88 -27
  208. package/lib/orchestration-policy.mjs +50 -0
  209. package/lib/parity.mjs +96 -2
  210. package/lib/persona-sections.mjs +66 -0
  211. package/lib/playwright-demo.mjs +252 -0
  212. package/lib/project-init-shared.mjs +4 -1
  213. package/lib/prompt-composer.js +9 -4
  214. package/lib/prompt-validation-contract.mjs +24 -0
  215. package/lib/provider-capabilities.js +57 -11
  216. package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
  217. package/lib/providers/contract/adapters/git/index.mjs +115 -0
  218. package/lib/providers/contract/adapters/github/index.mjs +166 -0
  219. package/lib/providers/contract/adapters/jira/index.mjs +187 -0
  220. package/lib/providers/contract/adapters/slack/index.mjs +175 -0
  221. package/lib/providers/contract/contract-tests.mjs +57 -0
  222. package/lib/providers/contract/errors.mjs +48 -0
  223. package/lib/providers/contract/interface.mjs +50 -0
  224. package/lib/providers/contract/registry.mjs +102 -0
  225. package/lib/providers/copilot-auth.mjs +297 -0
  226. package/lib/providers/credential-bootstrap.mjs +178 -0
  227. package/lib/providers/credential-catalog.mjs +46 -0
  228. package/lib/providers/credential-sources.mjs +63 -0
  229. package/lib/providers/creds.mjs +5 -2
  230. package/lib/providers/op-run.mjs +59 -0
  231. package/lib/providers/secret-resolver.mjs +159 -0
  232. package/lib/publish-template.mjs +163 -0
  233. package/lib/publish-tooling.mjs +119 -0
  234. package/lib/publish.mjs +305 -0
  235. package/lib/registry/cli.mjs +82 -0
  236. package/lib/registry/consolidation.mjs +147 -0
  237. package/lib/registry/generate-docs.mjs +75 -0
  238. package/lib/registry/skill-verification.mjs +57 -0
  239. package/lib/registry/surface-map.mjs +76 -0
  240. package/lib/registry/validate.mjs +135 -0
  241. package/lib/resources/budget.mjs +82 -0
  242. package/lib/resources/process-budget.mjs +45 -0
  243. package/lib/rules-delivery.mjs +9 -2
  244. package/lib/rules-read.mjs +26 -0
  245. package/lib/runtime-env.mjs +23 -0
  246. package/lib/runtime-pressure.mjs +51 -7
  247. package/lib/schema-infer.mjs +13 -2
  248. package/lib/server/chat-loop.mjs +622 -0
  249. package/lib/server/demo-preview.mjs +63 -0
  250. package/lib/server/index.mjs +229 -8
  251. package/lib/server/langfuse-login.mjs +3 -3
  252. package/lib/service-manager.mjs +61 -9
  253. package/lib/session-store.mjs +1 -1
  254. package/lib/setup.mjs +102 -2
  255. package/lib/specialists/prompt-schema.mjs +19 -10
  256. package/lib/specialists/roster.mjs +43 -0
  257. package/lib/specialists/scaffold.mjs +56 -0
  258. package/lib/storage/backend.mjs +6 -6
  259. package/lib/storage/embeddings-local.mjs +6 -3
  260. package/lib/storage/file-lock.mjs +18 -11
  261. package/lib/storage/hybrid-query.mjs +7 -4
  262. package/lib/storage/state-source.mjs +5 -5
  263. package/lib/storage/sync.mjs +2 -3
  264. package/lib/telemetry/rule-calls.mjs +52 -0
  265. package/lib/template-registry.mjs +2 -2
  266. package/lib/templates/visual-requirements.mjs +27 -51
  267. package/lib/test-corpus-inventory.mjs +313 -0
  268. package/lib/uninstall/uninstall.mjs +19 -7
  269. package/lib/update.mjs +12 -0
  270. package/lib/upgrade.mjs +14 -0
  271. package/lib/wireframe.mjs +20 -14
  272. package/lib/worker/run.mjs +17 -4
  273. package/lib/worker/trace.mjs +11 -3
  274. package/package.json +29 -13
  275. package/personas/construct.md +13 -13
  276. package/platforms/claude/settings.template.json +36 -0
  277. package/rules/common/patterns.md +1 -1
  278. package/rules/common/release-gates.md +4 -3
  279. package/scripts/sync-specialists.mjs +187 -60
  280. package/skills/devops/data-engineering.md +1 -1
  281. package/skills/docs/adr-workflow.md +1 -0
  282. package/skills/docs/backlog-proposal-workflow.md +1 -0
  283. package/skills/docs/codebase-research-workflow.md +40 -0
  284. package/skills/docs/customer-profile-workflow.md +1 -0
  285. package/skills/docs/document-ingest-workflow.md +1 -0
  286. package/skills/docs/evidence-ingest-workflow.md +1 -0
  287. package/skills/docs/init-docs.md +2 -2
  288. package/skills/docs/init-project.md +8 -3
  289. package/skills/docs/prd-workflow.md +24 -1
  290. package/skills/docs/prfaq-workflow.md +1 -0
  291. package/skills/docs/product-intelligence-workflow.md +1 -0
  292. package/skills/docs/product-signal-workflow.md +1 -0
  293. package/skills/docs/research-workflow.md +54 -37
  294. package/skills/docs/runbook-workflow.md +1 -0
  295. package/skills/docs/strategy-workflow.md +1 -0
  296. package/skills/docs/user-research-workflow.md +40 -0
  297. package/skills/operating/orchestration-reference.md +1 -1
  298. package/skills/roles/architect.md +5 -0
  299. package/skills/roles/operator.docs.md +4 -0
  300. package/skills/routing.md +4 -2
  301. package/specialists/artifact-manifest.json +480 -0
  302. package/specialists/artifact-manifest.schema.json +53 -0
  303. package/specialists/audit-enrichments.json +454 -0
  304. package/specialists/contracts.json +37 -25
  305. package/specialists/prompts/_shared/validation-contract.md +26 -0
  306. package/specialists/prompts/cx-accessibility.md +21 -2
  307. package/specialists/prompts/cx-ai-engineer.md +24 -1
  308. package/specialists/prompts/cx-architect.md +4 -1
  309. package/specialists/prompts/cx-business-strategist.md +23 -2
  310. package/specialists/prompts/cx-data-analyst.md +22 -1
  311. package/specialists/prompts/cx-data-engineer.md +23 -2
  312. package/specialists/prompts/cx-debugger.md +21 -2
  313. package/specialists/prompts/cx-designer.md +26 -3
  314. package/specialists/prompts/cx-devil-advocate.md +19 -2
  315. package/specialists/prompts/cx-docs-keeper.md +28 -1
  316. package/specialists/prompts/cx-engineer.md +22 -1
  317. package/specialists/prompts/cx-evaluator.md +18 -1
  318. package/specialists/prompts/cx-explorer.md +21 -2
  319. package/specialists/prompts/cx-legal-compliance.md +21 -2
  320. package/specialists/prompts/cx-operations.md +21 -2
  321. package/specialists/prompts/cx-oracle.md +94 -0
  322. package/specialists/prompts/cx-orchestrator.md +20 -1
  323. package/specialists/prompts/cx-platform-engineer.md +25 -2
  324. package/specialists/prompts/cx-product-manager.md +32 -1
  325. package/specialists/prompts/cx-qa.md +24 -2
  326. package/specialists/prompts/cx-rd-lead.md +23 -2
  327. package/specialists/prompts/cx-release-manager.md +23 -2
  328. package/specialists/prompts/cx-researcher.md +22 -2
  329. package/specialists/prompts/cx-reviewer.md +18 -1
  330. package/specialists/prompts/cx-security.md +22 -2
  331. package/specialists/prompts/cx-sre.md +30 -3
  332. package/specialists/prompts/cx-test-automation.md +7 -1
  333. package/specialists/prompts/cx-trace-reviewer.md +22 -3
  334. package/specialists/prompts/cx-ux-researcher.md +21 -2
  335. package/specialists/registry.json +52 -173
  336. package/specialists/tone-profiles.json +42 -0
  337. package/templates/demos/playwright/demo-recording.config.mjs +47 -0
  338. package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
  339. package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
  340. package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
  341. package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
  342. package/templates/demos/tapes/resource-guard-rails.tape +49 -0
  343. package/templates/demos/vhs/construct-cockpit.json +24 -0
  344. package/templates/distribution/construct-brand.typ +446 -0
  345. package/templates/distribution/construct-decision.typ +38 -0
  346. package/templates/distribution/construct-deck.html +95 -0
  347. package/templates/distribution/construct-pdf.typ +38 -0
  348. package/templates/distribution/construct-prd.typ +38 -0
  349. package/templates/distribution/construct-research.typ +38 -0
  350. package/templates/distribution/construct-web.html +92 -0
  351. package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
  352. package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
  353. package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
  354. package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
  355. package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
  356. package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
  357. package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
  358. package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
  359. package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
  360. package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
  361. package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
  362. package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
  363. package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
  364. package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
  365. package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
  366. package/templates/distribution/fonts/README.md +36 -0
  367. package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
  368. package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
  369. package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
  370. package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
  371. package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
  372. package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
  373. package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
  374. package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
  375. package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
  376. package/templates/distribution/icons/activity.svg +15 -0
  377. package/templates/distribution/icons/alert-triangle.svg +17 -0
  378. package/templates/distribution/icons/book-open.svg +16 -0
  379. package/templates/distribution/icons/bot.svg +20 -0
  380. package/templates/distribution/icons/brain.svg +22 -0
  381. package/templates/distribution/icons/circle-check.svg +16 -0
  382. package/templates/distribution/icons/clipboard-check.svg +17 -0
  383. package/templates/distribution/icons/cpu.svg +28 -0
  384. package/templates/distribution/icons/database.svg +17 -0
  385. package/templates/distribution/icons/eye.svg +16 -0
  386. package/templates/distribution/icons/file-text.svg +19 -0
  387. package/templates/distribution/icons/gauge.svg +16 -0
  388. package/templates/distribution/icons/git-branch.svg +17 -0
  389. package/templates/distribution/icons/key.svg +17 -0
  390. package/templates/distribution/icons/layers.svg +17 -0
  391. package/templates/distribution/icons/list-checks.svg +19 -0
  392. package/templates/distribution/icons/lock.svg +16 -0
  393. package/templates/distribution/icons/message-square.svg +15 -0
  394. package/templates/distribution/icons/network.svg +19 -0
  395. package/templates/distribution/icons/route.svg +17 -0
  396. package/templates/distribution/icons/scale.svg +19 -0
  397. package/templates/distribution/icons/search.svg +16 -0
  398. package/templates/distribution/icons/send.svg +16 -0
  399. package/templates/distribution/icons/server.svg +18 -0
  400. package/templates/distribution/icons/shield-check.svg +16 -0
  401. package/templates/distribution/icons/users.svg +18 -0
  402. package/templates/distribution/icons/webhook.svg +17 -0
  403. package/templates/distribution/icons/workflow.svg +17 -0
  404. package/templates/distribution/icons/wrench.svg +15 -0
  405. package/templates/distribution/run.mjs +18 -1
  406. package/templates/docs/adr.md +7 -0
  407. package/templates/docs/construct_guide.md +10 -10
  408. package/templates/docs/customer-profile.md +4 -0
  409. package/templates/docs/one-pager.md +4 -0
  410. package/templates/docs/postmortem.md +31 -0
  411. package/templates/docs/prd-platform.md +19 -0
  412. package/templates/docs/prd.md +15 -0
  413. package/templates/docs/prfaq.md +7 -0
  414. package/templates/docs/qa-strategy.md +103 -0
  415. package/templates/docs/research-brief.md +8 -0
  416. package/templates/docs/rfc-platform.md +12 -0
  417. package/templates/docs/test-plan.md +7 -0
  418. package/lib/ingest/chunker.mjs +0 -94
  419. package/lib/ingest/pipeline.mjs +0 -53
  420. package/lib/ingest/store.mjs +0 -82
  421. package/lib/mode-commands.mjs +0 -122
  422. package/lib/policy/unified-gates.mjs +0 -96
  423. package/lib/profiles/validate-custom.mjs +0 -114
  424. package/lib/services/telemetry-backend.mjs +0 -177
  425. package/lib/storage/fusion.mjs +0 -95
@@ -0,0 +1,89 @@
1
+ /**
2
+ * lib/improvement/store.mjs — durable proposal records for the governed
3
+ * improvement loop (construct-6zga.1.11).
4
+ *
5
+ * Each submission is a versioned JSON file under `.cx/improvement/proposals/`
6
+ * holding the proposal, held-out dataset item, evaluation report, optional
7
+ * specialist trace, governance verdict, and rollout plan. The store is the
8
+ * operator surface's source of truth; the controller never mutates artifacts in
9
+ * place without persisting the new state here.
10
+ */
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+
14
+ export const IMPROVEMENT_STORE_SCHEMA_VERSION = 1;
15
+
16
+ export function improvementDir(projectDir) {
17
+ return path.join(projectDir, '.cx', 'improvement');
18
+ }
19
+
20
+ export function proposalsDir(projectDir) {
21
+ return path.join(improvementDir(projectDir), 'proposals');
22
+ }
23
+
24
+ export function proposalRecordPath(projectDir, id) {
25
+ return path.join(proposalsDir(projectDir), `${id}.json`);
26
+ }
27
+
28
+ export function approversPath(projectDir) {
29
+ return path.join(improvementDir(projectDir), 'approvers.json');
30
+ }
31
+
32
+ function ensureProposalsDir(projectDir) {
33
+ const dir = proposalsDir(projectDir);
34
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
35
+ return dir;
36
+ }
37
+
38
+ export function loadApprovers(projectDir) {
39
+ const file = approversPath(projectDir);
40
+ if (!fs.existsSync(file)) return null;
41
+ try {
42
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
43
+ return Array.isArray(parsed?.identities) ? parsed.identities : null;
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ export function saveRecord(projectDir, record) {
50
+ ensureProposalsDir(projectDir);
51
+ const id = record?.id || record?.proposal?.id;
52
+ if (!id) throw new Error('record id required');
53
+ const now = new Date().toISOString();
54
+ const { projectDir: _omit, ...rest } = record;
55
+ const payload = {
56
+ schemaVersion: IMPROVEMENT_STORE_SCHEMA_VERSION,
57
+ ...rest,
58
+ id,
59
+ updatedAt: now,
60
+ submittedAt: record.submittedAt || now,
61
+ };
62
+ fs.writeFileSync(proposalRecordPath(projectDir, id), JSON.stringify(payload, null, 2), 'utf8');
63
+ return payload;
64
+ }
65
+
66
+ export function loadRecord(projectDir, id) {
67
+ const file = proposalRecordPath(projectDir, id);
68
+ if (!fs.existsSync(file)) return null;
69
+ try {
70
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+
76
+ export function listRecords(projectDir, { state = null } = {}) {
77
+ const dir = proposalsDir(projectDir);
78
+ if (!fs.existsSync(dir)) return [];
79
+ const out = [];
80
+ for (const name of fs.readdirSync(dir)) {
81
+ if (!name.endsWith('.json')) continue;
82
+ const record = loadRecord(projectDir, name.replace(/\.json$/, ''));
83
+ if (!record) continue;
84
+ if (state && record.proposal?.state !== state) continue;
85
+ out.push(record);
86
+ }
87
+ out.sort((a, b) => String(b.updatedAt || '').localeCompare(String(a.updatedAt || '')));
88
+ return out;
89
+ }
@@ -0,0 +1,219 @@
1
+ /**
2
+ * lib/improvement/surface.mjs — operator flow for the governed improvement loop
3
+ * (construct-6zga.1.11).
4
+ *
5
+ * Wires the specialist loop, eval gates, and approval-gated controller into a
6
+ * review → approve → apply path shared by the CLI and dashboard. Submissions
7
+ * persist under `.cx/improvement/`; the controller plans rollouts and records
8
+ * human-performed apply/rollback but never mutates live artifacts itself.
9
+ */
10
+ import os from 'node:os';
11
+
12
+ import {
13
+ governProposal,
14
+ requireApproval,
15
+ planRollout,
16
+ recordApplication,
17
+ recordRollback,
18
+ } from './controller.mjs';
19
+ import { transitionProposal } from './proposal.mjs';
20
+ import { runSpecialistImprovement } from './specialist-loop.mjs';
21
+ import {
22
+ loadApprovers,
23
+ loadRecord,
24
+ listRecords,
25
+ saveRecord,
26
+ } from './store.mjs';
27
+
28
+ export function resolveKnownApprovers(projectDir, env = process.env) {
29
+ const configured = loadApprovers(projectDir);
30
+ if (configured?.length) return [...new Set(configured)];
31
+ const fromEnv = [env.CONSTRUCT_IMPROVEMENT_APPROVER, env.USER, env.LOGNAME].filter(Boolean);
32
+ try {
33
+ fromEnv.push(os.userInfo().username);
34
+ } catch { /* ignore */ }
35
+ return [...new Set(fromEnv.filter((id) => typeof id === 'string' && id))];
36
+ }
37
+
38
+ function mergeGovernance(projectDir, record, governance) {
39
+ const next = { ...record, governance, updatedAt: new Date().toISOString() };
40
+ if (governance?.admissible && record.proposal?.state === 'proposal_ready') {
41
+ const moved = transitionProposal(record.proposal, 'awaiting_approval');
42
+ if (moved.ok) next.proposal = moved.proposal;
43
+ }
44
+ return saveRecord(projectDir, next);
45
+ }
46
+
47
+ export function submitBundle({
48
+ projectDir,
49
+ bundle = {},
50
+ knownApprovers = null,
51
+ resolvedDependencies = [],
52
+ } = {}) {
53
+ const approvers = knownApprovers || resolveKnownApprovers(projectDir);
54
+ const {
55
+ trace = null,
56
+ trigger = null,
57
+ dataset = null,
58
+ evaluationReport = null,
59
+ proposal: directProposal = null,
60
+ approver = null,
61
+ target = 'prompt',
62
+ baselineVersion = null,
63
+ candidateVersion = null,
64
+ heldOutProfiles = [],
65
+ } = bundle;
66
+
67
+ let proposal = directProposal;
68
+ let loopResult = null;
69
+
70
+ if (!proposal && trace) {
71
+ loopResult = runSpecialistImprovement({
72
+ trace,
73
+ trigger,
74
+ target,
75
+ heldOutProfiles,
76
+ baselineVersion,
77
+ candidateVersion,
78
+ approver,
79
+ dataset,
80
+ evaluationReport,
81
+ knownApprovers: approvers,
82
+ resolvedDependencies,
83
+ });
84
+ if (!loopResult.proposed) {
85
+ return { ok: false, stage: 'specialist-loop', loopResult };
86
+ }
87
+ proposal = loopResult.proposal;
88
+ }
89
+
90
+ if (!proposal) return { ok: false, error: 'bundle must include trace or proposal' };
91
+
92
+ const governance = governProposal({
93
+ proposal,
94
+ dataset,
95
+ evaluationReport,
96
+ knownApprovers: approvers,
97
+ resolvedDependencies,
98
+ });
99
+
100
+ const record = saveRecord(projectDir, {
101
+ projectDir,
102
+ id: proposal.id,
103
+ proposal,
104
+ dataset,
105
+ evaluationReport,
106
+ trace,
107
+ trigger,
108
+ loopResult,
109
+ governance,
110
+ });
111
+
112
+ if (governance.admissible) {
113
+ const advanced = mergeGovernance(projectDir, record, governance);
114
+ return { ok: true, record: advanced, governance, loopResult };
115
+ }
116
+
117
+ const failed = transitionProposal(proposal, 'evaluation_failed', {
118
+ terminalReason: governance.refusals?.join(', ') || 'governance-refused',
119
+ });
120
+ const refused = saveRecord(projectDir, {
121
+ ...record,
122
+ proposal: failed.ok ? failed.proposal : proposal,
123
+ governance,
124
+ });
125
+ return { ok: false, stage: governance.stage || 'governance', record: refused, governance, loopResult };
126
+ }
127
+
128
+ export function reviewRecord(projectDir, id, { knownApprovers = null, resolvedDependencies = [] } = {}) {
129
+ const record = loadRecord(projectDir, id);
130
+ if (!record) return { ok: false, error: 'not-found' };
131
+
132
+ const approvers = knownApprovers || resolveKnownApprovers(projectDir);
133
+ const governance = governProposal({
134
+ proposal: record.proposal,
135
+ dataset: record.dataset,
136
+ evaluationReport: record.evaluationReport,
137
+ knownApprovers: approvers,
138
+ resolvedDependencies,
139
+ });
140
+
141
+ const next = mergeGovernance(projectDir, record, governance);
142
+ return { ok: true, record: next, governance };
143
+ }
144
+
145
+ export function listPending(projectDir) {
146
+ return listRecords(projectDir, { state: 'awaiting_approval' });
147
+ }
148
+
149
+ export function approveRecord(projectDir, id, { identity = null, knownApprovers = null, nowIso = null } = {}) {
150
+ const record = loadRecord(projectDir, id);
151
+ if (!record) return { ok: false, error: 'not-found' };
152
+
153
+ const approvers = knownApprovers || resolveKnownApprovers(projectDir);
154
+ const who = identity || record.proposal?.approver?.identity || approvers[0] || null;
155
+ const approved = requireApproval({
156
+ proposal: record.proposal,
157
+ approval: { identity: who, approvedAt: nowIso || new Date().toISOString() },
158
+ knownApprovers: approvers,
159
+ nowIso,
160
+ });
161
+ if (!approved.ok) return approved;
162
+
163
+ const rollout = planRollout({ proposal: approved.proposal });
164
+ const saved = saveRecord(projectDir, {
165
+ ...record,
166
+ projectDir,
167
+ proposal: approved.proposal,
168
+ rolloutPlan: rollout.ok ? rollout.plan : null,
169
+ });
170
+ return { ok: true, record: saved, rollout };
171
+ }
172
+
173
+ export function applyRecord(projectDir, id, { monitor = null, nowIso = null } = {}) {
174
+ const record = loadRecord(projectDir, id);
175
+ if (!record) return { ok: false, error: 'not-found' };
176
+
177
+ const applied = recordApplication({
178
+ proposal: record.proposal,
179
+ monitor: monitor || `monitor-${id}`,
180
+ nowIso: nowIso || new Date().toISOString(),
181
+ });
182
+ if (!applied.ok) return applied;
183
+
184
+ const saved = saveRecord(projectDir, { ...record, projectDir, proposal: applied.proposal });
185
+ return { ok: true, record: saved };
186
+ }
187
+
188
+ export function rollbackRecord(projectDir, id, { reason = 'operator-rollback', nowIso = null } = {}) {
189
+ const record = loadRecord(projectDir, id);
190
+ if (!record) return { ok: false, error: 'not-found' };
191
+
192
+ const rolled = recordRollback({
193
+ proposal: record.proposal,
194
+ reason,
195
+ nowIso: nowIso || new Date().toISOString(),
196
+ });
197
+ if (!rolled.ok) return rolled;
198
+
199
+ const saved = saveRecord(projectDir, { ...record, projectDir, proposal: rolled.proposal });
200
+ return { ok: true, record: saved };
201
+ }
202
+
203
+ export function formatReviewSummary(record, governance = record?.governance) {
204
+ const p = record?.proposal || {};
205
+ const lines = [
206
+ `Proposal ${p.id} · ${p.type} · ${p.state}`,
207
+ `Profiles: ${(p.affectedProfiles || []).join(', ') || '—'}`,
208
+ `Blast radius: ${p.blastRadius}`,
209
+ `Baseline → candidate: ${p.lineage?.baselineVersion} → ${p.lineage?.candidateVersion}`,
210
+ ];
211
+ if (governance) {
212
+ lines.push(`Governance: ${governance.admissible ? 'admissible' : 'refused'} (${governance.stage || '—'})`);
213
+ if (governance.refusals?.length) lines.push(`Refusals: ${governance.refusals.join(', ')}`);
214
+ }
215
+ if (record?.rolloutPlan) {
216
+ lines.push(`Rollout: ${record.rolloutPlan.mode} · ${record.rolloutPlan.steps?.join(' → ')}`);
217
+ }
218
+ return lines.join('\n');
219
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * lib/ingest-tooling.mjs — detect optional binaries for document ingestion.
3
+ *
4
+ * Probes docling venv, whisper-cli, unpdf/mammoth optional deps, and
5
+ * docling-remote URL config for construct tools detect and doctor.
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { spawnSync } from 'node:child_process';
11
+ import { resolveDoclingServeUrl } from './ingest/docling-remote.mjs';
12
+
13
+ function whichBin(name) {
14
+ const cmd = process.platform === 'win32' ? 'where' : 'which';
15
+ const result = spawnSync(cmd, [name], { encoding: 'utf8' });
16
+ if (result.status !== 0) return null;
17
+ return (result.stdout || '').trim().split('\n')[0] || null;
18
+ }
19
+
20
+ export function doclingVenvPath(cwd = process.cwd()) {
21
+ return path.join(cwd, '.cx', 'runtime', 'docling', '.venv');
22
+ }
23
+
24
+ export function detectDoclingSidecar({ cwd = process.cwd() } = {}) {
25
+ const venv = doclingVenvPath(cwd);
26
+ const python = process.platform === 'win32'
27
+ ? path.join(venv, 'Scripts', 'python.exe')
28
+ : path.join(venv, 'bin', 'python');
29
+ const present = fs.existsSync(python);
30
+ return {
31
+ ok: true,
32
+ present,
33
+ venvDir: venv,
34
+ pythonBin: present ? python : null,
35
+ message: present
36
+ ? `Docling venv ready at ${venv}`
37
+ : 'Docling not provisioned — runs on first high-fidelity ingest or construct install --with-docling',
38
+ };
39
+ }
40
+
41
+ export function detectNodeNativeDeps({ repoRoot = process.cwd() } = {}) {
42
+ const unpdf = fs.existsSync(path.join(repoRoot, 'node_modules', 'unpdf'));
43
+ const mammoth = fs.existsSync(path.join(repoRoot, 'node_modules', 'mammoth'));
44
+ const present = unpdf && mammoth;
45
+ return {
46
+ ok: true,
47
+ present,
48
+ unpdf,
49
+ mammoth,
50
+ message: present
51
+ ? 'Node-native extraction ready (unpdf + mammoth)'
52
+ : `Missing optional deps: ${[!unpdf && 'unpdf', !mammoth && 'mammoth'].filter(Boolean).join(', ') || 'none'}`,
53
+ };
54
+ }
55
+
56
+ export function detectWhisper(env = process.env) {
57
+ const cli = whichBin('whisper-cli') || whichBin('whisper');
58
+ return {
59
+ ok: true,
60
+ present: Boolean(cli),
61
+ path: cli,
62
+ message: cli
63
+ ? `ASR ready (${cli})`
64
+ : 'Install whisper-cpp for audio/video ingest (brew install whisper-cpp on macOS)',
65
+ };
66
+ }
67
+
68
+ export function detectDoclingRemote(env = process.env) {
69
+ const url = resolveDoclingServeUrl(env);
70
+ return {
71
+ ok: true,
72
+ present: Boolean(url),
73
+ url: url || null,
74
+ message: url
75
+ ? `docling-remote configured (${url})`
76
+ : 'Set DOCLING_SERVE_URL for docling-remote ingest strategy',
77
+ };
78
+ }
79
+
80
+ export function detectIngestPipeline({ cwd = process.cwd(), env = process.env, repoRoot = cwd } = {}) {
81
+ const steps = {
82
+ docling: detectDoclingSidecar({ cwd }),
83
+ nodeNative: detectNodeNativeDeps({ repoRoot }),
84
+ whisper: detectWhisper(env),
85
+ doclingRemote: detectDoclingRemote(env),
86
+ };
87
+ return {
88
+ ok: true,
89
+ present: steps.docling.present || steps.nodeNative.present,
90
+ steps,
91
+ message: steps.docling.present
92
+ ? 'Ingest: high-fidelity docling ready'
93
+ : steps.nodeNative.present
94
+ ? 'Ingest: fast tier ready (unpdf/mammoth); high-fidelity provisions on first use'
95
+ : 'Ingest degraded — install optional deps or run construct install --with-docling',
96
+ };
97
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * lib/init/doc-lanes.mjs — shared documentation lane definitions for init flows.
3
+ *
4
+ * Single source for DOC_LANES, presets, lane order, and alias normalization used
5
+ * by both `construct init` (init-unified) and the legacy init-docs entrypoint.
6
+ */
7
+
8
+ export const DOC_LANES = {
9
+ adrs: {
10
+ title: 'ADRs',
11
+ dir: 'adr',
12
+ description: 'Architecture decision records for decisions that have already been made.',
13
+ templates: ['adr.md'],
14
+ },
15
+ briefs: {
16
+ title: 'Briefs',
17
+ dir: 'briefs',
18
+ description: 'Research, evidence, signal, and one-pager style documents.',
19
+ templates: [
20
+ 'research-brief.md',
21
+ 'evidence-brief.md',
22
+ 'signal-brief.md',
23
+ 'one-pager.md',
24
+ 'customer-profile.md',
25
+ 'product-intelligence-report.md',
26
+ 'backlog-proposal.md',
27
+ ],
28
+ },
29
+ changelogs: {
30
+ title: 'Changelogs',
31
+ dir: 'changelogs',
32
+ description: 'User-facing release notes and version history entries.',
33
+ templates: ['changelog-entry.md'],
34
+ },
35
+ intake: {
36
+ title: 'Intake',
37
+ dir: 'intake',
38
+ description: 'Intake batch records that explain what arrived, why it matters, and how it should be ingested.',
39
+ templates: ['__intake-template__'],
40
+ },
41
+ memos: {
42
+ title: 'Memos',
43
+ dir: 'memos',
44
+ description: 'Decision memos and internal arguments for alignment and approval.',
45
+ templates: ['memo.md'],
46
+ },
47
+ meetings: {
48
+ title: 'Meetings',
49
+ dir: 'meetings',
50
+ description: 'Meeting notes, minutes, retros, standups, planning sessions, and agendas.',
51
+ templates: ['__meeting-notes-template__'],
52
+ },
53
+ notes: {
54
+ title: 'Notes',
55
+ dir: 'notes',
56
+ description: 'Working notes and lightweight durable context outside formal docs or meetings.',
57
+ templates: ['__notes-template__'],
58
+ },
59
+ onboarding: {
60
+ title: 'Onboarding',
61
+ dir: 'onboarding',
62
+ description: 'Runnable setup guides and first-day workflows for engineers, product, or ops.',
63
+ templates: ['onboarding.md'],
64
+ },
65
+ postmortems: {
66
+ title: 'Postmortems',
67
+ dir: 'postmortems',
68
+ description: 'Blameless incident reports: timeline, root cause, contributing factors, and corrective actions.',
69
+ templates: ['incident-report.md'],
70
+ },
71
+ prds: {
72
+ title: 'PRDs',
73
+ dir: 'prds',
74
+ description: 'Product and capability requirement documents.',
75
+ templates: ['prd.md', 'meta-prd.md', 'prd-business.md', 'prd-platform.md', 'prfaq.md'],
76
+ },
77
+ rfcs: {
78
+ title: 'RFCs',
79
+ dir: 'rfcs',
80
+ description: 'Architecture and implementation proposals that need review before a decision.',
81
+ templates: ['rfc.md', 'rfc-platform.md'],
82
+ },
83
+ runbooks: {
84
+ title: 'Runbooks',
85
+ dir: 'runbooks',
86
+ description: 'Operational procedures, diagnostics, remediation, and escalation paths.',
87
+ templates: ['runbook.md'],
88
+ },
89
+ };
90
+
91
+ export const LANE_ORDER = ['adrs', 'briefs', 'changelogs', 'intake', 'memos', 'meetings', 'notes', 'onboarding', 'postmortems', 'prds', 'rfcs', 'runbooks'];
92
+
93
+ export const DOC_PRESETS = {
94
+ lean: ['adrs', 'intake', 'memos', 'meetings', 'notes', 'prds'],
95
+ product: ['adrs', 'intake', 'memos', 'meetings', 'notes', 'prds', 'rfcs'],
96
+ full: LANE_ORDER,
97
+ };
98
+
99
+ export const DEFAULT_LANES = DOC_PRESETS.lean;
100
+
101
+ export const LANE_ALIASES = {
102
+ adr: 'adrs',
103
+ adrs: 'adrs',
104
+ brief: 'briefs',
105
+ briefs: 'briefs',
106
+ changelog: 'changelogs',
107
+ changelogs: 'changelogs',
108
+ releases: 'changelogs',
109
+ release: 'changelogs',
110
+ intake: 'intake',
111
+ memo: 'memos',
112
+ memos: 'memos',
113
+ meeting: 'meetings',
114
+ meetings: 'meetings',
115
+ minutes: 'meetings',
116
+ retro: 'meetings',
117
+ note: 'notes',
118
+ notes: 'notes',
119
+ onboard: 'onboarding',
120
+ onboarding: 'onboarding',
121
+ postmortem: 'postmortems',
122
+ postmortems: 'postmortems',
123
+ incident: 'postmortems',
124
+ incidents: 'postmortems',
125
+ prd: 'prds',
126
+ prds: 'prds',
127
+ rfc: 'rfcs',
128
+ rfcs: 'rfcs',
129
+ runbook: 'runbooks',
130
+ runbooks: 'runbooks',
131
+ };
132
+
133
+ export function normalizeLaneKey(name) {
134
+ return LANE_ALIASES[name.trim().toLowerCase()] ?? name.trim().toLowerCase();
135
+ }
136
+
137
+ export function normalizeCustomLaneName(name) {
138
+ return name
139
+ .trim()
140
+ .toLowerCase()
141
+ .replace(/[^a-z0-9]+/g, '-')
142
+ .replace(/^-+|-+$/g, '');
143
+ }
144
+
145
+ export function parseCsvList(value) {
146
+ return value
147
+ .split(',')
148
+ .map((entry) => entry.trim())
149
+ .filter(Boolean);
150
+ }
151
+
152
+ export function parseSelectableLanes(value) {
153
+ return value
154
+ .split(/[,\n]/)
155
+ .map((entry) => entry.trim())
156
+ .filter(Boolean)
157
+ .map((entry) => {
158
+ if (/^\d+$/.test(entry)) {
159
+ const lane = LANE_ORDER[Number(entry) - 1];
160
+ return lane ?? '';
161
+ }
162
+ return normalizeLaneKey(entry);
163
+ })
164
+ .filter((lane) => lane in DOC_LANES);
165
+ }