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