@geraldmaron/construct 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (497) hide show
  1. package/README.md +7 -10
  2. package/bin/construct +286 -220
  3. package/bin/construct-postinstall.mjs +0 -21
  4. package/commands/work/optimize-prompts.md +1 -1
  5. package/examples/distribution/sources/adr.md +2 -2
  6. package/examples/seed-observations/decisions.md +1 -1
  7. package/lib/artifact-loop-core.mjs +412 -0
  8. package/lib/artifact-manifest.mjs +6 -0
  9. package/lib/artifact-release-gate.mjs +115 -49
  10. package/lib/audit-rules.mjs +2 -4
  11. package/lib/audit-skills.mjs +32 -20
  12. package/lib/audit-specialists.mjs +44 -26
  13. package/lib/auto-docs.mjs +6 -5
  14. package/lib/brand-prose.mjs +3 -3
  15. package/lib/brand-tokens.mjs +2 -2
  16. package/lib/certification/artifact-fixtures.mjs +4 -1
  17. package/lib/certification/demo-parity.mjs +6 -32
  18. package/lib/certification/real-llm-scenarios.mjs +58 -13
  19. package/lib/certification/role-cards.mjs +7 -6
  20. package/lib/certification/role-overlays.mjs +4 -4
  21. package/lib/certification/runner.mjs +9 -2
  22. package/lib/certification/skill-inventory.mjs +34 -22
  23. package/lib/certification/skill-scenarios.mjs +21 -8
  24. package/lib/certification/specialist-contracts.mjs +4 -3
  25. package/lib/certification/specialist-scenarios.mjs +7 -6
  26. package/lib/certification/status.mjs +5 -4
  27. package/lib/cli-commands.mjs +56 -57
  28. package/lib/comment-lint.mjs +1 -5
  29. package/lib/completions.mjs +20 -2
  30. package/lib/config/legacy-config-migration.mjs +125 -0
  31. package/lib/config/schema.mjs +5 -5
  32. package/lib/config/source-targets.mjs +80 -0
  33. package/lib/contracts/validate.mjs +119 -51
  34. package/lib/decisions/golden.mjs +3 -2
  35. package/lib/decisions/registry.mjs +11 -6
  36. package/lib/demo-project.mjs +188 -0
  37. package/lib/demo-recording.mjs +9 -34
  38. package/lib/demo-script.mjs +2 -2
  39. package/lib/demo-surface.mjs +13 -131
  40. package/lib/demo-tour-renderer.mjs +92 -0
  41. package/lib/demo.mjs +69 -54
  42. package/lib/diagram-export.mjs +63 -14
  43. package/lib/doctor/source-checkout.mjs +3 -4
  44. package/lib/doctor/watchers/consistency.mjs +90 -22
  45. package/lib/doctor/watchers/credential-parity.mjs +97 -0
  46. package/lib/doctor/watchers/mcp-protocol.mjs +1 -1
  47. package/lib/doctor/watchers/service-health.mjs +4 -32
  48. package/lib/document-export.mjs +73 -9
  49. package/lib/document-extract.mjs +10 -198
  50. package/lib/embed/artifact.mjs +2 -3
  51. package/lib/embed/cli.mjs +1 -1
  52. package/lib/embed/config.mjs +1 -2
  53. package/lib/embed/customer-profiles.mjs +1 -1
  54. package/lib/embed/daemon.mjs +5 -6
  55. package/lib/embed/demand-fetch.mjs +114 -2
  56. package/lib/embed/docs-lifecycle.mjs +9 -5
  57. package/lib/embed/intake-metrics.mjs +2 -2
  58. package/lib/embed/notifications.mjs +2 -3
  59. package/lib/embed/recommendation-store.mjs +25 -22
  60. package/lib/embed/role-framing.mjs +4 -9
  61. package/lib/embedded-contract/capability.mjs +4 -2
  62. package/lib/embedded-contract/contract-version.mjs +1 -1
  63. package/lib/env-config.mjs +2 -2
  64. package/lib/flavors/loader.mjs +21 -19
  65. package/lib/graph/build-from-registry.mjs +31 -7
  66. package/lib/graph/staleness.mjs +1 -1
  67. package/lib/headhunt.mjs +82 -35
  68. package/lib/hooks/agent-tracker.mjs +2 -2
  69. package/lib/hooks/config-protection.mjs +1 -1
  70. package/lib/hooks/registry-sync.mjs +3 -3
  71. package/lib/host-capabilities.mjs +5 -5
  72. package/lib/improvement/controller.mjs +2 -2
  73. package/lib/init-unified.mjs +19 -21
  74. package/lib/intake/classify.mjs +26 -0
  75. package/lib/intake/daemon.mjs +2 -3
  76. package/lib/intake/prepare.mjs +10 -3
  77. package/lib/intake/session-prelude.mjs +1 -1
  78. package/lib/intake/tables/creative.mjs +9 -9
  79. package/lib/intake/tables/operations.mjs +4 -4
  80. package/lib/intake/tables/research.mjs +2 -2
  81. package/lib/integrations/intake-integrations.mjs +9 -10
  82. package/lib/knowledge/research-store.mjs +2 -2
  83. package/lib/mcp/server.mjs +104 -61
  84. package/lib/mcp/tool-budget.mjs +1 -46
  85. package/lib/mcp/tool-recovery.mjs +44 -0
  86. package/lib/mcp/tools/artifact-author.mjs +36 -0
  87. package/lib/mcp/tools/find-tool.mjs +86 -0
  88. package/lib/mcp/tools/orchestration-run.mjs +107 -69
  89. package/lib/mcp/tools/project.mjs +4 -3
  90. package/lib/mcp/tools/{profile.mjs → scope.mjs} +54 -52
  91. package/lib/mcp/tools/skills.mjs +39 -22
  92. package/lib/mcp/tools/telemetry.mjs +2 -1
  93. package/lib/mcp/tools/workflow.mjs +1 -1
  94. package/lib/mcp-platform-config.mjs +7 -5
  95. package/lib/migrations/index.mjs +4 -2
  96. package/lib/migrations/v2-unified-registry.mjs +35 -0
  97. package/lib/model-router.mjs +203 -36
  98. package/lib/models/catalog.mjs +25 -9
  99. package/lib/models/execution-capability-profile.mjs +3 -3
  100. package/lib/models/execution-policy.mjs +7 -6
  101. package/lib/models/provider-poll.mjs +18 -4
  102. package/lib/opencode-config.mjs +56 -1
  103. package/lib/opencode-runtime-plugin.mjs +13 -10
  104. package/lib/oracle/artifact-gate.mjs +14 -4
  105. package/lib/oracle/cli.mjs +2 -0
  106. package/lib/oracle/dispatch.mjs +18 -1
  107. package/lib/oracle/execute.mjs +39 -7
  108. package/lib/oracle/index.mjs +1 -1
  109. package/lib/oracle/org-graph.mjs +10 -5
  110. package/lib/oracle/policy.mjs +6 -0
  111. package/lib/oracle/read-model.mjs +103 -6
  112. package/lib/oracle/reconcile.mjs +31 -4
  113. package/lib/oracle/remediation-dispatch.mjs +53 -0
  114. package/lib/oracle/routing.mjs +5 -0
  115. package/lib/oracle/synthesize.mjs +80 -3
  116. package/lib/orchestration/routing-tables.mjs +44 -10
  117. package/lib/orchestration/runtime.mjs +2 -0
  118. package/lib/orchestration/worker.mjs +1 -1
  119. package/lib/orchestration-policy.mjs +351 -62
  120. package/lib/overrides/resolver.mjs +1 -12
  121. package/lib/parity.mjs +37 -11
  122. package/lib/policy/engine.mjs +80 -15
  123. package/lib/prompt-composer.js +51 -15
  124. package/lib/prompt-metadata.mjs +3 -2
  125. package/lib/providers/connection-probe.mjs +58 -0
  126. package/lib/providers/copilot-auth.mjs +1 -1
  127. package/lib/providers/credential-bootstrap.mjs +1 -1
  128. package/lib/providers/op-run.mjs +3 -4
  129. package/lib/providers/secret-resolver.mjs +31 -0
  130. package/lib/publish-tooling.mjs +3 -11
  131. package/lib/publish.mjs +12 -29
  132. package/lib/reconcile/mcp-entry-reconcile.mjs +14 -9
  133. package/lib/reflect.mjs +2 -2
  134. package/lib/registry/agent-manifest.mjs +190 -0
  135. package/lib/registry/assemble.mjs +133 -0
  136. package/lib/registry/catalog.mjs +171 -0
  137. package/lib/registry/cli.mjs +590 -6
  138. package/lib/registry/consolidation.mjs +5 -4
  139. package/lib/registry/docs-sync.mjs +67 -0
  140. package/lib/registry/loader.mjs +147 -0
  141. package/lib/registry/org-io.mjs +76 -0
  142. package/lib/registry/retired-paths.mjs +71 -0
  143. package/lib/registry/surface-map.mjs +5 -7
  144. package/lib/registry/validator.mjs +438 -0
  145. package/lib/research-execution-policy.mjs +173 -0
  146. package/lib/roles/catalog.mjs +4 -9
  147. package/lib/roles/fence.mjs +107 -1
  148. package/lib/roles/flavor-bindings.mjs +68 -0
  149. package/lib/roles/gateway.mjs +140 -29
  150. package/lib/roles/manifest.mjs +22 -13
  151. package/lib/roles/router.mjs +1 -1
  152. package/lib/runtime-env.mjs +1 -1
  153. package/lib/scopes/enrich.mjs +67 -0
  154. package/lib/scopes/hooks.mjs +12 -0
  155. package/lib/{profiles → scopes}/lifecycle.mjs +70 -70
  156. package/lib/scopes/loader.mjs +104 -0
  157. package/lib/scopes/rebrand.mjs +32 -0
  158. package/lib/scopes/research-profile.mjs +16 -0
  159. package/lib/scopes/teams.mjs +98 -0
  160. package/lib/service-manager.mjs +1 -117
  161. package/lib/setup-prompts.mjs +1 -1
  162. package/lib/setup.mjs +17 -28
  163. package/lib/skills/composition-graph.mjs +98 -0
  164. package/lib/skills/router.mjs +80 -0
  165. package/lib/specialist-contracts.mjs +17 -18
  166. package/lib/specialists/postconditions.mjs +2 -2
  167. package/lib/specialists/prompt-schema.mjs +6 -5
  168. package/lib/specialists/roster.mjs +7 -12
  169. package/lib/specialists/schema.mjs +9 -6
  170. package/lib/status.mjs +25 -90
  171. package/lib/storage/file-lock.mjs +6 -3
  172. package/lib/telemetry/skill-calls.mjs +1 -1
  173. package/lib/templates/doc-presentation.mjs +63 -0
  174. package/lib/templates/visual-requirements.mjs +35 -2
  175. package/lib/term-format.mjs +107 -2
  176. package/lib/test-env-setup.mjs +21 -0
  177. package/lib/ui/components.mjs +42 -0
  178. package/lib/ui/glyphs.mjs +58 -0
  179. package/lib/ui/links.mjs +115 -0
  180. package/lib/ui/theme.mjs +108 -0
  181. package/lib/uninstall/uninstall.mjs +2 -1
  182. package/lib/validator.mjs +45 -8
  183. package/lib/validators/skill-effectiveness.mjs +174 -0
  184. package/lib/validators/skills.mjs +1 -1
  185. package/package.json +12 -7
  186. package/personas/construct.md +12 -3
  187. package/platforms/claude/CLAUDE.md +1 -1
  188. package/rules/common/beads-hygiene.md +52 -0
  189. package/rules/common/neurodivergent-output.md +4 -7
  190. package/rules/common/research.md +2 -0
  191. package/scripts/sync-specialists.mjs +260 -49
  192. package/skills/ai/prompt-optimizer.md +3 -3
  193. package/skills/brand/output-vibe.md +48 -0
  194. package/skills/docs/adr-workflow.md +3 -0
  195. package/skills/docs/backlog-proposal-workflow.md +3 -0
  196. package/skills/docs/codebase-research-workflow.md +5 -2
  197. package/skills/docs/customer-profile-workflow.md +3 -0
  198. package/skills/docs/document-ingest-workflow.md +3 -0
  199. package/skills/docs/evidence-ingest-workflow.md +4 -1
  200. package/skills/docs/init-project.md +1 -1
  201. package/skills/docs/prd-workflow.md +1 -1
  202. package/skills/docs/prfaq-workflow.md +3 -0
  203. package/skills/docs/product-intelligence-workflow.md +4 -1
  204. package/skills/docs/product-signal-workflow.md +3 -0
  205. package/skills/docs/research-workflow.md +13 -6
  206. package/skills/docs/runbook-workflow.md +3 -0
  207. package/skills/docs/strategy-workflow.md +3 -0
  208. package/skills/docs/user-research-workflow.md +6 -3
  209. package/skills/operating/orchestration-reference.md +1 -1
  210. package/skills/roles/{engineer.ai.md → ai-engineer.md} +2 -2
  211. package/skills/roles/architect.ai-systems.md +1 -1
  212. package/skills/roles/architect.data.md +1 -1
  213. package/skills/roles/architect.enterprise.md +1 -1
  214. package/skills/roles/architect.integration.md +1 -1
  215. package/skills/roles/architect.md +1 -1
  216. package/skills/roles/architect.platform.md +1 -1
  217. package/skills/roles/{product-manager.business-strategy.md → business-strategist.md} +3 -3
  218. package/skills/roles/data-analyst.experiment.md +1 -1
  219. package/skills/roles/data-analyst.md +1 -1
  220. package/skills/roles/data-analyst.product-intelligence.md +1 -1
  221. package/skills/roles/data-analyst.product.md +1 -1
  222. package/skills/roles/data-analyst.telemetry.md +1 -1
  223. package/skills/roles/{engineer.data.md → data-engineer.md} +2 -2
  224. package/skills/roles/data-engineer.pipeline.md +2 -2
  225. package/skills/roles/data-engineer.vector-retrieval.md +2 -2
  226. package/skills/roles/data-engineer.warehouse.md +2 -2
  227. package/skills/roles/debugger.md +1 -1
  228. package/skills/roles/designer.accessibility.md +1 -1
  229. package/skills/roles/designer.md +1 -1
  230. package/skills/roles/{reviewer.devil-advocate.md → devil-advocate.md} +2 -2
  231. package/skills/roles/{operator.docs.md → docs-keeper.md} +3 -3
  232. package/skills/roles/engineer.md +3 -7
  233. package/skills/roles/{reviewer.evaluator.md → evaluator.md} +2 -2
  234. package/skills/roles/{researcher.explorer.md → explorer.md} +2 -2
  235. package/skills/roles/{operator.md → operations.md} +2 -5
  236. package/skills/roles/orchestrator.md +1 -1
  237. package/skills/roles/{engineer.platform.md → platform-engineer.md} +2 -2
  238. package/skills/roles/product-manager.ai-product.md +1 -1
  239. package/skills/roles/product-manager.enterprise.md +1 -1
  240. package/skills/roles/product-manager.growth.md +1 -2
  241. package/skills/roles/product-manager.md +1 -2
  242. package/skills/roles/product-manager.platform.md +1 -1
  243. package/skills/roles/product-manager.product.md +1 -1
  244. package/skills/roles/qa.ai-eval.md +1 -1
  245. package/skills/roles/qa.api-contract.md +1 -1
  246. package/skills/roles/qa.data-pipeline.md +1 -1
  247. package/skills/roles/qa.md +1 -1
  248. package/skills/roles/qa.web-ui.md +1 -1
  249. package/skills/roles/{operator.release.md → release-manager.md} +3 -3
  250. package/skills/roles/researcher.md +1 -1
  251. package/skills/roles/reviewer.md +1 -1
  252. package/skills/roles/security.ai.md +1 -1
  253. package/skills/roles/security.appsec.md +1 -1
  254. package/skills/roles/security.cloud.md +1 -1
  255. package/skills/roles/security.legal-compliance.md +1 -1
  256. package/skills/roles/security.md +1 -1
  257. package/skills/roles/security.privacy.md +1 -1
  258. package/skills/roles/security.supply-chain.md +1 -1
  259. package/skills/roles/{operator.sre.md → sre.md} +3 -3
  260. package/skills/roles/{qa.test-automation.md → test-automation.md} +2 -2
  261. package/skills/roles/{reviewer.trace.md → trace-reviewer.md} +2 -2
  262. package/skills/roles/{researcher.ux.md → ux-researcher.md} +2 -2
  263. package/skills/routing.json +18 -0
  264. package/skills/routing.md +1 -1
  265. package/specialists/artifact-manifest.json +2 -1
  266. package/specialists/audit-enrichments.json +14 -14
  267. package/specialists/org/contracts/accessibility-to-qa.json +37 -0
  268. package/specialists/org/contracts/any-to-business-strategist.json +57 -0
  269. package/specialists/org/contracts/any-to-debugger.json +38 -0
  270. package/specialists/org/contracts/any-to-designer.json +33 -0
  271. package/specialists/org/contracts/any-to-docs-keeper.json +34 -0
  272. package/specialists/org/contracts/any-to-explorer.json +39 -0
  273. package/specialists/org/contracts/any-to-sre-incident.json +33 -0
  274. package/specialists/org/contracts/any-to-trace-reviewer.json +32 -0
  275. package/specialists/org/contracts/architect-to-ai-engineer.json +32 -0
  276. package/specialists/org/contracts/architect-to-data-engineer.json +52 -0
  277. package/specialists/org/contracts/architect-to-devil-advocate.json +38 -0
  278. package/specialists/org/contracts/architect-to-engineer.json +34 -0
  279. package/specialists/org/contracts/architect-to-evaluator.json +59 -0
  280. package/specialists/org/contracts/architect-to-legal-compliance.json +55 -0
  281. package/specialists/org/contracts/architect-to-operations.json +45 -0
  282. package/specialists/org/contracts/architect-to-platform-engineer.json +42 -0
  283. package/specialists/org/contracts/business-strategist-to-product-manager.json +39 -0
  284. package/specialists/org/contracts/construct-to-orchestrator.json +36 -0
  285. package/specialists/org/contracts/construct-to-rd-lead.json +53 -0
  286. package/specialists/org/contracts/data-analyst-to-product-manager.json +43 -0
  287. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +36 -0
  288. package/specialists/org/contracts/designer-to-accessibility.json +36 -0
  289. package/specialists/org/contracts/engineer-to-qa.json +34 -0
  290. package/specialists/org/contracts/engineer-to-reviewer.json +52 -0
  291. package/specialists/org/contracts/explorer-to-engineer.json +38 -0
  292. package/specialists/org/contracts/legal-compliance-to-release-manager.json +43 -0
  293. package/specialists/org/contracts/platform-engineer-to-engineer.json +31 -0
  294. package/specialists/org/contracts/product-manager-to-architect.json +71 -0
  295. package/specialists/org/contracts/product-manager-to-data-analyst.json +49 -0
  296. package/specialists/org/contracts/product-manager-to-ux-researcher.json +51 -0
  297. package/specialists/org/contracts/qa-to-release-manager.json +42 -0
  298. package/specialists/org/contracts/qa-to-test-automation.json +42 -0
  299. package/specialists/org/contracts/rd-lead-to-architect.json +38 -0
  300. package/specialists/org/contracts/researcher-to-architect.json +40 -0
  301. package/specialists/org/contracts/researcher-to-product-manager.json +57 -0
  302. package/specialists/org/contracts/reviewer-to-security.json +41 -0
  303. package/specialists/org/contracts/sre-to-release-manager.json +36 -0
  304. package/specialists/org/contracts/test-automation-to-engineer.json +34 -0
  305. package/specialists/org/contracts/trace-reviewer-to-sre.json +41 -0
  306. package/specialists/org/contracts/user-to-construct.json +30 -0
  307. package/specialists/org/groups/engineering-group.json +43 -0
  308. package/specialists/org/groups/governance-group.json +38 -0
  309. package/specialists/org/groups/operations-group.json +41 -0
  310. package/specialists/org/groups/product-group.json +46 -0
  311. package/specialists/org/groups/quality-group.json +42 -0
  312. package/specialists/org/groups/strategy-group.json +41 -0
  313. package/specialists/org/policies/action-approval.json +13 -0
  314. package/specialists/org/policies/agents-routing.json +13 -0
  315. package/specialists/org/policies/architecture.json +20 -0
  316. package/specialists/org/policies/bash-safety.json +13 -0
  317. package/specialists/org/policies/beads-hygiene.json +13 -0
  318. package/specialists/org/policies/bootstrap-state.json +13 -0
  319. package/specialists/org/policies/code-review.json +13 -0
  320. package/specialists/org/policies/coding-style.json +13 -0
  321. package/specialists/org/policies/comments.json +13 -0
  322. package/specialists/org/policies/commit-approval.json +13 -0
  323. package/specialists/org/policies/contract-preconditions.json +13 -0
  324. package/specialists/org/policies/deployment.json +20 -0
  325. package/specialists/org/policies/description.json +14 -0
  326. package/specialists/org/policies/design-approval.json +19 -0
  327. package/specialists/org/policies/doc-ownership.json +13 -0
  328. package/specialists/org/policies/file-path-fence.json +13 -0
  329. package/specialists/org/policies/framing.json +13 -0
  330. package/specialists/org/policies/git-workflow.json +13 -0
  331. package/specialists/org/policies/incident-response.json +15 -0
  332. package/specialists/org/policies/intake-triage.json +15 -0
  333. package/specialists/org/policies/neurodivergent-output.json +13 -0
  334. package/specialists/org/policies/no-fabrication.json +13 -0
  335. package/specialists/org/policies/patterns.json +13 -0
  336. package/specialists/org/policies/quality-gate-approval.json +17 -0
  337. package/specialists/org/policies/release-gates.json +13 -0
  338. package/specialists/org/policies/research-evidence.json +13 -0
  339. package/specialists/org/policies/review-before-change.json +13 -0
  340. package/specialists/org/policies/rollback.json +15 -0
  341. package/specialists/org/policies/scope-change.json +20 -0
  342. package/specialists/org/policies/secret-scan.json +13 -0
  343. package/specialists/org/policies/security-approval.json +17 -0
  344. package/specialists/org/policies/security.json +13 -0
  345. package/specialists/org/policies/session-efficiency.json +13 -0
  346. package/specialists/org/policies/skill-routing.json +13 -0
  347. package/specialists/org/policies/strategic-prioritization.json +17 -0
  348. package/specialists/org/policies/testing.json +13 -0
  349. package/specialists/org/policies/tool-invisibility.json +14 -0
  350. package/specialists/org/scopes/creative.json +54 -0
  351. package/specialists/org/scopes/operations.json +51 -0
  352. package/specialists/org/scopes/research.json +60 -0
  353. package/specialists/org/scopes/rnd.json +81 -0
  354. package/specialists/org/specialists/cx-accessibility.json +69 -0
  355. package/specialists/org/specialists/cx-ai-engineer.json +75 -0
  356. package/specialists/org/specialists/cx-architect.json +89 -0
  357. package/specialists/org/specialists/cx-business-strategist.json +72 -0
  358. package/specialists/org/specialists/cx-data-analyst.json +68 -0
  359. package/specialists/org/specialists/cx-data-engineer.json +71 -0
  360. package/specialists/org/specialists/cx-debugger.json +75 -0
  361. package/specialists/org/specialists/cx-designer.json +85 -0
  362. package/specialists/org/specialists/cx-devil-advocate.json +71 -0
  363. package/specialists/org/specialists/cx-docs-keeper.json +82 -0
  364. package/specialists/org/specialists/cx-engineer.json +106 -0
  365. package/specialists/org/specialists/cx-evaluator.json +69 -0
  366. package/specialists/org/specialists/cx-explorer.json +69 -0
  367. package/specialists/org/specialists/cx-legal-compliance.json +74 -0
  368. package/specialists/org/specialists/cx-operations.json +72 -0
  369. package/specialists/org/specialists/cx-oracle.json +46 -0
  370. package/specialists/org/specialists/cx-orchestrator.json +81 -0
  371. package/specialists/org/specialists/cx-platform-engineer.json +80 -0
  372. package/specialists/org/specialists/cx-product-manager.json +102 -0
  373. package/specialists/org/specialists/cx-qa.json +79 -0
  374. package/specialists/org/specialists/cx-rd-lead.json +73 -0
  375. package/specialists/org/specialists/cx-release-manager.json +77 -0
  376. package/specialists/org/specialists/cx-researcher.json +82 -0
  377. package/specialists/org/specialists/cx-reviewer.json +71 -0
  378. package/specialists/org/specialists/cx-security.json +91 -0
  379. package/specialists/org/specialists/cx-sre.json +94 -0
  380. package/specialists/org/specialists/cx-test-automation.json +69 -0
  381. package/specialists/org/specialists/cx-trace-reviewer.json +69 -0
  382. package/specialists/org/specialists/cx-ux-researcher.json +68 -0
  383. package/specialists/org/teams/accessibility-team.json +39 -0
  384. package/specialists/org/teams/design-team.json +40 -0
  385. package/specialists/org/teams/engineering-team.json +50 -0
  386. package/specialists/org/teams/governance-team.json +41 -0
  387. package/specialists/org/teams/operations-team.json +46 -0
  388. package/specialists/org/teams/product-management-team.json +45 -0
  389. package/specialists/org/teams/quality-team.json +49 -0
  390. package/specialists/org/teams/research-team.json +39 -0
  391. package/specialists/org/teams/strategy-team.json +48 -0
  392. package/specialists/org/teams/ux-research-team.json +39 -0
  393. package/specialists/prompts/_shared/validation-contract.md +1 -1
  394. package/specialists/prompts/_team-template.md +10 -0
  395. package/specialists/prompts/cx-accessibility.md +1 -0
  396. package/specialists/prompts/cx-ai-engineer.md +1 -1
  397. package/specialists/prompts/cx-business-strategist.md +1 -1
  398. package/specialists/prompts/cx-data-engineer.md +1 -1
  399. package/specialists/prompts/cx-designer.md +1 -0
  400. package/specialists/prompts/cx-devil-advocate.md +1 -1
  401. package/specialists/prompts/cx-docs-keeper.md +1 -1
  402. package/specialists/prompts/cx-evaluator.md +1 -1
  403. package/specialists/prompts/cx-explorer.md +1 -1
  404. package/specialists/prompts/cx-operations.md +1 -1
  405. package/specialists/prompts/cx-oracle.md +7 -3
  406. package/specialists/prompts/cx-orchestrator.md +17 -1
  407. package/specialists/prompts/cx-platform-engineer.md +1 -1
  408. package/specialists/prompts/cx-product-manager.md +2 -0
  409. package/specialists/prompts/cx-release-manager.md +1 -1
  410. package/specialists/prompts/cx-researcher.md +7 -4
  411. package/specialists/prompts/cx-sre.md +1 -1
  412. package/specialists/prompts/cx-test-automation.md +2 -2
  413. package/specialists/prompts/cx-trace-reviewer.md +3 -3
  414. package/specialists/prompts/cx-ux-researcher.md +2 -1
  415. package/templates/demos/scripts/architecture-review-adr.json +30 -0
  416. package/templates/demos/scripts/capability-contract.json +25 -0
  417. package/templates/demos/scripts/intake-triage.json +25 -0
  418. package/templates/demos/scripts/profile-doctor-health.json +25 -0
  419. package/templates/demos/tapes/agentic-platforms-prd.tape +2 -2
  420. package/templates/demos/tapes/architecture-review-adr.tape +44 -0
  421. package/templates/demos/tapes/capability-contract.tape +39 -0
  422. package/templates/demos/tapes/intake-triage.tape +39 -0
  423. package/templates/demos/tapes/profile-doctor-health.tape +39 -0
  424. package/templates/distribution/construct-brand.typ +23 -18
  425. package/templates/distribution/construct-decision.typ +1 -1
  426. package/templates/distribution/construct-pdf.typ +1 -1
  427. package/templates/distribution/construct-prd.typ +1 -1
  428. package/templates/distribution/construct-research.typ +1 -1
  429. package/templates/distribution/mermaid-puppeteer.json +8 -0
  430. package/templates/docs/persona-artifact.md +1 -1
  431. package/templates/docs/prd.md +6 -0
  432. package/lib/boundary.mjs +0 -127
  433. package/lib/certification/dashboard-api.mjs +0 -71
  434. package/lib/chat/cli.mjs +0 -333
  435. package/lib/chat/command-suggest.mjs +0 -161
  436. package/lib/chat/commands.mjs +0 -215
  437. package/lib/chat/config.mjs +0 -142
  438. package/lib/chat/context-compactor.mjs +0 -250
  439. package/lib/chat/context-continuation.mjs +0 -253
  440. package/lib/chat/continuation-source.mjs +0 -58
  441. package/lib/chat/demo-guide.mjs +0 -61
  442. package/lib/chat/design-tokens.mjs +0 -91
  443. package/lib/chat/desktop-binary.mjs +0 -81
  444. package/lib/chat/desktop-build.mjs +0 -130
  445. package/lib/chat/desktop-launcher.mjs +0 -133
  446. package/lib/chat/evidence.mjs +0 -145
  447. package/lib/chat/export.mjs +0 -74
  448. package/lib/chat/harness/driver.mjs +0 -91
  449. package/lib/chat/list-picker.mjs +0 -112
  450. package/lib/chat/model-picker.mjs +0 -356
  451. package/lib/chat/openrouter-fallback.mjs +0 -151
  452. package/lib/chat/permission-prompt.mjs +0 -33
  453. package/lib/chat/picker-catalog.mjs +0 -45
  454. package/lib/chat/policy-telemetry.mjs +0 -34
  455. package/lib/chat/present.mjs +0 -246
  456. package/lib/chat/session-context.mjs +0 -39
  457. package/lib/chat/session-persist.mjs +0 -73
  458. package/lib/chat/session-restore.mjs +0 -71
  459. package/lib/chat/session-settings.mjs +0 -53
  460. package/lib/chat/system-prompt.mjs +0 -52
  461. package/lib/chat/transparency.mjs +0 -93
  462. package/lib/chat/tui/color-scheme.mjs +0 -42
  463. package/lib/chat/tui/markdown.mjs +0 -123
  464. package/lib/chat/tui/presentation.mjs +0 -100
  465. package/lib/chat/tui/render.mjs +0 -500
  466. package/lib/chat/tui/turn-block.mjs +0 -284
  467. package/lib/chat/tui/turn-present.mjs +0 -18
  468. package/lib/chat/tui/turn-state.mjs +0 -88
  469. package/lib/chat/tui/usage.mjs +0 -122
  470. package/lib/chat/web-commands.mjs +0 -146
  471. package/lib/chat/web-launcher.mjs +0 -63
  472. package/lib/chat/web-picker-keys.mjs +0 -46
  473. package/lib/chat/web-session.mjs +0 -159
  474. package/lib/config/alias.mjs +0 -57
  475. package/lib/dashboard-demo.mjs +0 -71
  476. package/lib/dashboard-static.mjs +0 -175
  477. package/lib/install/desktop-binary-download.mjs +0 -88
  478. package/lib/profiles/loader.mjs +0 -123
  479. package/lib/profiles/rebrand.mjs +0 -46
  480. package/lib/server/auth.mjs +0 -169
  481. package/lib/server/chat-loop.mjs +0 -622
  482. package/lib/server/chat.mjs +0 -336
  483. package/lib/server/cors.mjs +0 -77
  484. package/lib/server/csrf.mjs +0 -103
  485. package/lib/server/demo-preview.mjs +0 -63
  486. package/lib/server/index.mjs +0 -2641
  487. package/lib/server/insights.mjs +0 -780
  488. package/lib/server/langfuse-login.mjs +0 -58
  489. package/lib/server/rate-limit.mjs +0 -93
  490. package/lib/server/telemetry-login.mjs +0 -100
  491. package/lib/server/webhook.mjs +0 -512
  492. package/specialists/contracts.json +0 -1032
  493. package/specialists/contracts.schema.json +0 -83
  494. package/specialists/policy-inventory.json +0 -188
  495. package/specialists/registry.json +0 -1412
  496. package/specialists/role-manifests.json +0 -217
  497. package/specialists/teams.json +0 -94
@@ -26,7 +26,6 @@ import { fileURLToPath } from 'node:url';
26
26
  import { stageProjectAdapters } from '../lib/install/stage-project.mjs';
27
27
  import { syncProjectAdapters } from '../lib/adapters-sync.mjs';
28
28
  import { missingIgnorePatterns, isConstructPackageRepo } from '../lib/host-disposition.mjs';
29
- import { sanitizeNpmSpawnEnv } from '../lib/npm-spawn-env.mjs';
30
29
 
31
30
  const HERE = path.dirname(fileURLToPath(import.meta.url));
32
31
  const PKG_ROOT = path.resolve(HERE, '..');
@@ -74,27 +73,7 @@ try {
74
73
  } catch (err) {
75
74
  fail(`Tool-repo adapter sync failed: ${err.message}`, 'Run `npm run adapters` manually.');
76
75
  }
77
- const chatBuild = spawnSync('npm', ['run', 'build:chat'], {
78
- cwd: PKG_ROOT,
79
- stdio: 'inherit',
80
- env: sanitizeNpmSpawnEnv(process.env),
81
- });
82
- if (chatBuild.status !== 0) {
83
- fail('Ink TUI build failed', 'Run `npm run build:chat` manually.');
84
- } else {
85
- log('built apps/chat/dist/tui.mjs');
86
- }
87
76
 
88
- const dashBuild = spawnSync('npm', ['run', 'build:dashboard'], {
89
- cwd: PKG_ROOT,
90
- stdio: 'inherit',
91
- env: sanitizeNpmSpawnEnv(process.env),
92
- });
93
- if (dashBuild.status !== 0) {
94
- fail('Dashboard build failed', 'Run `npm run build:dashboard` manually.');
95
- } else {
96
- log('built lib/server/static/ (dashboard)');
97
- }
98
77
  process.exit(0);
99
78
  }
100
79
  } catch { /* fall through */ }
@@ -12,7 +12,7 @@ Optimize prompt fragments and overlays, not the runtime orchestration policy. If
12
12
 
13
13
  ## Required steps
14
14
 
15
- 1. **Read current prompt**: read the agent's prompt from `specialists/registry.json` (or its `promptFile`)
15
+ 1. **Read current prompt**: read the agent's prompt from `specialists/org` (or its `promptFile`)
16
16
  2. **Fetch recent scores**: GET `{CONSTRUCT_TELEMETRY_URL}/api/public/scores?name=quality&limit=200`
17
17
  3. **Skip agents with fewer than 20 scored traces**: insufficient signal; note them but do not optimize
18
18
  4. **Diagnose failure patterns**: analyze low-scoring traces, identify top 3 recurring patterns
@@ -19,11 +19,11 @@ last_verified_at: 2026-06-22
19
19
 
20
20
  ## Problem
21
21
 
22
- Distribution exports (PDF, HTML, deck, PPTX) and dashboard/chat surfaces drifted when typography moved from retired font families to Space Grotesk and JetBrains Mono. Docs and templates cited stale names; some apps still declared unused npm font packages. Operators could not tell which file was canonical for brand behavior.
22
+ Distribution exports (PDF, HTML, deck, PPTX) and interactive host surfaces drifted when typography moved from retired font families to Space Grotesk and JetBrains Mono. Docs and templates cited stale names; some apps still declared unused npm font packages. Operators could not tell which file was canonical for brand behavior.
23
23
 
24
24
  ## Context
25
25
 
26
- Construct ships a monochrome ink ramp and bundled OFL fonts under `templates/distribution/fonts/`. Chat and dashboard consume the same tokens via `lib/brand-tokens.mjs` and `lib/chat/design-tokens.mjs`. Profile-specific intake terminology flows through `lib/profiles/rebrand.mjs`.
26
+ Construct ships a monochrome ink ramp and bundled OFL fonts under `templates/distribution/fonts/`. Published artifacts and interactive hosts consume the same tokens via `lib/brand-tokens.mjs`. Profile-specific intake terminology flows through `lib/scopes/rebrand.mjs`.
27
27
 
28
28
  ```mermaid
29
29
  flowchart LR
@@ -33,4 +33,4 @@ Hook guards for workflow, drive, bootstrap, and task behavior are expressed as Y
33
33
 
34
34
  ## Registry as Single Source of Truth
35
35
 
36
- `specialists/registry.json` is the canonical definition for all agents on all platforms (Claude Code, OpenCode, Codex, Copilot). `construct sync` regenerates all platform-specific files from the registry. Never edit generated files directly: edits are overwritten on the next sync.
36
+ `specialists/org` is the canonical definition for all agents on all platforms (Claude Code, OpenCode, Codex, Copilot). `construct sync` regenerates all platform-specific files from the registry. Never edit generated files directly: edits are overwritten on the next sync.
@@ -0,0 +1,412 @@
1
+ /**
2
+ * lib/artifact-loop-core.mjs — Surface-neutral Construct artifact loop core.
3
+ *
4
+ * Intent resolution, typed-artifact drafting helpers, workflow-plan invocation,
5
+ * on-disk materialization, and the release gate — with zero host UI imports so
6
+ * the MCP server and supported hosts share one author→materialize→validate path.
7
+ */
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+
11
+ import { findConstructRoot, getArtifactEntry } from './artifact-manifest.mjs';
12
+ import { validateArtifactRelease } from './artifact-release-gate.mjs';
13
+ import { detectDocAuthoringIntent, resolveDocTypeMention } from './orchestration-policy.mjs';
14
+ import { invokeWorkflow } from './embedded-contract/workflow-invoke.mjs';
15
+ import { getTemplate } from './mcp/tools/skills.mjs';
16
+
17
+ const LOOP_INTENT_RE = [
18
+ /\brun (?:it )?through (?:the )?(?:actual )?construct (?:artifact )?loop\b/i,
19
+ /\brun (?:this|that|it) through construct\b/i,
20
+ /\b(execute|run) (?:the )?(?:artifact|prd) (?:pipeline|workflow|gate)\b/i,
21
+ /\bconstruct(?:\s+artifact)?\s+loop\b/i,
22
+ /\bthrough the construct (?:artifact )?loop\b/i,
23
+ ];
24
+
25
+ const LOOP_DOC_GATE_RE = /\b(?:loop|release gate|artifact gate|artifact validate)\b/i;
26
+ const AUTHOR_VERBS_RE = /\b(write|writing|draft|drafting|create|creating|author|authoring|produce|producing|compose|composing|prepare|preparing|generate|generating)\b/i;
27
+ const VALIDATE_FOLLOWUP_RE = /\b(run|execute|push|send|put)\b.*\b(?:it|this|that|the draft)\b.*\b(?:through|via)\b/i;
28
+ const QUESTION_PREFIX_RE = /^\s*(what|how|why|when|where|who|which|is|are|does|do|can|could|should|would|tell me about|explain)\b/i;
29
+
30
+ const OUTPUT_DIR_BY_TYPE = {
31
+ prd: 'docs/specs/prd',
32
+ 'prd-platform': 'docs/prd-platform',
33
+ 'prd-business': 'docs/prd-business',
34
+ 'meta-prd': 'docs/meta-prd',
35
+ adr: 'docs/decisions/adr',
36
+ rfc: 'docs/decisions/rfc',
37
+ 'research-brief': '.cx/research',
38
+ 'evidence-brief': '.cx/knowledge/internal/evidence-briefs',
39
+ runbook: 'docs/runbooks',
40
+ strategy: '.cx',
41
+ };
42
+
43
+ const WORKFLOW_BY_TYPE = {
44
+ prd: 'prd-draft',
45
+ 'prd-platform': 'prd-draft',
46
+ 'prd-business': 'prd-draft',
47
+ 'meta-prd': 'prd-draft',
48
+ adr: 'architecture-review',
49
+ rfc: 'architecture-review',
50
+ };
51
+
52
+ export function lastAssistantBody(turnBlocks = []) {
53
+ for (let i = turnBlocks.length - 1; i >= 0; i--) {
54
+ const item = turnBlocks[i];
55
+ if (item?.kind === 'turn' && item.block?.assistant?.trim()) {
56
+ return item.block.assistant.trim();
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+
62
+ export function lastUserBody(turnBlocks = []) {
63
+ for (let i = turnBlocks.length - 1; i >= 0; i--) {
64
+ const item = turnBlocks[i];
65
+ if (item?.kind === 'turn' && item.block?.userText?.trim()) {
66
+ return item.block.userText.trim();
67
+ }
68
+ }
69
+ return null;
70
+ }
71
+
72
+ function combinedContext(text, turnBlocks) {
73
+ const parts = [String(text || ''), lastUserBody(turnBlocks), lastAssistantBody(turnBlocks)].filter(Boolean);
74
+ return parts.join('\n');
75
+ }
76
+
77
+ function mentionsConstructLoop(text) {
78
+ return LOOP_INTENT_RE.some((re) => re.test(text));
79
+ }
80
+
81
+ function isQuestion(text) {
82
+ const t = String(text || '').trim();
83
+ return QUESTION_PREFIX_RE.test(t) || /\?\s*$/.test(t);
84
+ }
85
+
86
+ function isValidateFollowUp(text) {
87
+ return VALIDATE_FOLLOWUP_RE.test(text)
88
+ || /\b(?:run|execute)\b.*\b(?:artifact|release)\s+gate\b/i.test(text)
89
+ || /\bvalidate (?:the )?(?:draft|artifact|prd)\b/i.test(text);
90
+ }
91
+
92
+ export function hasSubstantialDraft(turnBlocks = []) {
93
+ const draft = lastAssistantBody(turnBlocks);
94
+ return Boolean(draft && normalizeDraftMarkdown(draft).length >= 400 && /^#\s+/m.test(normalizeDraftMarkdown(draft)));
95
+ }
96
+
97
+ /**
98
+ * Resolve whether the user wants to author+loop or validate an existing draft.
99
+ * Returns null when the message should follow the normal host path.
100
+ */
101
+ export function resolveArtifactLoopRequest(text, { turnBlocks = [], explicit = false } = {}) {
102
+ const t = String(text || '').trim();
103
+ if (!t) return null;
104
+
105
+ const loopMention = explicit || mentionsConstructLoop(t);
106
+ const docIntent = detectDocAuthoringIntent(combinedContext(t, turnBlocks));
107
+ const hybridLoopDoc = !loopMention && Boolean(docIntent) && LOOP_DOC_GATE_RE.test(t);
108
+
109
+ if (!loopMention && !hybridLoopDoc) return null;
110
+ if (!explicit && isQuestion(t)) return null;
111
+
112
+ const artifactType = resolveLoopArtifactType(t, turnBlocks);
113
+ const hasDraft = hasSubstantialDraft(turnBlocks);
114
+ const wantsAuthor = Boolean(
115
+ explicit && t.replace(/^\/loop\s*/i, '').trim()
116
+ || detectDocAuthoringIntent(t)
117
+ || docIntent
118
+ || AUTHOR_VERBS_RE.test(t),
119
+ );
120
+
121
+ if (hasDraft && (isValidateFollowUp(t) || (!wantsAuthor && loopMention))) {
122
+ return { mode: 'validate', artifactType };
123
+ }
124
+
125
+ if (wantsAuthor) {
126
+ return { mode: 'author', artifactType };
127
+ }
128
+
129
+ if (hasDraft) {
130
+ return { mode: 'validate', artifactType };
131
+ }
132
+
133
+ return null;
134
+ }
135
+
136
+ export function detectConstructLoopIntent(text, { turnBlocks = [], explicit = false } = {}) {
137
+ return resolveArtifactLoopRequest(text, { turnBlocks, explicit }) !== null;
138
+ }
139
+
140
+ export function resolveLoopArtifactType(text, turnBlocks) {
141
+ const blob = combinedContext(text, turnBlocks);
142
+ let type = detectDocAuthoringIntent(blob)?.docType || resolveDocTypeMention(blob) || 'prd';
143
+ const platformSignals = /\bplatform prd\b|\bteam mode\b|\bteam deployments\b|\benterprise\b|\boidc\b|\bsso\b|\bapi contract\b/i.test(blob);
144
+ if (platformSignals && (type === 'prd' || !type)) type = 'prd-platform';
145
+ if (/\bbusiness prd\b|\bmarket\b|\bpricing strategy\b/i.test(blob)) type = 'prd-business';
146
+ if (/\bmeta[\s-]prd\b/i.test(blob)) type = 'meta-prd';
147
+ return type;
148
+ }
149
+
150
+ function extractLoopSubject(userText) {
151
+ return String(userText || '')
152
+ .replace(/^\/loop\s*/i, '')
153
+ .replace(/\b(?:use |the )?construct(?:\s+artifact)?\s+loop\b/gi, '')
154
+ .replace(/\b(?:to|and)\s+(?:write|draft|create|generate|author|compose|produce|prepare)\b/gi, '')
155
+ .replace(/\b(?:write|draft|create|generate|author|compose|produce|prepare)\b/gi, '')
156
+ .replace(/\b(?:a|an|the)\s+/gi, ' ')
157
+ .replace(/\s{2,}/g, ' ')
158
+ .trim();
159
+ }
160
+
161
+ export function buildArtifactAuthoringPrompt({
162
+ userText,
163
+ artifactType,
164
+ rootDir,
165
+ gateErrors = [],
166
+ } = {}) {
167
+ const root = rootDir || findConstructRoot();
168
+ const entry = getArtifactEntry(artifactType, { rootDir: root });
169
+ const subject = extractLoopSubject(userText) || 'the requested topic';
170
+ const sectionHeadings = (entry?.structureRequirements || [])
171
+ .map((section) => `## ${section}`)
172
+ .join('\n');
173
+ const visualLines = (entry?.visualRequirements || []).map((req) => {
174
+ if (req.check === 'artifact-has-mermaid') {
175
+ return `- Include a fenced \`\`\`mermaid\`\`\` block with a ${req.diagram || 'flowchart'} diagram`;
176
+ }
177
+ if (req.check === 'artifact-table-has-columns' && Array.isArray(req.columns)) {
178
+ return `- Include a markdown table with columns: ${req.columns.join(' | ')} (with at least one data row)`;
179
+ }
180
+ return `- Satisfy visual requirement: ${req.id}`;
181
+ });
182
+ let templateExcerpt = '';
183
+ try {
184
+ const tmpl = getTemplate({ name: artifactType }, { ROOT_DIR: root });
185
+ if (tmpl?.content) {
186
+ templateExcerpt = tmpl.content.split('\n').slice(0, 40).join('\n');
187
+ }
188
+ } catch { /* template excerpt is best-effort */ }
189
+
190
+ const gateBlock = gateErrors.length
191
+ ? `\nPrevious draft failed the release gate. Fix every item:\n${gateErrors.map((e) => `- ${e}`).join('\n')}\n`
192
+ : '';
193
+
194
+ return `${String(userText || '').trim()}
195
+
196
+ Produce a complete ${artifactType} markdown document for: ${subject}.
197
+ ${gateBlock}
198
+ Required structure (use these ## headings):
199
+ ${sectionHeadings || '## Problem\n## Goals and non-goals\n## Success metrics'}
200
+
201
+ Visual requirements:
202
+ ${visualLines.length ? visualLines.join('\n') : '- Include a mermaid flowchart and a metrics table when applicable'}
203
+
204
+ Content rules:
205
+ - Start with a single # title line
206
+ - Write at least three prose paragraphs (not lists/tables alone) across the document
207
+ - Use repo tools to find relevant paths before claiming facts; cite file paths or URLs
208
+ - Mark unsupported claims [unverified]
209
+ - Output only the document markdown (no preamble)
210
+
211
+ Template reference (shape only):
212
+ ${templateExcerpt || '(no template excerpt)'}`;
213
+ }
214
+
215
+ function slugify(title) {
216
+ return String(title)
217
+ .toLowerCase()
218
+ .replace(/[^a-z0-9]+/g, '-')
219
+ .replace(/^-|-$/g, '')
220
+ .slice(0, 56) || 'draft';
221
+ }
222
+
223
+ function extractTitle(draft, fallback = 'draft-artifact') {
224
+ if (!draft) return fallback;
225
+ const normalized = normalizeDraftMarkdown(draft);
226
+ const prd = normalized.match(/^#\s+(?:PRD:\s*)?(.+)$/m);
227
+ if (prd?.[1]) return prd[1].trim();
228
+ const h1 = normalized.match(/^#\s+(.+)$/m);
229
+ if (h1?.[1]) return h1[1].trim();
230
+ return fallback;
231
+ }
232
+
233
+ function todayStamp() {
234
+ return new Date().toISOString().slice(0, 10);
235
+ }
236
+
237
+ function outputPathForType(artifactType, slug, cwd) {
238
+ const relDir = OUTPUT_DIR_BY_TYPE[artifactType] || 'docs/specs/prd';
239
+ const fileName = artifactType === 'adr'
240
+ ? `ADR-draft-${slug}.md`
241
+ : `${todayStamp()}-${slug}.md`;
242
+ return path.join(cwd, relDir, fileName);
243
+ }
244
+
245
+ function fillTemplate(template, { title, date }) {
246
+ return template
247
+ .replace(/\{title\}/g, title)
248
+ .replace(/\{YYYY-MM-DD\}/g, date)
249
+ .replace(/\{name\}/g, 'cx-product-manager');
250
+ }
251
+
252
+ export function ensureFrontmatter(body, artifactType) {
253
+ if (/^---\r?\n[\s\S]*?\r?\n---/.test(body)) return body;
254
+ return `---
255
+ artifactType: ${artifactType}
256
+ status: draft
257
+ owner: cx-product-manager
258
+ last_verified_at: ${todayStamp()}
259
+ ---
260
+
261
+ ${body.trim()}\n`;
262
+ }
263
+
264
+ export function normalizeDraftMarkdown(draft) {
265
+ let text = String(draft || '').trim();
266
+ const fenced = text.match(/^```(?:markdown|md)?\s*\n([\s\S]*?)\n```\s*$/i);
267
+ if (fenced) text = fenced[1].trim();
268
+ return text;
269
+ }
270
+
271
+ export function extractArtifactMarkdown(text) {
272
+ const normalized = normalizeDraftMarkdown(text);
273
+ if (!normalized) return null;
274
+ const idx = normalized.search(/^#\s+/m);
275
+ if (idx < 0) return null;
276
+ const doc = normalized.slice(idx).trim();
277
+ if (doc.length < 200) return null;
278
+ return doc;
279
+ }
280
+
281
+ function buildDraftBody({ artifactType, title, draftFromHost, draftMarkdown, rootDir, allowScaffold = true }) {
282
+ const source = draftMarkdown || (draftFromHost ? normalizeDraftMarkdown(draftFromHost) : null);
283
+ if (source && source.length >= 200 && /^#\s+/m.test(source)) {
284
+ return ensureFrontmatter(source, artifactType);
285
+ }
286
+ if (!allowScaffold) return null;
287
+ const tmpl = getTemplate({ name: artifactType }, { ROOT_DIR: rootDir });
288
+ if (tmpl?.content) {
289
+ return fillTemplate(tmpl.content, { title, date: todayStamp() });
290
+ }
291
+ return ensureFrontmatter(`# ${title}\n\n## Summary\n\nDraft scaffold — expand from workflow plan and evidence.\n`, artifactType);
292
+ }
293
+
294
+ export async function runConstructArtifactLoop({
295
+ text = '',
296
+ turnBlocks = [],
297
+ cwd = process.cwd(),
298
+ rootDir,
299
+ explicit = false,
300
+ artifactType: artifactTypeOverride,
301
+ draftMarkdown,
302
+ allowScaffold = true,
303
+ } = {}) {
304
+ const root = rootDir || findConstructRoot(cwd);
305
+ const draftFromHost = draftMarkdown ? null : lastAssistantBody(turnBlocks);
306
+ const resolvedDraft = draftMarkdown || extractArtifactMarkdown(draftFromHost);
307
+ const artifactType = artifactTypeOverride || resolveLoopArtifactType(text, turnBlocks);
308
+ const title = extractTitle(
309
+ resolvedDraft || draftFromHost,
310
+ explicit ? (extractLoopSubject(text) || 'draft-artifact') : 'draft-artifact',
311
+ );
312
+ const slug = slugify(title);
313
+ const outPath = outputPathForType(artifactType, slug, cwd);
314
+ const workflowType = WORKFLOW_BY_TYPE[artifactType] || 'prd-draft';
315
+ const input = [
316
+ `Artifact loop: ${artifactType}`,
317
+ `Title: ${title}`,
318
+ resolvedDraft || draftFromHost
319
+ ? `Draft excerpt:\n${(resolvedDraft || draftFromHost).slice(0, 4000)}`
320
+ : String(text),
321
+ ].join('\n\n');
322
+
323
+ const invokePlan = await invokeWorkflow({
324
+ workflowType,
325
+ input,
326
+ approvalMode: 'proposal-only',
327
+ trace: true,
328
+ });
329
+
330
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
331
+ const body = buildDraftBody({
332
+ artifactType,
333
+ title,
334
+ draftFromHost,
335
+ draftMarkdown: resolvedDraft,
336
+ rootDir: root,
337
+ allowScaffold,
338
+ });
339
+ if (!body) {
340
+ const relPath = path.relative(cwd, outPath);
341
+ return {
342
+ ok: false,
343
+ artifactType,
344
+ path: null,
345
+ relPath,
346
+ title,
347
+ slug,
348
+ workflowType,
349
+ invokePlan,
350
+ validation: { ok: false, errors: ['No draft content to materialize'], warnings: [] },
351
+ summary: 'Release gate: not run — no draft content to materialize.',
352
+ draftMissing: true,
353
+ overlay: {
354
+ intent: 'doc-authoring',
355
+ workCategory: 'artifact-loop',
356
+ track: 'orchestrated',
357
+ specialists: (invokePlan?.selectedRoles || ['product-manager', 'architect']).map((r) => `cx-${r}`),
358
+ dispatchSummary: `artifact loop: ${artifactType} → validate`,
359
+ },
360
+ };
361
+ }
362
+ fs.writeFileSync(outPath, body.endsWith('\n') ? body : `${body}\n`);
363
+
364
+ const validation = validateArtifactRelease({
365
+ filePath: outPath,
366
+ type: artifactType,
367
+ cwd,
368
+ rootDir: root,
369
+ });
370
+
371
+ const relPath = path.relative(cwd, outPath);
372
+ const gateStatus = validation.ok
373
+ ? 'PASS'
374
+ : `FAIL (${validation.errors.length} error${validation.errors.length === 1 ? '' : 's'})`;
375
+ const summary = validation.ok
376
+ ? [
377
+ `Wrote ${relPath} (${artifactType}).`,
378
+ `Release gate: ${gateStatus}`,
379
+ invokePlan?.selectedRoles?.length
380
+ ? `Workflow plan (${workflowType}): ${invokePlan.selectedRoles.map((r) => `cx-${r}`).join(' → ')}`
381
+ : `Workflow plan (${workflowType}): ${invokePlan?.status || 'proposed'}`,
382
+ 'Next: address gate findings, run specialists per plan, then `construct publish` if distributing.',
383
+ ].join('\n')
384
+ : [
385
+ `Draft failed release gate for ${relPath} (${artifactType}).`,
386
+ `Release gate: ${gateStatus}`,
387
+ invokePlan?.selectedRoles?.length
388
+ ? `Workflow plan (${workflowType}): ${invokePlan.selectedRoles.map((r) => `cx-${r}`).join(' → ')}`
389
+ : `Workflow plan (${workflowType}): ${invokePlan?.status || 'proposed'}`,
390
+ 'Next: fix gate errors below, re-run `/loop` or `construct artifact validate`.',
391
+ ].join('\n');
392
+
393
+ return {
394
+ ok: validation.ok,
395
+ artifactType,
396
+ path: outPath,
397
+ relPath,
398
+ title,
399
+ slug,
400
+ workflowType,
401
+ invokePlan,
402
+ validation,
403
+ summary,
404
+ overlay: {
405
+ intent: 'doc-authoring',
406
+ workCategory: 'artifact-loop',
407
+ track: 'orchestrated',
408
+ specialists: (invokePlan?.selectedRoles || ['product-manager', 'architect']).map((r) => `cx-${r}`),
409
+ dispatchSummary: `artifact loop: ${artifactType} → validate`,
410
+ },
411
+ };
412
+ }
@@ -10,6 +10,7 @@
10
10
  import fs from 'node:fs';
11
11
  import path from 'node:path';
12
12
  import { fileURLToPath } from 'node:url';
13
+ import { resolveActiveScope } from './scopes/loader.mjs';
13
14
 
14
15
  const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
15
16
  const EMPTY_MANIFEST = { version: 1, artifacts: {} };
@@ -233,6 +234,11 @@ export function resolveToneForArtifact(type, { cwd = process.cwd(), rootDir } =
233
234
  const brand = loadBrandVoice(cwd);
234
235
  const override = brand?.toneOverride?.[type];
235
236
  if (override) return override;
237
+ try {
238
+ const scope = resolveActiveScope(cwd);
239
+ const scopeTone = scope?.toneDefaults?.[type];
240
+ if (scopeTone) return scopeTone;
241
+ } catch { /* scope optional */ }
236
242
  return entry?.toneDefault ?? 'direct';
237
243
  }
238
244
 
@@ -7,7 +7,9 @@
7
7
 
8
8
  import fs from 'node:fs';
9
9
  import path from 'node:path';
10
+ import os from 'node:os';
10
11
  import { lintDocStructure, lintDocVisuals } from './templates/visual-requirements.mjs';
12
+ import { lintDocPresentationFile } from './templates/doc-presentation.mjs';
11
13
  import { getArtifactEntry, resolveToneForArtifact } from './artifact-manifest.mjs';
12
14
  import { lintFile } from './comment-lint.mjs';
13
15
  import {
@@ -44,6 +46,112 @@ function countCitations(body) {
44
46
  return hits.size;
45
47
  }
46
48
 
49
+ export function dedupeGateErrors(errors = []) {
50
+ return [...new Set((errors || []).filter(Boolean))];
51
+ }
52
+
53
+ function validateArtifactBodyCore({
54
+ body,
55
+ resolvedType,
56
+ entry,
57
+ cwd,
58
+ rootDir,
59
+ reviewersSeen,
60
+ filePath = null,
61
+ }) {
62
+ const gate = entry.releaseGate ?? {};
63
+ const errors = [];
64
+ const warnings = [];
65
+ const rawBody = String(body || '');
66
+ const stripped = rawBody.replace(/^---\n[\s\S]*?\n---\n/, '');
67
+ const bodyWithoutComments = stripped.replace(/<!--[\s\S]*?-->/g, '');
68
+
69
+ if (gate.structuralLint !== false && filePath) {
70
+ errors.push(...lintDocStructure(filePath, resolvedType));
71
+ errors.push(...lintDocVisuals(filePath, resolvedType));
72
+ const presentation = lintDocPresentationFile(filePath, resolvedType);
73
+ errors.push(...presentation.errors);
74
+ warnings.push(...presentation.warnings);
75
+ }
76
+
77
+ if (gate.citationLint) {
78
+ const isShippedTemplate = filePath?.includes(`${path.sep}templates${path.sep}docs${path.sep}`);
79
+ if (!isShippedTemplate) {
80
+ if (filePath) {
81
+ const lint = lintFile(filePath);
82
+ const artifactIssues = [...(lint.errors ?? []), ...(lint.warnings ?? [])].filter((w) => w.kind === 'artifact');
83
+ for (const issue of artifactIssues) errors.push(issue.message || String(issue));
84
+ }
85
+ const citations = countCitations(bodyWithoutComments);
86
+ const unverified = /\[unverified\]/i.test(bodyWithoutComments);
87
+ if (citations < 1 && !unverified) {
88
+ errors.push('citationLint: no verifiable sources or [unverified] markers');
89
+ }
90
+ }
91
+ }
92
+
93
+ const minProse = gate.proseMinimum ?? 0;
94
+ if (minProse > 0) {
95
+ const prose = countProseParagraphs(bodyWithoutComments);
96
+ if (prose < minProse) {
97
+ errors.push(`proseMinimum: ${prose} paragraphs (need ${minProse})`);
98
+ }
99
+ }
100
+
101
+ if (filePath) {
102
+ const missing = missingRequiredReviewers({
103
+ docType: resolvedType,
104
+ filePath,
105
+ rootDir,
106
+ cwd,
107
+ reviewersSeen,
108
+ });
109
+ if (missing.length > 0) {
110
+ warnings.push(`requiredReviewers not seen in agent log: ${missing.join(', ')}`);
111
+ }
112
+ }
113
+
114
+ return {
115
+ ok: dedupeGateErrors(errors).length === 0,
116
+ errors: dedupeGateErrors(errors),
117
+ warnings,
118
+ tone: resolveToneForArtifact(resolvedType, { cwd, rootDir }),
119
+ type: resolvedType,
120
+ filePath,
121
+ };
122
+ }
123
+
124
+ export function validateArtifactBody({
125
+ body,
126
+ type,
127
+ cwd = process.cwd(),
128
+ rootDir,
129
+ reviewersSeen,
130
+ } = {}) {
131
+ const resolvedType = type;
132
+ const entry = getArtifactEntry(resolvedType, { rootDir });
133
+ if (!entry) {
134
+ return { ok: false, errors: [`Unknown artifact type: ${resolvedType}`], warnings: [], tone: null };
135
+ }
136
+
137
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cx-artifact-gate-'));
138
+ const filePath = path.join(tmpDir, 'draft.md');
139
+ try {
140
+ fs.writeFileSync(filePath, body.endsWith('\n') ? body : `${body}\n`);
141
+ return validateArtifactBodyCore({
142
+ body,
143
+ resolvedType,
144
+ entry,
145
+ cwd,
146
+ rootDir,
147
+ reviewersSeen,
148
+ filePath,
149
+ });
150
+ } finally {
151
+ fs.rmSync(tmpDir, { recursive: true, force: true });
152
+ }
153
+ }
154
+
47
155
  export function validateArtifactRelease({
48
156
  filePath,
49
157
  type,
@@ -81,60 +189,18 @@ export function validateArtifactRelease({
81
189
  };
82
190
  }
83
191
 
84
- const gate = entry.releaseGate ?? {};
85
- const errors = [];
86
- const warnings = [];
87
192
  const raw = fs.readFileSync(filePath, 'utf8');
88
- const body = raw.replace(/^---\n[\s\S]*?\n---\n/, '');
89
-
90
- if (gate.structuralLint !== false) {
91
- errors.push(...lintDocStructure(filePath, resolvedType));
92
- errors.push(...lintDocVisuals(filePath, resolvedType));
93
- }
94
193
 
95
- if (gate.citationLint) {
96
- const isShippedTemplate = filePath.includes(`${path.sep}templates${path.sep}docs${path.sep}`);
97
- if (!isShippedTemplate) {
98
- const lint = lintFile(filePath);
99
- const artifactIssues = [...(lint.errors ?? []), ...(lint.warnings ?? [])].filter((w) => w.kind === 'artifact');
100
- for (const issue of artifactIssues) errors.push(issue.message || String(issue));
101
- const citations = countCitations(body);
102
- const unverified = /\[unverified\]/i.test(body);
103
- if (citations < 1 && !unverified) {
104
- errors.push('citationLint: no verifiable sources or [unverified] markers');
105
- }
106
- }
107
- }
108
-
109
- const minProse = gate.proseMinimum ?? 0;
110
- if (minProse > 0) {
111
- const prose = countProseParagraphs(body);
112
- if (prose < minProse) {
113
- errors.push(`proseMinimum: ${prose} paragraphs (need ${minProse})`);
114
- }
115
- }
116
-
117
- const missing = missingRequiredReviewers({
118
- docType: resolvedType,
119
- filePath,
120
- rootDir,
194
+ const result = validateArtifactBodyCore({
195
+ body: raw,
196
+ resolvedType,
197
+ entry,
121
198
  cwd,
199
+ rootDir,
122
200
  reviewersSeen,
123
- });
124
- if (missing.length > 0) {
125
- warnings.push(`requiredReviewers not seen in agent log: ${missing.join(', ')}`);
126
- }
127
-
128
- const tone = resolveToneForArtifact(resolvedType, { cwd, rootDir });
129
-
130
- return {
131
- ok: errors.length === 0,
132
- errors,
133
- warnings,
134
- tone,
135
- type: resolvedType,
136
201
  filePath,
137
- };
202
+ });
203
+ return { ...result, filePath };
138
204
  }
139
205
 
140
206
  export async function runArtifactValidateCli(args = []) {