@geraldmaron/construct 1.4.2 → 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 (519) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1027 -64
  4. package/examples/distribution/sources/deck-one-pager.md +1 -1
  5. package/lib/acp/server.mjs +21 -7
  6. package/lib/adapters-sync.mjs +2 -1
  7. package/lib/audit-trail.mjs +185 -2
  8. package/lib/beads/auto-close.mjs +2 -1
  9. package/lib/beads/drift.mjs +2 -1
  10. package/lib/bridges/copilot-proxy.mjs +20 -5
  11. package/lib/certification/skill-inventory.mjs +10 -1
  12. package/lib/cli/approvals.mjs +147 -0
  13. package/lib/cli-commands.mjs +105 -14
  14. package/lib/comment-lint.mjs +127 -8
  15. package/lib/config/schema.mjs +6 -29
  16. package/lib/config/source-target-registry.mjs +70 -0
  17. package/lib/config/source-targets.mjs +179 -205
  18. package/lib/contracts/coverage.mjs +77 -0
  19. package/lib/contracts/validate.mjs +102 -13
  20. package/lib/contracts/violation-log.mjs +50 -4
  21. package/lib/db/migrate.mjs +69 -0
  22. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  23. package/lib/db/migrations/002_queue_provider.sql +50 -0
  24. package/lib/db/migrations/003_worker_registry.sql +17 -0
  25. package/lib/db/migrations/004_trace_events.sql +16 -0
  26. package/lib/db/migrations/005_shared_memory.sql +15 -0
  27. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  28. package/lib/decisions/enforced-baseline.json +0 -2
  29. package/lib/decisions/registry.mjs +3 -2
  30. package/lib/deployment/parity-contract.mjs +1 -1
  31. package/lib/deployment-mode.mjs +78 -2
  32. package/lib/diagram-export.mjs +10 -1
  33. package/lib/distill.mjs +5 -2
  34. package/lib/docs-verify.mjs +2 -1
  35. package/lib/doctor/cli.mjs +1 -0
  36. package/lib/doctor/command-on-path.mjs +24 -0
  37. package/lib/doctor/diagnosis.mjs +89 -0
  38. package/lib/doctor/embedding-health.mjs +50 -0
  39. package/lib/doctor/engine-health.mjs +93 -0
  40. package/lib/doctor/graph-validate.mjs +47 -0
  41. package/lib/doctor/index.mjs +18 -4
  42. package/lib/doctor/sidecar-providers.mjs +56 -0
  43. package/lib/doctor/watchers/consistency.mjs +93 -1
  44. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  45. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  46. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  47. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  48. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  49. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  50. package/lib/document-export.mjs +52 -27
  51. package/lib/document-extract/docling-client.mjs +9 -5
  52. package/lib/document-extract/docling-sidecar.py +30 -0
  53. package/lib/document-extract.mjs +1 -1
  54. package/lib/document-ingest.mjs +9 -0
  55. package/lib/embed/approval-queue.mjs +184 -88
  56. package/lib/embed/authority-guard.mjs +65 -2
  57. package/lib/embed/capability-jobs.mjs +365 -0
  58. package/lib/embed/capability-lifecycle.mjs +219 -0
  59. package/lib/embed/capability-loader.mjs +303 -0
  60. package/lib/embed/capability-runtime.mjs +58 -0
  61. package/lib/embed/cli.mjs +185 -8
  62. package/lib/embed/config.mjs +4 -0
  63. package/lib/embed/daemon.mjs +125 -6
  64. package/lib/embed/demand-fetch.mjs +190 -54
  65. package/lib/embed/inbox.mjs +12 -10
  66. package/lib/embed/presets/ops-triage.mjs +236 -0
  67. package/lib/embed/presets/pm-feedback.mjs +341 -0
  68. package/lib/embed/presets/tpm.mjs +401 -0
  69. package/lib/embed/providers/jira.mjs +72 -1
  70. package/lib/embed/providers/registry.mjs +140 -33
  71. package/lib/embedded-contract/capability.mjs +4 -0
  72. package/lib/embedded-contract/execution.mjs +1 -1
  73. package/lib/embedded-contract/model-resolve.mjs +53 -3
  74. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  75. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  76. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  77. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  78. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  79. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  80. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  81. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  82. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  83. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  84. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  85. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  86. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  88. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  90. package/lib/env-config.mjs +50 -9
  91. package/lib/extensions/index.mjs +27 -0
  92. package/lib/extensions/loader.mjs +120 -0
  93. package/lib/extensions/manifest-schema.mjs +141 -0
  94. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  95. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  96. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  97. package/lib/extensions/manifests/directory.manifest.json +12 -0
  98. package/lib/extensions/manifests/docling.manifest.json +37 -0
  99. package/lib/extensions/manifests/echo.manifest.json +8 -0
  100. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  101. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  102. package/lib/extensions/manifests/github.manifest.json +52 -0
  103. package/lib/extensions/manifests/linear.manifest.json +50 -0
  104. package/lib/extensions/manifests/local.manifest.json +12 -0
  105. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  106. package/lib/extensions/manifests/openai.manifest.json +12 -0
  107. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  108. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  109. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  110. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  113. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  114. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  115. package/lib/extensions/manifests/slack.manifest.json +58 -0
  116. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  117. package/lib/extensions/validate.mjs +175 -0
  118. package/lib/features.mjs +13 -9
  119. package/lib/flows/checkpoint.mjs +184 -0
  120. package/lib/flows/constants.mjs +27 -0
  121. package/lib/flows/define.mjs +146 -0
  122. package/lib/flows/engine.mjs +249 -0
  123. package/lib/flows/errors.mjs +19 -0
  124. package/lib/flows/index.mjs +27 -0
  125. package/lib/flows/joins.mjs +18 -0
  126. package/lib/flows/schema.mjs +90 -0
  127. package/lib/flows/state.mjs +31 -0
  128. package/lib/frameworks/loader.mjs +99 -0
  129. package/lib/frameworks/schema.mjs +157 -0
  130. package/lib/graph/build-from-corpus.mjs +67 -0
  131. package/lib/graph/build-from-embed.mjs +82 -0
  132. package/lib/graph/build-from-registry.mjs +164 -3
  133. package/lib/graph/cli.mjs +456 -12
  134. package/lib/graph/gap-queries.mjs +156 -0
  135. package/lib/graph/gaps.mjs +41 -0
  136. package/lib/graph/impacted.mjs +129 -0
  137. package/lib/graph/runtime-evidence.mjs +177 -0
  138. package/lib/graph/security-coverage.mjs +113 -0
  139. package/lib/graph/staleness.mjs +241 -10
  140. package/lib/graph/store.mjs +24 -7
  141. package/lib/graph/validate.mjs +161 -0
  142. package/lib/headhunt.mjs +9 -8
  143. package/lib/health-check.mjs +15 -7
  144. package/lib/hook-health.mjs +1 -1
  145. package/lib/hooks/agent-tracker.mjs +10 -4
  146. package/lib/hooks/edit-guard.mjs +3 -3
  147. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  148. package/lib/hooks/guard-bash.mjs +41 -15
  149. package/lib/hooks/mcp-health-check.mjs +11 -4
  150. package/lib/hooks/model-fallback.mjs +50 -6
  151. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  152. package/lib/hooks/pre-compact.mjs +181 -173
  153. package/lib/hooks/rule-verifier.mjs +2 -1
  154. package/lib/hooks/session-reflect.mjs +2 -1
  155. package/lib/hooks/session-start.mjs +3 -3
  156. package/lib/host-capabilities.mjs +13 -2
  157. package/lib/host-disposition.mjs +19 -7
  158. package/lib/identity.mjs +92 -0
  159. package/lib/ingest/degraded-extract.mjs +96 -0
  160. package/lib/ingest/docling-remote.mjs +2 -1
  161. package/lib/ingest/provider-extract.mjs +7 -19
  162. package/lib/ingest/sidecar-providers.mjs +196 -0
  163. package/lib/ingest-tooling.mjs +11 -5
  164. package/lib/init-unified.mjs +55 -38
  165. package/lib/init-update.mjs +2 -1
  166. package/lib/install/legacy-global-cleanup.mjs +25 -0
  167. package/lib/intake/daemon.mjs +99 -8
  168. package/lib/intake/git-queue.mjs +110 -38
  169. package/lib/intake/queue-registry.mjs +50 -0
  170. package/lib/intake/queue.mjs +133 -17
  171. package/lib/intake/session-prelude.mjs +57 -8
  172. package/lib/integrations/intake-integrations.mjs +61 -111
  173. package/lib/intent-classifier.mjs +43 -5
  174. package/lib/libreoffice-export.mjs +8 -8
  175. package/lib/logging/rotate.mjs +5 -4
  176. package/lib/mcp/broker.mjs +332 -17
  177. package/lib/mcp/denied-store.mjs +95 -0
  178. package/lib/mcp/destructive-gate.mjs +30 -0
  179. package/lib/mcp/dispatch-envelope.mjs +199 -0
  180. package/lib/mcp/memory-bridge.mjs +2 -1
  181. package/lib/mcp/server.mjs +154 -1411
  182. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  183. package/lib/mcp/tool-definitions-project.mjs +271 -0
  184. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  185. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  186. package/lib/mcp/tool-definitions.mjs +25 -0
  187. package/lib/mcp/tool-registry.mjs +107 -0
  188. package/lib/mcp/tool-safety.mjs +1 -0
  189. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  190. package/lib/mcp/tools/orchestration-run.mjs +165 -25
  191. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  192. package/lib/mcp/tools/provider-write.mjs +187 -0
  193. package/lib/mcp/tools/scope.mjs +0 -3
  194. package/lib/mcp/tools/skills.mjs +1 -1
  195. package/lib/mcp/tools/storage.mjs +0 -8
  196. package/lib/mcp/transport/auth.mjs +238 -0
  197. package/lib/mcp/transport/http.mjs +136 -0
  198. package/lib/mcp/transport/mode.mjs +31 -0
  199. package/lib/mcp/transport/stdio.mjs +22 -0
  200. package/lib/mcp-catalog.json +4 -4
  201. package/lib/mcp-manager.mjs +34 -23
  202. package/lib/mcp-platform-config.mjs +31 -7
  203. package/lib/mode-capabilities.mjs +109 -0
  204. package/lib/model-router.mjs +42 -9
  205. package/lib/models/catalog.mjs +40 -1
  206. package/lib/net-guard.mjs +247 -0
  207. package/lib/observation-store.mjs +6 -2
  208. package/lib/ollama/provision-context.mjs +2 -1
  209. package/lib/opencode-config.mjs +22 -3
  210. package/lib/opencode-runtime-plugin.mjs +120 -2
  211. package/lib/oracle/actions.mjs +204 -10
  212. package/lib/oracle/cli.mjs +24 -3
  213. package/lib/oracle/dispatch.mjs +2 -1
  214. package/lib/oracle/execute.mjs +2 -2
  215. package/lib/oracle/gaps.mjs +3 -3
  216. package/lib/oracle/heartbeat.mjs +53 -0
  217. package/lib/oracle/read-model.mjs +69 -13
  218. package/lib/oracle/reconcile.mjs +3 -46
  219. package/lib/oracle/routing.mjs +37 -31
  220. package/lib/oracle/synthesize.mjs +1 -1
  221. package/lib/orchestration/classification.mjs +434 -0
  222. package/lib/orchestration/delegation-flow.mjs +132 -0
  223. package/lib/orchestration/flow-selection.mjs +418 -0
  224. package/lib/orchestration/gates.mjs +244 -0
  225. package/lib/orchestration/host-sampling.mjs +121 -0
  226. package/lib/orchestration/policy-constants.mjs +48 -0
  227. package/lib/orchestration/provider-outcome.mjs +197 -0
  228. package/lib/orchestration/readiness.mjs +114 -13
  229. package/lib/orchestration/run-store-postgres.mjs +32 -26
  230. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  231. package/lib/orchestration/run-store.mjs +25 -12
  232. package/lib/orchestration/runtime.mjs +446 -39
  233. package/lib/orchestration/store.mjs +87 -12
  234. package/lib/orchestration/trace-store.mjs +125 -0
  235. package/lib/orchestration/web-capability.mjs +3 -2
  236. package/lib/orchestration/worker-runtime.mjs +162 -0
  237. package/lib/orchestration/worker.mjs +528 -89
  238. package/lib/orchestration-policy.mjs +67 -1111
  239. package/lib/packs/cli.mjs +144 -0
  240. package/lib/packs/core-pack.mjs +112 -0
  241. package/lib/packs/enablement.mjs +187 -0
  242. package/lib/packs/index.mjs +23 -0
  243. package/lib/packs/loader.mjs +176 -0
  244. package/lib/packs/manifest-schema.mjs +58 -0
  245. package/lib/packs/prompts.mjs +120 -0
  246. package/lib/packs/validate.mjs +208 -0
  247. package/lib/plugin-registry.mjs +4 -5
  248. package/lib/policy/audit-gate.mjs +42 -0
  249. package/lib/policy/consumption-budget.mjs +149 -0
  250. package/lib/policy/engine.mjs +81 -6
  251. package/lib/policy/role-authority.mjs +89 -0
  252. package/lib/prompt-composer.js +19 -3
  253. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  254. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  255. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  256. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  257. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  258. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  259. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  260. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  261. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  262. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  263. package/lib/providers/contract.mjs +207 -0
  264. package/lib/providers/credential-bootstrap.mjs +7 -4
  265. package/lib/providers/credential-sources.mjs +14 -2
  266. package/lib/providers/directory/index.mjs +261 -0
  267. package/lib/providers/feedback/index.mjs +392 -0
  268. package/lib/providers/filter-audit.mjs +68 -0
  269. package/lib/providers/filter-schema.mjs +54 -0
  270. package/lib/providers/github/index.mjs +31 -0
  271. package/lib/providers/instance-config.mjs +215 -0
  272. package/lib/providers/op-locate.mjs +96 -0
  273. package/lib/providers/op-run.mjs +64 -9
  274. package/lib/providers/registry.mjs +26 -3
  275. package/lib/providers/secret-audit-wiring.mjs +6 -0
  276. package/lib/providers/secret-resolver.mjs +240 -23
  277. package/lib/publish-template.mjs +6 -3
  278. package/lib/publish-tooling.mjs +4 -0
  279. package/lib/publish.mjs +32 -4
  280. package/lib/queue/pg-queue.mjs +395 -0
  281. package/lib/reflect.mjs +2 -1
  282. package/lib/registry/assemble.mjs +28 -2
  283. package/lib/registry/consolidation.mjs +8 -1
  284. package/lib/registry/custom-scaffold.mjs +200 -0
  285. package/lib/registry/custom-schema.mjs +137 -0
  286. package/lib/registry/loader.mjs +8 -3
  287. package/lib/registry/manifests/format-engines.default.json +15 -0
  288. package/lib/registry/manifests/surface-map.default.json +46 -0
  289. package/lib/registry/retired-paths.mjs +1 -0
  290. package/lib/registry/surface-map.mjs +100 -50
  291. package/lib/render-visual-check.mjs +240 -0
  292. package/lib/resources/budget.mjs +40 -17
  293. package/lib/roles/flavor-bindings.mjs +36 -14
  294. package/lib/roles/gateway.mjs +15 -8
  295. package/lib/roots.mjs +107 -0
  296. package/lib/runtime/uv-bootstrap.mjs +32 -6
  297. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  298. package/lib/runtime-env.mjs +5 -2
  299. package/lib/scheduler/index.mjs +8 -20
  300. package/lib/schema-infer.mjs +31 -2
  301. package/lib/scopes/lifecycle.mjs +29 -25
  302. package/lib/scopes/loader.mjs +49 -12
  303. package/lib/scopes/teams.mjs +1 -1
  304. package/lib/security/ingest-boundary.mjs +80 -0
  305. package/lib/security/recall-wrapper.mjs +99 -0
  306. package/lib/security/trust.mjs +132 -0
  307. package/lib/service-manager.mjs +38 -19
  308. package/lib/setup.mjs +41 -23
  309. package/lib/skills-apply.mjs +4 -2
  310. package/lib/specialists/postconditions.mjs +2 -2
  311. package/lib/state-root.mjs +147 -0
  312. package/lib/status.mjs +597 -6
  313. package/lib/storage/admin.mjs +77 -5
  314. package/lib/storage/backend-registry.mjs +73 -0
  315. package/lib/storage/backend.mjs +63 -9
  316. package/lib/storage/embeddings-openai.mjs +12 -2
  317. package/lib/storage/hybrid-query.mjs +11 -3
  318. package/lib/storage/retrieval-hardening.mjs +384 -0
  319. package/lib/storage/shared-memory.mjs +158 -0
  320. package/lib/storage/vector-client.mjs +69 -2
  321. package/lib/team/health.mjs +72 -0
  322. package/lib/telemetry/backends/local.mjs +9 -3
  323. package/lib/telemetry/client.mjs +3 -7
  324. package/lib/template-registry.mjs +10 -12
  325. package/lib/tenant/context.mjs +82 -0
  326. package/lib/tenant/isolation.mjs +129 -0
  327. package/lib/test-corpus-inventory.mjs +103 -2
  328. package/lib/uninstall/uninstall.mjs +78 -12
  329. package/lib/validator.mjs +4 -6
  330. package/lib/worker/entrypoint.mjs +2 -1
  331. package/lib/worker/run.mjs +2 -2
  332. package/lib/worker/trace.mjs +5 -11
  333. package/lib/workflow-state.mjs +52 -8
  334. package/lib/workflows/liveness.mjs +203 -0
  335. package/lib/workflows/loader.mjs +177 -0
  336. package/lib/workflows/manifest-schema.mjs +71 -0
  337. package/lib/workflows/surface-parity.mjs +118 -0
  338. package/lib/workflows/validate.mjs +127 -0
  339. package/lib/writes/control-plane.mjs +140 -0
  340. package/lib/writes/envelope.mjs +206 -0
  341. package/lib/writes/sent-log.mjs +86 -0
  342. package/lib/writes/write-intent.mjs +109 -0
  343. package/package.json +16 -8
  344. package/personas/construct.md +12 -3
  345. package/registry/capabilities.json +143 -36
  346. package/rules/common/comments.md +1 -0
  347. package/schemas/project-config.schema.json +0 -12
  348. package/schemas/unified-registry.schema.json +2 -4
  349. package/scripts/sync-specialists.mjs +284 -81
  350. package/skills/docs/adr-workflow.md +1 -1
  351. package/skills/docs/codebase-research-workflow.md +3 -3
  352. package/skills/docs/prd-workflow.md +5 -6
  353. package/skills/docs/runbook-workflow.md +2 -2
  354. package/skills/docs/user-research-workflow.md +3 -3
  355. package/skills/operating/fleet-health-routing.md +77 -0
  356. package/skills/roles/ai-engineer.md +1 -1
  357. package/skills/roles/architect.md +0 -1
  358. package/skills/roles/business-strategist.md +1 -1
  359. package/skills/roles/data-analyst.telemetry.md +1 -1
  360. package/skills/roles/data-engineer.md +1 -1
  361. package/skills/roles/data-engineer.pipeline.md +1 -1
  362. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  363. package/skills/roles/data-engineer.warehouse.md +1 -1
  364. package/skills/roles/designer.accessibility.md +1 -1
  365. package/skills/roles/designer.md +0 -1
  366. package/skills/roles/devil-advocate.md +1 -1
  367. package/skills/roles/docs-keeper.md +1 -1
  368. package/skills/roles/evaluator.md +1 -1
  369. package/skills/roles/explorer.md +1 -1
  370. package/skills/roles/platform-engineer.md +1 -1
  371. package/skills/roles/qa.ai-eval.md +1 -2
  372. package/skills/roles/qa.api-contract.md +0 -1
  373. package/skills/roles/qa.data-pipeline.md +0 -1
  374. package/skills/roles/qa.md +0 -1
  375. package/skills/roles/qa.web-ui.md +0 -1
  376. package/skills/roles/release-manager.md +1 -1
  377. package/skills/roles/researcher.md +0 -2
  378. package/skills/roles/reviewer.md +0 -3
  379. package/skills/roles/security.ai.md +1 -1
  380. package/skills/roles/security.legal-compliance.md +1 -1
  381. package/skills/roles/security.md +0 -1
  382. package/skills/roles/security.privacy.md +0 -1
  383. package/skills/roles/security.supply-chain.md +1 -1
  384. package/skills/roles/sre.md +1 -1
  385. package/skills/roles/test-automation.md +1 -1
  386. package/skills/roles/trace-reviewer.md +1 -1
  387. package/skills/roles/ux-researcher.md +1 -1
  388. package/specialists/artifact-manifest.json +36 -36
  389. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  390. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  391. package/specialists/org/contracts/any-to-debugger.json +7 -1
  392. package/specialists/org/contracts/any-to-designer.json +7 -1
  393. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  394. package/specialists/org/contracts/any-to-explorer.json +13 -4
  395. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  396. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  397. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  398. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  399. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  400. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  401. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  402. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  403. package/specialists/org/contracts/architect-to-operations.json +17 -4
  404. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  405. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  406. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  407. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  408. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  409. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  410. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  411. package/specialists/org/contracts/operations-to-user.json +53 -0
  412. package/specialists/org/contracts/operations-triage-output.json +53 -0
  413. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  414. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  415. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  416. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  417. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  418. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  419. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  420. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  421. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  422. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  423. package/specialists/org/contracts/user-to-construct.json +11 -4
  424. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  425. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  426. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  427. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  428. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  429. package/specialists/org/groups/engineering-group.json +2 -6
  430. package/specialists/org/groups/governance-group.json +2 -3
  431. package/specialists/org/groups/operations-group.json +5 -8
  432. package/specialists/org/groups/product-group.json +4 -8
  433. package/specialists/org/groups/quality-group.json +3 -7
  434. package/specialists/org/groups/strategy-group.json +6 -9
  435. package/specialists/org/models.json.example +8 -0
  436. package/specialists/org/scopes/creative.json +6 -1
  437. package/specialists/org/scopes/operations.json +7 -1
  438. package/specialists/org/scopes/research.json +6 -1
  439. package/specialists/org/scopes/rnd.json +7 -1
  440. package/specialists/org/specialists/cx-architect.json +27 -8
  441. package/specialists/org/specialists/cx-designer.json +22 -8
  442. package/specialists/org/specialists/cx-engineer.json +71 -7
  443. package/specialists/org/specialists/cx-operations.json +89 -15
  444. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  445. package/specialists/org/specialists/cx-product-manager.json +28 -9
  446. package/specialists/org/specialists/cx-qa.json +16 -6
  447. package/specialists/org/specialists/cx-researcher.json +40 -7
  448. package/specialists/org/specialists/cx-reviewer.json +61 -11
  449. package/specialists/org/specialists/cx-security.json +30 -10
  450. package/specialists/org/teams/design-team.json +3 -4
  451. package/specialists/org/teams/engineering-team.json +3 -10
  452. package/specialists/org/teams/governance-team.json +3 -5
  453. package/specialists/org/teams/operations-team.json +6 -12
  454. package/specialists/org/teams/product-management-team.json +2 -3
  455. package/specialists/org/teams/quality-team.json +4 -12
  456. package/specialists/org/teams/research-team.json +3 -3
  457. package/specialists/org/teams/strategy-team.json +7 -14
  458. package/specialists/prompts/cx-architect.md +20 -1
  459. package/specialists/prompts/cx-data-analyst.md +1 -1
  460. package/specialists/prompts/cx-designer.md +12 -4
  461. package/specialists/prompts/cx-engineer.md +15 -1
  462. package/specialists/prompts/cx-operations.md +14 -2
  463. package/specialists/prompts/cx-orchestrator.md +9 -5
  464. package/specialists/prompts/cx-product-manager.md +5 -1
  465. package/specialists/prompts/cx-qa.md +6 -2
  466. package/specialists/prompts/cx-researcher.md +25 -30
  467. package/specialists/prompts/cx-reviewer.md +19 -1
  468. package/specialists/prompts/cx-security.md +5 -1
  469. package/templates/distribution/construct-brand.typ +123 -59
  470. package/templates/distribution/construct-decision.typ +6 -3
  471. package/templates/distribution/construct-pdf.typ +11 -4
  472. package/templates/distribution/construct-prd.typ +6 -3
  473. package/templates/distribution/construct-research.typ +7 -4
  474. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  475. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  476. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  477. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  478. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  479. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  480. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  481. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  482. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  483. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  484. package/specialists/org/specialists/cx-accessibility.json +0 -69
  485. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  486. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  487. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  488. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  489. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  490. package/specialists/org/specialists/cx-evaluator.json +0 -69
  491. package/specialists/org/specialists/cx-explorer.json +0 -69
  492. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  493. package/specialists/org/specialists/cx-oracle.json +0 -46
  494. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  495. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  496. package/specialists/org/specialists/cx-release-manager.json +0 -77
  497. package/specialists/org/specialists/cx-sre.json +0 -94
  498. package/specialists/org/specialists/cx-test-automation.json +0 -69
  499. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  500. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  501. package/specialists/org/teams/accessibility-team.json +0 -39
  502. package/specialists/org/teams/ux-research-team.json +0 -39
  503. package/specialists/prompts/cx-accessibility.md +0 -55
  504. package/specialists/prompts/cx-ai-engineer.md +0 -124
  505. package/specialists/prompts/cx-business-strategist.md +0 -58
  506. package/specialists/prompts/cx-data-engineer.md +0 -55
  507. package/specialists/prompts/cx-devil-advocate.md +0 -59
  508. package/specialists/prompts/cx-docs-keeper.md +0 -164
  509. package/specialists/prompts/cx-evaluator.md +0 -49
  510. package/specialists/prompts/cx-explorer.md +0 -71
  511. package/specialists/prompts/cx-legal-compliance.md +0 -58
  512. package/specialists/prompts/cx-oracle.md +0 -98
  513. package/specialists/prompts/cx-platform-engineer.md +0 -97
  514. package/specialists/prompts/cx-rd-lead.md +0 -59
  515. package/specialists/prompts/cx-release-manager.md +0 -54
  516. package/specialists/prompts/cx-sre.md +0 -111
  517. package/specialists/prompts/cx-test-automation.md +0 -55
  518. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  519. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -0,0 +1,144 @@
1
+ /**
2
+ * lib/packs/cli.mjs — `construct pack` command surface (LMCP-E3).
3
+ *
4
+ * Subcommands:
5
+ * list Every pack discovered across builtin/user/project tiers
6
+ * (loader.mjs precedence merge), each annotated with its
7
+ * durable enabled state (enablement.mjs).
8
+ * enable <id> Validate the pack's on-disk manifest and record it as
9
+ * enabled in .cx/packs.json. Refuses — naming the exact
10
+ * validation error — on an incompatible compatVersion or
11
+ * any other manifest defect.
12
+ * disable <id> Remove the pack's enabled entry. Idempotent. Refuses to
13
+ * disable the core pack.
14
+ * info <id> Full manifest plus enabled state for one pack.
15
+ *
16
+ * `--json` on every subcommand emits a machine-readable payload; the text
17
+ * form is the human-readable default.
18
+ */
19
+
20
+ import { loadAllPacks } from './loader.mjs';
21
+ import { readEnablementState, isEnabled, enablePack, disablePack } from './enablement.mjs';
22
+
23
+ function annotatedPacks({ rootDir, homeDir, packageRoot, env, deploymentMode }) {
24
+ const { packs, errors } = loadAllPacks({ rootDir, homeDir, packageRoot, env, deploymentMode });
25
+ const state = readEnablementState(rootDir);
26
+ const annotated = packs.map((p) => ({
27
+ id: p.id,
28
+ version: p.version,
29
+ tier: p._tier,
30
+ enabled: isEnabled(p.id, state, { packageRoot }),
31
+ manifestPath: p._manifestPath || p._packDir || null,
32
+ }));
33
+ return { packs: annotated, errors, state };
34
+ }
35
+
36
+ function runList({ rootDir, packageRoot, json }) {
37
+ const { packs, errors } = annotatedPacks({ rootDir, packageRoot });
38
+ if (json) {
39
+ process.stdout.write(JSON.stringify({ packs, errors }, null, 2) + '\n');
40
+ return 0;
41
+ }
42
+ process.stdout.write(`Packs (${packs.length}):\n`);
43
+ for (const p of packs) {
44
+ process.stdout.write(` ${p.enabled ? '●' : '○'} ${p.id}@${p.version} [${p.tier}]${p.enabled ? ' (enabled)' : ''}\n`);
45
+ }
46
+ for (const e of errors) process.stdout.write(` ! ${e}\n`);
47
+ return 0;
48
+ }
49
+
50
+ /**
51
+ * Splits a `<pack-id>[@version]` spec on the last '@', so a scoped id like
52
+ * '@fixture/sample' (no version) stays intact while '@fixture/sample@2.0.0'
53
+ * splits into id '@fixture/sample' and version '2.0.0'.
54
+ */
55
+ function parsePackSpec(spec) {
56
+ const at = spec.lastIndexOf('@');
57
+ if (at <= 0) return { packId: spec, requestedVersion: undefined };
58
+ return { packId: spec.slice(0, at), requestedVersion: spec.slice(at + 1) };
59
+ }
60
+
61
+ function runEnable(args, { rootDir, packageRoot, json }) {
62
+ const id = args.find((a) => !a.startsWith('--'));
63
+ if (!id) {
64
+ process.stderr.write('Usage: construct pack enable <pack-id>[@version] [--json]\n');
65
+ return 1;
66
+ }
67
+ const { packId, requestedVersion } = parsePackSpec(id);
68
+
69
+ const result = enablePack(packId, { rootDir, packageRoot, requestedVersion });
70
+ if (json) {
71
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
72
+ return result.ok ? 0 : 1;
73
+ }
74
+ if (!result.ok) {
75
+ process.stderr.write(`✖ ${result.error}\n`);
76
+ return 1;
77
+ }
78
+ process.stdout.write(result.alreadyCore ? `✓ ${packId} is the core pack (always enabled)\n` : `✓ enabled ${packId}@${result.pack.version} [${result.tier}]\n`);
79
+ return 0;
80
+ }
81
+
82
+ function runDisable(args, { rootDir, packageRoot, json }) {
83
+ const id = args.find((a) => !a.startsWith('--'));
84
+ if (!id) {
85
+ process.stderr.write('Usage: construct pack disable <pack-id> [--json]\n');
86
+ return 1;
87
+ }
88
+ const result = disablePack(id, { rootDir, packageRoot });
89
+ if (json) {
90
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
91
+ return result.ok ? 0 : 1;
92
+ }
93
+ if (!result.ok) {
94
+ process.stderr.write(`✖ ${result.error}\n`);
95
+ return 1;
96
+ }
97
+ process.stdout.write(result.wasEnabled ? `✓ disabled ${id}\n` : `${id} was not enabled (no-op)\n`);
98
+ return 0;
99
+ }
100
+
101
+ function runInfo(args, { rootDir, packageRoot, json }) {
102
+ const id = args.find((a) => !a.startsWith('--'));
103
+ if (!id) {
104
+ process.stderr.write('Usage: construct pack info <pack-id> [--json]\n');
105
+ return 1;
106
+ }
107
+ const { packs: rawPacks } = loadAllPacks({ rootDir, packageRoot });
108
+ const pack = rawPacks.find((p) => p.id === id);
109
+ if (!pack) {
110
+ process.stderr.write(`pack not found: ${id}\n`);
111
+ return 1;
112
+ }
113
+ const state = readEnablementState(rootDir);
114
+ const enabled = isEnabled(id, state, { packageRoot });
115
+ const result = { ...pack, enabled, enabledDetail: state.enabled[id] || null };
116
+ if (json) {
117
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
118
+ return 0;
119
+ }
120
+ process.stdout.write(`${pack.id}@${pack.version} [${pack._tier}] ${enabled ? '(enabled)' : '(disabled)'}\n`);
121
+ if (pack.specialists) process.stdout.write(` specialists: ${Object.keys(pack.specialists).length}\n`);
122
+ if (pack.prompts) process.stdout.write(` prompts: ${Object.keys(pack.prompts).length}\n`);
123
+ if (pack.frameworks) process.stdout.write(` frameworks: ${Object.keys(pack.frameworks).length}\n`);
124
+ return 0;
125
+ }
126
+
127
+ /**
128
+ * @param {string[]} args
129
+ * @param {{ rootDir: string, packageRoot?: string }} ctx
130
+ * @returns {number} exit code
131
+ */
132
+ export function runPackCli(args, { rootDir, packageRoot } = {}) {
133
+ const sub = args[0];
134
+ const rest = args.slice(1);
135
+ const json = args.includes('--json');
136
+
137
+ if (sub === 'list') return runList({ rootDir, packageRoot, json });
138
+ if (sub === 'enable') return runEnable(rest, { rootDir, packageRoot, json });
139
+ if (sub === 'disable') return runDisable(rest, { rootDir, packageRoot, json });
140
+ if (sub === 'info') return runInfo(rest, { rootDir, packageRoot, json });
141
+
142
+ process.stderr.write(`Unknown pack subcommand: ${sub}. Available: list, enable, disable, info\n`);
143
+ return 1;
144
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * lib/packs/core-pack.mjs — programmatic builtin core pack loader.
3
+ *
4
+ * Reads specialists/org/ directory structure and wraps it as the
5
+ * @construct/core pack. This is the builtin tier pack that provides
6
+ * all default specialists, teams, prompts, and reasoning frameworks
7
+ * (ADR-0062) shipped with Construct.
8
+ *
9
+ * `embedBindings` (LMCP-E4) ships default per-specialist provider read/
10
+ * search grants and write-proposal grants for the built-in specialists most
11
+ * likely to run embedded: cx-product-manager, cx-operations, cx-engineer.
12
+ * These are defaults only — a project may override them via a project-tier
13
+ * pack (.cx/packs) under the same specialist id.
14
+ */
15
+
16
+ import { readFileSync, readdirSync, existsSync } from 'node:fs';
17
+ import { join, dirname, resolve } from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+ import yaml from 'js-yaml';
20
+
21
+ const __dirname = dirname(fileURLToPath(import.meta.url));
22
+ const PACKAGE_ROOT = resolve(__dirname, '..', '..');
23
+
24
+ const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/;
25
+
26
+ // Default per-specialist embed bindings (LMCP-E4). Read/search only — no
27
+ // specialist gets an autonomous write; `proposals` names the write kinds a
28
+ // specialist may propose, and every proposal still passes through
29
+ // AuthorityGuard's approval-queued path before anything executes.
30
+ const DEFAULT_EMBED_BINDINGS = Object.freeze({
31
+ 'cx-product-manager': {
32
+ providers: [
33
+ { id: 'atlassian-jira', capabilities: ['read', 'search'] },
34
+ { id: 'atlassian-confluence', capabilities: ['read', 'search'] },
35
+ ],
36
+ proposals: ['atlassian-jira.createIssue'],
37
+ },
38
+ 'cx-operations': {
39
+ providers: [
40
+ { id: 'atlassian-jira', capabilities: ['read', 'search'] },
41
+ { id: 'atlassian-confluence', capabilities: ['read', 'search'] },
42
+ { id: 'slack', capabilities: ['read', 'search'] },
43
+ ],
44
+ proposals: ['atlassian-jira.createIssue', 'slack.postMessage'],
45
+ },
46
+ 'cx-engineer': {
47
+ providers: [
48
+ { id: 'github', capabilities: ['read', 'search'] },
49
+ { id: 'atlassian-jira', capabilities: ['read', 'search'] },
50
+ ],
51
+ proposals: ['github.createIssue'],
52
+ },
53
+ });
54
+
55
+ export function loadCorePack(rootDir = PACKAGE_ROOT) {
56
+ const orgDir = join(rootDir, 'specialists', 'org');
57
+ const specialistDir = join(orgDir, 'specialists');
58
+ const teamDir = join(orgDir, 'teams');
59
+ const frameworkDir = join(orgDir, 'frameworks');
60
+
61
+ const specialists = existsSync(specialistDir)
62
+ ? readdirSync(specialistDir).filter(f => f.endsWith('.json')).map(f => f.replace(/\.json$/, ''))
63
+ : [];
64
+
65
+ const teams = existsSync(teamDir)
66
+ ? readdirSync(teamDir).filter(f => f.endsWith('.json')).map(f => f.replace(/\.json$/, ''))
67
+ : [];
68
+
69
+ const prompts = {};
70
+ for (const specId of specialists) {
71
+ try {
72
+ const spec = JSON.parse(readFileSync(join(specialistDir, `${specId}.json`), 'utf8'));
73
+ if (spec.promptFile) prompts[specId] = spec.promptFile;
74
+ } catch {}
75
+ }
76
+
77
+ // frameworks map keys by frontmatter `id` (not filename) so a project-tier
78
+ // override in .cx/packs can declare the same id from a differently named
79
+ // file and still win under mergePackTiers/resolveFramework precedence.
80
+ const frameworks = {};
81
+ if (existsSync(frameworkDir)) {
82
+ for (const file of readdirSync(frameworkDir).filter(f => f.endsWith('.md'))) {
83
+ try {
84
+ const raw = readFileSync(join(frameworkDir, file), 'utf8');
85
+ const match = raw.match(FRONTMATTER_RE);
86
+ const fm = match ? yaml.load(match[1]) : null;
87
+ if (fm?.id) frameworks[fm.id] = join('specialists', 'org', 'frameworks', file);
88
+ } catch {}
89
+ }
90
+ }
91
+
92
+ // Only bind specialists actually present on disk — a stripped-down or
93
+ // customized org directory should not surface bindings for personas it
94
+ // doesn't ship.
95
+ const embedBindings = {};
96
+ for (const [specId, binding] of Object.entries(DEFAULT_EMBED_BINDINGS)) {
97
+ if (specialists.includes(specId)) embedBindings[specId] = binding;
98
+ }
99
+
100
+ return {
101
+ id: '@construct/core',
102
+ version: '0.0.0',
103
+ compatVersion: 1,
104
+ teams,
105
+ specialists,
106
+ prompts,
107
+ embedBindings,
108
+ frameworks,
109
+ _tier: 'builtin',
110
+ _sourceDir: orgDir,
111
+ };
112
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * lib/packs/enablement.mjs — durable pack enable/disable lifecycle (LMCP-E3).
3
+ *
4
+ * `loadAllPacks` (loader.mjs) merges every pack it finds on disk across the
5
+ * builtin/user/project tiers unconditionally — correct for authoring-time
6
+ * precedence resolution, but it means dropping a pack manifest into
7
+ * `.cx/packs/` makes it "configured" and already influencing prompt/framework
8
+ * resolution with no explicit operator action (the OTel lesson this bead is
9
+ * named for: configured is not enabled). This module adds an explicit,
10
+ * durable enablement layer on top without changing loadAllPacks' own
11
+ * semantics — existing callers merging all discovered packs (precedence
12
+ * tests, `construct doctor`) are unaffected; a caller that wants
13
+ * enablement-gated packs calls `loadEnabledPacks` instead.
14
+ *
15
+ * State lives at `<rootDir>/.cx/packs.json`:
16
+ * { "version": 1, "enabled": { "<packId>": { "version": "1.0.0", "enabledAt": "<ISO>", "tier": "project" } } }
17
+ *
18
+ * `@construct/core` (the programmatically-injected builtin foundation pack,
19
+ * see core-pack.mjs) is always enabled — it is not optional and carries no
20
+ * manifest file to enable/disable.
21
+ *
22
+ * `enablePack` refuses to enable a pack whose manifest fails validation —
23
+ * including a compatVersion that exceeds this runtime's PACK_COMPAT_VERSION —
24
+ * naming the exact validation error rather than half-enabling it.
25
+ */
26
+
27
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
28
+ import { dirname, join } from 'node:path';
29
+
30
+ import { loadAllPacks, resolvePackDirs } from './loader.mjs';
31
+ import { validatePackManifest } from './validate.mjs';
32
+ import { loadCorePack } from './core-pack.mjs';
33
+
34
+ const STATE_VERSION = 1;
35
+
36
+ export function resolveEnablementStatePath(rootDir = process.cwd()) {
37
+ return join(rootDir, '.cx', 'packs.json');
38
+ }
39
+
40
+ /**
41
+ * @param {string} [rootDir]
42
+ * @returns {{ version: number, enabled: Record<string, {version: string, enabledAt: string, tier: string}> }}
43
+ */
44
+ export function readEnablementState(rootDir = process.cwd()) {
45
+ const statePath = resolveEnablementStatePath(rootDir);
46
+ if (!existsSync(statePath)) {
47
+ return { version: STATE_VERSION, enabled: {} };
48
+ }
49
+ try {
50
+ const parsed = JSON.parse(readFileSync(statePath, 'utf8'));
51
+ if (!parsed || typeof parsed !== 'object' || typeof parsed.enabled !== 'object' || parsed.enabled === null) {
52
+ return { version: STATE_VERSION, enabled: {} };
53
+ }
54
+ return { version: parsed.version || STATE_VERSION, enabled: parsed.enabled };
55
+ } catch {
56
+ return { version: STATE_VERSION, enabled: {} };
57
+ }
58
+ }
59
+
60
+ export function writeEnablementState(rootDir, state) {
61
+ const statePath = resolveEnablementStatePath(rootDir);
62
+ mkdirSync(dirname(statePath), { recursive: true });
63
+ writeFileSync(statePath, JSON.stringify({ version: state.version || STATE_VERSION, enabled: state.enabled || {} }, null, 2) + '\n', 'utf8');
64
+ }
65
+
66
+ /**
67
+ * The core pack ships with Construct and is always enabled — it has no
68
+ * manifest file on disk to toggle and every specialist/framework/prompt it
69
+ * carries is the foundation every deployment mode depends on.
70
+ */
71
+ export function isCorePackId(packId, packageRoot) {
72
+ return packId === loadCorePack(packageRoot).id;
73
+ }
74
+
75
+ export function isEnabled(packId, state, { packageRoot } = {}) {
76
+ if (isCorePackId(packId, packageRoot)) return true;
77
+ return Boolean(state?.enabled?.[packId]);
78
+ }
79
+
80
+ /**
81
+ * Scans the three tier directories (project, user, builtin — highest
82
+ * precedence first) for a `pack.manifest.json` whose parsed `id` matches.
83
+ * Returns the parsed manifest alongside its tier/path even when validation
84
+ * would reject it, so `enablePack` can name the exact validation failure
85
+ * instead of reporting a generic "not found".
86
+ *
87
+ * @returns {{ tier: string, packDir: string, manifestPath: string, parsed: object } | null}
88
+ */
89
+ function findPackManifest(packId, { rootDir, homeDir } = {}) {
90
+ const dirs = resolvePackDirs({ rootDir, homeDir });
91
+ const tiers = [
92
+ { tier: 'project', dir: dirs.project },
93
+ { tier: 'user', dir: dirs.user },
94
+ { tier: 'builtin', dir: dirs.builtin },
95
+ ];
96
+
97
+ for (const { tier, dir } of tiers) {
98
+ if (!existsSync(dir)) continue;
99
+ const entries = readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory());
100
+ for (const entry of entries) {
101
+ const packDir = join(dir, entry.name);
102
+ const manifestPath = join(packDir, 'pack.manifest.json');
103
+ if (!existsSync(manifestPath)) continue;
104
+ let parsed;
105
+ try {
106
+ parsed = JSON.parse(readFileSync(manifestPath, 'utf8'));
107
+ } catch {
108
+ continue;
109
+ }
110
+ if (parsed?.id === packId) {
111
+ return { tier, packDir, manifestPath, parsed };
112
+ }
113
+ }
114
+ }
115
+ return null;
116
+ }
117
+
118
+ /**
119
+ * @param {string} packId
120
+ * @param {{ rootDir?: string, homeDir?: string, packageRoot?: string, requestedVersion?: string, knownProviders?: object }} [opts]
121
+ * @returns {{ ok: true, pack: object, tier: string, alreadyCore?: true } | { ok: false, error: string }}
122
+ */
123
+ export function enablePack(packId, opts = {}) {
124
+ const { rootDir = process.cwd(), homeDir, packageRoot, requestedVersion, knownProviders } = opts;
125
+
126
+ if (isCorePackId(packId, packageRoot)) {
127
+ return { ok: true, alreadyCore: true, pack: { id: packId }, tier: 'builtin' };
128
+ }
129
+
130
+ const found = findPackManifest(packId, { rootDir, homeDir });
131
+ if (!found) {
132
+ const dirs = resolvePackDirs({ rootDir, homeDir });
133
+ return { ok: false, error: `pack '${packId}' not found in any builtin/user/project pack directory (looked under ${dirs.project}, ${dirs.user}, ${dirs.builtin})` };
134
+ }
135
+
136
+ if (requestedVersion && found.parsed.version !== requestedVersion) {
137
+ return { ok: false, error: `pack '${packId}' on disk is version ${found.parsed.version}, requested ${requestedVersion}` };
138
+ }
139
+
140
+ const result = validatePackManifest(found.parsed, { filePath: found.manifestPath, knownProviders });
141
+ if (!result.valid) {
142
+ return { ok: false, error: `pack '${packId}' failed validation: ${result.errors.join('; ')}` };
143
+ }
144
+
145
+ const state = readEnablementState(rootDir);
146
+ state.enabled[packId] = { version: found.parsed.version, enabledAt: new Date().toISOString(), tier: found.tier };
147
+ writeEnablementState(rootDir, state);
148
+
149
+ return { ok: true, pack: found.parsed, tier: found.tier };
150
+ }
151
+
152
+ /**
153
+ * Idempotent — disabling an already-disabled (or never-enabled) pack still
154
+ * returns ok:true. The core pack cannot be disabled (ADR-0055 foundation tier).
155
+ *
156
+ * @param {string} packId
157
+ * @param {{ rootDir?: string, packageRoot?: string }} [opts]
158
+ * @returns {{ ok: true, wasEnabled: boolean } | { ok: false, error: string }}
159
+ */
160
+ export function disablePack(packId, opts = {}) {
161
+ const { rootDir = process.cwd(), packageRoot } = opts;
162
+ if (isCorePackId(packId, packageRoot)) {
163
+ return { ok: false, error: `pack '${packId}' is the core pack and cannot be disabled` };
164
+ }
165
+ const state = readEnablementState(rootDir);
166
+ const wasEnabled = Boolean(state.enabled[packId]);
167
+ delete state.enabled[packId];
168
+ writeEnablementState(rootDir, state);
169
+ return { ok: true, wasEnabled };
170
+ }
171
+
172
+ /**
173
+ * Enablement-gated counterpart to `loadAllPacks` — same merge/precedence,
174
+ * filtered to only the core pack plus whatever the durable state marks
175
+ * enabled. Consumers that must not act on a merely-configured-but-unenabled
176
+ * pack (prompt/framework resolution, embed bindings) should call this
177
+ * instead of loadAllPacks directly.
178
+ *
179
+ * @param {object} opts same shape as loadAllPacks' opts
180
+ * @returns {{packs: object[], errors: string[], state: object}}
181
+ */
182
+ export function loadEnabledPacks(opts = {}) {
183
+ const { packs, errors } = loadAllPacks(opts);
184
+ const state = readEnablementState(opts.rootDir);
185
+ const filtered = packs.filter((p) => isEnabled(p.id, state, { packageRoot: opts.packageRoot }));
186
+ return { packs: filtered, errors, state };
187
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * lib/packs/index.mjs — public API for the pack manifest system.
3
+ *
4
+ * Re-exports all symbols from the five pack sub-modules so consumers
5
+ * can import from a single entry point:
6
+ *
7
+ * import { validatePackManifest, loadAllPacks, PACK_REQUIRED_FIELDS } from './packs/index.mjs';
8
+ */
9
+
10
+ export {
11
+ PACK_REQUIRED_FIELDS, PACK_OPTIONAL_FIELDS, PACK_COMPAT_VERSION,
12
+ PACK_ID_RE, PACK_SOURCE_TIERS,
13
+ } from './manifest-schema.mjs';
14
+
15
+ export { validatePackManifest } from './validate.mjs';
16
+
17
+ export { validatePackPrompts, resolvePersonaPrompt } from './prompts.mjs';
18
+
19
+ export { loadCorePack } from './core-pack.mjs';
20
+
21
+ export {
22
+ loadPacksFromDir, mergePackTiers, resolvePackDirs, loadAllPacks,
23
+ } from './loader.mjs';
@@ -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
+ }