@geraldmaron/construct 1.1.1 → 1.2.1

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 (426) hide show
  1. package/README.md +30 -19
  2. package/bin/construct +487 -87
  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 +258 -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/artifact-workflow.mjs +252 -0
  20. package/lib/audit-skills.mjs +4 -0
  21. package/lib/audit-specialists.mjs +285 -0
  22. package/lib/audit-trail.mjs +1 -1
  23. package/lib/auto-docs.mjs +90 -19
  24. package/lib/beads/drift.mjs +27 -6
  25. package/lib/beads-client.mjs +49 -121
  26. package/lib/beads-optimistic.mjs +5 -12
  27. package/lib/brand-fonts.mjs +93 -0
  28. package/lib/brand-prose.mjs +214 -0
  29. package/lib/brand-tokens.mjs +92 -0
  30. package/lib/bridges/copilot-proxy.mjs +13 -26
  31. package/lib/capability-ledger.mjs +156 -0
  32. package/lib/certification/artifact-fixtures.mjs +132 -0
  33. package/lib/certification/artifact-gates.mjs +63 -0
  34. package/lib/certification/artifact-provenance.mjs +97 -0
  35. package/lib/certification/canonical-scenarios.mjs +78 -0
  36. package/lib/certification/cli.mjs +191 -0
  37. package/lib/certification/dashboard-api.mjs +71 -0
  38. package/lib/certification/demo-parity.mjs +116 -0
  39. package/lib/certification/document-io-fixtures.mjs +246 -0
  40. package/lib/certification/document-workflow.mjs +97 -0
  41. package/lib/certification/eval-bridge.mjs +77 -0
  42. package/lib/certification/model-routing.mjs +149 -0
  43. package/lib/certification/prompt-budget.mjs +119 -0
  44. package/lib/certification/rc-gate.mjs +206 -0
  45. package/lib/certification/real-llm-scenarios.mjs +303 -0
  46. package/lib/certification/role-cards.mjs +113 -0
  47. package/lib/certification/role-overlays.mjs +117 -0
  48. package/lib/certification/run.mjs +122 -0
  49. package/lib/certification/runner.mjs +323 -0
  50. package/lib/certification/scenarios.mjs +51 -0
  51. package/lib/certification/skill-inventory.mjs +289 -0
  52. package/lib/certification/skill-scenarios.mjs +147 -0
  53. package/lib/certification/specialist-contracts.mjs +85 -0
  54. package/lib/certification/specialist-scenarios.mjs +175 -0
  55. package/lib/certification/stale-impact.mjs +146 -0
  56. package/lib/certification/status.mjs +252 -0
  57. package/lib/certification/store.mjs +77 -0
  58. package/lib/chat/cli.mjs +333 -0
  59. package/lib/chat/command-suggest.mjs +161 -0
  60. package/lib/chat/commands.mjs +215 -0
  61. package/lib/chat/config.mjs +142 -0
  62. package/lib/chat/context-compactor.mjs +250 -0
  63. package/lib/chat/context-continuation.mjs +253 -0
  64. package/lib/chat/continuation-source.mjs +58 -0
  65. package/lib/chat/demo-guide.mjs +61 -0
  66. package/lib/chat/design-tokens.mjs +91 -0
  67. package/lib/chat/desktop-binary.mjs +79 -0
  68. package/lib/chat/desktop-build.mjs +130 -0
  69. package/lib/chat/desktop-launcher.mjs +133 -0
  70. package/lib/chat/evidence.mjs +145 -0
  71. package/lib/chat/export.mjs +74 -0
  72. package/lib/chat/harness/driver.mjs +91 -0
  73. package/lib/chat/list-picker.mjs +112 -0
  74. package/lib/chat/model-picker.mjs +356 -0
  75. package/lib/chat/openrouter-fallback.mjs +151 -0
  76. package/lib/chat/permission-prompt.mjs +33 -0
  77. package/lib/chat/picker-catalog.mjs +45 -0
  78. package/lib/chat/policy-telemetry.mjs +34 -0
  79. package/lib/chat/present.mjs +246 -0
  80. package/lib/chat/session-context.mjs +39 -0
  81. package/lib/chat/session-persist.mjs +73 -0
  82. package/lib/chat/session-restore.mjs +71 -0
  83. package/lib/chat/session-settings.mjs +53 -0
  84. package/lib/chat/system-prompt.mjs +52 -0
  85. package/lib/chat/transparency.mjs +93 -0
  86. package/lib/chat/tui/color-scheme.mjs +42 -0
  87. package/lib/chat/tui/markdown.mjs +123 -0
  88. package/lib/chat/tui/presentation.mjs +100 -0
  89. package/lib/chat/tui/render.mjs +500 -0
  90. package/lib/chat/tui/turn-block.mjs +284 -0
  91. package/lib/chat/tui/turn-present.mjs +18 -0
  92. package/lib/chat/tui/turn-state.mjs +88 -0
  93. package/lib/chat/tui/usage.mjs +122 -0
  94. package/lib/chat/web-commands.mjs +146 -0
  95. package/lib/chat/web-launcher.mjs +63 -0
  96. package/lib/chat/web-picker-keys.mjs +46 -0
  97. package/lib/chat/web-session.mjs +159 -0
  98. package/lib/cli-commands.mjs +146 -15
  99. package/lib/cli-service-inventory.mjs +42 -0
  100. package/lib/comment-lint.mjs +6 -4
  101. package/lib/config/intake-policy.mjs +209 -0
  102. package/lib/config/project-config.mjs +11 -2
  103. package/lib/config/schema.mjs +86 -6
  104. package/lib/config/source-targets.mjs +311 -0
  105. package/lib/contract-schemas/decision.json +50 -0
  106. package/lib/contract-schemas/implementation.json +51 -0
  107. package/lib/contract-schemas/review-report.json +32 -0
  108. package/lib/contract-schemas/test-report.json +43 -0
  109. package/lib/contracts/construct-handoff.mjs +60 -0
  110. package/lib/contracts/validate.mjs +32 -3
  111. package/lib/contracts/violation-log.mjs +58 -5
  112. package/lib/dashboard-demo.mjs +71 -0
  113. package/lib/dashboard-static.mjs +19 -5
  114. package/lib/decisions/registry.mjs +5 -3
  115. package/lib/deck-export-pptx.mjs +1152 -0
  116. package/lib/demo-recording.mjs +142 -0
  117. package/lib/demo-script.mjs +114 -0
  118. package/lib/demo-surface.mjs +249 -0
  119. package/lib/demo.mjs +598 -140
  120. package/lib/diagram-export.mjs +192 -0
  121. package/lib/diagram.mjs +13 -11
  122. package/lib/docs-verify.mjs +25 -7
  123. package/lib/doctor/index.mjs +3 -1
  124. package/lib/doctor/project-adapters.mjs +44 -0
  125. package/lib/doctor/source-checkout.mjs +16 -0
  126. package/lib/doctor/watchers/cx-budget.mjs +98 -0
  127. package/lib/doctor/watchers/graph-staleness.mjs +52 -0
  128. package/lib/doctor/watchers/process-pressure.mjs +4 -3
  129. package/lib/document-export.mjs +384 -38
  130. package/lib/document-extract.mjs +44 -4
  131. package/lib/document-ingest.mjs +29 -20
  132. package/lib/embed/auto-sources.mjs +44 -0
  133. package/lib/embed/cli.mjs +4 -3
  134. package/lib/embed/daemon.mjs +18 -46
  135. package/lib/embed/demand-fetch.mjs +50 -19
  136. package/lib/embed/inbox.mjs +6 -10
  137. package/lib/embed/worker.mjs +4 -3
  138. package/lib/embedded-contract/index.mjs +11 -0
  139. package/lib/embedded-contract/model-resolve.mjs +10 -9
  140. package/lib/env-config.mjs +26 -0
  141. package/lib/evals/dataset.mjs +137 -0
  142. package/lib/evals/gates.mjs +175 -0
  143. package/lib/export-branding.mjs +32 -0
  144. package/lib/graph/build-co-change.mjs +54 -0
  145. package/lib/graph/build-from-registry.mjs +136 -0
  146. package/lib/graph/build-import-graph.mjs +153 -0
  147. package/lib/graph/cli.mjs +110 -0
  148. package/lib/graph/impact-cli.mjs +89 -0
  149. package/lib/graph/impact.mjs +108 -0
  150. package/lib/graph/staleness.mjs +44 -0
  151. package/lib/graph/store.mjs +172 -0
  152. package/lib/health-check.mjs +90 -76
  153. package/lib/hooks/artifact-release-gate.mjs +43 -0
  154. package/lib/hooks/brand-prose-lint.mjs +38 -0
  155. package/lib/hooks/graph-impact-advisory.mjs +62 -0
  156. package/lib/hooks/session-optimize.mjs +84 -207
  157. package/lib/hooks/session-start.mjs +18 -16
  158. package/lib/host-disposition.mjs +7 -0
  159. package/lib/improvement/cli.mjs +189 -0
  160. package/lib/improvement/controller.mjs +137 -0
  161. package/lib/improvement/proposal.mjs +120 -0
  162. package/lib/improvement/specialist-loop.mjs +192 -0
  163. package/lib/improvement/store.mjs +89 -0
  164. package/lib/improvement/surface.mjs +219 -0
  165. package/lib/ingest-tooling.mjs +97 -0
  166. package/lib/init/doc-lanes.mjs +165 -0
  167. package/lib/init-docs.mjs +31 -178
  168. package/lib/init-unified.mjs +50 -119
  169. package/lib/init-update-guide.mjs +102 -0
  170. package/lib/init-update.mjs +32 -1
  171. package/lib/init.mjs +8 -0
  172. package/lib/install/desktop-binary-download.mjs +85 -0
  173. package/lib/install/legacy-global-cleanup.mjs +189 -0
  174. package/lib/intake/constants.mjs +29 -0
  175. package/lib/intake/daemon.mjs +2 -0
  176. package/lib/intake/git-queue.mjs +9 -8
  177. package/lib/intake/intake-config.mjs +52 -105
  178. package/lib/intake/legacy-paths.mjs +5 -0
  179. package/lib/intake/queue.mjs +2 -1
  180. package/lib/intake/session-prelude.mjs +90 -1
  181. package/lib/libreoffice-export.mjs +97 -0
  182. package/lib/logging/rotate.mjs +9 -0
  183. package/lib/maintenance/docker-reclaim.mjs +206 -0
  184. package/lib/mcp/external-schema-cost.mjs +74 -0
  185. package/lib/mcp/server.mjs +103 -10
  186. package/lib/mcp/stdio-mcp-probe.mjs +188 -0
  187. package/lib/mcp/tool-budget.mjs +55 -4
  188. package/lib/mcp/tools/document.mjs +18 -4
  189. package/lib/mcp/tools/embedded-contract.mjs +14 -0
  190. package/lib/mcp/tools/skills.mjs +32 -3
  191. package/lib/mcp/tools/workflow.mjs +13 -1
  192. package/lib/model-free-selector.mjs +18 -0
  193. package/lib/model-registry.mjs +13 -229
  194. package/lib/model-router.mjs +373 -92
  195. package/lib/models/behavior-matrix.mjs +289 -0
  196. package/lib/models/catalog.mjs +209 -0
  197. package/lib/models/execution-capability-profile.mjs +196 -0
  198. package/lib/models/execution-policy.mjs +307 -0
  199. package/lib/models/provider-poll.mjs +383 -0
  200. package/lib/npm-spawn-env.mjs +17 -0
  201. package/lib/ollama/installed-models.mjs +129 -0
  202. package/lib/oracle/actions.mjs +187 -0
  203. package/lib/oracle/artifact-gate.mjs +99 -0
  204. package/lib/oracle/cli.mjs +204 -0
  205. package/lib/oracle/daemon-entry.mjs +14 -0
  206. package/lib/oracle/dispatch.mjs +81 -0
  207. package/lib/oracle/execute.mjs +143 -0
  208. package/lib/oracle/gaps.mjs +76 -0
  209. package/lib/oracle/index.mjs +84 -0
  210. package/lib/oracle/issues.mjs +164 -0
  211. package/lib/oracle/org-graph.mjs +170 -0
  212. package/lib/oracle/policy.mjs +80 -0
  213. package/lib/oracle/read-model.mjs +398 -0
  214. package/lib/oracle/reconcile.mjs +191 -0
  215. package/lib/oracle/routing.mjs +89 -0
  216. package/lib/oracle/synthesize.mjs +384 -0
  217. package/lib/oracle/verdicts.mjs +51 -0
  218. package/lib/orchestration/worker.mjs +88 -27
  219. package/lib/orchestration-policy.mjs +50 -0
  220. package/lib/parity.mjs +96 -2
  221. package/lib/playwright-demo.mjs +254 -0
  222. package/lib/project-init-shared.mjs +4 -1
  223. package/lib/prompt-composer.js +7 -3
  224. package/lib/prompt-validation-contract.mjs +24 -0
  225. package/lib/provider-capabilities.js +57 -11
  226. package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
  227. package/lib/providers/contract/adapters/git/index.mjs +115 -0
  228. package/lib/providers/contract/adapters/github/index.mjs +166 -0
  229. package/lib/providers/contract/adapters/jira/index.mjs +187 -0
  230. package/lib/providers/contract/adapters/slack/index.mjs +175 -0
  231. package/lib/providers/contract/contract-tests.mjs +57 -0
  232. package/lib/providers/contract/errors.mjs +48 -0
  233. package/lib/providers/contract/interface.mjs +50 -0
  234. package/lib/providers/contract/registry.mjs +102 -0
  235. package/lib/providers/copilot-auth.mjs +297 -0
  236. package/lib/providers/credential-bootstrap.mjs +178 -0
  237. package/lib/providers/credential-catalog.mjs +46 -0
  238. package/lib/providers/credential-sources.mjs +63 -0
  239. package/lib/providers/creds.mjs +5 -2
  240. package/lib/providers/op-run.mjs +59 -0
  241. package/lib/providers/secret-resolver.mjs +159 -0
  242. package/lib/publish-template.mjs +163 -0
  243. package/lib/publish-tooling.mjs +119 -0
  244. package/lib/publish.mjs +305 -0
  245. package/lib/registry/cli.mjs +82 -0
  246. package/lib/registry/consolidation.mjs +147 -0
  247. package/lib/registry/generate-docs.mjs +75 -0
  248. package/lib/registry/skill-verification.mjs +57 -0
  249. package/lib/registry/surface-map.mjs +76 -0
  250. package/lib/registry/validate.mjs +135 -0
  251. package/lib/resources/budget.mjs +82 -0
  252. package/lib/resources/process-budget.mjs +45 -0
  253. package/lib/rules-delivery.mjs +9 -2
  254. package/lib/rules-read.mjs +26 -0
  255. package/lib/runtime-env.mjs +23 -0
  256. package/lib/runtime-pressure.mjs +51 -7
  257. package/lib/schema-infer.mjs +13 -2
  258. package/lib/server/chat-loop.mjs +622 -0
  259. package/lib/server/demo-preview.mjs +63 -0
  260. package/lib/server/index.mjs +259 -9
  261. package/lib/server/rate-limit.mjs +5 -3
  262. package/lib/server/webhook.mjs +3 -2
  263. package/lib/service-manager.mjs +60 -8
  264. package/lib/setup.mjs +70 -7
  265. package/lib/specialists/prompt-schema.mjs +19 -10
  266. package/lib/specialists/roster.mjs +43 -0
  267. package/lib/specialists/scaffold.mjs +56 -0
  268. package/lib/storage/backend.mjs +6 -6
  269. package/lib/storage/hybrid-query.mjs +7 -4
  270. package/lib/storage/state-source.mjs +5 -5
  271. package/lib/storage/sync.mjs +2 -3
  272. package/lib/telemetry/rule-calls.mjs +52 -0
  273. package/lib/template-registry.mjs +2 -2
  274. package/lib/templates/visual-requirements.mjs +27 -51
  275. package/lib/test-corpus-inventory.mjs +313 -0
  276. package/lib/uninstall/uninstall.mjs +19 -7
  277. package/lib/update.mjs +12 -0
  278. package/lib/upgrade.mjs +14 -0
  279. package/lib/wireframe.mjs +20 -14
  280. package/lib/worker/run.mjs +17 -4
  281. package/lib/worker/trace.mjs +11 -3
  282. package/package.json +28 -14
  283. package/personas/construct.md +2 -2
  284. package/platforms/claude/settings.template.json +36 -0
  285. package/rules/common/patterns.md +1 -1
  286. package/rules/common/release-gates.md +4 -3
  287. package/scripts/sync-specialists.mjs +41 -28
  288. package/skills/devops/data-engineering.md +1 -1
  289. package/skills/docs/adr-workflow.md +1 -0
  290. package/skills/docs/backlog-proposal-workflow.md +1 -0
  291. package/skills/docs/codebase-research-workflow.md +40 -0
  292. package/skills/docs/customer-profile-workflow.md +1 -0
  293. package/skills/docs/document-ingest-workflow.md +1 -0
  294. package/skills/docs/evidence-ingest-workflow.md +1 -0
  295. package/skills/docs/init-docs.md +2 -2
  296. package/skills/docs/init-project.md +7 -2
  297. package/skills/docs/prd-workflow.md +24 -1
  298. package/skills/docs/prfaq-workflow.md +1 -0
  299. package/skills/docs/product-intelligence-workflow.md +1 -0
  300. package/skills/docs/product-signal-workflow.md +1 -0
  301. package/skills/docs/research-workflow.md +54 -37
  302. package/skills/docs/runbook-workflow.md +1 -0
  303. package/skills/docs/strategy-workflow.md +1 -0
  304. package/skills/docs/user-research-workflow.md +40 -0
  305. package/skills/operating/orchestration-reference.md +1 -1
  306. package/skills/roles/architect.md +5 -0
  307. package/skills/roles/operator.docs.md +4 -0
  308. package/skills/routing.md +4 -2
  309. package/specialists/artifact-manifest.json +489 -0
  310. package/specialists/artifact-manifest.schema.json +83 -0
  311. package/specialists/audit-enrichments.json +454 -0
  312. package/specialists/contracts.json +37 -25
  313. package/specialists/prompts/_shared/validation-contract.md +26 -0
  314. package/specialists/prompts/cx-accessibility.md +21 -2
  315. package/specialists/prompts/cx-ai-engineer.md +24 -1
  316. package/specialists/prompts/cx-architect.md +4 -1
  317. package/specialists/prompts/cx-business-strategist.md +23 -2
  318. package/specialists/prompts/cx-data-analyst.md +22 -1
  319. package/specialists/prompts/cx-data-engineer.md +23 -2
  320. package/specialists/prompts/cx-debugger.md +21 -2
  321. package/specialists/prompts/cx-designer.md +26 -3
  322. package/specialists/prompts/cx-devil-advocate.md +19 -2
  323. package/specialists/prompts/cx-docs-keeper.md +28 -1
  324. package/specialists/prompts/cx-engineer.md +22 -1
  325. package/specialists/prompts/cx-evaluator.md +18 -1
  326. package/specialists/prompts/cx-explorer.md +21 -2
  327. package/specialists/prompts/cx-legal-compliance.md +21 -2
  328. package/specialists/prompts/cx-operations.md +21 -2
  329. package/specialists/prompts/cx-oracle.md +94 -0
  330. package/specialists/prompts/cx-orchestrator.md +20 -1
  331. package/specialists/prompts/cx-platform-engineer.md +25 -2
  332. package/specialists/prompts/cx-product-manager.md +32 -1
  333. package/specialists/prompts/cx-qa.md +24 -2
  334. package/specialists/prompts/cx-rd-lead.md +23 -2
  335. package/specialists/prompts/cx-release-manager.md +23 -2
  336. package/specialists/prompts/cx-researcher.md +22 -2
  337. package/specialists/prompts/cx-reviewer.md +18 -1
  338. package/specialists/prompts/cx-security.md +22 -2
  339. package/specialists/prompts/cx-sre.md +30 -3
  340. package/specialists/prompts/cx-test-automation.md +7 -1
  341. package/specialists/prompts/cx-trace-reviewer.md +22 -3
  342. package/specialists/prompts/cx-ux-researcher.md +21 -2
  343. package/specialists/registry.json +52 -173
  344. package/specialists/tone-profiles.json +42 -0
  345. package/templates/demos/playwright/demo-recording.config.mjs +47 -0
  346. package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
  347. package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
  348. package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
  349. package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
  350. package/templates/demos/tapes/resource-guard-rails.tape +49 -0
  351. package/templates/demos/vhs/construct-cockpit.json +24 -0
  352. package/templates/distribution/construct-brand.typ +446 -0
  353. package/templates/distribution/construct-decision.typ +38 -0
  354. package/templates/distribution/construct-deck.html +95 -0
  355. package/templates/distribution/construct-pdf.typ +38 -0
  356. package/templates/distribution/construct-prd.typ +38 -0
  357. package/templates/distribution/construct-reference.docx +0 -0
  358. package/templates/distribution/construct-research.typ +38 -0
  359. package/templates/distribution/construct-web.html +92 -0
  360. package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
  361. package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
  362. package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
  363. package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
  364. package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
  365. package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
  366. package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
  367. package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
  368. package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
  369. package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
  370. package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
  371. package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
  372. package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
  373. package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
  374. package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
  375. package/templates/distribution/fonts/README.md +36 -0
  376. package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
  377. package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
  378. package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
  379. package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
  380. package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
  381. package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
  382. package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
  383. package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
  384. package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
  385. package/templates/distribution/icons/activity.svg +15 -0
  386. package/templates/distribution/icons/alert-triangle.svg +17 -0
  387. package/templates/distribution/icons/book-open.svg +16 -0
  388. package/templates/distribution/icons/bot.svg +20 -0
  389. package/templates/distribution/icons/brain.svg +22 -0
  390. package/templates/distribution/icons/circle-check.svg +16 -0
  391. package/templates/distribution/icons/clipboard-check.svg +17 -0
  392. package/templates/distribution/icons/cpu.svg +28 -0
  393. package/templates/distribution/icons/database.svg +17 -0
  394. package/templates/distribution/icons/eye.svg +16 -0
  395. package/templates/distribution/icons/file-text.svg +19 -0
  396. package/templates/distribution/icons/gauge.svg +16 -0
  397. package/templates/distribution/icons/git-branch.svg +17 -0
  398. package/templates/distribution/icons/key.svg +17 -0
  399. package/templates/distribution/icons/layers.svg +17 -0
  400. package/templates/distribution/icons/list-checks.svg +19 -0
  401. package/templates/distribution/icons/lock.svg +16 -0
  402. package/templates/distribution/icons/message-square.svg +15 -0
  403. package/templates/distribution/icons/network.svg +19 -0
  404. package/templates/distribution/icons/route.svg +17 -0
  405. package/templates/distribution/icons/scale.svg +19 -0
  406. package/templates/distribution/icons/search.svg +16 -0
  407. package/templates/distribution/icons/send.svg +16 -0
  408. package/templates/distribution/icons/server.svg +18 -0
  409. package/templates/distribution/icons/shield-check.svg +16 -0
  410. package/templates/distribution/icons/users.svg +18 -0
  411. package/templates/distribution/icons/webhook.svg +17 -0
  412. package/templates/distribution/icons/workflow.svg +17 -0
  413. package/templates/distribution/icons/wrench.svg +15 -0
  414. package/templates/distribution/run.mjs +18 -1
  415. package/templates/docs/adr.md +7 -0
  416. package/templates/docs/construct_guide.md +8 -8
  417. package/templates/docs/customer-profile.md +4 -0
  418. package/templates/docs/one-pager.md +4 -0
  419. package/templates/docs/postmortem.md +31 -0
  420. package/templates/docs/prd-platform.md +19 -0
  421. package/templates/docs/prd.md +15 -0
  422. package/templates/docs/prfaq.md +7 -0
  423. package/templates/docs/qa-strategy.md +103 -0
  424. package/templates/docs/research-brief.md +8 -0
  425. package/templates/docs/rfc-platform.md +12 -0
  426. package/templates/docs/test-plan.md +7 -0
package/lib/setup.mjs CHANGED
@@ -32,6 +32,9 @@ import { getCanonicalOpenCodeConfigPath, readOpenCodeConfig, writeOpenCodeConfig
32
32
  import { getEmbeddingModelInfo, warmupEmbeddingModel } from './storage/embeddings-engine.mjs';
33
33
  import { buildPressureGuardValues, installPressureGuardLaunchAgent, loadPressureGuardLaunchAgent } from './runtime-pressure.mjs';
34
34
  import { consentToInstall } from './setup-prompts.mjs';
35
+ import { cleanupLegacyGlobalConstruct } from './install/legacy-global-cleanup.mjs';
36
+ import { downloadDesktopBinary } from './install/desktop-binary-download.mjs';
37
+ import { buildDesktopBinary, hasDesktopSource } from './chat/desktop-build.mjs';
35
38
 
36
39
  const ROOT_DIR = path.resolve(import.meta.dirname, '..');
37
40
  const HOME = os.homedir();
@@ -40,7 +43,7 @@ function printHelp() {
40
43
  console.log(`Construct install — machine setup (once per machine)
41
44
 
42
45
  Usage:
43
- construct install [--scope=project|user|both] [--yes] [--dry-run] [--no-launch-agent] [--reconfigure] [--with-docling]
46
+ construct install [--scope=project|user|both] [--yes] [--dry-run] [--no-launch-agent] [--reconfigure] [--with-docling] [--cleanup-legacy-global]
44
47
 
45
48
  Flags:
46
49
  --scope=<s> project | user | both (default: project — see ADR-0029)
@@ -56,6 +59,9 @@ Flags:
56
59
  --with-docling eagerly provision the docling document-extraction venv now
57
60
  (heavy, ~10 min via uv; otherwise provisioned lazily on
58
61
  first document ingest)
62
+ --cleanup-legacy-global
63
+ remove legacy user-global Construct adapters/MCP/history
64
+ before setup; does not uninstall a global construct CLI
59
65
 
60
66
  What --scope=user does:
61
67
  - creates ~/.construct/config.env
@@ -307,14 +313,35 @@ function printProjectScopeGuidance() {
307
313
  console.log(' construct install --scope=both');
308
314
  }
309
315
 
316
+ function runLegacyGlobalCleanup({ homeDir, dryRun }) {
317
+ const result = cleanupLegacyGlobalConstruct({ homeDir, dryRun });
318
+ const action = dryRun ? 'would remove' : 'removed';
319
+ console.log(`Legacy global cleanup: ${result.removed.length ? `${action} ${result.removed.length} item(s)` : 'nothing detected'}`);
320
+ for (const item of result.removed.slice(0, 20)) {
321
+ console.log(` - ${item}`);
322
+ }
323
+ if (result.removed.length > 20) {
324
+ console.log(` - ...and ${result.removed.length - 20} more`);
325
+ }
326
+ return result;
327
+ }
328
+
310
329
  // Dry-run renders the user-scope plan as intent only — every line names a write,
311
330
  // network call, or service registration that the real run would perform — and
312
331
  // returns before touching disk so the preview is provably side-effect-free.
313
332
 
314
- function printInstallPlan({ scope, homeDir, withDocling, noLaunchAgent }) {
333
+ function printInstallPlan({ scope, homeDir, withDocling, noLaunchAgent, cleanupLegacyGlobal }) {
315
334
  console.log('Construct install — dry-run (no changes written)');
316
335
  console.log('────────────────────────────────────────────────');
317
336
  console.log(`Scope: ${scope}`);
337
+ if (cleanupLegacyGlobal) {
338
+ console.log('\nLegacy global cleanup:');
339
+ runLegacyGlobalCleanup({ homeDir, dryRun: true });
340
+ }
341
+ if (scope === 'project') {
342
+ console.log('\nWould not run user-scope setup. Re-run without --dry-run to apply cleanup only.');
343
+ return;
344
+ }
318
345
  if (scope === 'both') {
319
346
  console.log(' · would print project-scope guidance, then run the user-scope plan below');
320
347
  }
@@ -326,6 +353,7 @@ function printInstallPlan({ scope, homeDir, withDocling, noLaunchAgent }) {
326
353
  console.log(` · ${path.dirname(defaultVectorIndexPath(homeDir))} (LanceDB vector store dir)`);
327
354
  console.log('\nWould check / install runtime tooling:');
328
355
  console.log(' · cm (memory CLI) and cass (session search) when available on PATH');
356
+ console.log(' · construct-chat desktop binary (from GitHub releases → ~/.construct/bin/)');
329
357
  console.log('\nWould reach the network / external services:');
330
358
  console.log(' · embedding model warmup (loads from local cache; offline-safe, no download)');
331
359
  if (withDocling) console.log(' · docling Python venv provisioning via uv (~10 min)');
@@ -341,13 +369,14 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
341
369
  const argSet = new Set(args);
342
370
  const isYes = argSet.has('--yes');
343
371
  const reconfigure = argSet.has('--reconfigure');
372
+ const cleanupLegacyGlobal = argSet.has('--cleanup-legacy-global');
344
373
 
345
374
  if (argSet.has('--help') || argSet.has('-h')) {
346
375
  printHelp();
347
376
  return;
348
377
  }
349
378
 
350
- const KNOWN_FLAGS = new Set(['--yes', '--dry-run', '--no-launch-agent', '--reconfigure', '--with-docling', '--help', '-h']);
379
+ const KNOWN_FLAGS = new Set(['--yes', '--dry-run', '--no-launch-agent', '--reconfigure', '--with-docling', '--cleanup-legacy-global', '--help', '-h']);
351
380
  const dryRun = argSet.has('--dry-run');
352
381
  const withDocling = argSet.has('--with-docling');
353
382
  const unknownFlags = args.filter((a) => {
@@ -369,13 +398,25 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
369
398
  }
370
399
  const scope = scopeResult.scope;
371
400
 
372
- if (scope === 'project') {
373
- printProjectScopeGuidance();
401
+ if (dryRun) {
402
+ if (scope === 'project' && !cleanupLegacyGlobal) {
403
+ printProjectScopeGuidance();
404
+ return;
405
+ }
406
+ printInstallPlan({ scope, homeDir, withDocling, noLaunchAgent: argSet.has('--no-launch-agent'), cleanupLegacyGlobal });
374
407
  return;
375
408
  }
376
409
 
377
- if (dryRun) {
378
- printInstallPlan({ scope, homeDir, withDocling, noLaunchAgent: argSet.has('--no-launch-agent') });
410
+ if (cleanupLegacyGlobal) {
411
+ runLegacyGlobalCleanup({ homeDir, dryRun: false });
412
+ if (scope === 'project') {
413
+ printProjectScopeGuidance();
414
+ return;
415
+ }
416
+ }
417
+
418
+ if (scope === 'project') {
419
+ printProjectScopeGuidance();
379
420
  return;
380
421
  }
381
422
 
@@ -415,6 +456,28 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
415
456
  if (cassInstall.installCommand) console.log(` Install with: ${cassInstall.installCommand}`);
416
457
  }
417
458
 
459
+ try {
460
+ if (hasDesktopSource(ROOT_DIR)) {
461
+ const built = buildDesktopBinary({ repoRoot: ROOT_DIR, env: process.env });
462
+ if (built.ok) {
463
+ console.log(`Desktop binary: built from source → ${built.binaryPath}`);
464
+ } else {
465
+ console.log(`Desktop binary: build skipped (${built.reason}) — runs on first \`construct chat\``);
466
+ }
467
+ } else {
468
+ const desktop = await downloadDesktopBinary({ homeDir, env: process.env });
469
+ if (desktop.status === 'downloaded') {
470
+ console.log(`Desktop binary: ${desktop.version} → ${desktop.dest}`);
471
+ } else if (desktop.status === 'failed') {
472
+ console.log(`Desktop binary: download failed (${desktop.reason}) — run \`construct install --scope=user\` again to retry`);
473
+ } else {
474
+ console.log(`Desktop binary: ${desktop.reason}`);
475
+ }
476
+ }
477
+ } catch (err) {
478
+ console.log(`Desktop binary: skipped (${err?.message || 'unknown error'})`);
479
+ }
480
+
418
481
  const telemetryResult = process.env.CONSTRUCT_TELEMETRY_URL
419
482
  ? { status: 'configured', note: `remote export configured (${process.env.CONSTRUCT_TELEMETRY_URL})` }
420
483
  : { status: 'local', note: 'local JSONL traces in .cx/traces; remote export optional' };
@@ -5,8 +5,8 @@
5
5
  * shape: YAML frontmatter (structured metadata) + markdown body (the authored
6
6
  * prose), mirroring skills/roles/*.md. This validates the frontmatter and the
7
7
  * canonical-section contract, and gates the highest-value invariant — the
8
- * frontmatter `perspective{}` must equal the registry's, since the two
9
- * currently duplicate each other and drift silently (ADR-0037).
8
+ * frontmatter `perspective{}` is the sole source of truth (ADR-0037); registry.json
9
+ * carries routing metadata only.
10
10
  *
11
11
  * A file with no frontmatter is treated as not-yet-converted: reported, never an
12
12
  * error, so the linter can run across a half-migrated corpus. Frontmatter is the
@@ -55,11 +55,6 @@ function hasHeading(body, title) {
55
55
  return false;
56
56
  }
57
57
 
58
- function samePerspective(a, b) {
59
- if (!a || !b) return false;
60
- return PERSPECTIVE_FIELDS.every((f) => String(a[f] ?? '').trim() === String(b[f] ?? '').trim());
61
- }
62
-
63
58
  /**
64
59
  * Validate one prompt file's content against the hybrid format.
65
60
  *
@@ -103,9 +98,6 @@ export function validatePromptContent({ content, id = '(prompt)', registryEntry
103
98
  for (const f of PERSPECTIVE_FIELDS) {
104
99
  if (!persp[f] || !String(persp[f]).trim()) errors.push(`${id}: perspective.${f} is required and must be non-empty`);
105
100
  }
106
- if (registryEntry?.perspective && !samePerspective(persp, registryEntry.perspective)) {
107
- errors.push(`${id}: perspective{} drifts from registry.json — they must match while both exist (frontmatter is the source of truth; update the registry)`);
108
- }
109
101
  } else if (frontmatter.perspective != null) {
110
102
  errors.push(`${id}: perspective must be an object with ${PERSPECTIVE_FIELDS.join(', ')}`);
111
103
  }
@@ -160,3 +152,20 @@ function loadRegistry(rootDir) {
160
152
  return { specialists: [] };
161
153
  }
162
154
  }
155
+
156
+ /**
157
+ * Resolve a specialist's perspective from its prompt frontmatter.
158
+ * @param {string} agentName - registry name or cx-prefixed id
159
+ * @returns {object|null}
160
+ */
161
+ export function resolvePerspectiveFromPrompt(agentName, { rootDir = process.cwd(), registry } = {}) {
162
+ const reg = registry ?? loadRegistry(rootDir);
163
+ const normalized = String(agentName ?? '').trim().replace(/^cx-/, '');
164
+ const entry = (reg.specialists ?? []).find((s) => s.name === normalized);
165
+ if (!entry?.promptFile) return null;
166
+ const filePath = path.join(rootDir, entry.promptFile);
167
+ if (!fs.existsSync(filePath)) return null;
168
+ const { frontmatter } = splitFrontmatter(fs.readFileSync(filePath, 'utf8'));
169
+ const persp = frontmatter?.perspective;
170
+ return persp && typeof persp === 'object' ? persp : null;
171
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * lib/specialists/roster.mjs — lazy specialist catalog for runtime resolution
3
+ * (construct-ymp5).
4
+ *
5
+ * The specialist catalog is resolved at runtime through orchestration_policy rather
6
+ * than inlined into synced orchestrator prompts. Each catalog entry carries id and
7
+ * whenToUse for routing discovery without an always-on prompt block.
8
+ */
9
+ import { readFileSync } from 'node:fs';
10
+ import { dirname, join } from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+
13
+ const REGISTRY_PATH = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'specialists', 'registry.json');
14
+
15
+ function loadRegistry(rootDir = null) {
16
+ const path = rootDir
17
+ ? join(rootDir, 'specialists', 'registry.json')
18
+ : REGISTRY_PATH;
19
+ return JSON.parse(readFileSync(path, 'utf8'));
20
+ }
21
+
22
+ /**
23
+ * Compact specialist catalog for orchestration_policy responses. Each entry names
24
+ * the cx-prefixed id and the routing hint (when_to_use when present, else description).
25
+ */
26
+ export function buildSpecialistCatalog({ rootDir = null, includeInternal = true } = {}) {
27
+ const registry = loadRegistry(rootDir);
28
+ const prefix = registry.prefix || 'cx';
29
+ const specialists = Array.isArray(registry.specialists) ? registry.specialists : [];
30
+ return specialists
31
+ .filter((s) => includeInternal || !s.internal)
32
+ .map((s) => ({
33
+ id: `${prefix}-${s.name}`,
34
+ whenToUse: s.when_to_use || s.description || '',
35
+ }));
36
+ }
37
+
38
+ /**
39
+ * Legacy text roster shape retained for tests and migration comparisons.
40
+ */
41
+ export function formatSpecialistRosterText(catalog) {
42
+ return catalog.map((row) => `- ${row.id}: ${row.whenToUse}`).join('\n');
43
+ }
@@ -51,6 +51,62 @@ export function renderSkeleton({ role, perspective = {}, roleGuidance } = {}) {
51
51
  return `---\n${yaml.dump(fm).trimEnd()}\n---\n\n${body}`;
52
52
  }
53
53
 
54
+ function buildFrontmatterFromRegistry(role, { perspective, roleGuidance, roleOverlays, docArtifacts } = {}) {
55
+ if (!perspective || typeof perspective !== 'object') {
56
+ throw new Error(`perspective is required to wrap legacy prompt cx-${role} (frontmatter is the source of truth)`);
57
+ }
58
+ const fm = {
59
+ name: `cx-${role}`,
60
+ role,
61
+ version: 1,
62
+ perspective,
63
+ };
64
+ if (roleGuidance) fm.roleGuidance = roleGuidance;
65
+ if (Array.isArray(roleOverlays) && roleOverlays.length) fm.roleOverlays = roleOverlays;
66
+ if (Array.isArray(docArtifacts) && docArtifacts.length) fm.templates = docArtifacts;
67
+ return fm;
68
+ }
69
+
70
+ /**
71
+ * Wrap a legacy prompt body with hybrid frontmatter from the registry entry.
72
+ * The body bytes are preserved (emit-neutral) aside from a trailing newline.
73
+ */
74
+ export function wrapLegacyPromptWithFrontmatter({ role, perspective, registryEntry = {}, body }) {
75
+ const fm = buildFrontmatterFromRegistry(role, {
76
+ perspective,
77
+ roleGuidance: registryEntry.roleGuidance,
78
+ roleOverlays: registryEntry.roleOverlays,
79
+ docArtifacts: registryEntry.docArtifacts,
80
+ });
81
+ const normalizedBody = String(body ?? '').replace(/^\n+/, '');
82
+ const withNl = normalizedBody.endsWith('\n') ? normalizedBody : `${normalizedBody}\n`;
83
+ return `---\n${yaml.dump(fm).trimEnd()}\n---\n\n${withNl}`;
84
+ }
85
+
86
+ /**
87
+ * Convert one on-disk legacy prompt to hybrid format in place.
88
+ * @returns {{ path: string, relPath: string, converted: boolean }}
89
+ */
90
+ export function convertLegacyPromptFile({ rootDir = process.cwd(), role, perspective, registryEntry } = {}) {
91
+ const relPath = promptRelPath(role);
92
+ const filePath = path.join(rootDir, relPath);
93
+ if (!fs.existsSync(filePath)) throw new Error(`${relPath} does not exist`);
94
+ const raw = fs.readFileSync(filePath, 'utf8');
95
+ const { frontmatter, body } = splitFrontmatter(raw);
96
+ if (frontmatter) return { path: filePath, relPath, converted: false };
97
+
98
+ const persp = perspective ?? registryEntry?.perspective;
99
+ if (!persp) {
100
+ throw new Error(`perspective required to convert ${relPath} — pass explicitly or supply registryEntry.perspective`);
101
+ }
102
+ const content = wrapLegacyPromptWithFrontmatter({ role, perspective: persp, registryEntry, body });
103
+ const { errors } = validatePromptContent({ content, id: `cx-${role}`, registryEntry });
104
+ if (errors.length) throw new Error(`convert ${relPath} failed:\n ${errors.join('\n ')}`);
105
+
106
+ fs.writeFileSync(filePath, content);
107
+ return { path: filePath, relPath, converted: true };
108
+ }
109
+
54
110
  /**
55
111
  * Create a specialist prompt draft on disk. Refuses to overwrite. Validates
56
112
  * the rendered skeleton before writing.
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * lib/storage/backend.mjs — shared storage backend helpers.
3
3
  *
4
- * Construct now uses embedded LanceDB and Git-backed state.
5
- * createSqlClient and related helpers are retained as stubs to prevent
6
- * breaking existing imports, but they no longer facilitate local
7
- * Postgres orchestration.
4
+ * Construct uses embedded LanceDB and Git-backed state. createSqlClient and
5
+ * related helpers are stubs that keep existing imports working without
6
+ * orchestrating local Postgres.
8
7
  */
9
8
 
10
9
  export function createSqlClient(env = process.env) {
11
- // Construct no longer manages local Postgres. External DATABASE_URL is
12
- // ignored for local-first operations.
10
+ // Stub: local-first operations skip Postgres, so external DATABASE_URL has
11
+ // no effect here.
12
+
13
13
  return null;
14
14
  }
15
15
 
@@ -1,21 +1,24 @@
1
1
  /**
2
2
  * lib/storage/hybrid-query.mjs — unified search across knowledge and facts.
3
3
  *
4
- * This is a stub for the new LanceDB-backed search.
4
+ * Stub for the LanceDB-backed search.
5
5
  */
6
6
  import { VectorClient } from './vector-client.mjs';
7
7
 
8
8
  export async function buildHybridSearchResultsAsync(rootDir, query, { limit = 10, env = process.env } = {}) {
9
- // 1. Embed query
9
+ // Embed the query
10
+
10
11
  const { embedText } = await import('./embeddings-engine.mjs');
11
12
  const { embedding } = await embedText(query, { env });
12
13
 
13
- // 2. Search LanceDB
14
+ // Search LanceDB documents and observations
15
+
14
16
  const client = new VectorClient({ env });
15
17
  const docs = await client.searchDocuments({ project: 'construct', queryEmbedding: embedding, limit });
16
18
  const obs = await client.searchObservations({ project: 'construct', queryEmbedding: embedding, limit });
17
19
 
18
- // 3. Merge and rank (simple merge for now)
20
+ // Merge both result sets and rank by similarity
21
+
19
22
  const all = [...docs.map(d => ({ ...d, type: 'document' })), ...obs.map(o => ({ ...o, type: 'observation' }))];
20
23
  return all.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
21
24
  }
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { existsSync, readdirSync, statSync, readFileSync } from 'node:fs';
6
6
  import { basename, extname, join, relative } from 'node:path';
7
- import { EXTRACTABLE_DOCUMENT_EXTS, extractDocumentText } from '../document-extract.mjs';
7
+ import { EXTRACTABLE_DOCUMENT_EXTS, extractDocumentTextNodeNative } from '../document-extract.mjs';
8
8
 
9
9
  function readJsonIfExists(filePath) {
10
10
  if (!existsSync(filePath)) return null;
@@ -28,7 +28,7 @@ function titleFromPath(filePath) {
28
28
  return basename(filePath, extname(filePath)).replace(/[-_]+/g, ' ');
29
29
  }
30
30
 
31
- function collectTextFiles(rootDir, dirs, { extensions = EXTRACTABLE_DOCUMENT_EXTS } = {}) {
31
+ async function collectTextFiles(rootDir, dirs, { extensions = EXTRACTABLE_DOCUMENT_EXTS } = {}) {
32
32
  const files = [];
33
33
  for (const dir of dirs) {
34
34
  const abs = join(rootDir, dir);
@@ -47,7 +47,7 @@ function collectTextFiles(rootDir, dirs, { extensions = EXTRACTABLE_DOCUMENT_EXT
47
47
  try {
48
48
  const stat = statSync(full);
49
49
  if (stat.size > 5_000_000) continue;
50
- const body = extractDocumentText(full, { maxChars: 200_000 }).text;
50
+ const body = (await extractDocumentTextNodeNative(full, { maxChars: 200_000 })).text;
51
51
  files.push({
52
52
  path: relative(rootDir, full),
53
53
  title: titleFromPath(full),
@@ -62,12 +62,12 @@ function collectTextFiles(rootDir, dirs, { extensions = EXTRACTABLE_DOCUMENT_EXT
62
62
  return files.sort((a, b) => a.path.localeCompare(b.path));
63
63
  }
64
64
 
65
- export function loadStateSnapshot(rootDir) {
65
+ export async function loadStateSnapshot(rootDir) {
66
66
  const contextJsonPath = join(rootDir, '.cx', 'context.json');
67
67
  const planPath = join(rootDir, 'plan.md');
68
68
  const architecturePath = join(rootDir, 'docs', 'architecture.md');
69
69
  const docsReadmePath = join(rootDir, 'docs', 'README.md');
70
- const productIntelDocs = collectTextFiles(rootDir, [
70
+ const productIntelDocs = await collectTextFiles(rootDir, [
71
71
  '.cx/knowledge',
72
72
  'docs/prd',
73
73
  'docs/meta-prd',
@@ -2,8 +2,7 @@
2
2
  /**
3
3
  * lib/storage/sync.mjs — sync file-state artifacts into local LanceDB indices.
4
4
  *
5
- * This implementation replaces the Postgres-backed sync with a LanceDB-backed
6
- * one, matching the new local-first, Git-backed architecture.
5
+ * Syncs into LanceDB indices, matching the local-first, Git-backed architecture.
7
6
  */
8
7
  import crypto from 'node:crypto';
9
8
  import { loadStateSnapshot } from './state-source.mjs';
@@ -87,7 +86,7 @@ function toDocumentRows(rootDir, snapshot, project = 'construct') {
87
86
  }
88
87
 
89
88
  export async function syncFileStateToSql(rootDir, { env = process.env, project = 'construct' } = {}) {
90
- const snapshot = loadStateSnapshot(rootDir);
89
+ const snapshot = await loadStateSnapshot(rootDir);
91
90
  const rows = toDocumentRows(rootDir, snapshot, project);
92
91
 
93
92
  const modelInfo = await getEmbeddingModelInfo({ env });
@@ -0,0 +1,52 @@
1
+ /**
2
+ * lib/telemetry/rule-calls.mjs — record when a rule path is referenced at runtime.
3
+ *
4
+ * Mirrors hook-calls telemetry: append-only JSONL at ~/.cx/rule-calls.jsonl.
5
+ * Consumed by `construct rules usage` for consolidation decisions.
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import os from 'node:os';
10
+ import path from 'node:path';
11
+ import { appendBounded } from '../logging/rotate.mjs';
12
+
13
+ export const DEFAULT_LOG_PATH = path.join(os.homedir(), '.cx', 'rule-calls.jsonl');
14
+
15
+ /**
16
+ * @param {object} event
17
+ * @param {string} event.rulePath — e.g. rules/common/no-fabrication.md
18
+ * @param {string} event.source — mcp | hook | prompt-composer | validation | other
19
+ * @param {string} [event.callerContext]
20
+ */
21
+ export function logRuleCall(event, { logPath = DEFAULT_LOG_PATH, env = process.env } = {}) {
22
+ if (env.CONSTRUCT_RULE_TELEMETRY === 'off') return;
23
+ const entry = {
24
+ ts: new Date().toISOString(),
25
+ rulePath: event.rulePath,
26
+ source: event.source || 'other',
27
+ callerContext: event.callerContext || null,
28
+ };
29
+ try {
30
+ appendBounded('rule-calls', logPath, `${JSON.stringify(entry)}\n`);
31
+ } catch {
32
+ /* non-fatal */
33
+ }
34
+ }
35
+
36
+ export function summarizeRuleCalls({ logPath = DEFAULT_LOG_PATH, since = '30d' } = {}) {
37
+ if (!fs.existsSync(logPath)) return { rules: {}, total: 0 };
38
+ const ms = since.endsWith('d') ? parseInt(since, 10) * 86400000 : 30 * 86400000;
39
+ const cutoff = Date.now() - ms;
40
+ const rules = {};
41
+ let total = 0;
42
+ for (const line of fs.readFileSync(logPath, 'utf8').split('\n')) {
43
+ if (!line.trim()) continue;
44
+ try {
45
+ const row = JSON.parse(line);
46
+ if (Date.parse(row.ts) < cutoff) continue;
47
+ total += 1;
48
+ rules[row.rulePath] = (rules[row.rulePath] || 0) + 1;
49
+ } catch { /* skip */ }
50
+ }
51
+ return { rules, total, since };
52
+ }
@@ -20,7 +20,7 @@ export const SPECIALIST_TEMPLATES = {
20
20
  // Tranche 1 — five new templates authored alongside this map.
21
21
  'cx-reviewer': ['code-review-report'],
22
22
  'cx-security': ['security-audit-report'],
23
- 'cx-qa': ['qa-report', 'test-plan'],
23
+ 'cx-qa': ['qa-report', 'test-plan', 'qa-strategy'],
24
24
  'cx-debugger': ['debug-investigation'],
25
25
  'cx-devil-advocate': ['verdict'],
26
26
  'cx-evaluator': ['verdict'],
@@ -31,7 +31,7 @@ export const SPECIALIST_TEMPLATES = {
31
31
  // Already-wired specialists pinned here so the gate guards them against drift.
32
32
  'cx-architect': ['adr', 'rfc', 'architecture-review'],
33
33
  'cx-researcher': ['research-brief', 'evidence-brief'],
34
- 'cx-sre': ['runbook', 'incident-report'],
34
+ 'cx-sre': ['runbook', 'incident-report', 'postmortem'],
35
35
  'cx-product-manager': ['prd', 'meta-prd'],
36
36
 
37
37
  // Tranche 2 — three lower-frequency artifacts.
@@ -1,66 +1,47 @@
1
1
  /**
2
- * lib/templates/visual-requirements.mjs — required visuals per document type.
2
+ * lib/templates/visual-requirements.mjs — required visuals and structure per document type.
3
3
  *
4
- * The machine-readable form of docs/concepts/doc-visual-matrix.md: which doc
5
- * types must carry which visual (a diagram of a given kind, or a table with given
6
- * columns), expressed as postcondition checks so the existing validate.mjs engine
7
- * enforces them. The shipped template for each listed type must satisfy its own
8
- * requirements — pinned by tests/template-visuals.test.mjs — so the templates can
9
- * never drift out of step with the matrix (beads wvbf.10 / wvbf.11).
4
+ * Loads structure and visual requirements from specialists/artifact-manifest.json
5
+ * (single source of truth) and merges legacy types not yet in the manifest.
6
+ * Paired with docs/concepts/doc-visual-matrix.md; pinned by template tests.
10
7
  */
11
8
 
12
9
  import { validateArtifactPostconditions } from '../contracts/validate.mjs';
10
+ import {
11
+ structureRequirementsFromManifest,
12
+ visualRequirementsFromManifest,
13
+ getArtifactEntry,
14
+ } from '../artifact-manifest.mjs';
15
+
16
+ const LEGACY_STRUCTURE = {
17
+ 'persona-artifact': ['Goals', 'Frustrations', 'Decision rights', 'Output contract', 'Failure modes', 'Evidence'],
18
+ 'skill-artifact': ['What this skill produces', 'When to invoke it', 'Competency rubric', 'Failure modes', 'Worked example'],
19
+ 'research-finding': ['SOURCES', 'FINDINGS', 'INFERENCES', 'CONFIDENCE', 'GAPS', 'RECOMMENDATION'],
20
+ };
21
+
22
+ export const STRUCTURE_REQUIREMENTS = {
23
+ ...structureRequirementsFromManifest(),
24
+ ...LEGACY_STRUCTURE,
25
+ };
13
26
 
14
27
  export const VISUAL_REQUIREMENTS = {
15
- runbook: [
16
- { id: 'runbook-diagnostic-flowchart', check: 'artifact-has-mermaid', diagram: 'flowchart' },
17
- ],
18
- 'incident-report': [
19
- { id: 'incident-timeline-table', check: 'artifact-table-has-columns', columns: ['Time (UTC)', 'Event'] },
20
- ],
21
- rfc: [
22
- { id: 'rfc-sequence-diagram', check: 'artifact-has-mermaid', diagram: 'sequenceDiagram' },
23
- ],
28
+ ...visualRequirementsFromManifest(),
24
29
  };
25
30
 
26
31
  export function visualRequirementTypes() {
27
32
  return Object.keys(VISUAL_REQUIREMENTS);
28
33
  }
29
34
 
30
- // The structural floor of the quality rubric (ADR-0018): the sections a domain
31
- // expert expects for each doc type. Each shipped template must satisfy its own
32
- // entry — pinned by tests/structure-requirements.test.mjs — so a template can
33
- // never quietly drop a required section. Section requirements compose with the
34
- // VISUAL_REQUIREMENTS above; lintDocStructure runs both.
35
-
36
- export const STRUCTURE_REQUIREMENTS = {
37
- adr: ['Problem', 'Decision', 'Rejected alternatives', 'Consequences', 'Reversibility'],
38
- rfc: ['Summary', 'Motivation', 'Proposed design', 'Risks', 'Verification'],
39
- prd: ['Problem', 'Goals', 'Success metrics', 'Risks and mitigations'],
40
- 'research-brief': ['Sources', 'Findings', 'Confidence summary', 'Recommendation'],
41
- 'incident-report': ['Summary', 'Severity rationale', 'Impact', 'Timeline', 'Trigger', 'Root cause', 'Contributing factors', 'Action items'],
42
- runbook: ['Alert trigger', 'Symptoms', 'Impact', 'Severity and response', 'Diagnostic steps', 'Remediation', 'Rollback', 'Escalation'],
43
- strategy: ['Vision', 'Bets', 'Non-bets', 'North Star Metric', 'Metrics', 'Milestones', 'Risks'],
44
- 'signal-brief': ['Signal', 'Evidence', 'Counter-signal', 'What would make this actionable'],
45
- prfaq: ['Problem statement', 'Press release', 'External FAQ', 'Internal FAQ', 'Evidence appendix'],
46
- 'customer-profile': ['Snapshot', 'Active pain points', 'Open asks', 'Evidence links'],
47
- 'one-pager': ['Problem', 'Proposal', 'Why now', 'Success measure', 'Cost', 'Asks'],
48
- 'product-intelligence-report': ['Executive readout', 'Evidence base', 'Themes', 'Customer asks', 'Product implications', 'Recommended actions', 'Gaps and risks'],
49
- 'backlog-proposal': ['Source evidence', 'Proposed changes', 'Duplicate and conflict check', 'Approval request'],
50
- 'persona-artifact': ['Goals', 'Frustrations', 'Decision rights', 'Output contract', 'Failure modes', 'Evidence'],
51
- 'skill-artifact': ['What this skill produces', 'When to invoke it', 'Competency rubric', 'Failure modes', 'Worked example'],
52
- 'research-finding': ['SOURCES', 'FINDINGS', 'INFERENCES', 'CONFIDENCE', 'GAPS', 'RECOMMENDATION'],
53
- };
54
-
55
35
  export function structureRequirementTypes() {
56
36
  return [...new Set([...Object.keys(STRUCTURE_REQUIREMENTS), ...Object.keys(VISUAL_REQUIREMENTS)])];
57
37
  }
58
38
 
59
- /**
60
- * Lint one document against both the required sections and required visuals for
61
- * its type. Returns an array of violation strings (empty when satisfied or when
62
- * the type declares no requirements).
63
- */
39
+ export function resolveTemplatePath(type, rootDir) {
40
+ const entry = getArtifactEntry(type, { rootDir });
41
+ if (entry?.template) return entry.template;
42
+ return `templates/docs/${type}.md`;
43
+ }
44
+
64
45
  export function lintDocStructure(filePath, type) {
65
46
  const sectionChecks = (STRUCTURE_REQUIREMENTS[type] || []).map((section) => ({
66
47
  id: `${type}-section-${section}`,
@@ -72,11 +53,6 @@ export function lintDocStructure(filePath, type) {
72
53
  return validateArtifactPostconditions({ contract: { postconditions }, artifactPath: filePath });
73
54
  }
74
55
 
75
- /**
76
- * Lint one document against the visual requirements for its type. Returns an
77
- * array of violation strings (empty when satisfied, or when the type has no
78
- * declared visual requirements).
79
- */
80
56
  export function lintDocVisuals(filePath, type) {
81
57
  const postconditions = VISUAL_REQUIREMENTS[type];
82
58
  if (!postconditions) return [];