@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/bin/construct
CHANGED
|
@@ -15,7 +15,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
15
15
|
import { CLI_COMMANDS, CATEGORY_ORDER, formatCommandHelp } from '../lib/cli-commands.mjs';
|
|
16
16
|
import { buildStatus, formatStatusReport } from '../lib/status.mjs';
|
|
17
17
|
import { validateRegistry } from '../lib/validator.mjs';
|
|
18
|
-
import { readCurrentModels, readOpenRouterApiKeyFromOpenCodeConfig, applyToEnv, resetEnv, setModelWithTierInference } from '../lib/model-router.mjs';
|
|
18
|
+
import { readCurrentModels, readOpenRouterApiKeyFromOpenCodeConfig, applyToEnv, resetEnv, setModelWithTierInference, listAvailableModels } from '../lib/model-router.mjs';
|
|
19
19
|
import { pollFreeModels, topForTier, selectForTier } from '../lib/model-free-selector.mjs';
|
|
20
20
|
import { cmdMcpList, cmdMcpAdd, cmdMcpRemove, cmdMcpInfo } from '../lib/mcp-manager.mjs';
|
|
21
21
|
import { cmdOllama } from '../lib/ollama-manager.mjs';
|
|
@@ -64,15 +64,20 @@ import { runCapabilityLedgerAuditCli } from '../lib/capability-ledger.mjs';
|
|
|
64
64
|
import { runCorpusInventoryAuditCli } from '../lib/test-corpus-inventory.mjs';
|
|
65
65
|
import { runArtifactValidateCli } from '../lib/artifact-release-gate.mjs';
|
|
66
66
|
import { runTeamReviewCli } from '../lib/telemetry/team-rollup.mjs';
|
|
67
|
-
import {
|
|
67
|
+
import { startServices } from '../lib/service-manager.mjs';
|
|
68
68
|
import { readCostLog, summarizeCostData, formatCostReport, clearCostLog } from '../lib/cost.mjs';
|
|
69
69
|
import { readEfficiencyLog, summarizeEfficiencyData, formatEfficiencyReport } from '../lib/efficiency.mjs';
|
|
70
|
-
import {
|
|
70
|
+
import { resolveUiColors } from '../lib/ui/theme.mjs';
|
|
71
|
+
import { UNICODE_GLYPHS, ASCII_GLYPHS } from '../lib/ui/glyphs.mjs';
|
|
72
|
+
import { formatPathLink, terminalLinksEnabled } from '../lib/ui/links.mjs';
|
|
73
|
+
import { ok as uiOk, warn as uiWarn, info as uiInfo, fail as uiFail, section as uiSection } from '../lib/ui/components.mjs';
|
|
74
|
+
import { getInstalledVersion } from '../lib/version.mjs';
|
|
71
75
|
import { buildHybridSearchResultsAsync } from '../lib/storage/hybrid-query.mjs';
|
|
72
76
|
import { createSqlClient, closeSqlClient } from '../lib/storage/backend.mjs';
|
|
73
77
|
import { syncFileStateToSql } from '../lib/storage/sync.mjs';
|
|
74
78
|
import { deleteIngestedArtifacts, getStorageStatus, inferProjectName, resetStorage } from '../lib/storage/admin.mjs';
|
|
75
79
|
import { runPressureRelease } from '../lib/runtime-pressure.mjs';
|
|
80
|
+
import { loadRegistry, listSpecialists } from '../lib/registry/loader.mjs';
|
|
76
81
|
|
|
77
82
|
const ROOT_DIR = path.resolve(import.meta.dirname, '..');
|
|
78
83
|
const HOME = os.homedir();
|
|
@@ -82,13 +87,21 @@ for (const [key, value] of Object.entries(ENV)) {
|
|
|
82
87
|
if (!(key in process.env)) process.env[key] = value;
|
|
83
88
|
}
|
|
84
89
|
|
|
85
|
-
const COLORS =
|
|
90
|
+
const COLORS = resolveUiColors();
|
|
91
|
+
const GLYPHS = process.env.CX_ASCII === '1' ? ASCII_GLYPHS : UNICODE_GLYPHS;
|
|
92
|
+
const LINKS_ON = terminalLinksEnabled(process.env, { stream: process.stdout });
|
|
86
93
|
|
|
87
94
|
function println(message = '') { process.stdout.write(`${message}\n`); }
|
|
88
95
|
function errorln(message = '') { process.stderr.write(`${message}\n`); }
|
|
89
|
-
function ok(message) { println(
|
|
90
|
-
function warn(message) { println(
|
|
91
|
-
function info(message) { println(
|
|
96
|
+
function ok(message) { println(uiOk(message, { colors: COLORS, glyphs: GLYPHS })); }
|
|
97
|
+
function warn(message) { println(uiWarn(message, { colors: COLORS })); }
|
|
98
|
+
function info(message) { println(uiInfo(message, { colors: COLORS, glyphs: GLYPHS })); }
|
|
99
|
+
|
|
100
|
+
// A repo path becomes an OSC-8 hyperlink where the terminal honors them; the
|
|
101
|
+
// visible label stays the raw path so Terminal.app's own Cmd-click detection
|
|
102
|
+
// still resolves it where OSC-8 is ignored.
|
|
103
|
+
|
|
104
|
+
function linkPath(p) { return formatPathLink(p, COLORS, { enabled: LINKS_ON }); }
|
|
92
105
|
|
|
93
106
|
// VS16-suffixed emojis (e.g. ⌨️, 🛠️, ⬆️) come from BMP "text symbol" Unicode
|
|
94
107
|
// blocks and are commonly rendered as 1 visual column in macOS Terminal /
|
|
@@ -108,58 +121,34 @@ function usage(opts = {}) {
|
|
|
108
121
|
const visible = CLI_COMMANDS.filter((c) => !c.internal);
|
|
109
122
|
const commands = showAll ? visible : visible.filter((c) => c.core);
|
|
110
123
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
124
|
+
const ver = getInstalledVersion().version;
|
|
125
|
+
const wordmark = `${COLORS.brandAccent}${COLORS.bold}construct${COLORS.reset}`;
|
|
126
|
+
const tagline = showAll ? 'AI agent harness · all commands' : 'AI agent harness';
|
|
127
|
+
println(`\n${wordmark} ${COLORS.dim}v${ver}${COLORS.reset} ${COLORS.muted}${tagline}${COLORS.reset}`);
|
|
128
|
+
if (!showAll) {
|
|
129
|
+
println(`${COLORS.dim}Tip: ${COLORS.reset}construct --all${COLORS.dim} lists every command · ${COLORS.reset}construct <cmd> --help${COLORS.dim} for details${COLORS.reset}`);
|
|
116
130
|
}
|
|
117
131
|
|
|
118
|
-
println(`\n${COLORS.dim}Usage:${COLORS.reset} construct <command> [options]
|
|
132
|
+
println(`\n${COLORS.dim}Usage:${COLORS.reset} construct <command> [options]`);
|
|
133
|
+
println(`${COLORS.dim}OpenCode is the primary conversation surface. Run ${COLORS.reset}construct sync${COLORS.dim} to install or refresh editor adapters.${COLORS.reset}`);
|
|
134
|
+
|
|
135
|
+
// The name column sizes to the longest visible command (capped) so long names
|
|
136
|
+
// are never truncated and descriptions stay aligned regardless of the set shown.
|
|
137
|
+
|
|
138
|
+
const nameWidth = Math.min(18, Math.max(...commands.map((c) => c.name.length)));
|
|
119
139
|
|
|
120
140
|
for (const category of CATEGORY_ORDER) {
|
|
121
141
|
const categoryCommands = commands.filter((c) => c.category === category);
|
|
122
142
|
if (!categoryCommands.length) continue;
|
|
123
|
-
println(
|
|
143
|
+
println(`\n${uiSection(category, { colors: COLORS })}`);
|
|
124
144
|
for (const command of categoryCommands) {
|
|
125
145
|
const emoji = command.emoji ?? ' ';
|
|
126
|
-
|
|
146
|
+
const name = `${COLORS.bold}${command.name.padEnd(nameWidth)}${COLORS.reset}`;
|
|
147
|
+
println(` ${iconColumn(emoji)}${name} ${command.description}`);
|
|
127
148
|
}
|
|
128
|
-
println('');
|
|
129
149
|
}
|
|
130
|
-
}
|
|
131
150
|
|
|
132
|
-
|
|
133
|
-
// --help`, prefixed with a context line so users know where they are, and
|
|
134
|
-
// suffixed with the one next-step suggestion that applies to their state.
|
|
135
|
-
|
|
136
|
-
async function showInteractiveMenu() {
|
|
137
|
-
const { existsSync } = await import('node:fs');
|
|
138
|
-
const { join } = await import('node:path');
|
|
139
|
-
|
|
140
|
-
const projectRoot = process.cwd();
|
|
141
|
-
const isConstructProject = existsSync(join(projectRoot, 'construct.config.json')) ||
|
|
142
|
-
existsSync(join(projectRoot, '.cx'));
|
|
143
|
-
|
|
144
|
-
if (isConstructProject) {
|
|
145
|
-
const projectName = process.env.CX_PROJECT_NAME || path.basename(projectRoot);
|
|
146
|
-
println(`${COLORS.dim}Project:${COLORS.reset} ${projectName}`);
|
|
147
|
-
println('');
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
usage({ all: false });
|
|
151
|
-
|
|
152
|
-
if (isConstructProject) {
|
|
153
|
-
const { readDashboardState } = await import('../lib/service-manager.mjs');
|
|
154
|
-
const dashboard = readDashboardState(HOME);
|
|
155
|
-
if (!dashboard) {
|
|
156
|
-
println(`${COLORS.yellow}💡 Services not running. Start with:${COLORS.reset} ${COLORS.green}construct dev${COLORS.reset}`);
|
|
157
|
-
println('');
|
|
158
|
-
}
|
|
159
|
-
} else {
|
|
160
|
-
println(`${COLORS.yellow}💡 Not a Construct project. Initialize with:${COLORS.reset} ${COLORS.green}construct init${COLORS.reset}`);
|
|
161
|
-
println('');
|
|
162
|
-
}
|
|
151
|
+
println(`\n${COLORS.dim}Docs:${COLORS.reset} ${linkPath('docs/guides/reference/cli/')}`);
|
|
163
152
|
}
|
|
164
153
|
|
|
165
154
|
function runNodeScript(scriptPath, args = [], extraEnv = {}, { exitOnError = true } = {}) {
|
|
@@ -190,6 +179,42 @@ async function cmdRegistryGenerateDocs(args = []) {
|
|
|
190
179
|
if (code) process.exit(code);
|
|
191
180
|
}
|
|
192
181
|
|
|
182
|
+
async function cmdRegistryDiff(args = []) {
|
|
183
|
+
const { runRegistryDiff } = await import('../lib/registry/cli.mjs');
|
|
184
|
+
const code = await runRegistryDiff(args, { rootDir: ROOT_DIR, println, errorln });
|
|
185
|
+
if (code) process.exit(code);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function cmdRegistryPrune(args = []) {
|
|
189
|
+
const { runRegistryPrune } = await import('../lib/registry/cli.mjs');
|
|
190
|
+
const code = await runRegistryPrune(args, { rootDir: ROOT_DIR, println, errorln });
|
|
191
|
+
if (code) process.exit(code);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function cmdTeamAdd(args = []) {
|
|
195
|
+
const { runTeamAdd } = await import('../lib/registry/cli.mjs');
|
|
196
|
+
const code = await runTeamAdd(args, { rootDir: ROOT_DIR, println, errorln });
|
|
197
|
+
if (code) process.exit(code);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function cmdTeamRemove(args = []) {
|
|
201
|
+
const { runTeamRemove } = await import('../lib/registry/cli.mjs');
|
|
202
|
+
const code = await runTeamRemove(args, { rootDir: ROOT_DIR, println, errorln });
|
|
203
|
+
if (code) process.exit(code);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function cmdSpecialistAdd(args = []) {
|
|
207
|
+
const { runSpecialistAdd } = await import('../lib/registry/cli.mjs');
|
|
208
|
+
const code = await runSpecialistAdd(args, { rootDir: ROOT_DIR, println, errorln });
|
|
209
|
+
if (code) process.exit(code);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function cmdSpecialistRemove(args = []) {
|
|
213
|
+
const { runSpecialistRemove } = await import('../lib/registry/cli.mjs');
|
|
214
|
+
const code = await runSpecialistRemove(args, { rootDir: ROOT_DIR, println, errorln });
|
|
215
|
+
if (code) process.exit(code);
|
|
216
|
+
}
|
|
217
|
+
|
|
193
218
|
async function cmdRulesUsage(args = []) {
|
|
194
219
|
const { summarizeRuleCalls } = await import('../lib/telemetry/rule-calls.mjs');
|
|
195
220
|
const { summarizeHookCalls } = await import('../lib/telemetry/hook-calls.mjs');
|
|
@@ -235,12 +260,12 @@ async function cmdStatus() {
|
|
|
235
260
|
// to "is this thing actually learning?" without remembering a second
|
|
236
261
|
// command. Cheap: just reads .cx/observations + .cx/outcomes summaries.
|
|
237
262
|
try {
|
|
238
|
-
const {
|
|
263
|
+
const { resolveActiveScope } = await import('../lib/scopes/loader.mjs');
|
|
239
264
|
const { readSummary } = await import('../lib/outcomes/aggregate.mjs');
|
|
240
265
|
const fs = await import('node:fs');
|
|
241
266
|
const path = await import('node:path');
|
|
242
267
|
const cwd = process.cwd();
|
|
243
|
-
const active =
|
|
268
|
+
const active = resolveActiveScope(cwd);
|
|
244
269
|
const obsIdxPath = path.join(cwd, '.cx', 'observations', 'index.json');
|
|
245
270
|
let obsTotal = 0;
|
|
246
271
|
let obs24h = 0;
|
|
@@ -349,15 +374,13 @@ async function cmdCompletions(args) {
|
|
|
349
374
|
}
|
|
350
375
|
|
|
351
376
|
async function cmdList() {
|
|
352
|
-
const registry =
|
|
377
|
+
const registry = loadRegistry({ rootDir: ROOT_DIR });
|
|
353
378
|
println('Construct Agents\n');
|
|
354
|
-
println('
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
for (const agent of registry.agents ?? []) {
|
|
360
|
-
println(` ${registry.prefix}-${agent.name} ${agent.description}`);
|
|
379
|
+
println('Specialists:');
|
|
380
|
+
const specialists = Object.values(registry.specialists ?? {});
|
|
381
|
+
for (const specialist of specialists) {
|
|
382
|
+
const id = specialist.name.startsWith('cx-') ? specialist.name : `cx-${specialist.name}`;
|
|
383
|
+
println(` ${id} ${specialist.displayName || ''}`);
|
|
361
384
|
}
|
|
362
385
|
}
|
|
363
386
|
|
|
@@ -503,18 +526,9 @@ async function cmdDoctor() {
|
|
|
503
526
|
const add = (label, pass, optional = false) => checks.push({ label, pass, optional });
|
|
504
527
|
const { isConstructSourceCheckout } = await import('../lib/doctor/source-checkout.mjs');
|
|
505
528
|
const isSourceCheckout = isConstructSourceCheckout(ROOT_DIR);
|
|
506
|
-
add('
|
|
529
|
+
add('specialists/org exists', fs.existsSync(path.join(ROOT_DIR, 'specialists', 'org')));
|
|
507
530
|
add('sync-specialists.mjs exists', fs.existsSync(path.join(ROOT_DIR, 'scripts', 'sync-specialists.mjs')));
|
|
508
531
|
|
|
509
|
-
// Dashboard static build output is absent from published npm installs.
|
|
510
|
-
|
|
511
|
-
if (isSourceCheckout) {
|
|
512
|
-
const dashboardIndex = path.join(ROOT_DIR, 'lib', 'server', 'static', 'index.html');
|
|
513
|
-
add('Dashboard built (lib/server/static/index.html)', fs.existsSync(dashboardIndex));
|
|
514
|
-
} else {
|
|
515
|
-
add('Dashboard built (skipped — published package)', true, true);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
532
|
// Project .gitignore must cover .cx/. Auto-fix via --fix-gitignore.
|
|
519
533
|
// Only checks when cwd looks like a Construct project (has .cx/ or
|
|
520
534
|
// .construct/); non-project cwds get no warning.
|
|
@@ -542,8 +556,8 @@ async function cmdDoctor() {
|
|
|
542
556
|
// name across Claude / Codex / Copilot user-scope dirs.
|
|
543
557
|
|
|
544
558
|
const userHome = HOME;
|
|
545
|
-
const registry =
|
|
546
|
-
const specialistSet = new Set((registry.specialists ??
|
|
559
|
+
const registry = loadRegistry({ rootDir: ROOT_DIR });
|
|
560
|
+
const specialistSet = new Set(Object.values(registry.specialists ?? {}).map((s) => `cx-${s.name}`));
|
|
547
561
|
const legacyTargets = [
|
|
548
562
|
{ dir: path.join(userHome, '.claude', 'agents'), ext: '.md' },
|
|
549
563
|
{ dir: path.join(userHome, '.codex', 'agents'), ext: '.toml' },
|
|
@@ -653,6 +667,26 @@ async function cmdDoctor() {
|
|
|
653
667
|
);
|
|
654
668
|
}
|
|
655
669
|
|
|
670
|
+
// Mirror model tier overrides stranded in the pre-XDG legacy config forward
|
|
671
|
+
// before resolving tiers, so an install upgraded across the XDG move reports
|
|
672
|
+
// configured without a manual copy. Applied to process.env too so the check
|
|
673
|
+
// below sees the just-migrated values in the same run.
|
|
674
|
+
try {
|
|
675
|
+
const { migrateLegacyModelConfig, migrateLegacyCredentialConfig } = await import('../lib/config/legacy-config-migration.mjs');
|
|
676
|
+
const migration = migrateLegacyModelConfig({ homeDir: HOME });
|
|
677
|
+
if (migration.performed) {
|
|
678
|
+
Object.assign(process.env, migration.migrated);
|
|
679
|
+
println(` fix: migrated ${Object.keys(migration.migrated).join(', ')} from ${migration.legacyPath} → ${migration.xdgPath}`);
|
|
680
|
+
}
|
|
681
|
+
const credentialMigration = migrateLegacyCredentialConfig({ homeDir: HOME });
|
|
682
|
+
if (credentialMigration.performed) {
|
|
683
|
+
Object.assign(process.env, credentialMigration.migrated);
|
|
684
|
+
println(` fix: migrated credentials ${Object.keys(credentialMigration.migrated).join(', ')} from ${credentialMigration.legacyPath} → ${credentialMigration.xdgPath}`);
|
|
685
|
+
}
|
|
686
|
+
} catch (err) {
|
|
687
|
+
add(`Models — legacy config migration failed: ${err.message}`, false, true);
|
|
688
|
+
}
|
|
689
|
+
|
|
656
690
|
// Tier model selection. Construct ships with no default — at least
|
|
657
691
|
// one tier must be configured (registry.json primary OR CX_MODEL_*
|
|
658
692
|
// env override) before any LLM-backed workflow can run.
|
|
@@ -665,7 +699,7 @@ async function cmdDoctor() {
|
|
|
665
699
|
if (resolved.complete && !resolved.errors) {
|
|
666
700
|
add('Models — all tiers configured', true);
|
|
667
701
|
} else if (resolved.configured === 0) {
|
|
668
|
-
add('Models — no tier configured (
|
|
702
|
+
add('Models — no tier configured (run `construct models --apply`)', false, true);
|
|
669
703
|
} else {
|
|
670
704
|
const missing = Object.entries(resolved.models)
|
|
671
705
|
.filter(([, v]) => !v)
|
|
@@ -847,19 +881,28 @@ async function cmdDoctor() {
|
|
|
847
881
|
|
|
848
882
|
// Validate promptFile references and skill bindings
|
|
849
883
|
try {
|
|
850
|
-
const registry =
|
|
851
|
-
const brokenPromptFiles = (registry.specialists ??
|
|
884
|
+
const registry = loadRegistry({ rootDir: ROOT_DIR });
|
|
885
|
+
const brokenPromptFiles = Object.values(registry.specialists ?? {}).filter((s) => {
|
|
852
886
|
if (!s.promptFile) return false;
|
|
853
887
|
return !fs.existsSync(path.join(ROOT_DIR, s.promptFile));
|
|
854
888
|
});
|
|
855
889
|
add('All specialist promptFiles resolve', brokenPromptFiles.length === 0);
|
|
856
890
|
const headhuntClassifierAgents = ['cx-docs-keeper', 'cx-researcher', 'cx-architect', 'cx-security', 'cx-engineer'];
|
|
857
|
-
const registryNames = new Set((registry.specialists ??
|
|
891
|
+
const registryNames = new Set(Object.values(registry.specialists ?? {}).map((s) => `cx-${s.name}`));
|
|
858
892
|
const classifierOrphans = headhuntClassifierAgents.filter((n) => !registryNames.has(n));
|
|
859
893
|
add('Headhunt classifier agents exist in registry', classifierOrphans.length === 0);
|
|
860
894
|
const skillAudit = auditSkills({ rootDir: ROOT_DIR, silent: true });
|
|
861
895
|
add('No declared skills missing on disk', skillAudit.pass, false);
|
|
862
896
|
if (isSourceCheckout) {
|
|
897
|
+
const { scanRetiredRegistryPathReferences } = await import('../lib/registry/retired-paths.mjs');
|
|
898
|
+
const staleRegistryPaths = scanRetiredRegistryPathReferences(ROOT_DIR);
|
|
899
|
+
add(
|
|
900
|
+
staleRegistryPaths.length === 0
|
|
901
|
+
? 'No retired registry path references'
|
|
902
|
+
: `Retired registry path references (${staleRegistryPaths.length}: ${staleRegistryPaths.slice(0, 3).map((h) => h.file).join(', ')}${staleRegistryPaths.length > 3 ? ', …' : ''})`,
|
|
903
|
+
staleRegistryPaths.length === 0,
|
|
904
|
+
false,
|
|
905
|
+
);
|
|
863
906
|
const specialistAudit = auditSpecialists({ rootDir: ROOT_DIR, silent: true });
|
|
864
907
|
add('Specialist/skill audit cross-checks', specialistAudit.pass, false);
|
|
865
908
|
|
|
@@ -904,7 +947,8 @@ async function cmdDoctor() {
|
|
|
904
947
|
if (fs.existsSync(beadsPreCommit) && !inCI) {
|
|
905
948
|
const hp = spawnSync('git', ['config', '--get', 'core.hooksPath'], { cwd: ROOT_DIR, encoding: 'utf8' });
|
|
906
949
|
const value = hp.status === 0 ? (hp.stdout || '').trim() : '';
|
|
907
|
-
const
|
|
950
|
+
const absEquiv = path.join(ROOT_DIR, '.beads', 'hooks');
|
|
951
|
+
const wired = value === '.beads/hooks' || value === absEquiv;
|
|
908
952
|
const label = wired
|
|
909
953
|
? 'Git hooks wired (core.hooksPath = .beads/hooks)'
|
|
910
954
|
: value
|
|
@@ -1407,7 +1451,6 @@ async function cmdDev(args = []) {
|
|
|
1407
1451
|
}
|
|
1408
1452
|
|
|
1409
1453
|
println('');
|
|
1410
|
-
println('Dashboard: http://127.0.0.1:4242');
|
|
1411
1454
|
println('Run \x1b[32mconstruct\x1b[0m for commands');
|
|
1412
1455
|
println('');
|
|
1413
1456
|
|
|
@@ -1434,29 +1477,6 @@ async function cmdStop() {
|
|
|
1434
1477
|
} catch { /* best effort */ }
|
|
1435
1478
|
}
|
|
1436
1479
|
|
|
1437
|
-
async function cmdDashboard() {
|
|
1438
|
-
if (restArgsCache.includes('--token')) {
|
|
1439
|
-
const { generateToken, setDashboardToken, getDashboardToken } = await import('../lib/server/auth.mjs');
|
|
1440
|
-
const existing = getDashboardToken();
|
|
1441
|
-
if (existing) {
|
|
1442
|
-
println(`Dashboard token already set. To rotate it, remove CONSTRUCT_DASHBOARD_TOKEN from ~/.construct/config.env and re-run.`);
|
|
1443
|
-
return;
|
|
1444
|
-
}
|
|
1445
|
-
const token = generateToken();
|
|
1446
|
-
setDashboardToken(token);
|
|
1447
|
-
println(`Dashboard token generated and saved to ~/.construct/config.env`);
|
|
1448
|
-
println(`CONSTRUCT_DASHBOARD_TOKEN=${token}`);
|
|
1449
|
-
println(`Pass this token in the dashboard login prompt or as: Authorization: Bearer <token>`);
|
|
1450
|
-
return;
|
|
1451
|
-
}
|
|
1452
|
-
const dashboard = await startDashboard({ rootDir: ROOT_DIR, homeDir: HOME });
|
|
1453
|
-
if (dashboard.reused) {
|
|
1454
|
-
println(`Construct dashboard already running on ${dashboard.url} (pid ${dashboard.pid})`);
|
|
1455
|
-
} else {
|
|
1456
|
-
println(`Started Construct dashboard on ${dashboard.url} (pid ${dashboard.pid})`);
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
1480
|
async function cmdVersion() {
|
|
1461
1481
|
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'package.json'), 'utf8'));
|
|
1462
1482
|
println(`construct v${pkg.version}`);
|
|
@@ -1471,9 +1491,7 @@ async function cmdMigrate(args) {
|
|
|
1471
1491
|
const candidates = pathArg
|
|
1472
1492
|
? [pathArg]
|
|
1473
1493
|
: [
|
|
1474
|
-
path.join(ROOT_DIR, 'specialists', 'registry.json'),
|
|
1475
|
-
path.join(ROOT_DIR, 'specialists', 'contracts.json'),
|
|
1476
|
-
path.join(ROOT_DIR, 'specialists', 'role-manifests.json'),
|
|
1494
|
+
path.join(ROOT_DIR, 'specialists', 'unified-registry.json'),
|
|
1477
1495
|
path.join(process.cwd(), '.cx', 'config.json'),
|
|
1478
1496
|
];
|
|
1479
1497
|
|
|
@@ -1600,7 +1618,7 @@ async function cmdExport(args) {
|
|
|
1600
1618
|
inputPath: resolvedIn,
|
|
1601
1619
|
outputPath: resolvedOut,
|
|
1602
1620
|
format: toFlag,
|
|
1603
|
-
figures
|
|
1621
|
+
figures,
|
|
1604
1622
|
branding,
|
|
1605
1623
|
repoRoot: ROOT_DIR,
|
|
1606
1624
|
});
|
|
@@ -1620,19 +1638,17 @@ async function cmdPublish(args) {
|
|
|
1620
1638
|
async function cmdTools(args) {
|
|
1621
1639
|
const [sub, ...rest] = args;
|
|
1622
1640
|
if (sub !== 'detect') {
|
|
1623
|
-
errorln('Usage: construct tools detect [--json] [--figures] [--no-figures] [--demo=NAME]
|
|
1641
|
+
errorln('Usage: construct tools detect [--json] [--figures] [--no-figures] [--demo=NAME]');
|
|
1624
1642
|
process.exit(1);
|
|
1625
1643
|
}
|
|
1626
1644
|
const { detectPublishPipeline, formatToolsDetectReport } = await import('../lib/publish-tooling.mjs');
|
|
1627
1645
|
const json = rest.includes('--json');
|
|
1628
1646
|
const figures = !rest.includes('--no-figures');
|
|
1629
1647
|
const demos = rest.filter((a) => a.startsWith('--demo=')).map((a) => a.slice(7));
|
|
1630
|
-
const dashboardDemos = rest.filter((a) => a.startsWith('--dashboard-demo=')).map((a) => a.slice(17));
|
|
1631
1648
|
const detection = detectPublishPipeline({
|
|
1632
1649
|
format: 'pdf',
|
|
1633
1650
|
includeFigures: figures,
|
|
1634
1651
|
includeTerminalDemo: demos.length > 0,
|
|
1635
|
-
includeDashboardDemo: dashboardDemos.length > 0,
|
|
1636
1652
|
cwd: process.cwd(),
|
|
1637
1653
|
repoRoot: ROOT_DIR,
|
|
1638
1654
|
});
|
|
@@ -2091,7 +2107,7 @@ function printActiveConfig() {
|
|
|
2091
2107
|
}
|
|
2092
2108
|
|
|
2093
2109
|
async function printIntakeHelp() {
|
|
2094
|
-
const { getRebrand } = await import('../lib/
|
|
2110
|
+
const { getRebrand } = await import('../lib/scopes/rebrand.mjs');
|
|
2095
2111
|
const { intakeQueueLabel, signalNoun } = getRebrand(process.cwd());
|
|
2096
2112
|
println(`Construct intake — inspect and process the ${intakeQueueLabel}.`);
|
|
2097
2113
|
println('');
|
|
@@ -2209,7 +2225,7 @@ async function cmdIntake(args) {
|
|
|
2209
2225
|
}
|
|
2210
2226
|
|
|
2211
2227
|
const { createIntakeQueue } = await import('../lib/intake/queue.mjs');
|
|
2212
|
-
const { getRebrand } = await import('../lib/
|
|
2228
|
+
const { getRebrand } = await import('../lib/scopes/rebrand.mjs');
|
|
2213
2229
|
const cwd = process.cwd();
|
|
2214
2230
|
const rebrand = getRebrand(cwd);
|
|
2215
2231
|
const queue = createIntakeQueue(cwd, process.env);
|
|
@@ -3637,7 +3653,7 @@ async function cmdUpgrade(args) {
|
|
|
3637
3653
|
}
|
|
3638
3654
|
}
|
|
3639
3655
|
async function cmdValidate() {
|
|
3640
|
-
const reg =
|
|
3656
|
+
const reg = loadRegistry({ rootDir: ROOT_DIR });
|
|
3641
3657
|
const result = validateRegistry(reg, { rootDir: ROOT_DIR });
|
|
3642
3658
|
if (result.valid) {
|
|
3643
3659
|
println(`✓ Registry valid (${result.summary})`);
|
|
@@ -3680,9 +3696,9 @@ async function cmdOrchestrate(args) {
|
|
|
3680
3696
|
// running in-process, proving the engine-as-service contract editors/CI use.
|
|
3681
3697
|
if (rest.includes('--remote')) {
|
|
3682
3698
|
const remoteVal = flag('--remote');
|
|
3683
|
-
const base = (remoteVal && /^https?:\/\//.test(remoteVal)) ? remoteVal.replace(/\/$/, '') :
|
|
3684
|
-
|
|
3685
|
-
const token =
|
|
3699
|
+
const base = (remoteVal && /^https?:\/\//.test(remoteVal)) ? remoteVal.replace(/\/$/, '') : (process.env.CONSTRUCT_ORCHESTRATION_URL || '').replace(/\/$/, '');
|
|
3700
|
+
if (!base) { errorln('No orchestration service configured. Pass --remote <url> or set CONSTRUCT_ORCHESTRATION_URL.'); process.exit(1); }
|
|
3701
|
+
const token = process.env.CONSTRUCT_ORCHESTRATION_TOKEN || process.env.CONSTRUCT_DASHBOARD_TOKEN || null;
|
|
3686
3702
|
const headers = { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) };
|
|
3687
3703
|
const positional = rest.filter((a) => !a.startsWith('--'));
|
|
3688
3704
|
try {
|
|
@@ -3712,7 +3728,7 @@ async function cmdOrchestrate(args) {
|
|
|
3712
3728
|
return;
|
|
3713
3729
|
}
|
|
3714
3730
|
} catch (err) {
|
|
3715
|
-
errorln(`Could not reach the orchestration
|
|
3731
|
+
errorln(`Could not reach the orchestration service at ${base}: ${err.message}. Check CONSTRUCT_ORCHESTRATION_URL.`);
|
|
3716
3732
|
process.exit(1);
|
|
3717
3733
|
}
|
|
3718
3734
|
}
|
|
@@ -3773,7 +3789,15 @@ async function cmdOrchestrate(args) {
|
|
|
3773
3789
|
}
|
|
3774
3790
|
|
|
3775
3791
|
async function cmdModels(args) {
|
|
3776
|
-
|
|
3792
|
+
const envPath = getUserEnvPath(HOME);
|
|
3793
|
+
const sub = args[0] && !args[0].startsWith('-') ? args[0] : null;
|
|
3794
|
+
const KNOWN_SUBCOMMANDS = new Set(['list', 'set', 'free', 'reset', 'resolve']);
|
|
3795
|
+
if (sub && !KNOWN_SUBCOMMANDS.has(sub)) {
|
|
3796
|
+
errorln(`Unknown models subcommand: ${sub}. Run \`construct models --help\` for the supported set.`);
|
|
3797
|
+
process.exit(1);
|
|
3798
|
+
}
|
|
3799
|
+
|
|
3800
|
+
if (sub === 'resolve') {
|
|
3777
3801
|
const flag = (name) => { const i = args.indexOf(name); return i !== -1 ? args[i + 1] : undefined; };
|
|
3778
3802
|
const { resolveEmbeddedModel } = await import('../lib/embedded-contract/model-resolve.mjs');
|
|
3779
3803
|
const { wrapContractResult } = await import('../lib/embedded-contract/envelope.mjs');
|
|
@@ -3790,21 +3814,36 @@ async function cmdModels(args) {
|
|
|
3790
3814
|
println(JSON.stringify(envelope, null, 2));
|
|
3791
3815
|
return;
|
|
3792
3816
|
}
|
|
3817
|
+
// Legacy flag forms stay as compatibility aliases for the documented
|
|
3818
|
+
// subcommands. Each emits a one-line deprecation notice on stderr so stdout
|
|
3819
|
+
// stays clean while operators migrate to `models set|reset|free`.
|
|
3793
3820
|
const tier = args.find((arg) => arg.startsWith('--tier='))?.split('=')[1] ?? '';
|
|
3794
|
-
const
|
|
3795
|
-
const
|
|
3796
|
-
|
|
3821
|
+
const modelFlag = args.find((arg) => arg.startsWith('--model='))?.split('=')[1];
|
|
3822
|
+
const legacySetFlag = args.find((arg) => arg.startsWith('--set='))?.split('=')[1];
|
|
3823
|
+
const setModel = modelFlag ?? legacySetFlag ?? '';
|
|
3824
|
+
|
|
3825
|
+
if (sub === 'reset' || args.includes('--reset')) {
|
|
3826
|
+
if (sub !== 'reset') errorln('Note: `construct models --reset` is a deprecated alias for `construct models reset`.');
|
|
3797
3827
|
resetEnv(envPath);
|
|
3798
|
-
println(
|
|
3828
|
+
println(`Removed CX_MODEL_* overrides from ${envPath}.`);
|
|
3799
3829
|
await cmdSync([]);
|
|
3800
3830
|
return;
|
|
3801
3831
|
}
|
|
3802
|
-
if (tier && setModel) {
|
|
3803
|
-
|
|
3832
|
+
if (sub === 'set' || (sub === null && tier && setModel)) {
|
|
3833
|
+
if (!tier || !setModel) {
|
|
3834
|
+
errorln('Usage: construct models set --tier=<reasoning|standard|fast> --model=<provider/model-id>');
|
|
3835
|
+
process.exit(1);
|
|
3836
|
+
}
|
|
3837
|
+
if (modelFlag === undefined && legacySetFlag !== undefined) {
|
|
3838
|
+
errorln('Note: `--set=<model>` is a deprecated alias for `--model=<model>`.');
|
|
3839
|
+
}
|
|
3840
|
+
if (sub === null) {
|
|
3841
|
+
errorln('Note: `construct models --tier=… --set=…` is a deprecated alias for `construct models set --tier=… --model=…`.');
|
|
3842
|
+
}
|
|
3804
3843
|
const preferFree = args.includes('--prefer-free');
|
|
3805
3844
|
const preferFreeSameFamily = args.includes('--prefer-free-same-family');
|
|
3806
|
-
const inferred = setModelWithTierInference(envPath, tier, setModel,
|
|
3807
|
-
println(`Set ${tier} -> ${setModel} in
|
|
3845
|
+
const inferred = setModelWithTierInference(envPath, tier, setModel, {}, { preferFree, preferFreeSameFamily });
|
|
3846
|
+
println(`Set ${tier} -> ${setModel} in ${envPath}`);
|
|
3808
3847
|
if (preferFreeSameFamily) println('Prefer-free-same-family mode: enabled');
|
|
3809
3848
|
if (preferFree) println('Prefer-free mode: enabled');
|
|
3810
3849
|
println(`Resolved tier set:`);
|
|
@@ -3814,7 +3853,8 @@ async function cmdModels(args) {
|
|
|
3814
3853
|
await cmdSync([]);
|
|
3815
3854
|
return;
|
|
3816
3855
|
}
|
|
3817
|
-
if (args.includes('--poll')) {
|
|
3856
|
+
if (sub === 'free' || args.includes('--poll')) {
|
|
3857
|
+
if (sub !== 'free') errorln('Note: `construct models --poll` is a deprecated alias for `construct models free`.');
|
|
3818
3858
|
const apiKey = process.env.OPENROUTER_API_KEY || readOpenRouterApiKeyFromOpenCodeConfig();
|
|
3819
3859
|
if (!apiKey) {
|
|
3820
3860
|
errorln('Error: no OpenRouter API key found in OPENROUTER_API_KEY or OpenCode config.');
|
|
@@ -3833,15 +3873,14 @@ async function cmdModels(args) {
|
|
|
3833
3873
|
if (args.includes('--apply')) {
|
|
3834
3874
|
const apiKey = process.env.OPENROUTER_API_KEY || readOpenRouterApiKeyFromOpenCodeConfig();
|
|
3835
3875
|
const models = await pollFreeModels(apiKey);
|
|
3836
|
-
const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'specialists', 'registry.json'), 'utf8'));
|
|
3837
3876
|
const selections = {};
|
|
3838
3877
|
for (const currentTier of ['reasoning', 'standard', 'fast']) {
|
|
3839
|
-
const fallbacks =
|
|
3878
|
+
const fallbacks = [];
|
|
3840
3879
|
const id = selectForTier(models, currentTier, fallbacks);
|
|
3841
3880
|
if (id) selections[currentTier] = id;
|
|
3842
3881
|
}
|
|
3843
3882
|
applyToEnv(envPath, selections);
|
|
3844
|
-
println(
|
|
3883
|
+
println(`Written to ${envPath}. Running construct sync...`);
|
|
3845
3884
|
await cmdSync([]);
|
|
3846
3885
|
return;
|
|
3847
3886
|
}
|
|
@@ -3878,16 +3917,15 @@ async function cmdModels(args) {
|
|
|
3878
3917
|
for (const [tier, model] of Object.entries(selections)) {
|
|
3879
3918
|
println(` ${tier.padEnd(11)} ${model}`);
|
|
3880
3919
|
}
|
|
3881
|
-
println(
|
|
3920
|
+
println(`Written to ${envPath}. Running construct sync...`);
|
|
3882
3921
|
setCheapestProviderPreference(envPath, true);
|
|
3883
3922
|
await cmdSync([]);
|
|
3884
3923
|
return;
|
|
3885
3924
|
}
|
|
3886
|
-
if (args.includes('--list') ||
|
|
3925
|
+
if (args.includes('--list') || sub === 'list') {
|
|
3887
3926
|
const { getProviderModelCatalog } = await import('../lib/model-router.mjs');
|
|
3888
|
-
const { listChatModels } = await import('../apps/chat/engine/models.mjs');
|
|
3889
3927
|
const catalog = getProviderModelCatalog({ env: process.env, cwd: process.cwd() });
|
|
3890
|
-
const models =
|
|
3928
|
+
const models = listAvailableModels({ env: process.env, cwd: process.cwd() });
|
|
3891
3929
|
if (args.includes('--json')) {
|
|
3892
3930
|
println(JSON.stringify({ catalog, models }, null, 2));
|
|
3893
3931
|
return;
|
|
@@ -3899,8 +3937,7 @@ async function cmdModels(args) {
|
|
|
3899
3937
|
}
|
|
3900
3938
|
return;
|
|
3901
3939
|
}
|
|
3902
|
-
const
|
|
3903
|
-
const result = readCurrentModels(envPath, registry.models ?? {});
|
|
3940
|
+
const result = readCurrentModels(envPath, {});
|
|
3904
3941
|
const nothingSelected = ['reasoning', 'standard', 'fast'].every((t) => !result[t]);
|
|
3905
3942
|
println('Current model assignments:');
|
|
3906
3943
|
for (const currentTier of ['reasoning', 'standard', 'fast']) {
|
|
@@ -3912,8 +3949,8 @@ async function cmdModels(args) {
|
|
|
3912
3949
|
println('');
|
|
3913
3950
|
println('No tier has a model selected. Construct ships with no default.');
|
|
3914
3951
|
println('Pick one of:');
|
|
3915
|
-
println(' •
|
|
3916
|
-
println(' • construct models
|
|
3952
|
+
println(' • `construct models` (single source of truth)');
|
|
3953
|
+
println(' • construct models set --tier=<reasoning|standard|fast> --model=<provider/model-id>');
|
|
3917
3954
|
println(' • construct models --apply Poll OpenRouter free catalog and seed all tiers');
|
|
3918
3955
|
}
|
|
3919
3956
|
}
|
|
@@ -4440,12 +4477,6 @@ async function cmdDocsSite(args = []) {
|
|
|
4440
4477
|
}
|
|
4441
4478
|
}
|
|
4442
4479
|
|
|
4443
|
-
async function cmdDashboardSync(args) {
|
|
4444
|
-
const { runDashboardStaticCli } = await import('../lib/dashboard-static.mjs');
|
|
4445
|
-
const exitCode = await runDashboardStaticCli(args, { rootDir: ROOT_DIR });
|
|
4446
|
-
if (exitCode) process.exit(exitCode);
|
|
4447
|
-
}
|
|
4448
|
-
|
|
4449
4480
|
async function cmdBootstrap(args) {
|
|
4450
4481
|
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
4451
4482
|
const { runBootstrap } = await import('../lib/bootstrap.mjs');
|
|
@@ -4783,14 +4814,15 @@ async function cmdLintResearch() {
|
|
|
4783
4814
|
}
|
|
4784
4815
|
|
|
4785
4816
|
async function cmdLintAgents() {
|
|
4786
|
-
const {
|
|
4787
|
-
const
|
|
4788
|
-
const
|
|
4817
|
+
const { validateRegistry } = await import('../lib/specialists/schema.mjs');
|
|
4818
|
+
const { loadRegistry } = await import('../lib/registry/loader.mjs');
|
|
4819
|
+
const registry = loadRegistry({ rootDir: ROOT_DIR, skipValidation: true });
|
|
4820
|
+
const { errors, agentCount } = validateRegistry(registry, { rootDir: ROOT_DIR });
|
|
4789
4821
|
if (errors.length === 0) {
|
|
4790
|
-
console.log(`specialists/
|
|
4822
|
+
console.log(`specialists/org: 0 errors`);
|
|
4791
4823
|
return;
|
|
4792
4824
|
}
|
|
4793
|
-
console.error(`specialists/
|
|
4825
|
+
console.error(`specialists/org: ${errors.length} error(s)`);
|
|
4794
4826
|
for (const err of errors) console.error(` ${err}`);
|
|
4795
4827
|
process.exit(1);
|
|
4796
4828
|
}
|
|
@@ -4799,10 +4831,10 @@ async function cmdLintContracts() {
|
|
|
4799
4831
|
const { validateContractsFile } = await import('../lib/contracts/validate.mjs');
|
|
4800
4832
|
const result = validateContractsFile({ repoRoot: ROOT_DIR });
|
|
4801
4833
|
if (result.ok) {
|
|
4802
|
-
console.log('specialists/
|
|
4834
|
+
console.log('specialists/unified-registry.json contracts: 0 errors');
|
|
4803
4835
|
return;
|
|
4804
4836
|
}
|
|
4805
|
-
console.error(`specialists/
|
|
4837
|
+
console.error(`specialists/unified-registry.json contracts: ${result.errors.length} error(s)`);
|
|
4806
4838
|
for (const err of result.errors) console.error(` ${err}`);
|
|
4807
4839
|
process.exit(1);
|
|
4808
4840
|
}
|
|
@@ -4830,7 +4862,7 @@ async function cmdSpecialist(args) {
|
|
|
4830
4862
|
const { createSpecialistDraft } = await import('../lib/specialists/scaffold.mjs');
|
|
4831
4863
|
try {
|
|
4832
4864
|
const { relPath } = createSpecialistDraft({ rootDir: ROOT_DIR, role });
|
|
4833
|
-
println(`Created ${relPath} — fill in the stubbed sections, add it to specialists/registry.json, then run \`construct specialist lint\`.`);
|
|
4865
|
+
println(`Created ${relPath} — fill in the stubbed sections, add it to specialists/unified-registry.json, then run \`construct specialist lint\`.`);
|
|
4834
4866
|
} catch (e) { errorln(e.message); process.exit(1); }
|
|
4835
4867
|
return;
|
|
4836
4868
|
}
|
|
@@ -5136,7 +5168,7 @@ async function cmdCreds(args) {
|
|
|
5136
5168
|
for (const p of providers) {
|
|
5137
5169
|
println(` ${p.configured ? '✓' : '·'} ${p.id}`);
|
|
5138
5170
|
}
|
|
5139
|
-
println(
|
|
5171
|
+
println(`\n Set keys as values or op:// refs in ${getUserEnvPath(HOME)}; run \`construct creds login copilot\` for GitHub Copilot.`);
|
|
5140
5172
|
} catch { /* readiness view is informational */ }
|
|
5141
5173
|
return;
|
|
5142
5174
|
}
|
|
@@ -5144,6 +5176,13 @@ async function cmdCreds(args) {
|
|
|
5144
5176
|
if (sub === 'test') {
|
|
5145
5177
|
const provider = args[1];
|
|
5146
5178
|
if (!provider) { errorln('Usage: construct creds test <provider>'); process.exit(1); }
|
|
5179
|
+
if (['openrouter', 'anthropic', 'openai'].includes(provider)) {
|
|
5180
|
+
const { probeProviderConnection } = await import('../lib/providers/connection-probe.mjs');
|
|
5181
|
+
const result = await probeProviderConnection(provider, { env: process.env, cwd: process.cwd() });
|
|
5182
|
+
println(`${provider}: ${result.ok ? 'ok' : 'unhealthy'}`);
|
|
5183
|
+
if (result.code) println(` ${result.code}${result.status ? ` (${result.status})` : ''}`);
|
|
5184
|
+
process.exit(result.ok ? 0 : 1);
|
|
5185
|
+
}
|
|
5147
5186
|
const { resolveProviders } = await import('../lib/providers/registry.mjs');
|
|
5148
5187
|
const { providers } = await resolveProviders({ rootDir: ROOT_DIR, env: process.env });
|
|
5149
5188
|
if (!providers[provider]) { errorln(`Unknown provider: ${provider}`); process.exit(1); }
|
|
@@ -5521,7 +5560,6 @@ async function readStdin() {
|
|
|
5521
5560
|
const handlers = new Map([
|
|
5522
5561
|
// Core
|
|
5523
5562
|
['dev', cmdDev],
|
|
5524
|
-
['dashboard', cmdDashboard],
|
|
5525
5563
|
['stop', cmdStop],
|
|
5526
5564
|
['status', cmdStatus],
|
|
5527
5565
|
['install', cmdInstall],
|
|
@@ -5553,6 +5591,12 @@ const handlers = new Map([
|
|
|
5553
5591
|
['registry:status', cmdRegistryStatus],
|
|
5554
5592
|
['registry:validate', cmdRegistryValidate],
|
|
5555
5593
|
['registry:generate-docs', cmdRegistryGenerateDocs],
|
|
5594
|
+
['registry:diff', cmdRegistryDiff],
|
|
5595
|
+
['registry:prune', cmdRegistryPrune],
|
|
5596
|
+
['team:add', cmdTeamAdd],
|
|
5597
|
+
['team:remove', cmdTeamRemove],
|
|
5598
|
+
['specialist:add', cmdSpecialistAdd],
|
|
5599
|
+
['specialist:remove', cmdSpecialistRemove],
|
|
5556
5600
|
['rules', cmdRulesUsage],
|
|
5557
5601
|
// Pricing / cost readouts are stubbed out: the ledger writes, model-pricing
|
|
5558
5602
|
// catalog, and per-turn accounting still run, but no CLI surface exposes
|
|
@@ -5594,10 +5638,6 @@ const handlers = new Map([
|
|
|
5594
5638
|
const { runAcpServer } = await import('../lib/acp/server.mjs');
|
|
5595
5639
|
runAcpServer({ input: process.stdin, output: process.stdout, env: process.env, defaultCwd: process.cwd() });
|
|
5596
5640
|
}],
|
|
5597
|
-
['chat', async (args) => {
|
|
5598
|
-
const { runChat } = await import('../lib/chat/cli.mjs');
|
|
5599
|
-
process.exitCode = await runChat(args, { env: process.env, cwd: process.cwd() });
|
|
5600
|
-
}],
|
|
5601
5641
|
['beads:stats', async (args) => {
|
|
5602
5642
|
const { getContentionStats, getHumanStatus } = await import('../lib/beads-optimistic.mjs');
|
|
5603
5643
|
const jsonOutput = args.includes('--json');
|
|
@@ -5646,14 +5686,8 @@ const handlers = new Map([
|
|
|
5646
5686
|
return;
|
|
5647
5687
|
}
|
|
5648
5688
|
if (sub === 'show') {
|
|
5649
|
-
const {
|
|
5650
|
-
const
|
|
5651
|
-
const inventoryPath = join(ROOT_DIR, 'specialists', 'policy-inventory.json');
|
|
5652
|
-
if (!existsSync(inventoryPath)) {
|
|
5653
|
-
errorln('specialists/policy-inventory.json not found');
|
|
5654
|
-
process.exit(1);
|
|
5655
|
-
}
|
|
5656
|
-
const { policies } = JSON.parse(readFileSync(inventoryPath, 'utf8'));
|
|
5689
|
+
const registry = loadRegistry({ rootDir: ROOT_DIR });
|
|
5690
|
+
const policies = Object.values(registry.policies ?? {});
|
|
5657
5691
|
if (args.includes('--json')) {
|
|
5658
5692
|
println(JSON.stringify(policies, null, 2));
|
|
5659
5693
|
return;
|
|
@@ -5722,7 +5756,6 @@ const handlers = new Map([
|
|
|
5722
5756
|
['docs:update', cmdDocsUpdate],
|
|
5723
5757
|
['docs:check', cmdDocsCheck],
|
|
5724
5758
|
['docs:site', cmdDocsSite],
|
|
5725
|
-
['dashboard:sync', cmdDashboardSync],
|
|
5726
5759
|
['lint:comments', cmdLintComments],
|
|
5727
5760
|
['lint:templates', cmdLintTemplates],
|
|
5728
5761
|
['gates:audit', cmdGatesAudit],
|
|
@@ -5812,7 +5845,12 @@ const handlers = new Map([
|
|
|
5812
5845
|
const sub = args[0];
|
|
5813
5846
|
if (sub === 'review') return runTeamReviewCli(args.slice(1));
|
|
5814
5847
|
if (sub === 'templates') { listTeamTemplates(); return; }
|
|
5815
|
-
|
|
5848
|
+
if (sub === 'list' || sub === 'show') {
|
|
5849
|
+
const { runTeamList, runTeamShow } = await import('../lib/registry/cli.mjs');
|
|
5850
|
+
if (sub === 'list') return runTeamList(args.slice(1), { rootDir: ROOT_DIR, println, errorln });
|
|
5851
|
+
return runTeamShow(args.slice(1), { rootDir: ROOT_DIR, println, errorln });
|
|
5852
|
+
}
|
|
5853
|
+
errorln(`Unknown team subcommand: ${sub}. Available: list, show, review, templates`);
|
|
5816
5854
|
process.exit(1);
|
|
5817
5855
|
}],
|
|
5818
5856
|
['role', async (args) => {
|
|
@@ -6198,6 +6236,22 @@ const handlers = new Map([
|
|
|
6198
6236
|
}],
|
|
6199
6237
|
['skills', async (args) => {
|
|
6200
6238
|
const sub = args[0];
|
|
6239
|
+
if (sub === 'suggest') {
|
|
6240
|
+
const { suggestSkills } = await import('../lib/skills/router.mjs');
|
|
6241
|
+
const query = args.slice(1).join(' ').trim();
|
|
6242
|
+
if (!query) {
|
|
6243
|
+
errorln('Usage: construct skills suggest <intent>');
|
|
6244
|
+
process.exit(1);
|
|
6245
|
+
}
|
|
6246
|
+
const result = suggestSkills({ intent: query, rootDir: ROOT_DIR });
|
|
6247
|
+
println(JSON.stringify(result, null, 2));
|
|
6248
|
+
return;
|
|
6249
|
+
}
|
|
6250
|
+
if (sub === 'routing') {
|
|
6251
|
+
const p = path.join(ROOT_DIR, 'skills', 'routing.json');
|
|
6252
|
+
println(fs.readFileSync(p, 'utf8'));
|
|
6253
|
+
return;
|
|
6254
|
+
}
|
|
6201
6255
|
if (sub === 'scope' || !sub) {
|
|
6202
6256
|
const { runSkillsScopeCli } = await import('../lib/skills-scope.mjs');
|
|
6203
6257
|
return runSkillsScopeCli(args.slice(sub === 'scope' ? 1 : 0));
|
|
@@ -6209,7 +6263,7 @@ const handlers = new Map([
|
|
|
6209
6263
|
if (sub === 'usage' || sub === 'orphans' || sub === 'hot' || sub === 'correlate-quality' || sub === 'backfill-postgres') {
|
|
6210
6264
|
return cmdSkillsUsage(sub, args.slice(1));
|
|
6211
6265
|
}
|
|
6212
|
-
errorln(`Unknown skills subcommand: ${sub}. Available: scope, apply, usage, orphans, hot, correlate-quality, backfill-postgres`);
|
|
6266
|
+
errorln(`Unknown skills subcommand: ${sub}. Available: scope, apply, suggest, routing, usage, orphans, hot, correlate-quality, backfill-postgres`);
|
|
6213
6267
|
process.exit(1);
|
|
6214
6268
|
}],
|
|
6215
6269
|
['ask', async (args) => {
|
|
@@ -6378,18 +6432,18 @@ const handlers = new Map([
|
|
|
6378
6432
|
errorln(`Unknown sandbox subcommand: ${sub}. Available: create, list, delete, prune`);
|
|
6379
6433
|
process.exit(1);
|
|
6380
6434
|
}],
|
|
6381
|
-
['
|
|
6435
|
+
['scope', async (args) => {
|
|
6382
6436
|
const sub = args[0] || 'show';
|
|
6383
|
-
const {
|
|
6437
|
+
const { listScopes, loadScope, resolveActiveScope } = await import('../lib/scopes/loader.mjs');
|
|
6384
6438
|
if (sub === 'list') {
|
|
6385
|
-
for (const id of
|
|
6386
|
-
const p =
|
|
6439
|
+
for (const id of listScopes()) {
|
|
6440
|
+
const p = loadScope(id);
|
|
6387
6441
|
process.stdout.write(` ${id.padEnd(14)} ${p?.displayName || ''}\n`);
|
|
6388
6442
|
}
|
|
6389
6443
|
return;
|
|
6390
6444
|
}
|
|
6391
6445
|
if (sub === 'show') {
|
|
6392
|
-
const active =
|
|
6446
|
+
const active = resolveActiveScope(process.cwd());
|
|
6393
6447
|
process.stdout.write(JSON.stringify({
|
|
6394
6448
|
id: active.id,
|
|
6395
6449
|
displayName: active.displayName,
|
|
@@ -6402,12 +6456,12 @@ const handlers = new Map([
|
|
|
6402
6456
|
if (sub === 'set') {
|
|
6403
6457
|
const id = args[1];
|
|
6404
6458
|
if (!id) {
|
|
6405
|
-
errorln('Usage: construct
|
|
6459
|
+
errorln('Usage: construct scope set <id> [--yes] [--dry-run]');
|
|
6406
6460
|
process.exit(1);
|
|
6407
6461
|
}
|
|
6408
|
-
const target =
|
|
6462
|
+
const target = loadScope(id);
|
|
6409
6463
|
if (!target) {
|
|
6410
|
-
errorln(`Unknown
|
|
6464
|
+
errorln(`Unknown scope: ${id}. Available: ${listScopes().join(', ')}`);
|
|
6411
6465
|
process.exit(1);
|
|
6412
6466
|
}
|
|
6413
6467
|
const skipConfirm = args.includes('--yes') || args.includes('-y') || args.includes('--non-interactive');
|
|
@@ -6425,12 +6479,12 @@ const handlers = new Map([
|
|
|
6425
6479
|
const cfg = loaded?.raw ? structuredClone(loaded.raw) : { version: 1 };
|
|
6426
6480
|
if (!cfg.version) cfg.version = 1;
|
|
6427
6481
|
|
|
6428
|
-
const currentId = cfg.
|
|
6482
|
+
const currentId = cfg.scope || 'rnd';
|
|
6429
6483
|
if (currentId === id) {
|
|
6430
|
-
println(`
|
|
6484
|
+
println(`Scope already set to ${id} (${target.displayName}). Nothing to do.`);
|
|
6431
6485
|
return;
|
|
6432
6486
|
}
|
|
6433
|
-
const current =
|
|
6487
|
+
const current = loadScope(currentId) || { id: currentId, displayName: currentId, roles: [], intake: { types: [], stages: [] }, departments: [] };
|
|
6434
6488
|
|
|
6435
6489
|
// Render a structural diff so the operator sees what loop they are switching into.
|
|
6436
6490
|
const setDiff = (label, before, after) => {
|
|
@@ -6446,7 +6500,7 @@ const handlers = new Map([
|
|
|
6446
6500
|
};
|
|
6447
6501
|
|
|
6448
6502
|
println('');
|
|
6449
|
-
println(`About to switch active
|
|
6503
|
+
println(`About to switch active scope:`);
|
|
6450
6504
|
println(` from: ${currentId} (${current.displayName})`);
|
|
6451
6505
|
println(` to: ${id} (${target.displayName})`);
|
|
6452
6506
|
println('');
|
|
@@ -6459,10 +6513,10 @@ const handlers = new Map([
|
|
|
6459
6513
|
setDiff('departments', cDept, tDept);
|
|
6460
6514
|
println('');
|
|
6461
6515
|
println(`Files that will change:`);
|
|
6462
|
-
println(` ${pathMod.relative(process.cwd(), cfgPath)} (
|
|
6516
|
+
println(` ${pathMod.relative(process.cwd(), cfgPath)} (scope field: ${currentId} -> ${id})`);
|
|
6463
6517
|
println('');
|
|
6464
|
-
println(`The change only writes the
|
|
6465
|
-
println(`Sync runs against the new
|
|
6518
|
+
println(`The change only writes the scope field. No specialists are added or removed.`);
|
|
6519
|
+
println(`Sync runs against the new scope on next \`construct sync\`.`);
|
|
6466
6520
|
println('');
|
|
6467
6521
|
|
|
6468
6522
|
if (dryRun) {
|
|
@@ -6472,7 +6526,7 @@ const handlers = new Map([
|
|
|
6472
6526
|
if (!skipConfirm && isTTY) {
|
|
6473
6527
|
const readline = await import('node:readline');
|
|
6474
6528
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
6475
|
-
const answer = await new Promise((res) => rl.question(`Switch
|
|
6529
|
+
const answer = await new Promise((res) => rl.question(`Switch scope to ${id}? (Y/n) `, (a) => res(a.trim().toLowerCase())));
|
|
6476
6530
|
rl.close();
|
|
6477
6531
|
if (answer && answer !== 'y' && answer !== 'yes') {
|
|
6478
6532
|
println('Aborted. No config was changed.');
|
|
@@ -6480,15 +6534,15 @@ const handlers = new Map([
|
|
|
6480
6534
|
}
|
|
6481
6535
|
}
|
|
6482
6536
|
|
|
6483
|
-
cfg.
|
|
6537
|
+
cfg.scope = id;
|
|
6484
6538
|
writeProjectConfig(cfgPath, cfg, { validate: true });
|
|
6485
|
-
process.stdout.write(`✓
|
|
6539
|
+
process.stdout.write(`✓ scope set to ${id} (${target.displayName})\n`);
|
|
6486
6540
|
return;
|
|
6487
6541
|
}
|
|
6488
6542
|
if (sub === 'create') {
|
|
6489
6543
|
const id = args[1];
|
|
6490
6544
|
if (!id) {
|
|
6491
|
-
errorln('Usage: construct
|
|
6545
|
+
errorln('Usage: construct scope create <id> [--display="..."] [--role=<id>...] [--department=<id>:<displayName>...] [--yes]');
|
|
6492
6546
|
errorln(' Run with no flags for interactive mode; pass --yes to skip the confirmation prompt.');
|
|
6493
6547
|
process.exit(1);
|
|
6494
6548
|
}
|
|
@@ -6526,8 +6580,8 @@ const handlers = new Map([
|
|
|
6526
6580
|
const ask = (q, def = '') => new Promise((res) => rl.question(`${q}${def ? ` [${def}]` : ''} `, (a) => res(a.trim() || def)));
|
|
6527
6581
|
|
|
6528
6582
|
println('');
|
|
6529
|
-
println(`Scaffolding a new
|
|
6530
|
-
println(`Background: docs/guides/concepts/persona-research.md and docs/guides/concepts/
|
|
6583
|
+
println(`Scaffolding a new scope draft. Three questions, then a preview.`);
|
|
6584
|
+
println(`Background: docs/guides/concepts/persona-research.md and docs/guides/concepts/scope-lifecycle.md`);
|
|
6531
6585
|
println('');
|
|
6532
6586
|
if (!displayName) displayName = await ask('Display name?', id);
|
|
6533
6587
|
if (seedDepartments.length === 0) {
|
|
@@ -6546,23 +6600,23 @@ const handlers = new Map([
|
|
|
6546
6600
|
if (!displayName) displayName = id;
|
|
6547
6601
|
|
|
6548
6602
|
const pathMod = await import('node:path');
|
|
6549
|
-
const draftRoot = pathMod.join(process.cwd(), '.cx', '
|
|
6603
|
+
const draftRoot = pathMod.join(process.cwd(), '.cx', 'scopes', `draft-${id}`);
|
|
6550
6604
|
|
|
6551
6605
|
println('');
|
|
6552
|
-
println(`About to scaffold a draft
|
|
6606
|
+
println(`About to scaffold a draft scope:`);
|
|
6553
6607
|
println(` id: ${id}`);
|
|
6554
6608
|
println(` displayName: ${displayName}`);
|
|
6555
6609
|
println(` departments: ${seedDepartments.length}${seedDepartments.length ? ` (${seedDepartments.map((d) => d.id).join(', ')})` : ''}`);
|
|
6556
6610
|
println(` roles: ${seedRoles.length}${seedRoles.length ? ` (${seedRoles.join(', ')})` : ''}`);
|
|
6557
6611
|
println('');
|
|
6558
6612
|
println(`Files that will be written under ${pathMod.relative(process.cwd(), draftRoot)}/:`);
|
|
6559
|
-
println(`
|
|
6613
|
+
println(` scope.json structural manifest`);
|
|
6560
6614
|
println(` requirements.md research brief naming each cx-* specialist`);
|
|
6561
6615
|
for (const r of seedRoles) println(` personas/${r}.md`.padEnd(40) + ` persona artifact (filled during discover)`);
|
|
6562
6616
|
for (const d of seedDepartments) println(` departments/${d.id}.md`.padEnd(40) + ` department charter (filled during frame)`);
|
|
6563
6617
|
println('');
|
|
6564
|
-
println(`Nothing else is touched. No config is changed, no active
|
|
6565
|
-
println(`To activate this
|
|
6618
|
+
println(`Nothing else is touched. No config is changed, no active scope is switched.`);
|
|
6619
|
+
println(`To activate this scope after promotion: construct scope set ${id}`);
|
|
6566
6620
|
println('');
|
|
6567
6621
|
|
|
6568
6622
|
if (dryRun) {
|
|
@@ -6580,16 +6634,16 @@ const handlers = new Map([
|
|
|
6580
6634
|
}
|
|
6581
6635
|
}
|
|
6582
6636
|
|
|
6583
|
-
const {
|
|
6637
|
+
const { createDraftScope } = await import('../lib/scopes/lifecycle.mjs');
|
|
6584
6638
|
try {
|
|
6585
|
-
const result =
|
|
6639
|
+
const result = createDraftScope({ cwd: process.cwd(), id, displayName, seedRoles, seedDepartments });
|
|
6586
6640
|
println('');
|
|
6587
6641
|
println(`✓ draft created at ${pathMod.relative(process.cwd(), result.dir)}`);
|
|
6588
6642
|
println('');
|
|
6589
|
-
println(`Next steps (in order, per docs/guides/concepts/
|
|
6643
|
+
println(`Next steps (in order, per docs/guides/concepts/scope-lifecycle.md):`);
|
|
6590
6644
|
println(` 1. Discover (cx-ux-researcher): fill personas/<role>.md from interviews + primary sources.`);
|
|
6591
6645
|
println(` 2. Frame (cx-product-manager): fill departments/<dept>.md charters and intake taxonomy.`);
|
|
6592
|
-
println(` 3. Architect (cx-architect): reconcile role reuse vs new; populate departments[] in
|
|
6646
|
+
println(` 3. Architect (cx-architect): reconcile role reuse vs new; populate departments[] in scope.json.`);
|
|
6593
6647
|
println(` 4. Validate (cx-evaluator): run persona-eval and classifier-eval against representative signals.`);
|
|
6594
6648
|
println('');
|
|
6595
6649
|
println(`Inspect the brief: open ${pathMod.relative(process.cwd(), result.briefPath)}`);
|
|
@@ -6600,11 +6654,11 @@ const handlers = new Map([
|
|
|
6600
6654
|
}
|
|
6601
6655
|
}
|
|
6602
6656
|
if (sub === 'drafts') {
|
|
6603
|
-
const { listDrafts } = await import('../lib/
|
|
6657
|
+
const { listDrafts } = await import('../lib/scopes/lifecycle.mjs');
|
|
6604
6658
|
const drafts = listDrafts(process.cwd());
|
|
6605
6659
|
if (drafts.length === 0) { process.stdout.write(' (no drafts)\n'); return; }
|
|
6606
6660
|
for (const d of drafts) {
|
|
6607
|
-
const flags = [d.hasBrief ? 'brief' : null, d.
|
|
6661
|
+
const flags = [d.hasBrief ? 'brief' : null, d.hasScope ? 'scope' : null].filter(Boolean).join(', ');
|
|
6608
6662
|
process.stdout.write(` ${d.id.padEnd(20)} ${flags}\n`);
|
|
6609
6663
|
}
|
|
6610
6664
|
return;
|
|
@@ -6614,7 +6668,7 @@ const handlers = new Map([
|
|
|
6614
6668
|
const reasonArg = args.find((a) => a.startsWith('--reason='));
|
|
6615
6669
|
let reason = reasonArg ? reasonArg.split('=')[1] : '';
|
|
6616
6670
|
if (!id) {
|
|
6617
|
-
errorln('Usage: construct
|
|
6671
|
+
errorln('Usage: construct scope archive <id> --reason="..." [--yes] [--dry-run]');
|
|
6618
6672
|
process.exit(1);
|
|
6619
6673
|
}
|
|
6620
6674
|
const skipConfirm = args.includes('--yes') || args.includes('-y') || args.includes('--non-interactive');
|
|
@@ -6623,18 +6677,18 @@ const handlers = new Map([
|
|
|
6623
6677
|
|
|
6624
6678
|
const pathMod = await import('node:path');
|
|
6625
6679
|
const fsMod = await import('node:fs');
|
|
6626
|
-
const target =
|
|
6680
|
+
const target = loadScope(id);
|
|
6627
6681
|
if (!target) {
|
|
6628
|
-
errorln(`Unknown
|
|
6682
|
+
errorln(`Unknown scope: ${id}. Curated catalog: ${listScopes().join(', ')}`);
|
|
6629
6683
|
process.exit(1);
|
|
6630
6684
|
}
|
|
6631
6685
|
const fileURL = await import('node:url');
|
|
6632
6686
|
const moduleDir = pathMod.dirname(fileURL.fileURLToPath(import.meta.url));
|
|
6633
6687
|
const repoRoot = pathMod.resolve(moduleDir, '..');
|
|
6634
|
-
const
|
|
6688
|
+
const srcScope = pathMod.join(repoRoot, 'specialists', 'org', 'scopes', `${id}.json`);
|
|
6635
6689
|
const tableRel = typeof target?.intake?.classificationTable === 'string' ? target.intake.classificationTable : null;
|
|
6636
6690
|
const srcTable = tableRel ? pathMod.join(repoRoot, tableRel) : null;
|
|
6637
|
-
const dstDir = pathMod.join(repoRoot, 'archive', '
|
|
6691
|
+
const dstDir = pathMod.join(repoRoot, 'archive', 'scopes', id);
|
|
6638
6692
|
|
|
6639
6693
|
// Gather reason interactively if missing. The lifecycle module already
|
|
6640
6694
|
// enforces a >= 8 char reason, but the operator should see why before
|
|
@@ -6651,13 +6705,13 @@ const handlers = new Map([
|
|
|
6651
6705
|
}
|
|
6652
6706
|
|
|
6653
6707
|
println('');
|
|
6654
|
-
println(`About to archive curated
|
|
6708
|
+
println(`About to archive curated scope:`);
|
|
6655
6709
|
println(` id: ${id}`);
|
|
6656
6710
|
println(` displayName: ${target.displayName || id}`);
|
|
6657
6711
|
println(` reason: ${reason}`);
|
|
6658
6712
|
println('');
|
|
6659
6713
|
println(`Files that will move:`);
|
|
6660
|
-
println(` ${pathMod.relative(process.cwd(),
|
|
6714
|
+
println(` ${pathMod.relative(process.cwd(), srcScope)}`);
|
|
6661
6715
|
println(` -> ${pathMod.relative(process.cwd(), pathMod.join(dstDir, `${id}.json`))}`);
|
|
6662
6716
|
if (srcTable && fsMod.existsSync(srcTable)) {
|
|
6663
6717
|
println(` ${pathMod.relative(process.cwd(), srcTable)}`);
|
|
@@ -6668,7 +6722,7 @@ const handlers = new Map([
|
|
|
6668
6722
|
println(`What stays:`);
|
|
6669
6723
|
println(` Observations under .cx/observations/ are not touched.`);
|
|
6670
6724
|
println(` Outcomes under .cx/outcomes/ are not touched.`);
|
|
6671
|
-
println(` Any project still configured with
|
|
6725
|
+
println(` Any project still configured with scope=${id} will fall back to rnd on next load.`);
|
|
6672
6726
|
println('');
|
|
6673
6727
|
|
|
6674
6728
|
if (dryRun) {
|
|
@@ -6686,9 +6740,9 @@ const handlers = new Map([
|
|
|
6686
6740
|
}
|
|
6687
6741
|
}
|
|
6688
6742
|
|
|
6689
|
-
const {
|
|
6743
|
+
const { archiveScope } = await import('../lib/scopes/lifecycle.mjs');
|
|
6690
6744
|
try {
|
|
6691
|
-
const { archived } =
|
|
6745
|
+
const { archived } = archiveScope({ id, reason });
|
|
6692
6746
|
process.stdout.write(`✓ archived ${id} to ${pathMod.relative(process.cwd(), archived)}\n`);
|
|
6693
6747
|
return;
|
|
6694
6748
|
} catch (err) {
|
|
@@ -6698,25 +6752,19 @@ const handlers = new Map([
|
|
|
6698
6752
|
}
|
|
6699
6753
|
if (sub === 'health') {
|
|
6700
6754
|
const id = args[1];
|
|
6701
|
-
if (!id) { errorln('Usage: construct
|
|
6755
|
+
if (!id) { errorln('Usage: construct scope health <id> [--days=N]'); process.exit(1); }
|
|
6702
6756
|
const daysArg = args.find((a) => a.startsWith('--days='));
|
|
6703
6757
|
const windowDays = daysArg ? Number(daysArg.split('=')[1]) || 30 : 30;
|
|
6704
|
-
const {
|
|
6705
|
-
const report =
|
|
6758
|
+
const { scopeHealth } = await import('../lib/scopes/lifecycle.mjs');
|
|
6759
|
+
const report = scopeHealth(process.cwd(), id, { windowDays });
|
|
6706
6760
|
process.stdout.write(JSON.stringify(report, null, 2) + '\n');
|
|
6707
6761
|
return;
|
|
6708
6762
|
}
|
|
6709
|
-
errorln(`Unknown
|
|
6763
|
+
errorln(`Unknown scope subcommand: ${sub}. Available: list, show, set, create, drafts, archive, health`);
|
|
6710
6764
|
process.exit(1);
|
|
6711
6765
|
}],
|
|
6712
6766
|
]);
|
|
6713
6767
|
|
|
6714
|
-
// No command provided → show interactive menu
|
|
6715
|
-
if (!command) {
|
|
6716
|
-
await showInteractiveMenu();
|
|
6717
|
-
process.exit(0);
|
|
6718
|
-
}
|
|
6719
|
-
|
|
6720
6768
|
// Help flags → show usage
|
|
6721
6769
|
if (command === '--help' || command === '-h' || command === 'help') {
|
|
6722
6770
|
usage();
|
|
@@ -6737,6 +6785,19 @@ if (command === '--version' || command === '-V') {
|
|
|
6737
6785
|
process.exit(0);
|
|
6738
6786
|
}
|
|
6739
6787
|
|
|
6788
|
+
// Bare invocation stays non-interactive. OpenCode is the first-class
|
|
6789
|
+
// conversational surface; this CLI remains the setup/admin/headless substrate.
|
|
6790
|
+
if (!command) {
|
|
6791
|
+
usage();
|
|
6792
|
+
process.exit(0);
|
|
6793
|
+
}
|
|
6794
|
+
|
|
6795
|
+
if (command.startsWith('-')) {
|
|
6796
|
+
errorln(uiFail(`Unknown global flag: ${COLORS.bold}${command}${COLORS.reset}`, { colors: COLORS, glyphs: GLYPHS }));
|
|
6797
|
+
errorln(`${COLORS.dim}OpenCode is the primary conversation surface; run ${COLORS.reset}construct sync${COLORS.dim} to refresh adapters, or ${COLORS.reset}construct --help${COLORS.dim} for CLI commands.${COLORS.reset}`);
|
|
6798
|
+
process.exit(1);
|
|
6799
|
+
}
|
|
6800
|
+
|
|
6740
6801
|
// Unknown-flag guidance for commands that fully declare their flags (strictFlags
|
|
6741
6802
|
// in the spec). A stray or typo'd flag on these is a user error, not a no-op:
|
|
6742
6803
|
// reject it with the nearest declared flag and the command's help, matching how
|
|
@@ -6800,7 +6861,12 @@ function validateKnownFlags(name, argv) {
|
|
|
6800
6861
|
|
|
6801
6862
|
const handler = handlers.get(command);
|
|
6802
6863
|
if (!handler) {
|
|
6803
|
-
|
|
6864
|
+
if (command === 'chat') {
|
|
6865
|
+
errorln('Unknown command: chat');
|
|
6866
|
+
errorln(`${COLORS.dim}Construct's local conversation UI has been removed. Use OpenCode as the primary conversation surface after running ${COLORS.reset}construct sync${COLORS.dim}.${COLORS.reset}`);
|
|
6867
|
+
process.exit(1);
|
|
6868
|
+
}
|
|
6869
|
+
errorln(uiFail(`Unknown command: ${COLORS.bold}${command}${COLORS.reset}`, { colors: COLORS, glyphs: GLYPHS }));
|
|
6804
6870
|
// Three-tier suggestion: prefix match → substring match → Levenshtein
|
|
6805
6871
|
// distance ≤ 2. Catches single-letter typos, missing-letter typos, and
|
|
6806
6872
|
// transpositions ('sycn' → 'sync') without dragging in a full fuzzy lib.
|
|
@@ -6831,9 +6897,9 @@ if (!handler) {
|
|
|
6831
6897
|
if (d < bestDist) { bestDist = d; suggestion = name; }
|
|
6832
6898
|
}
|
|
6833
6899
|
}
|
|
6834
|
-
if (suggestion) errorln(
|
|
6900
|
+
if (suggestion) errorln(`${COLORS.muted}Did you mean${COLORS.reset} ${COLORS.highlight}construct ${suggestion}${COLORS.reset}?`);
|
|
6835
6901
|
println('');
|
|
6836
|
-
println(`${COLORS.dim}Run
|
|
6902
|
+
println(`${COLORS.dim}Run ${COLORS.reset}construct --help${COLORS.dim} for available commands${COLORS.reset}`);
|
|
6837
6903
|
process.exit(1);
|
|
6838
6904
|
}
|
|
6839
6905
|
|