@geraldmaron/construct 1.4.1 → 1.5.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 (556) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1029 -65
  4. package/bin/construct-postinstall.mjs +27 -2
  5. package/config/tag-vocabulary.json +264 -0
  6. package/examples/distribution/sources/deck-one-pager.md +1 -1
  7. package/lib/acp/server.mjs +21 -7
  8. package/lib/adapters-sync.mjs +2 -1
  9. package/lib/audit-trail.mjs +185 -2
  10. package/lib/beads/auto-close.mjs +2 -1
  11. package/lib/beads/drift.mjs +2 -1
  12. package/lib/bridges/copilot-proxy.mjs +20 -5
  13. package/lib/certification/skill-inventory.mjs +10 -1
  14. package/lib/cli/approvals.mjs +147 -0
  15. package/lib/cli-commands.mjs +105 -14
  16. package/lib/comment-lint.mjs +127 -8
  17. package/lib/config/schema.mjs +7 -30
  18. package/lib/config/source-target-registry.mjs +70 -0
  19. package/lib/config/source-targets.mjs +179 -205
  20. package/lib/contracts/coverage.mjs +77 -0
  21. package/lib/contracts/validate.mjs +102 -13
  22. package/lib/contracts/violation-log.mjs +50 -4
  23. package/lib/db/migrate.mjs +69 -0
  24. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  25. package/lib/db/migrations/002_queue_provider.sql +50 -0
  26. package/lib/db/migrations/003_worker_registry.sql +17 -0
  27. package/lib/db/migrations/004_trace_events.sql +16 -0
  28. package/lib/db/migrations/005_shared_memory.sql +15 -0
  29. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  30. package/lib/decisions/enforced-baseline.json +0 -2
  31. package/lib/decisions/registry.mjs +3 -2
  32. package/lib/deployment/parity-contract.mjs +1 -1
  33. package/lib/deployment-mode.mjs +78 -2
  34. package/lib/diagram-export.mjs +10 -1
  35. package/lib/distill.mjs +5 -2
  36. package/lib/docs-verify.mjs +2 -1
  37. package/lib/doctor/cli.mjs +1 -0
  38. package/lib/doctor/command-on-path.mjs +24 -0
  39. package/lib/doctor/diagnosis.mjs +109 -0
  40. package/lib/doctor/embedding-health.mjs +50 -0
  41. package/lib/doctor/engine-health.mjs +93 -0
  42. package/lib/doctor/graph-validate.mjs +47 -0
  43. package/lib/doctor/index.mjs +23 -4
  44. package/lib/doctor/sidecar-providers.mjs +56 -0
  45. package/lib/doctor/watchers/consistency.mjs +93 -1
  46. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  47. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  48. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  49. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  50. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  51. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  52. package/lib/document-export.mjs +52 -27
  53. package/lib/document-extract/docling-client.mjs +9 -5
  54. package/lib/document-extract/docling-sidecar.py +30 -0
  55. package/lib/document-extract.mjs +1 -1
  56. package/lib/document-ingest.mjs +9 -0
  57. package/lib/embed/approval-queue.mjs +184 -88
  58. package/lib/embed/authority-guard.mjs +65 -2
  59. package/lib/embed/capability-jobs.mjs +365 -0
  60. package/lib/embed/capability-lifecycle.mjs +219 -0
  61. package/lib/embed/capability-loader.mjs +303 -0
  62. package/lib/embed/capability-runtime.mjs +58 -0
  63. package/lib/embed/cli.mjs +185 -8
  64. package/lib/embed/config.mjs +4 -0
  65. package/lib/embed/daemon.mjs +125 -6
  66. package/lib/embed/demand-fetch.mjs +190 -54
  67. package/lib/embed/inbox.mjs +12 -10
  68. package/lib/embed/presets/ops-triage.mjs +236 -0
  69. package/lib/embed/presets/pm-feedback.mjs +341 -0
  70. package/lib/embed/presets/tpm.mjs +401 -0
  71. package/lib/embed/providers/jira.mjs +72 -1
  72. package/lib/embed/providers/registry.mjs +140 -33
  73. package/lib/embed/worker.mjs +5 -0
  74. package/lib/embedded-contract/capability.mjs +4 -0
  75. package/lib/embedded-contract/execution.mjs +1 -1
  76. package/lib/embedded-contract/model-resolve.mjs +53 -3
  77. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  78. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  79. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  80. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  81. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  82. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  83. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  84. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  85. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  86. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  88. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  90. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  91. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  92. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  93. package/lib/env-config.mjs +60 -11
  94. package/lib/export-validate.mjs +34 -2
  95. package/lib/extensions/index.mjs +27 -0
  96. package/lib/extensions/loader.mjs +120 -0
  97. package/lib/extensions/manifest-schema.mjs +141 -0
  98. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  99. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  100. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  101. package/lib/extensions/manifests/directory.manifest.json +12 -0
  102. package/lib/extensions/manifests/docling.manifest.json +37 -0
  103. package/lib/extensions/manifests/echo.manifest.json +8 -0
  104. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  105. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  106. package/lib/extensions/manifests/github.manifest.json +52 -0
  107. package/lib/extensions/manifests/linear.manifest.json +50 -0
  108. package/lib/extensions/manifests/local.manifest.json +12 -0
  109. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  110. package/lib/extensions/manifests/openai.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  113. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  114. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  115. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  116. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  117. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  118. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  119. package/lib/extensions/manifests/slack.manifest.json +58 -0
  120. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  121. package/lib/extensions/validate.mjs +175 -0
  122. package/lib/features.mjs +13 -9
  123. package/lib/flows/checkpoint.mjs +184 -0
  124. package/lib/flows/constants.mjs +27 -0
  125. package/lib/flows/define.mjs +146 -0
  126. package/lib/flows/engine.mjs +249 -0
  127. package/lib/flows/errors.mjs +19 -0
  128. package/lib/flows/index.mjs +27 -0
  129. package/lib/flows/joins.mjs +18 -0
  130. package/lib/flows/schema.mjs +90 -0
  131. package/lib/flows/state.mjs +31 -0
  132. package/lib/frameworks/loader.mjs +99 -0
  133. package/lib/frameworks/schema.mjs +157 -0
  134. package/lib/graph/build-from-corpus.mjs +67 -0
  135. package/lib/graph/build-from-embed.mjs +82 -0
  136. package/lib/graph/build-from-registry.mjs +164 -3
  137. package/lib/graph/cli.mjs +456 -12
  138. package/lib/graph/gap-queries.mjs +156 -0
  139. package/lib/graph/gaps.mjs +41 -0
  140. package/lib/graph/impacted.mjs +129 -0
  141. package/lib/graph/runtime-evidence.mjs +177 -0
  142. package/lib/graph/security-coverage.mjs +113 -0
  143. package/lib/graph/staleness.mjs +241 -10
  144. package/lib/graph/store.mjs +24 -7
  145. package/lib/graph/validate.mjs +161 -0
  146. package/lib/headhunt.mjs +9 -8
  147. package/lib/health-check.mjs +15 -7
  148. package/lib/hook-health.mjs +1 -1
  149. package/lib/hooks/agent-tracker.mjs +10 -4
  150. package/lib/hooks/edit-guard.mjs +3 -3
  151. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  152. package/lib/hooks/guard-bash.mjs +41 -15
  153. package/lib/hooks/mcp-health-check.mjs +11 -4
  154. package/lib/hooks/model-fallback.mjs +50 -6
  155. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  156. package/lib/hooks/pre-compact.mjs +181 -173
  157. package/lib/hooks/rule-verifier.mjs +2 -1
  158. package/lib/hooks/session-reflect.mjs +2 -1
  159. package/lib/hooks/session-start.mjs +3 -3
  160. package/lib/host/readiness.mjs +109 -0
  161. package/lib/host-capabilities.mjs +13 -2
  162. package/lib/host-disposition.mjs +29 -8
  163. package/lib/identity.mjs +92 -0
  164. package/lib/ingest/degraded-extract.mjs +96 -0
  165. package/lib/ingest/docling-remote.mjs +2 -1
  166. package/lib/ingest/provider-extract.mjs +7 -19
  167. package/lib/ingest/sidecar-providers.mjs +196 -0
  168. package/lib/ingest-tooling.mjs +11 -5
  169. package/lib/init-unified.mjs +55 -38
  170. package/lib/init-update.mjs +2 -1
  171. package/lib/install/legacy-global-cleanup.mjs +25 -0
  172. package/lib/install/stage-project.mjs +41 -4
  173. package/lib/intake/daemon.mjs +99 -8
  174. package/lib/intake/git-queue.mjs +110 -38
  175. package/lib/intake/prepare.mjs +2 -0
  176. package/lib/intake/queue-registry.mjs +50 -0
  177. package/lib/intake/queue.mjs +133 -17
  178. package/lib/intake/session-prelude.mjs +57 -8
  179. package/lib/integrations/intake-integrations.mjs +61 -111
  180. package/lib/intent-classifier.mjs +43 -5
  181. package/lib/libreoffice-export.mjs +8 -8
  182. package/lib/logging/rotate.mjs +5 -4
  183. package/lib/mcp/broker.mjs +332 -17
  184. package/lib/mcp/denied-store.mjs +95 -0
  185. package/lib/mcp/destructive-approval.mjs +57 -0
  186. package/lib/mcp/destructive-gate.mjs +30 -0
  187. package/lib/mcp/dispatch-envelope.mjs +199 -0
  188. package/lib/mcp/memory-bridge.mjs +2 -1
  189. package/lib/mcp/server.mjs +185 -1268
  190. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  191. package/lib/mcp/tool-definitions-project.mjs +271 -0
  192. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  193. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  194. package/lib/mcp/tool-definitions.mjs +25 -0
  195. package/lib/mcp/tool-rate-limit.mjs +47 -0
  196. package/lib/mcp/tool-registry.mjs +107 -0
  197. package/lib/mcp/tool-safety.mjs +95 -0
  198. package/lib/mcp/tool-surface-parity.mjs +60 -0
  199. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  200. package/lib/mcp/tools/orchestration-run.mjs +197 -19
  201. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  202. package/lib/mcp/tools/project.mjs +25 -8
  203. package/lib/mcp/tools/provider-write.mjs +187 -0
  204. package/lib/mcp/tools/scope.mjs +0 -3
  205. package/lib/mcp/tools/skills.mjs +1 -1
  206. package/lib/mcp/tools/storage.mjs +2 -2
  207. package/lib/mcp/tools/web-search-governance.mjs +96 -0
  208. package/lib/mcp/tools/web-search.mjs +5 -76
  209. package/lib/mcp/transport/auth.mjs +238 -0
  210. package/lib/mcp/transport/http.mjs +136 -0
  211. package/lib/mcp/transport/mode.mjs +31 -0
  212. package/lib/mcp/transport/stdio.mjs +22 -0
  213. package/lib/mcp-catalog.json +4 -4
  214. package/lib/mcp-manager.mjs +34 -23
  215. package/lib/mcp-platform-config.mjs +53 -20
  216. package/lib/mode-capabilities.mjs +109 -0
  217. package/lib/model-router.mjs +42 -9
  218. package/lib/models/catalog.mjs +40 -1
  219. package/lib/net-guard.mjs +247 -0
  220. package/lib/observation-store.mjs +6 -2
  221. package/lib/ollama/provision-context.mjs +2 -1
  222. package/lib/opencode-config.mjs +22 -3
  223. package/lib/opencode-runtime-plugin.mjs +120 -2
  224. package/lib/oracle/actions.mjs +204 -10
  225. package/lib/oracle/cli.mjs +24 -3
  226. package/lib/oracle/daemon-entry.mjs +6 -0
  227. package/lib/oracle/dispatch.mjs +2 -1
  228. package/lib/oracle/execute.mjs +2 -2
  229. package/lib/oracle/gaps.mjs +3 -3
  230. package/lib/oracle/heartbeat.mjs +53 -0
  231. package/lib/oracle/read-model.mjs +69 -13
  232. package/lib/oracle/reconcile.mjs +3 -46
  233. package/lib/oracle/routing.mjs +37 -31
  234. package/lib/oracle/synthesize.mjs +1 -1
  235. package/lib/orchestration/classification.mjs +434 -0
  236. package/lib/orchestration/delegation-flow.mjs +132 -0
  237. package/lib/orchestration/flow-selection.mjs +418 -0
  238. package/lib/orchestration/gates.mjs +244 -0
  239. package/lib/orchestration/host-sampling.mjs +121 -0
  240. package/lib/orchestration/policy-constants.mjs +48 -0
  241. package/lib/orchestration/provider-outcome.mjs +197 -0
  242. package/lib/orchestration/readiness.mjs +114 -13
  243. package/lib/orchestration/run-store-postgres.mjs +32 -26
  244. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  245. package/lib/orchestration/run-store.mjs +25 -12
  246. package/lib/orchestration/runtime.mjs +508 -62
  247. package/lib/orchestration/store.mjs +87 -12
  248. package/lib/orchestration/trace-store.mjs +125 -0
  249. package/lib/orchestration/web-capability.mjs +60 -0
  250. package/lib/orchestration/worker-runtime.mjs +162 -0
  251. package/lib/orchestration/worker.mjs +763 -80
  252. package/lib/orchestration-policy.mjs +67 -1111
  253. package/lib/output-quality.mjs +61 -2
  254. package/lib/packs/cli.mjs +144 -0
  255. package/lib/packs/core-pack.mjs +112 -0
  256. package/lib/packs/enablement.mjs +187 -0
  257. package/lib/packs/index.mjs +23 -0
  258. package/lib/packs/loader.mjs +176 -0
  259. package/lib/packs/manifest-schema.mjs +58 -0
  260. package/lib/packs/prompts.mjs +120 -0
  261. package/lib/packs/validate.mjs +208 -0
  262. package/lib/path-policy.mjs +56 -0
  263. package/lib/plugin-registry.mjs +4 -5
  264. package/lib/policy/audit-gate.mjs +42 -0
  265. package/lib/policy/consumption-budget.mjs +149 -0
  266. package/lib/policy/engine.mjs +81 -6
  267. package/lib/policy/role-authority.mjs +89 -0
  268. package/lib/prompt-composer.js +19 -3
  269. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  270. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  271. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  272. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  273. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  274. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  275. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  276. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  277. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  278. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  279. package/lib/providers/contract.mjs +207 -0
  280. package/lib/providers/credential-bootstrap.mjs +7 -4
  281. package/lib/providers/credential-sources.mjs +14 -2
  282. package/lib/providers/directory/index.mjs +261 -0
  283. package/lib/providers/feedback/index.mjs +392 -0
  284. package/lib/providers/filter-audit.mjs +68 -0
  285. package/lib/providers/filter-schema.mjs +54 -0
  286. package/lib/providers/github/index.mjs +31 -0
  287. package/lib/providers/instance-config.mjs +215 -0
  288. package/lib/providers/op-locate.mjs +96 -0
  289. package/lib/providers/op-run.mjs +64 -9
  290. package/lib/providers/registry.mjs +26 -3
  291. package/lib/providers/secret-audit-wiring.mjs +41 -18
  292. package/lib/providers/secret-resolver.mjs +263 -31
  293. package/lib/publish-template.mjs +6 -3
  294. package/lib/publish-tooling.mjs +4 -0
  295. package/lib/publish.mjs +32 -4
  296. package/lib/queue/pg-queue.mjs +395 -0
  297. package/lib/reflect.mjs +2 -1
  298. package/lib/registry/assemble.mjs +28 -2
  299. package/lib/registry/catalog.mjs +6 -0
  300. package/lib/registry/consolidation.mjs +8 -1
  301. package/lib/registry/custom-scaffold.mjs +200 -0
  302. package/lib/registry/custom-schema.mjs +137 -0
  303. package/lib/registry/loader.mjs +15 -4
  304. package/lib/registry/manifests/format-engines.default.json +15 -0
  305. package/lib/registry/manifests/surface-map.default.json +46 -0
  306. package/lib/registry/retired-paths.mjs +1 -0
  307. package/lib/registry/surface-map.mjs +100 -50
  308. package/lib/registry/validate.mjs +3 -3
  309. package/lib/render-visual-check.mjs +240 -0
  310. package/lib/resources/budget.mjs +40 -17
  311. package/lib/roles/flavor-bindings.mjs +36 -14
  312. package/lib/roles/gateway.mjs +15 -8
  313. package/lib/roots.mjs +107 -0
  314. package/lib/runtime/uv-bootstrap.mjs +32 -6
  315. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  316. package/lib/runtime-env.mjs +5 -2
  317. package/lib/sandbox.mjs +1 -1
  318. package/lib/scheduler/index.mjs +8 -20
  319. package/lib/schema-infer.mjs +31 -2
  320. package/lib/scopes/lifecycle.mjs +29 -25
  321. package/lib/scopes/loader.mjs +49 -12
  322. package/lib/scopes/teams.mjs +1 -1
  323. package/lib/security/ingest-boundary.mjs +80 -0
  324. package/lib/security/recall-wrapper.mjs +99 -0
  325. package/lib/security/trust.mjs +132 -0
  326. package/lib/service-manager.mjs +93 -24
  327. package/lib/setup.mjs +41 -23
  328. package/lib/skills-apply.mjs +4 -2
  329. package/lib/specialists/postconditions.mjs +2 -2
  330. package/lib/state-root.mjs +147 -0
  331. package/lib/status.mjs +597 -6
  332. package/lib/storage/admin.mjs +84 -7
  333. package/lib/storage/backend-registry.mjs +73 -0
  334. package/lib/storage/backend.mjs +63 -9
  335. package/lib/storage/embeddings-openai.mjs +12 -2
  336. package/lib/storage/hybrid-query.mjs +11 -3
  337. package/lib/storage/retrieval-hardening.mjs +384 -0
  338. package/lib/storage/shared-memory.mjs +158 -0
  339. package/lib/storage/vector-client.mjs +69 -2
  340. package/lib/team/health.mjs +72 -0
  341. package/lib/telemetry/backends/local.mjs +9 -3
  342. package/lib/telemetry/client.mjs +3 -7
  343. package/lib/template-registry.mjs +10 -12
  344. package/lib/tenant/context.mjs +82 -0
  345. package/lib/tenant/isolation.mjs +129 -0
  346. package/lib/test-corpus-inventory.mjs +103 -2
  347. package/lib/uninstall/uninstall.mjs +78 -12
  348. package/lib/validator.mjs +4 -6
  349. package/lib/worker/entrypoint.mjs +2 -1
  350. package/lib/worker/run.mjs +2 -2
  351. package/lib/worker/trace.mjs +5 -11
  352. package/lib/workflow-state.mjs +52 -8
  353. package/lib/workflows/liveness.mjs +203 -0
  354. package/lib/workflows/loader.mjs +177 -0
  355. package/lib/workflows/manifest-schema.mjs +71 -0
  356. package/lib/workflows/surface-parity.mjs +118 -0
  357. package/lib/workflows/validate.mjs +127 -0
  358. package/lib/writes/control-plane.mjs +140 -0
  359. package/lib/writes/envelope.mjs +206 -0
  360. package/lib/writes/sent-log.mjs +86 -0
  361. package/lib/writes/write-intent.mjs +109 -0
  362. package/package.json +20 -7
  363. package/personas/construct.md +12 -3
  364. package/registry/agent-manifest.json +117 -0
  365. package/registry/capabilities.json +1987 -0
  366. package/rules/common/comments.md +1 -0
  367. package/schemas/brand-voice.schema.json +24 -0
  368. package/schemas/capability-registry.schema.json +72 -0
  369. package/schemas/certification-run.schema.json +130 -0
  370. package/schemas/demo-recording.schema.json +46 -0
  371. package/schemas/eval-dataset.schema.json +79 -0
  372. package/schemas/execution-capability-profile.schema.json +46 -0
  373. package/schemas/execution-policy.schema.json +114 -0
  374. package/schemas/improvement-proposal.schema.json +65 -0
  375. package/schemas/mcp-tool-output.schema.json +61 -0
  376. package/schemas/platform-capabilities.schema.json +83 -0
  377. package/schemas/project-config.schema.json +215 -0
  378. package/schemas/project-demo.schema.json +60 -0
  379. package/schemas/provider-behavior-matrix.schema.json +91 -0
  380. package/schemas/scope.schema.json +197 -0
  381. package/schemas/specialist-trace.schema.json +107 -0
  382. package/schemas/team.schema.json +99 -0
  383. package/schemas/unified-registry.schema.json +546 -0
  384. package/scripts/sync-specialists.mjs +336 -106
  385. package/skills/docs/adr-workflow.md +1 -1
  386. package/skills/docs/codebase-research-workflow.md +3 -3
  387. package/skills/docs/prd-workflow.md +5 -6
  388. package/skills/docs/runbook-workflow.md +2 -2
  389. package/skills/docs/user-research-workflow.md +3 -3
  390. package/skills/operating/fleet-health-routing.md +77 -0
  391. package/skills/roles/ai-engineer.md +1 -1
  392. package/skills/roles/architect.md +0 -1
  393. package/skills/roles/business-strategist.md +1 -1
  394. package/skills/roles/data-analyst.telemetry.md +1 -1
  395. package/skills/roles/data-engineer.md +1 -1
  396. package/skills/roles/data-engineer.pipeline.md +1 -1
  397. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  398. package/skills/roles/data-engineer.warehouse.md +1 -1
  399. package/skills/roles/designer.accessibility.md +1 -1
  400. package/skills/roles/designer.md +0 -1
  401. package/skills/roles/devil-advocate.md +1 -1
  402. package/skills/roles/docs-keeper.md +1 -1
  403. package/skills/roles/evaluator.md +1 -1
  404. package/skills/roles/explorer.md +1 -1
  405. package/skills/roles/platform-engineer.md +1 -1
  406. package/skills/roles/qa.ai-eval.md +1 -2
  407. package/skills/roles/qa.api-contract.md +0 -1
  408. package/skills/roles/qa.data-pipeline.md +0 -1
  409. package/skills/roles/qa.md +0 -1
  410. package/skills/roles/qa.web-ui.md +0 -1
  411. package/skills/roles/release-manager.md +1 -1
  412. package/skills/roles/researcher.md +0 -2
  413. package/skills/roles/reviewer.md +0 -3
  414. package/skills/roles/security.ai.md +1 -1
  415. package/skills/roles/security.legal-compliance.md +1 -1
  416. package/skills/roles/security.md +0 -1
  417. package/skills/roles/security.privacy.md +0 -1
  418. package/skills/roles/security.supply-chain.md +1 -1
  419. package/skills/roles/sre.md +1 -1
  420. package/skills/roles/test-automation.md +1 -1
  421. package/skills/roles/trace-reviewer.md +1 -1
  422. package/skills/roles/ux-researcher.md +1 -1
  423. package/specialists/artifact-manifest.json +36 -36
  424. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  425. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  426. package/specialists/org/contracts/any-to-debugger.json +7 -1
  427. package/specialists/org/contracts/any-to-designer.json +7 -1
  428. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  429. package/specialists/org/contracts/any-to-explorer.json +13 -4
  430. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  431. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  432. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  433. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  434. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  435. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  436. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  437. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  438. package/specialists/org/contracts/architect-to-operations.json +17 -4
  439. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  440. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  441. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  442. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  443. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  444. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  445. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  446. package/specialists/org/contracts/operations-to-user.json +53 -0
  447. package/specialists/org/contracts/operations-triage-output.json +53 -0
  448. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  449. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  450. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  451. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  452. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  453. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  454. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  455. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  456. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  457. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  458. package/specialists/org/contracts/user-to-construct.json +11 -4
  459. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  460. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  461. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  462. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  463. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  464. package/specialists/org/groups/engineering-group.json +2 -6
  465. package/specialists/org/groups/governance-group.json +2 -3
  466. package/specialists/org/groups/operations-group.json +5 -8
  467. package/specialists/org/groups/product-group.json +4 -8
  468. package/specialists/org/groups/quality-group.json +3 -7
  469. package/specialists/org/groups/strategy-group.json +6 -9
  470. package/specialists/org/models.json.example +8 -0
  471. package/specialists/org/scopes/creative.json +6 -1
  472. package/specialists/org/scopes/operations.json +7 -1
  473. package/specialists/org/scopes/research.json +6 -1
  474. package/specialists/org/scopes/rnd.json +7 -1
  475. package/specialists/org/specialists/cx-architect.json +27 -8
  476. package/specialists/org/specialists/cx-designer.json +22 -8
  477. package/specialists/org/specialists/cx-engineer.json +71 -7
  478. package/specialists/org/specialists/cx-operations.json +89 -15
  479. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  480. package/specialists/org/specialists/cx-product-manager.json +28 -9
  481. package/specialists/org/specialists/cx-qa.json +16 -6
  482. package/specialists/org/specialists/cx-researcher.json +41 -7
  483. package/specialists/org/specialists/cx-reviewer.json +61 -11
  484. package/specialists/org/specialists/cx-security.json +30 -10
  485. package/specialists/org/teams/design-team.json +3 -4
  486. package/specialists/org/teams/engineering-team.json +3 -10
  487. package/specialists/org/teams/governance-team.json +3 -5
  488. package/specialists/org/teams/operations-team.json +6 -12
  489. package/specialists/org/teams/product-management-team.json +2 -3
  490. package/specialists/org/teams/quality-team.json +4 -12
  491. package/specialists/org/teams/research-team.json +3 -3
  492. package/specialists/org/teams/strategy-team.json +7 -14
  493. package/specialists/prompts/cx-architect.md +20 -1
  494. package/specialists/prompts/cx-data-analyst.md +1 -1
  495. package/specialists/prompts/cx-designer.md +12 -4
  496. package/specialists/prompts/cx-engineer.md +15 -1
  497. package/specialists/prompts/cx-operations.md +14 -2
  498. package/specialists/prompts/cx-orchestrator.md +9 -5
  499. package/specialists/prompts/cx-product-manager.md +5 -1
  500. package/specialists/prompts/cx-qa.md +6 -2
  501. package/specialists/prompts/cx-researcher.md +27 -31
  502. package/specialists/prompts/cx-reviewer.md +19 -1
  503. package/specialists/prompts/cx-security.md +5 -1
  504. package/templates/distribution/construct-brand.typ +123 -59
  505. package/templates/distribution/construct-decision.typ +6 -3
  506. package/templates/distribution/construct-pdf.typ +11 -4
  507. package/templates/distribution/construct-prd.typ +6 -3
  508. package/templates/distribution/construct-research.typ +7 -4
  509. package/vendor/pandoc-ext/README.md +3 -0
  510. package/vendor/pandoc-ext/diagram.lua +687 -0
  511. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  512. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  513. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  514. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  515. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  516. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  517. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  518. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  519. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  520. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  521. package/specialists/org/specialists/cx-accessibility.json +0 -69
  522. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  523. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  524. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  525. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  526. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  527. package/specialists/org/specialists/cx-evaluator.json +0 -69
  528. package/specialists/org/specialists/cx-explorer.json +0 -69
  529. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  530. package/specialists/org/specialists/cx-oracle.json +0 -46
  531. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  532. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  533. package/specialists/org/specialists/cx-release-manager.json +0 -77
  534. package/specialists/org/specialists/cx-sre.json +0 -94
  535. package/specialists/org/specialists/cx-test-automation.json +0 -69
  536. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  537. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  538. package/specialists/org/teams/accessibility-team.json +0 -39
  539. package/specialists/org/teams/ux-research-team.json +0 -39
  540. package/specialists/prompts/cx-accessibility.md +0 -55
  541. package/specialists/prompts/cx-ai-engineer.md +0 -124
  542. package/specialists/prompts/cx-business-strategist.md +0 -58
  543. package/specialists/prompts/cx-data-engineer.md +0 -55
  544. package/specialists/prompts/cx-devil-advocate.md +0 -59
  545. package/specialists/prompts/cx-docs-keeper.md +0 -164
  546. package/specialists/prompts/cx-evaluator.md +0 -49
  547. package/specialists/prompts/cx-explorer.md +0 -71
  548. package/specialists/prompts/cx-legal-compliance.md +0 -58
  549. package/specialists/prompts/cx-oracle.md +0 -98
  550. package/specialists/prompts/cx-platform-engineer.md +0 -97
  551. package/specialists/prompts/cx-rd-lead.md +0 -59
  552. package/specialists/prompts/cx-release-manager.md +0 -54
  553. package/specialists/prompts/cx-sre.md +0 -111
  554. package/specialists/prompts/cx-test-automation.md +0 -55
  555. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  556. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -0,0 +1,176 @@
1
+ /**
2
+ * lib/packs/loader.mjs — pack manifest directory loader and merger.
3
+ *
4
+ * Three manifest tiers exist: builtin (shipped with Construct), user (in
5
+ * ~/.config/construct/packs/), and project (in .cx/packs/). Project packs
6
+ * take highest precedence; user packs override builtin. The core pack is
7
+ * programmatically injected at the builtin tier via loadCorePack().
8
+ *
9
+ * `loadPacksFromDir` reads every subdirectory containing pack.manifest.json
10
+ * and validates each one. `mergePackTiers` merges the three tiers by id.
11
+ * `resolvePackDirs` returns the canonical paths for each tier.
12
+ * `loadAllPacks` is a convenience that loads from all tiers.
13
+ *
14
+ * Prompt validation (LMCP-E2): in team/enterprise deployment mode a pack whose
15
+ * declared prompt files are missing or lack valid frontmatter is rejected at
16
+ * load time — the error names the missing file — rather than letting the
17
+ * worker discover the gap mid-run and silently substitute a generic persona.
18
+ * Solo mode does not hard-fail here; the worker records the miss as a visible
19
+ * degraded fallback instead (lib/orchestration/worker.mjs).
20
+ *
21
+ * embedBindings validation (LMCP-E4): every pack's `embedBindings` block is
22
+ * cross-checked against the builtin extension manifests (lib/extensions) so
23
+ * a binding naming an unknown provider id or an undeclared capability fails
24
+ * pack validation with a path, rather than the authority guard discovering
25
+ * the gap at proposal time.
26
+ */
27
+
28
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
29
+ import { dirname, join, resolve } from 'node:path';
30
+ import { fileURLToPath } from 'node:url';
31
+
32
+ import { validatePackManifest } from './validate.mjs';
33
+ import { validatePackPrompts } from './prompts.mjs';
34
+ import { loadCorePack } from './core-pack.mjs';
35
+ import { getDeploymentMode } from '../deployment-mode.mjs';
36
+ import { loadManifestsFromDir, resolveManifestDirs } from '../extensions/loader.mjs';
37
+
38
+ const __dirname = dirname(fileURLToPath(import.meta.url));
39
+ const PACKAGE_ROOT = resolve(__dirname, '..', '..');
40
+
41
+ const PROMPT_HARD_FAIL_MODES = new Set(['team', 'enterprise']);
42
+
43
+ /**
44
+ * Load builtin extension manifests keyed by id, for embedBindings
45
+ * cross-validation. Only the builtin tier is consulted — the same set every
46
+ * `construct doctor` / pack loader run sees, independent of project overrides.
47
+ */
48
+ function loadKnownProviderManifests(packageRoot) {
49
+ const { builtin } = resolveManifestDirs({ rootDir: packageRoot });
50
+ const { manifests } = loadManifestsFromDir(builtin);
51
+ const byId = {};
52
+ for (const m of manifests) byId[m.id] = m;
53
+ return byId;
54
+ }
55
+
56
+ export function loadPacksFromDir(packsBaseDir, opts = {}) {
57
+ const packs = [];
58
+ const errors = [];
59
+
60
+ if (!existsSync(packsBaseDir)) {
61
+ return { packs, errors };
62
+ }
63
+
64
+ let entries;
65
+ try {
66
+ entries = readdirSync(packsBaseDir, { withFileTypes: true });
67
+ } catch (err) {
68
+ errors.push(`${packsBaseDir}: failed to read directory (${err.message})`);
69
+ return { packs, errors };
70
+ }
71
+
72
+ const packDirs = entries.filter(e => e.isDirectory()).map(e => join(packsBaseDir, e.name)).sort();
73
+ const deploymentMode = opts.deploymentMode || getDeploymentMode(opts.env);
74
+ const packageRoot = opts.packageRoot || PACKAGE_ROOT;
75
+ const knownProviders = opts.knownProviders || loadKnownProviderManifests(packageRoot);
76
+
77
+ for (const packDir of packDirs) {
78
+ const manifestPath = join(packDir, 'pack.manifest.json');
79
+ if (!existsSync(manifestPath)) continue;
80
+
81
+ let parsed;
82
+ try {
83
+ parsed = JSON.parse(readFileSync(manifestPath, 'utf8'));
84
+ } catch (err) {
85
+ errors.push(`${manifestPath}: failed to parse JSON (${err.message})`);
86
+ continue;
87
+ }
88
+
89
+ const result = validatePackManifest(parsed, { filePath: manifestPath, strict: opts.strict, knownProviders });
90
+ if (!result.valid) {
91
+ errors.push(...result.errors);
92
+ continue;
93
+ }
94
+
95
+ if (PROMPT_HARD_FAIL_MODES.has(deploymentMode)) {
96
+ const promptResult = validatePackPrompts({ ...parsed, _packDir: packDir }, { packageRoot });
97
+ if (!promptResult.valid) {
98
+ errors.push(...promptResult.errors.map((e) => `${manifestPath}: ${e}`));
99
+ continue;
100
+ }
101
+ }
102
+
103
+ packs.push({ ...parsed, _tier: opts.tier || 'unknown', _manifestPath: manifestPath, _packDir: packDir });
104
+ }
105
+
106
+ return { packs, errors };
107
+ }
108
+
109
+ export function mergePackTiers(builtin = [], user = [], project = []) {
110
+ const map = new Map();
111
+
112
+ for (const p of builtin) map.set(p.id, p);
113
+ for (const p of user) map.set(p.id, p);
114
+ for (const p of project) map.set(p.id, p);
115
+
116
+ return [...map.values()];
117
+ }
118
+
119
+ export function resolvePackDirs({ rootDir, homeDir } = {}) {
120
+ const home = homeDir ?? (process.env.HOME || process.env.USERPROFILE || '');
121
+
122
+ return {
123
+ builtin: join(PACKAGE_ROOT, 'lib', 'packs', 'manifests'),
124
+ user: join(home, '.config', 'construct', 'packs'),
125
+ project: join(rootDir || process.cwd(), '.cx', 'packs'),
126
+ };
127
+ }
128
+
129
+ export function loadAllPacks(opts = {}) {
130
+ const dirs = resolvePackDirs(opts);
131
+ const allErrors = [];
132
+ const deploymentMode = opts.deploymentMode || getDeploymentMode(opts.env);
133
+ const packageRoot = opts.packageRoot || PACKAGE_ROOT;
134
+ const knownProviders = opts.knownProviders || loadKnownProviderManifests(packageRoot);
135
+
136
+ const builtinResult = loadPacksFromDir(dirs.builtin, { ...opts, tier: 'builtin', deploymentMode, packageRoot, knownProviders });
137
+ allErrors.push(...builtinResult.errors);
138
+
139
+ const userResult = loadPacksFromDir(dirs.user, { ...opts, tier: 'user', deploymentMode, packageRoot, knownProviders });
140
+ allErrors.push(...userResult.errors);
141
+
142
+ const projectResult = loadPacksFromDir(dirs.project, { ...opts, tier: 'project', deploymentMode, packageRoot, knownProviders });
143
+ allErrors.push(...projectResult.errors);
144
+
145
+ const corePack = loadCorePack(packageRoot);
146
+ let builtinPacks = [...builtinResult.packs, corePack];
147
+
148
+ // The core pack ships with Construct and is not read from a manifest file,
149
+ // bypassing loadPacksFromDir's validation loop above; the same hard-fail
150
+ // check still applies under team/enterprise, naming the missing file. The
151
+ // same is true of embedBindings — validated explicitly here so an unknown
152
+ // provider id or undeclared capability in the core pack's own bindings
153
+ // fails loudly instead of silently granting excess authority.
154
+ if (PROMPT_HARD_FAIL_MODES.has(deploymentMode)) {
155
+ const coreResult = validatePackPrompts(corePack, { packageRoot });
156
+ if (!coreResult.valid) {
157
+ allErrors.push(...coreResult.errors.map((e) => `${corePack._sourceDir || '@construct/core'}: ${e}`));
158
+ builtinPacks = builtinResult.packs;
159
+ }
160
+ }
161
+
162
+ if (corePack.embedBindings) {
163
+ const coreBindingResult = validatePackManifest(
164
+ { id: corePack.id, version: corePack.version, compatVersion: corePack.compatVersion, embedBindings: corePack.embedBindings },
165
+ { filePath: corePack._sourceDir || '@construct/core', knownProviders }
166
+ );
167
+ if (!coreBindingResult.valid) {
168
+ allErrors.push(...coreBindingResult.errors);
169
+ builtinPacks = builtinPacks.filter((p) => p.id !== corePack.id);
170
+ }
171
+ }
172
+
173
+ const merged = mergePackTiers(builtinPacks, userResult.packs, projectResult.packs);
174
+
175
+ return { packs: merged, errors: allErrors };
176
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * lib/packs/manifest-schema.mjs — canonical pack manifest schema descriptor.
3
+ *
4
+ * Defines the vocabulary for pack manifests as per ADR-0055 (A5 pack schema).
5
+ * A plain JS descriptor, not JSON Schema — the validator in validate.mjs
6
+ * consumes it directly. The schema is intentionally additive: new optional
7
+ * fields may be added in a minor version bump; new required fields are
8
+ * breaking and require a PACK_COMPAT_VERSION increment.
9
+ *
10
+ * `embedBindings` (LMCP-E4) is one such additive field: per-specialist
11
+ * provider read/search grants and external-write proposal grants, closing
12
+ * the excessive-agency gap where an embedded specialist could read any
13
+ * provider and propose any write with no declared authorization.
14
+ */
15
+
16
+ export const PACK_REQUIRED_FIELDS = ['id', 'version', 'compatVersion'];
17
+
18
+ export const PACK_OPTIONAL_FIELDS = [
19
+ 'teams', 'specialists', 'prompts', 'frameworks', 'perspectives',
20
+ 'modelTierHints', 'toolGrantsRequested', 'workflowContributions',
21
+ 'handoffContracts', 'outputContracts', 'gates', 'tests', 'docs',
22
+ 'installConditions', 'enableConditions', 'deprecation', 'embedBindings',
23
+ ];
24
+
25
+ export const PACK_COMPAT_VERSION = 1;
26
+ export const PACK_ID_RE = /^[a-z0-9\-./@]+$/;
27
+ export const PACK_SOURCE_TIERS = ['builtin', 'user', 'project'];
28
+
29
+ /**
30
+ * embedBindings shape (LMCP-E4) — per-specialist read/propose grant.
31
+ *
32
+ * `embedBindings` is a pack-level map keyed by specialist id:
33
+ *
34
+ * {
35
+ * "<specialistId>": {
36
+ * "providers": [
37
+ * { "id": "<extension-manifest-id>", "capabilities": ["read"], "filters": { ... ADR-0060 filter block ... } }
38
+ * ],
39
+ * "proposals": ["jira.createIssue"]
40
+ * }
41
+ * }
42
+ *
43
+ * `providers[].capabilities` values must be drawn from EMBED_BINDING_CAPABILITIES
44
+ * and must each be declared by the referenced extension manifest's own
45
+ * `capabilities` array — the binding cannot grant a capability the provider
46
+ * itself never advertises. `providers[].filters` reuses the ADR-0060 provider
47
+ * filter block verbatim (scope/predicates/nativeQuery); it is optional and,
48
+ * when present, validated with the same rules poll-time filters use.
49
+ * `proposals` is a flat list of `<providerId>.<writeKind>` tokens the
50
+ * specialist may propose (never execute autonomously — see authority-guard.mjs).
51
+ */
52
+ export const EMBED_BINDING_CAPABILITIES = ['read', 'search'];
53
+
54
+ export const EMBED_BINDING_FIELDS = ['providers', 'proposals'];
55
+
56
+ export const EMBED_BINDING_PROVIDER_FIELDS = ['id', 'capabilities', 'filters'];
57
+
58
+ export const EMBED_BINDING_PROPOSAL_RE = /^[a-z0-9-]+\.[a-zA-Z0-9]+$/;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * lib/packs/prompts.mjs — pack prompt-file validation and registry resolution.
3
+ *
4
+ * A pack manifest's `prompts` map declares `{ specialistId: relativePath }`,
5
+ * relative to that pack's own directory (`_packDir`, set by
6
+ * lib/packs/loader.mjs for every manifest-loaded pack) — a project pack's
7
+ * prompt paths are relative to the project, not to the Construct install.
8
+ * The core pack (lib/packs/core-pack.mjs) has no `_packDir`; its prompts map
9
+ * uses paths relative to `packageRoot` (the Construct install), the fallback
10
+ * root used whenever a pack carries no `_packDir` of its own.
11
+ *
12
+ * validatePackPrompts() confirms every declared file exists AND carries
13
+ * parseable YAML frontmatter with a `role` field — the two things worker.mjs
14
+ * needs before it can trust a persona prompt. In team/enterprise deployment
15
+ * mode a miss is a hard load-time error (the pack is rejected, naming the
16
+ * missing file) per LMCP-E2: a governed pack must never let a specialist run
17
+ * under the wrong persona.
18
+ *
19
+ * resolvePersonaPrompt() is the ONLY path worker.mjs uses to read a persona
20
+ * body: a walk of the pack list in the order given (the caller sorts by tier
21
+ * precedence — project before user before builtin, per ADR-0055), returning
22
+ * the first pack that declares the role, or `{ found: false }` when no pack
23
+ * in the registry declares it. No fallback to reading specialists/prompts/
24
+ * directly exists — that would defeat the pack boundary the registry is
25
+ * meant to enforce.
26
+ */
27
+
28
+ import { existsSync, readFileSync } from 'node:fs';
29
+ import { join } from 'node:path';
30
+ import yaml from 'js-yaml';
31
+
32
+ const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/;
33
+
34
+ function parseFrontmatter(content) {
35
+ const match = content.match(FRONTMATTER_RE);
36
+ if (!match) return null;
37
+ try {
38
+ return yaml.load(match[1]) || {};
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+
44
+ function packPromptRoot(pack, fallbackRoot) {
45
+ return pack?._packDir || fallbackRoot;
46
+ }
47
+
48
+ /**
49
+ * Validate every prompt file a pack declares.
50
+ *
51
+ * @param {object} pack a manifest object carrying `prompts: {id: relPath}`
52
+ * @param {object} opts
53
+ * @param {string} opts.packageRoot fallback root for packs with no `_packDir` (e.g. the core pack)
54
+ * @returns {{valid: boolean, errors: string[]}}
55
+ */
56
+ export function validatePackPrompts(pack, { packageRoot } = {}) {
57
+ const errors = [];
58
+ const prompts = pack?.prompts || {};
59
+ const root = packPromptRoot(pack, packageRoot);
60
+
61
+ for (const [specId, relPath] of Object.entries(prompts)) {
62
+ if (typeof relPath !== 'string' || !relPath) {
63
+ errors.push(`pack '${pack.id}': prompt entry for '${specId}' has no file path`);
64
+ continue;
65
+ }
66
+ const absPath = join(root, relPath);
67
+ if (!existsSync(absPath)) {
68
+ errors.push(`pack '${pack.id}': declared prompt file missing for '${specId}': ${relPath}`);
69
+ continue;
70
+ }
71
+ let raw;
72
+ try {
73
+ raw = readFileSync(absPath, 'utf8');
74
+ } catch (err) {
75
+ errors.push(`pack '${pack.id}': declared prompt file unreadable for '${specId}': ${relPath} (${err.message})`);
76
+ continue;
77
+ }
78
+ const fm = parseFrontmatter(raw);
79
+ if (!fm) {
80
+ errors.push(`pack '${pack.id}': prompt file missing or invalid frontmatter for '${specId}': ${relPath}`);
81
+ continue;
82
+ }
83
+ if (!fm.role || typeof fm.role !== 'string') {
84
+ errors.push(`pack '${pack.id}': prompt frontmatter missing 'role' for '${specId}': ${relPath}`);
85
+ }
86
+ }
87
+
88
+ return { valid: errors.length === 0, errors };
89
+ }
90
+
91
+ /**
92
+ * Resolve one specialist's persona prompt through the pack registry only.
93
+ *
94
+ * Packs are tried in the order given (the caller sorts by tier precedence;
95
+ * see the module header) — the first pack declaring the role wins.
96
+ *
97
+ * @param {string} role specialist id, e.g. 'cx-engineer' (accepts 'engineer' too)
98
+ * @param {object} opts
99
+ * @param {object[]} opts.packs pack list, tier-precedence ordered (see loadAllPacks)
100
+ * @param {string} opts.packageRoot fallback root for packs with no `_packDir` (e.g. the core pack)
101
+ * @returns {{found: true, content: string, packId: string} | {found: false}}
102
+ */
103
+ export function resolvePersonaPrompt(role, { packs, packageRoot }) {
104
+ const slug = String(role || '').replace(/^cx-/, '');
105
+ const specId = `cx-${slug}`;
106
+
107
+ for (const pack of packs || []) {
108
+ const relPath = pack?.prompts?.[specId];
109
+ if (!relPath) continue;
110
+ const absPath = join(packPromptRoot(pack, packageRoot), relPath);
111
+ if (!existsSync(absPath)) continue;
112
+ try {
113
+ return { found: true, content: readFileSync(absPath, 'utf8'), packId: pack.id };
114
+ } catch {
115
+ continue;
116
+ }
117
+ }
118
+
119
+ return { found: false };
120
+ }
@@ -0,0 +1,208 @@
1
+ /**
2
+ * lib/packs/validate.mjs — pack manifest validator.
3
+ *
4
+ * Validates a plain-JS pack manifest object against the canonical schema
5
+ * defined in manifest-schema.mjs. All errors are returned in a structured
6
+ * result; this function never throws. Error messages are single-line,
7
+ * actionable, and prefixed with the filePath (when provided).
8
+ *
9
+ * `embedBindings` validation (LMCP-E4) additionally cross-checks each bound
10
+ * provider id and capability against `knownProviders` — a map of provider id
11
+ * to its loaded extension manifest (lib/extensions/loader.mjs). A binding
12
+ * naming a provider id absent from that map, or granting a capability the
13
+ * provider's own manifest does not declare, fails validation with a path.
14
+ */
15
+
16
+ import {
17
+ PACK_REQUIRED_FIELDS, PACK_OPTIONAL_FIELDS, PACK_COMPAT_VERSION,
18
+ EMBED_BINDING_CAPABILITIES, EMBED_BINDING_FIELDS, EMBED_BINDING_PROVIDER_FIELDS,
19
+ EMBED_BINDING_PROPOSAL_RE,
20
+ } from './manifest-schema.mjs';
21
+ import { validateFilterConfig } from '../providers/contract.mjs';
22
+
23
+ const SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+].+)?$/;
24
+
25
+ const PACK_ID_RE = /^[a-z0-9\-./@]+$/;
26
+
27
+ /**
28
+ * Validate the `embedBindings` block (LMCP-E4) against known extension
29
+ * manifests. `knownProviders` is a map of providerId → manifest (as loaded
30
+ * by lib/extensions/loader.mjs); each bound provider must exist in that map
31
+ * and must itself declare every capability the binding grants — a pack
32
+ * cannot hand a specialist a read/search grant the provider never advertises.
33
+ * Every error names the JSON path of the offending field.
34
+ */
35
+ function validateEmbedBindings(embedBindings, { prefix, knownProviders = {} }) {
36
+ const errors = [];
37
+
38
+ if (typeof embedBindings !== 'object' || embedBindings === null || Array.isArray(embedBindings)) {
39
+ return [`${prefix}embedBindings must be an object keyed by specialist id`];
40
+ }
41
+
42
+ for (const [specialistId, binding] of Object.entries(embedBindings)) {
43
+ const bindingPath = `embedBindings.${specialistId}`;
44
+
45
+ if (typeof binding !== 'object' || binding === null || Array.isArray(binding)) {
46
+ errors.push(`${prefix}${bindingPath} must be an object`);
47
+ continue;
48
+ }
49
+
50
+ for (const key of Object.keys(binding)) {
51
+ if (!EMBED_BINDING_FIELDS.includes(key)) {
52
+ errors.push(`${prefix}${bindingPath}.${key} is not a recognized embedBindings field (allowed: ${EMBED_BINDING_FIELDS.join(', ')})`);
53
+ }
54
+ }
55
+
56
+ const providers = binding.providers;
57
+ if (providers !== undefined) {
58
+ if (!Array.isArray(providers)) {
59
+ errors.push(`${prefix}${bindingPath}.providers must be an array`);
60
+ } else {
61
+ providers.forEach((entry, idx) => {
62
+ const entryPath = `${bindingPath}.providers[${idx}]`;
63
+
64
+ if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
65
+ errors.push(`${prefix}${entryPath} must be an object`);
66
+ return;
67
+ }
68
+
69
+ for (const key of Object.keys(entry)) {
70
+ if (!EMBED_BINDING_PROVIDER_FIELDS.includes(key)) {
71
+ errors.push(`${prefix}${entryPath}.${key} is not a recognized provider binding field (allowed: ${EMBED_BINDING_PROVIDER_FIELDS.join(', ')})`);
72
+ }
73
+ }
74
+
75
+ const providerId = entry.id;
76
+ if (typeof providerId !== 'string' || providerId.length === 0) {
77
+ errors.push(`${prefix}${entryPath}.id must be a non-empty string`);
78
+ return;
79
+ }
80
+
81
+ const providerManifest = knownProviders[providerId];
82
+ if (!providerManifest) {
83
+ errors.push(`${prefix}${entryPath}.id references unknown provider "${providerId}" (no extension manifest declares this id)`);
84
+ return;
85
+ }
86
+
87
+ const declaredCapabilities = new Set(providerManifest.capabilities || []);
88
+
89
+ if (!Array.isArray(entry.capabilities) || entry.capabilities.length === 0) {
90
+ errors.push(`${prefix}${entryPath}.capabilities must be a non-empty array`);
91
+ } else {
92
+ entry.capabilities.forEach((cap, capIdx) => {
93
+ const capPath = `${entryPath}.capabilities[${capIdx}]`;
94
+ if (!EMBED_BINDING_CAPABILITIES.includes(cap)) {
95
+ errors.push(`${prefix}${capPath} "${cap}" is not a recognized embed capability (allowed: ${EMBED_BINDING_CAPABILITIES.join(', ')})`);
96
+ } else if (!declaredCapabilities.has(cap)) {
97
+ errors.push(`${prefix}${capPath} "${cap}" is not declared by provider "${providerId}"'s manifest capabilities (${[...declaredCapabilities].join(', ') || '(none)'})`);
98
+ }
99
+ });
100
+ }
101
+
102
+ if (entry.filters !== undefined) {
103
+ try {
104
+ validateFilterConfig(providerId, entry.filters);
105
+ } catch (err) {
106
+ errors.push(`${prefix}${entryPath}.filters: ${err.message}`);
107
+ }
108
+ }
109
+ });
110
+ }
111
+ }
112
+
113
+ const proposals = binding.proposals;
114
+ if (proposals !== undefined) {
115
+ if (!Array.isArray(proposals)) {
116
+ errors.push(`${prefix}${bindingPath}.proposals must be an array`);
117
+ } else {
118
+ const boundProviderIds = new Set((Array.isArray(providers) ? providers : []).map((p) => p?.id).filter(Boolean));
119
+ proposals.forEach((token, idx) => {
120
+ const tokenPath = `${bindingPath}.proposals[${idx}]`;
121
+ if (typeof token !== 'string' || !EMBED_BINDING_PROPOSAL_RE.test(token)) {
122
+ errors.push(`${prefix}${tokenPath} "${token}" must be a "<providerId>.<writeKind>" token (e.g. "jira.createIssue")`);
123
+ return;
124
+ }
125
+ const [providerId] = token.split('.');
126
+ if (!knownProviders[providerId]) {
127
+ errors.push(`${prefix}${tokenPath} "${token}" references unknown provider "${providerId}" (no extension manifest declares this id)`);
128
+ } else if (boundProviderIds.size > 0 && !boundProviderIds.has(providerId)) {
129
+ errors.push(`${prefix}${tokenPath} "${token}" proposes against provider "${providerId}" which is not in this specialist's providers[] grant`);
130
+ }
131
+ });
132
+ }
133
+ }
134
+ }
135
+
136
+ return errors;
137
+ }
138
+
139
+ export function validatePackManifest(manifest, { filePath, strict = false, knownProviders = {} } = {}) {
140
+ const prefix = filePath ? `${filePath}: ` : '';
141
+ const errors = [];
142
+
143
+ if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
144
+ return { valid: false, errors: [`${prefix}manifest must be a JSON object`] };
145
+ }
146
+
147
+ for (const field of PACK_REQUIRED_FIELDS) {
148
+ if (!(field in manifest) || manifest[field] === undefined || manifest[field] === null) {
149
+ errors.push(`${prefix}missing required field: ${field}`);
150
+ }
151
+ }
152
+
153
+ if ('id' in manifest && manifest.id !== undefined && manifest.id !== null) {
154
+ if (typeof manifest.id !== 'string' || manifest.id.length === 0) {
155
+ errors.push(`${prefix}id must be a non-empty string`);
156
+ } else if (!PACK_ID_RE.test(manifest.id)) {
157
+ errors.push(`${prefix}id must match [a-z0-9-./@]+ (got '${manifest.id}')`);
158
+ }
159
+ }
160
+
161
+ if ('version' in manifest && manifest.version !== undefined && manifest.version !== null) {
162
+ if (typeof manifest.version !== 'string' || !SEMVER_RE.test(manifest.version)) {
163
+ errors.push(`${prefix}version must be a semver string`);
164
+ }
165
+ }
166
+
167
+ if (manifest.compatVersion !== undefined && manifest.compatVersion !== null) {
168
+ if (typeof manifest.compatVersion !== 'number' || !Number.isInteger(manifest.compatVersion)) {
169
+ errors.push(`${prefix}compatVersion must be an integer`);
170
+ } else if (manifest.compatVersion > PACK_COMPAT_VERSION) {
171
+ errors.push(
172
+ `${prefix}compatVersion ${manifest.compatVersion} exceeds supported version ${PACK_COMPAT_VERSION}; upgrade Construct to use this pack`
173
+ );
174
+ }
175
+ }
176
+
177
+ if (manifest.deprecation !== undefined && manifest.deprecation !== null) {
178
+ if (typeof manifest.deprecation !== 'object' || Array.isArray(manifest.deprecation)) {
179
+ errors.push(`${prefix}deprecation must be an object`);
180
+ } else {
181
+ if (!('since' in manifest.deprecation)) {
182
+ errors.push(`${prefix}deprecation must include 'since' field`);
183
+ }
184
+ if (!('message' in manifest.deprecation)) {
185
+ errors.push(`${prefix}deprecation must include 'message' field`);
186
+ }
187
+ }
188
+ }
189
+
190
+ if (manifest.embedBindings !== undefined && manifest.embedBindings !== null) {
191
+ errors.push(...validateEmbedBindings(manifest.embedBindings, { prefix, knownProviders }));
192
+ }
193
+
194
+ if (strict) {
195
+ const knownFields = new Set([...PACK_REQUIRED_FIELDS, ...PACK_OPTIONAL_FIELDS]);
196
+ for (const key of Object.keys(manifest)) {
197
+ if (key.startsWith('_')) continue;
198
+ if (!knownFields.has(key)) {
199
+ errors.push(`${prefix}unknown field '${key}' in strict mode`);
200
+ }
201
+ }
202
+ }
203
+
204
+ if (errors.length > 0) {
205
+ return { valid: false, errors };
206
+ }
207
+ return { valid: true };
208
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * lib/path-policy.mjs — root containment for model-supplied file paths.
3
+ *
4
+ * MCP file tools take paths from model arguments. resolveWithinRoot pins each path to a
5
+ * declared root: it rejects backslash/UNC separators, out-of-root absolute or ../ targets,
6
+ * and symlinks whose real target escapes the root. Containment is checked on the realpath
7
+ * of the longest existing ancestor, so a not-yet-existing target is still anchored through
8
+ * real directories. On any escape it throws PathContainmentError whose message names the
9
+ * denial; otherwise it returns the canonical absolute path.
10
+ */
11
+ import { realpathSync } from 'node:fs';
12
+ import path from 'node:path';
13
+
14
+ export class PathContainmentError extends Error {
15
+ constructor(message) {
16
+ super(message);
17
+ this.name = 'PathContainmentError';
18
+ }
19
+ }
20
+
21
+ // A target may not exist yet (a delete of an already-gone file, a path with a missing
22
+ // tail). Resolve symlinks on the longest existing prefix, then re-append the remaining
23
+ // segments, so the escape check still sees through real ancestors.
24
+
25
+ function canonical(p) {
26
+ const abs = path.resolve(p);
27
+ let cur = abs;
28
+ const tail = [];
29
+ while (true) {
30
+ try {
31
+ const real = realpathSync(cur);
32
+ return tail.length ? path.join(real, ...tail) : real;
33
+ } catch {
34
+ const parent = path.dirname(cur);
35
+ if (parent === cur) return abs;
36
+ tail.unshift(path.basename(cur));
37
+ cur = parent;
38
+ }
39
+ }
40
+ }
41
+
42
+ export function resolveWithinRoot(root, candidate) {
43
+ if (typeof candidate !== 'string' || candidate.length === 0) {
44
+ throw new PathContainmentError('path denied: empty or non-string file path');
45
+ }
46
+ if (candidate.includes('\\')) {
47
+ throw new PathContainmentError('path denied: backslash/UNC separators escape the allowed root');
48
+ }
49
+ const base = canonical(root);
50
+ const requested = path.isAbsolute(candidate) ? candidate : path.join(path.resolve(root), candidate);
51
+ const resolved = canonical(requested);
52
+ if (resolved !== base && !resolved.startsWith(base + path.sep)) {
53
+ throw new PathContainmentError(`path denied: ${candidate} resolves outside the allowed root`);
54
+ }
55
+ return resolved;
56
+ }
@@ -6,11 +6,10 @@
6
6
  */
7
7
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
8
8
  import { homedir } from "node:os";
9
- import { dirname, extname, join, resolve } from "node:path";
10
- import { fileURLToPath } from "node:url";
9
+ import { extname, join, resolve } from "node:path";
11
10
  import { configDir } from "./config/xdg.mjs";
11
+ import { packageRoot } from "./roots.mjs";
12
12
 
13
- const __dirname = dirname(fileURLToPath(import.meta.url));
14
13
  export const MANIFEST_VERSION = 1;
15
14
 
16
15
  function readJson(filePath) {
@@ -97,7 +96,7 @@ export function validatePluginManifest(manifest, { manifestPath = "<memory>" } =
97
96
 
98
97
  function loadBuiltinPlugin(rootDir) {
99
98
  const rootCatalogPath = join(rootDir, "lib", "mcp-catalog.json");
100
- const catalogPath = existsSync(rootCatalogPath) ? rootCatalogPath : join(__dirname, "mcp-catalog.json");
99
+ const catalogPath = existsSync(rootCatalogPath) ? rootCatalogPath : join(packageRoot, "lib", "mcp-catalog.json");
101
100
  const catalog = readJson(catalogPath);
102
101
  const plugin = {
103
102
  id: "construct-builtins",
@@ -142,7 +141,7 @@ function findManifestFiles(pluginDirs) {
142
141
  return files.sort();
143
142
  }
144
143
 
145
- export function loadPluginRegistry({ cwd = process.cwd(), homeDir = homedir(), rootDir = resolve(__dirname, ".."), env = process.env } = {}) {
144
+ export function loadPluginRegistry({ cwd = process.cwd(), homeDir = homedir(), rootDir = packageRoot, env = process.env } = {}) {
146
145
  const errors = [];
147
146
  const plugins = [loadBuiltinPlugin(rootDir)];
148
147
  const pluginDirs = resolvePluginDirs({ cwd, homeDir, env });