@geraldmaron/construct 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -10
- package/bin/construct +286 -220
- package/bin/construct-postinstall.mjs +0 -21
- package/commands/work/optimize-prompts.md +1 -1
- package/examples/distribution/sources/adr.md +2 -2
- package/examples/seed-observations/decisions.md +1 -1
- package/lib/artifact-loop-core.mjs +412 -0
- package/lib/artifact-manifest.mjs +6 -0
- package/lib/artifact-release-gate.mjs +115 -49
- package/lib/audit-rules.mjs +2 -4
- package/lib/audit-skills.mjs +32 -20
- package/lib/audit-specialists.mjs +44 -26
- package/lib/auto-docs.mjs +6 -5
- package/lib/brand-prose.mjs +3 -3
- package/lib/brand-tokens.mjs +2 -2
- package/lib/certification/artifact-fixtures.mjs +4 -1
- package/lib/certification/demo-parity.mjs +6 -32
- package/lib/certification/real-llm-scenarios.mjs +58 -13
- package/lib/certification/role-cards.mjs +7 -6
- package/lib/certification/role-overlays.mjs +4 -4
- package/lib/certification/runner.mjs +9 -2
- package/lib/certification/skill-inventory.mjs +34 -22
- package/lib/certification/skill-scenarios.mjs +21 -8
- package/lib/certification/specialist-contracts.mjs +4 -3
- package/lib/certification/specialist-scenarios.mjs +7 -6
- package/lib/certification/status.mjs +5 -4
- package/lib/cli-commands.mjs +56 -57
- package/lib/comment-lint.mjs +1 -5
- package/lib/completions.mjs +20 -2
- package/lib/config/legacy-config-migration.mjs +125 -0
- package/lib/config/schema.mjs +5 -5
- package/lib/config/source-targets.mjs +80 -0
- package/lib/contracts/validate.mjs +119 -51
- package/lib/decisions/golden.mjs +3 -2
- package/lib/decisions/registry.mjs +11 -6
- package/lib/demo-project.mjs +188 -0
- package/lib/demo-recording.mjs +9 -34
- package/lib/demo-script.mjs +2 -2
- package/lib/demo-surface.mjs +13 -131
- package/lib/demo-tour-renderer.mjs +92 -0
- package/lib/demo.mjs +69 -54
- package/lib/diagram-export.mjs +63 -14
- package/lib/doctor/source-checkout.mjs +3 -4
- package/lib/doctor/watchers/consistency.mjs +90 -22
- package/lib/doctor/watchers/credential-parity.mjs +97 -0
- package/lib/doctor/watchers/mcp-protocol.mjs +1 -1
- package/lib/doctor/watchers/service-health.mjs +4 -32
- package/lib/document-export.mjs +73 -9
- package/lib/document-extract.mjs +10 -198
- package/lib/embed/artifact.mjs +2 -3
- package/lib/embed/cli.mjs +1 -1
- package/lib/embed/config.mjs +1 -2
- package/lib/embed/customer-profiles.mjs +1 -1
- package/lib/embed/daemon.mjs +5 -6
- package/lib/embed/demand-fetch.mjs +114 -2
- package/lib/embed/docs-lifecycle.mjs +9 -5
- package/lib/embed/intake-metrics.mjs +2 -2
- package/lib/embed/notifications.mjs +2 -3
- package/lib/embed/recommendation-store.mjs +25 -22
- package/lib/embed/role-framing.mjs +4 -9
- package/lib/embedded-contract/capability.mjs +4 -2
- package/lib/embedded-contract/contract-version.mjs +1 -1
- package/lib/env-config.mjs +2 -2
- package/lib/flavors/loader.mjs +21 -19
- package/lib/graph/build-from-registry.mjs +31 -7
- package/lib/graph/staleness.mjs +1 -1
- package/lib/headhunt.mjs +82 -35
- package/lib/hooks/agent-tracker.mjs +2 -2
- package/lib/hooks/config-protection.mjs +1 -1
- package/lib/hooks/registry-sync.mjs +3 -3
- package/lib/host-capabilities.mjs +5 -5
- package/lib/improvement/controller.mjs +2 -2
- package/lib/init-unified.mjs +19 -21
- package/lib/intake/classify.mjs +26 -0
- package/lib/intake/daemon.mjs +2 -3
- package/lib/intake/prepare.mjs +10 -3
- package/lib/intake/session-prelude.mjs +1 -1
- package/lib/intake/tables/creative.mjs +9 -9
- package/lib/intake/tables/operations.mjs +4 -4
- package/lib/intake/tables/research.mjs +2 -2
- package/lib/integrations/intake-integrations.mjs +9 -10
- package/lib/knowledge/research-store.mjs +2 -2
- package/lib/mcp/server.mjs +104 -61
- package/lib/mcp/tool-budget.mjs +1 -46
- package/lib/mcp/tool-recovery.mjs +44 -0
- package/lib/mcp/tools/artifact-author.mjs +36 -0
- package/lib/mcp/tools/find-tool.mjs +86 -0
- package/lib/mcp/tools/orchestration-run.mjs +107 -69
- package/lib/mcp/tools/project.mjs +4 -3
- package/lib/mcp/tools/{profile.mjs → scope.mjs} +54 -52
- package/lib/mcp/tools/skills.mjs +39 -22
- package/lib/mcp/tools/telemetry.mjs +2 -1
- package/lib/mcp/tools/workflow.mjs +1 -1
- package/lib/mcp-platform-config.mjs +7 -5
- package/lib/migrations/index.mjs +4 -2
- package/lib/migrations/v2-unified-registry.mjs +35 -0
- package/lib/model-router.mjs +203 -36
- package/lib/models/catalog.mjs +25 -9
- package/lib/models/execution-capability-profile.mjs +3 -3
- package/lib/models/execution-policy.mjs +7 -6
- package/lib/models/provider-poll.mjs +18 -4
- package/lib/opencode-config.mjs +56 -1
- package/lib/opencode-runtime-plugin.mjs +13 -10
- package/lib/oracle/artifact-gate.mjs +14 -4
- package/lib/oracle/cli.mjs +2 -0
- package/lib/oracle/dispatch.mjs +18 -1
- package/lib/oracle/execute.mjs +39 -7
- package/lib/oracle/index.mjs +1 -1
- package/lib/oracle/org-graph.mjs +10 -5
- package/lib/oracle/policy.mjs +6 -0
- package/lib/oracle/read-model.mjs +103 -6
- package/lib/oracle/reconcile.mjs +31 -4
- package/lib/oracle/remediation-dispatch.mjs +53 -0
- package/lib/oracle/routing.mjs +5 -0
- package/lib/oracle/synthesize.mjs +80 -3
- package/lib/orchestration/routing-tables.mjs +44 -10
- package/lib/orchestration/runtime.mjs +2 -0
- package/lib/orchestration/worker.mjs +1 -1
- package/lib/orchestration-policy.mjs +351 -62
- package/lib/overrides/resolver.mjs +1 -12
- package/lib/parity.mjs +37 -11
- package/lib/policy/engine.mjs +80 -15
- package/lib/prompt-composer.js +51 -15
- package/lib/prompt-metadata.mjs +3 -2
- package/lib/providers/connection-probe.mjs +58 -0
- package/lib/providers/copilot-auth.mjs +1 -1
- package/lib/providers/credential-bootstrap.mjs +1 -1
- package/lib/providers/op-run.mjs +3 -4
- package/lib/providers/secret-resolver.mjs +31 -0
- package/lib/publish-tooling.mjs +3 -11
- package/lib/publish.mjs +12 -29
- package/lib/reconcile/mcp-entry-reconcile.mjs +14 -9
- package/lib/reflect.mjs +2 -2
- package/lib/registry/agent-manifest.mjs +190 -0
- package/lib/registry/assemble.mjs +133 -0
- package/lib/registry/catalog.mjs +171 -0
- package/lib/registry/cli.mjs +590 -6
- package/lib/registry/consolidation.mjs +5 -4
- package/lib/registry/docs-sync.mjs +67 -0
- package/lib/registry/loader.mjs +147 -0
- package/lib/registry/org-io.mjs +76 -0
- package/lib/registry/retired-paths.mjs +71 -0
- package/lib/registry/surface-map.mjs +5 -7
- package/lib/registry/validator.mjs +438 -0
- package/lib/research-execution-policy.mjs +173 -0
- package/lib/roles/catalog.mjs +4 -9
- package/lib/roles/fence.mjs +107 -1
- package/lib/roles/flavor-bindings.mjs +68 -0
- package/lib/roles/gateway.mjs +140 -29
- package/lib/roles/manifest.mjs +22 -13
- package/lib/roles/router.mjs +1 -1
- package/lib/runtime-env.mjs +1 -1
- package/lib/scopes/enrich.mjs +67 -0
- package/lib/scopes/hooks.mjs +12 -0
- package/lib/{profiles → scopes}/lifecycle.mjs +70 -70
- package/lib/scopes/loader.mjs +104 -0
- package/lib/scopes/rebrand.mjs +32 -0
- package/lib/scopes/research-profile.mjs +16 -0
- package/lib/scopes/teams.mjs +98 -0
- package/lib/service-manager.mjs +1 -117
- package/lib/setup-prompts.mjs +1 -1
- package/lib/setup.mjs +17 -28
- package/lib/skills/composition-graph.mjs +98 -0
- package/lib/skills/router.mjs +80 -0
- package/lib/specialist-contracts.mjs +17 -18
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/specialists/prompt-schema.mjs +6 -5
- package/lib/specialists/roster.mjs +7 -12
- package/lib/specialists/schema.mjs +9 -6
- package/lib/status.mjs +25 -90
- package/lib/storage/file-lock.mjs +6 -3
- package/lib/telemetry/skill-calls.mjs +1 -1
- package/lib/templates/doc-presentation.mjs +63 -0
- package/lib/templates/visual-requirements.mjs +35 -2
- package/lib/term-format.mjs +107 -2
- package/lib/test-env-setup.mjs +21 -0
- package/lib/ui/components.mjs +42 -0
- package/lib/ui/glyphs.mjs +58 -0
- package/lib/ui/links.mjs +115 -0
- package/lib/ui/theme.mjs +108 -0
- package/lib/uninstall/uninstall.mjs +2 -1
- package/lib/validator.mjs +45 -8
- package/lib/validators/skill-effectiveness.mjs +174 -0
- package/lib/validators/skills.mjs +1 -1
- package/package.json +12 -7
- package/personas/construct.md +12 -3
- package/platforms/claude/CLAUDE.md +1 -1
- package/rules/common/beads-hygiene.md +52 -0
- package/rules/common/neurodivergent-output.md +4 -7
- package/rules/common/research.md +2 -0
- package/scripts/sync-specialists.mjs +260 -49
- package/skills/ai/prompt-optimizer.md +3 -3
- package/skills/brand/output-vibe.md +48 -0
- package/skills/docs/adr-workflow.md +3 -0
- package/skills/docs/backlog-proposal-workflow.md +3 -0
- package/skills/docs/codebase-research-workflow.md +5 -2
- package/skills/docs/customer-profile-workflow.md +3 -0
- package/skills/docs/document-ingest-workflow.md +3 -0
- package/skills/docs/evidence-ingest-workflow.md +4 -1
- package/skills/docs/init-project.md +1 -1
- package/skills/docs/prd-workflow.md +1 -1
- package/skills/docs/prfaq-workflow.md +3 -0
- package/skills/docs/product-intelligence-workflow.md +4 -1
- package/skills/docs/product-signal-workflow.md +3 -0
- package/skills/docs/research-workflow.md +13 -6
- package/skills/docs/runbook-workflow.md +3 -0
- package/skills/docs/strategy-workflow.md +3 -0
- package/skills/docs/user-research-workflow.md +6 -3
- package/skills/operating/orchestration-reference.md +1 -1
- package/skills/roles/{engineer.ai.md → ai-engineer.md} +2 -2
- package/skills/roles/architect.ai-systems.md +1 -1
- package/skills/roles/architect.data.md +1 -1
- package/skills/roles/architect.enterprise.md +1 -1
- package/skills/roles/architect.integration.md +1 -1
- package/skills/roles/architect.md +1 -1
- package/skills/roles/architect.platform.md +1 -1
- package/skills/roles/{product-manager.business-strategy.md → business-strategist.md} +3 -3
- package/skills/roles/data-analyst.experiment.md +1 -1
- package/skills/roles/data-analyst.md +1 -1
- package/skills/roles/data-analyst.product-intelligence.md +1 -1
- package/skills/roles/data-analyst.product.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/{engineer.data.md → data-engineer.md} +2 -2
- package/skills/roles/data-engineer.pipeline.md +2 -2
- package/skills/roles/data-engineer.vector-retrieval.md +2 -2
- package/skills/roles/data-engineer.warehouse.md +2 -2
- package/skills/roles/debugger.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +1 -1
- package/skills/roles/{reviewer.devil-advocate.md → devil-advocate.md} +2 -2
- package/skills/roles/{operator.docs.md → docs-keeper.md} +3 -3
- package/skills/roles/engineer.md +3 -7
- package/skills/roles/{reviewer.evaluator.md → evaluator.md} +2 -2
- package/skills/roles/{researcher.explorer.md → explorer.md} +2 -2
- package/skills/roles/{operator.md → operations.md} +2 -5
- package/skills/roles/orchestrator.md +1 -1
- package/skills/roles/{engineer.platform.md → platform-engineer.md} +2 -2
- package/skills/roles/product-manager.ai-product.md +1 -1
- package/skills/roles/product-manager.enterprise.md +1 -1
- package/skills/roles/product-manager.growth.md +1 -2
- package/skills/roles/product-manager.md +1 -2
- package/skills/roles/product-manager.platform.md +1 -1
- package/skills/roles/product-manager.product.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -1
- package/skills/roles/qa.api-contract.md +1 -1
- package/skills/roles/qa.data-pipeline.md +1 -1
- package/skills/roles/qa.md +1 -1
- package/skills/roles/qa.web-ui.md +1 -1
- package/skills/roles/{operator.release.md → release-manager.md} +3 -3
- package/skills/roles/researcher.md +1 -1
- package/skills/roles/reviewer.md +1 -1
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.appsec.md +1 -1
- package/skills/roles/security.cloud.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +1 -1
- package/skills/roles/security.privacy.md +1 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/{operator.sre.md → sre.md} +3 -3
- package/skills/roles/{qa.test-automation.md → test-automation.md} +2 -2
- package/skills/roles/{reviewer.trace.md → trace-reviewer.md} +2 -2
- package/skills/roles/{researcher.ux.md → ux-researcher.md} +2 -2
- package/skills/routing.json +18 -0
- package/skills/routing.md +1 -1
- package/specialists/artifact-manifest.json +2 -1
- package/specialists/audit-enrichments.json +14 -14
- package/specialists/org/contracts/accessibility-to-qa.json +37 -0
- package/specialists/org/contracts/any-to-business-strategist.json +57 -0
- package/specialists/org/contracts/any-to-debugger.json +38 -0
- package/specialists/org/contracts/any-to-designer.json +33 -0
- package/specialists/org/contracts/any-to-docs-keeper.json +34 -0
- package/specialists/org/contracts/any-to-explorer.json +39 -0
- package/specialists/org/contracts/any-to-sre-incident.json +33 -0
- package/specialists/org/contracts/any-to-trace-reviewer.json +32 -0
- package/specialists/org/contracts/architect-to-ai-engineer.json +32 -0
- package/specialists/org/contracts/architect-to-data-engineer.json +52 -0
- package/specialists/org/contracts/architect-to-devil-advocate.json +38 -0
- package/specialists/org/contracts/architect-to-engineer.json +34 -0
- package/specialists/org/contracts/architect-to-evaluator.json +59 -0
- package/specialists/org/contracts/architect-to-legal-compliance.json +55 -0
- package/specialists/org/contracts/architect-to-operations.json +45 -0
- package/specialists/org/contracts/architect-to-platform-engineer.json +42 -0
- package/specialists/org/contracts/business-strategist-to-product-manager.json +39 -0
- package/specialists/org/contracts/construct-to-orchestrator.json +36 -0
- package/specialists/org/contracts/construct-to-rd-lead.json +53 -0
- package/specialists/org/contracts/data-analyst-to-product-manager.json +43 -0
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +36 -0
- package/specialists/org/contracts/designer-to-accessibility.json +36 -0
- package/specialists/org/contracts/engineer-to-qa.json +34 -0
- package/specialists/org/contracts/engineer-to-reviewer.json +52 -0
- package/specialists/org/contracts/explorer-to-engineer.json +38 -0
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +43 -0
- package/specialists/org/contracts/platform-engineer-to-engineer.json +31 -0
- package/specialists/org/contracts/product-manager-to-architect.json +71 -0
- package/specialists/org/contracts/product-manager-to-data-analyst.json +49 -0
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +51 -0
- package/specialists/org/contracts/qa-to-release-manager.json +42 -0
- package/specialists/org/contracts/qa-to-test-automation.json +42 -0
- package/specialists/org/contracts/rd-lead-to-architect.json +38 -0
- package/specialists/org/contracts/researcher-to-architect.json +40 -0
- package/specialists/org/contracts/researcher-to-product-manager.json +57 -0
- package/specialists/org/contracts/reviewer-to-security.json +41 -0
- package/specialists/org/contracts/sre-to-release-manager.json +36 -0
- package/specialists/org/contracts/test-automation-to-engineer.json +34 -0
- package/specialists/org/contracts/trace-reviewer-to-sre.json +41 -0
- package/specialists/org/contracts/user-to-construct.json +30 -0
- package/specialists/org/groups/engineering-group.json +43 -0
- package/specialists/org/groups/governance-group.json +38 -0
- package/specialists/org/groups/operations-group.json +41 -0
- package/specialists/org/groups/product-group.json +46 -0
- package/specialists/org/groups/quality-group.json +42 -0
- package/specialists/org/groups/strategy-group.json +41 -0
- package/specialists/org/policies/action-approval.json +13 -0
- package/specialists/org/policies/agents-routing.json +13 -0
- package/specialists/org/policies/architecture.json +20 -0
- package/specialists/org/policies/bash-safety.json +13 -0
- package/specialists/org/policies/beads-hygiene.json +13 -0
- package/specialists/org/policies/bootstrap-state.json +13 -0
- package/specialists/org/policies/code-review.json +13 -0
- package/specialists/org/policies/coding-style.json +13 -0
- package/specialists/org/policies/comments.json +13 -0
- package/specialists/org/policies/commit-approval.json +13 -0
- package/specialists/org/policies/contract-preconditions.json +13 -0
- package/specialists/org/policies/deployment.json +20 -0
- package/specialists/org/policies/description.json +14 -0
- package/specialists/org/policies/design-approval.json +19 -0
- package/specialists/org/policies/doc-ownership.json +13 -0
- package/specialists/org/policies/file-path-fence.json +13 -0
- package/specialists/org/policies/framing.json +13 -0
- package/specialists/org/policies/git-workflow.json +13 -0
- package/specialists/org/policies/incident-response.json +15 -0
- package/specialists/org/policies/intake-triage.json +15 -0
- package/specialists/org/policies/neurodivergent-output.json +13 -0
- package/specialists/org/policies/no-fabrication.json +13 -0
- package/specialists/org/policies/patterns.json +13 -0
- package/specialists/org/policies/quality-gate-approval.json +17 -0
- package/specialists/org/policies/release-gates.json +13 -0
- package/specialists/org/policies/research-evidence.json +13 -0
- package/specialists/org/policies/review-before-change.json +13 -0
- package/specialists/org/policies/rollback.json +15 -0
- package/specialists/org/policies/scope-change.json +20 -0
- package/specialists/org/policies/secret-scan.json +13 -0
- package/specialists/org/policies/security-approval.json +17 -0
- package/specialists/org/policies/security.json +13 -0
- package/specialists/org/policies/session-efficiency.json +13 -0
- package/specialists/org/policies/skill-routing.json +13 -0
- package/specialists/org/policies/strategic-prioritization.json +17 -0
- package/specialists/org/policies/testing.json +13 -0
- package/specialists/org/policies/tool-invisibility.json +14 -0
- package/specialists/org/scopes/creative.json +54 -0
- package/specialists/org/scopes/operations.json +51 -0
- package/specialists/org/scopes/research.json +60 -0
- package/specialists/org/scopes/rnd.json +81 -0
- package/specialists/org/specialists/cx-accessibility.json +69 -0
- package/specialists/org/specialists/cx-ai-engineer.json +75 -0
- package/specialists/org/specialists/cx-architect.json +89 -0
- package/specialists/org/specialists/cx-business-strategist.json +72 -0
- package/specialists/org/specialists/cx-data-analyst.json +68 -0
- package/specialists/org/specialists/cx-data-engineer.json +71 -0
- package/specialists/org/specialists/cx-debugger.json +75 -0
- package/specialists/org/specialists/cx-designer.json +85 -0
- package/specialists/org/specialists/cx-devil-advocate.json +71 -0
- package/specialists/org/specialists/cx-docs-keeper.json +82 -0
- package/specialists/org/specialists/cx-engineer.json +106 -0
- package/specialists/org/specialists/cx-evaluator.json +69 -0
- package/specialists/org/specialists/cx-explorer.json +69 -0
- package/specialists/org/specialists/cx-legal-compliance.json +74 -0
- package/specialists/org/specialists/cx-operations.json +72 -0
- package/specialists/org/specialists/cx-oracle.json +46 -0
- package/specialists/org/specialists/cx-orchestrator.json +81 -0
- package/specialists/org/specialists/cx-platform-engineer.json +80 -0
- package/specialists/org/specialists/cx-product-manager.json +102 -0
- package/specialists/org/specialists/cx-qa.json +79 -0
- package/specialists/org/specialists/cx-rd-lead.json +73 -0
- package/specialists/org/specialists/cx-release-manager.json +77 -0
- package/specialists/org/specialists/cx-researcher.json +82 -0
- package/specialists/org/specialists/cx-reviewer.json +71 -0
- package/specialists/org/specialists/cx-security.json +91 -0
- package/specialists/org/specialists/cx-sre.json +94 -0
- package/specialists/org/specialists/cx-test-automation.json +69 -0
- package/specialists/org/specialists/cx-trace-reviewer.json +69 -0
- package/specialists/org/specialists/cx-ux-researcher.json +68 -0
- package/specialists/org/teams/accessibility-team.json +39 -0
- package/specialists/org/teams/design-team.json +40 -0
- package/specialists/org/teams/engineering-team.json +50 -0
- package/specialists/org/teams/governance-team.json +41 -0
- package/specialists/org/teams/operations-team.json +46 -0
- package/specialists/org/teams/product-management-team.json +45 -0
- package/specialists/org/teams/quality-team.json +49 -0
- package/specialists/org/teams/research-team.json +39 -0
- package/specialists/org/teams/strategy-team.json +48 -0
- package/specialists/org/teams/ux-research-team.json +39 -0
- package/specialists/prompts/_shared/validation-contract.md +1 -1
- package/specialists/prompts/_team-template.md +10 -0
- package/specialists/prompts/cx-accessibility.md +1 -0
- package/specialists/prompts/cx-ai-engineer.md +1 -1
- package/specialists/prompts/cx-business-strategist.md +1 -1
- package/specialists/prompts/cx-data-engineer.md +1 -1
- package/specialists/prompts/cx-designer.md +1 -0
- package/specialists/prompts/cx-devil-advocate.md +1 -1
- package/specialists/prompts/cx-docs-keeper.md +1 -1
- package/specialists/prompts/cx-evaluator.md +1 -1
- package/specialists/prompts/cx-explorer.md +1 -1
- package/specialists/prompts/cx-operations.md +1 -1
- package/specialists/prompts/cx-oracle.md +7 -3
- package/specialists/prompts/cx-orchestrator.md +17 -1
- package/specialists/prompts/cx-platform-engineer.md +1 -1
- package/specialists/prompts/cx-product-manager.md +2 -0
- package/specialists/prompts/cx-release-manager.md +1 -1
- package/specialists/prompts/cx-researcher.md +7 -4
- package/specialists/prompts/cx-sre.md +1 -1
- package/specialists/prompts/cx-test-automation.md +2 -2
- package/specialists/prompts/cx-trace-reviewer.md +3 -3
- package/specialists/prompts/cx-ux-researcher.md +2 -1
- package/templates/demos/scripts/architecture-review-adr.json +30 -0
- package/templates/demos/scripts/capability-contract.json +25 -0
- package/templates/demos/scripts/intake-triage.json +25 -0
- package/templates/demos/scripts/profile-doctor-health.json +25 -0
- package/templates/demos/tapes/agentic-platforms-prd.tape +2 -2
- package/templates/demos/tapes/architecture-review-adr.tape +44 -0
- package/templates/demos/tapes/capability-contract.tape +39 -0
- package/templates/demos/tapes/intake-triage.tape +39 -0
- package/templates/demos/tapes/profile-doctor-health.tape +39 -0
- package/templates/distribution/construct-brand.typ +23 -18
- package/templates/distribution/construct-decision.typ +1 -1
- package/templates/distribution/construct-pdf.typ +1 -1
- package/templates/distribution/construct-prd.typ +1 -1
- package/templates/distribution/construct-research.typ +1 -1
- package/templates/distribution/mermaid-puppeteer.json +8 -0
- package/templates/docs/persona-artifact.md +1 -1
- package/templates/docs/prd.md +6 -0
- package/lib/boundary.mjs +0 -127
- package/lib/certification/dashboard-api.mjs +0 -71
- package/lib/chat/cli.mjs +0 -333
- package/lib/chat/command-suggest.mjs +0 -161
- package/lib/chat/commands.mjs +0 -215
- package/lib/chat/config.mjs +0 -142
- package/lib/chat/context-compactor.mjs +0 -250
- package/lib/chat/context-continuation.mjs +0 -253
- package/lib/chat/continuation-source.mjs +0 -58
- package/lib/chat/demo-guide.mjs +0 -61
- package/lib/chat/design-tokens.mjs +0 -91
- package/lib/chat/desktop-binary.mjs +0 -81
- package/lib/chat/desktop-build.mjs +0 -130
- package/lib/chat/desktop-launcher.mjs +0 -133
- package/lib/chat/evidence.mjs +0 -145
- package/lib/chat/export.mjs +0 -74
- package/lib/chat/harness/driver.mjs +0 -91
- package/lib/chat/list-picker.mjs +0 -112
- package/lib/chat/model-picker.mjs +0 -356
- package/lib/chat/openrouter-fallback.mjs +0 -151
- package/lib/chat/permission-prompt.mjs +0 -33
- package/lib/chat/picker-catalog.mjs +0 -45
- package/lib/chat/policy-telemetry.mjs +0 -34
- package/lib/chat/present.mjs +0 -246
- package/lib/chat/session-context.mjs +0 -39
- package/lib/chat/session-persist.mjs +0 -73
- package/lib/chat/session-restore.mjs +0 -71
- package/lib/chat/session-settings.mjs +0 -53
- package/lib/chat/system-prompt.mjs +0 -52
- package/lib/chat/transparency.mjs +0 -93
- package/lib/chat/tui/color-scheme.mjs +0 -42
- package/lib/chat/tui/markdown.mjs +0 -123
- package/lib/chat/tui/presentation.mjs +0 -100
- package/lib/chat/tui/render.mjs +0 -500
- package/lib/chat/tui/turn-block.mjs +0 -284
- package/lib/chat/tui/turn-present.mjs +0 -18
- package/lib/chat/tui/turn-state.mjs +0 -88
- package/lib/chat/tui/usage.mjs +0 -122
- package/lib/chat/web-commands.mjs +0 -146
- package/lib/chat/web-launcher.mjs +0 -63
- package/lib/chat/web-picker-keys.mjs +0 -46
- package/lib/chat/web-session.mjs +0 -159
- package/lib/config/alias.mjs +0 -57
- package/lib/dashboard-demo.mjs +0 -71
- package/lib/dashboard-static.mjs +0 -175
- package/lib/install/desktop-binary-download.mjs +0 -88
- package/lib/profiles/loader.mjs +0 -123
- package/lib/profiles/rebrand.mjs +0 -46
- package/lib/server/auth.mjs +0 -169
- package/lib/server/chat-loop.mjs +0 -622
- package/lib/server/chat.mjs +0 -336
- package/lib/server/cors.mjs +0 -77
- package/lib/server/csrf.mjs +0 -103
- package/lib/server/demo-preview.mjs +0 -63
- package/lib/server/index.mjs +0 -2641
- package/lib/server/insights.mjs +0 -780
- package/lib/server/langfuse-login.mjs +0 -58
- package/lib/server/rate-limit.mjs +0 -93
- package/lib/server/telemetry-login.mjs +0 -100
- package/lib/server/webhook.mjs +0 -512
- package/specialists/contracts.json +0 -1032
- package/specialists/contracts.schema.json +0 -83
- package/specialists/policy-inventory.json +0 -188
- package/specialists/registry.json +0 -1412
- package/specialists/role-manifests.json +0 -217
- package/specialists/teams.json +0 -94
package/lib/mcp/tools/skills.mjs
CHANGED
|
@@ -19,6 +19,9 @@ import { buildTaskPacketFromIntent } from '../../workflow-state.mjs';
|
|
|
19
19
|
import { logSkillCall } from '../../telemetry/skill-calls.mjs';
|
|
20
20
|
import { templateMetadata } from '../../artifact-manifest.mjs';
|
|
21
21
|
import { buildSpecialistCatalog } from '../../specialists/roster.mjs';
|
|
22
|
+
import { loadRegistry } from '../../registry/loader.mjs';
|
|
23
|
+
import { listGroupsHierarchical } from '../../registry/assemble.mjs';
|
|
24
|
+
import { suggestSkills } from '../../skills/router.mjs';
|
|
22
25
|
|
|
23
26
|
export function listSkills({ ROOT_DIR }) {
|
|
24
27
|
const skillsDir = join(ROOT_DIR, 'skills');
|
|
@@ -48,9 +51,22 @@ export function listSkills({ ROOT_DIR }) {
|
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
export function getSkill(args, { ROOT_DIR }) {
|
|
51
|
-
const { path: skillPath } = args;
|
|
54
|
+
const { path: skillPath, specialistId, agentId } = args;
|
|
52
55
|
if (!skillPath) return { error: 'Missing path argument' };
|
|
53
56
|
|
|
57
|
+
const strict = process.env.CONSTRUCT_STRICT_SKILLS === '1';
|
|
58
|
+
const specId = specialistId || agentId;
|
|
59
|
+
let entitlementWarning = null;
|
|
60
|
+
if (specId) {
|
|
61
|
+
const registry = loadRegistry({ rootDir: ROOT_DIR, skipValidation: true });
|
|
62
|
+
const id = String(specId).startsWith('cx-') ? specId : `cx-${specId}`;
|
|
63
|
+
const entitled = new Set(registry.specialists?.[id]?.skills || []);
|
|
64
|
+
if (entitled.size > 0 && !entitled.has(skillPath)) {
|
|
65
|
+
entitlementWarning = `Skill ${skillPath} is not in ${id} entitlement list`;
|
|
66
|
+
if (strict) return { error: entitlementWarning };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
54
70
|
// Primary: flat file form skills/<path>.md
|
|
55
71
|
// Fallback: directory form skills/<path>/SKILL.md (one minor-release grace period)
|
|
56
72
|
const candidates = [
|
|
@@ -73,7 +89,7 @@ export function getSkill(args, { ROOT_DIR }) {
|
|
|
73
89
|
sessionId: args.sessionId,
|
|
74
90
|
tokensReturned: Math.ceil(content.length / 4),
|
|
75
91
|
});
|
|
76
|
-
return { content };
|
|
92
|
+
return { content, ...(entitlementWarning ? { warning: entitlementWarning } : {}) };
|
|
77
93
|
}
|
|
78
94
|
|
|
79
95
|
export function searchSkills(args, { ROOT_DIR }) {
|
|
@@ -434,29 +450,30 @@ export async function workerRun(args = {}) {
|
|
|
434
450
|
return { ...result, ...(evidence ? { evidence } : {}) };
|
|
435
451
|
}
|
|
436
452
|
|
|
437
|
-
export function
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
453
|
+
export function suggestSkillsTool(args, { ROOT_DIR, cwd } = {}) {
|
|
454
|
+
const intent = args?.intent || args?.query || '';
|
|
455
|
+
return suggestSkills({
|
|
456
|
+
intent,
|
|
457
|
+
specialistId: args?.specialistId || args?.agentId,
|
|
458
|
+
limit: args?.limit || 5,
|
|
459
|
+
rootDir: ROOT_DIR,
|
|
460
|
+
cwd: cwd || ROOT_DIR,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export function listTeams({ ROOT_DIR } = {}) {
|
|
465
|
+
const registry = loadRegistry({ rootDir: ROOT_DIR, skipValidation: true });
|
|
441
466
|
return {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
description: t.description,
|
|
445
|
-
members: t.members,
|
|
446
|
-
focus: t.focus,
|
|
447
|
-
skills: t.skills,
|
|
448
|
-
promotionGates: t.promotionGates,
|
|
449
|
-
})),
|
|
467
|
+
groups: listGroupsHierarchical(registry),
|
|
468
|
+
teams: Object.entries(registry.teams || {}).map(([id, t]) => ({ id, ...t })),
|
|
450
469
|
};
|
|
451
470
|
}
|
|
452
471
|
|
|
453
|
-
export function getTeam(args, { ROOT_DIR }) {
|
|
454
|
-
const
|
|
472
|
+
export function getTeam(args, { ROOT_DIR } = {}) {
|
|
473
|
+
const name = args?.name || args?.id;
|
|
455
474
|
if (!name) return { error: 'Missing name argument' };
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
if (!team) return { error: `Team not found: ${name}`, available: Object.keys(templates) };
|
|
461
|
-
return { name, ...team };
|
|
475
|
+
const registry = loadRegistry({ rootDir: ROOT_DIR, skipValidation: true });
|
|
476
|
+
const team = registry.teams?.[name];
|
|
477
|
+
if (!team) return { error: `Team not found: ${name}`, available: Object.keys(registry.teams || {}) };
|
|
478
|
+
return { id: name, ...team };
|
|
462
479
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* adapter when configured; local JSONL remains available by default.
|
|
7
7
|
*/
|
|
8
8
|
import { join, resolve } from 'node:path';
|
|
9
|
+
import { loadRegistry } from '../../registry/loader.mjs';
|
|
9
10
|
import { homedir } from 'node:os';
|
|
10
11
|
import { readFileSync, existsSync } from 'node:fs';
|
|
11
12
|
import { createTelemetryClient } from '../../telemetry/client.mjs';
|
|
@@ -71,7 +72,7 @@ function resolveSessionContext() {
|
|
|
71
72
|
|
|
72
73
|
export async function cxTrace(args, { ROOT_DIR }) {
|
|
73
74
|
const ctx = resolveSessionContext();
|
|
74
|
-
const registry =
|
|
75
|
+
const registry = loadRegistry({ rootDir: ROOT_DIR });
|
|
75
76
|
const registryModels = registry.models ?? {};
|
|
76
77
|
const currentModels = readCurrentModels(join(ROOT_DIR, '.env'), registryModels, process.env);
|
|
77
78
|
const route = typeof args.input === 'string' ? routeRequest({ request: args.input }) : null;
|
|
@@ -97,7 +97,7 @@ export function workflowValidate(args) {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
/**
|
|
100
|
-
* Validate a producer→consumer handoff against specialists/
|
|
100
|
+
* Validate a producer→consumer handoff against specialists/org.
|
|
101
101
|
*
|
|
102
102
|
* Args:
|
|
103
103
|
* producer — agent or persona name producing the artifact
|
|
@@ -14,16 +14,16 @@ export function getOpenCodeMcpId(id) {
|
|
|
14
14
|
return id;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function resolveTemplateString(value, resolvedValues) {
|
|
18
|
-
return value.replace(/__([A-Z0-9_]+)__/g, (_, name) => resolvedValues[name] ??
|
|
17
|
+
function resolveTemplateString(value, resolvedValues, fallback = (name) => `__${name}__`) {
|
|
18
|
+
return value.replace(/__([A-Z0-9_]+)__/g, (_, name) => resolvedValues[name] ?? fallback(name));
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
function resolveTemplateObject(input, resolvedValues) {
|
|
21
|
+
function resolveTemplateObject(input, resolvedValues, fallback) {
|
|
22
22
|
if (!input || typeof input !== "object" || Array.isArray(input)) return {};
|
|
23
23
|
return Object.fromEntries(
|
|
24
24
|
Object.entries(input).map(([key, value]) => [
|
|
25
25
|
key,
|
|
26
|
-
typeof value === "string" ? resolveTemplateString(value, resolvedValues) : value,
|
|
26
|
+
typeof value === "string" ? resolveTemplateString(value, resolvedValues, fallback) : value,
|
|
27
27
|
]),
|
|
28
28
|
);
|
|
29
29
|
}
|
|
@@ -45,7 +45,9 @@ function buildLocalEnvironment(mcpDef, resolvedValues) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
function buildRemoteHeaders(mcpDef, resolvedValues) {
|
|
48
|
-
return stripUnresolvedValues(
|
|
48
|
+
return stripUnresolvedValues(
|
|
49
|
+
resolveTemplateObject(mcpDef.headers ?? {}, resolvedValues, (name) => `{env:${name}}`),
|
|
50
|
+
);
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
function withMemoryDefaults(id, values) {
|
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 }) ?? [])
|