@geraldmaron/construct 1.2.3 → 1.3.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 +7 -10
- package/bin/construct +235 -209
- 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 +55 -54
- package/lib/comment-lint.mjs +1 -5
- package/lib/completions.mjs +20 -2
- package/lib/config/legacy-config-migration.mjs +90 -24
- 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/cli.mjs +12 -2
- package/lib/doctor/source-checkout.mjs +3 -4
- package/lib/doctor/watchers/consistency.mjs +148 -43
- 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/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 +6 -26
- 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 -8
- 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 +269 -51
- 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/apps/chat/engine/ai-sdk-agent.mjs +0 -183
- package/apps/chat/engine/loop-driver.mjs +0 -211
- package/apps/chat/engine/models.mjs +0 -122
- package/apps/chat/engine/provider-adapters.mjs +0 -171
- package/apps/chat/engine/tools/permission.mjs +0 -54
- package/apps/chat/engine/tools/primitives.mjs +0 -180
- package/apps/chat/engine/tools/registry.mjs +0 -122
- package/apps/chat/engine/turn-controls.mjs +0 -70
- 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
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* apps/chat/engine/turn-controls.mjs — resolve one turn's execution controls from
|
|
3
|
-
* the compiled execution policy (construct-rv2x).
|
|
4
|
-
*
|
|
5
|
-
* The owned-loop engine (ai-sdk-agent.mjs) calls this once per turn to turn a
|
|
6
|
-
* capability profile plus the turn overlay into the concrete knobs streamText
|
|
7
|
-
* needs: the step cap, the output-token cap, caching eligibility, and the tool-
|
|
8
|
-
* group / schema budget, plus the continuation/compaction budget the session uses
|
|
9
|
-
* to decide when to compact context (construct-6zga.1.9). The capability profile
|
|
10
|
-
* is resolved from the active model id, so a mid-session model switch re-derives
|
|
11
|
-
* the envelope.
|
|
12
|
-
*
|
|
13
|
-
* Behavior preservation is the contract: this never throws and never returns a
|
|
14
|
-
* tighter envelope than today for a hosted-direct model. hosted-direct keeps the
|
|
15
|
-
* legacy step cap, the full tool set, and unbounded output; every other class
|
|
16
|
-
* enforces its compiled budget. An explicit CX_CHAT_MAX_STEPS operator override
|
|
17
|
-
* wins over the policy's step cap. Any failure in policy resolution falls back to
|
|
18
|
-
* the legacy envelope (16 steps, full tool set, no cap, no caching) so a policy
|
|
19
|
-
* bug can never break a chat turn.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { resolveExecutionCapabilityProfile } from '../../../lib/models/execution-capability-profile.mjs';
|
|
23
|
-
import { compilePolicyFromOverlay } from '../../../lib/models/execution-policy.mjs';
|
|
24
|
-
import { continuationBudgetFromPolicy } from '../../../lib/chat/context-continuation.mjs';
|
|
25
|
-
|
|
26
|
-
const LEGACY_STEP_CAP = 16;
|
|
27
|
-
|
|
28
|
-
const LEGACY_CONTROLS = Object.freeze({
|
|
29
|
-
policy: null,
|
|
30
|
-
iterations: LEGACY_STEP_CAP,
|
|
31
|
-
outputCap: null,
|
|
32
|
-
cacheEligible: false,
|
|
33
|
-
allowedToolGroups: null,
|
|
34
|
-
maxToolSchemas: Infinity,
|
|
35
|
-
degraded: false,
|
|
36
|
-
continuation: Object.freeze({ triggerTokens: null, triggerRatio: null }),
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
function legacyControls() {
|
|
40
|
-
return { ...LEGACY_CONTROLS };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function resolveTurnControls({ model = null, turnOverlay = null, env = {} } = {}) {
|
|
44
|
-
try {
|
|
45
|
-
const profile = resolveExecutionCapabilityProfile({ model });
|
|
46
|
-
const toolFailure = turnOverlay?.toolFailure === true;
|
|
47
|
-
const policy = compilePolicyFromOverlay({ profile, overlay: turnOverlay, toolFailure });
|
|
48
|
-
|
|
49
|
-
const envSteps = Number(env?.CX_CHAT_MAX_STEPS);
|
|
50
|
-
const iterations = envSteps > 0 ? Math.floor(envSteps) : policy.tools.maxToolIterations;
|
|
51
|
-
|
|
52
|
-
// hosted-direct is the mandated default path: keep today's unbounded output
|
|
53
|
-
// until live-probe evidence supersedes the compatibility_fallback tier. Every
|
|
54
|
-
// other class enforces its compiled output budget.
|
|
55
|
-
const outputCap = policy.source.capabilityClass === 'hosted-direct' ? null : policy.output.outputTokenBudget;
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
policy,
|
|
59
|
-
iterations,
|
|
60
|
-
outputCap,
|
|
61
|
-
cacheEligible: policy.caching.eligible === true,
|
|
62
|
-
allowedToolGroups: policy.tools.allowedToolGroups,
|
|
63
|
-
maxToolSchemas: policy.tools.maxToolSchemas,
|
|
64
|
-
degraded: policy.degradedMode === true,
|
|
65
|
-
continuation: continuationBudgetFromPolicy(policy),
|
|
66
|
-
};
|
|
67
|
-
} catch {
|
|
68
|
-
return legacyControls();
|
|
69
|
-
}
|
|
70
|
-
}
|
package/lib/boundary.mjs
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/boundary.mjs — Authenticated parent/child boundary registration.
|
|
3
|
-
*
|
|
4
|
-
* Verifies a parent Construct instance is reachable, validates an HMAC
|
|
5
|
-
* signature over `childInstanceId|nonce` when a shared secret is configured,
|
|
6
|
-
* and persists the binding to boundary.json in the XDG config dir (mode 0600). A
|
|
7
|
-
* different parent rotates the registration only when explicitly allowed via
|
|
8
|
-
* CONSTRUCT_BOUNDARY_ALLOW_OVERRIDE=1; prior configs are archived alongside
|
|
9
|
-
* the active one. Exposed as a standalone module so the dashboard endpoint
|
|
10
|
-
* and the functional tests share the same logic.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
14
|
-
import { join } from 'node:path';
|
|
15
|
-
import { homedir } from 'node:os';
|
|
16
|
-
import { createHmac, timingSafeEqual, randomBytes } from 'node:crypto';
|
|
17
|
-
import { request as httpRequest } from 'node:http';
|
|
18
|
-
import { request as httpsRequest } from 'node:https';
|
|
19
|
-
import { configDir } from './config/xdg.mjs';
|
|
20
|
-
|
|
21
|
-
export const BOUNDARY_VERSION = '1.0';
|
|
22
|
-
|
|
23
|
-
function boundaryDir(home = homedir()) {
|
|
24
|
-
return configDir(home);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function boundaryConfigPath(home = homedir()) {
|
|
28
|
-
return join(boundaryDir(home), 'boundary.json');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function registerBoundary({
|
|
32
|
-
parentInstance,
|
|
33
|
-
parentUrl,
|
|
34
|
-
childInstanceId,
|
|
35
|
-
nonce,
|
|
36
|
-
signature,
|
|
37
|
-
sharedSecret = null,
|
|
38
|
-
home = homedir(),
|
|
39
|
-
allowOverride = process.env.CONSTRUCT_BOUNDARY_ALLOW_OVERRIDE === '1',
|
|
40
|
-
probe = probeParent,
|
|
41
|
-
}) {
|
|
42
|
-
if (!parentInstance || !parentUrl) {
|
|
43
|
-
return { ok: false, status: 400, error: 'parentInstance and parentUrl are required' };
|
|
44
|
-
}
|
|
45
|
-
if (!childInstanceId) {
|
|
46
|
-
return { ok: false, status: 400, error: 'childInstanceId is required' };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (sharedSecret) {
|
|
50
|
-
if (!nonce || !signature) {
|
|
51
|
-
return { ok: false, status: 401, error: 'nonce and signature are required when a shared secret is configured' };
|
|
52
|
-
}
|
|
53
|
-
const expected = createHmac('sha256', sharedSecret).update(`${childInstanceId}|${nonce}`).digest('hex');
|
|
54
|
-
const a = Buffer.from(expected, 'hex');
|
|
55
|
-
let b;
|
|
56
|
-
try { b = Buffer.from(String(signature), 'hex'); } catch { return { ok: false, status: 401, error: 'invalid signature encoding' }; }
|
|
57
|
-
if (a.length !== b.length || !timingSafeEqual(a, b)) {
|
|
58
|
-
return { ok: false, status: 401, error: 'signature mismatch' };
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const reachable = await probe(parentUrl);
|
|
63
|
-
if (!reachable.ok) {
|
|
64
|
-
return { ok: false, status: 502, error: `parent unreachable: ${reachable.error}` };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const dir = boundaryDir(home);
|
|
68
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
69
|
-
const cfgPath = boundaryConfigPath(home);
|
|
70
|
-
|
|
71
|
-
let prior = null;
|
|
72
|
-
if (existsSync(cfgPath)) {
|
|
73
|
-
try { prior = JSON.parse(readFileSync(cfgPath, 'utf8')); } catch { prior = null; }
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (prior && prior.parentInstance && prior.parentInstance !== parentInstance) {
|
|
77
|
-
if (!allowOverride) {
|
|
78
|
-
return {
|
|
79
|
-
ok: false,
|
|
80
|
-
status: 409,
|
|
81
|
-
error: `child already bound to parent '${prior.parentInstance}'; set CONSTRUCT_BOUNDARY_ALLOW_OVERRIDE=1 to rotate`,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
const archivePath = join(dir, `boundary.${Date.now()}.json`);
|
|
85
|
-
try { writeFileSync(archivePath, JSON.stringify(prior, null, 2)); } catch { /* archive is best effort */ }
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const config = {
|
|
89
|
-
parentInstance,
|
|
90
|
-
parentUrl,
|
|
91
|
-
childInstanceId,
|
|
92
|
-
registeredAt: new Date().toISOString(),
|
|
93
|
-
rotatedFrom: prior?.parentInstance || null,
|
|
94
|
-
nonce: nonce || randomBytes(16).toString('hex'),
|
|
95
|
-
boundaryVersion: BOUNDARY_VERSION,
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
writeFileSync(cfgPath, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
99
|
-
return { ok: true, config, path: cfgPath };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function probeParent(parentUrl) {
|
|
103
|
-
return new Promise((resolve) => {
|
|
104
|
-
let parsed;
|
|
105
|
-
try { parsed = new URL(parentUrl); } catch { return resolve({ ok: false, error: 'invalid URL' }); }
|
|
106
|
-
if (!/^https?:$/.test(parsed.protocol)) return resolve({ ok: false, error: 'unsupported protocol' });
|
|
107
|
-
const fn = parsed.protocol === 'https:' ? httpsRequest : httpRequest;
|
|
108
|
-
const req = fn({
|
|
109
|
-
method: 'HEAD',
|
|
110
|
-
protocol: parsed.protocol,
|
|
111
|
-
hostname: parsed.hostname,
|
|
112
|
-
port: parsed.port,
|
|
113
|
-
path: parsed.pathname || '/',
|
|
114
|
-
timeout: 3000,
|
|
115
|
-
}, (response) => {
|
|
116
|
-
resolve({ ok: response.statusCode < 500 });
|
|
117
|
-
response.resume();
|
|
118
|
-
});
|
|
119
|
-
req.on('timeout', () => { req.destroy(); resolve({ ok: false, error: 'timeout' }); });
|
|
120
|
-
req.on('error', (err) => resolve({ ok: false, error: err.message }));
|
|
121
|
-
req.end();
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function signBoundaryRequest({ childInstanceId, nonce, sharedSecret }) {
|
|
126
|
-
return createHmac('sha256', sharedSecret).update(`${childInstanceId}|${nonce}`).digest('hex');
|
|
127
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/certification/dashboard-api.mjs — dashboard-safe certification status payload.
|
|
3
|
-
*
|
|
4
|
-
* Mirrors construct certify status for GET /api/certification/status without secrets.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import fs from 'node:fs';
|
|
8
|
-
import path from 'node:path';
|
|
9
|
-
|
|
10
|
-
import { buildCertificationStatus } from './status.mjs';
|
|
11
|
-
import { loadCertificationStatus } from './stale-impact.mjs';
|
|
12
|
-
import { certificationRunDir, certificationRunsRoot, listCertificationRunIds, readCertificationRun } from './store.mjs';
|
|
13
|
-
|
|
14
|
-
function findConstructRoot(startPath = process.cwd()) {
|
|
15
|
-
let current = path.resolve(startPath);
|
|
16
|
-
while (true) {
|
|
17
|
-
if (fs.existsSync(path.join(current, 'package.json'))) return current;
|
|
18
|
-
const parent = path.dirname(current);
|
|
19
|
-
if (parent === current) return path.resolve(startPath);
|
|
20
|
-
current = parent;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function buildStaleRows({ rootDir }) {
|
|
25
|
-
const { status } = loadCertificationStatus({ rootDir });
|
|
26
|
-
if (!status?.capabilities) return [];
|
|
27
|
-
return Object.entries(status.capabilities)
|
|
28
|
-
.filter(([, entry]) => entry.status === 'stale')
|
|
29
|
-
.map(([capabilityId, entry]) => ({
|
|
30
|
-
capabilityId,
|
|
31
|
-
staleSince: entry.staleSince ?? null,
|
|
32
|
-
staleReason: entry.staleReason ?? null,
|
|
33
|
-
stalePaths: entry.stalePaths ?? [],
|
|
34
|
-
}));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function buildCertificationDashboard({ rootDir = process.cwd() } = {}) {
|
|
38
|
-
const root = findConstructRoot(rootDir);
|
|
39
|
-
const report = buildCertificationStatus({ rootDir: root });
|
|
40
|
-
const runs = [];
|
|
41
|
-
for (const runId of listCertificationRunIds({ rootDir: root }).slice(-20)) {
|
|
42
|
-
try {
|
|
43
|
-
const { run } = readCertificationRun(runId, { rootDir: root });
|
|
44
|
-
const dir = certificationRunDir(runId, root);
|
|
45
|
-
runs.push({
|
|
46
|
-
id: run.id,
|
|
47
|
-
scenarioId: run.scenarioId,
|
|
48
|
-
capabilityId: run.capabilityId,
|
|
49
|
-
status: run.verdict?.status ?? 'unknown',
|
|
50
|
-
createdAt: run.createdAt,
|
|
51
|
-
artifactPath: path.relative(root, path.join(dir, 'run.json')),
|
|
52
|
-
});
|
|
53
|
-
} catch { /* skip corrupt */ }
|
|
54
|
-
}
|
|
55
|
-
return {
|
|
56
|
-
generatedAt: report.generatedAt,
|
|
57
|
-
capabilities: report.capabilities,
|
|
58
|
-
specialists: report.specialists,
|
|
59
|
-
skills: report.skills,
|
|
60
|
-
artifactTypes: report.artifactTypes,
|
|
61
|
-
documentCategories: report.documentCategories,
|
|
62
|
-
demos: report.demos,
|
|
63
|
-
stale: buildStaleRows({ rootDir: root }),
|
|
64
|
-
runs,
|
|
65
|
-
runsDir: path.relative(root, certificationRunsRoot(root)),
|
|
66
|
-
artifactLinks: {
|
|
67
|
-
runsRoot: '.cx/certification/runs',
|
|
68
|
-
recentRunIds: runs.map((r) => r.id),
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
}
|
package/lib/chat/cli.mjs
DELETED
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/chat/cli.mjs — `construct chat` command handler (zero-dep launcher).
|
|
3
|
-
*
|
|
4
|
-
* ADR-0041: Construct owns the loop. This launcher stays in the zero-dep core and
|
|
5
|
-
* only orchestrates — it loads project settings, resolves the model, builds the
|
|
6
|
-
* owned-loop driver (apps/chat/engine, which lazy-imports the optional Vercel AI
|
|
7
|
-
* SDK and provider packages), persists the normalized event timeline under
|
|
8
|
-
* .cx/chat-sessions/, and then chooses a surface.
|
|
9
|
-
*
|
|
10
|
-
* Surface selection: desktop window (Tauri or Chromium app mode) is the default on
|
|
11
|
-
* machines with a display; `--web` opens a browser tab; `--plain`, `--accessible`,
|
|
12
|
-
* NO_COLOR, TERM=dumb, and non-TTY streams route to the linear renderer.
|
|
13
|
-
* `--window` is an alias for the desktop path. `--no-window` forces a browser tab.
|
|
14
|
-
*
|
|
15
|
-
* `--list` reports the model catalog and which providers are configured; `--model`
|
|
16
|
-
* pins the model for this launch; `--resume` restores the latest (or given) session
|
|
17
|
-
* transcript from `.cx/chat-sessions/`; the `--no-*` flags override saved transparency
|
|
18
|
-
* layers.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import path from 'node:path';
|
|
22
|
-
import { fileURLToPath } from 'node:url';
|
|
23
|
-
import { resolveColors } from '../term-format.mjs';
|
|
24
|
-
import { resolveLayers } from './transparency.mjs';
|
|
25
|
-
import { runChatLoop } from './tui/render.mjs';
|
|
26
|
-
import { createSessionUsage } from './tui/usage.mjs';
|
|
27
|
-
import { loadChatConfig } from './config.mjs';
|
|
28
|
-
import { loadConstructEnv } from '../env-config.mjs';
|
|
29
|
-
import { createCommands } from './commands.mjs';
|
|
30
|
-
import { resolveResumePath, restoreFromSession } from './session-restore.mjs';
|
|
31
|
-
import { buildSystemPrompt } from './system-prompt.mjs';
|
|
32
|
-
import { resolveExecutionCapabilityProfile } from '../models/execution-capability-profile.mjs';
|
|
33
|
-
import { compileExecutionPolicy } from '../models/execution-policy.mjs';
|
|
34
|
-
import { createChatPersister } from './session-persist.mjs';
|
|
35
|
-
import { hasGuiDisplay } from './desktop-binary.mjs';
|
|
36
|
-
import { createOwnedLoopDriver } from '../../apps/chat/engine/loop-driver.mjs';
|
|
37
|
-
import { listChatModels, resolveChatModelSelectionAsync, resolveSessionModel } from '../../apps/chat/engine/models.mjs';
|
|
38
|
-
import { resolveDemoGuideForChat } from '../demo-surface.mjs';
|
|
39
|
-
|
|
40
|
-
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
41
|
-
const REPO_ROOT = path.resolve(HERE, '..', '..');
|
|
42
|
-
|
|
43
|
-
function parseFlags(args) {
|
|
44
|
-
const flags = {
|
|
45
|
-
plain: false,
|
|
46
|
-
accessible: false,
|
|
47
|
-
web: false,
|
|
48
|
-
window: false,
|
|
49
|
-
noWindow: false,
|
|
50
|
-
list: false,
|
|
51
|
-
quiet: false,
|
|
52
|
-
free: false,
|
|
53
|
-
model: null,
|
|
54
|
-
resume: null,
|
|
55
|
-
ascii: null,
|
|
56
|
-
demo: null,
|
|
57
|
-
};
|
|
58
|
-
for (let i = 0; i < args.length; i++) {
|
|
59
|
-
const arg = args[i];
|
|
60
|
-
if (arg === '--list') flags.list = true;
|
|
61
|
-
else if (arg === '--plain') flags.plain = true;
|
|
62
|
-
else if (arg === '--accessible') flags.accessible = true;
|
|
63
|
-
else if (arg === '--web') flags.web = true;
|
|
64
|
-
else if (arg === '--window') flags.window = true;
|
|
65
|
-
else if (arg === '--no-window') flags.noWindow = true;
|
|
66
|
-
else if (arg === '--quiet') flags.quiet = true;
|
|
67
|
-
else if (arg === '--free') flags.free = true;
|
|
68
|
-
else if (arg === '--ascii') flags.ascii = true;
|
|
69
|
-
else if (arg === '--resume') flags.resume = true;
|
|
70
|
-
else if (arg.startsWith('--resume=')) flags.resume = arg.slice('--resume='.length);
|
|
71
|
-
else if (arg === '--no-thinking') flags.thinking = false;
|
|
72
|
-
else if (arg === '--no-path') flags.path = false;
|
|
73
|
-
else if (arg === '--no-specialists') flags.specialists = false;
|
|
74
|
-
else if (arg === '--no-tools') flags.tools = false;
|
|
75
|
-
else if (arg === '--no-observability') flags.observability = false;
|
|
76
|
-
else if (arg === '--model') flags.model = args[++i] || null;
|
|
77
|
-
else if (arg.startsWith('--model=')) flags.model = arg.slice('--model='.length);
|
|
78
|
-
else if (arg === '--demo') flags.demo = args[++i] || null;
|
|
79
|
-
else if (arg.startsWith('--demo=')) flags.demo = arg.slice('--demo='.length);
|
|
80
|
-
}
|
|
81
|
-
return flags;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function printModels(output, env) {
|
|
85
|
-
const colors = resolveColors({ stream: output, env });
|
|
86
|
-
const models = listChatModels({ env });
|
|
87
|
-
const configured = models.filter((m) => m.configured);
|
|
88
|
-
output.write(`${colors.bold}Owned loop \u2014 model catalog${colors.reset}\n`);
|
|
89
|
-
if (!configured.length) {
|
|
90
|
-
output.write(`${colors.dim}No provider configured. Set a key (a value or an op:// reference) in ~/.construct/config.env, run \`construct creds login copilot\` for GitHub Copilot, or start Ollama locally.${colors.reset}\n`);
|
|
91
|
-
}
|
|
92
|
-
for (const m of models) {
|
|
93
|
-
const mark = m.configured ? `${colors.green}\u2713${colors.reset}` : `${colors.dim}\u00b7${colors.reset}`;
|
|
94
|
-
output.write(` ${mark} ${m.id}${m.local ? `${colors.dim} (local)${colors.reset}` : ''}\n`);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function createPersister({ cwd, sessionId, resumePath = null }) {
|
|
99
|
-
return createChatPersister({ cwd, sessionId, resumePath, host: 'construct' });
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function wantsLinearSurface({ flags, env, output, input }) {
|
|
103
|
-
if (flags.plain || flags.accessible) return true;
|
|
104
|
-
if (env.NO_COLOR || env.TERM === 'dumb') return true;
|
|
105
|
-
if (env.CX_CHAT_FORCE_LINEAR === '1') return true;
|
|
106
|
-
if (!output.isTTY || !input.isTTY) return true;
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function resolveChatSurface({ flags, env, output, input }) {
|
|
111
|
-
if (wantsLinearSurface({ flags, env, output, input })) return 'linear';
|
|
112
|
-
if (flags.web || flags.noWindow) return 'web';
|
|
113
|
-
if (flags.window || env.CONSTRUCT_CHAT_WINDOW === '1' || hasGuiDisplay(env)) return 'desktop';
|
|
114
|
-
return 'headless';
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Desktop means the native construct-chat window: an existing binary, or Tauri
|
|
118
|
-
// source that runDesktopChat builds on demand. A browser does not qualify;
|
|
119
|
-
// `construct chat --web` is the explicit browser path.
|
|
120
|
-
|
|
121
|
-
async function canLaunchDesktop(env, cwd = process.cwd()) {
|
|
122
|
-
const { hasGuiDisplay, resolveDesktopBinary } = await import('./desktop-binary.mjs');
|
|
123
|
-
if (!hasGuiDisplay(env)) return false;
|
|
124
|
-
if (resolveDesktopBinary()) return true;
|
|
125
|
-
const { hasDesktopSource } = await import('./desktop-build.mjs');
|
|
126
|
-
return hasDesktopSource(cwd);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function resolveAsciiMode({ flags, env, config }) {
|
|
130
|
-
if (flags.ascii === true) return true;
|
|
131
|
-
if (env.CX_CHAT_ASCII === '1') return true;
|
|
132
|
-
if (config.ui?.ascii) return true;
|
|
133
|
-
if (process.platform === 'win32' && !env.WT_SESSION && !env.WT_PROFILE_ID && env.TERM !== 'xterm-256color') {
|
|
134
|
-
return true;
|
|
135
|
-
}
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function createPermissionHandler({ session, bridge }) {
|
|
140
|
-
return async (req) => {
|
|
141
|
-
const mode = session.permissionMode || 'allow_once';
|
|
142
|
-
if (mode === 'reject') return 'reject';
|
|
143
|
-
if (mode === 'allow_always') return 'allow_always';
|
|
144
|
-
if (mode === 'allow_once') return 'allow';
|
|
145
|
-
if (mode === 'ask' && bridge?.prompt) return bridge.prompt(req);
|
|
146
|
-
return 'allow';
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export async function runChat(args = [], { env = process.env, cwd = process.cwd(), input = process.stdin, output = process.stdout, errorOutput = process.stderr } = {}) {
|
|
151
|
-
const flags = parseFlags(args);
|
|
152
|
-
if (!flags.demo && env.CONSTRUCT_DEMO) flags.demo = env.CONSTRUCT_DEMO;
|
|
153
|
-
|
|
154
|
-
const effectiveEnv = loadConstructEnv({ rootDir: cwd, env: flags.plain ? { ...env, NO_COLOR: '1' } : env, warn: false });
|
|
155
|
-
|
|
156
|
-
if (flags.list) { printModels(output, effectiveEnv); return 0; }
|
|
157
|
-
|
|
158
|
-
const { config } = loadChatConfig({ cwd });
|
|
159
|
-
|
|
160
|
-
let modelMode = config.modelMode || 'pinned';
|
|
161
|
-
if (flags.free) modelMode = 'free-router';
|
|
162
|
-
|
|
163
|
-
const savedModel = flags.model || (modelMode === 'pinned' ? config.model : null) || null;
|
|
164
|
-
|
|
165
|
-
let resolvedModel = savedModel;
|
|
166
|
-
if (modelMode === 'free-router') {
|
|
167
|
-
const sessionStub = { modelMode: 'free-router', failedModels: new Set() };
|
|
168
|
-
resolvedModel = await resolveSessionModel(sessionStub, { env: effectiveEnv, tier: 'standard' });
|
|
169
|
-
if (!resolvedModel && !flags.list) {
|
|
170
|
-
errorOutput.write('OpenRouter free router needs OPENROUTER_API_KEY in ~/.construct/config.env, or no free models are available.\n');
|
|
171
|
-
return 1;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Saved layers seed the run; --no-* overrides win for this launch.
|
|
176
|
-
const layers = resolveLayers({ flags, env: effectiveEnv });
|
|
177
|
-
for (const key of Object.keys(config.layers)) {
|
|
178
|
-
if (flags[key] === undefined && config.layers[key] === false) layers[key] = false;
|
|
179
|
-
}
|
|
180
|
-
if (config.thinking === false && flags.thinking === undefined) layers.thinking = false;
|
|
181
|
-
|
|
182
|
-
const resumePath = resolveResumePath({ cwd, resume: flags.resume });
|
|
183
|
-
const restored = resumePath ? restoreFromSession(resumePath) : null;
|
|
184
|
-
|
|
185
|
-
const demoPack = flags.demo
|
|
186
|
-
? resolveDemoGuideForChat(flags.demo, { cwd, repoRoot: REPO_ROOT })
|
|
187
|
-
: null;
|
|
188
|
-
|
|
189
|
-
const modelResolution = modelMode === 'free-router' && resolvedModel
|
|
190
|
-
? { id: resolvedModel, source: 'free-router', notice: null, rejected: [] }
|
|
191
|
-
: await resolveChatModelSelectionAsync({
|
|
192
|
-
env: effectiveEnv,
|
|
193
|
-
requested: savedModel,
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
const session = {
|
|
197
|
-
model: modelResolution.id || resolvedModel,
|
|
198
|
-
savedModel: modelMode === 'pinned' ? (savedModel || modelResolution.id) : null,
|
|
199
|
-
modelMode,
|
|
200
|
-
modelNotice: modelResolution.notice || null,
|
|
201
|
-
failedModels: new Set(),
|
|
202
|
-
layers,
|
|
203
|
-
thinking: layers.thinking,
|
|
204
|
-
permissionMode: config.permissionMode || 'allow_once',
|
|
205
|
-
sandbox: config.sandbox || 'workspace-write',
|
|
206
|
-
ui: {
|
|
207
|
-
ascii: resolveAsciiMode({ flags, env: effectiveEnv, config }),
|
|
208
|
-
inspector: config.ui?.inspector || 'auto',
|
|
209
|
-
theme: config.ui?.theme || 'auto',
|
|
210
|
-
},
|
|
211
|
-
usage: restored?.usage || createSessionUsage(),
|
|
212
|
-
demoGuide: demoPack?.guide || null,
|
|
213
|
-
demoTitle: demoPack?.script?.title || null,
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
let surface = resolveChatSurface({ flags, env: effectiveEnv, output, input });
|
|
217
|
-
|
|
218
|
-
if (surface === 'headless') {
|
|
219
|
-
errorOutput.write('No graphical display available. Use `construct chat --plain` for terminal mode.\n');
|
|
220
|
-
return 1;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (surface === 'desktop') {
|
|
224
|
-
if (!(await canLaunchDesktop(effectiveEnv, cwd))) {
|
|
225
|
-
errorOutput.write('No graphical display available. Use `construct chat --plain` for terminal mode, or `construct chat --web` for a browser tab.\n');
|
|
226
|
-
return 1;
|
|
227
|
-
}
|
|
228
|
-
try {
|
|
229
|
-
const { runDesktopChat } = await import('./desktop-launcher.mjs');
|
|
230
|
-
return runDesktopChat({ cwd, env: effectiveEnv, output, errorOutput });
|
|
231
|
-
} catch (err) {
|
|
232
|
-
errorOutput.write(`Desktop chat unavailable: ${err.message}\n`);
|
|
233
|
-
return 1;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (surface === 'web') {
|
|
238
|
-
try {
|
|
239
|
-
const { runWebChat } = await import('./web-launcher.mjs');
|
|
240
|
-
return runWebChat({ cwd, env: effectiveEnv, output, errorOutput });
|
|
241
|
-
} catch (err) {
|
|
242
|
-
errorOutput.write(`Web chat unavailable: ${err.message}\n`);
|
|
243
|
-
return 1;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const permissionBridge = { prompt: null };
|
|
248
|
-
const handlers = {
|
|
249
|
-
getSandbox: () => session.sandbox,
|
|
250
|
-
getPermissionMode: () => session.permissionMode,
|
|
251
|
-
requestPermission: createPermissionHandler({ session, bridge: permissionBridge }),
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
const createAgent = async (opts) => {
|
|
255
|
-
const { createAiSdkAgent } = await import('../../apps/chat/engine/ai-sdk-agent.mjs');
|
|
256
|
-
return createAiSdkAgent(opts);
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
const driver = createOwnedLoopDriver({
|
|
260
|
-
env: effectiveEnv,
|
|
261
|
-
cwd,
|
|
262
|
-
model: session.model,
|
|
263
|
-
handlers,
|
|
264
|
-
systemPrompt: (() => {
|
|
265
|
-
const base = buildSystemPrompt({ capabilityTier: compileExecutionPolicy({ profile: resolveExecutionCapabilityProfile({ model: session.model }) }).prompt.systemPromptTier });
|
|
266
|
-
return demoPack?.systemOverlay ? `${base}\n\nDemo script:\n${demoPack.systemOverlay}` : base;
|
|
267
|
-
})(),
|
|
268
|
-
createAgent,
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
let started;
|
|
272
|
-
try {
|
|
273
|
-
started = await driver.start();
|
|
274
|
-
} catch (err) {
|
|
275
|
-
const hint = /Cannot find package|ERR_MODULE_NOT_FOUND|find module/i.test(err.message)
|
|
276
|
-
? '\nThe owned loop needs its optional dependencies. Install them with:\n npm install ai @ai-sdk/anthropic @ai-sdk/openai @ai-sdk/openai-compatible zod'
|
|
277
|
-
: '';
|
|
278
|
-
errorOutput.write(`Failed to start the owned loop: ${err.message}${hint}\n`);
|
|
279
|
-
try { driver.stop?.(); } catch { /* nothing started */ }
|
|
280
|
-
return 1;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const notices = [];
|
|
284
|
-
if (modelResolution.notice) notices.push(modelResolution.notice);
|
|
285
|
-
if (demoPack?.welcome) notices.push(demoPack.welcome);
|
|
286
|
-
if (!session.model) {
|
|
287
|
-
notices.push('no model resolved \u2014 set a provider key or run `construct creds login copilot`, then use /model');
|
|
288
|
-
}
|
|
289
|
-
if (flags.resume && !restored?.sessionFile) {
|
|
290
|
-
notices.push('no prior session found to resume \u2014 starting fresh');
|
|
291
|
-
} else if (restored?.sessionFile && (restored.turnBlocks?.length || restored.transcript.length)) {
|
|
292
|
-
const count = restored.turnBlocks?.length || restored.transcript.length;
|
|
293
|
-
notices.push(`resumed ${count} turn(s) from ${path.basename(restored.sessionFile)}`);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const persist = createPersister({ cwd, sessionId: started.sessionId, resumePath: restored?.sessionFile || null });
|
|
297
|
-
const commands = createCommands({
|
|
298
|
-
driver,
|
|
299
|
-
host: 'construct',
|
|
300
|
-
hostId: 'construct',
|
|
301
|
-
version: null,
|
|
302
|
-
cwd,
|
|
303
|
-
env: effectiveEnv,
|
|
304
|
-
turnBlocksRef: () => [],
|
|
305
|
-
demoGuide: demoPack?.guide || null,
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
try {
|
|
309
|
-
await runChatLoop({
|
|
310
|
-
driver,
|
|
311
|
-
host: 'construct',
|
|
312
|
-
version: null,
|
|
313
|
-
layers,
|
|
314
|
-
input,
|
|
315
|
-
env: effectiveEnv,
|
|
316
|
-
output,
|
|
317
|
-
persist,
|
|
318
|
-
commands,
|
|
319
|
-
session,
|
|
320
|
-
notices,
|
|
321
|
-
permissionBridge,
|
|
322
|
-
initialTranscript: restored?.transcript || [],
|
|
323
|
-
initialTurnBlocks: restored?.turnBlocks || [],
|
|
324
|
-
cwd,
|
|
325
|
-
flags,
|
|
326
|
-
});
|
|
327
|
-
} finally {
|
|
328
|
-
try { driver.stop?.(); } catch { /* already stopped */ }
|
|
329
|
-
}
|
|
330
|
-
return 0;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
export { resolveChatSurface, wantsLinearSurface };
|