@geraldmaron/construct 1.1.1 → 1.2.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 (426) hide show
  1. package/README.md +30 -19
  2. package/bin/construct +487 -87
  3. package/bin/construct-postinstall.mjs +30 -2
  4. package/examples/distribution/README.md +42 -0
  5. package/examples/distribution/manifest.json +53 -0
  6. package/examples/distribution/sources/adr.md +84 -0
  7. package/examples/distribution/sources/deck-one-pager.md +65 -0
  8. package/examples/distribution/sources/prd-platform.md +161 -0
  9. package/examples/distribution/sources/research-brief.md +80 -0
  10. package/examples/distribution/sources/rfc-platform.md +82 -0
  11. package/examples/distribution/sources/runbook.md +103 -0
  12. package/examples/distribution/sources/strategy.md +88 -0
  13. package/lib/adapters-sync.mjs +67 -0
  14. package/lib/artifact-gate-notice.mjs +38 -0
  15. package/lib/artifact-manifest.mjs +258 -0
  16. package/lib/artifact-release-gate.mjs +171 -0
  17. package/lib/artifact-reviewers.mjs +68 -0
  18. package/lib/artifact-type-from-path.mjs +79 -0
  19. package/lib/artifact-workflow.mjs +252 -0
  20. package/lib/audit-skills.mjs +4 -0
  21. package/lib/audit-specialists.mjs +285 -0
  22. package/lib/audit-trail.mjs +1 -1
  23. package/lib/auto-docs.mjs +90 -19
  24. package/lib/beads/drift.mjs +27 -6
  25. package/lib/beads-client.mjs +49 -121
  26. package/lib/beads-optimistic.mjs +5 -12
  27. package/lib/brand-fonts.mjs +93 -0
  28. package/lib/brand-prose.mjs +214 -0
  29. package/lib/brand-tokens.mjs +92 -0
  30. package/lib/bridges/copilot-proxy.mjs +13 -26
  31. package/lib/capability-ledger.mjs +156 -0
  32. package/lib/certification/artifact-fixtures.mjs +132 -0
  33. package/lib/certification/artifact-gates.mjs +63 -0
  34. package/lib/certification/artifact-provenance.mjs +97 -0
  35. package/lib/certification/canonical-scenarios.mjs +78 -0
  36. package/lib/certification/cli.mjs +191 -0
  37. package/lib/certification/dashboard-api.mjs +71 -0
  38. package/lib/certification/demo-parity.mjs +116 -0
  39. package/lib/certification/document-io-fixtures.mjs +246 -0
  40. package/lib/certification/document-workflow.mjs +97 -0
  41. package/lib/certification/eval-bridge.mjs +77 -0
  42. package/lib/certification/model-routing.mjs +149 -0
  43. package/lib/certification/prompt-budget.mjs +119 -0
  44. package/lib/certification/rc-gate.mjs +206 -0
  45. package/lib/certification/real-llm-scenarios.mjs +303 -0
  46. package/lib/certification/role-cards.mjs +113 -0
  47. package/lib/certification/role-overlays.mjs +117 -0
  48. package/lib/certification/run.mjs +122 -0
  49. package/lib/certification/runner.mjs +323 -0
  50. package/lib/certification/scenarios.mjs +51 -0
  51. package/lib/certification/skill-inventory.mjs +289 -0
  52. package/lib/certification/skill-scenarios.mjs +147 -0
  53. package/lib/certification/specialist-contracts.mjs +85 -0
  54. package/lib/certification/specialist-scenarios.mjs +175 -0
  55. package/lib/certification/stale-impact.mjs +146 -0
  56. package/lib/certification/status.mjs +252 -0
  57. package/lib/certification/store.mjs +77 -0
  58. package/lib/chat/cli.mjs +333 -0
  59. package/lib/chat/command-suggest.mjs +161 -0
  60. package/lib/chat/commands.mjs +215 -0
  61. package/lib/chat/config.mjs +142 -0
  62. package/lib/chat/context-compactor.mjs +250 -0
  63. package/lib/chat/context-continuation.mjs +253 -0
  64. package/lib/chat/continuation-source.mjs +58 -0
  65. package/lib/chat/demo-guide.mjs +61 -0
  66. package/lib/chat/design-tokens.mjs +91 -0
  67. package/lib/chat/desktop-binary.mjs +79 -0
  68. package/lib/chat/desktop-build.mjs +130 -0
  69. package/lib/chat/desktop-launcher.mjs +133 -0
  70. package/lib/chat/evidence.mjs +145 -0
  71. package/lib/chat/export.mjs +74 -0
  72. package/lib/chat/harness/driver.mjs +91 -0
  73. package/lib/chat/list-picker.mjs +112 -0
  74. package/lib/chat/model-picker.mjs +356 -0
  75. package/lib/chat/openrouter-fallback.mjs +151 -0
  76. package/lib/chat/permission-prompt.mjs +33 -0
  77. package/lib/chat/picker-catalog.mjs +45 -0
  78. package/lib/chat/policy-telemetry.mjs +34 -0
  79. package/lib/chat/present.mjs +246 -0
  80. package/lib/chat/session-context.mjs +39 -0
  81. package/lib/chat/session-persist.mjs +73 -0
  82. package/lib/chat/session-restore.mjs +71 -0
  83. package/lib/chat/session-settings.mjs +53 -0
  84. package/lib/chat/system-prompt.mjs +52 -0
  85. package/lib/chat/transparency.mjs +93 -0
  86. package/lib/chat/tui/color-scheme.mjs +42 -0
  87. package/lib/chat/tui/markdown.mjs +123 -0
  88. package/lib/chat/tui/presentation.mjs +100 -0
  89. package/lib/chat/tui/render.mjs +500 -0
  90. package/lib/chat/tui/turn-block.mjs +284 -0
  91. package/lib/chat/tui/turn-present.mjs +18 -0
  92. package/lib/chat/tui/turn-state.mjs +88 -0
  93. package/lib/chat/tui/usage.mjs +122 -0
  94. package/lib/chat/web-commands.mjs +146 -0
  95. package/lib/chat/web-launcher.mjs +63 -0
  96. package/lib/chat/web-picker-keys.mjs +46 -0
  97. package/lib/chat/web-session.mjs +159 -0
  98. package/lib/cli-commands.mjs +146 -15
  99. package/lib/cli-service-inventory.mjs +42 -0
  100. package/lib/comment-lint.mjs +6 -4
  101. package/lib/config/intake-policy.mjs +209 -0
  102. package/lib/config/project-config.mjs +11 -2
  103. package/lib/config/schema.mjs +86 -6
  104. package/lib/config/source-targets.mjs +311 -0
  105. package/lib/contract-schemas/decision.json +50 -0
  106. package/lib/contract-schemas/implementation.json +51 -0
  107. package/lib/contract-schemas/review-report.json +32 -0
  108. package/lib/contract-schemas/test-report.json +43 -0
  109. package/lib/contracts/construct-handoff.mjs +60 -0
  110. package/lib/contracts/validate.mjs +32 -3
  111. package/lib/contracts/violation-log.mjs +58 -5
  112. package/lib/dashboard-demo.mjs +71 -0
  113. package/lib/dashboard-static.mjs +19 -5
  114. package/lib/decisions/registry.mjs +5 -3
  115. package/lib/deck-export-pptx.mjs +1152 -0
  116. package/lib/demo-recording.mjs +142 -0
  117. package/lib/demo-script.mjs +114 -0
  118. package/lib/demo-surface.mjs +249 -0
  119. package/lib/demo.mjs +598 -140
  120. package/lib/diagram-export.mjs +192 -0
  121. package/lib/diagram.mjs +13 -11
  122. package/lib/docs-verify.mjs +25 -7
  123. package/lib/doctor/index.mjs +3 -1
  124. package/lib/doctor/project-adapters.mjs +44 -0
  125. package/lib/doctor/source-checkout.mjs +16 -0
  126. package/lib/doctor/watchers/cx-budget.mjs +98 -0
  127. package/lib/doctor/watchers/graph-staleness.mjs +52 -0
  128. package/lib/doctor/watchers/process-pressure.mjs +4 -3
  129. package/lib/document-export.mjs +384 -38
  130. package/lib/document-extract.mjs +44 -4
  131. package/lib/document-ingest.mjs +29 -20
  132. package/lib/embed/auto-sources.mjs +44 -0
  133. package/lib/embed/cli.mjs +4 -3
  134. package/lib/embed/daemon.mjs +18 -46
  135. package/lib/embed/demand-fetch.mjs +50 -19
  136. package/lib/embed/inbox.mjs +6 -10
  137. package/lib/embed/worker.mjs +4 -3
  138. package/lib/embedded-contract/index.mjs +11 -0
  139. package/lib/embedded-contract/model-resolve.mjs +10 -9
  140. package/lib/env-config.mjs +26 -0
  141. package/lib/evals/dataset.mjs +137 -0
  142. package/lib/evals/gates.mjs +175 -0
  143. package/lib/export-branding.mjs +32 -0
  144. package/lib/graph/build-co-change.mjs +54 -0
  145. package/lib/graph/build-from-registry.mjs +136 -0
  146. package/lib/graph/build-import-graph.mjs +153 -0
  147. package/lib/graph/cli.mjs +110 -0
  148. package/lib/graph/impact-cli.mjs +89 -0
  149. package/lib/graph/impact.mjs +108 -0
  150. package/lib/graph/staleness.mjs +44 -0
  151. package/lib/graph/store.mjs +172 -0
  152. package/lib/health-check.mjs +90 -76
  153. package/lib/hooks/artifact-release-gate.mjs +43 -0
  154. package/lib/hooks/brand-prose-lint.mjs +38 -0
  155. package/lib/hooks/graph-impact-advisory.mjs +62 -0
  156. package/lib/hooks/session-optimize.mjs +84 -207
  157. package/lib/hooks/session-start.mjs +18 -16
  158. package/lib/host-disposition.mjs +7 -0
  159. package/lib/improvement/cli.mjs +189 -0
  160. package/lib/improvement/controller.mjs +137 -0
  161. package/lib/improvement/proposal.mjs +120 -0
  162. package/lib/improvement/specialist-loop.mjs +192 -0
  163. package/lib/improvement/store.mjs +89 -0
  164. package/lib/improvement/surface.mjs +219 -0
  165. package/lib/ingest-tooling.mjs +97 -0
  166. package/lib/init/doc-lanes.mjs +165 -0
  167. package/lib/init-docs.mjs +31 -178
  168. package/lib/init-unified.mjs +50 -119
  169. package/lib/init-update-guide.mjs +102 -0
  170. package/lib/init-update.mjs +32 -1
  171. package/lib/init.mjs +8 -0
  172. package/lib/install/desktop-binary-download.mjs +85 -0
  173. package/lib/install/legacy-global-cleanup.mjs +189 -0
  174. package/lib/intake/constants.mjs +29 -0
  175. package/lib/intake/daemon.mjs +2 -0
  176. package/lib/intake/git-queue.mjs +9 -8
  177. package/lib/intake/intake-config.mjs +52 -105
  178. package/lib/intake/legacy-paths.mjs +5 -0
  179. package/lib/intake/queue.mjs +2 -1
  180. package/lib/intake/session-prelude.mjs +90 -1
  181. package/lib/libreoffice-export.mjs +97 -0
  182. package/lib/logging/rotate.mjs +9 -0
  183. package/lib/maintenance/docker-reclaim.mjs +206 -0
  184. package/lib/mcp/external-schema-cost.mjs +74 -0
  185. package/lib/mcp/server.mjs +103 -10
  186. package/lib/mcp/stdio-mcp-probe.mjs +188 -0
  187. package/lib/mcp/tool-budget.mjs +55 -4
  188. package/lib/mcp/tools/document.mjs +18 -4
  189. package/lib/mcp/tools/embedded-contract.mjs +14 -0
  190. package/lib/mcp/tools/skills.mjs +32 -3
  191. package/lib/mcp/tools/workflow.mjs +13 -1
  192. package/lib/model-free-selector.mjs +18 -0
  193. package/lib/model-registry.mjs +13 -229
  194. package/lib/model-router.mjs +373 -92
  195. package/lib/models/behavior-matrix.mjs +289 -0
  196. package/lib/models/catalog.mjs +209 -0
  197. package/lib/models/execution-capability-profile.mjs +196 -0
  198. package/lib/models/execution-policy.mjs +307 -0
  199. package/lib/models/provider-poll.mjs +383 -0
  200. package/lib/npm-spawn-env.mjs +17 -0
  201. package/lib/ollama/installed-models.mjs +129 -0
  202. package/lib/oracle/actions.mjs +187 -0
  203. package/lib/oracle/artifact-gate.mjs +99 -0
  204. package/lib/oracle/cli.mjs +204 -0
  205. package/lib/oracle/daemon-entry.mjs +14 -0
  206. package/lib/oracle/dispatch.mjs +81 -0
  207. package/lib/oracle/execute.mjs +143 -0
  208. package/lib/oracle/gaps.mjs +76 -0
  209. package/lib/oracle/index.mjs +84 -0
  210. package/lib/oracle/issues.mjs +164 -0
  211. package/lib/oracle/org-graph.mjs +170 -0
  212. package/lib/oracle/policy.mjs +80 -0
  213. package/lib/oracle/read-model.mjs +398 -0
  214. package/lib/oracle/reconcile.mjs +191 -0
  215. package/lib/oracle/routing.mjs +89 -0
  216. package/lib/oracle/synthesize.mjs +384 -0
  217. package/lib/oracle/verdicts.mjs +51 -0
  218. package/lib/orchestration/worker.mjs +88 -27
  219. package/lib/orchestration-policy.mjs +50 -0
  220. package/lib/parity.mjs +96 -2
  221. package/lib/playwright-demo.mjs +254 -0
  222. package/lib/project-init-shared.mjs +4 -1
  223. package/lib/prompt-composer.js +7 -3
  224. package/lib/prompt-validation-contract.mjs +24 -0
  225. package/lib/provider-capabilities.js +57 -11
  226. package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
  227. package/lib/providers/contract/adapters/git/index.mjs +115 -0
  228. package/lib/providers/contract/adapters/github/index.mjs +166 -0
  229. package/lib/providers/contract/adapters/jira/index.mjs +187 -0
  230. package/lib/providers/contract/adapters/slack/index.mjs +175 -0
  231. package/lib/providers/contract/contract-tests.mjs +57 -0
  232. package/lib/providers/contract/errors.mjs +48 -0
  233. package/lib/providers/contract/interface.mjs +50 -0
  234. package/lib/providers/contract/registry.mjs +102 -0
  235. package/lib/providers/copilot-auth.mjs +297 -0
  236. package/lib/providers/credential-bootstrap.mjs +178 -0
  237. package/lib/providers/credential-catalog.mjs +46 -0
  238. package/lib/providers/credential-sources.mjs +63 -0
  239. package/lib/providers/creds.mjs +5 -2
  240. package/lib/providers/op-run.mjs +59 -0
  241. package/lib/providers/secret-resolver.mjs +159 -0
  242. package/lib/publish-template.mjs +163 -0
  243. package/lib/publish-tooling.mjs +119 -0
  244. package/lib/publish.mjs +305 -0
  245. package/lib/registry/cli.mjs +82 -0
  246. package/lib/registry/consolidation.mjs +147 -0
  247. package/lib/registry/generate-docs.mjs +75 -0
  248. package/lib/registry/skill-verification.mjs +57 -0
  249. package/lib/registry/surface-map.mjs +76 -0
  250. package/lib/registry/validate.mjs +135 -0
  251. package/lib/resources/budget.mjs +82 -0
  252. package/lib/resources/process-budget.mjs +45 -0
  253. package/lib/rules-delivery.mjs +9 -2
  254. package/lib/rules-read.mjs +26 -0
  255. package/lib/runtime-env.mjs +23 -0
  256. package/lib/runtime-pressure.mjs +51 -7
  257. package/lib/schema-infer.mjs +13 -2
  258. package/lib/server/chat-loop.mjs +622 -0
  259. package/lib/server/demo-preview.mjs +63 -0
  260. package/lib/server/index.mjs +259 -9
  261. package/lib/server/rate-limit.mjs +5 -3
  262. package/lib/server/webhook.mjs +3 -2
  263. package/lib/service-manager.mjs +60 -8
  264. package/lib/setup.mjs +70 -7
  265. package/lib/specialists/prompt-schema.mjs +19 -10
  266. package/lib/specialists/roster.mjs +43 -0
  267. package/lib/specialists/scaffold.mjs +56 -0
  268. package/lib/storage/backend.mjs +6 -6
  269. package/lib/storage/hybrid-query.mjs +7 -4
  270. package/lib/storage/state-source.mjs +5 -5
  271. package/lib/storage/sync.mjs +2 -3
  272. package/lib/telemetry/rule-calls.mjs +52 -0
  273. package/lib/template-registry.mjs +2 -2
  274. package/lib/templates/visual-requirements.mjs +27 -51
  275. package/lib/test-corpus-inventory.mjs +313 -0
  276. package/lib/uninstall/uninstall.mjs +19 -7
  277. package/lib/update.mjs +12 -0
  278. package/lib/upgrade.mjs +14 -0
  279. package/lib/wireframe.mjs +20 -14
  280. package/lib/worker/run.mjs +17 -4
  281. package/lib/worker/trace.mjs +11 -3
  282. package/package.json +28 -14
  283. package/personas/construct.md +2 -2
  284. package/platforms/claude/settings.template.json +36 -0
  285. package/rules/common/patterns.md +1 -1
  286. package/rules/common/release-gates.md +4 -3
  287. package/scripts/sync-specialists.mjs +41 -28
  288. package/skills/devops/data-engineering.md +1 -1
  289. package/skills/docs/adr-workflow.md +1 -0
  290. package/skills/docs/backlog-proposal-workflow.md +1 -0
  291. package/skills/docs/codebase-research-workflow.md +40 -0
  292. package/skills/docs/customer-profile-workflow.md +1 -0
  293. package/skills/docs/document-ingest-workflow.md +1 -0
  294. package/skills/docs/evidence-ingest-workflow.md +1 -0
  295. package/skills/docs/init-docs.md +2 -2
  296. package/skills/docs/init-project.md +7 -2
  297. package/skills/docs/prd-workflow.md +24 -1
  298. package/skills/docs/prfaq-workflow.md +1 -0
  299. package/skills/docs/product-intelligence-workflow.md +1 -0
  300. package/skills/docs/product-signal-workflow.md +1 -0
  301. package/skills/docs/research-workflow.md +54 -37
  302. package/skills/docs/runbook-workflow.md +1 -0
  303. package/skills/docs/strategy-workflow.md +1 -0
  304. package/skills/docs/user-research-workflow.md +40 -0
  305. package/skills/operating/orchestration-reference.md +1 -1
  306. package/skills/roles/architect.md +5 -0
  307. package/skills/roles/operator.docs.md +4 -0
  308. package/skills/routing.md +4 -2
  309. package/specialists/artifact-manifest.json +489 -0
  310. package/specialists/artifact-manifest.schema.json +83 -0
  311. package/specialists/audit-enrichments.json +454 -0
  312. package/specialists/contracts.json +37 -25
  313. package/specialists/prompts/_shared/validation-contract.md +26 -0
  314. package/specialists/prompts/cx-accessibility.md +21 -2
  315. package/specialists/prompts/cx-ai-engineer.md +24 -1
  316. package/specialists/prompts/cx-architect.md +4 -1
  317. package/specialists/prompts/cx-business-strategist.md +23 -2
  318. package/specialists/prompts/cx-data-analyst.md +22 -1
  319. package/specialists/prompts/cx-data-engineer.md +23 -2
  320. package/specialists/prompts/cx-debugger.md +21 -2
  321. package/specialists/prompts/cx-designer.md +26 -3
  322. package/specialists/prompts/cx-devil-advocate.md +19 -2
  323. package/specialists/prompts/cx-docs-keeper.md +28 -1
  324. package/specialists/prompts/cx-engineer.md +22 -1
  325. package/specialists/prompts/cx-evaluator.md +18 -1
  326. package/specialists/prompts/cx-explorer.md +21 -2
  327. package/specialists/prompts/cx-legal-compliance.md +21 -2
  328. package/specialists/prompts/cx-operations.md +21 -2
  329. package/specialists/prompts/cx-oracle.md +94 -0
  330. package/specialists/prompts/cx-orchestrator.md +20 -1
  331. package/specialists/prompts/cx-platform-engineer.md +25 -2
  332. package/specialists/prompts/cx-product-manager.md +32 -1
  333. package/specialists/prompts/cx-qa.md +24 -2
  334. package/specialists/prompts/cx-rd-lead.md +23 -2
  335. package/specialists/prompts/cx-release-manager.md +23 -2
  336. package/specialists/prompts/cx-researcher.md +22 -2
  337. package/specialists/prompts/cx-reviewer.md +18 -1
  338. package/specialists/prompts/cx-security.md +22 -2
  339. package/specialists/prompts/cx-sre.md +30 -3
  340. package/specialists/prompts/cx-test-automation.md +7 -1
  341. package/specialists/prompts/cx-trace-reviewer.md +22 -3
  342. package/specialists/prompts/cx-ux-researcher.md +21 -2
  343. package/specialists/registry.json +52 -173
  344. package/specialists/tone-profiles.json +42 -0
  345. package/templates/demos/playwright/demo-recording.config.mjs +47 -0
  346. package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
  347. package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
  348. package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
  349. package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
  350. package/templates/demos/tapes/resource-guard-rails.tape +49 -0
  351. package/templates/demos/vhs/construct-cockpit.json +24 -0
  352. package/templates/distribution/construct-brand.typ +446 -0
  353. package/templates/distribution/construct-decision.typ +38 -0
  354. package/templates/distribution/construct-deck.html +95 -0
  355. package/templates/distribution/construct-pdf.typ +38 -0
  356. package/templates/distribution/construct-prd.typ +38 -0
  357. package/templates/distribution/construct-reference.docx +0 -0
  358. package/templates/distribution/construct-research.typ +38 -0
  359. package/templates/distribution/construct-web.html +92 -0
  360. package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
  361. package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
  362. package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
  363. package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
  364. package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
  365. package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
  366. package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
  367. package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
  368. package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
  369. package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
  370. package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
  371. package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
  372. package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
  373. package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
  374. package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
  375. package/templates/distribution/fonts/README.md +36 -0
  376. package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
  377. package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
  378. package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
  379. package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
  380. package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
  381. package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
  382. package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
  383. package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
  384. package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
  385. package/templates/distribution/icons/activity.svg +15 -0
  386. package/templates/distribution/icons/alert-triangle.svg +17 -0
  387. package/templates/distribution/icons/book-open.svg +16 -0
  388. package/templates/distribution/icons/bot.svg +20 -0
  389. package/templates/distribution/icons/brain.svg +22 -0
  390. package/templates/distribution/icons/circle-check.svg +16 -0
  391. package/templates/distribution/icons/clipboard-check.svg +17 -0
  392. package/templates/distribution/icons/cpu.svg +28 -0
  393. package/templates/distribution/icons/database.svg +17 -0
  394. package/templates/distribution/icons/eye.svg +16 -0
  395. package/templates/distribution/icons/file-text.svg +19 -0
  396. package/templates/distribution/icons/gauge.svg +16 -0
  397. package/templates/distribution/icons/git-branch.svg +17 -0
  398. package/templates/distribution/icons/key.svg +17 -0
  399. package/templates/distribution/icons/layers.svg +17 -0
  400. package/templates/distribution/icons/list-checks.svg +19 -0
  401. package/templates/distribution/icons/lock.svg +16 -0
  402. package/templates/distribution/icons/message-square.svg +15 -0
  403. package/templates/distribution/icons/network.svg +19 -0
  404. package/templates/distribution/icons/route.svg +17 -0
  405. package/templates/distribution/icons/scale.svg +19 -0
  406. package/templates/distribution/icons/search.svg +16 -0
  407. package/templates/distribution/icons/send.svg +16 -0
  408. package/templates/distribution/icons/server.svg +18 -0
  409. package/templates/distribution/icons/shield-check.svg +16 -0
  410. package/templates/distribution/icons/users.svg +18 -0
  411. package/templates/distribution/icons/webhook.svg +17 -0
  412. package/templates/distribution/icons/workflow.svg +17 -0
  413. package/templates/distribution/icons/wrench.svg +15 -0
  414. package/templates/distribution/run.mjs +18 -1
  415. package/templates/docs/adr.md +7 -0
  416. package/templates/docs/construct_guide.md +8 -8
  417. package/templates/docs/customer-profile.md +4 -0
  418. package/templates/docs/one-pager.md +4 -0
  419. package/templates/docs/postmortem.md +31 -0
  420. package/templates/docs/prd-platform.md +19 -0
  421. package/templates/docs/prd.md +15 -0
  422. package/templates/docs/prfaq.md +7 -0
  423. package/templates/docs/qa-strategy.md +103 -0
  424. package/templates/docs/research-brief.md +8 -0
  425. package/templates/docs/rfc-platform.md +12 -0
  426. package/templates/docs/test-plan.md +7 -0
@@ -0,0 +1,250 @@
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 };
@@ -0,0 +1,253 @@
1
+ /**
2
+ * lib/chat/context-continuation.mjs — the deterministic context-continuation
3
+ * contract (construct-6zga.1.9).
4
+ *
5
+ * A chat session's context is an ordered set of layers — static instructions,
6
+ * role guidance, the task packet, validated evidence, tool results, the
7
+ * conversation summary, optional learned patterns, plus the protected state
8
+ * (user constraints, approvals, unresolved blockers). When context pressure
9
+ * crosses the capability profile's compaction trigger (the execution policy's
10
+ * continuation budget, never a model-name fixed limit), the session must compact
11
+ * without silent loss: it produces a structured continuation packet that retains
12
+ * required state verbatim, references reconstructible layers by source id for
13
+ * re-derivation, elides compactible layers with a truthful marker, and — when the
14
+ * required state alone exceeds the budget — yields a user-visible blocker instead
15
+ * of dropping anything. The packet carries source ids and token accounting so a
16
+ * reader can re-verify it, and rehydrateContinuation feeds the same layer set back
17
+ * through the composer on every surface. Reference shape:
18
+ * schemas/continuation-packet.schema.json.
19
+ */
20
+
21
+ export const CONTINUATION_PACKET_SCHEMA_VERSION = 1;
22
+
23
+ export const CONTEXT_LAYER_KINDS = Object.freeze([
24
+ 'static-instructions',
25
+ 'role-guidance',
26
+ 'task-packet',
27
+ 'validated-evidence',
28
+ 'tool-results',
29
+ 'conversation-summary',
30
+ 'learned-patterns',
31
+ 'user-constraints',
32
+ 'approvals',
33
+ 'blockers',
34
+ ]);
35
+
36
+ export const COMPACTION_ELIGIBILITY = Object.freeze(['required', 'reconstructible', 'compactible']);
37
+
38
+ export const LAYER_DISPOSITIONS = Object.freeze(['retained', 'referenced', 'elided']);
39
+
40
+ // The protected layers carry state the loop must never silently drop: user
41
+ // constraints, approvals, the current task packet, evidence provenance, and
42
+ // unresolved blockers. Reconstructible layers can be re-derived from a durable
43
+ // source by id, so the packet references rather than stores them. Compactible
44
+ // layers are conversation history that compacts lossily but is never silently
45
+ // lost — an elided one keeps a truthful marker of what was removed.
46
+
47
+ const DEFAULT_ELIGIBILITY = Object.freeze({
48
+ 'user-constraints': 'required',
49
+ approvals: 'required',
50
+ 'task-packet': 'required',
51
+ 'validated-evidence': 'required',
52
+ blockers: 'required',
53
+ 'static-instructions': 'reconstructible',
54
+ 'role-guidance': 'reconstructible',
55
+ 'learned-patterns': 'reconstructible',
56
+ 'conversation-summary': 'compactible',
57
+ 'tool-results': 'compactible',
58
+ });
59
+
60
+ function estimateTextTokens(text) {
61
+ return Math.ceil(String(text || '').length / 4);
62
+ }
63
+
64
+ function sumTokens(layers) {
65
+ return layers.reduce((total, l) => total + (Number.isFinite(l.tokens) ? l.tokens : 0), 0);
66
+ }
67
+
68
+ /**
69
+ * Normalize a raw layer set into a deterministic inventory: assign each layer an
70
+ * id, a known kind, its compaction eligibility (explicit or kind-defaulted), and a
71
+ * token count. Order is preserved so callers control retention priority.
72
+ */
73
+ export function classifyContextLayers(layers = [], { estimateTokens = estimateTextTokens } = {}) {
74
+ const list = Array.isArray(layers) ? layers : [];
75
+ return list.map((raw, index) => {
76
+ const kind = CONTEXT_LAYER_KINDS.includes(raw?.kind) ? raw.kind : 'conversation-summary';
77
+ const eligibility = COMPACTION_ELIGIBILITY.includes(raw?.eligibility)
78
+ ? raw.eligibility
79
+ : (DEFAULT_ELIGIBILITY[kind] || 'compactible');
80
+ const content = typeof raw?.content === 'string' ? raw.content : '';
81
+ const tokens = Number.isFinite(raw?.tokens) ? raw.tokens : estimateTokens(content);
82
+ return {
83
+ id: raw?.id || `${kind}-${index}`,
84
+ kind,
85
+ eligibility,
86
+ sourceId: raw?.sourceId ?? null,
87
+ tokens,
88
+ content,
89
+ };
90
+ });
91
+ }
92
+
93
+ function retainedLayer(layer) {
94
+ return { id: layer.id, kind: layer.kind, eligibility: layer.eligibility, disposition: 'retained', tokens: layer.tokens, sourceId: layer.sourceId, content: layer.content };
95
+ }
96
+
97
+ function referencedLayer(layer) {
98
+ return { id: layer.id, kind: layer.kind, eligibility: layer.eligibility, disposition: 'referenced', tokens: layer.tokens, sourceId: layer.sourceId, content: null };
99
+ }
100
+
101
+ function elidedLayer(layer) {
102
+ return { id: layer.id, kind: layer.kind, eligibility: layer.eligibility, disposition: 'elided', tokens: layer.tokens, sourceId: layer.sourceId, content: null };
103
+ }
104
+
105
+ /**
106
+ * Compact a classified inventory to the continuation budget. Required layers are
107
+ * retained verbatim; a reconstructible layer with a source id is referenced for
108
+ * re-derivation (and costs no budget), while one without a source id falls back to
109
+ * the compactible pool so it is never lost. Compactible layers fill the remaining
110
+ * budget in order and are elided with a marker once it is spent. When the required
111
+ * state alone exceeds the budget the result is a truthful blocker — nothing is
112
+ * dropped to make room for it.
113
+ */
114
+ export function compactContext(inventory = [], { triggerTokens = null } = {}) {
115
+ const layers = classifyContextLayers(inventory);
116
+ const required = layers.filter((l) => l.eligibility === 'required');
117
+ const reconstructible = layers.filter((l) => l.eligibility === 'reconstructible');
118
+ const compactible = layers.filter((l) => l.eligibility === 'compactible');
119
+
120
+ const requiredTokens = sumTokens(required);
121
+ const budget = Number.isFinite(triggerTokens) && triggerTokens > 0 ? triggerTokens : Infinity;
122
+
123
+ const referenced = [];
124
+ const pressurePool = [...compactible];
125
+ for (const layer of reconstructible) {
126
+ if (layer.sourceId) referenced.push(referencedLayer(layer));
127
+ else pressurePool.push(layer);
128
+ }
129
+
130
+ const blocked = requiredTokens > budget;
131
+ const blocker = blocked
132
+ ? { reason: 'required-state-exceeds-budget', requiredTokens, budgetTokens: budget, overflowTokens: requiredTokens - budget }
133
+ : null;
134
+
135
+ const retainedCompactible = [];
136
+ const elided = [];
137
+ let used = 0;
138
+ const remaining = blocked ? 0 : budget - requiredTokens;
139
+ for (const layer of pressurePool) {
140
+ if (!blocked && used + layer.tokens <= remaining) {
141
+ retainedCompactible.push(retainedLayer(layer));
142
+ used += layer.tokens;
143
+ } else {
144
+ elided.push(elidedLayer(layer));
145
+ }
146
+ }
147
+
148
+ const packetLayers = [
149
+ ...required.map(retainedLayer),
150
+ ...referenced,
151
+ ...retainedCompactible,
152
+ ...elided,
153
+ ];
154
+
155
+ return buildContinuationPacket({
156
+ layers: packetLayers,
157
+ requiredState: required.map((l) => ({ id: l.id, kind: l.kind, sourceId: l.sourceId, tokens: l.tokens })),
158
+ budget,
159
+ requiredTokens,
160
+ blocker,
161
+ });
162
+ }
163
+
164
+ /**
165
+ * Assemble a structured, JSON-serializable continuation packet from a compaction
166
+ * result. Token accounting balances the retained, referenced, and elided layers
167
+ * against the budget so the packet is independently re-verifiable.
168
+ */
169
+ export function buildContinuationPacket({ layers = [], requiredState = [], budget = Infinity, requiredTokens = 0, blocker = null } = {}) {
170
+ const retainedTokens = sumTokens(layers.filter((l) => l.disposition === 'retained'));
171
+ const elidedTokens = sumTokens(layers.filter((l) => l.disposition === 'elided'));
172
+ const referencedCount = layers.filter((l) => l.disposition === 'referenced').length;
173
+
174
+ return {
175
+ schemaVersion: CONTINUATION_PACKET_SCHEMA_VERSION,
176
+ budget: {
177
+ triggerTokens: Number.isFinite(budget) ? budget : null,
178
+ requiredTokens,
179
+ retainedTokens,
180
+ elidedTokens,
181
+ referencedCount,
182
+ },
183
+ layers,
184
+ requiredState,
185
+ blocker,
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Re-derive the ordered layer set a composer needs to resume a session. Required
191
+ * and retained layers return their stored content; a referenced layer is re-derived
192
+ * through resolveSource(sourceId) so reconstructible context returns intact; an
193
+ * elided layer returns a truthful marker of what was removed. The same call backs
194
+ * terminal, web, export, and resume — one rehydration path, no surface drift.
195
+ */
196
+ export function rehydrateContinuation(packet, { resolveSource = null } = {}) {
197
+ const layers = Array.isArray(packet?.layers) ? packet.layers : [];
198
+ return layers.map((layer) => {
199
+ if (layer.disposition === 'referenced') {
200
+ const content = typeof resolveSource === 'function' ? resolveSource(layer.sourceId, layer) : null;
201
+ return { id: layer.id, kind: layer.kind, content: typeof content === 'string' ? content : null, resolved: typeof content === 'string', sourceId: layer.sourceId };
202
+ }
203
+ if (layer.disposition === 'elided') {
204
+ return { id: layer.id, kind: layer.kind, content: null, elided: true, tokens: layer.tokens, sourceId: layer.sourceId };
205
+ }
206
+ return { id: layer.id, kind: layer.kind, content: layer.content ?? '' };
207
+ });
208
+ }
209
+
210
+ /**
211
+ * Read the continuation budget from a compiled execution policy
212
+ * (lib/models/execution-policy.mjs). Capability-profile driven, never a model-name
213
+ * fixed limit (construct-6zga.1.2 AC4).
214
+ */
215
+ export function continuationBudgetFromPolicy(policy) {
216
+ const c = policy && typeof policy === 'object' ? policy.continuation : null;
217
+ return {
218
+ triggerTokens: Number.isFinite(c?.compactionTriggerTokens) ? c.compactionTriggerTokens : null,
219
+ triggerRatio: Number.isFinite(c?.compactionTriggerRatio) ? c.compactionTriggerRatio : null,
220
+ };
221
+ }
222
+
223
+ /**
224
+ * Hand-rolled validator (no ajv — Construct stays dependency-free at startup).
225
+ * Beyond shape, it enforces the no-silent-loss invariant: every required layer is
226
+ * retained unless the packet carries a blocker.
227
+ */
228
+ export function validateContinuationPacket(packet) {
229
+ const errors = [];
230
+ if (!packet || typeof packet !== 'object') return { valid: false, errors: ['packet is not an object'] };
231
+ if (packet.schemaVersion !== CONTINUATION_PACKET_SCHEMA_VERSION) errors.push(`schemaVersion must be ${CONTINUATION_PACKET_SCHEMA_VERSION}`);
232
+
233
+ if (!Array.isArray(packet.layers)) errors.push('layers must be an array');
234
+ else {
235
+ for (const layer of packet.layers) {
236
+ if (!CONTEXT_LAYER_KINDS.includes(layer?.kind)) errors.push(`layer.kind invalid: ${layer?.kind}`);
237
+ if (!COMPACTION_ELIGIBILITY.includes(layer?.eligibility)) errors.push(`layer.eligibility invalid: ${layer?.eligibility}`);
238
+ if (!LAYER_DISPOSITIONS.includes(layer?.disposition)) errors.push(`layer.disposition invalid: ${layer?.disposition}`);
239
+ }
240
+ }
241
+
242
+ if (!Array.isArray(packet.requiredState)) errors.push('requiredState must be an array');
243
+ if (!packet.budget || typeof packet.budget !== 'object') errors.push('budget missing');
244
+ else if (!Number.isInteger(packet.budget.requiredTokens) || packet.budget.requiredTokens < 0) errors.push('budget.requiredTokens invalid');
245
+
246
+ const hasBlocker = packet.blocker && typeof packet.blocker === 'object';
247
+ if (Array.isArray(packet.layers) && !hasBlocker) {
248
+ const droppedRequired = packet.layers.find((l) => l?.eligibility === 'required' && l?.disposition !== 'retained');
249
+ if (droppedRequired) errors.push(`required layer not retained without a blocker: ${droppedRequired.id}`);
250
+ }
251
+
252
+ return { valid: errors.length === 0, errors };
253
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * lib/chat/continuation-source.mjs — the one resolveSource every surface shares to
3
+ * rehydrate a continuation packet's reconstructible layers (construct-6zga.1.10).
4
+ *
5
+ * A continuation packet stores required state verbatim but references reconstructible
6
+ * layers (static instructions, role guidance, learned patterns) by source id, so
7
+ * rehydration must re-derive them rather than read stored content. One shared
8
+ * resolver across terminal, web, export, and resume guarantees every surface
9
+ * reconstructs identical context (AC3). Static-instructions and role-guidance
10
+ * families re-derive through the same composer a live turn runs
11
+ * (lib/chat/system-prompt.mjs); learned-patterns re-derive from the observation
12
+ * store, prefetched once because rehydration runs synchronously.
13
+ */
14
+
15
+ import { buildSystemPrompt } from './system-prompt.mjs';
16
+
17
+ function observationText(record) {
18
+ return [record?.summary, record?.content].filter(Boolean).join(' — ').trim();
19
+ }
20
+
21
+ export function createContinuationResolver({
22
+ systemText = null,
23
+ overlay = null,
24
+ capabilityTier = 'full',
25
+ cwd = process.cwd(),
26
+ learnedPatterns = null,
27
+ } = {}) {
28
+ const composedSystem = typeof systemText === 'string' ? systemText : buildSystemPrompt({ overlay, capabilityTier });
29
+ const cache = new Map([['prompt:system', composedSystem]]);
30
+ if (typeof learnedPatterns === 'string') cache.set('learned-patterns', learnedPatterns);
31
+
32
+ function resolveSource(sourceId) {
33
+ if (sourceId == null) return null;
34
+ if (cache.has(sourceId)) return cache.get(sourceId);
35
+ if (sourceId.startsWith('prompt:') || sourceId.startsWith('role:')) return composedSystem;
36
+ if (sourceId === 'learned-patterns' || sourceId.startsWith('observation:')) return cache.get('learned-patterns') ?? null;
37
+ return null;
38
+ }
39
+
40
+ // Learned patterns live in the observation store and load asynchronously; a
41
+ // surface that wants them prefetches once, after which the synchronous
42
+ // resolveSource serves them from cache. A store miss leaves the layer null —
43
+ // rehydration reports it unresolved rather than inventing content.
44
+
45
+ async function loadLearnedPatterns(query, { limit = 5 } = {}) {
46
+ try {
47
+ const { searchObservations } = await import('../observation-store.mjs');
48
+ const results = await searchObservations(cwd, query, { limit });
49
+ const text = (results || []).map(observationText).filter(Boolean).map((line) => `- ${line}`).join('\n');
50
+ cache.set('learned-patterns', text);
51
+ return text;
52
+ } catch {
53
+ return cache.get('learned-patterns') ?? null;
54
+ }
55
+ }
56
+
57
+ return { resolveSource, loadLearnedPatterns, get systemText() { return composedSystem; } };
58
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * lib/chat/demo-guide.mjs — slash-command helpers for construct chat demo mode.
3
+ */
4
+
5
+ export function formatDemoStepLine(step, colors = {}) {
6
+ const dim = colors.dim || '';
7
+ const reset = colors.reset || '';
8
+ const bold = colors.bold || '';
9
+ const lines = [`${bold}Step ${step.index}${reset}: ${step.title || 'prompt'}`];
10
+ if (step.prompt) lines.push(`${step.prompt}`);
11
+ if (step.command) lines.push(`${dim}command: ${step.command}${reset}`);
12
+ return lines.join('\n');
13
+ }
14
+
15
+ export function formatDemoStepsList(guide, colors = {}) {
16
+ const dim = colors.dim || '';
17
+ const reset = colors.reset || '';
18
+ const bold = colors.bold || '';
19
+ const lines = [`${bold}Demo steps${reset}`];
20
+ guide.script.steps.forEach((step, i) => {
21
+ lines.push(` ${i + 1}. ${step.title || step.prompt?.slice(0, 50) || 'step'}`);
22
+ });
23
+ lines.push(`${dim}Use /demo next for the next prompt${reset}`);
24
+ return lines.join('\n');
25
+ }
26
+
27
+ export function registerDemoCommands(HELP, demoGuide) {
28
+ if (!demoGuide) return HELP;
29
+ return [
30
+ ...HELP.slice(0, 1),
31
+ ['/demo [next|steps|reset]', 'walk the active demo script'],
32
+ ...HELP.slice(1),
33
+ ];
34
+ }
35
+
36
+ export function handleDemoCommand(arg, { demoGuide, output, colors }) {
37
+ if (!demoGuide) {
38
+ output.write(`${colors.dim}No active demo. Launch with \`construct demo <name>\`.${colors.reset}\n`);
39
+ return;
40
+ }
41
+ const action = (arg || 'steps').toLowerCase();
42
+ if (action === 'reset') {
43
+ demoGuide.reset();
44
+ output.write(`${colors.green}Demo reset to step 1.${colors.reset}\n`);
45
+ return;
46
+ }
47
+ if (action === 'next') {
48
+ const step = demoGuide.next();
49
+ if (!step) {
50
+ output.write(`${colors.dim}Demo complete — all steps shown.${colors.reset}\n`);
51
+ return;
52
+ }
53
+ output.write(`\n${formatDemoStepLine(step, colors)}\n\n`);
54
+ return;
55
+ }
56
+ output.write(`${formatDemoStepsList(demoGuide, colors)}\n`);
57
+ const peek = demoGuide.peek();
58
+ if (peek) {
59
+ output.write(`${colors.dim}Next: /demo next → ${peek.title || 'step'}${colors.reset}\n`);
60
+ }
61
+ }