@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.
- package/README.md +30 -19
- package/bin/construct +487 -87
- package/bin/construct-postinstall.mjs +30 -2
- package/examples/distribution/README.md +42 -0
- package/examples/distribution/manifest.json +53 -0
- package/examples/distribution/sources/adr.md +84 -0
- package/examples/distribution/sources/deck-one-pager.md +65 -0
- package/examples/distribution/sources/prd-platform.md +161 -0
- package/examples/distribution/sources/research-brief.md +80 -0
- package/examples/distribution/sources/rfc-platform.md +82 -0
- package/examples/distribution/sources/runbook.md +103 -0
- package/examples/distribution/sources/strategy.md +88 -0
- package/lib/adapters-sync.mjs +67 -0
- package/lib/artifact-gate-notice.mjs +38 -0
- package/lib/artifact-manifest.mjs +258 -0
- package/lib/artifact-release-gate.mjs +171 -0
- package/lib/artifact-reviewers.mjs +68 -0
- package/lib/artifact-type-from-path.mjs +79 -0
- package/lib/artifact-workflow.mjs +252 -0
- package/lib/audit-skills.mjs +4 -0
- package/lib/audit-specialists.mjs +285 -0
- package/lib/audit-trail.mjs +1 -1
- package/lib/auto-docs.mjs +90 -19
- package/lib/beads/drift.mjs +27 -6
- package/lib/beads-client.mjs +49 -121
- package/lib/beads-optimistic.mjs +5 -12
- package/lib/brand-fonts.mjs +93 -0
- package/lib/brand-prose.mjs +214 -0
- package/lib/brand-tokens.mjs +92 -0
- package/lib/bridges/copilot-proxy.mjs +13 -26
- package/lib/capability-ledger.mjs +156 -0
- package/lib/certification/artifact-fixtures.mjs +132 -0
- package/lib/certification/artifact-gates.mjs +63 -0
- package/lib/certification/artifact-provenance.mjs +97 -0
- package/lib/certification/canonical-scenarios.mjs +78 -0
- package/lib/certification/cli.mjs +191 -0
- package/lib/certification/dashboard-api.mjs +71 -0
- package/lib/certification/demo-parity.mjs +116 -0
- package/lib/certification/document-io-fixtures.mjs +246 -0
- package/lib/certification/document-workflow.mjs +97 -0
- package/lib/certification/eval-bridge.mjs +77 -0
- package/lib/certification/model-routing.mjs +149 -0
- package/lib/certification/prompt-budget.mjs +119 -0
- package/lib/certification/rc-gate.mjs +206 -0
- package/lib/certification/real-llm-scenarios.mjs +303 -0
- package/lib/certification/role-cards.mjs +113 -0
- package/lib/certification/role-overlays.mjs +117 -0
- package/lib/certification/run.mjs +122 -0
- package/lib/certification/runner.mjs +323 -0
- package/lib/certification/scenarios.mjs +51 -0
- package/lib/certification/skill-inventory.mjs +289 -0
- package/lib/certification/skill-scenarios.mjs +147 -0
- package/lib/certification/specialist-contracts.mjs +85 -0
- package/lib/certification/specialist-scenarios.mjs +175 -0
- package/lib/certification/stale-impact.mjs +146 -0
- package/lib/certification/status.mjs +252 -0
- package/lib/certification/store.mjs +77 -0
- package/lib/chat/cli.mjs +333 -0
- package/lib/chat/command-suggest.mjs +161 -0
- package/lib/chat/commands.mjs +215 -0
- package/lib/chat/config.mjs +142 -0
- package/lib/chat/context-compactor.mjs +250 -0
- package/lib/chat/context-continuation.mjs +253 -0
- package/lib/chat/continuation-source.mjs +58 -0
- package/lib/chat/demo-guide.mjs +61 -0
- package/lib/chat/design-tokens.mjs +91 -0
- package/lib/chat/desktop-binary.mjs +79 -0
- package/lib/chat/desktop-build.mjs +130 -0
- package/lib/chat/desktop-launcher.mjs +133 -0
- package/lib/chat/evidence.mjs +145 -0
- package/lib/chat/export.mjs +74 -0
- package/lib/chat/harness/driver.mjs +91 -0
- package/lib/chat/list-picker.mjs +112 -0
- package/lib/chat/model-picker.mjs +356 -0
- package/lib/chat/openrouter-fallback.mjs +151 -0
- package/lib/chat/permission-prompt.mjs +33 -0
- package/lib/chat/picker-catalog.mjs +45 -0
- package/lib/chat/policy-telemetry.mjs +34 -0
- package/lib/chat/present.mjs +246 -0
- package/lib/chat/session-context.mjs +39 -0
- package/lib/chat/session-persist.mjs +73 -0
- package/lib/chat/session-restore.mjs +71 -0
- package/lib/chat/session-settings.mjs +53 -0
- package/lib/chat/system-prompt.mjs +52 -0
- package/lib/chat/transparency.mjs +93 -0
- package/lib/chat/tui/color-scheme.mjs +42 -0
- package/lib/chat/tui/markdown.mjs +123 -0
- package/lib/chat/tui/presentation.mjs +100 -0
- package/lib/chat/tui/render.mjs +500 -0
- package/lib/chat/tui/turn-block.mjs +284 -0
- package/lib/chat/tui/turn-present.mjs +18 -0
- package/lib/chat/tui/turn-state.mjs +88 -0
- package/lib/chat/tui/usage.mjs +122 -0
- package/lib/chat/web-commands.mjs +146 -0
- package/lib/chat/web-launcher.mjs +63 -0
- package/lib/chat/web-picker-keys.mjs +46 -0
- package/lib/chat/web-session.mjs +159 -0
- package/lib/cli-commands.mjs +146 -15
- package/lib/cli-service-inventory.mjs +42 -0
- package/lib/comment-lint.mjs +6 -4
- package/lib/config/intake-policy.mjs +209 -0
- package/lib/config/project-config.mjs +11 -2
- package/lib/config/schema.mjs +86 -6
- package/lib/config/source-targets.mjs +311 -0
- package/lib/contract-schemas/decision.json +50 -0
- package/lib/contract-schemas/implementation.json +51 -0
- package/lib/contract-schemas/review-report.json +32 -0
- package/lib/contract-schemas/test-report.json +43 -0
- package/lib/contracts/construct-handoff.mjs +60 -0
- package/lib/contracts/validate.mjs +32 -3
- package/lib/contracts/violation-log.mjs +58 -5
- package/lib/dashboard-demo.mjs +71 -0
- package/lib/dashboard-static.mjs +19 -5
- package/lib/decisions/registry.mjs +5 -3
- package/lib/deck-export-pptx.mjs +1152 -0
- package/lib/demo-recording.mjs +142 -0
- package/lib/demo-script.mjs +114 -0
- package/lib/demo-surface.mjs +249 -0
- package/lib/demo.mjs +598 -140
- package/lib/diagram-export.mjs +192 -0
- package/lib/diagram.mjs +13 -11
- package/lib/docs-verify.mjs +25 -7
- package/lib/doctor/index.mjs +3 -1
- package/lib/doctor/project-adapters.mjs +44 -0
- package/lib/doctor/source-checkout.mjs +16 -0
- package/lib/doctor/watchers/cx-budget.mjs +98 -0
- package/lib/doctor/watchers/graph-staleness.mjs +52 -0
- package/lib/doctor/watchers/process-pressure.mjs +4 -3
- package/lib/document-export.mjs +384 -38
- package/lib/document-extract.mjs +44 -4
- package/lib/document-ingest.mjs +29 -20
- package/lib/embed/auto-sources.mjs +44 -0
- package/lib/embed/cli.mjs +4 -3
- package/lib/embed/daemon.mjs +18 -46
- package/lib/embed/demand-fetch.mjs +50 -19
- package/lib/embed/inbox.mjs +6 -10
- package/lib/embed/worker.mjs +4 -3
- package/lib/embedded-contract/index.mjs +11 -0
- package/lib/embedded-contract/model-resolve.mjs +10 -9
- package/lib/env-config.mjs +26 -0
- package/lib/evals/dataset.mjs +137 -0
- package/lib/evals/gates.mjs +175 -0
- package/lib/export-branding.mjs +32 -0
- package/lib/graph/build-co-change.mjs +54 -0
- package/lib/graph/build-from-registry.mjs +136 -0
- package/lib/graph/build-import-graph.mjs +153 -0
- package/lib/graph/cli.mjs +110 -0
- package/lib/graph/impact-cli.mjs +89 -0
- package/lib/graph/impact.mjs +108 -0
- package/lib/graph/staleness.mjs +44 -0
- package/lib/graph/store.mjs +172 -0
- package/lib/health-check.mjs +90 -76
- package/lib/hooks/artifact-release-gate.mjs +43 -0
- package/lib/hooks/brand-prose-lint.mjs +38 -0
- package/lib/hooks/graph-impact-advisory.mjs +62 -0
- package/lib/hooks/session-optimize.mjs +84 -207
- package/lib/hooks/session-start.mjs +18 -16
- package/lib/host-disposition.mjs +7 -0
- package/lib/improvement/cli.mjs +189 -0
- package/lib/improvement/controller.mjs +137 -0
- package/lib/improvement/proposal.mjs +120 -0
- package/lib/improvement/specialist-loop.mjs +192 -0
- package/lib/improvement/store.mjs +89 -0
- package/lib/improvement/surface.mjs +219 -0
- package/lib/ingest-tooling.mjs +97 -0
- package/lib/init/doc-lanes.mjs +165 -0
- package/lib/init-docs.mjs +31 -178
- package/lib/init-unified.mjs +50 -119
- package/lib/init-update-guide.mjs +102 -0
- package/lib/init-update.mjs +32 -1
- package/lib/init.mjs +8 -0
- package/lib/install/desktop-binary-download.mjs +85 -0
- package/lib/install/legacy-global-cleanup.mjs +189 -0
- package/lib/intake/constants.mjs +29 -0
- package/lib/intake/daemon.mjs +2 -0
- package/lib/intake/git-queue.mjs +9 -8
- package/lib/intake/intake-config.mjs +52 -105
- package/lib/intake/legacy-paths.mjs +5 -0
- package/lib/intake/queue.mjs +2 -1
- package/lib/intake/session-prelude.mjs +90 -1
- package/lib/libreoffice-export.mjs +97 -0
- package/lib/logging/rotate.mjs +9 -0
- package/lib/maintenance/docker-reclaim.mjs +206 -0
- package/lib/mcp/external-schema-cost.mjs +74 -0
- package/lib/mcp/server.mjs +103 -10
- package/lib/mcp/stdio-mcp-probe.mjs +188 -0
- package/lib/mcp/tool-budget.mjs +55 -4
- package/lib/mcp/tools/document.mjs +18 -4
- package/lib/mcp/tools/embedded-contract.mjs +14 -0
- package/lib/mcp/tools/skills.mjs +32 -3
- package/lib/mcp/tools/workflow.mjs +13 -1
- package/lib/model-free-selector.mjs +18 -0
- package/lib/model-registry.mjs +13 -229
- package/lib/model-router.mjs +373 -92
- package/lib/models/behavior-matrix.mjs +289 -0
- package/lib/models/catalog.mjs +209 -0
- package/lib/models/execution-capability-profile.mjs +196 -0
- package/lib/models/execution-policy.mjs +307 -0
- package/lib/models/provider-poll.mjs +383 -0
- package/lib/npm-spawn-env.mjs +17 -0
- package/lib/ollama/installed-models.mjs +129 -0
- package/lib/oracle/actions.mjs +187 -0
- package/lib/oracle/artifact-gate.mjs +99 -0
- package/lib/oracle/cli.mjs +204 -0
- package/lib/oracle/daemon-entry.mjs +14 -0
- package/lib/oracle/dispatch.mjs +81 -0
- package/lib/oracle/execute.mjs +143 -0
- package/lib/oracle/gaps.mjs +76 -0
- package/lib/oracle/index.mjs +84 -0
- package/lib/oracle/issues.mjs +164 -0
- package/lib/oracle/org-graph.mjs +170 -0
- package/lib/oracle/policy.mjs +80 -0
- package/lib/oracle/read-model.mjs +398 -0
- package/lib/oracle/reconcile.mjs +191 -0
- package/lib/oracle/routing.mjs +89 -0
- package/lib/oracle/synthesize.mjs +384 -0
- package/lib/oracle/verdicts.mjs +51 -0
- package/lib/orchestration/worker.mjs +88 -27
- package/lib/orchestration-policy.mjs +50 -0
- package/lib/parity.mjs +96 -2
- package/lib/playwright-demo.mjs +254 -0
- package/lib/project-init-shared.mjs +4 -1
- package/lib/prompt-composer.js +7 -3
- package/lib/prompt-validation-contract.mjs +24 -0
- package/lib/provider-capabilities.js +57 -11
- package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
- package/lib/providers/contract/adapters/git/index.mjs +115 -0
- package/lib/providers/contract/adapters/github/index.mjs +166 -0
- package/lib/providers/contract/adapters/jira/index.mjs +187 -0
- package/lib/providers/contract/adapters/slack/index.mjs +175 -0
- package/lib/providers/contract/contract-tests.mjs +57 -0
- package/lib/providers/contract/errors.mjs +48 -0
- package/lib/providers/contract/interface.mjs +50 -0
- package/lib/providers/contract/registry.mjs +102 -0
- package/lib/providers/copilot-auth.mjs +297 -0
- package/lib/providers/credential-bootstrap.mjs +178 -0
- package/lib/providers/credential-catalog.mjs +46 -0
- package/lib/providers/credential-sources.mjs +63 -0
- package/lib/providers/creds.mjs +5 -2
- package/lib/providers/op-run.mjs +59 -0
- package/lib/providers/secret-resolver.mjs +159 -0
- package/lib/publish-template.mjs +163 -0
- package/lib/publish-tooling.mjs +119 -0
- package/lib/publish.mjs +305 -0
- package/lib/registry/cli.mjs +82 -0
- package/lib/registry/consolidation.mjs +147 -0
- package/lib/registry/generate-docs.mjs +75 -0
- package/lib/registry/skill-verification.mjs +57 -0
- package/lib/registry/surface-map.mjs +76 -0
- package/lib/registry/validate.mjs +135 -0
- package/lib/resources/budget.mjs +82 -0
- package/lib/resources/process-budget.mjs +45 -0
- package/lib/rules-delivery.mjs +9 -2
- package/lib/rules-read.mjs +26 -0
- package/lib/runtime-env.mjs +23 -0
- package/lib/runtime-pressure.mjs +51 -7
- package/lib/schema-infer.mjs +13 -2
- package/lib/server/chat-loop.mjs +622 -0
- package/lib/server/demo-preview.mjs +63 -0
- package/lib/server/index.mjs +259 -9
- package/lib/server/rate-limit.mjs +5 -3
- package/lib/server/webhook.mjs +3 -2
- package/lib/service-manager.mjs +60 -8
- package/lib/setup.mjs +70 -7
- package/lib/specialists/prompt-schema.mjs +19 -10
- package/lib/specialists/roster.mjs +43 -0
- package/lib/specialists/scaffold.mjs +56 -0
- package/lib/storage/backend.mjs +6 -6
- package/lib/storage/hybrid-query.mjs +7 -4
- package/lib/storage/state-source.mjs +5 -5
- package/lib/storage/sync.mjs +2 -3
- package/lib/telemetry/rule-calls.mjs +52 -0
- package/lib/template-registry.mjs +2 -2
- package/lib/templates/visual-requirements.mjs +27 -51
- package/lib/test-corpus-inventory.mjs +313 -0
- package/lib/uninstall/uninstall.mjs +19 -7
- package/lib/update.mjs +12 -0
- package/lib/upgrade.mjs +14 -0
- package/lib/wireframe.mjs +20 -14
- package/lib/worker/run.mjs +17 -4
- package/lib/worker/trace.mjs +11 -3
- package/package.json +28 -14
- package/personas/construct.md +2 -2
- package/platforms/claude/settings.template.json +36 -0
- package/rules/common/patterns.md +1 -1
- package/rules/common/release-gates.md +4 -3
- package/scripts/sync-specialists.mjs +41 -28
- package/skills/devops/data-engineering.md +1 -1
- package/skills/docs/adr-workflow.md +1 -0
- package/skills/docs/backlog-proposal-workflow.md +1 -0
- package/skills/docs/codebase-research-workflow.md +40 -0
- package/skills/docs/customer-profile-workflow.md +1 -0
- package/skills/docs/document-ingest-workflow.md +1 -0
- package/skills/docs/evidence-ingest-workflow.md +1 -0
- package/skills/docs/init-docs.md +2 -2
- package/skills/docs/init-project.md +7 -2
- package/skills/docs/prd-workflow.md +24 -1
- package/skills/docs/prfaq-workflow.md +1 -0
- package/skills/docs/product-intelligence-workflow.md +1 -0
- package/skills/docs/product-signal-workflow.md +1 -0
- package/skills/docs/research-workflow.md +54 -37
- package/skills/docs/runbook-workflow.md +1 -0
- package/skills/docs/strategy-workflow.md +1 -0
- package/skills/docs/user-research-workflow.md +40 -0
- package/skills/operating/orchestration-reference.md +1 -1
- package/skills/roles/architect.md +5 -0
- package/skills/roles/operator.docs.md +4 -0
- package/skills/routing.md +4 -2
- package/specialists/artifact-manifest.json +489 -0
- package/specialists/artifact-manifest.schema.json +83 -0
- package/specialists/audit-enrichments.json +454 -0
- package/specialists/contracts.json +37 -25
- package/specialists/prompts/_shared/validation-contract.md +26 -0
- package/specialists/prompts/cx-accessibility.md +21 -2
- package/specialists/prompts/cx-ai-engineer.md +24 -1
- package/specialists/prompts/cx-architect.md +4 -1
- package/specialists/prompts/cx-business-strategist.md +23 -2
- package/specialists/prompts/cx-data-analyst.md +22 -1
- package/specialists/prompts/cx-data-engineer.md +23 -2
- package/specialists/prompts/cx-debugger.md +21 -2
- package/specialists/prompts/cx-designer.md +26 -3
- package/specialists/prompts/cx-devil-advocate.md +19 -2
- package/specialists/prompts/cx-docs-keeper.md +28 -1
- package/specialists/prompts/cx-engineer.md +22 -1
- package/specialists/prompts/cx-evaluator.md +18 -1
- package/specialists/prompts/cx-explorer.md +21 -2
- package/specialists/prompts/cx-legal-compliance.md +21 -2
- package/specialists/prompts/cx-operations.md +21 -2
- package/specialists/prompts/cx-oracle.md +94 -0
- package/specialists/prompts/cx-orchestrator.md +20 -1
- package/specialists/prompts/cx-platform-engineer.md +25 -2
- package/specialists/prompts/cx-product-manager.md +32 -1
- package/specialists/prompts/cx-qa.md +24 -2
- package/specialists/prompts/cx-rd-lead.md +23 -2
- package/specialists/prompts/cx-release-manager.md +23 -2
- package/specialists/prompts/cx-researcher.md +22 -2
- package/specialists/prompts/cx-reviewer.md +18 -1
- package/specialists/prompts/cx-security.md +22 -2
- package/specialists/prompts/cx-sre.md +30 -3
- package/specialists/prompts/cx-test-automation.md +7 -1
- package/specialists/prompts/cx-trace-reviewer.md +22 -3
- package/specialists/prompts/cx-ux-researcher.md +21 -2
- package/specialists/registry.json +52 -173
- package/specialists/tone-profiles.json +42 -0
- package/templates/demos/playwright/demo-recording.config.mjs +47 -0
- package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
- package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
- package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
- package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
- package/templates/demos/tapes/resource-guard-rails.tape +49 -0
- package/templates/demos/vhs/construct-cockpit.json +24 -0
- package/templates/distribution/construct-brand.typ +446 -0
- package/templates/distribution/construct-decision.typ +38 -0
- package/templates/distribution/construct-deck.html +95 -0
- package/templates/distribution/construct-pdf.typ +38 -0
- package/templates/distribution/construct-prd.typ +38 -0
- package/templates/distribution/construct-reference.docx +0 -0
- package/templates/distribution/construct-research.typ +38 -0
- package/templates/distribution/construct-web.html +92 -0
- package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
- package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
- package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
- package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/README.md +36 -0
- package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
- package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
- package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
- package/templates/distribution/icons/activity.svg +15 -0
- package/templates/distribution/icons/alert-triangle.svg +17 -0
- package/templates/distribution/icons/book-open.svg +16 -0
- package/templates/distribution/icons/bot.svg +20 -0
- package/templates/distribution/icons/brain.svg +22 -0
- package/templates/distribution/icons/circle-check.svg +16 -0
- package/templates/distribution/icons/clipboard-check.svg +17 -0
- package/templates/distribution/icons/cpu.svg +28 -0
- package/templates/distribution/icons/database.svg +17 -0
- package/templates/distribution/icons/eye.svg +16 -0
- package/templates/distribution/icons/file-text.svg +19 -0
- package/templates/distribution/icons/gauge.svg +16 -0
- package/templates/distribution/icons/git-branch.svg +17 -0
- package/templates/distribution/icons/key.svg +17 -0
- package/templates/distribution/icons/layers.svg +17 -0
- package/templates/distribution/icons/list-checks.svg +19 -0
- package/templates/distribution/icons/lock.svg +16 -0
- package/templates/distribution/icons/message-square.svg +15 -0
- package/templates/distribution/icons/network.svg +19 -0
- package/templates/distribution/icons/route.svg +17 -0
- package/templates/distribution/icons/scale.svg +19 -0
- package/templates/distribution/icons/search.svg +16 -0
- package/templates/distribution/icons/send.svg +16 -0
- package/templates/distribution/icons/server.svg +18 -0
- package/templates/distribution/icons/shield-check.svg +16 -0
- package/templates/distribution/icons/users.svg +18 -0
- package/templates/distribution/icons/webhook.svg +17 -0
- package/templates/distribution/icons/workflow.svg +17 -0
- package/templates/distribution/icons/wrench.svg +15 -0
- package/templates/distribution/run.mjs +18 -1
- package/templates/docs/adr.md +7 -0
- package/templates/docs/construct_guide.md +8 -8
- package/templates/docs/customer-profile.md +4 -0
- package/templates/docs/one-pager.md +4 -0
- package/templates/docs/postmortem.md +31 -0
- package/templates/docs/prd-platform.md +19 -0
- package/templates/docs/prd.md +15 -0
- package/templates/docs/prfaq.md +7 -0
- package/templates/docs/qa-strategy.md +103 -0
- package/templates/docs/research-brief.md +8 -0
- package/templates/docs/rfc-platform.md +12 -0
- 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
|
+
}
|