@geraldmaron/construct 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (471) hide show
  1. package/.env.example +10 -7
  2. package/LICENSE +201 -21
  3. package/README.md +132 -257
  4. package/agents/contracts.json +387 -0
  5. package/agents/prompts/cx-accessibility.md +8 -0
  6. package/agents/prompts/cx-ai-engineer.md +92 -0
  7. package/agents/prompts/cx-architect.md +10 -2
  8. package/agents/prompts/cx-business-strategist.md +13 -0
  9. package/agents/prompts/cx-data-analyst.md +80 -0
  10. package/agents/prompts/cx-data-engineer.md +4 -0
  11. package/agents/prompts/cx-debugger.md +8 -0
  12. package/agents/prompts/cx-designer.md +19 -0
  13. package/agents/prompts/cx-devil-advocate.md +4 -0
  14. package/agents/prompts/cx-docs-keeper.md +128 -0
  15. package/agents/prompts/cx-engineer.md +13 -0
  16. package/agents/prompts/cx-evaluator.md +4 -0
  17. package/agents/prompts/cx-explorer.md +12 -0
  18. package/agents/prompts/cx-legal-compliance.md +13 -0
  19. package/agents/prompts/cx-operations.md +13 -0
  20. package/agents/prompts/cx-orchestrator.md +107 -4
  21. package/agents/prompts/cx-platform-engineer.md +75 -0
  22. package/agents/prompts/cx-product-manager.md +8 -0
  23. package/agents/prompts/cx-qa.md +100 -0
  24. package/agents/prompts/cx-rd-lead.md +13 -0
  25. package/agents/prompts/cx-release-manager.md +8 -0
  26. package/agents/prompts/cx-researcher.md +21 -1
  27. package/agents/prompts/cx-reviewer.md +8 -0
  28. package/agents/prompts/cx-security.md +104 -0
  29. package/agents/prompts/cx-sre.md +104 -0
  30. package/agents/prompts/cx-test-automation.md +4 -0
  31. package/agents/prompts/cx-trace-reviewer.md +7 -3
  32. package/agents/prompts/cx-ux-researcher.md +4 -0
  33. package/agents/registry.json +365 -90
  34. package/agents/role-manifests.json +217 -0
  35. package/bin/construct +3345 -141
  36. package/bin/construct-postinstall.mjs +87 -0
  37. package/commands/build/feature.md +6 -6
  38. package/commands/plan/feature.md +6 -5
  39. package/commands/plan/requirements.md +1 -1
  40. package/commands/ship/status.md +1 -1
  41. package/commands/work/drive.md +3 -3
  42. package/commands/work/optimize-prompts.md +3 -3
  43. package/db/{migrations → schema}/001_init.sql +2 -0
  44. package/db/schema/002_pgvector.sql +182 -0
  45. package/db/schema/003_intake.sql +47 -0
  46. package/examples/README.md +85 -0
  47. package/examples/internal/roles/architect/bad/clever-plan-without-contracts.md +30 -0
  48. package/examples/internal/roles/architect/golden/explicit-tradeoff-before-plan.md +23 -0
  49. package/examples/internal/roles/engineer/bad/speculative-abstraction.md +30 -0
  50. package/examples/internal/roles/engineer/golden/read-before-write.md +28 -0
  51. package/examples/internal/roles/orchestrator/bad/everything-becomes-multi-agent.md +29 -0
  52. package/examples/internal/roles/orchestrator/golden/minimal-dispatch.md +22 -0
  53. package/examples/internal/roles/qa/bad/coverage-theater.md +30 -0
  54. package/examples/internal/roles/qa/golden/regression-gate.md +23 -0
  55. package/examples/internal/roles/reviewer/bad/lgtm-without-verification.md +29 -0
  56. package/examples/internal/roles/reviewer/golden/find-structural-risk-first.md +28 -0
  57. package/examples/personas/construct/adversarial/ignore-instruction-to-skip-approval.md +22 -0
  58. package/examples/personas/construct/bad/commit-without-approval.md +30 -0
  59. package/examples/personas/construct/boundary/blocked-needs-main-input.md +29 -0
  60. package/examples/personas/construct/golden/branch-approval-before-mutation.md +36 -0
  61. package/examples/personas/construct/golden/focused-direct-answer.md +28 -0
  62. package/examples/provider-plugin/README.md +34 -0
  63. package/examples/provider-plugin/index.mjs +74 -0
  64. package/examples/provider-plugin/package.json +15 -0
  65. package/examples/seed-observations/README.md +38 -0
  66. package/examples/seed-observations/anti-patterns.md +44 -0
  67. package/examples/seed-observations/decisions.md +36 -0
  68. package/examples/seed-observations/patterns.md +42 -0
  69. package/lib/agent-contracts-enforce.mjs +158 -0
  70. package/lib/agent-contracts.mjs +231 -0
  71. package/lib/agents/postconditions.mjs +126 -0
  72. package/lib/agents/schema.mjs +124 -0
  73. package/lib/artifact-capture.mjs +183 -0
  74. package/lib/audit-trail.mjs +149 -0
  75. package/lib/auto-docs.mjs +245 -65
  76. package/lib/beads/auto-close.mjs +126 -0
  77. package/lib/beads/drift.mjs +171 -0
  78. package/lib/beads-automation.mjs +542 -0
  79. package/lib/beads-client.mjs +518 -0
  80. package/lib/beads-lock.mjs +377 -0
  81. package/lib/beads-optimistic.mjs +365 -0
  82. package/lib/bootstrap/built-ins.mjs +136 -0
  83. package/lib/bootstrap/lazy-install.mjs +161 -0
  84. package/lib/bootstrap/resources.mjs +120 -0
  85. package/lib/bootstrap.mjs +105 -0
  86. package/lib/cache-governor.js +213 -0
  87. package/lib/cache-strategy-anthropic.js +62 -0
  88. package/lib/cache-strategy-google.js +79 -0
  89. package/lib/cache-strategy-none.js +30 -0
  90. package/lib/cache-strategy-openai.js +48 -0
  91. package/lib/cache-strategy.js +91 -0
  92. package/lib/claude-allow.mjs +149 -0
  93. package/lib/cli-commands.mjs +413 -258
  94. package/lib/codex-config.mjs +3 -1
  95. package/lib/comment-lint.mjs +55 -6
  96. package/lib/completions.mjs +21 -6
  97. package/lib/config/alias.mjs +56 -0
  98. package/lib/config/project-config.mjs +335 -0
  99. package/lib/config/schema.mjs +159 -0
  100. package/lib/context-router.mjs +308 -0
  101. package/lib/cost-ledger.mjs +177 -0
  102. package/lib/cost.mjs +171 -10
  103. package/lib/dashboard-static.mjs +158 -0
  104. package/lib/deployment-mode.mjs +86 -0
  105. package/lib/deprecate.mjs +49 -0
  106. package/lib/dispatch-batch.js +183 -0
  107. package/lib/distill.mjs +21 -8
  108. package/lib/doc-stamp.mjs +164 -0
  109. package/lib/doc-verify.mjs +119 -0
  110. package/lib/docs-routing.mjs +89 -0
  111. package/lib/docs-verify.mjs +417 -0
  112. package/lib/doctor/audit.mjs +71 -0
  113. package/lib/doctor/cli.mjs +99 -0
  114. package/lib/doctor/escalate.mjs +29 -0
  115. package/lib/doctor/index.mjs +140 -0
  116. package/lib/doctor/report.mjs +170 -0
  117. package/lib/doctor/watchers/bd-watch.mjs +117 -0
  118. package/lib/doctor/watchers/cost.mjs +130 -0
  119. package/lib/doctor/watchers/disk.mjs +122 -0
  120. package/lib/doctor/watchers/handoffs.mjs +33 -0
  121. package/lib/doctor/watchers/process-pressure.mjs +60 -0
  122. package/lib/doctor/watchers/service-health.mjs +188 -0
  123. package/lib/document-extract.mjs +288 -0
  124. package/lib/document-ingest.mjs +230 -0
  125. package/lib/drop.mjs +282 -0
  126. package/lib/embed/approval-queue.mjs +176 -0
  127. package/lib/embed/artifact.mjs +349 -0
  128. package/lib/embed/authority-guard.mjs +155 -0
  129. package/lib/embed/cli.mjs +408 -0
  130. package/lib/embed/config.mjs +355 -0
  131. package/lib/embed/conflict-detection.mjs +264 -0
  132. package/lib/embed/customer-profiles.mjs +480 -0
  133. package/lib/embed/daemon.mjs +1309 -0
  134. package/lib/embed/demand-fetch.mjs +449 -0
  135. package/lib/embed/docs-lifecycle.mjs +349 -0
  136. package/lib/embed/inbox-live-watcher.mjs +119 -0
  137. package/lib/embed/inbox.mjs +343 -0
  138. package/lib/embed/intake-metrics.mjs +190 -0
  139. package/lib/embed/jobs/vector-sync.mjs +198 -0
  140. package/lib/embed/notifications.mjs +75 -0
  141. package/lib/embed/output.mjs +79 -0
  142. package/lib/embed/providers/github.mjs +295 -0
  143. package/lib/embed/providers/jira.mjs +192 -0
  144. package/lib/embed/providers/linear.mjs +186 -0
  145. package/lib/embed/providers/registry.mjs +115 -0
  146. package/lib/embed/providers/slack.mjs +203 -0
  147. package/lib/embed/recommendation-store.mjs +378 -0
  148. package/lib/embed/roadmap.mjs +374 -0
  149. package/lib/embed/role-framing.mjs +110 -0
  150. package/lib/embed/scheduler.mjs +99 -0
  151. package/lib/embed/semantic.mjs +325 -0
  152. package/lib/embed/snapshot.mjs +191 -0
  153. package/lib/embed/supervision.mjs +235 -0
  154. package/lib/embed/target-resolver.mjs +186 -0
  155. package/lib/embed/worker.mjs +62 -0
  156. package/lib/embed/workspaces.mjs +297 -0
  157. package/lib/engine/chunker-headings.mjs +110 -0
  158. package/lib/engine/compressor-heuristic.mjs +100 -0
  159. package/lib/engine/consolidate.mjs +287 -0
  160. package/lib/engine/contracts.mjs +126 -0
  161. package/lib/engine/defaults.mjs +129 -0
  162. package/lib/engine/eval-retrieval.mjs +148 -0
  163. package/lib/engine/fuser-rrf.mjs +62 -0
  164. package/lib/engine/index.mjs +37 -0
  165. package/lib/engine/registry.mjs +146 -0
  166. package/lib/engine/reranker-mmr.mjs +90 -0
  167. package/lib/engine/tokens.mjs +77 -0
  168. package/lib/entity-store.mjs +280 -0
  169. package/lib/env-config.mjs +69 -4
  170. package/lib/evals/retrieval-bench.mjs +159 -0
  171. package/lib/evaluator-optimizer.mjs +317 -0
  172. package/lib/features.mjs +159 -29
  173. package/lib/gates-audit.mjs +236 -0
  174. package/lib/git-hooks/prepare-commit-msg +58 -0
  175. package/lib/handoffs/cleanup.mjs +159 -0
  176. package/lib/handoffs/contract.mjs +162 -0
  177. package/lib/handoffs/inventory.mjs +120 -0
  178. package/lib/headhunt.mjs +28 -47
  179. package/lib/health-check.mjs +399 -0
  180. package/lib/hook-health.mjs +442 -0
  181. package/lib/hooks/_lib/log.mjs +82 -0
  182. package/lib/hooks/adaptive-lint.mjs +26 -2
  183. package/lib/hooks/agent-tracker.mjs +171 -14
  184. package/lib/hooks/audit-reads.mjs +109 -0
  185. package/lib/hooks/audit-trail.mjs +153 -0
  186. package/lib/hooks/bash-output-logger.mjs +71 -0
  187. package/lib/hooks/block-no-verify.mjs +41 -0
  188. package/lib/hooks/ci-status-check.mjs +82 -0
  189. package/lib/hooks/comment-lint.mjs +29 -7
  190. package/lib/hooks/config-protection.mjs +39 -18
  191. package/lib/hooks/context-watch.mjs +137 -0
  192. package/lib/hooks/context-window-recovery.mjs +4 -20
  193. package/lib/hooks/dep-audit.mjs +16 -0
  194. package/lib/hooks/doc-coupling-check.mjs +80 -0
  195. package/lib/hooks/edit-accumulator.mjs +3 -0
  196. package/lib/hooks/edit-error-recovery.mjs +3 -0
  197. package/lib/hooks/edit-guard.mjs +45 -4
  198. package/lib/hooks/env-check.mjs +3 -0
  199. package/lib/hooks/guard-bash.mjs +58 -1
  200. package/lib/hooks/mcp-audit.mjs +18 -20
  201. package/lib/hooks/mcp-health-check.mjs +36 -0
  202. package/lib/hooks/model-fallback.mjs +42 -56
  203. package/lib/hooks/policy-engine.mjs +209 -0
  204. package/lib/hooks/post-merge-docs-check.mjs +63 -0
  205. package/lib/hooks/pre-compact.mjs +4 -24
  206. package/lib/hooks/pre-push-gate.mjs +200 -37
  207. package/lib/hooks/proactive-activation.mjs +284 -0
  208. package/lib/hooks/read-tracker.mjs +27 -4
  209. package/lib/hooks/readme-age-check.mjs +77 -0
  210. package/lib/hooks/registry-sync.mjs +12 -5
  211. package/lib/hooks/scan-secrets.mjs +50 -7
  212. package/lib/hooks/session-optimize.mjs +311 -0
  213. package/lib/hooks/session-start.mjs +287 -28
  214. package/lib/hooks/stop-notify.mjs +236 -96
  215. package/lib/hooks/stop-typecheck.mjs +13 -1
  216. package/lib/hooks/test-watch.mjs +68 -0
  217. package/lib/host-capabilities.mjs +31 -10
  218. package/lib/init-docs.mjs +731 -291
  219. package/lib/init-unified.mjs +1116 -0
  220. package/lib/init-update.mjs +168 -0
  221. package/lib/init.mjs +107 -0
  222. package/lib/install/first-invocation.mjs +119 -0
  223. package/lib/install/stage-project.mjs +69 -0
  224. package/lib/intake/classify.mjs +278 -0
  225. package/lib/intake/feedback.mjs +273 -0
  226. package/lib/intake/filesystem-queue.mjs +158 -0
  227. package/lib/intake/intake-config.mjs +132 -0
  228. package/lib/intake/postgres-queue.mjs +198 -0
  229. package/lib/intake/prepare.mjs +139 -0
  230. package/lib/intake/queue.mjs +83 -0
  231. package/lib/intake/session-prelude.mjs +50 -0
  232. package/lib/integrations/intake-integrations.mjs +740 -0
  233. package/lib/intent-classifier.mjs +253 -0
  234. package/lib/knowledge/layout.mjs +72 -0
  235. package/lib/knowledge/rag.mjs +331 -0
  236. package/lib/knowledge/search.mjs +315 -0
  237. package/lib/knowledge/trends.mjs +261 -0
  238. package/lib/logger.mjs +85 -0
  239. package/lib/mcp/broker.mjs +124 -0
  240. package/lib/mcp/server.mjs +554 -854
  241. package/lib/mcp/tools/document.mjs +132 -0
  242. package/lib/mcp/tools/memory.mjs +209 -0
  243. package/lib/mcp/tools/project.mjs +349 -0
  244. package/lib/mcp/tools/skills.mjs +409 -0
  245. package/lib/mcp/tools/storage.mjs +60 -0
  246. package/lib/mcp/tools/telemetry.mjs +315 -0
  247. package/lib/mcp/tools/workflow.mjs +112 -0
  248. package/lib/mcp-catalog.json +54 -3
  249. package/lib/mcp-manager.mjs +96 -48
  250. package/lib/mcp-platform-config.mjs +22 -22
  251. package/lib/memory-stats.mjs +121 -0
  252. package/lib/mode-commands.mjs +124 -0
  253. package/lib/model-free-selector.mjs +184 -0
  254. package/lib/model-pricing.mjs +152 -0
  255. package/lib/model-registry.mjs +226 -0
  256. package/lib/model-router.mjs +362 -386
  257. package/lib/observation-store.mjs +391 -0
  258. package/lib/ollama-manager.mjs +428 -0
  259. package/lib/opencode-config.mjs +13 -3
  260. package/lib/opencode-runtime-plugin.mjs +70 -38
  261. package/lib/opencode-telemetry.mjs +64 -6
  262. package/lib/orchestration-policy.mjs +509 -6
  263. package/lib/overrides/resolver.mjs +207 -0
  264. package/lib/parity.mjs +147 -0
  265. package/lib/paths.mjs +33 -0
  266. package/lib/performance/generate.mjs +212 -0
  267. package/lib/plugin-registry.mjs +268 -0
  268. package/lib/policy/engine.mjs +130 -0
  269. package/lib/policy/unified-gates.mjs +96 -0
  270. package/lib/project-detection.mjs +129 -0
  271. package/lib/project-init-shared.mjs +272 -0
  272. package/lib/project-profile.mjs +447 -0
  273. package/lib/prompt-composer.js +434 -0
  274. package/lib/prompt-metadata.mjs +1 -1
  275. package/lib/provider-capabilities-anthropic.js +44 -0
  276. package/lib/provider-capabilities-deepseek.js +37 -0
  277. package/lib/provider-capabilities-generic.js +26 -0
  278. package/lib/provider-capabilities-google.js +47 -0
  279. package/lib/provider-capabilities-openai.js +45 -0
  280. package/lib/provider-capabilities.js +142 -0
  281. package/lib/providers/atlassian-confluence/index.mjs +103 -0
  282. package/lib/providers/atlassian-jira/index.mjs +100 -0
  283. package/lib/providers/auth-manager.mjs +126 -0
  284. package/lib/providers/circuit-breaker.mjs +124 -0
  285. package/lib/providers/contract.mjs +93 -0
  286. package/lib/providers/github/index.mjs +126 -0
  287. package/lib/providers/registry.mjs +184 -0
  288. package/lib/providers/salesforce/index.mjs +100 -0
  289. package/lib/providers/slack/index.mjs +80 -0
  290. package/lib/reflect.mjs +137 -0
  291. package/lib/research-lint.mjs +164 -0
  292. package/lib/resources/budget.mjs +259 -0
  293. package/lib/role-preload.mjs +21 -6
  294. package/lib/roles/approval-surface.mjs +54 -0
  295. package/lib/roles/cli.mjs +118 -0
  296. package/lib/roles/event-bus.mjs +79 -0
  297. package/lib/roles/fence.mjs +84 -0
  298. package/lib/roles/gateway.mjs +260 -0
  299. package/lib/roles/hook-emit.mjs +37 -0
  300. package/lib/roles/manifest.mjs +48 -0
  301. package/lib/roles/router.mjs +27 -0
  302. package/lib/runtime-pressure.mjs +360 -0
  303. package/lib/schema-artifact.mjs +134 -0
  304. package/lib/schema-infer.mjs +551 -0
  305. package/lib/server/auth.mjs +168 -0
  306. package/lib/server/chat.mjs +336 -0
  307. package/lib/server/cors.mjs +77 -0
  308. package/lib/server/csrf.mjs +91 -0
  309. package/lib/server/index.mjs +1927 -78
  310. package/lib/server/insights.mjs +765 -0
  311. package/lib/server/rate-limit.mjs +91 -0
  312. package/lib/server/static/assets/index-ab25c707.js +70 -0
  313. package/lib/server/static/assets/index-f0c80a2b.css +1 -0
  314. package/lib/server/static/index.html +12 -817
  315. package/lib/server/telemetry-login.mjs +108 -0
  316. package/lib/server/webhook.mjs +510 -0
  317. package/lib/service-manager.mjs +522 -58
  318. package/lib/services/pattern-promotion-service.mjs +167 -0
  319. package/lib/services/telemetry-backend.mjs +178 -0
  320. package/lib/session-store.mjs +374 -0
  321. package/lib/setup-prompts.mjs +96 -0
  322. package/lib/setup.mjs +525 -38
  323. package/lib/skills-apply.mjs +280 -0
  324. package/lib/skills-scope.mjs +118 -0
  325. package/lib/status.mjs +261 -70
  326. package/lib/storage/admin.mjs +355 -0
  327. package/lib/storage/backend.mjs +2 -1
  328. package/lib/storage/backup.mjs +347 -0
  329. package/lib/storage/embeddings-engine.mjs +133 -0
  330. package/lib/storage/embeddings-legacy.mjs +85 -0
  331. package/lib/storage/embeddings-local.mjs +108 -0
  332. package/lib/storage/embeddings-ollama.mjs +78 -0
  333. package/lib/storage/embeddings-openai.mjs +85 -0
  334. package/lib/storage/embeddings.mjs +92 -33
  335. package/lib/storage/file-lock.mjs +130 -0
  336. package/lib/storage/fusion.mjs +95 -0
  337. package/lib/storage/hybrid-query.mjs +34 -27
  338. package/lib/storage/migrations.mjs +187 -0
  339. package/lib/storage/postgres-backup.mjs +124 -0
  340. package/lib/storage/sql-store.mjs +5 -15
  341. package/lib/storage/state-source.mjs +12 -13
  342. package/lib/storage/store-version.mjs +115 -0
  343. package/lib/storage/sync.mjs +144 -35
  344. package/lib/storage/unified-storage.mjs +550 -0
  345. package/lib/storage/vector-client.mjs +286 -0
  346. package/lib/storage/vector-store.mjs +71 -30
  347. package/lib/task-graph/generate.mjs +135 -0
  348. package/lib/task-graph/schema.mjs +81 -0
  349. package/lib/task-graph/store.mjs +71 -0
  350. package/lib/telemetry/backends/local.mjs +62 -0
  351. package/lib/telemetry/backends/{langfuse.mjs → remote.mjs} +27 -14
  352. package/lib/telemetry/backfill.mjs +180 -0
  353. package/lib/telemetry/eval-datasets.mjs +203 -0
  354. package/lib/telemetry/{langfuse-ingest.mjs → ingest.mjs} +26 -20
  355. package/lib/telemetry/intent-verifications.mjs +86 -0
  356. package/lib/telemetry/llm-judge.mjs +350 -0
  357. package/lib/telemetry/model-pricing-catalog.mjs +557 -0
  358. package/lib/telemetry/setup.mjs +151 -0
  359. package/lib/telemetry/skill-calls.mjs +78 -0
  360. package/lib/telemetry/team-rollup.mjs +4 -4
  361. package/lib/token-engine.js +117 -0
  362. package/lib/token-estimator-anthropic.js +15 -0
  363. package/lib/token-estimator-deepseek.js +13 -0
  364. package/lib/token-estimator-default.js +13 -0
  365. package/lib/token-estimator-google.js +13 -0
  366. package/lib/token-estimator-openai.js +13 -0
  367. package/lib/toolkit-env.mjs +1 -1
  368. package/lib/tty-prompts.mjs +211 -0
  369. package/lib/uninstall/uninstall.mjs +423 -0
  370. package/lib/update.mjs +115 -0
  371. package/lib/upgrade.mjs +141 -0
  372. package/lib/validator.mjs +51 -2
  373. package/lib/validators/skills.mjs +142 -0
  374. package/lib/wireframe.mjs +422 -0
  375. package/lib/worker/entrypoint.mjs +241 -0
  376. package/lib/worker/evidence.mjs +107 -0
  377. package/lib/worker/run.mjs +154 -0
  378. package/lib/worker/trace.mjs +182 -0
  379. package/lib/workflow-state.mjs +14 -18
  380. package/package.json +21 -8
  381. package/personas/construct.md +53 -51
  382. package/platforms/claude/CLAUDE.md +1 -1
  383. package/platforms/claude/settings.template.json +115 -68
  384. package/platforms/opencode/config.template.json +3 -7
  385. package/rules/common/agents.md +11 -38
  386. package/rules/common/beads-hygiene.md +75 -0
  387. package/rules/common/code-review.md +11 -100
  388. package/rules/common/coding-style.md +5 -3
  389. package/rules/common/comments.md +30 -111
  390. package/rules/common/commit-approval.md +53 -0
  391. package/rules/common/cx-agent-routing.md +1 -0
  392. package/rules/common/development-workflow.md +23 -41
  393. package/rules/common/doc-ownership.md +54 -0
  394. package/rules/common/efficiency.md +57 -0
  395. package/rules/common/framing.md +76 -0
  396. package/rules/common/git-workflow.md +2 -4
  397. package/rules/common/patterns.md +3 -7
  398. package/rules/common/performance.md +15 -31
  399. package/rules/common/release-gates.md +69 -0
  400. package/rules/common/research.md +107 -0
  401. package/rules/common/security.md +6 -6
  402. package/rules/common/skill-composition.md +67 -0
  403. package/rules/common/testing.md +15 -27
  404. package/rules/golang/hooks.md +0 -4
  405. package/rules/policy/bootstrap.yaml +11 -0
  406. package/rules/policy/drive.yaml +8 -0
  407. package/rules/policy/task.yaml +9 -0
  408. package/rules/policy/workflow.yaml +9 -0
  409. package/rules/python/hooks.md +0 -4
  410. package/rules/swift/hooks.md +0 -4
  411. package/rules/typescript/hooks.md +0 -4
  412. package/rules/web/hooks.md +0 -2
  413. package/{sync-agents.mjs → scripts/sync-agents.mjs} +306 -61
  414. package/skills/ai/prompt-optimizer.md +7 -7
  415. package/skills/compliance/ai-disclosure.md +58 -0
  416. package/skills/compliance/data-privacy.md +46 -0
  417. package/skills/compliance/license-audit.md +40 -0
  418. package/skills/compliance/regulatory-review.md +61 -0
  419. package/skills/docs/document-ingest-workflow.md +52 -0
  420. package/skills/docs/evidence-ingest-workflow.md +9 -0
  421. package/skills/docs/init-docs.md +51 -18
  422. package/skills/docs/product-intelligence-workflow.md +12 -0
  423. package/skills/docs/product-signal-workflow.md +4 -0
  424. package/skills/docs/research-workflow.md +23 -8
  425. package/skills/docs/runbook-workflow.md +1 -1
  426. package/skills/operating/orchestration-reference.md +151 -0
  427. package/skills/roles/architect.md +5 -0
  428. package/skills/roles/engineer.md +32 -0
  429. package/skills/roles/operator.md +12 -0
  430. package/skills/roles/reviewer.md +23 -0
  431. package/skills/routing.md +1 -1
  432. package/templates/devcontainer/Dockerfile.devcontainer +38 -0
  433. package/templates/devcontainer/devcontainer.json +31 -0
  434. package/templates/distribution/bootstrap.ps1 +142 -0
  435. package/templates/distribution/bootstrap.sh +196 -0
  436. package/templates/distribution/run.mjs +187 -0
  437. package/templates/docs/adr.md +44 -8
  438. package/templates/docs/changelog-entry.md +43 -0
  439. package/templates/docs/construct_guide.md +149 -0
  440. package/templates/docs/evidence-brief.md +2 -2
  441. package/templates/docs/meta-prd.md +140 -19
  442. package/templates/docs/onboarding.md +57 -0
  443. package/templates/docs/prd.md +159 -15
  444. package/templates/docs/research-brief.md +4 -4
  445. package/templates/homebrew/construct.rb +67 -0
  446. package/langfuse/docker-compose.yml +0 -82
  447. package/lib/eval-harness.mjs +0 -59
  448. package/lib/hooks/bootstrap-guard.mjs +0 -90
  449. package/lib/hooks/console-warn.mjs +0 -43
  450. package/lib/hooks/continuation-enforcer.mjs +0 -72
  451. package/lib/hooks/drive-guard.mjs +0 -89
  452. package/lib/hooks/mcp-task-scope.mjs +0 -47
  453. package/lib/hooks/task-completed-guard.mjs +0 -43
  454. package/lib/hooks/teammate-idle-guard.mjs +0 -54
  455. package/lib/hooks/workflow-guard.mjs +0 -62
  456. package/lib/prompt-composer.mjs +0 -196
  457. package/lib/review.mjs +0 -429
  458. package/lib/server/static/app.js +0 -841
  459. package/lib/telemetry/langfuse-model-sync.mjs +0 -270
  460. package/rules/common/hooks.md +0 -35
  461. package/rules/zh/README.md +0 -113
  462. package/rules/zh/agents.md +0 -55
  463. package/rules/zh/code-review.md +0 -129
  464. package/rules/zh/coding-style.md +0 -53
  465. package/rules/zh/development-workflow.md +0 -49
  466. package/rules/zh/git-workflow.md +0 -29
  467. package/rules/zh/hooks.md +0 -35
  468. package/rules/zh/patterns.md +0 -36
  469. package/rules/zh/performance.md +0 -60
  470. package/rules/zh/security.md +0 -34
  471. package/rules/zh/testing.md +0 -34
@@ -0,0 +1,79 @@
1
+ /**
2
+ * lib/roles/event-bus.mjs — append-only event log at ~/.cx/events.jsonl.
3
+ *
4
+ * Hook processes emit failure/signal events here. The router + gateway read
5
+ * recent events to decide escalation. Lines are rotated to keep the file
6
+ * bounded. Fingerprint = sha1(type + project + first summary line) so the
7
+ * same failure dedups across emits.
8
+ */
9
+
10
+ import { createHash } from 'node:crypto';
11
+ import { existsSync, readFileSync, appendFileSync, writeFileSync, mkdirSync } from 'node:fs';
12
+ import { homedir } from 'node:os';
13
+ import { join } from 'node:path';
14
+
15
+ const MAX_LINES = 1000;
16
+
17
+ function rootDir() {
18
+ return process.env.CONSTRUCT_ROLES_ROOT || join(homedir(), '.cx');
19
+ }
20
+ function eventsPath() {
21
+ return join(rootDir(), 'events.jsonl');
22
+ }
23
+ function ensureDir() {
24
+ const r = rootDir();
25
+ if (!existsSync(r)) mkdirSync(r, { recursive: true });
26
+ }
27
+
28
+ export function fingerprintOf(type, project, summary) {
29
+ const firstLine = String(summary || '').split('\n')[0].trim();
30
+ return createHash('sha1').update(`${type}|${project}|${firstLine}`).digest('hex').slice(0, 16);
31
+ }
32
+
33
+ export function emit(eventType, payload = {}) {
34
+ ensureDir();
35
+ const project = payload.project || '';
36
+ const summary = payload.summary || '';
37
+ const fingerprint = fingerprintOf(eventType, project, summary);
38
+ const line = {
39
+ ts: Date.now(),
40
+ type: eventType,
41
+ project,
42
+ branch: payload.branch || '',
43
+ cwd: payload.cwd || '',
44
+ summary,
45
+ context: payload.context ?? null,
46
+ fingerprint,
47
+ };
48
+ appendFileSync(eventsPath(), JSON.stringify(line) + '\n');
49
+ rotate();
50
+ return line;
51
+ }
52
+
53
+ export function recent({ since = 0, type, fingerprint, limit = 200 } = {}) {
54
+ const ep = eventsPath();
55
+ if (!existsSync(ep)) return [];
56
+ const raw = readFileSync(ep, 'utf8').split('\n').filter(Boolean);
57
+ const out = [];
58
+ for (let i = raw.length - 1; i >= 0 && out.length < limit; i--) {
59
+ let entry;
60
+ try { entry = JSON.parse(raw[i]); } catch { continue; }
61
+ if (since && entry.ts < since) break;
62
+ if (type && entry.type !== type) continue;
63
+ if (fingerprint && entry.fingerprint !== fingerprint) continue;
64
+ out.push(entry);
65
+ }
66
+ return out;
67
+ }
68
+
69
+ function rotate() {
70
+ try {
71
+ const ep = eventsPath();
72
+ const raw = readFileSync(ep, 'utf8').split('\n').filter(Boolean);
73
+ if (raw.length <= MAX_LINES) return;
74
+ const trimmed = raw.slice(raw.length - MAX_LINES).join('\n') + '\n';
75
+ writeFileSync(ep, trimmed);
76
+ } catch { /* rotate is best-effort */ }
77
+ }
78
+
79
+ export const _paths = { rootDir, eventsPath };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * lib/roles/fence.mjs — pure action-vs-manifest fence check.
3
+ *
4
+ * Invoked from the policy-engine and edit-guard hooks when a session is
5
+ * tagged with a persona. Glob patterns use simple ** / * matching (no full
6
+ * minimatch).
7
+ *
8
+ * Decision semantics:
9
+ * - allowed: target is inside the persona's allowedPaths/Commands/Labels
10
+ * - approval: target matches an entry in approvalRequired (needs user yes)
11
+ * - denied: neither — default deny
12
+ */
13
+
14
+ import { loadManifest } from './manifest.mjs';
15
+
16
+ function globMatch(pattern, target) {
17
+ if (!pattern || !target) return false;
18
+ const re = pattern
19
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
20
+ .replace(/\*\*\//g, '__GLOBSTARSLASH__')
21
+ .replace(/\*\*/g, '__GLOBSTAR__')
22
+ .replace(/\*/g, '[^/]*')
23
+ .replace(/__GLOBSTARSLASH__/g, '(?:.*\\/)?')
24
+ .replace(/__GLOBSTAR__/g, '.*');
25
+ return new RegExp('^' + re + '$').test(target);
26
+ }
27
+
28
+ function matchAny(patterns, target) {
29
+ if (!Array.isArray(patterns)) return false;
30
+ return patterns.some((p) => globMatch(p, target));
31
+ }
32
+
33
+ function actionPrefix(action) {
34
+ const idx = action.indexOf(':');
35
+ return idx === -1 ? action : action.slice(0, idx);
36
+ }
37
+
38
+ export function checkAction({ personaId, action, target = '' }) {
39
+ const manifest = loadManifest(personaId);
40
+ if (!manifest) return { allowed: false, reason: 'persona-not-onboarded' };
41
+ const fence = manifest.fence || {};
42
+
43
+ if (action === 'edit' || action === 'write') {
44
+ if (matchAny(fence.allowedPaths, target)) return { allowed: true };
45
+ const approvalForms = [`edit:${target}`, 'edit:**', `${action}:${target}`];
46
+ if (
47
+ (fence.approvalRequired || []).some((req) =>
48
+ req === 'edit' || req === action || approvalForms.includes(req) || globMatch(req.replace(/^edit:/, ''), target)
49
+ )
50
+ ) {
51
+ return { allowed: false, reason: 'needs-approval', approval: true };
52
+ }
53
+ return { allowed: false, reason: 'outside-fence' };
54
+ }
55
+
56
+ if (action === 'bash') {
57
+ const cmd = String(target).trim();
58
+ const allowed = fence.allowedCommands || [];
59
+ const isPrefixMatch = allowed.some((c) => cmd === c || cmd.startsWith(c + ' '));
60
+ if (isPrefixMatch) return { allowed: true };
61
+ if ((fence.approvalRequired || []).includes('bash')) {
62
+ return { allowed: false, reason: 'needs-approval', approval: true };
63
+ }
64
+ return { allowed: false, reason: 'outside-fence' };
65
+ }
66
+
67
+ if (action === 'commit' || action === 'push') {
68
+ if ((fence.approvalRequired || []).includes(action)) {
69
+ return { allowed: false, reason: 'needs-approval', approval: true };
70
+ }
71
+ return { allowed: false, reason: 'outside-fence' };
72
+ }
73
+
74
+ if (action === 'bd-label') {
75
+ const labels = String(target).split(',').map((s) => s.trim()).filter(Boolean);
76
+ const allowed = fence.allowedBdLabels || [];
77
+ if (labels.every((l) => allowed.includes(l) || l.startsWith('next:'))) return { allowed: true };
78
+ return { allowed: false, reason: 'label-outside-fence' };
79
+ }
80
+
81
+ return { allowed: false, reason: 'unknown-action' };
82
+ }
83
+
84
+ export { globMatch };
@@ -0,0 +1,260 @@
1
+ /**
2
+ * lib/roles/gateway.mjs — single entry point hooks call to record events and
3
+ * (maybe) invoke a persona.
4
+ *
5
+ * Flow: kill-switch checks → emit to bus → route → threshold/cooldown/rate
6
+ * ceiling → create bd issue → queue pending invocation → SSE toast.
7
+ *
8
+ * Defensive: every step is best-effort so a partial failure (bd unavailable,
9
+ * SSE bus unloaded) never blocks the calling hook. Returns a structured
10
+ * decision so callers can log / chain.
11
+ */
12
+
13
+ import { existsSync, readFileSync, appendFileSync, writeFileSync, mkdirSync } from 'node:fs';
14
+ import { homedir } from 'node:os';
15
+ import { join } from 'node:path';
16
+
17
+ import { emit, _paths } from './event-bus.mjs';
18
+ import { route as routeEvent } from './router.mjs';
19
+ import { runBd } from '../beads-client.mjs';
20
+
21
+ function rootDir() {
22
+ return process.env.CONSTRUCT_ROLES_ROOT || join(homedir(), '.cx');
23
+ }
24
+ function pendingPath() {
25
+ return join(rootDir(), 'role-pending.jsonl');
26
+ }
27
+
28
+ const DEFAULTS = {
29
+ thresholdHits: 2,
30
+ thresholdWindowMs: 10 * 60 * 1000,
31
+ cooldownMs: 30 * 60 * 1000,
32
+ rateCeilingPerHour: 3,
33
+ };
34
+
35
+ function ensureDir() {
36
+ const r = rootDir();
37
+ if (!existsSync(r)) mkdirSync(r, { recursive: true });
38
+ }
39
+
40
+ function readPending() {
41
+ const pp = pendingPath();
42
+ if (!existsSync(pp)) return [];
43
+ try {
44
+ return readFileSync(pp, 'utf8')
45
+ .split('\n')
46
+ .filter(Boolean)
47
+ .map((l) => { try { return JSON.parse(l); } catch { return null; } })
48
+ .filter(Boolean);
49
+ } catch { return []; }
50
+ }
51
+
52
+ function appendPending(entry) {
53
+ ensureDir();
54
+ appendFileSync(pendingPath(), JSON.stringify(entry) + '\n');
55
+ }
56
+
57
+ export function shouldEscalate(event, manifest, now = Date.now()) {
58
+ const fp = event.fingerprint;
59
+ const pending = readPending();
60
+
61
+ // Cooldown: same fingerprint already escalated within window?
62
+ const recentSame = pending.find(
63
+ (p) => p.fingerprint === fp && now - p.ts < DEFAULTS.cooldownMs
64
+ );
65
+ if (recentSame) return { escalate: false, reason: 'cooldown' };
66
+
67
+ // Rate ceiling per persona per hour
68
+ const personaId = (manifest && manifest.killSwitchEnv) ? manifest.killSwitchEnv : '';
69
+ const hourAgo = now - 60 * 60 * 1000;
70
+ const recentForPersona = pending.filter(
71
+ (p) => p.ts > hourAgo && p.killSwitchEnv === personaId
72
+ );
73
+ if (recentForPersona.length >= DEFAULTS.rateCeilingPerHour) {
74
+ return { escalate: false, reason: 'rate-ceiling' };
75
+ }
76
+
77
+ // Severity short-circuit: immediate on first hit
78
+ const immediate = Array.isArray(manifest?.severityImmediate)
79
+ && manifest.severityImmediate.includes(event.type);
80
+ if (immediate) return { escalate: true, reason: 'severity-immediate', count: 1 };
81
+
82
+ // Threshold: N hits within window from events.jsonl
83
+ const ep = _paths.eventsPath();
84
+ let hits = 0;
85
+ if (existsSync(ep)) {
86
+ const lines = readFileSync(ep, 'utf8').split('\n').filter(Boolean);
87
+ for (let i = lines.length - 1; i >= 0; i--) {
88
+ let e;
89
+ try { e = JSON.parse(lines[i]); } catch { continue; }
90
+ if (now - e.ts > DEFAULTS.thresholdWindowMs) break;
91
+ if (e.fingerprint === fp) hits++;
92
+ }
93
+ }
94
+ if (hits >= DEFAULTS.thresholdHits) {
95
+ return { escalate: true, reason: 'threshold', count: hits };
96
+ }
97
+ return { escalate: false, reason: 'below-threshold', count: hits };
98
+ }
99
+
100
+ async function createBdIncident(event, routeResult) {
101
+ const labels = (routeResult.manifest.outputs?.bdLabels || []).join(',') || 'incident';
102
+ const firstLine = String(event.summary || '').split('\n')[0].slice(0, 120);
103
+ const title = `[${routeResult.personaId}] ${event.type} — ${firstLine}`;
104
+ const body = JSON.stringify(
105
+ {
106
+ eventType: event.type,
107
+ ts: event.ts,
108
+ project: event.project,
109
+ branch: event.branch,
110
+ cwd: event.cwd,
111
+ summary: event.summary,
112
+ context: event.context,
113
+ fingerprint: event.fingerprint,
114
+ fence: routeResult.manifest.fence || {},
115
+ handoffCandidates: routeResult.manifest.handoffCandidates || [],
116
+ },
117
+ null,
118
+ 2
119
+ );
120
+ const result = await runBd(
121
+ ['create', title, '-t', 'bug', '-l', labels, '-d', body],
122
+ { actor: `roles:${routeResult.personaId}`, silent: true, timeoutSeconds: 10, commandTimeoutSeconds: 30 }
123
+ );
124
+ if (!result.success) return { success: false, error: result.error || 'bd-failed' };
125
+ const match = String(result.output || '').match(/Created issue:\s*([\w-]+)/);
126
+ return { success: true, issueId: match ? match[1] : null, raw: result.output };
127
+ }
128
+
129
+ async function emitToastBestEffort(payload) {
130
+ try {
131
+ const mod = await import('../embed/notifications.mjs');
132
+ if (typeof mod.emitEmbedNotification === 'function') {
133
+ mod.emitEmbedNotification(payload);
134
+ }
135
+ } catch { /* dashboard not loaded; toast is best-effort */ }
136
+ }
137
+
138
+ export async function tryEscalate(event) {
139
+ const r = routeEvent(event);
140
+ if (!r) return { escalated: false, event, reason: 'no-owner' };
141
+
142
+ if (r.manifest.killSwitchEnv && process.env[r.manifest.killSwitchEnv] === 'off') {
143
+ return { escalated: false, event, reason: 'persona-off' };
144
+ }
145
+
146
+ const decision = shouldEscalate(event, r.manifest);
147
+ if (!decision.escalate) {
148
+ return { escalated: false, event, reason: decision.reason, decision };
149
+ }
150
+
151
+ try {
152
+ const { checkBudget } = await import('../cost-ledger.mjs');
153
+ const budget = checkBudget({ personaId: r.personaId });
154
+ if (!budget.allowed) {
155
+ return { escalated: false, event, reason: 'budget-exhausted', budget };
156
+ }
157
+ } catch { /* cost ledger optional — never block on missing module */ }
158
+
159
+ const bd = await createBdIncident(event, r);
160
+ if (!bd.success) {
161
+ return { escalated: false, event, reason: 'bd-create-failed', error: bd.error };
162
+ }
163
+
164
+ const pendingEntry = {
165
+ ts: Date.now(),
166
+ personaId: r.personaId,
167
+ cxId: r.cxId,
168
+ bdIssueId: bd.issueId,
169
+ fingerprint: event.fingerprint,
170
+ eventType: event.type,
171
+ summary: String(event.summary || '').split('\n')[0],
172
+ killSwitchEnv: r.manifest.killSwitchEnv || '',
173
+ };
174
+ appendPending(pendingEntry);
175
+
176
+ await emitToastBestEffort({
177
+ type: 'warning',
178
+ source: 'roles',
179
+ message: `${r.cxId}: ${pendingEntry.summary}`,
180
+ meta: {
181
+ personaId: r.personaId,
182
+ bdIssueId: bd.issueId,
183
+ fingerprint: event.fingerprint,
184
+ eventType: event.type,
185
+ },
186
+ });
187
+
188
+ return { escalated: true, event, personaId: r.personaId, bdIssueId: bd.issueId, decision };
189
+ }
190
+
191
+ export async function recordAndMaybeInvoke(eventType, payload = {}) {
192
+ if (process.env.CONSTRUCT_ROLES === 'off') {
193
+ return { recorded: false, escalated: false, reason: 'global-off' };
194
+ }
195
+ const event = emit(eventType, payload);
196
+ const r = await tryEscalate(event);
197
+ return { recorded: true, ...r };
198
+ }
199
+
200
+ export function listPending({ unresolved = true } = {}) {
201
+ const entries = readPending();
202
+ if (!unresolved) return entries;
203
+ return entries.filter((e) => !e.resolvedAt);
204
+ }
205
+
206
+ // Drain events that accumulated outside a Claude session. Walks recent
207
+ // events.jsonl, runs each unescalated one through tryEscalate. Bounded by
208
+ // `sinceMs` and `maxProcess` so a long-quiet machine can't trigger a flood.
209
+
210
+ export async function processBacklog({ sinceMs = 60 * 60 * 1000, maxProcess = 10 } = {}) {
211
+ if (process.env.CONSTRUCT_ROLES === 'off') return { processed: 0, escalated: 0, reason: 'global-off' };
212
+
213
+ const ep = _paths.eventsPath();
214
+ if (!existsSync(ep)) return { processed: 0, escalated: 0 };
215
+ const cutoff = Date.now() - sinceMs;
216
+ const lines = readFileSync(ep, 'utf8').split('\n').filter(Boolean);
217
+ const pending = readPending();
218
+ const pendingFps = new Set(pending.map((p) => p.fingerprint));
219
+
220
+ const candidates = [];
221
+ for (let i = lines.length - 1; i >= 0 && candidates.length < maxProcess * 4; i--) {
222
+ let e;
223
+ try { e = JSON.parse(lines[i]); } catch { continue; }
224
+ if (e.ts < cutoff) break;
225
+ if (pendingFps.has(e.fingerprint)) continue;
226
+ candidates.push(e);
227
+ }
228
+
229
+ let escalated = 0;
230
+ let processed = 0;
231
+ for (const e of candidates.reverse()) {
232
+ if (processed >= maxProcess) break;
233
+ processed++;
234
+ const r = await tryEscalate(e);
235
+ if (r.escalated) escalated++;
236
+ }
237
+ return { processed, escalated };
238
+ }
239
+
240
+ export function markResolved(fingerprint) {
241
+ const entries = readPending();
242
+ let changed = false;
243
+ const next = entries.map((e) => {
244
+ if (e.fingerprint === fingerprint && !e.resolvedAt) {
245
+ changed = true;
246
+ return { ...e, resolvedAt: Date.now() };
247
+ }
248
+ return e;
249
+ });
250
+ if (!changed) return false;
251
+ writeFileSync(pendingPath(), next.map((e) => JSON.stringify(e)).join('\n') + '\n');
252
+ return true;
253
+ }
254
+
255
+ export function resetPending() {
256
+ const pp = pendingPath();
257
+ if (existsSync(pp)) writeFileSync(pp, '');
258
+ }
259
+
260
+ export const _gatewayPaths = { rootDir, pendingPath };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * lib/roles/hook-emit.mjs — synchronous one-liner hooks call to publish events.
3
+ *
4
+ * Wraps event-bus.emit with auto-detection of project + branch from the hook
5
+ * input. Never throws — hooks must stay on the hot path. Returns the event or
6
+ * null on failure. Use `emitRoleEvent({ type, summary, hookInput, context })`.
7
+ */
8
+
9
+ import { execSync } from 'node:child_process';
10
+ import { emit } from './event-bus.mjs';
11
+
12
+ function gitInfo(cwd) {
13
+ try {
14
+ const project = execSync('git rev-parse --show-toplevel 2>/dev/null', { cwd, timeout: 1000 }).toString().trim();
15
+ const branch = execSync('git rev-parse --abbrev-ref HEAD 2>/dev/null', { cwd, timeout: 1000 }).toString().trim();
16
+ return { project, branch };
17
+ } catch {
18
+ return { project: cwd || '', branch: '' };
19
+ }
20
+ }
21
+
22
+ export function emitRoleEvent({ type, summary, hookInput = {}, context = null }) {
23
+ if (process.env.CONSTRUCT_ROLES === 'off') return null;
24
+ try {
25
+ const cwd = hookInput.cwd || process.cwd();
26
+ const { project, branch } = gitInfo(cwd);
27
+ return emit(type, {
28
+ project,
29
+ branch,
30
+ cwd,
31
+ summary: String(summary || '').slice(0, 4096),
32
+ context,
33
+ });
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * lib/roles/manifest.mjs — role manifest loader.
3
+ *
4
+ * Reads agents/role-manifests.json. A persona is "onboarded" when its
5
+ * `events` array is non-empty. Empty entries reserve the slot for future
6
+ * wiring without code change.
7
+ */
8
+
9
+ import { readFileSync } from 'node:fs';
10
+ import { dirname, join } from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ const MANIFEST_PATH = join(__dirname, '..', '..', 'agents', 'role-manifests.json');
15
+
16
+ let cached = null;
17
+
18
+ function load() {
19
+ if (cached) return cached;
20
+ const raw = readFileSync(MANIFEST_PATH, 'utf8');
21
+ const parsed = JSON.parse(raw);
22
+ cached = parsed.personas || {};
23
+ return cached;
24
+ }
25
+
26
+ export function loadManifest(personaId) {
27
+ const id = String(personaId).replace(/^cx-/, '');
28
+ const all = load();
29
+ return all[id] || null;
30
+ }
31
+
32
+ export function isOnboarded(personaId) {
33
+ const m = loadManifest(personaId);
34
+ return !!(m && Array.isArray(m.events) && m.events.length > 0);
35
+ }
36
+
37
+ export function listOnboardedPersonas() {
38
+ const all = load();
39
+ return Object.keys(all).filter((id) => isOnboarded(id));
40
+ }
41
+
42
+ export function listAllPersonas() {
43
+ return Object.keys(load());
44
+ }
45
+
46
+ export function _resetCache() {
47
+ cached = null;
48
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * lib/roles/router.mjs — pure event → persona resolver.
3
+ *
4
+ * Uses EVENT_OWNERSHIP from orchestration-policy and validates against
5
+ * the loaded role manifest. Returns null (no owner) silently if the
6
+ * event has no declared owner or the persona is not yet onboarded —
7
+ * letting us declare ownership ahead of full wiring.
8
+ */
9
+
10
+ import { EVENT_OWNERSHIP } from '../orchestration-policy.mjs';
11
+ import { loadManifest, isOnboarded } from './manifest.mjs';
12
+
13
+ export function route(event) {
14
+ const type = event?.type;
15
+ if (!type) return null;
16
+ const ownerCxId = EVENT_OWNERSHIP[type];
17
+ if (!ownerCxId) return null;
18
+ const personaId = ownerCxId.replace(/^cx-/, '');
19
+ if (!isOnboarded(personaId)) return null;
20
+ const manifest = loadManifest(personaId);
21
+ if (!manifest.events.includes(type)) return null;
22
+ return { personaId, cxId: ownerCxId, manifest };
23
+ }
24
+
25
+ export function ownerOf(eventType) {
26
+ return EVENT_OWNERSHIP[eventType] || null;
27
+ }