@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
@@ -8,8 +8,17 @@
8
8
  * screenshot evidence recorded. Every check degrades with a typed reason when its tool is absent
9
9
  * rather than failing the publish; only a real, document-scoped validity failure (a zero-page PDF,
10
10
  * dropped content, a missing local reference) marks the result not-ok.
11
+ *
12
+ * Markup visual gate (HTML only): invisible text (opacity:0, matched fg/bg color) and hard-overlapping
13
+ * absolutely-positioned elements are detected from the produced file's own markup, closing the blind
14
+ * spot where the text roundtrip passes because hidden glyphs are still extractable.
15
+ *
16
+ * Render-smoke hard-fail: for pdf and pptx deliverables at render-smoke+, a missing required renderer
17
+ * is a hard failure rather than a graceful degrade — a user inspecting output before claiming done
18
+ * cannot accept a quality claim that rests on a tool that was never invoked.
11
19
  */
12
20
 
21
+ import fs from 'node:fs';
13
22
  import { validatePdf, contentRoundtrip, referenceIntegrity } from './export-validate.mjs';
14
23
  import { validateBrandContrast } from './brand-contrast.mjs';
15
24
  import { captureRenderEvidence } from './render-evidence.mjs';
@@ -18,6 +27,49 @@ import { auditAccessibility } from './a11y-audit.mjs';
18
27
 
19
28
  const RENDER_GATE_LEVELS = new Set(['render-smoke', 'full-certification', 'human-reviewed']);
20
29
 
30
+ // PDF and PPTX require external renderers; their absence at render-smoke+ is a hard failure.
31
+ const RENDER_REQUIRED_FORMATS = new Set(['pdf', 'pptx']);
32
+
33
+ // Detect invisible and hard-overlapping text in a produced HTML deliverable. The text roundtrip
34
+ // passes even when body text is invisible because hidden glyphs are still extractable; this gate
35
+ // catches opacity:0, matched foreground/background color, and hard-stacked absolute elements.
36
+
37
+ function markupVisualGate(exportPath, format) {
38
+ if (format !== 'html') return { ok: true, issues: [], degradation: null };
39
+ let html;
40
+ try { html = fs.readFileSync(exportPath, 'utf8'); } catch { return { ok: true, issues: [], degradation: null }; }
41
+
42
+ const issues = [];
43
+
44
+ if (/\bopacity\s*:\s*0(?!\.\d)/i.test(html)) {
45
+ issues.push('invisible-text: opacity:0 on element');
46
+ }
47
+
48
+ for (const m of html.matchAll(/style\s*=\s*["']([^"']*)["']/gi)) {
49
+ const style = m[1];
50
+ const fgM = style.match(/\bcolor\s*:\s*(#[0-9a-f]{3,6}|[a-z]+)/i);
51
+ const bgM = style.match(/\bbackground(?:-color)?\s*:\s*(#[0-9a-f]{3,6}|[a-z]+)/i);
52
+ if (fgM && bgM && fgM[1].toLowerCase() === bgM[1].toLowerCase()) {
53
+ issues.push(`invisible-text: foreground and background both "${fgM[1].toLowerCase()}"`);
54
+ }
55
+ }
56
+
57
+ const positions = new Map();
58
+ for (const m of html.matchAll(/style\s*=\s*["']([^"']*)["']/gi)) {
59
+ const style = m[1];
60
+ if (!/position\s*:\s*absolute/i.test(style)) continue;
61
+ const top = (style.match(/\btop\s*:\s*([^;,"'\s]+)/) || [])[1] ?? '';
62
+ const left = (style.match(/\bleft\s*:\s*([^;,"'\s]+)/) || [])[1] ?? '';
63
+ if (top === '' && left === '') continue;
64
+ const key = `${top}|${left}`;
65
+ const cnt = (positions.get(key) || 0) + 1;
66
+ positions.set(key, cnt);
67
+ if (cnt === 2) issues.push(`overlapping-text: elements stacked at top:${top} left:${left}`);
68
+ }
69
+
70
+ return { ok: issues.length === 0, issues, degradation: null };
71
+ }
72
+
21
73
  // render-pipeline keys an export format to an image renderer; deck is HTML under the hood. A
22
74
  // format with no renderer skips the screenshot capture rather than guessing one.
23
75
 
@@ -42,7 +94,8 @@ export function runOutputQuality({
42
94
 
43
95
  if (format === 'pdf') checks.pdf = validatePdf(exportPath, env);
44
96
  checks.roundtrip = contentRoundtrip({ exportPath, format, sourceMarkdown, env });
45
- if (sourceMarkdown && baseDir) checks.references = referenceIntegrity(sourceMarkdown, baseDir);
97
+ if (sourceMarkdown && baseDir) checks.references = referenceIntegrity(sourceMarkdown, baseDir, exportPath);
98
+ if (format === 'html') checks.markup = markupVisualGate(exportPath, format);
46
99
 
47
100
  // Diagram legibility is advisory by default; `cx_diagram_quality: strict` escalates it.
48
101
  const diagrams = lintDocumentDiagrams(sourceMarkdown);
@@ -62,12 +115,18 @@ export function runOutputQuality({
62
115
  }
63
116
 
64
117
  // A typed degradation (tool absent) never fails the publish; only a non-degraded,
65
- // document-scoped check whose ok is explicitly false does.
118
+ // document-scoped check whose ok is explicitly false does. Exception: at render-smoke+ gate
119
+ // levels, a missing required renderer for pdf/pptx is a hard failure — the quality claim depends
120
+ // on a tool that was never invoked.
66
121
 
67
122
  const failures = Object.entries(checks)
68
123
  .filter(([, result]) => result && result.ok === false && !result.degradation)
69
124
  .map(([name, result]) => `${name}: ${result.message || 'failed'}`);
70
125
 
126
+ if (RENDER_GATE_LEVELS.has(gateLevel) && RENDER_REQUIRED_FORMATS.has(format) && render?.result?.degradation === 'missing-dependency') {
127
+ failures.push(`render: required ${format} renderer absent at gate level '${gateLevel}'`);
128
+ }
129
+
71
130
  if (diagramStrict && !diagrams.ok) {
72
131
  for (const w of diagrams.warnings) failures.push(`diagram ${w.diagram} (${w.kind}): ${w.code}`);
73
132
  }
@@ -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';