@geraldmaron/construct 1.4.2 → 1.5.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 (519) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1027 -64
  4. package/examples/distribution/sources/deck-one-pager.md +1 -1
  5. package/lib/acp/server.mjs +21 -7
  6. package/lib/adapters-sync.mjs +2 -1
  7. package/lib/audit-trail.mjs +185 -2
  8. package/lib/beads/auto-close.mjs +2 -1
  9. package/lib/beads/drift.mjs +2 -1
  10. package/lib/bridges/copilot-proxy.mjs +20 -5
  11. package/lib/certification/skill-inventory.mjs +10 -1
  12. package/lib/cli/approvals.mjs +147 -0
  13. package/lib/cli-commands.mjs +105 -14
  14. package/lib/comment-lint.mjs +127 -8
  15. package/lib/config/schema.mjs +6 -29
  16. package/lib/config/source-target-registry.mjs +70 -0
  17. package/lib/config/source-targets.mjs +179 -205
  18. package/lib/contracts/coverage.mjs +77 -0
  19. package/lib/contracts/validate.mjs +102 -13
  20. package/lib/contracts/violation-log.mjs +50 -4
  21. package/lib/db/migrate.mjs +69 -0
  22. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  23. package/lib/db/migrations/002_queue_provider.sql +50 -0
  24. package/lib/db/migrations/003_worker_registry.sql +17 -0
  25. package/lib/db/migrations/004_trace_events.sql +16 -0
  26. package/lib/db/migrations/005_shared_memory.sql +15 -0
  27. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  28. package/lib/decisions/enforced-baseline.json +0 -2
  29. package/lib/decisions/registry.mjs +3 -2
  30. package/lib/deployment/parity-contract.mjs +1 -1
  31. package/lib/deployment-mode.mjs +78 -2
  32. package/lib/diagram-export.mjs +10 -1
  33. package/lib/distill.mjs +5 -2
  34. package/lib/docs-verify.mjs +2 -1
  35. package/lib/doctor/cli.mjs +1 -0
  36. package/lib/doctor/command-on-path.mjs +24 -0
  37. package/lib/doctor/diagnosis.mjs +89 -0
  38. package/lib/doctor/embedding-health.mjs +50 -0
  39. package/lib/doctor/engine-health.mjs +93 -0
  40. package/lib/doctor/graph-validate.mjs +47 -0
  41. package/lib/doctor/index.mjs +18 -4
  42. package/lib/doctor/sidecar-providers.mjs +56 -0
  43. package/lib/doctor/watchers/consistency.mjs +93 -1
  44. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  45. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  46. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  47. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  48. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  49. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  50. package/lib/document-export.mjs +52 -27
  51. package/lib/document-extract/docling-client.mjs +9 -5
  52. package/lib/document-extract/docling-sidecar.py +30 -0
  53. package/lib/document-extract.mjs +1 -1
  54. package/lib/document-ingest.mjs +9 -0
  55. package/lib/embed/approval-queue.mjs +184 -88
  56. package/lib/embed/authority-guard.mjs +65 -2
  57. package/lib/embed/capability-jobs.mjs +365 -0
  58. package/lib/embed/capability-lifecycle.mjs +219 -0
  59. package/lib/embed/capability-loader.mjs +303 -0
  60. package/lib/embed/capability-runtime.mjs +58 -0
  61. package/lib/embed/cli.mjs +185 -8
  62. package/lib/embed/config.mjs +4 -0
  63. package/lib/embed/daemon.mjs +125 -6
  64. package/lib/embed/demand-fetch.mjs +190 -54
  65. package/lib/embed/inbox.mjs +12 -10
  66. package/lib/embed/presets/ops-triage.mjs +236 -0
  67. package/lib/embed/presets/pm-feedback.mjs +341 -0
  68. package/lib/embed/presets/tpm.mjs +401 -0
  69. package/lib/embed/providers/jira.mjs +72 -1
  70. package/lib/embed/providers/registry.mjs +140 -33
  71. package/lib/embedded-contract/capability.mjs +4 -0
  72. package/lib/embedded-contract/execution.mjs +1 -1
  73. package/lib/embedded-contract/model-resolve.mjs +53 -3
  74. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  75. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  76. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  77. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  78. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  79. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  80. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  81. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  82. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  83. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  84. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  85. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  86. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  88. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  90. package/lib/env-config.mjs +50 -9
  91. package/lib/extensions/index.mjs +27 -0
  92. package/lib/extensions/loader.mjs +120 -0
  93. package/lib/extensions/manifest-schema.mjs +141 -0
  94. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  95. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  96. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  97. package/lib/extensions/manifests/directory.manifest.json +12 -0
  98. package/lib/extensions/manifests/docling.manifest.json +37 -0
  99. package/lib/extensions/manifests/echo.manifest.json +8 -0
  100. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  101. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  102. package/lib/extensions/manifests/github.manifest.json +52 -0
  103. package/lib/extensions/manifests/linear.manifest.json +50 -0
  104. package/lib/extensions/manifests/local.manifest.json +12 -0
  105. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  106. package/lib/extensions/manifests/openai.manifest.json +12 -0
  107. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  108. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  109. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  110. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  113. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  114. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  115. package/lib/extensions/manifests/slack.manifest.json +58 -0
  116. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  117. package/lib/extensions/validate.mjs +175 -0
  118. package/lib/features.mjs +13 -9
  119. package/lib/flows/checkpoint.mjs +184 -0
  120. package/lib/flows/constants.mjs +27 -0
  121. package/lib/flows/define.mjs +146 -0
  122. package/lib/flows/engine.mjs +249 -0
  123. package/lib/flows/errors.mjs +19 -0
  124. package/lib/flows/index.mjs +27 -0
  125. package/lib/flows/joins.mjs +18 -0
  126. package/lib/flows/schema.mjs +90 -0
  127. package/lib/flows/state.mjs +31 -0
  128. package/lib/frameworks/loader.mjs +99 -0
  129. package/lib/frameworks/schema.mjs +157 -0
  130. package/lib/graph/build-from-corpus.mjs +67 -0
  131. package/lib/graph/build-from-embed.mjs +82 -0
  132. package/lib/graph/build-from-registry.mjs +164 -3
  133. package/lib/graph/cli.mjs +456 -12
  134. package/lib/graph/gap-queries.mjs +156 -0
  135. package/lib/graph/gaps.mjs +41 -0
  136. package/lib/graph/impacted.mjs +129 -0
  137. package/lib/graph/runtime-evidence.mjs +177 -0
  138. package/lib/graph/security-coverage.mjs +113 -0
  139. package/lib/graph/staleness.mjs +241 -10
  140. package/lib/graph/store.mjs +24 -7
  141. package/lib/graph/validate.mjs +161 -0
  142. package/lib/headhunt.mjs +9 -8
  143. package/lib/health-check.mjs +15 -7
  144. package/lib/hook-health.mjs +1 -1
  145. package/lib/hooks/agent-tracker.mjs +10 -4
  146. package/lib/hooks/edit-guard.mjs +3 -3
  147. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  148. package/lib/hooks/guard-bash.mjs +41 -15
  149. package/lib/hooks/mcp-health-check.mjs +11 -4
  150. package/lib/hooks/model-fallback.mjs +50 -6
  151. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  152. package/lib/hooks/pre-compact.mjs +181 -173
  153. package/lib/hooks/rule-verifier.mjs +2 -1
  154. package/lib/hooks/session-reflect.mjs +2 -1
  155. package/lib/hooks/session-start.mjs +3 -3
  156. package/lib/host-capabilities.mjs +13 -2
  157. package/lib/host-disposition.mjs +19 -7
  158. package/lib/identity.mjs +92 -0
  159. package/lib/ingest/degraded-extract.mjs +96 -0
  160. package/lib/ingest/docling-remote.mjs +2 -1
  161. package/lib/ingest/provider-extract.mjs +7 -19
  162. package/lib/ingest/sidecar-providers.mjs +196 -0
  163. package/lib/ingest-tooling.mjs +11 -5
  164. package/lib/init-unified.mjs +57 -45
  165. package/lib/init-update.mjs +2 -1
  166. package/lib/install/legacy-global-cleanup.mjs +25 -0
  167. package/lib/intake/daemon.mjs +99 -8
  168. package/lib/intake/git-queue.mjs +110 -38
  169. package/lib/intake/queue-registry.mjs +50 -0
  170. package/lib/intake/queue.mjs +133 -17
  171. package/lib/intake/session-prelude.mjs +57 -8
  172. package/lib/integrations/intake-integrations.mjs +61 -111
  173. package/lib/intent-classifier.mjs +43 -5
  174. package/lib/libreoffice-export.mjs +8 -8
  175. package/lib/logging/rotate.mjs +5 -4
  176. package/lib/mcp/broker.mjs +332 -17
  177. package/lib/mcp/denied-store.mjs +95 -0
  178. package/lib/mcp/destructive-gate.mjs +30 -0
  179. package/lib/mcp/dispatch-envelope.mjs +199 -0
  180. package/lib/mcp/memory-bridge.mjs +2 -1
  181. package/lib/mcp/server.mjs +154 -1411
  182. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  183. package/lib/mcp/tool-definitions-project.mjs +271 -0
  184. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  185. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  186. package/lib/mcp/tool-definitions.mjs +25 -0
  187. package/lib/mcp/tool-registry.mjs +107 -0
  188. package/lib/mcp/tool-safety.mjs +1 -0
  189. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  190. package/lib/mcp/tools/orchestration-run.mjs +165 -25
  191. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  192. package/lib/mcp/tools/provider-write.mjs +187 -0
  193. package/lib/mcp/tools/scope.mjs +0 -3
  194. package/lib/mcp/tools/skills.mjs +1 -1
  195. package/lib/mcp/tools/storage.mjs +0 -8
  196. package/lib/mcp/transport/auth.mjs +238 -0
  197. package/lib/mcp/transport/http.mjs +136 -0
  198. package/lib/mcp/transport/mode.mjs +31 -0
  199. package/lib/mcp/transport/stdio.mjs +22 -0
  200. package/lib/mcp-catalog.json +4 -4
  201. package/lib/mcp-manager.mjs +34 -23
  202. package/lib/mcp-platform-config.mjs +31 -7
  203. package/lib/mode-capabilities.mjs +109 -0
  204. package/lib/model-router.mjs +42 -9
  205. package/lib/models/catalog.mjs +40 -1
  206. package/lib/net-guard.mjs +247 -0
  207. package/lib/observation-store.mjs +6 -2
  208. package/lib/ollama/provision-context.mjs +2 -1
  209. package/lib/opencode-config.mjs +22 -3
  210. package/lib/opencode-runtime-plugin.mjs +120 -2
  211. package/lib/oracle/actions.mjs +204 -10
  212. package/lib/oracle/cli.mjs +24 -3
  213. package/lib/oracle/dispatch.mjs +2 -1
  214. package/lib/oracle/execute.mjs +2 -2
  215. package/lib/oracle/gaps.mjs +3 -3
  216. package/lib/oracle/heartbeat.mjs +53 -0
  217. package/lib/oracle/read-model.mjs +69 -13
  218. package/lib/oracle/reconcile.mjs +3 -46
  219. package/lib/oracle/routing.mjs +37 -31
  220. package/lib/oracle/synthesize.mjs +1 -1
  221. package/lib/orchestration/classification.mjs +434 -0
  222. package/lib/orchestration/delegation-flow.mjs +132 -0
  223. package/lib/orchestration/flow-selection.mjs +418 -0
  224. package/lib/orchestration/gates.mjs +244 -0
  225. package/lib/orchestration/host-sampling.mjs +121 -0
  226. package/lib/orchestration/policy-constants.mjs +48 -0
  227. package/lib/orchestration/provider-outcome.mjs +197 -0
  228. package/lib/orchestration/readiness.mjs +114 -13
  229. package/lib/orchestration/run-store-postgres.mjs +32 -26
  230. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  231. package/lib/orchestration/run-store.mjs +25 -12
  232. package/lib/orchestration/runtime.mjs +446 -39
  233. package/lib/orchestration/store.mjs +87 -12
  234. package/lib/orchestration/trace-store.mjs +125 -0
  235. package/lib/orchestration/web-capability.mjs +3 -2
  236. package/lib/orchestration/worker-runtime.mjs +162 -0
  237. package/lib/orchestration/worker.mjs +528 -89
  238. package/lib/orchestration-policy.mjs +67 -1111
  239. package/lib/packs/cli.mjs +144 -0
  240. package/lib/packs/core-pack.mjs +112 -0
  241. package/lib/packs/enablement.mjs +187 -0
  242. package/lib/packs/index.mjs +23 -0
  243. package/lib/packs/loader.mjs +176 -0
  244. package/lib/packs/manifest-schema.mjs +58 -0
  245. package/lib/packs/prompts.mjs +120 -0
  246. package/lib/packs/validate.mjs +208 -0
  247. package/lib/plugin-registry.mjs +4 -5
  248. package/lib/policy/audit-gate.mjs +42 -0
  249. package/lib/policy/consumption-budget.mjs +149 -0
  250. package/lib/policy/engine.mjs +81 -6
  251. package/lib/policy/role-authority.mjs +89 -0
  252. package/lib/prompt-composer.js +19 -3
  253. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  254. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  255. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  256. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  257. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  258. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  259. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  260. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  261. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  262. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  263. package/lib/providers/contract.mjs +207 -0
  264. package/lib/providers/credential-bootstrap.mjs +7 -4
  265. package/lib/providers/credential-sources.mjs +14 -2
  266. package/lib/providers/directory/index.mjs +273 -0
  267. package/lib/providers/feedback/index.mjs +392 -0
  268. package/lib/providers/filter-audit.mjs +68 -0
  269. package/lib/providers/filter-schema.mjs +54 -0
  270. package/lib/providers/github/index.mjs +31 -0
  271. package/lib/providers/instance-config.mjs +215 -0
  272. package/lib/providers/op-locate.mjs +96 -0
  273. package/lib/providers/op-run.mjs +64 -9
  274. package/lib/providers/registry.mjs +26 -3
  275. package/lib/providers/secret-audit-wiring.mjs +6 -0
  276. package/lib/providers/secret-resolver.mjs +240 -23
  277. package/lib/publish-template.mjs +6 -3
  278. package/lib/publish-tooling.mjs +4 -0
  279. package/lib/publish.mjs +32 -4
  280. package/lib/queue/pg-queue.mjs +395 -0
  281. package/lib/reflect.mjs +2 -1
  282. package/lib/registry/assemble.mjs +28 -2
  283. package/lib/registry/consolidation.mjs +8 -1
  284. package/lib/registry/custom-scaffold.mjs +200 -0
  285. package/lib/registry/custom-schema.mjs +137 -0
  286. package/lib/registry/loader.mjs +8 -3
  287. package/lib/registry/manifests/format-engines.default.json +15 -0
  288. package/lib/registry/manifests/surface-map.default.json +46 -0
  289. package/lib/registry/retired-paths.mjs +1 -0
  290. package/lib/registry/surface-map.mjs +100 -50
  291. package/lib/render-visual-check.mjs +240 -0
  292. package/lib/resources/budget.mjs +40 -17
  293. package/lib/roles/flavor-bindings.mjs +36 -14
  294. package/lib/roles/gateway.mjs +15 -8
  295. package/lib/roots.mjs +107 -0
  296. package/lib/runtime/uv-bootstrap.mjs +32 -6
  297. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  298. package/lib/runtime-env.mjs +5 -2
  299. package/lib/scheduler/index.mjs +8 -20
  300. package/lib/schema-infer.mjs +31 -2
  301. package/lib/scopes/lifecycle.mjs +29 -25
  302. package/lib/scopes/loader.mjs +49 -12
  303. package/lib/scopes/teams.mjs +1 -1
  304. package/lib/security/ingest-boundary.mjs +80 -0
  305. package/lib/security/recall-wrapper.mjs +99 -0
  306. package/lib/security/trust.mjs +132 -0
  307. package/lib/service-manager.mjs +38 -19
  308. package/lib/setup.mjs +41 -23
  309. package/lib/skills-apply.mjs +4 -2
  310. package/lib/specialists/postconditions.mjs +2 -2
  311. package/lib/state-root.mjs +147 -0
  312. package/lib/status.mjs +597 -6
  313. package/lib/storage/admin.mjs +77 -5
  314. package/lib/storage/backend-registry.mjs +73 -0
  315. package/lib/storage/backend.mjs +63 -9
  316. package/lib/storage/embeddings-openai.mjs +12 -2
  317. package/lib/storage/hybrid-query.mjs +11 -3
  318. package/lib/storage/retrieval-hardening.mjs +384 -0
  319. package/lib/storage/shared-memory.mjs +158 -0
  320. package/lib/storage/vector-client.mjs +69 -2
  321. package/lib/team/health.mjs +72 -0
  322. package/lib/telemetry/backends/local.mjs +9 -3
  323. package/lib/telemetry/client.mjs +3 -7
  324. package/lib/template-registry.mjs +10 -12
  325. package/lib/tenant/context.mjs +82 -0
  326. package/lib/tenant/isolation.mjs +129 -0
  327. package/lib/test-corpus-inventory.mjs +104 -2
  328. package/lib/uninstall/uninstall.mjs +78 -12
  329. package/lib/validator.mjs +4 -6
  330. package/lib/worker/entrypoint.mjs +2 -1
  331. package/lib/worker/run.mjs +2 -2
  332. package/lib/worker/trace.mjs +5 -11
  333. package/lib/workflow-state.mjs +52 -8
  334. package/lib/workflows/liveness.mjs +203 -0
  335. package/lib/workflows/loader.mjs +177 -0
  336. package/lib/workflows/manifest-schema.mjs +71 -0
  337. package/lib/workflows/surface-parity.mjs +118 -0
  338. package/lib/workflows/validate.mjs +127 -0
  339. package/lib/writes/control-plane.mjs +140 -0
  340. package/lib/writes/envelope.mjs +206 -0
  341. package/lib/writes/sent-log.mjs +86 -0
  342. package/lib/writes/write-intent.mjs +109 -0
  343. package/package.json +16 -8
  344. package/personas/construct.md +12 -3
  345. package/registry/capabilities.json +143 -36
  346. package/rules/common/comments.md +1 -0
  347. package/schemas/project-config.schema.json +0 -12
  348. package/schemas/unified-registry.schema.json +2 -4
  349. package/scripts/sync-specialists.mjs +284 -81
  350. package/skills/docs/adr-workflow.md +1 -1
  351. package/skills/docs/codebase-research-workflow.md +3 -3
  352. package/skills/docs/prd-workflow.md +5 -6
  353. package/skills/docs/runbook-workflow.md +2 -2
  354. package/skills/docs/user-research-workflow.md +3 -3
  355. package/skills/operating/fleet-health-routing.md +77 -0
  356. package/skills/roles/ai-engineer.md +1 -1
  357. package/skills/roles/architect.md +0 -1
  358. package/skills/roles/business-strategist.md +1 -1
  359. package/skills/roles/data-analyst.telemetry.md +1 -1
  360. package/skills/roles/data-engineer.md +1 -1
  361. package/skills/roles/data-engineer.pipeline.md +1 -1
  362. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  363. package/skills/roles/data-engineer.warehouse.md +1 -1
  364. package/skills/roles/designer.accessibility.md +1 -1
  365. package/skills/roles/designer.md +0 -1
  366. package/skills/roles/devil-advocate.md +1 -1
  367. package/skills/roles/docs-keeper.md +1 -1
  368. package/skills/roles/evaluator.md +1 -1
  369. package/skills/roles/explorer.md +1 -1
  370. package/skills/roles/platform-engineer.md +1 -1
  371. package/skills/roles/qa.ai-eval.md +1 -2
  372. package/skills/roles/qa.api-contract.md +0 -1
  373. package/skills/roles/qa.data-pipeline.md +0 -1
  374. package/skills/roles/qa.md +0 -1
  375. package/skills/roles/qa.web-ui.md +0 -1
  376. package/skills/roles/release-manager.md +1 -1
  377. package/skills/roles/researcher.md +0 -2
  378. package/skills/roles/reviewer.md +0 -3
  379. package/skills/roles/security.ai.md +1 -1
  380. package/skills/roles/security.legal-compliance.md +1 -1
  381. package/skills/roles/security.md +0 -1
  382. package/skills/roles/security.privacy.md +0 -1
  383. package/skills/roles/security.supply-chain.md +1 -1
  384. package/skills/roles/sre.md +1 -1
  385. package/skills/roles/test-automation.md +1 -1
  386. package/skills/roles/trace-reviewer.md +1 -1
  387. package/skills/roles/ux-researcher.md +1 -1
  388. package/specialists/artifact-manifest.json +36 -36
  389. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  390. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  391. package/specialists/org/contracts/any-to-debugger.json +7 -1
  392. package/specialists/org/contracts/any-to-designer.json +7 -1
  393. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  394. package/specialists/org/contracts/any-to-explorer.json +13 -4
  395. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  396. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  397. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  398. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  399. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  400. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  401. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  402. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  403. package/specialists/org/contracts/architect-to-operations.json +17 -4
  404. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  405. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  406. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  407. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  408. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  409. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  410. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  411. package/specialists/org/contracts/operations-to-user.json +53 -0
  412. package/specialists/org/contracts/operations-triage-output.json +53 -0
  413. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  414. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  415. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  416. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  417. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  418. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  419. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  420. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  421. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  422. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  423. package/specialists/org/contracts/user-to-construct.json +11 -4
  424. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  425. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  426. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  427. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  428. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  429. package/specialists/org/groups/engineering-group.json +2 -6
  430. package/specialists/org/groups/governance-group.json +2 -3
  431. package/specialists/org/groups/operations-group.json +5 -8
  432. package/specialists/org/groups/product-group.json +4 -8
  433. package/specialists/org/groups/quality-group.json +3 -7
  434. package/specialists/org/groups/strategy-group.json +6 -9
  435. package/specialists/org/models.json.example +8 -0
  436. package/specialists/org/scopes/creative.json +6 -1
  437. package/specialists/org/scopes/operations.json +7 -1
  438. package/specialists/org/scopes/research.json +6 -1
  439. package/specialists/org/scopes/rnd.json +7 -1
  440. package/specialists/org/specialists/cx-architect.json +27 -8
  441. package/specialists/org/specialists/cx-designer.json +22 -8
  442. package/specialists/org/specialists/cx-engineer.json +71 -7
  443. package/specialists/org/specialists/cx-operations.json +89 -15
  444. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  445. package/specialists/org/specialists/cx-product-manager.json +28 -9
  446. package/specialists/org/specialists/cx-qa.json +16 -6
  447. package/specialists/org/specialists/cx-researcher.json +40 -7
  448. package/specialists/org/specialists/cx-reviewer.json +61 -11
  449. package/specialists/org/specialists/cx-security.json +30 -10
  450. package/specialists/org/teams/design-team.json +3 -4
  451. package/specialists/org/teams/engineering-team.json +3 -10
  452. package/specialists/org/teams/governance-team.json +3 -5
  453. package/specialists/org/teams/operations-team.json +6 -12
  454. package/specialists/org/teams/product-management-team.json +2 -3
  455. package/specialists/org/teams/quality-team.json +4 -12
  456. package/specialists/org/teams/research-team.json +3 -3
  457. package/specialists/org/teams/strategy-team.json +7 -14
  458. package/specialists/prompts/cx-architect.md +20 -1
  459. package/specialists/prompts/cx-data-analyst.md +1 -1
  460. package/specialists/prompts/cx-designer.md +12 -4
  461. package/specialists/prompts/cx-engineer.md +15 -1
  462. package/specialists/prompts/cx-operations.md +14 -2
  463. package/specialists/prompts/cx-orchestrator.md +9 -5
  464. package/specialists/prompts/cx-product-manager.md +5 -1
  465. package/specialists/prompts/cx-qa.md +6 -2
  466. package/specialists/prompts/cx-researcher.md +25 -30
  467. package/specialists/prompts/cx-reviewer.md +19 -1
  468. package/specialists/prompts/cx-security.md +5 -1
  469. package/templates/distribution/construct-brand.typ +123 -59
  470. package/templates/distribution/construct-decision.typ +6 -3
  471. package/templates/distribution/construct-pdf.typ +11 -4
  472. package/templates/distribution/construct-prd.typ +6 -3
  473. package/templates/distribution/construct-research.typ +7 -4
  474. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  475. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  476. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  477. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  478. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  479. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  480. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  481. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  482. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  483. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  484. package/specialists/org/specialists/cx-accessibility.json +0 -69
  485. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  486. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  487. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  488. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  489. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  490. package/specialists/org/specialists/cx-evaluator.json +0 -69
  491. package/specialists/org/specialists/cx-explorer.json +0 -69
  492. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  493. package/specialists/org/specialists/cx-oracle.json +0 -46
  494. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  495. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  496. package/specialists/org/specialists/cx-release-manager.json +0 -77
  497. package/specialists/org/specialists/cx-sre.json +0 -94
  498. package/specialists/org/specialists/cx-test-automation.json +0 -69
  499. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  500. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  501. package/specialists/org/teams/accessibility-team.json +0 -39
  502. package/specialists/org/teams/ux-research-team.json +0 -39
  503. package/specialists/prompts/cx-accessibility.md +0 -55
  504. package/specialists/prompts/cx-ai-engineer.md +0 -124
  505. package/specialists/prompts/cx-business-strategist.md +0 -58
  506. package/specialists/prompts/cx-data-engineer.md +0 -55
  507. package/specialists/prompts/cx-devil-advocate.md +0 -59
  508. package/specialists/prompts/cx-docs-keeper.md +0 -164
  509. package/specialists/prompts/cx-evaluator.md +0 -49
  510. package/specialists/prompts/cx-explorer.md +0 -71
  511. package/specialists/prompts/cx-legal-compliance.md +0 -58
  512. package/specialists/prompts/cx-oracle.md +0 -98
  513. package/specialists/prompts/cx-platform-engineer.md +0 -97
  514. package/specialists/prompts/cx-rd-lead.md +0 -59
  515. package/specialists/prompts/cx-release-manager.md +0 -54
  516. package/specialists/prompts/cx-sre.md +0 -111
  517. package/specialists/prompts/cx-test-automation.md +0 -55
  518. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  519. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -55,21 +55,23 @@
55
55
  #let construct-font-display = ("Space Grotesk",)
56
56
  #let construct-font-mono = ("JetBrains Mono",)
57
57
 
58
- #let horizontalrule = block(width: 100%, above: 0.85em, below: 0.95em)[
58
+ #let horizontalrule = block(width: 100%, above: 1.3em, below: 1.3em)[
59
59
  #line(length: 100%, stroke: 0.6pt + hairline-strong)
60
60
  ]
61
61
 
62
- // Type scale. One modular ramp shared by every document type so sizing never
63
- // drifts between a PRD, a research brief, and a decision record.
64
-
65
- #let fs-micro = 8pt
66
- #let fs-small = 8.5pt
67
- #let fs-meta = 9pt
68
- #let fs-body = 10.4pt
69
- #let fs-h4 = 8.5pt
70
- #let fs-h3 = 11pt
71
- #let fs-h2 = 13pt
72
- #let fs-h1 = 17pt
62
+ // Type scale. One modular ramp (~1.2 steps off a 10.5pt body) shared by every
63
+ // document type so sizing never drifts between a PRD, a research brief, and a
64
+ // decision record. Every step must be visibly distinct from its neighbours at
65
+ // reading distance — heading levels may not differ by weight alone.
66
+
67
+ #let fs-micro = 8pt // running header/footer, figure captions
68
+ #let fs-small = 8.5pt // table headers, bylines
69
+ #let fs-meta = 9pt // table body, block code
70
+ #let fs-body = 10.5pt // body prose
71
+ #let fs-h4 = 9pt // uppercase kicker heading
72
+ #let fs-h3 = 12pt // subsection heading
73
+ #let fs-h2 = 14pt // section heading
74
+ #let fs-h1 = 16.5pt // part heading (the document title belongs to the masthead)
73
75
  #let fs-subtitle = 11.5pt
74
76
  #let fs-title = 24pt
75
77
 
@@ -80,9 +82,25 @@
80
82
  #let wt-semibold = 600
81
83
  #let wt-bold = 700
82
84
 
85
+ // Shared page geometry. Layout templates import these instead of hardcoding
86
+ // margins so the document classes can never drift apart (they already had,
87
+ // before these tokens existed).
88
+
89
+ #let construct-page-paper = "a4"
90
+ #let construct-page-margin = (x: 2.15cm, top: 2cm, bottom: 2.4cm)
91
+
83
92
  #let construct-figure-max-width = 92%
84
93
  #let construct-figure-max-height = 3.4in
85
94
 
95
+ // Pandoc's typst writer encodes smart punctuation as markup ("---", "--", "..."),
96
+ // but template variables land inside Typst *strings*, where markup is never
97
+ // evaluated — without this a subtitle prints a literal "---". Normalize any
98
+ // metadata string before it reaches the page.
99
+
100
+ #let construct-smart-text(s) = {
101
+ str(s).replace("---", "\u{2014}").replace("--", "\u{2013}").replace("...", "\u{2026}")
102
+ }
103
+
86
104
  // A document-type word maps to a compact badge so the masthead reads as a
87
105
  // labelled artifact rather than a bare title.
88
106
 
@@ -185,24 +203,28 @@
185
203
  )
186
204
  if chips != none {
187
205
  chips
188
- v(0.62em)
206
+ v(0.7em)
189
207
  }
190
- text(font: construct-font-display, size: fs-title, weight: wt-semibold, fill: ink, tracking: -0.02em)[#title]
208
+
209
+ // Display sizes need display leading: the body's paragraph leading applied at
210
+ // 26pt reads as a gap, not a title, so the title paragraph pins its own.
211
+
212
+ par(leading: 0.42em, text(font: construct-font-display, size: fs-title, weight: wt-semibold, fill: ink, tracking: -0.02em)[#construct-smart-text(title)])
191
213
  if subtitle != "" {
192
- v(0.45em)
193
- text(font: construct-font-sans, size: fs-subtitle, fill: ink-muted, tracking: -0.005em)[#subtitle]
214
+ v(0.55em)
215
+ par(leading: 0.5em, text(font: construct-font-sans, size: fs-subtitle, fill: ink-muted, tracking: -0.005em)[#construct-smart-text(subtitle)])
194
216
  }
195
217
  let byline = construct-editorial-line(status, owner, date)
196
218
  if byline != none {
197
- v(0.78em)
219
+ v(0.95em)
198
220
  byline
199
221
  }
200
- v(0.95em)
222
+ v(1.15em)
201
223
  block(width: 100%, height: 2pt, {
202
224
  place(left + horizon, line(length: 100%, stroke: 0.5pt + hairline))
203
225
  place(left + horizon, line(length: 46pt, stroke: 2pt + ink))
204
226
  })
205
- v(1.05em)
227
+ v(1.5em)
206
228
  }
207
229
 
208
230
  // Shared callout shell: a grey panel with a black left bar and a small bold
@@ -216,6 +238,8 @@
216
238
  inset: (left: 14pt, top: 11pt, bottom: 11pt, right: 13pt),
217
239
  width: 100%,
218
240
  radius: (right: 3pt),
241
+ above: 1.05em,
242
+ below: 1.05em,
219
243
  )[
220
244
  #set par(leading: 0.66em, spacing: 0.72em)
221
245
  #set text(font: construct-font-sans, size: fs-body, fill: ink-body)
@@ -223,7 +247,6 @@
223
247
  #v(0.42em)
224
248
  #body
225
249
  ]
226
- v(0.85em)
227
250
  }
228
251
 
229
252
  #let construct-at-a-glance(body) = construct-callout("At a glance", body, tint: surface-alt)
@@ -242,12 +265,13 @@
242
265
  inset: 13pt,
243
266
  width: 100%,
244
267
  radius: (bottom: 3pt),
268
+ above: 1.05em,
269
+ below: 1.05em,
245
270
  )[
246
271
  #text(font: construct-font-sans, size: 7.5pt, weight: wt-bold, fill: ink, tracking: 0.09em)[KEY METRICS]
247
272
  #v(0.48em)
248
273
  #body
249
274
  ]
250
- v(0.85em)
251
275
  }
252
276
 
253
277
  // Running header appears from page two: short title at left, doc id and page at
@@ -256,7 +280,11 @@
256
280
  #let construct-running-header(title, doc-id: "", version: "") = context {
257
281
  if counter(page).get().first() > 1 [
258
282
  #set text(font: construct-font-sans, size: fs-micro, fill: ink-muted)
259
- #let short-title = if title.len() > 52 { title.slice(0, 49) + "…" } else { title }
283
+ // Truncate on grapheme clusters, not bytes: slice() panics mid-codepoint on
284
+ // multibyte characters (em dashes, accents) in long titles.
285
+ #let clean-title = construct-smart-text(title)
286
+ #let glyphs = clean-title.clusters()
287
+ #let short-title = if glyphs.len() > 52 { glyphs.slice(0, 49).join("") + "…" } else { clean-title }
260
288
  #text(fill: ink-muted)[#short-title]
261
289
  #h(1fr)
262
290
  #if doc-id != "" [
@@ -273,19 +301,34 @@
273
301
  ]
274
302
  }
275
303
 
304
+ // Running footer: a quiet wordmark line, not a second headline. The header
305
+ // already carries the strong ink; the footer stays in the muted register so
306
+ // page chrome frames the content instead of competing with it.
307
+
276
308
  #let construct-running-footer(footer-label, classification: "") = context {
277
309
  if counter(page).get().first() > 1 [
278
310
  #v(0.3em)
279
311
  #line(length: 100%, stroke: 0.4pt + hairline)
280
- #v(0.28em)
312
+ #v(0.32em)
281
313
  #set text(font: construct-font-sans, size: fs-micro, fill: ink-faint)
282
314
  #align(center)[
283
- #text(weight: wt-bold, fill: ink, tracking: 0.04em)[#footer-label]
284
- #if classification != "" [ #sym.dot.c #upper(classification)]
315
+ #text(weight: wt-semibold, fill: ink-muted, tracking: 0.16em)[CONSTRUCT]
316
+ #text(fill: ink-faint)[ #sym.dot.c ]
317
+ #text(fill: ink-muted)[#footer-label]
318
+ #if classification != "" [ #text(fill: ink-faint)[ #sym.dot.c ] #upper(classification)]
285
319
  ]
286
320
  ]
287
321
  }
288
322
 
323
+ // Pandoc's typst writer has emitted two different calls for a markdown
324
+ // blockquote across versions: older releases call a bare `blockquote(...)`
325
+ // (relying on their own default template to define it, which ours does not),
326
+ // newer releases call Typst's native `quote(block: true, ...)` (handled by the
327
+ // `show quote:` rule below). Defining `blockquote` as a forward to `quote` keeps
328
+ // both pandoc generations rendering through the same show rule.
329
+
330
+ #let blockquote(body) = quote(block: true, body)
331
+
289
332
  // The theme function carries every body-level #set/#show rule. Module-scope set
290
333
  // rules do NOT propagate through `#import`, so each layout template applies this
291
334
  // with `#show: construct-theme` to wrap the Pandoc $body$ in the styled scope.
@@ -303,8 +346,14 @@
303
346
  // preserve Typst's native counter context or every item will render as `1.`.
304
347
  // Spacing belongs on the list container, not on a wrapped enum.item block.
305
348
 
306
- set list(marker: (text(fill: ink)[•], text(fill: ink-muted)[‣], text(fill: ink-faint)[–]), indent: 2pt, body-indent: 9pt, spacing: 1.02em)
307
- set enum(numbering: "1.", indent: 2pt, body-indent: 9pt, spacing: 1.02em)
349
+ // Indents are em-based so they track the type size, and identical between
350
+ // bullets and numbers so both list kinds share one text column. Item gaps
351
+ // (1em) sit between the in-item line leading (0.9em) and the paragraph gap
352
+ // (1.24em): wrapped lines, items, and blocks stay three distinct rhythms.
353
+
354
+ set list(marker: (text(fill: ink)[•], text(fill: ink-muted)[‣], text(fill: ink-faint)[–]), indent: 0.25em, body-indent: 0.65em, spacing: 1em)
355
+ set enum(numbering: "1.", indent: 0.25em, body-indent: 0.65em, spacing: 1em)
356
+ set terms(hanging-indent: 0.9em, spacing: 1em)
308
357
 
309
358
  // Links carry ink with a hairline underline so citations stay legible without
310
359
  // introducing color.
@@ -317,36 +366,38 @@
317
366
  // Headings. Level 2 is the working section header (the H1 title is rendered by
318
367
  // the masthead and stripped from the body), so it carries a short black rule.
319
368
 
369
+ // Heading rhythm: space above a heading is always noticeably larger than the
370
+ // space below it, so each heading binds to the section it opens rather than
371
+ // floating between two blocks. Above:below stays near 2:1 at every level.
372
+ // Boundary spacing lives on `block(above:, below:)` — a trailing weak v()
373
+ // inside the block sits at a block boundary, where Typst trims it to zero,
374
+ // so the gap under a heading silently never rendered.
375
+
320
376
  show heading.where(level: 1): it => {
321
- v(1.2em, weak: true)
322
- block(sticky: true, {
377
+ block(sticky: true, above: 2em, below: 0.9em, {
378
+ set par(leading: 0.45em)
323
379
  text(font: construct-font-display, size: fs-h1, weight: wt-semibold, fill: ink, tracking: -0.015em)[#it.body]
324
- v(0.22em)
380
+ v(0.3em)
325
381
  line(length: 100%, stroke: 0.5pt + hairline)
326
- v(0.7em, weak: true)
327
382
  })
328
383
  }
329
384
  show heading.where(level: 2): it => {
330
- v(1.65em, weak: true)
331
- block(sticky: true, {
385
+ block(sticky: true, above: 2.6em, below: 0.95em, {
386
+ set par(leading: 0.45em)
332
387
  text(font: construct-font-display, size: fs-h2, weight: wt-semibold, fill: ink, tracking: -0.01em)[#it.body]
333
- v(0.4em)
388
+ v(0.42em)
334
389
  line(length: 26pt, stroke: 1.5pt + ink)
335
- v(0.78em, weak: true)
336
390
  })
337
391
  }
338
392
  show heading.where(level: 3): it => {
339
- v(1.15em, weak: true)
340
- block(sticky: true, {
393
+ block(sticky: true, above: 1.8em, below: 0.9em, {
394
+ set par(leading: 0.45em)
341
395
  text(font: construct-font-sans, size: fs-h3, weight: wt-semibold, fill: ink-strong)[#it.body]
342
- v(0.48em, weak: true)
343
396
  })
344
397
  }
345
398
  show heading.where(level: 4): it => {
346
- v(0.75em, weak: true)
347
- block(sticky: true, {
399
+ block(sticky: true, above: 1.2em, below: 0.35em, {
348
400
  text(font: construct-font-sans, size: fs-h4, weight: wt-bold, fill: ink-muted, tracking: 0.07em)[#upper(it.body)]
349
- v(0.22em, weak: true)
350
401
  })
351
402
  }
352
403
 
@@ -365,6 +416,8 @@
365
416
  inset: 11pt,
366
417
  radius: 4pt,
367
418
  width: 100%,
419
+ above: 1.24em,
420
+ below: 1.24em,
368
421
  )[#text(font: construct-font-mono, size: fs-meta, fill: ink-body)[#it]]
369
422
 
370
423
  // Tables use a horizontal-only rule system: a strong black line under the bold
@@ -396,43 +449,54 @@
396
449
 
397
450
  // Pandoc wraps markdown tables in a centered figure; lift the table out and
398
451
  // left-align it so it reads as a clean data block aligned to the text column.
452
+ // The lift must re-supply the vertical rhythm the figure wrapper carried —
453
+ // without it a table sits flush against the heading above and prose below.
454
+ // Figures are unbreakable blocks even when a show rule replaces their body,
455
+ // so without the `set block(breakable: true)` a long table jumps whole to
456
+ // the next page and strands half a page of dead space behind it.
399
457
 
458
+ show figure.where(kind: table): set block(breakable: true)
400
459
  show figure.where(kind: table): it => {
401
- if it.body.has("body") { align(left, it.body.body) } else { align(left, it.body) }
460
+ let content = if it.body.has("body") { it.body.body } else { it.body }
461
+ block(above: 1.2em, below: 1.35em, width: 100%, align(left, content))
402
462
  }
403
463
 
404
- // Figures: a light bordered frame, a constrained width, and a caption that
405
- // leads with a bold figure number.
464
+ // Figures: a light bordered frame, a caption that leads with a bold figure
465
+ // number, and measured scaling instead of a forced box. Fixing both width and
466
+ // height with fit:contain letterboxed every image — a wide diagram reserved
467
+ // the full max height and floated tiny inside dead whitespace. Measure the
468
+ // natural size, scale to fit width and the height cap, never upscale, and let
469
+ // the frame hug the result.
406
470
 
407
471
  set figure(supplement: "Figure")
408
- show figure.where(kind: image): it => {
409
- v(0.95em, weak: true)
472
+ show figure.where(kind: image): it => block(above: 1.2em, below: 1.2em, breakable: false, {
410
473
  block(
411
474
  stroke: 0.5pt + hairline,
412
475
  fill: white,
413
- inset: 9pt,
476
+ inset: 10pt,
414
477
  width: 100%,
415
478
  radius: 4pt,
416
479
  )[
417
- #align(center)[
418
- #show image: img => image(
419
- img,
420
- width: construct-figure-max-width,
421
- height: construct-figure-max-height,
422
- fit: "contain",
423
- )
424
- #it.body
425
- ]
480
+ #align(center, layout(avail => {
481
+ let natural = measure(it.body)
482
+ if natural.width == 0pt or natural.height == 0pt { it.body } else {
483
+ let f = calc.min(
484
+ (avail.width * construct-figure-max-width) / natural.width,
485
+ construct-figure-max-height / natural.height,
486
+ 1,
487
+ )
488
+ scale(x: f * 100%, y: f * 100%, reflow: true, it.body)
489
+ }
490
+ }))
426
491
  ]
427
492
  if it.caption != none {
428
- v(0.35em)
493
+ v(0.4em)
429
494
  align(center)[
430
495
  #text(font: construct-font-sans, size: fs-micro, weight: wt-bold, fill: ink, tracking: 0.03em)[#it.supplement #it.counter.display()]
431
496
  #text(font: construct-font-sans, size: fs-micro, fill: ink-muted)[ — #it.caption.body]
432
497
  ]
433
498
  }
434
- v(0.95em, weak: true)
435
- }
499
+ })
436
500
 
437
501
  // Blockquotes route to callouts: a quote carrying a Metric/Baseline table
438
502
  // becomes the key-metrics card, anything else becomes the at-a-glance aside.
@@ -1,5 +1,8 @@
1
1
  /**
2
- * templates/distribution/construct-decision.typ — Construct distribution PDF.
2
+ * templates/distribution/construct-decision.typ — layout for decision-class
3
+ * artifacts (adr, rfc, rfc-platform, security-audit-report). Same brand system
4
+ * as every other class; only the footer label differs. Metadata contract
5
+ * documented in construct-pdf.typ.
3
6
  */
4
7
 
5
8
  #import "construct-brand.typ": *
@@ -7,8 +10,8 @@
7
10
  #show: construct-theme
8
11
 
9
12
  #set page(
10
- paper: "a4",
11
- margin: (x: 2.15cm, top: 1.95cm, bottom: 2.35cm),
13
+ paper: construct-page-paper,
14
+ margin: construct-page-margin,
12
15
  numbering: "1",
13
16
  header: construct-running-header(
14
17
  "$if(title)$$title$$endif$",
@@ -1,5 +1,12 @@
1
1
  /**
2
- * templates/distribution/construct-pdf.typ — Construct distribution PDF.
2
+ * templates/distribution/construct-pdf.typ — fallback layout for any artifact
3
+ * type without a mapped class (see lib/publish-template.mjs ARTIFACT_TEMPLATE_MAP).
4
+ *
5
+ * Metadata contract (Pandoc -M vars, sourced from artifact YAML frontmatter by
6
+ * lib/publish-template.mjs parseArtifactMetadata): title, subtitle, date, status,
7
+ * owner, artifactType, version, docId, classification. All optional; the masthead
8
+ * and running chrome degrade gracefully when a field is absent. Page geometry and
9
+ * all type/spacing tokens come from construct-brand.typ — never hardcode them here.
3
10
  */
4
11
 
5
12
  #import "construct-brand.typ": *
@@ -7,8 +14,8 @@
7
14
  #show: construct-theme
8
15
 
9
16
  #set page(
10
- paper: "a4",
11
- margin: (x: 2.15cm, top: 1.95cm, bottom: 2.35cm),
17
+ paper: construct-page-paper,
18
+ margin: construct-page-margin,
12
19
  numbering: "1",
13
20
  header: construct-running-header(
14
21
  "$if(title)$$title$$endif$",
@@ -16,7 +23,7 @@
16
23
  version: "$if(version)$$version$$endif$",
17
24
  ),
18
25
  footer: construct-running-footer(
19
- "Brief",
26
+ "Document",
20
27
  classification: "$if(classification)$$classification$$endif$",
21
28
  ),
22
29
  )
@@ -1,5 +1,8 @@
1
1
  /**
2
- * templates/distribution/construct-prd.typ — Construct distribution PDF.
2
+ * templates/distribution/construct-prd.typ — layout for editorial/product-class
3
+ * artifacts (prd, meta-prd, strategy, runbook, memo, one-pager, postmortem, …).
4
+ * Same brand system as every other class; only the footer label differs.
5
+ * Metadata contract documented in construct-pdf.typ.
3
6
  */
4
7
 
5
8
  #import "construct-brand.typ": *
@@ -7,8 +10,8 @@
7
10
  #show: construct-theme
8
11
 
9
12
  #set page(
10
- paper: "a4",
11
- margin: (x: 2.15cm, top: 1.95cm, bottom: 2.35cm),
13
+ paper: construct-page-paper,
14
+ margin: construct-page-margin,
12
15
  numbering: "1",
13
16
  header: construct-running-header(
14
17
  "$if(title)$$title$$endif$",
@@ -1,5 +1,8 @@
1
1
  /**
2
- * templates/distribution/construct-research.typ — Construct distribution PDF.
2
+ * templates/distribution/construct-research.typ — layout for research-class
3
+ * artifacts (research-brief, evidence-brief, signal-brief, research-finding,
4
+ * product-intelligence-report). Same brand system as every other class; only
5
+ * the footer label differs. Metadata contract documented in construct-pdf.typ.
3
6
  */
4
7
 
5
8
  #import "construct-brand.typ": *
@@ -7,8 +10,8 @@
7
10
  #show: construct-theme
8
11
 
9
12
  #set page(
10
- paper: "a4",
11
- margin: (x: 2cm, top: 1.9cm, bottom: 2.25cm),
13
+ paper: construct-page-paper,
14
+ margin: construct-page-margin,
12
15
  numbering: "1",
13
16
  header: construct-running-header(
14
17
  "$if(title)$$title$$endif$",
@@ -16,7 +19,7 @@
16
19
  version: "$if(version)$$version$$endif$",
17
20
  ),
18
21
  footer: construct-running-footer(
19
- "Research analytics",
22
+ "Research brief",
20
23
  classification: "$if(classification)$$classification$$endif$",
21
24
  ),
22
25
  )
@@ -1,115 +0,0 @@
1
- /**
2
- * providers/git/index.mjs — git repo provider.
3
- *
4
- * Transport: git CLI (via child_process). No external deps.
5
- *
6
- * Capabilities: read, search
7
- *
8
- * read refs:
9
- * "commits" → recent commits (default 20)
10
- * "commits:<n>" → last n commits
11
- * "branches" → all branch names
12
- * "file:<path>" → file contents at HEAD
13
- * "status" → working tree status
14
- * "log:<path>" → commit log for a specific path
15
- *
16
- * search: grep across tracked files (git grep)
17
- */
18
-
19
- import { spawnSync } from 'node:child_process';
20
- import path from 'node:path';
21
- import { AuthError, NotFoundError } from '../../errors.mjs';
22
-
23
- function git(args, cwd) {
24
- const result = spawnSync('git', args, { cwd, encoding: 'utf8' });
25
- if (result.error) throw new Error(`git spawn error: ${result.error.message}`);
26
- return { stdout: result.stdout ?? '', stderr: result.stderr ?? '', status: result.status };
27
- }
28
-
29
- export default {
30
- name: 'git',
31
- capabilities: ['read', 'search'],
32
-
33
- _cwd: null,
34
-
35
- async init(config = {}) {
36
- const cwd = config.cwd ?? process.cwd();
37
- // Verify this is actually a git repo
38
- const check = git(['rev-parse', '--git-dir'], cwd);
39
- if (check.status !== 0) {
40
- throw new AuthError(`Not a git repository: ${cwd}`, { provider: 'git' });
41
- }
42
- this._cwd = cwd;
43
- },
44
-
45
- async read(ref, _opts = {}) {
46
- const cwd = this._cwd ?? process.cwd();
47
-
48
- if (ref === 'branches') {
49
- const r = git(['branch', '-a', '--format=%(refname:short)'], cwd);
50
- return r.stdout.trim().split('\n').filter(Boolean).map((name) => ({ type: 'branch', name }));
51
- }
52
-
53
- if (ref === 'status') {
54
- const r = git(['status', '--porcelain=v1'], cwd);
55
- return r.stdout.trim().split('\n').filter(Boolean).map((line) => ({
56
- type: 'status-entry',
57
- code: line.slice(0, 2).trim(),
58
- path: line.slice(3),
59
- }));
60
- }
61
-
62
- if (ref.startsWith('commits') || ref === 'commits') {
63
- const n = ref.includes(':') ? parseInt(ref.split(':')[1], 10) : 20;
64
- const r = git(['log', `--max-count=${n}`, '--pretty=format:%H\t%an\t%ae\t%ai\t%s'], cwd);
65
- return r.stdout.trim().split('\n').filter(Boolean).map((line) => {
66
- const [hash, author, email, date, ...subjectParts] = line.split('\t');
67
- return { type: 'commit', hash, author, email, date, subject: subjectParts.join('\t') };
68
- });
69
- }
70
-
71
- if (ref.startsWith('file:')) {
72
- const filePath = ref.slice(5);
73
- const r = git(['show', `HEAD:${filePath}`], cwd);
74
- if (r.status !== 0) {
75
- throw new NotFoundError(`File not found in HEAD: ${filePath}`, { provider: 'git' });
76
- }
77
- return [{ type: 'file', path: filePath, content: r.stdout }];
78
- }
79
-
80
- if (ref.startsWith('log:')) {
81
- const filePath = ref.slice(4);
82
- const r = git(['log', '--follow', '--pretty=format:%H\t%an\t%ai\t%s', '--', filePath], cwd);
83
- return r.stdout.trim().split('\n').filter(Boolean).map((line) => {
84
- const [hash, author, date, ...subjectParts] = line.split('\t');
85
- return { type: 'commit', hash, author, date, subject: subjectParts.join('\t'), path: filePath };
86
- });
87
- }
88
-
89
- throw new NotFoundError(`Unknown git read ref: "${ref}"`, { provider: 'git' });
90
- },
91
-
92
- async search(query, opts = {}) {
93
- const cwd = this._cwd ?? process.cwd();
94
- const args = ['grep', '--line-number', '-I'];
95
- if (opts.ignoreCase) args.push('-i');
96
- args.push(query);
97
- if (opts.paths) args.push('--', ...opts.paths);
98
- const r = git(args, cwd);
99
- if (r.status === 1 && r.stdout === '') return []; // no matches
100
- if (r.status !== 0 && r.status !== 1) {
101
- throw new Error(`git grep failed: ${r.stderr}`);
102
- }
103
- return r.stdout.trim().split('\n').filter(Boolean).map((line) => {
104
- const colonIdx = line.indexOf(':');
105
- const rest = line.slice(colonIdx + 1);
106
- const lineNumIdx = rest.indexOf(':');
107
- return {
108
- type: 'grep-match',
109
- file: line.slice(0, colonIdx),
110
- lineNumber: parseInt(rest.slice(0, lineNumIdx), 10),
111
- text: rest.slice(lineNumIdx + 1),
112
- };
113
- });
114
- },
115
- };