@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
package/lib/migrations/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/migrations/index.mjs — Schema migration registry and runner.
|
|
3
3
|
*
|
|
4
|
-
* Construct's machine-readable artifacts (specialists/
|
|
5
|
-
*
|
|
4
|
+
* Construct's machine-readable artifacts (specialists/org/, .cx/config.json,
|
|
5
|
+
* .cx/config.json, profile JSON) carry an integer
|
|
6
6
|
* `version` field. When the installed binary's expected schema version is
|
|
7
7
|
* higher than the on-disk version, migrations apply in order. When the on-disk
|
|
8
8
|
* version is higher, the runner refuses to load and instructs the operator to
|
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import v1Baseline from './v1-baseline.mjs';
|
|
21
|
+
import v2UnifiedRegistry from './v2-unified-registry.mjs';
|
|
21
22
|
|
|
22
23
|
const REGISTRY = [
|
|
23
24
|
v1Baseline,
|
|
25
|
+
v2UnifiedRegistry,
|
|
24
26
|
];
|
|
25
27
|
|
|
26
28
|
export const CURRENT_SCHEMA_VERSION = Math.max(...REGISTRY.map((m) => m.to));
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/migrations/v2-unified-registry.mjs — v1 → v2 schema stamp.
|
|
3
|
+
*
|
|
4
|
+
* v2 is the version that unified-registry.json was born at when the legacy
|
|
5
|
+
* specialists/registry.json + contracts.json + teams.json were consolidated
|
|
6
|
+
* into a single unified file. Artifacts already at v2 need no shape change.
|
|
7
|
+
* For any v1 artifact carrying the old flat-registry shape, this stamps the
|
|
8
|
+
* version field so the migrate runner reports "already at v2" on re-run.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
12
|
+
|
|
13
|
+
export default {
|
|
14
|
+
from: 1,
|
|
15
|
+
to: 2,
|
|
16
|
+
description: 'Stamp legacy v1 artifacts with version: 2 (no shape changes required)',
|
|
17
|
+
async apply(artifactPath, { dryRun = false } = {}) {
|
|
18
|
+
if (!existsSync(artifactPath)) {
|
|
19
|
+
return { changed: false, summary: `${artifactPath}: not present (skip)` };
|
|
20
|
+
}
|
|
21
|
+
let data;
|
|
22
|
+
try { data = JSON.parse(readFileSync(artifactPath, 'utf8')); }
|
|
23
|
+
catch (err) { throw new Error(`failed to parse ${artifactPath}: ${err.message}`); }
|
|
24
|
+
|
|
25
|
+
if (data && typeof data === 'object' && data.version === 2) {
|
|
26
|
+
return { changed: false, summary: `${artifactPath}: already at v2` };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const next = { ...data, version: 2 };
|
|
30
|
+
if (!dryRun) {
|
|
31
|
+
writeFileSync(artifactPath, JSON.stringify(next, null, 2) + '\n');
|
|
32
|
+
}
|
|
33
|
+
return { changed: true, summary: `${artifactPath}: stamped version: 2` };
|
|
34
|
+
},
|
|
35
|
+
};
|
package/lib/model-router.mjs
CHANGED
|
@@ -38,10 +38,13 @@ import {
|
|
|
38
38
|
} from "./model-free-selector.mjs";
|
|
39
39
|
import {
|
|
40
40
|
applyModelVisibilityFilter,
|
|
41
|
+
collectTierDefaultIds,
|
|
41
42
|
loadModelsCatalogContext,
|
|
42
43
|
mergeLiveModelsIntoProviders,
|
|
44
|
+
providerFamilyEnabled,
|
|
43
45
|
} from "./models/catalog.mjs";
|
|
44
46
|
import { configDir } from "./config/xdg.mjs";
|
|
47
|
+
import { getUserEnvPath } from "./env-config.mjs";
|
|
45
48
|
|
|
46
49
|
function uniqueStrings(values = []) {
|
|
47
50
|
return [...new Set(values.filter((value) => typeof value === "string" && value.trim()))];
|
|
@@ -448,7 +451,7 @@ export function getProviderModelCatalog({
|
|
|
448
451
|
} = {}) {
|
|
449
452
|
const baseProviders = PROVIDER_FAMILY_TIERS.map((family) => {
|
|
450
453
|
const tiers = family.resolve({});
|
|
451
|
-
const
|
|
454
|
+
const staticOptions = {
|
|
452
455
|
reasoning: uniqueStrings([...(family.options?.reasoning ?? []), tiers.reasoning]),
|
|
453
456
|
standard: uniqueStrings([...(family.options?.standard ?? []), tiers.standard]),
|
|
454
457
|
fast: uniqueStrings([...(family.options?.fast ?? []), tiers.fast]),
|
|
@@ -457,7 +460,8 @@ export function getProviderModelCatalog({
|
|
|
457
460
|
id: family.id,
|
|
458
461
|
label: family.label,
|
|
459
462
|
tiers,
|
|
460
|
-
|
|
463
|
+
staticOptions,
|
|
464
|
+
options: staticOptions,
|
|
461
465
|
local: family.local === true,
|
|
462
466
|
requiresEnv: Array.isArray(family.requiresEnv) ? family.requiresEnv : [],
|
|
463
467
|
pricingHint: family.pricingHint ?? null,
|
|
@@ -488,6 +492,54 @@ export function getProviderModelCatalog({
|
|
|
488
492
|
);
|
|
489
493
|
}
|
|
490
494
|
|
|
495
|
+
/**
|
|
496
|
+
* Tier pins and option lists for configured provider families limit live
|
|
497
|
+
* OpenRouter polls in model-selection surfaces without dumping the full catalog.
|
|
498
|
+
*/
|
|
499
|
+
export function getPickerModelAllowlist({
|
|
500
|
+
env = process.env,
|
|
501
|
+
cwd = process.cwd(),
|
|
502
|
+
currentModel = null,
|
|
503
|
+
modelsConfig = null,
|
|
504
|
+
registryModels = null,
|
|
505
|
+
} = {}) {
|
|
506
|
+
const ctx = loadModelsCatalogContext({ cwd, env });
|
|
507
|
+
const vis = modelsConfig?.visibility ?? ctx.modelsConfig.visibility;
|
|
508
|
+
const registry = registryModels ?? ctx.registryModels;
|
|
509
|
+
const tierDefaults = collectTierDefaultIds(registry);
|
|
510
|
+
const allowed = new Set();
|
|
511
|
+
|
|
512
|
+
if (currentModel) allowed.add(currentModel);
|
|
513
|
+
|
|
514
|
+
for (const family of PROVIDER_FAMILY_TIERS) {
|
|
515
|
+
if (!isProviderConfigured(family.id, env)) continue;
|
|
516
|
+
if (!providerFamilyEnabled(family.id, vis)) continue;
|
|
517
|
+
|
|
518
|
+
const tiers = family.resolve({});
|
|
519
|
+
const pins = uniqueStrings(['reasoning', 'standard', 'fast'].map((tier) => tiers[tier]));
|
|
520
|
+
const shipped = uniqueStrings(
|
|
521
|
+
['reasoning', 'standard', 'fast'].flatMap((tier) => family.options?.[tier] || []),
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
if (vis.mode === 'explicit') {
|
|
525
|
+
for (const id of vis.include || []) allowed.add(id);
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
if (vis.mode === 'all_configured') {
|
|
529
|
+
for (const id of [...pins, ...shipped]) allowed.add(id);
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
if (tierDefaults.length > 0) {
|
|
533
|
+
for (const id of [...tierDefaults, ...pins]) allowed.add(id);
|
|
534
|
+
} else {
|
|
535
|
+
for (const id of [...pins, ...shipped]) allowed.add(id);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
for (const id of vis.exclude || []) allowed.delete(id);
|
|
540
|
+
return allowed;
|
|
541
|
+
}
|
|
542
|
+
|
|
491
543
|
// Provider families with open-ended model catalogs: credential presence is the
|
|
492
544
|
// gate; static option lists are hints, not exhaustive allowlists.
|
|
493
545
|
|
|
@@ -504,16 +556,44 @@ const LENIENT_MODEL_FAMILIES = new Set([
|
|
|
504
556
|
'local',
|
|
505
557
|
]);
|
|
506
558
|
|
|
559
|
+
export const RETIRED_MODEL_SLUGS = new Set([
|
|
560
|
+
'anthropic/claude-3.5-sonnet',
|
|
561
|
+
'openrouter/anthropic/claude-3.5-sonnet',
|
|
562
|
+
]);
|
|
563
|
+
|
|
564
|
+
function shippedModelIdsForFamily(family) {
|
|
565
|
+
if (!family) return [];
|
|
566
|
+
const tiers = family.resolve({});
|
|
567
|
+
return uniqueStrings([
|
|
568
|
+
tiers.reasoning,
|
|
569
|
+
tiers.standard,
|
|
570
|
+
tiers.fast,
|
|
571
|
+
...(family.options?.reasoning ?? []),
|
|
572
|
+
...(family.options?.standard ?? []),
|
|
573
|
+
...(family.options?.fast ?? []),
|
|
574
|
+
]);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function isLenientPollSlug(modelId) {
|
|
578
|
+
return modelId === 'openrouter/free'
|
|
579
|
+
|| modelId === 'openrouter/openrouter/free'
|
|
580
|
+
|| /:free$/i.test(modelId);
|
|
581
|
+
}
|
|
582
|
+
|
|
507
583
|
/**
|
|
508
584
|
* Check whether a model id can be used on this machine right now: the provider
|
|
509
585
|
* family must match, credentials must be present (including op:// and Copilot
|
|
510
586
|
* auth-store), and for fixed-catalog families (e.g. github-copilot) the id must
|
|
511
587
|
* appear in the shipped option list or tier defaults — not a stale CX_MODEL pin.
|
|
512
588
|
*/
|
|
513
|
-
export function
|
|
589
|
+
export function isModelAvailable(modelId, { env = process.env, excludeFamilies = [], strictPin = false } = {}) {
|
|
514
590
|
if (!modelId || typeof modelId !== 'string') {
|
|
515
591
|
return { ok: false, reason: 'missing', modelId: modelId || null };
|
|
516
592
|
}
|
|
593
|
+
if (RETIRED_MODEL_SLUGS.has(modelId)) {
|
|
594
|
+
const family = matchProviderFamily(modelId);
|
|
595
|
+
return { ok: false, reason: 'model_retired', modelId, provider: family?.id || null };
|
|
596
|
+
}
|
|
517
597
|
const family = matchProviderFamily(modelId);
|
|
518
598
|
if (!family) {
|
|
519
599
|
return { ok: false, reason: 'unknown_family', modelId };
|
|
@@ -543,7 +623,14 @@ export function isChatModelAvailable(modelId, { env = process.env, excludeFamili
|
|
|
543
623
|
return { ok: true, modelId, provider: family.id };
|
|
544
624
|
}
|
|
545
625
|
if (LENIENT_MODEL_FAMILIES.has(family.id)) {
|
|
546
|
-
|
|
626
|
+
if (!strictPin || isLenientPollSlug(modelId)) {
|
|
627
|
+
return { ok: true, modelId, provider: family.id };
|
|
628
|
+
}
|
|
629
|
+
const shipped = shippedModelIdsForFamily(family);
|
|
630
|
+
if (shipped.includes(modelId)) {
|
|
631
|
+
return { ok: true, modelId, provider: family.id };
|
|
632
|
+
}
|
|
633
|
+
return { ok: false, reason: 'model_not_available', modelId, provider: family.id };
|
|
547
634
|
}
|
|
548
635
|
const { providers } = getProviderModelCatalog({ env });
|
|
549
636
|
const provider = providers.find((p) => p.id === family.id);
|
|
@@ -564,27 +651,105 @@ export function isChatModelAvailable(modelId, { env = process.env, excludeFamili
|
|
|
564
651
|
return { ok: false, reason: 'model_not_available', modelId, provider: family.id };
|
|
565
652
|
}
|
|
566
653
|
|
|
567
|
-
function
|
|
568
|
-
|
|
569
|
-
const
|
|
654
|
+
export function listAvailableModels({ env = process.env, cwd = process.cwd(), activeModelId = null } = {}) {
|
|
655
|
+
const { providers } = getProviderModelCatalog({ env, cwd, activeModelId });
|
|
656
|
+
const models = [];
|
|
657
|
+
const seen = new Set();
|
|
658
|
+
for (const provider of providers) {
|
|
659
|
+
for (const tier of ['reasoning', 'standard', 'fast']) {
|
|
660
|
+
for (const id of provider.options?.[tier] || []) {
|
|
661
|
+
if (seen.has(id)) continue;
|
|
662
|
+
seen.add(id);
|
|
663
|
+
models.push({
|
|
664
|
+
id,
|
|
665
|
+
label: id,
|
|
666
|
+
provider: provider.id,
|
|
667
|
+
configured: provider.configured,
|
|
668
|
+
local: provider.local === true,
|
|
669
|
+
suitable: true,
|
|
670
|
+
tier,
|
|
671
|
+
available: isModelAvailable(id, { env }).ok,
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return models.sort((a, b) => Number(b.configured) - Number(a.configured) || a.id.localeCompare(b.id));
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Structured notices when a pinned model cannot run and a fallback is used.
|
|
681
|
+
* Returns multiple lines; `notice` on the resolution object joins them for
|
|
682
|
+
* single-string consumers.
|
|
683
|
+
*/
|
|
684
|
+
export function formatModelResolutionNotices({
|
|
685
|
+
rejected,
|
|
686
|
+
activeId = null,
|
|
687
|
+
activeProviderLabel = null,
|
|
688
|
+
configEnvPath = null,
|
|
689
|
+
} = {}) {
|
|
690
|
+
if (!rejected?.modelId) return [];
|
|
691
|
+
const pinned = rejected.modelId;
|
|
692
|
+
const configPath = configEnvPath || getUserEnvPath();
|
|
693
|
+
const lines = [];
|
|
694
|
+
|
|
570
695
|
if (rejected.reason === 'provider_not_configured') {
|
|
571
696
|
const family = rejected.provider || '';
|
|
697
|
+
lines.push(`Pinned ${pinned}`);
|
|
572
698
|
if (family.startsWith('openrouter')) {
|
|
573
|
-
|
|
699
|
+
lines.push(`OpenRouter unavailable: OPENROUTER_API_KEY not found in ${configPath}`);
|
|
700
|
+
lines.push('Set a value or op:// ref in config.env, run `node scripts/setup-credentials.mjs`, or launch with `op run --env-file=…`');
|
|
701
|
+
} else {
|
|
702
|
+
lines.push(`Provider unavailable: credentials not configured for ${family || 'requested provider'}`);
|
|
703
|
+
lines.push(`Set keys in ${configPath} or run \`construct creds set <provider>\``);
|
|
574
704
|
}
|
|
575
|
-
|
|
705
|
+
if (activeId) {
|
|
706
|
+
const suffix = activeProviderLabel ? ` (${activeProviderLabel})` : '';
|
|
707
|
+
lines.push(`Active model: ${activeId}${suffix}`);
|
|
708
|
+
}
|
|
709
|
+
return lines;
|
|
576
710
|
}
|
|
577
|
-
|
|
578
|
-
|
|
711
|
+
|
|
712
|
+
if (rejected.reason === 'model_not_available' || rejected.reason === 'model_retired') {
|
|
713
|
+
lines.push(`Pinned ${pinned} — not available on your account.`);
|
|
714
|
+
if (activeId) {
|
|
715
|
+
const suffix = activeProviderLabel ? ` (${activeProviderLabel})` : '';
|
|
716
|
+
lines.push(`Active model: ${activeId}${suffix}`);
|
|
717
|
+
}
|
|
718
|
+
return lines;
|
|
579
719
|
}
|
|
720
|
+
|
|
580
721
|
if (rejected.reason === 'model_not_pulled') {
|
|
581
|
-
const native = rejected.nativeModel || toOllamaNativeModelId(
|
|
582
|
-
|
|
722
|
+
const native = rejected.nativeModel || toOllamaNativeModelId(pinned);
|
|
723
|
+
lines.push(`Pinned ${pinned} — not installed locally. Run \`ollama pull ${native}\` or \`construct ollama pull ${native}\`.`);
|
|
724
|
+
if (activeId) {
|
|
725
|
+
const suffix = activeProviderLabel ? ` (${activeProviderLabel})` : '';
|
|
726
|
+
lines.push(`Active model: ${activeId}${suffix}`);
|
|
727
|
+
}
|
|
728
|
+
return lines;
|
|
583
729
|
}
|
|
730
|
+
|
|
584
731
|
if (rejected.reason === 'unknown_family') {
|
|
585
|
-
|
|
732
|
+
lines.push(`Pinned ${pinned} — unrecognized model id.`);
|
|
733
|
+
return lines;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
lines.push(`Pinned ${pinned} — unavailable.`);
|
|
737
|
+
if (activeId) {
|
|
738
|
+
const suffix = activeProviderLabel ? ` (${activeProviderLabel})` : '';
|
|
739
|
+
lines.push(`Active model: ${activeId}${suffix}`);
|
|
586
740
|
}
|
|
587
|
-
return
|
|
741
|
+
return lines;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function resolutionNotices(rejections, activeId, activeProviderLabel) {
|
|
745
|
+
if (!rejections.length) return { notice: null, notices: [] };
|
|
746
|
+
const notices = formatModelResolutionNotices({
|
|
747
|
+
rejected: rejections[0],
|
|
748
|
+
activeId,
|
|
749
|
+
activeProviderLabel,
|
|
750
|
+
configEnvPath: getUserEnvPath(),
|
|
751
|
+
});
|
|
752
|
+
return { notice: notices.length ? notices.join(' ') : null, notices };
|
|
588
753
|
}
|
|
589
754
|
|
|
590
755
|
function recommendTierModel(tier, { env = process.env, excludeFamilies = [] } = {}) {
|
|
@@ -596,7 +761,7 @@ function recommendTierModel(tier, { env = process.env, excludeFamilies = [] } =
|
|
|
596
761
|
...(provider.options?.[tier] ?? []),
|
|
597
762
|
]);
|
|
598
763
|
for (const id of candidates) {
|
|
599
|
-
const check =
|
|
764
|
+
const check = isModelAvailable(id, { env, excludeFamilies });
|
|
600
765
|
if (check.ok) return { id, provider: provider.label, tier };
|
|
601
766
|
}
|
|
602
767
|
}
|
|
@@ -604,17 +769,17 @@ function recommendTierModel(tier, { env = process.env, excludeFamilies = [] } =
|
|
|
604
769
|
}
|
|
605
770
|
|
|
606
771
|
/**
|
|
607
|
-
* Resolve
|
|
772
|
+
* Resolve a model with validation: explicit request and CX_MODEL pins
|
|
608
773
|
* are preferences, not blind truth. Stale or unconfigured pins fall through to
|
|
609
774
|
* the next configured provider for the tier, with a human-readable notice.
|
|
610
775
|
*/
|
|
611
|
-
export function
|
|
776
|
+
export function resolveValidatedModel({ env = process.env, requested = null, excludeFamilies = [] } = {}) {
|
|
612
777
|
const rejections = [];
|
|
613
778
|
|
|
614
779
|
if (requested) {
|
|
615
|
-
const check =
|
|
780
|
+
const check = isModelAvailable(requested, { env, excludeFamilies, strictPin: true });
|
|
616
781
|
if (check.ok) {
|
|
617
|
-
return { id: requested, source: 'explicit', notice: null, rejected: [] };
|
|
782
|
+
return { id: requested, source: 'explicit', notice: null, notices: [], rejected: [] };
|
|
618
783
|
}
|
|
619
784
|
rejections.push(check);
|
|
620
785
|
}
|
|
@@ -626,34 +791,36 @@ export function resolveValidatedChatModel({ env = process.env, requested = null,
|
|
|
626
791
|
];
|
|
627
792
|
for (const [tier, pin] of pinOrder) {
|
|
628
793
|
if (!pin) continue;
|
|
629
|
-
const check =
|
|
794
|
+
const check = isModelAvailable(pin, { env, excludeFamilies, strictPin: true });
|
|
630
795
|
if (check.ok) {
|
|
631
|
-
const notice = rejections.length
|
|
632
|
-
?
|
|
633
|
-
: null;
|
|
634
|
-
return { id: pin, source: 'pin', tier, notice, rejected: rejections };
|
|
796
|
+
const { notice, notices } = rejections.length
|
|
797
|
+
? resolutionNotices(rejections, pin, null)
|
|
798
|
+
: { notice: null, notices: [] };
|
|
799
|
+
return { id: pin, source: 'pin', tier, notice, notices, rejected: rejections };
|
|
635
800
|
}
|
|
636
801
|
rejections.push(check);
|
|
637
802
|
}
|
|
638
803
|
|
|
639
804
|
const standard = recommendTierModel('standard', { env, excludeFamilies });
|
|
640
805
|
if (standard) {
|
|
641
|
-
const notice = rejections.length
|
|
642
|
-
?
|
|
643
|
-
: null;
|
|
644
|
-
return { id: standard.id, source: 'recommended', tier: 'standard', notice, rejected: rejections };
|
|
806
|
+
const { notice, notices } = rejections.length
|
|
807
|
+
? resolutionNotices(rejections, standard.id, standard.provider)
|
|
808
|
+
: { notice: null, notices: [] };
|
|
809
|
+
return { id: standard.id, source: 'recommended', tier: 'standard', notice, notices, rejected: rejections };
|
|
645
810
|
}
|
|
646
811
|
|
|
647
812
|
const anyTier = recommendTierModel('reasoning', { env, excludeFamilies }) || recommendTierModel('fast', { env, excludeFamilies });
|
|
648
813
|
if (anyTier) {
|
|
649
|
-
const notice = rejections.length
|
|
650
|
-
?
|
|
651
|
-
: null;
|
|
652
|
-
return { id: anyTier.id, source: 'recommended', tier: anyTier.tier, notice, rejected: rejections };
|
|
814
|
+
const { notice, notices } = rejections.length
|
|
815
|
+
? resolutionNotices(rejections, anyTier.id, anyTier.provider)
|
|
816
|
+
: { notice: null, notices: [] };
|
|
817
|
+
return { id: anyTier.id, source: 'recommended', tier: anyTier.tier, notice, notices, rejected: rejections };
|
|
653
818
|
}
|
|
654
819
|
|
|
655
|
-
const notice = rejections.length
|
|
656
|
-
|
|
820
|
+
const { notice, notices } = rejections.length
|
|
821
|
+
? resolutionNotices(rejections, null, null)
|
|
822
|
+
: { notice: null, notices: [] };
|
|
823
|
+
return { id: null, source: null, notice, notices, rejected: rejections };
|
|
657
824
|
}
|
|
658
825
|
|
|
659
826
|
/**
|
|
@@ -893,7 +1060,7 @@ export function classifyProviderFailure(input) {
|
|
|
893
1060
|
|
|
894
1061
|
const patterns = [
|
|
895
1062
|
{ kind: "rate_limit", retryable: true, test: /\b429\b|rate limit|usage limits?|too many requests|quota exceeded|weekly limit|monthly limit|daily limit/i },
|
|
896
|
-
{ kind: "provider_unavailable", retryable: true, test: /model unavailable|model.*overloaded|ProviderModelNotFoundError|model.*not found|no such model/i },
|
|
1063
|
+
{ kind: "provider_unavailable", retryable: true, test: /model unavailable|model.*overloaded|ProviderModelNotFoundError|model.*not found|no such model|no endpoints found/i },
|
|
897
1064
|
{ kind: "provider_unavailable", retryable: true, test: /service unavailable|temporarily unavailable|upstream error|server error|\b5\d\d\b/i },
|
|
898
1065
|
{ kind: "transient_network", retryable: true, test: /timeout|timed out|ETIMEDOUT|ECONNRESET|network error|fetch failed/i },
|
|
899
1066
|
{ kind: "auth_error", retryable: false, test: /unauthorized|forbidden|invalid api key|authentication failed/i },
|
package/lib/models/catalog.mjs
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
* lib/models/catalog.mjs — live model catalog merge, cache, and visibility filter.
|
|
3
3
|
*
|
|
4
4
|
* Merges static provider-family hints with cached OpenRouter free-model polls,
|
|
5
|
-
* then applies construct.config.json models.visibility rules so
|
|
5
|
+
* then applies construct.config.json models.visibility rules so CLI, hosts, and
|
|
6
6
|
* dashboard share one filtered catalog surface.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import fs from 'node:fs';
|
|
10
|
+
import { loadRegistry } from '../registry/loader.mjs';
|
|
10
11
|
import path from 'node:path';
|
|
11
12
|
import os from 'node:os';
|
|
12
13
|
import { pollFreeModels } from '../model-free-selector.mjs';
|
|
@@ -18,7 +19,7 @@ export const MODEL_VISIBILITY_MODES = ['all_configured', 'tier_defaults', 'expli
|
|
|
18
19
|
|
|
19
20
|
export const DEFAULT_MODELS_CONFIG = Object.freeze({
|
|
20
21
|
visibility: Object.freeze({
|
|
21
|
-
mode: '
|
|
22
|
+
mode: 'tier_defaults',
|
|
22
23
|
include: [],
|
|
23
24
|
exclude: [],
|
|
24
25
|
providers: {},
|
|
@@ -93,7 +94,7 @@ export async function refreshLiveOpenRouterCatalog({ env = process.env, homeDir
|
|
|
93
94
|
return models;
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
function providerFamilyEnabled(providerId, visibility) {
|
|
97
|
+
export function providerFamilyEnabled(providerId, visibility) {
|
|
97
98
|
const map = visibility.providers || {};
|
|
98
99
|
if (Object.keys(map).length === 0) return true;
|
|
99
100
|
if (map[providerId] === false) return false;
|
|
@@ -101,7 +102,7 @@ function providerFamilyEnabled(providerId, visibility) {
|
|
|
101
102
|
return map[providerId] !== false;
|
|
102
103
|
}
|
|
103
104
|
|
|
104
|
-
function collectTierDefaultIds(registryModels = {}) {
|
|
105
|
+
export function collectTierDefaultIds(registryModels = {}) {
|
|
105
106
|
const ids = [];
|
|
106
107
|
for (const tier of ['reasoning', 'standard', 'fast']) {
|
|
107
108
|
const def = registryModels[tier];
|
|
@@ -130,6 +131,18 @@ export function mergeLiveModelsIntoProviders(providers, liveModels = [], { maxLi
|
|
|
130
131
|
});
|
|
131
132
|
}
|
|
132
133
|
|
|
134
|
+
function collectShippedModelIds(provider) {
|
|
135
|
+
if (!provider) return [];
|
|
136
|
+
return uniqueStrings([
|
|
137
|
+
provider.tiers?.reasoning,
|
|
138
|
+
provider.tiers?.standard,
|
|
139
|
+
provider.tiers?.fast,
|
|
140
|
+
...(provider.staticOptions?.reasoning ?? []),
|
|
141
|
+
...(provider.staticOptions?.standard ?? []),
|
|
142
|
+
...(provider.staticOptions?.fast ?? []),
|
|
143
|
+
]);
|
|
144
|
+
}
|
|
145
|
+
|
|
133
146
|
export function applyModelVisibilityFilter(catalog, {
|
|
134
147
|
visibility = DEFAULT_MODELS_CONFIG.visibility,
|
|
135
148
|
registryModels = {},
|
|
@@ -138,6 +151,7 @@ export function applyModelVisibilityFilter(catalog, {
|
|
|
138
151
|
const includeSet = new Set(visibility.include || []);
|
|
139
152
|
const excludeSet = new Set(visibility.exclude || []);
|
|
140
153
|
const tierDefaults = collectTierDefaultIds(registryModels);
|
|
154
|
+
const providerById = new Map((catalog.providers || []).map((provider) => [provider.id, provider]));
|
|
141
155
|
|
|
142
156
|
const modelAllowed = (modelId, providerId) => {
|
|
143
157
|
if (!modelId) return false;
|
|
@@ -148,7 +162,11 @@ export function applyModelVisibilityFilter(catalog, {
|
|
|
148
162
|
return includeSet.has(modelId);
|
|
149
163
|
}
|
|
150
164
|
if (visibility.mode === 'tier_defaults') {
|
|
151
|
-
|
|
165
|
+
const shipped = collectShippedModelIds(providerById.get(providerId));
|
|
166
|
+
if (tierDefaults.length > 0) {
|
|
167
|
+
return tierDefaults.includes(modelId) || shipped.includes(modelId);
|
|
168
|
+
}
|
|
169
|
+
return shipped.includes(modelId);
|
|
152
170
|
}
|
|
153
171
|
return true;
|
|
154
172
|
};
|
|
@@ -183,10 +201,8 @@ export function loadModelsCatalogContext({ cwd = process.cwd(), env = process.en
|
|
|
183
201
|
const modelsConfig = resolveModelsConfig(config);
|
|
184
202
|
let registryModels = {};
|
|
185
203
|
try {
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
registryModels = JSON.parse(fs.readFileSync(registryPath, 'utf8')).models ?? {};
|
|
189
|
-
}
|
|
204
|
+
const registry = loadRegistry({ rootDir: cwd, skipValidation: true });
|
|
205
|
+
registryModels = registry?.models ?? {};
|
|
190
206
|
} catch { /* registry read is best-effort outside repo root */ }
|
|
191
207
|
const liveModels = modelsConfig.catalog.liveOpenRouter
|
|
192
208
|
? (readLiveCatalogCache({ homeDir }) ?? [])
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* record (construct-6zga.1.8).
|
|
4
4
|
*
|
|
5
5
|
* Consolidates the four formerly-scattered capability producers into one
|
|
6
|
-
* serializable, versioned record that
|
|
6
|
+
* serializable, versioned record that model routing (resolveCapabilityTier) and specialist
|
|
7
7
|
* prompt composition (resolveModelOperatingProfile via lib/prompt-composer.js) both
|
|
8
8
|
* consume. It is keyed by provider, requested/resolved model, adapter protocol, and
|
|
9
9
|
* observation time. Every capability declares an evidence source
|
|
@@ -57,8 +57,8 @@ export function providerGroupForModel(model) {
|
|
|
57
57
|
return seg || 'unknown';
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
// Adapter protocol mirrors
|
|
61
|
-
// Anthropic uses its native messages API, OpenAI and Copilot use
|
|
60
|
+
// Adapter protocol mirrors provider worker dispatch:
|
|
61
|
+
// Anthropic uses its native messages API, OpenAI and Copilot use completions,
|
|
62
62
|
// and every other provider goes through the OpenAI-compatible shim.
|
|
63
63
|
|
|
64
64
|
function adapterProtocolForGroup(group) {
|
|
@@ -81,11 +81,10 @@ function oneOf(allowed, value, fallback) {
|
|
|
81
81
|
return allowed.includes(value) ? value : fallback;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
//
|
|
85
|
-
//
|
|
84
|
+
// Routing overlays speak the orchestration vocabulary (INTENT_CLASSES, risk flags,
|
|
85
|
+
// externalResearch). These helpers map that vocabulary
|
|
86
86
|
// onto the compiler's stable input enums so the compiler stays decoupled from
|
|
87
|
-
// routing changes. A turn that follows a failed tool result is the recovery case
|
|
88
|
-
// the loop signals with toolFailure.
|
|
87
|
+
// routing changes. A turn that follows a failed tool result is the recovery case.
|
|
89
88
|
|
|
90
89
|
export function normalizePolicyIntent({ intent = null, assumptionsBlocked = false, toolFailure = false } = {}) {
|
|
91
90
|
if (toolFailure === true) return 'tool-failure';
|
|
@@ -240,10 +239,10 @@ export function compileExecutionPolicy({
|
|
|
240
239
|
}
|
|
241
240
|
|
|
242
241
|
/**
|
|
243
|
-
* Compile a policy directly from a
|
|
242
|
+
* Compile a policy directly from a routing overlay,
|
|
244
243
|
* normalizing its routing vocabulary into the compiler's input enums first.
|
|
245
244
|
*/
|
|
246
|
-
export function
|
|
245
|
+
export function compilePolicyFromRoutingOverlay({ profile = null, overlay = null, toolFailure = false } = {}) {
|
|
247
246
|
const intent = normalizePolicyIntent({
|
|
248
247
|
intent: overlay?.intent,
|
|
249
248
|
assumptionsBlocked: overlay?.assumptionsBlocked === true,
|
|
@@ -257,6 +256,8 @@ export function compilePolicyFromOverlay({ profile = null, overlay = null, toolF
|
|
|
257
256
|
return compileExecutionPolicy({ profile, intent, risk, evidenceRequirement });
|
|
258
257
|
}
|
|
259
258
|
|
|
259
|
+
export const compilePolicyFromOverlay = compilePolicyFromRoutingOverlay;
|
|
260
|
+
|
|
260
261
|
/**
|
|
261
262
|
* Hand-rolled validator (no ajv — Construct stays dependency-free at startup).
|
|
262
263
|
* Returns { valid, errors } against schemas/execution-policy.schema.json.
|
|
@@ -71,8 +71,16 @@ function openRouterCapabilities(model) {
|
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
function resolveSecretOrNull(varNames, opts) {
|
|
75
|
+
try {
|
|
76
|
+
return resolveFirstSecret(varNames, opts);
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
74
82
|
export async function pollOpenRouter({ env = process.env } = {}) {
|
|
75
|
-
const apiKey =
|
|
83
|
+
const apiKey = resolveSecretOrNull(['OPENROUTER_API_KEY', 'OPEN_ROUTER_API_KEY'], { env, allowAmbient: env === process.env });
|
|
76
84
|
const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
|
|
77
85
|
const data = await fetchJson('https://openrouter.ai/api/v1/models', { headers });
|
|
78
86
|
if (!data) return null;
|
|
@@ -80,10 +88,15 @@ export async function pollOpenRouter({ env = process.env } = {}) {
|
|
|
80
88
|
const models = [];
|
|
81
89
|
for (const m of list) {
|
|
82
90
|
if (!m?.id) continue;
|
|
91
|
+
const outputs = m.architecture?.output_modalities;
|
|
92
|
+
if (Array.isArray(outputs) && outputs.length && !outputs.includes('text')) continue;
|
|
93
|
+
if (/(embed|rerank|whisper|tts|dall-e|moderation|transcribe|sora)/i.test(m.id)) continue;
|
|
83
94
|
const free = isFreePricing(m.pricing);
|
|
84
95
|
const input = Number(m.pricing?.prompt);
|
|
85
96
|
const output = Number(m.pricing?.completion);
|
|
86
97
|
const caps = openRouterCapabilities(m);
|
|
98
|
+
const context = Number(m.context_length) || null;
|
|
99
|
+
if (!free && context && context < 8000) continue;
|
|
87
100
|
models.push({
|
|
88
101
|
id: `openrouter/${m.id}`,
|
|
89
102
|
label: m.name || m.id,
|
|
@@ -92,11 +105,12 @@ export async function pollOpenRouter({ env = process.env } = {}) {
|
|
|
92
105
|
pricing: Number.isFinite(input) && Number.isFinite(output)
|
|
93
106
|
? { input: input * 1_000_000, output: output * 1_000_000 }
|
|
94
107
|
: null,
|
|
95
|
-
context
|
|
108
|
+
context,
|
|
96
109
|
reasoning: caps.reasoning,
|
|
97
110
|
tools: caps.tools,
|
|
98
111
|
toolsKnown: true,
|
|
99
112
|
vision: caps.vision,
|
|
113
|
+
architecture: m.architecture || null,
|
|
100
114
|
source: 'live',
|
|
101
115
|
});
|
|
102
116
|
}
|
|
@@ -110,7 +124,7 @@ export async function pollOpenRouter({ env = process.env } = {}) {
|
|
|
110
124
|
const OPENAI_NON_CHAT = /(embedding|whisper|tts|audio|realtime|image|dall-e|moderation|search|babbage|davinci|transcribe|sora)/i;
|
|
111
125
|
|
|
112
126
|
export async function pollOpenAI({ env = process.env } = {}) {
|
|
113
|
-
const apiKey =
|
|
127
|
+
const apiKey = resolveSecretOrNull(['OPENAI_API_KEY'], { env, allowAmbient: env === process.env });
|
|
114
128
|
if (!apiKey) return null;
|
|
115
129
|
const data = await fetchJson('https://api.openai.com/v1/models', {
|
|
116
130
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
@@ -141,7 +155,7 @@ export async function pollOpenAI({ env = process.env } = {}) {
|
|
|
141
155
|
}
|
|
142
156
|
|
|
143
157
|
export async function pollAnthropic({ env = process.env } = {}) {
|
|
144
|
-
const apiKey =
|
|
158
|
+
const apiKey = resolveSecretOrNull(['ANTHROPIC_API_KEY'], { env, allowAmbient: env === process.env });
|
|
145
159
|
if (!apiKey) return null;
|
|
146
160
|
const data = await fetchJson('https://api.anthropic.com/v1/models?limit=100', {
|
|
147
161
|
headers: { 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' },
|