@geraldmaron/construct 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (497) hide show
  1. package/README.md +7 -10
  2. package/bin/construct +286 -220
  3. package/bin/construct-postinstall.mjs +0 -21
  4. package/commands/work/optimize-prompts.md +1 -1
  5. package/examples/distribution/sources/adr.md +2 -2
  6. package/examples/seed-observations/decisions.md +1 -1
  7. package/lib/artifact-loop-core.mjs +412 -0
  8. package/lib/artifact-manifest.mjs +6 -0
  9. package/lib/artifact-release-gate.mjs +115 -49
  10. package/lib/audit-rules.mjs +2 -4
  11. package/lib/audit-skills.mjs +32 -20
  12. package/lib/audit-specialists.mjs +44 -26
  13. package/lib/auto-docs.mjs +6 -5
  14. package/lib/brand-prose.mjs +3 -3
  15. package/lib/brand-tokens.mjs +2 -2
  16. package/lib/certification/artifact-fixtures.mjs +4 -1
  17. package/lib/certification/demo-parity.mjs +6 -32
  18. package/lib/certification/real-llm-scenarios.mjs +58 -13
  19. package/lib/certification/role-cards.mjs +7 -6
  20. package/lib/certification/role-overlays.mjs +4 -4
  21. package/lib/certification/runner.mjs +9 -2
  22. package/lib/certification/skill-inventory.mjs +34 -22
  23. package/lib/certification/skill-scenarios.mjs +21 -8
  24. package/lib/certification/specialist-contracts.mjs +4 -3
  25. package/lib/certification/specialist-scenarios.mjs +7 -6
  26. package/lib/certification/status.mjs +5 -4
  27. package/lib/cli-commands.mjs +56 -57
  28. package/lib/comment-lint.mjs +1 -5
  29. package/lib/completions.mjs +20 -2
  30. package/lib/config/legacy-config-migration.mjs +125 -0
  31. package/lib/config/schema.mjs +5 -5
  32. package/lib/config/source-targets.mjs +80 -0
  33. package/lib/contracts/validate.mjs +119 -51
  34. package/lib/decisions/golden.mjs +3 -2
  35. package/lib/decisions/registry.mjs +11 -6
  36. package/lib/demo-project.mjs +188 -0
  37. package/lib/demo-recording.mjs +9 -34
  38. package/lib/demo-script.mjs +2 -2
  39. package/lib/demo-surface.mjs +13 -131
  40. package/lib/demo-tour-renderer.mjs +92 -0
  41. package/lib/demo.mjs +69 -54
  42. package/lib/diagram-export.mjs +63 -14
  43. package/lib/doctor/source-checkout.mjs +3 -4
  44. package/lib/doctor/watchers/consistency.mjs +90 -22
  45. package/lib/doctor/watchers/credential-parity.mjs +97 -0
  46. package/lib/doctor/watchers/mcp-protocol.mjs +1 -1
  47. package/lib/doctor/watchers/service-health.mjs +4 -32
  48. package/lib/document-export.mjs +73 -9
  49. package/lib/document-extract.mjs +10 -198
  50. package/lib/embed/artifact.mjs +2 -3
  51. package/lib/embed/cli.mjs +1 -1
  52. package/lib/embed/config.mjs +1 -2
  53. package/lib/embed/customer-profiles.mjs +1 -1
  54. package/lib/embed/daemon.mjs +5 -6
  55. package/lib/embed/demand-fetch.mjs +114 -2
  56. package/lib/embed/docs-lifecycle.mjs +9 -5
  57. package/lib/embed/intake-metrics.mjs +2 -2
  58. package/lib/embed/notifications.mjs +2 -3
  59. package/lib/embed/recommendation-store.mjs +25 -22
  60. package/lib/embed/role-framing.mjs +4 -9
  61. package/lib/embedded-contract/capability.mjs +4 -2
  62. package/lib/embedded-contract/contract-version.mjs +1 -1
  63. package/lib/env-config.mjs +2 -2
  64. package/lib/flavors/loader.mjs +21 -19
  65. package/lib/graph/build-from-registry.mjs +31 -7
  66. package/lib/graph/staleness.mjs +1 -1
  67. package/lib/headhunt.mjs +82 -35
  68. package/lib/hooks/agent-tracker.mjs +2 -2
  69. package/lib/hooks/config-protection.mjs +1 -1
  70. package/lib/hooks/registry-sync.mjs +3 -3
  71. package/lib/host-capabilities.mjs +5 -5
  72. package/lib/improvement/controller.mjs +2 -2
  73. package/lib/init-unified.mjs +19 -21
  74. package/lib/intake/classify.mjs +26 -0
  75. package/lib/intake/daemon.mjs +2 -3
  76. package/lib/intake/prepare.mjs +10 -3
  77. package/lib/intake/session-prelude.mjs +1 -1
  78. package/lib/intake/tables/creative.mjs +9 -9
  79. package/lib/intake/tables/operations.mjs +4 -4
  80. package/lib/intake/tables/research.mjs +2 -2
  81. package/lib/integrations/intake-integrations.mjs +9 -10
  82. package/lib/knowledge/research-store.mjs +2 -2
  83. package/lib/mcp/server.mjs +104 -61
  84. package/lib/mcp/tool-budget.mjs +1 -46
  85. package/lib/mcp/tool-recovery.mjs +44 -0
  86. package/lib/mcp/tools/artifact-author.mjs +36 -0
  87. package/lib/mcp/tools/find-tool.mjs +86 -0
  88. package/lib/mcp/tools/orchestration-run.mjs +107 -69
  89. package/lib/mcp/tools/project.mjs +4 -3
  90. package/lib/mcp/tools/{profile.mjs → scope.mjs} +54 -52
  91. package/lib/mcp/tools/skills.mjs +39 -22
  92. package/lib/mcp/tools/telemetry.mjs +2 -1
  93. package/lib/mcp/tools/workflow.mjs +1 -1
  94. package/lib/mcp-platform-config.mjs +7 -5
  95. package/lib/migrations/index.mjs +4 -2
  96. package/lib/migrations/v2-unified-registry.mjs +35 -0
  97. package/lib/model-router.mjs +203 -36
  98. package/lib/models/catalog.mjs +25 -9
  99. package/lib/models/execution-capability-profile.mjs +3 -3
  100. package/lib/models/execution-policy.mjs +7 -6
  101. package/lib/models/provider-poll.mjs +18 -4
  102. package/lib/opencode-config.mjs +56 -1
  103. package/lib/opencode-runtime-plugin.mjs +13 -10
  104. package/lib/oracle/artifact-gate.mjs +14 -4
  105. package/lib/oracle/cli.mjs +2 -0
  106. package/lib/oracle/dispatch.mjs +18 -1
  107. package/lib/oracle/execute.mjs +39 -7
  108. package/lib/oracle/index.mjs +1 -1
  109. package/lib/oracle/org-graph.mjs +10 -5
  110. package/lib/oracle/policy.mjs +6 -0
  111. package/lib/oracle/read-model.mjs +103 -6
  112. package/lib/oracle/reconcile.mjs +31 -4
  113. package/lib/oracle/remediation-dispatch.mjs +53 -0
  114. package/lib/oracle/routing.mjs +5 -0
  115. package/lib/oracle/synthesize.mjs +80 -3
  116. package/lib/orchestration/routing-tables.mjs +44 -10
  117. package/lib/orchestration/runtime.mjs +2 -0
  118. package/lib/orchestration/worker.mjs +1 -1
  119. package/lib/orchestration-policy.mjs +351 -62
  120. package/lib/overrides/resolver.mjs +1 -12
  121. package/lib/parity.mjs +37 -11
  122. package/lib/policy/engine.mjs +80 -15
  123. package/lib/prompt-composer.js +51 -15
  124. package/lib/prompt-metadata.mjs +3 -2
  125. package/lib/providers/connection-probe.mjs +58 -0
  126. package/lib/providers/copilot-auth.mjs +1 -1
  127. package/lib/providers/credential-bootstrap.mjs +1 -1
  128. package/lib/providers/op-run.mjs +3 -4
  129. package/lib/providers/secret-resolver.mjs +31 -0
  130. package/lib/publish-tooling.mjs +3 -11
  131. package/lib/publish.mjs +12 -29
  132. package/lib/reconcile/mcp-entry-reconcile.mjs +14 -9
  133. package/lib/reflect.mjs +2 -2
  134. package/lib/registry/agent-manifest.mjs +190 -0
  135. package/lib/registry/assemble.mjs +133 -0
  136. package/lib/registry/catalog.mjs +171 -0
  137. package/lib/registry/cli.mjs +590 -6
  138. package/lib/registry/consolidation.mjs +5 -4
  139. package/lib/registry/docs-sync.mjs +67 -0
  140. package/lib/registry/loader.mjs +147 -0
  141. package/lib/registry/org-io.mjs +76 -0
  142. package/lib/registry/retired-paths.mjs +71 -0
  143. package/lib/registry/surface-map.mjs +5 -7
  144. package/lib/registry/validator.mjs +438 -0
  145. package/lib/research-execution-policy.mjs +173 -0
  146. package/lib/roles/catalog.mjs +4 -9
  147. package/lib/roles/fence.mjs +107 -1
  148. package/lib/roles/flavor-bindings.mjs +68 -0
  149. package/lib/roles/gateway.mjs +140 -29
  150. package/lib/roles/manifest.mjs +22 -13
  151. package/lib/roles/router.mjs +1 -1
  152. package/lib/runtime-env.mjs +1 -1
  153. package/lib/scopes/enrich.mjs +67 -0
  154. package/lib/scopes/hooks.mjs +12 -0
  155. package/lib/{profiles → scopes}/lifecycle.mjs +70 -70
  156. package/lib/scopes/loader.mjs +104 -0
  157. package/lib/scopes/rebrand.mjs +32 -0
  158. package/lib/scopes/research-profile.mjs +16 -0
  159. package/lib/scopes/teams.mjs +98 -0
  160. package/lib/service-manager.mjs +1 -117
  161. package/lib/setup-prompts.mjs +1 -1
  162. package/lib/setup.mjs +17 -28
  163. package/lib/skills/composition-graph.mjs +98 -0
  164. package/lib/skills/router.mjs +80 -0
  165. package/lib/specialist-contracts.mjs +17 -18
  166. package/lib/specialists/postconditions.mjs +2 -2
  167. package/lib/specialists/prompt-schema.mjs +6 -5
  168. package/lib/specialists/roster.mjs +7 -12
  169. package/lib/specialists/schema.mjs +9 -6
  170. package/lib/status.mjs +25 -90
  171. package/lib/storage/file-lock.mjs +6 -3
  172. package/lib/telemetry/skill-calls.mjs +1 -1
  173. package/lib/templates/doc-presentation.mjs +63 -0
  174. package/lib/templates/visual-requirements.mjs +35 -2
  175. package/lib/term-format.mjs +107 -2
  176. package/lib/test-env-setup.mjs +21 -0
  177. package/lib/ui/components.mjs +42 -0
  178. package/lib/ui/glyphs.mjs +58 -0
  179. package/lib/ui/links.mjs +115 -0
  180. package/lib/ui/theme.mjs +108 -0
  181. package/lib/uninstall/uninstall.mjs +2 -1
  182. package/lib/validator.mjs +45 -8
  183. package/lib/validators/skill-effectiveness.mjs +174 -0
  184. package/lib/validators/skills.mjs +1 -1
  185. package/package.json +12 -7
  186. package/personas/construct.md +12 -3
  187. package/platforms/claude/CLAUDE.md +1 -1
  188. package/rules/common/beads-hygiene.md +52 -0
  189. package/rules/common/neurodivergent-output.md +4 -7
  190. package/rules/common/research.md +2 -0
  191. package/scripts/sync-specialists.mjs +260 -49
  192. package/skills/ai/prompt-optimizer.md +3 -3
  193. package/skills/brand/output-vibe.md +48 -0
  194. package/skills/docs/adr-workflow.md +3 -0
  195. package/skills/docs/backlog-proposal-workflow.md +3 -0
  196. package/skills/docs/codebase-research-workflow.md +5 -2
  197. package/skills/docs/customer-profile-workflow.md +3 -0
  198. package/skills/docs/document-ingest-workflow.md +3 -0
  199. package/skills/docs/evidence-ingest-workflow.md +4 -1
  200. package/skills/docs/init-project.md +1 -1
  201. package/skills/docs/prd-workflow.md +1 -1
  202. package/skills/docs/prfaq-workflow.md +3 -0
  203. package/skills/docs/product-intelligence-workflow.md +4 -1
  204. package/skills/docs/product-signal-workflow.md +3 -0
  205. package/skills/docs/research-workflow.md +13 -6
  206. package/skills/docs/runbook-workflow.md +3 -0
  207. package/skills/docs/strategy-workflow.md +3 -0
  208. package/skills/docs/user-research-workflow.md +6 -3
  209. package/skills/operating/orchestration-reference.md +1 -1
  210. package/skills/roles/{engineer.ai.md → ai-engineer.md} +2 -2
  211. package/skills/roles/architect.ai-systems.md +1 -1
  212. package/skills/roles/architect.data.md +1 -1
  213. package/skills/roles/architect.enterprise.md +1 -1
  214. package/skills/roles/architect.integration.md +1 -1
  215. package/skills/roles/architect.md +1 -1
  216. package/skills/roles/architect.platform.md +1 -1
  217. package/skills/roles/{product-manager.business-strategy.md → business-strategist.md} +3 -3
  218. package/skills/roles/data-analyst.experiment.md +1 -1
  219. package/skills/roles/data-analyst.md +1 -1
  220. package/skills/roles/data-analyst.product-intelligence.md +1 -1
  221. package/skills/roles/data-analyst.product.md +1 -1
  222. package/skills/roles/data-analyst.telemetry.md +1 -1
  223. package/skills/roles/{engineer.data.md → data-engineer.md} +2 -2
  224. package/skills/roles/data-engineer.pipeline.md +2 -2
  225. package/skills/roles/data-engineer.vector-retrieval.md +2 -2
  226. package/skills/roles/data-engineer.warehouse.md +2 -2
  227. package/skills/roles/debugger.md +1 -1
  228. package/skills/roles/designer.accessibility.md +1 -1
  229. package/skills/roles/designer.md +1 -1
  230. package/skills/roles/{reviewer.devil-advocate.md → devil-advocate.md} +2 -2
  231. package/skills/roles/{operator.docs.md → docs-keeper.md} +3 -3
  232. package/skills/roles/engineer.md +3 -7
  233. package/skills/roles/{reviewer.evaluator.md → evaluator.md} +2 -2
  234. package/skills/roles/{researcher.explorer.md → explorer.md} +2 -2
  235. package/skills/roles/{operator.md → operations.md} +2 -5
  236. package/skills/roles/orchestrator.md +1 -1
  237. package/skills/roles/{engineer.platform.md → platform-engineer.md} +2 -2
  238. package/skills/roles/product-manager.ai-product.md +1 -1
  239. package/skills/roles/product-manager.enterprise.md +1 -1
  240. package/skills/roles/product-manager.growth.md +1 -2
  241. package/skills/roles/product-manager.md +1 -2
  242. package/skills/roles/product-manager.platform.md +1 -1
  243. package/skills/roles/product-manager.product.md +1 -1
  244. package/skills/roles/qa.ai-eval.md +1 -1
  245. package/skills/roles/qa.api-contract.md +1 -1
  246. package/skills/roles/qa.data-pipeline.md +1 -1
  247. package/skills/roles/qa.md +1 -1
  248. package/skills/roles/qa.web-ui.md +1 -1
  249. package/skills/roles/{operator.release.md → release-manager.md} +3 -3
  250. package/skills/roles/researcher.md +1 -1
  251. package/skills/roles/reviewer.md +1 -1
  252. package/skills/roles/security.ai.md +1 -1
  253. package/skills/roles/security.appsec.md +1 -1
  254. package/skills/roles/security.cloud.md +1 -1
  255. package/skills/roles/security.legal-compliance.md +1 -1
  256. package/skills/roles/security.md +1 -1
  257. package/skills/roles/security.privacy.md +1 -1
  258. package/skills/roles/security.supply-chain.md +1 -1
  259. package/skills/roles/{operator.sre.md → sre.md} +3 -3
  260. package/skills/roles/{qa.test-automation.md → test-automation.md} +2 -2
  261. package/skills/roles/{reviewer.trace.md → trace-reviewer.md} +2 -2
  262. package/skills/roles/{researcher.ux.md → ux-researcher.md} +2 -2
  263. package/skills/routing.json +18 -0
  264. package/skills/routing.md +1 -1
  265. package/specialists/artifact-manifest.json +2 -1
  266. package/specialists/audit-enrichments.json +14 -14
  267. package/specialists/org/contracts/accessibility-to-qa.json +37 -0
  268. package/specialists/org/contracts/any-to-business-strategist.json +57 -0
  269. package/specialists/org/contracts/any-to-debugger.json +38 -0
  270. package/specialists/org/contracts/any-to-designer.json +33 -0
  271. package/specialists/org/contracts/any-to-docs-keeper.json +34 -0
  272. package/specialists/org/contracts/any-to-explorer.json +39 -0
  273. package/specialists/org/contracts/any-to-sre-incident.json +33 -0
  274. package/specialists/org/contracts/any-to-trace-reviewer.json +32 -0
  275. package/specialists/org/contracts/architect-to-ai-engineer.json +32 -0
  276. package/specialists/org/contracts/architect-to-data-engineer.json +52 -0
  277. package/specialists/org/contracts/architect-to-devil-advocate.json +38 -0
  278. package/specialists/org/contracts/architect-to-engineer.json +34 -0
  279. package/specialists/org/contracts/architect-to-evaluator.json +59 -0
  280. package/specialists/org/contracts/architect-to-legal-compliance.json +55 -0
  281. package/specialists/org/contracts/architect-to-operations.json +45 -0
  282. package/specialists/org/contracts/architect-to-platform-engineer.json +42 -0
  283. package/specialists/org/contracts/business-strategist-to-product-manager.json +39 -0
  284. package/specialists/org/contracts/construct-to-orchestrator.json +36 -0
  285. package/specialists/org/contracts/construct-to-rd-lead.json +53 -0
  286. package/specialists/org/contracts/data-analyst-to-product-manager.json +43 -0
  287. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +36 -0
  288. package/specialists/org/contracts/designer-to-accessibility.json +36 -0
  289. package/specialists/org/contracts/engineer-to-qa.json +34 -0
  290. package/specialists/org/contracts/engineer-to-reviewer.json +52 -0
  291. package/specialists/org/contracts/explorer-to-engineer.json +38 -0
  292. package/specialists/org/contracts/legal-compliance-to-release-manager.json +43 -0
  293. package/specialists/org/contracts/platform-engineer-to-engineer.json +31 -0
  294. package/specialists/org/contracts/product-manager-to-architect.json +71 -0
  295. package/specialists/org/contracts/product-manager-to-data-analyst.json +49 -0
  296. package/specialists/org/contracts/product-manager-to-ux-researcher.json +51 -0
  297. package/specialists/org/contracts/qa-to-release-manager.json +42 -0
  298. package/specialists/org/contracts/qa-to-test-automation.json +42 -0
  299. package/specialists/org/contracts/rd-lead-to-architect.json +38 -0
  300. package/specialists/org/contracts/researcher-to-architect.json +40 -0
  301. package/specialists/org/contracts/researcher-to-product-manager.json +57 -0
  302. package/specialists/org/contracts/reviewer-to-security.json +41 -0
  303. package/specialists/org/contracts/sre-to-release-manager.json +36 -0
  304. package/specialists/org/contracts/test-automation-to-engineer.json +34 -0
  305. package/specialists/org/contracts/trace-reviewer-to-sre.json +41 -0
  306. package/specialists/org/contracts/user-to-construct.json +30 -0
  307. package/specialists/org/groups/engineering-group.json +43 -0
  308. package/specialists/org/groups/governance-group.json +38 -0
  309. package/specialists/org/groups/operations-group.json +41 -0
  310. package/specialists/org/groups/product-group.json +46 -0
  311. package/specialists/org/groups/quality-group.json +42 -0
  312. package/specialists/org/groups/strategy-group.json +41 -0
  313. package/specialists/org/policies/action-approval.json +13 -0
  314. package/specialists/org/policies/agents-routing.json +13 -0
  315. package/specialists/org/policies/architecture.json +20 -0
  316. package/specialists/org/policies/bash-safety.json +13 -0
  317. package/specialists/org/policies/beads-hygiene.json +13 -0
  318. package/specialists/org/policies/bootstrap-state.json +13 -0
  319. package/specialists/org/policies/code-review.json +13 -0
  320. package/specialists/org/policies/coding-style.json +13 -0
  321. package/specialists/org/policies/comments.json +13 -0
  322. package/specialists/org/policies/commit-approval.json +13 -0
  323. package/specialists/org/policies/contract-preconditions.json +13 -0
  324. package/specialists/org/policies/deployment.json +20 -0
  325. package/specialists/org/policies/description.json +14 -0
  326. package/specialists/org/policies/design-approval.json +19 -0
  327. package/specialists/org/policies/doc-ownership.json +13 -0
  328. package/specialists/org/policies/file-path-fence.json +13 -0
  329. package/specialists/org/policies/framing.json +13 -0
  330. package/specialists/org/policies/git-workflow.json +13 -0
  331. package/specialists/org/policies/incident-response.json +15 -0
  332. package/specialists/org/policies/intake-triage.json +15 -0
  333. package/specialists/org/policies/neurodivergent-output.json +13 -0
  334. package/specialists/org/policies/no-fabrication.json +13 -0
  335. package/specialists/org/policies/patterns.json +13 -0
  336. package/specialists/org/policies/quality-gate-approval.json +17 -0
  337. package/specialists/org/policies/release-gates.json +13 -0
  338. package/specialists/org/policies/research-evidence.json +13 -0
  339. package/specialists/org/policies/review-before-change.json +13 -0
  340. package/specialists/org/policies/rollback.json +15 -0
  341. package/specialists/org/policies/scope-change.json +20 -0
  342. package/specialists/org/policies/secret-scan.json +13 -0
  343. package/specialists/org/policies/security-approval.json +17 -0
  344. package/specialists/org/policies/security.json +13 -0
  345. package/specialists/org/policies/session-efficiency.json +13 -0
  346. package/specialists/org/policies/skill-routing.json +13 -0
  347. package/specialists/org/policies/strategic-prioritization.json +17 -0
  348. package/specialists/org/policies/testing.json +13 -0
  349. package/specialists/org/policies/tool-invisibility.json +14 -0
  350. package/specialists/org/scopes/creative.json +54 -0
  351. package/specialists/org/scopes/operations.json +51 -0
  352. package/specialists/org/scopes/research.json +60 -0
  353. package/specialists/org/scopes/rnd.json +81 -0
  354. package/specialists/org/specialists/cx-accessibility.json +69 -0
  355. package/specialists/org/specialists/cx-ai-engineer.json +75 -0
  356. package/specialists/org/specialists/cx-architect.json +89 -0
  357. package/specialists/org/specialists/cx-business-strategist.json +72 -0
  358. package/specialists/org/specialists/cx-data-analyst.json +68 -0
  359. package/specialists/org/specialists/cx-data-engineer.json +71 -0
  360. package/specialists/org/specialists/cx-debugger.json +75 -0
  361. package/specialists/org/specialists/cx-designer.json +85 -0
  362. package/specialists/org/specialists/cx-devil-advocate.json +71 -0
  363. package/specialists/org/specialists/cx-docs-keeper.json +82 -0
  364. package/specialists/org/specialists/cx-engineer.json +106 -0
  365. package/specialists/org/specialists/cx-evaluator.json +69 -0
  366. package/specialists/org/specialists/cx-explorer.json +69 -0
  367. package/specialists/org/specialists/cx-legal-compliance.json +74 -0
  368. package/specialists/org/specialists/cx-operations.json +72 -0
  369. package/specialists/org/specialists/cx-oracle.json +46 -0
  370. package/specialists/org/specialists/cx-orchestrator.json +81 -0
  371. package/specialists/org/specialists/cx-platform-engineer.json +80 -0
  372. package/specialists/org/specialists/cx-product-manager.json +102 -0
  373. package/specialists/org/specialists/cx-qa.json +79 -0
  374. package/specialists/org/specialists/cx-rd-lead.json +73 -0
  375. package/specialists/org/specialists/cx-release-manager.json +77 -0
  376. package/specialists/org/specialists/cx-researcher.json +82 -0
  377. package/specialists/org/specialists/cx-reviewer.json +71 -0
  378. package/specialists/org/specialists/cx-security.json +91 -0
  379. package/specialists/org/specialists/cx-sre.json +94 -0
  380. package/specialists/org/specialists/cx-test-automation.json +69 -0
  381. package/specialists/org/specialists/cx-trace-reviewer.json +69 -0
  382. package/specialists/org/specialists/cx-ux-researcher.json +68 -0
  383. package/specialists/org/teams/accessibility-team.json +39 -0
  384. package/specialists/org/teams/design-team.json +40 -0
  385. package/specialists/org/teams/engineering-team.json +50 -0
  386. package/specialists/org/teams/governance-team.json +41 -0
  387. package/specialists/org/teams/operations-team.json +46 -0
  388. package/specialists/org/teams/product-management-team.json +45 -0
  389. package/specialists/org/teams/quality-team.json +49 -0
  390. package/specialists/org/teams/research-team.json +39 -0
  391. package/specialists/org/teams/strategy-team.json +48 -0
  392. package/specialists/org/teams/ux-research-team.json +39 -0
  393. package/specialists/prompts/_shared/validation-contract.md +1 -1
  394. package/specialists/prompts/_team-template.md +10 -0
  395. package/specialists/prompts/cx-accessibility.md +1 -0
  396. package/specialists/prompts/cx-ai-engineer.md +1 -1
  397. package/specialists/prompts/cx-business-strategist.md +1 -1
  398. package/specialists/prompts/cx-data-engineer.md +1 -1
  399. package/specialists/prompts/cx-designer.md +1 -0
  400. package/specialists/prompts/cx-devil-advocate.md +1 -1
  401. package/specialists/prompts/cx-docs-keeper.md +1 -1
  402. package/specialists/prompts/cx-evaluator.md +1 -1
  403. package/specialists/prompts/cx-explorer.md +1 -1
  404. package/specialists/prompts/cx-operations.md +1 -1
  405. package/specialists/prompts/cx-oracle.md +7 -3
  406. package/specialists/prompts/cx-orchestrator.md +17 -1
  407. package/specialists/prompts/cx-platform-engineer.md +1 -1
  408. package/specialists/prompts/cx-product-manager.md +2 -0
  409. package/specialists/prompts/cx-release-manager.md +1 -1
  410. package/specialists/prompts/cx-researcher.md +7 -4
  411. package/specialists/prompts/cx-sre.md +1 -1
  412. package/specialists/prompts/cx-test-automation.md +2 -2
  413. package/specialists/prompts/cx-trace-reviewer.md +3 -3
  414. package/specialists/prompts/cx-ux-researcher.md +2 -1
  415. package/templates/demos/scripts/architecture-review-adr.json +30 -0
  416. package/templates/demos/scripts/capability-contract.json +25 -0
  417. package/templates/demos/scripts/intake-triage.json +25 -0
  418. package/templates/demos/scripts/profile-doctor-health.json +25 -0
  419. package/templates/demos/tapes/agentic-platforms-prd.tape +2 -2
  420. package/templates/demos/tapes/architecture-review-adr.tape +44 -0
  421. package/templates/demos/tapes/capability-contract.tape +39 -0
  422. package/templates/demos/tapes/intake-triage.tape +39 -0
  423. package/templates/demos/tapes/profile-doctor-health.tape +39 -0
  424. package/templates/distribution/construct-brand.typ +23 -18
  425. package/templates/distribution/construct-decision.typ +1 -1
  426. package/templates/distribution/construct-pdf.typ +1 -1
  427. package/templates/distribution/construct-prd.typ +1 -1
  428. package/templates/distribution/construct-research.typ +1 -1
  429. package/templates/distribution/mermaid-puppeteer.json +8 -0
  430. package/templates/docs/persona-artifact.md +1 -1
  431. package/templates/docs/prd.md +6 -0
  432. package/lib/boundary.mjs +0 -127
  433. package/lib/certification/dashboard-api.mjs +0 -71
  434. package/lib/chat/cli.mjs +0 -333
  435. package/lib/chat/command-suggest.mjs +0 -161
  436. package/lib/chat/commands.mjs +0 -215
  437. package/lib/chat/config.mjs +0 -142
  438. package/lib/chat/context-compactor.mjs +0 -250
  439. package/lib/chat/context-continuation.mjs +0 -253
  440. package/lib/chat/continuation-source.mjs +0 -58
  441. package/lib/chat/demo-guide.mjs +0 -61
  442. package/lib/chat/design-tokens.mjs +0 -91
  443. package/lib/chat/desktop-binary.mjs +0 -81
  444. package/lib/chat/desktop-build.mjs +0 -130
  445. package/lib/chat/desktop-launcher.mjs +0 -133
  446. package/lib/chat/evidence.mjs +0 -145
  447. package/lib/chat/export.mjs +0 -74
  448. package/lib/chat/harness/driver.mjs +0 -91
  449. package/lib/chat/list-picker.mjs +0 -112
  450. package/lib/chat/model-picker.mjs +0 -356
  451. package/lib/chat/openrouter-fallback.mjs +0 -151
  452. package/lib/chat/permission-prompt.mjs +0 -33
  453. package/lib/chat/picker-catalog.mjs +0 -45
  454. package/lib/chat/policy-telemetry.mjs +0 -34
  455. package/lib/chat/present.mjs +0 -246
  456. package/lib/chat/session-context.mjs +0 -39
  457. package/lib/chat/session-persist.mjs +0 -73
  458. package/lib/chat/session-restore.mjs +0 -71
  459. package/lib/chat/session-settings.mjs +0 -53
  460. package/lib/chat/system-prompt.mjs +0 -52
  461. package/lib/chat/transparency.mjs +0 -93
  462. package/lib/chat/tui/color-scheme.mjs +0 -42
  463. package/lib/chat/tui/markdown.mjs +0 -123
  464. package/lib/chat/tui/presentation.mjs +0 -100
  465. package/lib/chat/tui/render.mjs +0 -500
  466. package/lib/chat/tui/turn-block.mjs +0 -284
  467. package/lib/chat/tui/turn-present.mjs +0 -18
  468. package/lib/chat/tui/turn-state.mjs +0 -88
  469. package/lib/chat/tui/usage.mjs +0 -122
  470. package/lib/chat/web-commands.mjs +0 -146
  471. package/lib/chat/web-launcher.mjs +0 -63
  472. package/lib/chat/web-picker-keys.mjs +0 -46
  473. package/lib/chat/web-session.mjs +0 -159
  474. package/lib/config/alias.mjs +0 -57
  475. package/lib/dashboard-demo.mjs +0 -71
  476. package/lib/dashboard-static.mjs +0 -175
  477. package/lib/install/desktop-binary-download.mjs +0 -88
  478. package/lib/profiles/loader.mjs +0 -123
  479. package/lib/profiles/rebrand.mjs +0 -46
  480. package/lib/server/auth.mjs +0 -169
  481. package/lib/server/chat-loop.mjs +0 -622
  482. package/lib/server/chat.mjs +0 -336
  483. package/lib/server/cors.mjs +0 -77
  484. package/lib/server/csrf.mjs +0 -103
  485. package/lib/server/demo-preview.mjs +0 -63
  486. package/lib/server/index.mjs +0 -2641
  487. package/lib/server/insights.mjs +0 -780
  488. package/lib/server/langfuse-login.mjs +0 -58
  489. package/lib/server/rate-limit.mjs +0 -93
  490. package/lib/server/telemetry-login.mjs +0 -100
  491. package/lib/server/webhook.mjs +0 -512
  492. package/specialists/contracts.json +0 -1032
  493. package/specialists/contracts.schema.json +0 -83
  494. package/specialists/policy-inventory.json +0 -188
  495. package/specialists/registry.json +0 -1412
  496. package/specialists/role-manifests.json +0 -217
  497. package/specialists/teams.json +0 -94
@@ -1,215 +0,0 @@
1
- /**
2
- * lib/chat/commands.mjs — in-session slash commands for `construct chat`.
3
- *
4
- * Owns the interactive control surface: changing the model, toggling transparency
5
- * layers, editing and persisting settings, and inspecting token usage — without
6
- * leaving the chat. Each command mutates the shared `session` object (which the
7
- * renderer reads per turn) and, for settings, writes through to the project config
8
- * via lib/chat/config.mjs. handle() returns false only for /exit so the REPL loop
9
- * stays the single owner of the readline lifecycle. Ink passes `onClear` instead of
10
- * ANSI clear-screen codes; both surfaces share `createCollectWriter` for capture.
11
- *
12
- * Model enumeration is delegated to the driver's optional listModels(); hosts that
13
- * cannot enumerate models degrade to a clear notice rather than failing.
14
- */
15
-
16
- import { saveChatConfig, LAYER_KEYS, PERMISSION_MODES, SANDBOX_LEVELS } from './config.mjs';
17
- import { exportTurns } from './export.mjs';
18
- import { formatUsagePanel } from './tui/usage.mjs';
19
- import { applySessionSetting } from './session-settings.mjs';
20
- import { commitPickerModel, promptModelPickerTerminal } from './model-picker.mjs';
21
- import { handleDemoCommand, registerDemoCommands } from './demo-guide.mjs';
22
-
23
- export const HELP = [
24
- ['/help', 'show this help'],
25
- ['/model [id]', 'show or set the model (no id opens a searchable picker)'],
26
- ['/free', 'set OpenRouter free-router mode (--free equivalent)'],
27
- ['/export [last|session]', 'write plain markdown answer to .cx/chat-sessions/exports/'],
28
- ['/set <key> <on|off|value>', 'change a setting (thinking, tools, path, specialists, observability, permission, sandbox, model)'],
29
- ['/settings', 'show current settings'],
30
- ['/layers', 'show transparency layers'],
31
- ['/usage', 'show session token and cost breakdown'],
32
- ['/oracle', 'show Oracle overseer verdict and pending approvals'],
33
- ['/host', 'show the active host'],
34
- ['/clear', 'clear the screen'],
35
- ['/inspect', 'toggle turn inspector panel (on/off/auto)'],
36
- ['/exit', 'quit'],
37
- ];
38
-
39
- function ask(rl, question) {
40
- return new Promise((resolve) => rl.question(question, (answer) => resolve(answer)));
41
- }
42
-
43
- export function createCommands({ driver, host, hostId = host, version, cwd = process.cwd(), env = process.env, turnBlocksRef = null, demoGuide = null } = {}) {
44
- async function handle(input, ctx) {
45
- const { output, colors, layers, session, rl, onClear } = ctx;
46
- const runtimeEnv = ctx.env || env;
47
- const [cmd, ...rest] = input.trim().split(/\s+/);
48
- const arg = rest.join(' ').trim();
49
- const activeGuide = demoGuide || session?.demoGuide || null;
50
-
51
- switch (cmd) {
52
- case '/exit':
53
- case '/quit':
54
- return false;
55
-
56
- case '/help':
57
- output.write(`${colors.bold}commands${colors.reset}\n`);
58
- for (const [name, desc] of registerDemoCommands(HELP, activeGuide)) {
59
- output.write(` ${colors.cyan}${name.padEnd(28)}${colors.reset}${colors.dim}${desc}${colors.reset}\n`);
60
- }
61
- break;
62
-
63
- case '/demo':
64
- handleDemoCommand(arg, { demoGuide: activeGuide, output, colors });
65
- break;
66
-
67
- case '/host': {
68
- const mode = session.modelMode === 'free-router' ? 'free-router' : 'pinned';
69
- output.write(`${colors.dim}engine:${colors.reset} ${host} (owned loop)${version ? ` (${version})` : ''} ${colors.dim}model:${colors.reset} ${session.model || '(default)'} ${colors.dim}mode:${colors.reset} ${mode}\n`);
70
- output.write(`${colors.dim}construct runs the loop itself; switch models with /model${colors.reset}\n`);
71
- break;
72
- }
73
-
74
- case '/models':
75
- case '/model':
76
- if (arg) {
77
- commitPickerModel(session, { mode: 'pinned', modelId: arg }, { cwd, hostId, layers: session.layers });
78
- output.write(`${colors.green}model set:${colors.reset} ${arg} ${colors.dim}(pinned, saved)${colors.reset}\n`);
79
- break;
80
- }
81
- await promptModelPickerTerminal({
82
- output,
83
- colors,
84
- session,
85
- rl,
86
- askFn: ctx.ask,
87
- env: runtimeEnv,
88
- cwd,
89
- hostId,
90
- layers: session.layers,
91
- });
92
- break;
93
-
94
- case '/free': {
95
- const { resolveFreeOpenRouterModel } = await import('../../apps/chat/engine/models.mjs');
96
- const freeId = await resolveFreeOpenRouterModel({ env: runtimeEnv, tier: 'standard' });
97
- if (!freeId) {
98
- output.write(`${colors.red}OpenRouter free router needs OPENROUTER_API_KEY${colors.reset}\n`);
99
- break;
100
- }
101
- commitPickerModel(session, { mode: 'free-router', modelId: freeId }, { cwd, hostId, layers: session.layers });
102
- output.write(`${colors.green}model mode:${colors.reset} free-router → ${freeId} ${colors.dim}(saved)${colors.reset}\n`);
103
- break;
104
- }
105
-
106
- case '/export': {
107
- const scope = arg === 'session' ? 'session' : 'last';
108
- const blocks = turnBlocksRef?.() || [];
109
- const result = exportTurns(blocks, { scope, cwd });
110
- if (!result.ok) output.write(`${colors.red}${result.error}${colors.reset}\n`);
111
- else output.write(`${colors.green}exported${colors.reset} ${result.count} turn(s) to ${result.path}\n`);
112
- break;
113
- }
114
-
115
- case '/set':
116
- applySetting(output, colors, session, layers, rest);
117
- break;
118
-
119
- case '/settings':
120
- showSettings(output, colors, session, layers);
121
- break;
122
-
123
- case '/layers':
124
- output.write(`${colors.dim}layers:${colors.reset} ${LAYER_KEYS.map((k) => `${k}=${layers[k] ? 'on' : 'off'}`).join(' ')}\n`);
125
- output.write(`${colors.dim}toggle with: /set <layer> on|off${colors.reset}\n`);
126
- break;
127
-
128
- case '/usage':
129
- output.write(formatUsagePanel(session.usage, colors) + '\n');
130
- break;
131
-
132
- case '/oracle': {
133
- const { readOracleDockState, formatOracleDockDetail } = await import('../intake/session-prelude.mjs');
134
- const state = readOracleDockState({ cwd, env: runtimeEnv });
135
- output.write(`${colors.bold}oracle${colors.reset}\n`);
136
- output.write(`${formatOracleDockDetail(state)}\n`);
137
- break;
138
- }
139
-
140
- case '/inspect': {
141
- session.inspectorForced = session.inspectorForced === true ? false : session.inspectorForced === false ? null : true;
142
- const label = session.inspectorForced === true ? 'on' : session.inspectorForced === false ? 'off' : 'auto';
143
- output.write(`${colors.green}inspector:${colors.reset} ${label}\n`);
144
- break;
145
- }
146
-
147
- case '/clear':
148
- if (typeof onClear === 'function') onClear();
149
- else output.write('\u001b[2J\u001b[H');
150
- break;
151
-
152
- default:
153
- output.write(`${colors.dim}unknown command: ${cmd} \u2014 try /help${colors.reset}\n`);
154
- }
155
- return true;
156
- }
157
-
158
- function applySetting(output, colors, session, layers, parts) {
159
- if (parts.length < 2) {
160
- output.write(`${colors.dim}usage: /set <key> <value> (keys: ${[...LAYER_KEYS, 'thinking', 'permission', 'sandbox', 'model', 'ascii', 'inspector'].join(', ')})${colors.reset}\n`);
161
- output.write(`${colors.dim}or run /set alone in the Ink UI for a searchable picker${colors.reset}\n`);
162
- return;
163
- }
164
- const [key, ...valueParts] = parts;
165
- const value = valueParts.join(' ');
166
- if (key === 'host') {
167
- output.write(`${colors.dim}host can only be changed by relaunching: construct chat --host ${value}${colors.reset}\n`);
168
- return;
169
- }
170
- const result = applySessionSetting(session, layers, key, value, { cwd, hostId });
171
- if (!result.ok) { output.write(`${colors.red}${result.error}${colors.reset}\n`); return; }
172
- output.write(`${colors.green}set:${colors.reset} ${result.key} = ${result.value} ${colors.dim}(saved)${colors.reset}\n`);
173
- }
174
-
175
- function showSettings(output, colors, session, layers) {
176
- output.write(`${colors.bold}settings${colors.reset}\n`);
177
- output.write(` ${colors.cyan}host${colors.reset} ${host}\n`);
178
- output.write(` ${colors.cyan}model${colors.reset} ${session.model || '(host default)'} ${colors.dim}(${session.modelMode || 'pinned'})${colors.reset}\n`);
179
- output.write(` ${colors.cyan}thinking${colors.reset} ${layers.thinking ? 'on' : 'off'}\n`);
180
- output.write(` ${colors.cyan}layers${colors.reset} ${LAYER_KEYS.map((k) => `${k}=${layers[k] ? 'on' : 'off'}`).join(' ')}\n`);
181
- const perm = session.permissionMode || 'allow_once';
182
- output.write(` ${colors.cyan}permission${colors.reset} ${perm} ${colors.dim}(${PERMISSION_MODES.join('/')})${colors.reset}\n`);
183
- output.write(` ${colors.cyan}ascii${colors.reset} ${session.ui?.ascii ? 'on' : 'off'} ${colors.dim}(glyph fallback for limited terminals)${colors.reset}\n`);
184
- output.write(` ${colors.cyan}inspector${colors.reset} ${session.ui?.inspector || 'auto'} ${colors.dim}(off/auto/on — per-turn detail panel)${colors.reset}\n`);
185
- output.write(` ${colors.cyan}sandbox${colors.reset} ${session.sandbox || '(host default)'} ${colors.dim}(${SANDBOX_LEVELS.join('/')})${colors.reset}\n`);
186
- output.write(` ${colors.dim}chat sandbox gates tools in this session; isolated project copies use \`construct sandbox create\`${colors.reset}\n`);
187
- }
188
-
189
- function persist(session, layers = session.layers) {
190
- try {
191
- saveChatConfig({
192
- host: hostId,
193
- model: session.modelMode === 'pinned' ? session.model : null,
194
- modelMode: session.modelMode || 'pinned',
195
- layers,
196
- thinking: layers?.thinking,
197
- permissionMode: session.permissionMode,
198
- sandbox: session.sandbox,
199
- ui: session.ui,
200
- }, { cwd });
201
- } catch { /* settings persistence is best-effort */ }
202
- }
203
-
204
- return { handle };
205
- }
206
-
207
- export function createCollectWriter() {
208
- const parts = [];
209
- return {
210
- stream: { write(chunk) { parts.push(String(chunk)); } },
211
- text() { return parts.join(''); },
212
- };
213
- }
214
-
215
- export const PLAIN_COLORS = Object.freeze({ bold: '', dim: '', reset: '', cyan: '', green: '', red: '', yellow: '' });
@@ -1,142 +0,0 @@
1
- /**
2
- * lib/chat/config.mjs — persisted settings for `construct chat`.
3
- *
4
- * Settings are layered: built-in DEFAULTS < global (~/.cx/chat-config.json) <
5
- * project (<project>/.cx/chat-config.json). Reads merge all three; writes always go
6
- * to the project file (resolveProjectScopedPath falls back to ~/.cx when there is no
7
- * project root), so a user's global preferences seed every project while per-project
8
- * overrides stay local. Only the known, validated keys are persisted.
9
- *
10
- * Exposed settings: host, model, transparency layers, thinking, permissionMode,
11
- * sandbox, ui (ascii, inspector). Validation lives here so the command layer and the launcher
12
- * share one source of truth for allowed values.
13
- */
14
-
15
- import fs from 'node:fs';
16
- import path from 'node:path';
17
- import { resolveProjectScopedPath } from '../project-root.mjs';
18
- import { doctorRoot } from '../config/xdg.mjs';
19
-
20
- export const LAYER_KEYS = ['thinking', 'path', 'specialists', 'tools', 'observability'];
21
- export const PERMISSION_MODES = ['ask', 'allow_once', 'allow_always', 'reject'];
22
- export const SANDBOX_LEVELS = ['read-only', 'workspace-write', 'danger-full-access'];
23
-
24
- export const INSPECTOR_MODES = ['off', 'auto', 'on'];
25
- export const THEME_MODES = ['auto', 'light', 'dark'];
26
- export const MODEL_MODES = ['pinned', 'free-router'];
27
-
28
- export const DEFAULTS = Object.freeze({
29
- host: null,
30
- model: null,
31
- modelMode: 'pinned',
32
- layers: Object.freeze(Object.fromEntries(LAYER_KEYS.map((k) => [k, true]))),
33
- thinking: true,
34
- permissionMode: 'allow_once',
35
- sandbox: null,
36
- ui: Object.freeze({ ascii: false, inspector: 'off', theme: 'auto' }),
37
- });
38
-
39
- const CONFIG_BASENAME = 'chat-config.json';
40
-
41
- function readJson(file) {
42
- try {
43
- if (!fs.existsSync(file)) return null;
44
- return JSON.parse(fs.readFileSync(file, 'utf8'));
45
- } catch {
46
- return null;
47
- }
48
- }
49
-
50
- function mergeConfig(base, override) {
51
- if (!override || typeof override !== 'object') return base;
52
- const out = { ...base };
53
- for (const key of Object.keys(DEFAULTS)) {
54
- if (override[key] == null) continue;
55
- if (key === 'layers' && typeof override.layers === 'object') {
56
- out.layers = { ...base.layers, ...override.layers };
57
- } else if (key === 'ui' && typeof override.ui === 'object') {
58
- out.ui = { ...base.ui, ...override.ui };
59
- } else {
60
- out[key] = override[key];
61
- }
62
- }
63
- return out;
64
- }
65
-
66
- export function globalConfigPath() {
67
- return path.join(doctorRoot(), CONFIG_BASENAME);
68
- }
69
-
70
- export function projectConfigPath({ cwd = process.cwd() } = {}) {
71
- return resolveProjectScopedPath(CONFIG_BASENAME, { cwd, ensureDir: false });
72
- }
73
-
74
- export function loadChatConfig({ cwd = process.cwd() } = {}) {
75
- let config = { ...DEFAULTS, layers: { ...DEFAULTS.layers } };
76
- config = mergeConfig(config, readJson(globalConfigPath()));
77
- const projectPath = projectConfigPath({ cwd });
78
- config = mergeConfig(config, readJson(projectPath));
79
- return { config, path: projectPath };
80
- }
81
-
82
- export function saveChatConfig(config, { cwd = process.cwd() } = {}) {
83
- const target = resolveProjectScopedPath(CONFIG_BASENAME, { cwd, ensureDir: true });
84
- const persisted = {};
85
- for (const key of Object.keys(DEFAULTS)) {
86
- if (config[key] == null) continue;
87
- persisted[key] = key === 'layers' ? { ...config.layers } : key === 'ui' ? { ...config.ui } : config[key];
88
- }
89
- fs.writeFileSync(target, `${JSON.stringify(persisted, null, 2)}\n`);
90
- return target;
91
- }
92
-
93
- // Validates and coerces a single `key value` setting change. Returns
94
- // { ok, value, error } so the command layer can report precisely.
95
-
96
- export function validateSetting(key, rawValue) {
97
- switch (key) {
98
- case 'host':
99
- return { ok: true, value: String(rawValue) };
100
- case 'model':
101
- return { ok: true, value: String(rawValue) };
102
- case 'thinking': {
103
- const v = parseBool(rawValue);
104
- return v == null ? { ok: false, error: 'thinking must be on/off' } : { ok: true, value: v };
105
- }
106
- case 'permissionMode':
107
- case 'permission':
108
- return PERMISSION_MODES.includes(rawValue)
109
- ? { ok: true, key: 'permissionMode', value: rawValue }
110
- : { ok: false, error: `permission must be one of: ${PERMISSION_MODES.join(', ')}` };
111
- case 'sandbox':
112
- return SANDBOX_LEVELS.includes(rawValue)
113
- ? { ok: true, value: rawValue }
114
- : { ok: false, error: `sandbox must be one of: ${SANDBOX_LEVELS.join(', ')}` };
115
- case 'ascii': {
116
- const v = parseBool(rawValue);
117
- return v == null ? { ok: false, error: 'ascii must be on/off' } : { ok: true, key: 'ui.ascii', value: v };
118
- }
119
- case 'inspector':
120
- return INSPECTOR_MODES.includes(String(rawValue).toLowerCase())
121
- ? { ok: true, key: 'ui.inspector', value: String(rawValue).toLowerCase() }
122
- : { ok: false, error: `inspector must be one of: ${INSPECTOR_MODES.join(', ')}` };
123
- case 'theme':
124
- return THEME_MODES.includes(String(rawValue).toLowerCase())
125
- ? { ok: true, key: 'ui.theme', value: String(rawValue).toLowerCase() }
126
- : { ok: false, error: `theme must be one of: ${THEME_MODES.join(', ')}` };
127
- default:
128
- if (LAYER_KEYS.includes(key)) {
129
- const v = parseBool(rawValue);
130
- return v == null ? { ok: false, error: `${key} must be on/off` } : { ok: true, key: `layers.${key}`, value: v };
131
- }
132
- return { ok: false, error: `unknown setting: ${key}` };
133
- }
134
- }
135
-
136
- function parseBool(v) {
137
- if (v === true || v === false) return v;
138
- const s = String(v).toLowerCase();
139
- if (['on', 'true', '1', 'yes', 'show'].includes(s)) return true;
140
- if (['off', 'false', '0', 'no', 'hide'].includes(s)) return false;
141
- return null;
142
- }
@@ -1,250 +0,0 @@
1
- /**
2
- * lib/chat/context-compactor.mjs — the live bridge between the owned-loop's
3
- * message history and the deterministic context-continuation contract
4
- * (lib/chat/context-continuation.mjs, construct-6zga.1.10).
5
- *
6
- * The owned-loop engine keeps an ordered array of provider messages (the AI SDK
7
- * `messages[]`: the user turns it pushed plus the assistant/tool messages the model
8
- * produced). When a turn's reported context crosses the capability profile's
9
- * compaction trigger, this module maps that live history onto the contract's layer
10
- * inventory, runs compactContext, and — crucially — rebuilds a *provider-valid*
11
- * message array from the result. Provider validity is the constraint the pure
12
- * contract module does not own: the rebuilt array must begin with a user message,
13
- * keep tool-call/tool-result pairs intact, and never place two same-role turns
14
- * adjacently. The rebuild satisfies all three by collapsing the compacted prefix
15
- * into a single user "continuation" message (task + every user constraint verbatim,
16
- * plus one real summary of the elided assistant/tool work) followed by an
17
- * assistant-led verbatim suffix of the most recent turns.
18
- *
19
- * No silent loss: user text (task, constraints, approvals) is always carried
20
- * verbatim into the recap; only the model's own assistant reasoning and tool
21
- * results are summarized. When required state alone exceeds the budget the contract
22
- * returns a blocker and this module compacts nothing — it surfaces the blocker as a
23
- * user-visible notice instead.
24
- *
25
- * Pure except for the injected async `summarize`, so the rebuild logic is tested
26
- * without the AI SDK or a network call.
27
- */
28
-
29
- import { compactContext, validateContinuationPacket } from './context-continuation.mjs';
30
-
31
- function estimateTextTokens(text) {
32
- return Math.ceil(String(text || '').length / 4);
33
- }
34
-
35
- // AI SDK messages carry content either as a plain string (the user turns the
36
- // engine pushes) or as an array of typed parts (assistant/tool messages from the
37
- // model). Flatten both to text for token accounting and summarization, naming
38
- // tool calls and results so an elided run still reads as what it did.
39
-
40
- function partToText(part) {
41
- if (typeof part === 'string') return part;
42
- if (typeof part?.text === 'string') return part.text;
43
- if (part?.type === 'tool-call') return `[tool ${part.toolName || 'call'} ${safeJson(part.input ?? part.args)}]`;
44
- if (part?.type === 'tool-result') return `[result ${part.toolName || ''} ${safeJson(part.output ?? part.result)}]`.trim();
45
- if (typeof part?.content === 'string') return part.content;
46
- return '';
47
- }
48
-
49
- function safeJson(value) {
50
- if (value == null) return '';
51
- if (typeof value === 'string') return value;
52
- try { return JSON.stringify(value); } catch { return String(value); }
53
- }
54
-
55
- export function messageText(message) {
56
- if (typeof message?.content === 'string') return message.content;
57
- if (Array.isArray(message?.content)) return message.content.map(partToText).filter(Boolean).join('\n');
58
- return '';
59
- }
60
-
61
- function messageRole(message) {
62
- return message?.role || 'user';
63
- }
64
-
65
- // A segment is one turn-unit: a head message (user or assistant) plus any tool
66
- // messages that belong to it. Segmenting on tool boundaries is what lets the
67
- // rebuild keep every tool-call/tool-result pair together — a tool message never
68
- // becomes the head of a kept suffix, so the provider never sees an orphan result.
69
-
70
- export function segmentMessages(messages = []) {
71
- const segments = [];
72
- let current = null;
73
- messages.forEach((message, index) => {
74
- const role = messageRole(message);
75
- if (role === 'tool' && current) {
76
- current.indices.push(index);
77
- current.hasTools = true;
78
- return;
79
- }
80
- current = { firstIndex: index, indices: [index], head: role, hasTools: role === 'tool' };
81
- segments.push(current);
82
- });
83
- return segments;
84
- }
85
-
86
- // Map the live segments onto the contract inventory. The system prompt is a
87
- // reconstructible layer (re-derived each turn by the composer, referenced by
88
- // source id). The first user segment is the task packet and every other user
89
- // segment is a user constraint — both required, kept verbatim. Assistant segments
90
- // are compactible; ordering them newest-first means compactContext fills the budget
91
- // with the most recent turns and elides the oldest, so the kept suffix is the
92
- // recent contiguous tail.
93
-
94
- export function buildInventory({ systemText = '', segments = [], messages = [], estimateTokens = estimateTextTokens } = {}) {
95
- const layers = [];
96
- if (systemText) {
97
- layers.push({ id: 'static-instructions', kind: 'static-instructions', eligibility: 'reconstructible', sourceId: 'prompt:system', tokens: estimateTokens(systemText), content: systemText });
98
- }
99
-
100
- let seenUser = false;
101
- const required = [];
102
- const compactible = [];
103
- for (const segment of segments) {
104
- const text = segment.indices.map((i) => messageText(messages[i])).join('\n');
105
- const tokens = estimateTokens(text);
106
- if (segment.head === 'user') {
107
- const kind = seenUser ? 'user-constraints' : 'task-packet';
108
- seenUser = true;
109
- required.push({ id: `seg-${segment.firstIndex}`, kind, eligibility: 'required', sourceId: null, tokens, content: text, firstIndex: segment.firstIndex });
110
- } else {
111
- const kind = segment.hasTools ? 'tool-results' : 'conversation-summary';
112
- compactible.push({ id: `seg-${segment.firstIndex}`, kind, eligibility: 'compactible', sourceId: null, tokens, content: text, firstIndex: segment.firstIndex });
113
- }
114
- }
115
-
116
- compactible.sort((a, b) => b.firstIndex - a.firstIndex);
117
- return [...layers, ...required, ...compactible];
118
- }
119
-
120
- // Rebuild a provider-valid message array from the compaction packet. The cut index
121
- // is the earliest message belonging to a retained (recent) compactible segment,
122
- // nudged forward to land on an assistant message so the recap (a single user
123
- // message) is followed by an assistant turn. Everything before the cut collapses
124
- // into the recap: user text verbatim, assistant/tool work as the summary.
125
-
126
- export function rebuildMessages({ messages = [], segments = [], packet = null, summaryText = '' } = {}) {
127
- const dispositionByFirstIndex = new Map();
128
- for (const layer of packet?.layers || []) {
129
- const match = /^seg-(\d+)$/.exec(layer.id || '');
130
- if (match) dispositionByFirstIndex.set(Number(match[1]), layer.disposition);
131
- }
132
-
133
- const retainedFirstIndices = segments
134
- .filter((s) => s.head !== 'user' && dispositionByFirstIndex.get(s.firstIndex) === 'retained')
135
- .map((s) => s.firstIndex);
136
-
137
- const lastAssistantIndex = (() => {
138
- for (let i = messages.length - 1; i >= 0; i--) {
139
- if (messageRole(messages[i]) === 'assistant') return i;
140
- }
141
- return messages.length;
142
- })();
143
-
144
- let cut = retainedFirstIndices.length ? Math.min(...retainedFirstIndices) : lastAssistantIndex;
145
- while (cut < messages.length && messageRole(messages[cut]) !== 'assistant') cut++;
146
- if (cut >= messages.length) cut = lastAssistantIndex;
147
-
148
- const userVerbatim = [];
149
- for (let i = 0; i < cut; i++) {
150
- if (messageRole(messages[i]) === 'user') userVerbatim.push(messageText(messages[i]));
151
- }
152
-
153
- const recap = buildRecapText({ userVerbatim, summaryText, packet });
154
- return [{ role: 'user', content: recap }, ...messages.slice(cut)];
155
- }
156
-
157
- export function buildRecapText({ userVerbatim = [], summaryText = '', packet = null } = {}) {
158
- const elidedTokens = packet?.budget?.elidedTokens || 0;
159
- const lines = [
160
- '[Context continuation — earlier turns were compacted to fit the model context budget. Nothing required was dropped.]',
161
- '',
162
- '== Task & constraints (verbatim) ==',
163
- ...userVerbatim.filter(Boolean),
164
- ];
165
- if (summaryText) {
166
- lines.push('', `== Summary of prior assistant work & tool results (~${elidedTokens} tokens compacted) ==`, summaryText);
167
- }
168
- return lines.join('\n');
169
- }
170
-
171
- function fallbackSummary(elidedSegments, messages) {
172
- const count = elidedSegments.reduce((n, s) => n + s.indices.length, 0);
173
- const tail = elidedSegments.slice(-1).map((s) => messageText(messages[s.firstIndex]))[0] || '';
174
- const clipped = tail.length > 280 ? `${tail.slice(0, 277)}...` : tail;
175
- return `Compacted ${count} earlier message(s) without a model summary. Most recent elided turn began: ${clipped}`.trim();
176
- }
177
-
178
- export function shouldCompact({ contextTokens = 0, triggerTokens = null } = {}) {
179
- return Number.isFinite(triggerTokens) && triggerTokens > 0 && contextTokens >= triggerTokens;
180
- }
181
-
182
- /**
183
- * The orchestrator the engine calls once per turn. Returns the compaction outcome:
184
- * `compacted` with a rebuilt message array and a notice, a `blocker` with a notice
185
- * and no mutation when required state exceeds the budget, or an inert result when
186
- * there is no pressure or nothing safe to elide. The packet is always returned for
187
- * persistence and re-verification.
188
- */
189
- export async function maybeCompact({
190
- messages = [],
191
- systemText = '',
192
- triggerTokens = null,
193
- contextTokens = 0,
194
- summarize = null,
195
- estimateTokens = estimateTextTokens,
196
- } = {}) {
197
- if (!shouldCompact({ contextTokens, triggerTokens })) return { compacted: false, packet: null, notice: null, blocker: null };
198
-
199
- const segments = segmentMessages(messages);
200
- if (segments.length <= 2) return { compacted: false, packet: null, notice: null, blocker: null };
201
-
202
- const inventory = buildInventory({ systemText, segments, messages, estimateTokens });
203
- const packet = compactContext(inventory, { triggerTokens });
204
-
205
- if (packet.blocker) {
206
- const { requiredTokens, budgetTokens, overflowTokens } = packet.blocker;
207
- return {
208
- compacted: false,
209
- packet,
210
- blocker: packet.blocker,
211
- notice: `Context pressure: required context (~${requiredTokens} tokens) exceeds the model budget (~${budgetTokens}) by ~${overflowTokens}. Nothing was dropped — reduce scope or start a new session.`,
212
- };
213
- }
214
-
215
- const elidedSegments = segments.filter((s) => {
216
- const layer = packet.layers.find((l) => l.id === `seg-${s.firstIndex}`);
217
- return layer && layer.disposition === 'elided';
218
- });
219
- if (!elidedSegments.length) return { compacted: false, packet, notice: null, blocker: null };
220
-
221
- const elidedText = elidedSegments
222
- .sort((a, b) => a.firstIndex - b.firstIndex)
223
- .map((s) => s.indices.map((i) => messageText(messages[i])).join('\n'))
224
- .join('\n\n');
225
-
226
- let summaryText = '';
227
- if (typeof summarize === 'function') {
228
- try {
229
- const out = await summarize(elidedText, {
230
- segmentCount: elidedSegments.length,
231
- tokens: packet.budget.elidedTokens,
232
- });
233
- summaryText = typeof out === 'string' ? out.trim() : '';
234
- } catch { summaryText = ''; }
235
- }
236
- if (!summaryText) summaryText = fallbackSummary(elidedSegments, messages);
237
-
238
- const newMessages = rebuildMessages({ messages, segments, packet, summaryText });
239
-
240
- const elidedMessageCount = elidedSegments.reduce((n, s) => n + s.indices.length, 0);
241
- return {
242
- compacted: true,
243
- messages: newMessages,
244
- packet,
245
- blocker: null,
246
- notice: `Context compacted: summarized ${elidedMessageCount} earlier message(s) (~${packet.budget.elidedTokens} tokens) to stay within the model's ~${triggerTokens}-token budget. Task, constraints, and recent turns kept verbatim.`,
247
- };
248
- }
249
-
250
- export { estimateTextTokens, validateContinuationPacket };