@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
@@ -0,0 +1,137 @@
1
+ /**
2
+ * lib/evals/dataset.mjs — provenance-backed evaluation dataset: validation,
3
+ * leakage-safe splitting, expiry, and held-out selection (construct-6zga.1.6).
4
+ *
5
+ * A dataset item carries the provenance the governed improvement loop needs to
6
+ * evaluate a candidate on data it did not generate: source trace ids, human-label
7
+ * provenance, redaction state, a capability snapshot, the allowed tools, the
8
+ * expected evidence and contract behavior, a split assignment, and an expiry.
9
+ * Split assignment is deterministic and keyed on task family plus source trace, so
10
+ * every item from one trace lands in the same split — a candidate's generating
11
+ * trace can never leak across the train/dev/test boundary. selectEvalSet then
12
+ * strips the generating trace, near-duplicates, and the same user correction from
13
+ * the evaluation set so a candidate is never scored on its own origin. Reference
14
+ * shape: schemas/eval-dataset.schema.json.
15
+ */
16
+ import crypto from 'node:crypto';
17
+
18
+ export const EVAL_DATASET_SCHEMA_VERSION = 1;
19
+
20
+ export const DATASET_SPLITS = Object.freeze(['train', 'dev', 'test']);
21
+ export const REDACTION_STATES = Object.freeze(['raw', 'redacted', 'synthetic']);
22
+ export const EVIDENCE_REQUIREMENTS = Object.freeze(['none', 'preferred', 'required']);
23
+ export const LABEL_PROVENANCE = Object.freeze(['human', 'model-assisted', 'unlabeled']);
24
+ export const CONTRACT_OUTCOMES = Object.freeze(['pass', 'fail', 'blocker', 'handoff']);
25
+ export const CAPABILITY_CLASSES = Object.freeze(['hosted-direct', 'hosted-routed', 'local-capable', 'local-constrained', 'unknown']);
26
+
27
+ function hashBucket(text) {
28
+ const digest = crypto.createHash('sha256').update(String(text)).digest();
29
+ return digest.readUInt32BE(0) % 100;
30
+ }
31
+
32
+ function normalizePrompt(text) {
33
+ return String(text || '').toLowerCase().replace(/\s+/g, ' ').trim();
34
+ }
35
+
36
+ function promptOf(item) {
37
+ return item?.taskInput?.prompt ?? item?.prompt ?? '';
38
+ }
39
+
40
+ export function nearDuplicateKey(item) {
41
+ const normalized = normalizePrompt(promptOf(item));
42
+ if (!normalized) return null;
43
+ return crypto.createHash('sha256').update(normalized).digest('hex').slice(0, 16);
44
+ }
45
+
46
+ function primarySourceTrace(item) {
47
+ return Array.isArray(item?.sourceTraceIds) && item.sourceTraceIds.length ? item.sourceTraceIds[0] : null;
48
+ }
49
+
50
+ /**
51
+ * Deterministically assign a split. The bucket is keyed on task family plus the
52
+ * item's primary source trace, so all items sharing a trace co-locate in one
53
+ * split. devRatio/testRatio are fractions of the bucket space; the remainder is
54
+ * train.
55
+ */
56
+ export function assignSplit(item, { devRatio = 0.15, testRatio = 0.15 } = {}) {
57
+ const trace = primarySourceTrace(item) || item?.id || '';
58
+ const bucket = hashBucket(`${item?.taskFamily || ''}:${trace}`);
59
+ const testCut = Math.round(testRatio * 100);
60
+ const devCut = testCut + Math.round(devRatio * 100);
61
+ if (bucket < testCut) return 'test';
62
+ if (bucket < devCut) return 'dev';
63
+ return 'train';
64
+ }
65
+
66
+ export function isExpired(item, nowIso) {
67
+ if (!item?.expiry || !nowIso) return false;
68
+ return String(item.expiry) <= String(nowIso);
69
+ }
70
+
71
+ /**
72
+ * Strip every item that would leak into a candidate's evaluation: the candidate's
73
+ * own generating / source traces, a near-duplicate task input, and the same user
74
+ * correction. This is the held-out guarantee (construct-6zga.1.6 AC2).
75
+ */
76
+ export function excludeLeakage(items, candidate = {}) {
77
+ const list = Array.isArray(items) ? items : [];
78
+ const generatingTraces = new Set([
79
+ ...(Array.isArray(candidate.generatingTraceIds) ? candidate.generatingTraceIds : []),
80
+ ...(Array.isArray(candidate.sourceTraceIds) ? candidate.sourceTraceIds : []),
81
+ ]);
82
+ const candidateDupKey = nearDuplicateKey(candidate);
83
+ const candidateCorrection = candidate.correctionId ?? candidate.humanLabel?.correctionId ?? null;
84
+
85
+ return list.filter((item) => {
86
+ const traces = Array.isArray(item?.sourceTraceIds) ? item.sourceTraceIds : [];
87
+ if (traces.some((t) => generatingTraces.has(t))) return false;
88
+ if (candidateDupKey && nearDuplicateKey(item) === candidateDupKey) return false;
89
+ const correction = item?.humanLabel?.correctionId ?? null;
90
+ if (candidateCorrection && correction && correction === candidateCorrection) return false;
91
+ return true;
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Build the evaluation set for a candidate: the items in the requested split,
97
+ * minus expired items, minus anything that would leak (excludeLeakage).
98
+ */
99
+ export function selectEvalSet(items, candidate = {}, { split = 'test', nowIso = null } = {}) {
100
+ const list = Array.isArray(items) ? items : [];
101
+ const inSplit = list.filter((item) => item?.split === split && !isExpired(item, nowIso));
102
+ return excludeLeakage(inSplit, candidate);
103
+ }
104
+
105
+ /**
106
+ * Hand-rolled validator (no ajv — Construct stays dependency-free at startup).
107
+ * Returns { valid, errors } against schemas/eval-dataset.schema.json.
108
+ */
109
+ export function validateDatasetItem(item) {
110
+ const errors = [];
111
+ if (!item || typeof item !== 'object') return { valid: false, errors: ['item is not an object'] };
112
+ if (item.schemaVersion !== EVAL_DATASET_SCHEMA_VERSION) errors.push(`schemaVersion must be ${EVAL_DATASET_SCHEMA_VERSION}`);
113
+ if (typeof item.id !== 'string' || !item.id) errors.push('id required');
114
+ if (typeof item.taskFamily !== 'string' || !item.taskFamily) errors.push('taskFamily required');
115
+ if (!item.taskInput || typeof item.taskInput.prompt !== 'string') errors.push('taskInput.prompt required');
116
+
117
+ if (!item.capabilitySnapshot || !CAPABILITY_CLASSES.includes(item.capabilitySnapshot.capabilityClass)) {
118
+ errors.push('capabilitySnapshot.capabilityClass invalid');
119
+ }
120
+ if (!Array.isArray(item.allowedTools)) errors.push('allowedTools must be an array');
121
+
122
+ const ev = item.expectedEvidenceBehavior;
123
+ if (!ev || !EVIDENCE_REQUIREMENTS.includes(ev.requirement) || typeof ev.citationsRequired !== 'boolean') {
124
+ errors.push('expectedEvidenceBehavior invalid');
125
+ }
126
+ if (!item.expectedContractResult || !CONTRACT_OUTCOMES.includes(item.expectedContractResult.outcome)) {
127
+ errors.push('expectedContractResult.outcome invalid');
128
+ }
129
+ if (!item.redaction || !REDACTION_STATES.includes(item.redaction.state)) errors.push('redaction.state invalid');
130
+
131
+ if (!Array.isArray(item.sourceTraceIds) || item.sourceTraceIds.length < 1) errors.push('sourceTraceIds must be non-empty');
132
+ if (!item.humanLabel || !LABEL_PROVENANCE.includes(item.humanLabel.provenance)) errors.push('humanLabel.provenance invalid');
133
+ if (!DATASET_SPLITS.includes(item.split)) errors.push(`split invalid: ${item.split}`);
134
+ if (item.expiry !== null && typeof item.expiry !== 'string') errors.push('expiry must be an ISO string or null');
135
+
136
+ return { valid: errors.length === 0, errors };
137
+ }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * lib/evals/gates.mjs — independent evaluation gates for the governed improvement
3
+ * loop (construct-6zga.1.6).
4
+ *
5
+ * Six deterministic gates run first and own the promotion decision: contract
6
+ * schema, source provenance, safety/permission behavior, tool use, cost/latency
7
+ * budgets, and output structure. Any deterministic regression blocks promotion —
8
+ * a candidate that fabricates a source, uses a disallowed tool, busts the budget,
9
+ * or returns the wrong contract outcome cannot ship. Model judges are supplemental:
10
+ * recordJudgeMetadata captures the model, profile, rubric/prompt version,
11
+ * repetitions, disagreement, human calibration, and abstention failures behind each
12
+ * verdict, and decidePromotion treats judges as a veto that can withhold promotion
13
+ * but never as a standalone signal that grants it. buildEvaluationReport presents
14
+ * the baseline/candidate deltas alongside the gate results and evaluator metadata.
15
+ */
16
+ import { CONTRACT_OUTCOMES } from './dataset.mjs';
17
+
18
+ export const EVAL_REPORT_SCHEMA_VERSION = 1;
19
+
20
+ export const DETERMINISTIC_GATES = Object.freeze([
21
+ 'contract-schema',
22
+ 'source-provenance',
23
+ 'safety-permission',
24
+ 'tool-use',
25
+ 'cost-latency',
26
+ 'output-structure',
27
+ ]);
28
+
29
+ export const JUDGE_VERDICTS = Object.freeze(['pass', 'fail', 'abstain']);
30
+
31
+ function gate(name, pass, detail = null) {
32
+ return { gate: name, pass, detail: pass ? null : detail };
33
+ }
34
+
35
+ export function contractSchemaGate(candidate, item) {
36
+ const expected = item?.expectedContractResult?.outcome;
37
+ const actual = candidate?.contractResult?.outcome;
38
+ const pass = CONTRACT_OUTCOMES.includes(actual) && actual === expected;
39
+ return gate('contract-schema', pass, `expected ${expected}, got ${actual ?? 'none'}`);
40
+ }
41
+
42
+ // Every cited source must resolve to a known source trace, and required evidence
43
+ // must be present — a fabricated citation or missing required evidence fails the
44
+ // gate even if the answer looks right.
45
+
46
+ export function sourceProvenanceGate(candidate, item) {
47
+ const known = new Set(Array.isArray(item?.sourceTraceIds) ? item.sourceTraceIds : []);
48
+ const cited = Array.isArray(candidate?.citedSourceIds) ? candidate.citedSourceIds : [];
49
+ const fabricated = cited.filter((c) => !known.has(c));
50
+ const requirement = item?.expectedEvidenceBehavior?.requirement || 'none';
51
+ const citationsRequired = item?.expectedEvidenceBehavior?.citationsRequired === true;
52
+ const missingEvidence = requirement === 'required' && candidate?.evidence?.provided !== true;
53
+ const missingCitations = citationsRequired && cited.length === 0;
54
+ const pass = fabricated.length === 0 && !missingEvidence && !missingCitations;
55
+ const reasons = [
56
+ fabricated.length ? `fabricated sources: ${fabricated.join(', ')}` : null,
57
+ missingEvidence ? 'required evidence missing' : null,
58
+ missingCitations ? 'required citations missing' : null,
59
+ ].filter(Boolean).join('; ');
60
+ return gate('source-provenance', pass, reasons);
61
+ }
62
+
63
+ export function safetyPermissionGate(candidate) {
64
+ const violations = Array.isArray(candidate?.permissionViolations) ? candidate.permissionViolations : [];
65
+ return gate('safety-permission', violations.length === 0, `permission violations: ${violations.join(', ')}`);
66
+ }
67
+
68
+ export function toolUseGate(candidate, item) {
69
+ const allowed = new Set(Array.isArray(item?.allowedTools) ? item.allowedTools : []);
70
+ const used = Array.isArray(candidate?.toolsUsed) ? candidate.toolsUsed : [];
71
+ const disallowed = used.filter((t) => !allowed.has(t));
72
+ return gate('tool-use', disallowed.length === 0, `disallowed tools: ${disallowed.join(', ')}`);
73
+ }
74
+
75
+ export function costLatencyGate(candidate, budgets = {}) {
76
+ const maxCost = Number.isFinite(budgets?.maxCost) ? budgets.maxCost : Infinity;
77
+ const maxLatencyMs = Number.isFinite(budgets?.maxLatencyMs) ? budgets.maxLatencyMs : Infinity;
78
+ const overCost = Number.isFinite(candidate?.cost) && candidate.cost > maxCost;
79
+ const overLatency = Number.isFinite(candidate?.latencyMs) && candidate.latencyMs > maxLatencyMs;
80
+ const reasons = [
81
+ overCost ? `cost ${candidate.cost} > ${maxCost}` : null,
82
+ overLatency ? `latency ${candidate.latencyMs}ms > ${maxLatencyMs}ms` : null,
83
+ ].filter(Boolean).join('; ');
84
+ return gate('cost-latency', !overCost && !overLatency, reasons);
85
+ }
86
+
87
+ export function outputStructureGate(candidate) {
88
+ const pass = candidate?.output != null && candidate?.outputStructured !== false;
89
+ return gate('output-structure', pass, 'output missing or structurally invalid');
90
+ }
91
+
92
+ /**
93
+ * Run all deterministic gates. Any failure blocks promotion (construct-6zga.1.6
94
+ * AC3): the regression list names every gate that failed.
95
+ */
96
+ export function runDeterministicGates(candidate, item, budgets = {}) {
97
+ const gates = [
98
+ contractSchemaGate(candidate, item),
99
+ sourceProvenanceGate(candidate, item),
100
+ safetyPermissionGate(candidate),
101
+ toolUseGate(candidate, item),
102
+ costLatencyGate(candidate, budgets),
103
+ outputStructureGate(candidate),
104
+ ];
105
+ const regressions = gates.filter((g) => !g.pass).map((g) => g.gate);
106
+ return { gates, blocked: regressions.length > 0, regressions };
107
+ }
108
+
109
+ /**
110
+ * Normalize a model-judge record. A judge is supplemental evidence; the record
111
+ * keeps the metadata that makes its verdict auditable and calibratable.
112
+ */
113
+ export function recordJudgeMetadata(judge = {}) {
114
+ return {
115
+ model: judge.model ?? null,
116
+ profile: judge.profile ?? null,
117
+ rubricVersion: judge.rubricVersion ?? null,
118
+ promptVersion: judge.promptVersion ?? null,
119
+ repetitions: Number.isFinite(judge.repetitions) ? judge.repetitions : 0,
120
+ disagreement: Number.isFinite(judge.disagreement) ? judge.disagreement : null,
121
+ humanCalibration: Number.isFinite(judge.humanCalibration) ? judge.humanCalibration : null,
122
+ abstentionFailures: Number.isFinite(judge.abstentionFailures) ? judge.abstentionFailures : 0,
123
+ verdict: JUDGE_VERDICTS.includes(judge.verdict) ? judge.verdict : 'abstain',
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Decide promotion. Deterministic gates are mandatory and final: a missing or
129
+ * blocked deterministic result is never promotable, and no judge can override a
130
+ * deterministic block. Judges are a veto only — they can withhold promotion with a
131
+ * fail verdict but never grant it on their own (construct-6zga.1.6 AC4).
132
+ */
133
+ export function decidePromotion({ deterministic = null, judges = [] } = {}) {
134
+ const judging = Array.isArray(judges) ? judges.map(recordJudgeMetadata) : [];
135
+ if (!deterministic || deterministic.blocked) {
136
+ const reason = deterministic?.blocked
137
+ ? `deterministic regression: ${deterministic.regressions.join(', ')}`
138
+ : 'no deterministic gate result';
139
+ return { promotable: false, reason, judgesConsulted: judging.length };
140
+ }
141
+ const failing = judging.filter((j) => j.verdict === 'fail');
142
+ if (failing.length > 0) {
143
+ return { promotable: false, reason: `judge withheld: ${failing.length} fail verdict(s)`, judgesConsulted: judging.length };
144
+ }
145
+ return { promotable: true, reason: 'deterministic gates passed; no judge objection', judgesConsulted: judging.length };
146
+ }
147
+
148
+ function numDelta(baseline, candidate) {
149
+ if (!Number.isFinite(baseline) || !Number.isFinite(candidate)) return null;
150
+ return { baseline, candidate, delta: candidate - baseline };
151
+ }
152
+
153
+ /**
154
+ * Build the evaluation report: baseline/candidate deltas, the deterministic gate
155
+ * results, the evaluator (judge) metadata, and the promotion decision. Judges
156
+ * never stand alone — the decision is computed through decidePromotion.
157
+ */
158
+ export function buildEvaluationReport({ baseline = null, candidate = null, deterministic = null, judges = [] } = {}) {
159
+ const deltas = baseline && candidate
160
+ ? {
161
+ cost: numDelta(baseline.cost, candidate.cost),
162
+ latencyMs: numDelta(baseline.latencyMs, candidate.latencyMs),
163
+ contractOutcome: { baseline: baseline.contractResult?.outcome ?? null, candidate: candidate.contractResult?.outcome ?? null },
164
+ }
165
+ : null;
166
+
167
+ return {
168
+ schemaVersion: EVAL_REPORT_SCHEMA_VERSION,
169
+ deltas,
170
+ gates: deterministic?.gates || [],
171
+ blocked: deterministic?.blocked ?? null,
172
+ evaluators: (Array.isArray(judges) ? judges : []).map(recordJudgeMetadata),
173
+ decision: decidePromotion({ deterministic, judges }),
174
+ };
175
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * lib/export-branding.mjs — one honest branding contract for distribution exports.
3
+ */
4
+
5
+ import { BRAND_CAPABLE_FORMATS, PLAIN_OUTPUT_FORMATS } from './artifact-manifest.mjs';
6
+
7
+ export const EXPORT_BRANDING_CAPABILITIES = Object.freeze({
8
+ pdf: { capable: true, mechanism: 'typst-template', asset: 'construct-pdf.typ' },
9
+ html: { capable: true, mechanism: 'pandoc-template', asset: 'construct-web.html' },
10
+ deck: { capable: true, mechanism: 'pandoc-template', asset: 'construct-deck.html' },
11
+ pptx: { capable: true, mechanism: 'native-pptx', asset: 'pptxgenjs' },
12
+ docx: { capable: true, mechanism: 'reference-doc', asset: 'construct-reference.docx' },
13
+ doc: { capable: true, mechanism: 'docx-reference-via-libreoffice', asset: 'construct-reference.docx' },
14
+ rtf: { capable: true, mechanism: 'pandoc-writer', asset: null },
15
+ odt: { capable: true, mechanism: 'pandoc-writer', asset: null },
16
+ epub: { capable: true, mechanism: 'pandoc-writer', asset: null },
17
+ tex: { capable: true, mechanism: 'pandoc-writer', asset: null },
18
+ txt: { capable: false, mechanism: 'plain-source', asset: null },
19
+ md: { capable: false, mechanism: 'plain-source', asset: null },
20
+ mdx: { capable: false, mechanism: 'plain-source', asset: null },
21
+ });
22
+
23
+ export function resolveExportBranding(format, requested = 'construct') {
24
+ const capability = EXPORT_BRANDING_CAPABILITIES[format];
25
+ if (!capability) return { requested, applied: 'none', capable: false, reason: 'unsupported format' };
26
+ if (!BRAND_CAPABLE_FORMATS.includes(format) || PLAIN_OUTPUT_FORMATS.includes(format)) {
27
+ return { requested, applied: 'none', capable: false, reason: 'format has no styling surface' };
28
+ }
29
+ if (requested === 'plain') return { requested, applied: 'plain', capable: true, mechanism: capability.mechanism, reason: 'explicit opt-out' };
30
+ return { requested: 'construct', applied: 'construct', capable: true, mechanism: capability.mechanism, asset: capability.asset, reason: 'default policy' };
31
+ }
32
+
@@ -0,0 +1,54 @@
1
+ /**
2
+ * lib/graph/build-co-change.mjs — co-change corroboration layer.
3
+ *
4
+ * Adds the third leg of hybrid population (after registry ingest and static
5
+ * import derivation): module nodes for each source directory, `contains` edges
6
+ * (module→file/test) that anchor those modules to the file graph, and
7
+ * `co_changes` edges between directories that change together in git history.
8
+ *
9
+ * The git analysis reuses captureDependencyPatterns, which also records the
10
+ * same directory relationships into the knowledge-graph entity store — so the
11
+ * matrix and the entity graph share one co-change source of truth.
12
+ */
13
+
14
+ import { captureDependencyPatterns } from '../artifact-capture.mjs';
15
+ import { nodeId } from './store.mjs';
16
+
17
+ function moduleOf(rel) {
18
+ return rel.split('/').slice(0, 2).join('/');
19
+ }
20
+
21
+ function fileNodeId(rel) {
22
+ return (rel.endsWith('.test.mjs') || rel.endsWith('.test.js')) ? `test:${rel}` : `file:${rel}`;
23
+ }
24
+
25
+ /**
26
+ * @param {object} opts
27
+ * @param {string} opts.rootDir — repo whose git history and entity store are read/written.
28
+ * @param {string[]} [opts.sourceRels] — repo-relative source paths to anchor under modules.
29
+ * @returns {{ nodes: object[], edges: object[] }}
30
+ */
31
+ export function buildCoChange({ rootDir, sourceRels = [] }) {
32
+ const nodes = [];
33
+ const edges = [];
34
+ const modules = new Set();
35
+
36
+ for (const rel of sourceRels) {
37
+ const mod = moduleOf(rel);
38
+ modules.add(mod);
39
+ edges.push({ from: nodeId('module', mod), to: fileNodeId(rel), rel: 'contains', source: 'import-graph' });
40
+ }
41
+
42
+ let pairs = [];
43
+ try { pairs = captureDependencyPatterns(rootDir) || []; } catch { pairs = []; }
44
+ for (const { a, b, count } of pairs) {
45
+ modules.add(a);
46
+ modules.add(b);
47
+ const [from, to] = [a, b].sort();
48
+ edges.push({ from: nodeId('module', from), to: nodeId('module', to), rel: 'co_changes', weight: count, source: 'co-change' });
49
+ }
50
+
51
+ for (const mod of modules) nodes.push({ id: nodeId('module', mod), type: 'module', name: mod, attrs: {} });
52
+
53
+ return { nodes, edges };
54
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * lib/graph/build-from-registry.mjs — seed the dependency graph from the
3
+ * authoritative declarative catalogs.
4
+ *
5
+ * Ingests registry/capabilities.json, lib/embedded-contract/workflow-defs.mjs,
6
+ * and specialists/contracts.json into typed nodes and directed edges:
7
+ * capability --embeds--> workflow (embeddedWorkflow)
8
+ * test --validates--> capability (verification.functional / hostEmulation / per-surface test)
9
+ * capability --uses--> skill | rule (skill + dependencies.skills/.rules)
10
+ * capability --governed_by-->contract (contracts[])
11
+ * capability --exposes--> surface (supported surfaces)
12
+ * Node existence on disk (tests, skills, rules) is stamped as an attr so the
13
+ * Oracle collector can later distinguish declared-but-missing from realized.
14
+ * Covers the registry half of the hybrid population; the import-graph half
15
+ * (file/module nodes, imports/covers/realizes) lands in build-import-graph.
16
+ */
17
+
18
+ import { existsSync, readFileSync, statSync } from 'node:fs';
19
+ import path from 'node:path';
20
+ import { createHash } from 'node:crypto';
21
+
22
+ import { listWorkflowDefs } from '../embedded-contract/workflow-defs.mjs';
23
+ import { loadCapabilityRegistry } from '../registry/validate.mjs';
24
+ import { nodeId } from './store.mjs';
25
+
26
+ function readJsonSafe(filePath) {
27
+ try { return JSON.parse(readFileSync(filePath, 'utf8')); } catch { return null; }
28
+ }
29
+
30
+ function existsRel(rootDir, rel) {
31
+ return !!rel && existsSync(path.join(rootDir, rel));
32
+ }
33
+
34
+ function hashFiles(rootDir, rels) {
35
+ const h = createHash('sha256');
36
+ for (const rel of rels) {
37
+ h.update(rel);
38
+ try { h.update(readFileSync(path.join(rootDir, rel))); } catch { h.update('\0missing'); }
39
+ }
40
+ return h.digest('hex').slice(0, 16);
41
+ }
42
+
43
+ /**
44
+ * Build the registry-seeded portion of the dependency graph.
45
+ *
46
+ * @param {object} opts
47
+ * @param {string} opts.rootDir — Construct package root holding registry/, specialists/.
48
+ * @returns {{ nodes: object[], edges: object[], sourceHash: string }}
49
+ */
50
+ export function buildFromRegistry({ rootDir }) {
51
+ const nodes = [];
52
+ const edges = [];
53
+ const node = (type, key, name, attrs) => nodes.push({ id: nodeId(type, key), type, name: name ?? key, attrs: attrs || {} });
54
+ const edge = (from, rel, to) => edges.push({ from, to, rel, source: 'registry' });
55
+
56
+ // Workflow catalog: every embedded workflow type is a node regardless of
57
+ // capability coverage, so reverse queries see uncovered workflows.
58
+
59
+ const workflowDefs = listWorkflowDefs();
60
+ for (const wf of workflowDefs) {
61
+ node('workflow', wf.type, wf.type, {
62
+ tier: wf.tier,
63
+ defaultApprovalMode: wf.defaultApprovalMode,
64
+ chain: wf.chain,
65
+ description: wf.description,
66
+ });
67
+ }
68
+
69
+ const contractsDoc = readJsonSafe(path.join(rootDir, 'specialists', 'contracts.json'));
70
+ for (const c of contractsDoc?.contracts ?? []) {
71
+ if (!c?.id) continue;
72
+ node('contract', c.id, c.id, { producer: c.producer ?? null, consumer: c.consumer ?? null });
73
+ }
74
+
75
+ const registry = loadCapabilityRegistry({ rootDir });
76
+ for (const cap of registry.capabilities ?? []) {
77
+ if (!cap.id) continue;
78
+ const capNode = nodeId('capability', cap.id);
79
+ node('capability', cap.id, cap.name ?? cap.id, {
80
+ kind: cap.kind ?? 'capability',
81
+ criticality: cap.criticality ?? null,
82
+ humanGate: cap.humanGate ?? null,
83
+ lastValidated: cap.lastValidated ?? null,
84
+ ownerSpecialists: cap.ownerSpecialists ?? [],
85
+ surfaces: cap.surfaces ?? {},
86
+ });
87
+
88
+ if (cap.embeddedWorkflow) edge(capNode, 'embeds', nodeId('workflow', cap.embeddedWorkflow));
89
+
90
+ const ver = cap.verification ?? {};
91
+ for (const rel of [ver.functional, ver.hostEmulation].filter(Boolean)) {
92
+ const tid = nodeId('test', rel);
93
+ node('test', rel, rel, { path: rel, exists: existsRel(rootDir, rel) });
94
+ edge(tid, 'validates', capNode);
95
+ }
96
+
97
+ for (const [surface, status] of Object.entries(cap.surfaces ?? {})) {
98
+ if (!status?.supported) continue;
99
+ node('surface', surface, surface, {});
100
+ edge(capNode, 'exposes', nodeId('surface', surface));
101
+ if (status.primary) {
102
+ const tierTest = path.join('tests', 'capabilities', cap.id, `${surface}.test.mjs`);
103
+ if (existsRel(rootDir, tierTest)) {
104
+ node('test', tierTest, tierTest, { path: tierTest, exists: true });
105
+ edge(nodeId('test', tierTest), 'validates', capNode);
106
+ }
107
+ }
108
+ }
109
+
110
+ const skillIds = new Set([cap.skill, ...(cap.dependencies?.skills ?? [])].filter(Boolean));
111
+ for (const sid of skillIds) {
112
+ node('skill', sid, sid, { exists: existsRel(rootDir, path.join('skills', `${sid}.md`)) });
113
+ edge(capNode, 'uses', nodeId('skill', sid));
114
+ }
115
+
116
+ for (const rid of cap.dependencies?.rules ?? []) {
117
+ node('rule', rid, rid, { exists: existsRel(rootDir, rid) });
118
+ edge(capNode, 'uses', nodeId('rule', rid));
119
+ }
120
+
121
+ for (const cid of cap.contracts ?? []) {
122
+ node('contract', cid, cid, {});
123
+ edge(capNode, 'governed_by', nodeId('contract', cid));
124
+ }
125
+ }
126
+
127
+ const sourceHash = hashFiles(rootDir, [
128
+ 'registry/capabilities.json',
129
+ 'specialists/contracts.json',
130
+ 'lib/embedded-contract/workflow-defs.mjs',
131
+ ]);
132
+
133
+ return { nodes, edges, sourceHash };
134
+ }
135
+
136
+ export { hashFiles };