@geraldmaron/construct 1.2.3 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -10
- package/bin/construct +235 -209
- package/bin/construct-postinstall.mjs +0 -21
- package/commands/work/optimize-prompts.md +1 -1
- package/examples/distribution/sources/adr.md +2 -2
- package/examples/seed-observations/decisions.md +1 -1
- package/lib/artifact-loop-core.mjs +412 -0
- package/lib/artifact-manifest.mjs +6 -0
- package/lib/artifact-release-gate.mjs +115 -49
- package/lib/audit-rules.mjs +2 -4
- package/lib/audit-skills.mjs +32 -20
- package/lib/audit-specialists.mjs +44 -26
- package/lib/auto-docs.mjs +6 -5
- package/lib/brand-prose.mjs +3 -3
- package/lib/brand-tokens.mjs +2 -2
- package/lib/certification/artifact-fixtures.mjs +4 -1
- package/lib/certification/demo-parity.mjs +6 -32
- package/lib/certification/real-llm-scenarios.mjs +58 -13
- package/lib/certification/role-cards.mjs +7 -6
- package/lib/certification/role-overlays.mjs +4 -4
- package/lib/certification/runner.mjs +9 -2
- package/lib/certification/skill-inventory.mjs +34 -22
- package/lib/certification/skill-scenarios.mjs +21 -8
- package/lib/certification/specialist-contracts.mjs +4 -3
- package/lib/certification/specialist-scenarios.mjs +7 -6
- package/lib/certification/status.mjs +5 -4
- package/lib/cli-commands.mjs +55 -54
- package/lib/comment-lint.mjs +1 -5
- package/lib/completions.mjs +20 -2
- package/lib/config/legacy-config-migration.mjs +90 -24
- package/lib/config/schema.mjs +5 -5
- package/lib/config/source-targets.mjs +80 -0
- package/lib/contracts/validate.mjs +119 -51
- package/lib/decisions/golden.mjs +3 -2
- package/lib/decisions/registry.mjs +11 -6
- package/lib/demo-project.mjs +188 -0
- package/lib/demo-recording.mjs +9 -34
- package/lib/demo-script.mjs +2 -2
- package/lib/demo-surface.mjs +13 -131
- package/lib/demo-tour-renderer.mjs +92 -0
- package/lib/demo.mjs +69 -54
- package/lib/diagram-export.mjs +63 -14
- package/lib/doctor/cli.mjs +12 -2
- package/lib/doctor/source-checkout.mjs +3 -4
- package/lib/doctor/watchers/consistency.mjs +148 -43
- package/lib/doctor/watchers/credential-parity.mjs +97 -0
- package/lib/doctor/watchers/mcp-protocol.mjs +1 -1
- package/lib/doctor/watchers/service-health.mjs +4 -32
- package/lib/document-export.mjs +73 -9
- package/lib/document-extract.mjs +10 -198
- package/lib/embed/artifact.mjs +2 -3
- package/lib/embed/cli.mjs +1 -1
- package/lib/embed/config.mjs +1 -2
- package/lib/embed/customer-profiles.mjs +1 -1
- package/lib/embed/daemon.mjs +5 -6
- package/lib/embed/demand-fetch.mjs +114 -2
- package/lib/embed/docs-lifecycle.mjs +9 -5
- package/lib/embed/intake-metrics.mjs +2 -2
- package/lib/embed/notifications.mjs +2 -3
- package/lib/embed/recommendation-store.mjs +25 -22
- package/lib/embed/role-framing.mjs +4 -9
- package/lib/embedded-contract/capability.mjs +4 -2
- package/lib/embedded-contract/contract-version.mjs +1 -1
- package/lib/env-config.mjs +2 -2
- package/lib/flavors/loader.mjs +21 -19
- package/lib/graph/build-from-registry.mjs +31 -7
- package/lib/graph/staleness.mjs +1 -1
- package/lib/headhunt.mjs +82 -35
- package/lib/hooks/agent-tracker.mjs +2 -2
- package/lib/hooks/config-protection.mjs +1 -1
- package/lib/hooks/registry-sync.mjs +3 -3
- package/lib/host-capabilities.mjs +5 -5
- package/lib/improvement/controller.mjs +2 -2
- package/lib/init-unified.mjs +19 -21
- package/lib/intake/classify.mjs +26 -0
- package/lib/intake/daemon.mjs +2 -3
- package/lib/intake/prepare.mjs +10 -3
- package/lib/intake/session-prelude.mjs +1 -1
- package/lib/intake/tables/creative.mjs +9 -9
- package/lib/intake/tables/operations.mjs +4 -4
- package/lib/intake/tables/research.mjs +2 -2
- package/lib/integrations/intake-integrations.mjs +9 -10
- package/lib/knowledge/research-store.mjs +2 -2
- package/lib/mcp/server.mjs +104 -61
- package/lib/mcp/tool-budget.mjs +1 -46
- package/lib/mcp/tool-recovery.mjs +44 -0
- package/lib/mcp/tools/artifact-author.mjs +36 -0
- package/lib/mcp/tools/find-tool.mjs +86 -0
- package/lib/mcp/tools/orchestration-run.mjs +107 -69
- package/lib/mcp/tools/project.mjs +4 -3
- package/lib/mcp/tools/{profile.mjs → scope.mjs} +54 -52
- package/lib/mcp/tools/skills.mjs +39 -22
- package/lib/mcp/tools/telemetry.mjs +2 -1
- package/lib/mcp/tools/workflow.mjs +1 -1
- package/lib/mcp-platform-config.mjs +7 -5
- package/lib/migrations/index.mjs +4 -2
- package/lib/migrations/v2-unified-registry.mjs +35 -0
- package/lib/model-router.mjs +203 -36
- package/lib/models/catalog.mjs +25 -9
- package/lib/models/execution-capability-profile.mjs +3 -3
- package/lib/models/execution-policy.mjs +7 -6
- package/lib/models/provider-poll.mjs +18 -4
- package/lib/opencode-config.mjs +56 -1
- package/lib/opencode-runtime-plugin.mjs +13 -10
- package/lib/oracle/artifact-gate.mjs +14 -4
- package/lib/oracle/cli.mjs +2 -0
- package/lib/oracle/dispatch.mjs +18 -1
- package/lib/oracle/execute.mjs +39 -7
- package/lib/oracle/index.mjs +1 -1
- package/lib/oracle/org-graph.mjs +10 -5
- package/lib/oracle/policy.mjs +6 -0
- package/lib/oracle/read-model.mjs +103 -6
- package/lib/oracle/reconcile.mjs +31 -4
- package/lib/oracle/remediation-dispatch.mjs +53 -0
- package/lib/oracle/routing.mjs +5 -0
- package/lib/oracle/synthesize.mjs +80 -3
- package/lib/orchestration/routing-tables.mjs +44 -10
- package/lib/orchestration/runtime.mjs +2 -0
- package/lib/orchestration/worker.mjs +1 -1
- package/lib/orchestration-policy.mjs +351 -62
- package/lib/overrides/resolver.mjs +1 -12
- package/lib/parity.mjs +37 -11
- package/lib/policy/engine.mjs +80 -15
- package/lib/prompt-composer.js +51 -15
- package/lib/prompt-metadata.mjs +3 -2
- package/lib/providers/connection-probe.mjs +58 -0
- package/lib/providers/copilot-auth.mjs +1 -1
- package/lib/providers/credential-bootstrap.mjs +1 -1
- package/lib/providers/op-run.mjs +3 -4
- package/lib/providers/secret-resolver.mjs +31 -0
- package/lib/publish-tooling.mjs +3 -11
- package/lib/publish.mjs +12 -29
- package/lib/reconcile/mcp-entry-reconcile.mjs +14 -9
- package/lib/reflect.mjs +2 -2
- package/lib/registry/agent-manifest.mjs +190 -0
- package/lib/registry/assemble.mjs +133 -0
- package/lib/registry/catalog.mjs +171 -0
- package/lib/registry/cli.mjs +590 -6
- package/lib/registry/consolidation.mjs +5 -4
- package/lib/registry/docs-sync.mjs +67 -0
- package/lib/registry/loader.mjs +147 -0
- package/lib/registry/org-io.mjs +76 -0
- package/lib/registry/retired-paths.mjs +71 -0
- package/lib/registry/surface-map.mjs +5 -7
- package/lib/registry/validator.mjs +438 -0
- package/lib/research-execution-policy.mjs +173 -0
- package/lib/roles/catalog.mjs +4 -9
- package/lib/roles/fence.mjs +107 -1
- package/lib/roles/flavor-bindings.mjs +68 -0
- package/lib/roles/gateway.mjs +140 -29
- package/lib/roles/manifest.mjs +22 -13
- package/lib/roles/router.mjs +1 -1
- package/lib/scopes/enrich.mjs +67 -0
- package/lib/scopes/hooks.mjs +12 -0
- package/lib/{profiles → scopes}/lifecycle.mjs +70 -70
- package/lib/scopes/loader.mjs +104 -0
- package/lib/scopes/rebrand.mjs +32 -0
- package/lib/scopes/research-profile.mjs +16 -0
- package/lib/scopes/teams.mjs +98 -0
- package/lib/service-manager.mjs +1 -117
- package/lib/setup-prompts.mjs +1 -1
- package/lib/setup.mjs +6 -26
- package/lib/skills/composition-graph.mjs +98 -0
- package/lib/skills/router.mjs +80 -0
- package/lib/specialist-contracts.mjs +17 -18
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/specialists/prompt-schema.mjs +6 -5
- package/lib/specialists/roster.mjs +7 -12
- package/lib/specialists/schema.mjs +9 -6
- package/lib/status.mjs +25 -90
- package/lib/storage/file-lock.mjs +6 -3
- package/lib/telemetry/skill-calls.mjs +1 -1
- package/lib/templates/doc-presentation.mjs +63 -0
- package/lib/templates/visual-requirements.mjs +35 -2
- package/lib/term-format.mjs +107 -2
- package/lib/test-env-setup.mjs +21 -0
- package/lib/ui/components.mjs +42 -0
- package/lib/ui/glyphs.mjs +58 -0
- package/lib/ui/links.mjs +115 -0
- package/lib/ui/theme.mjs +108 -0
- package/lib/uninstall/uninstall.mjs +2 -1
- package/lib/validator.mjs +45 -8
- package/lib/validators/skill-effectiveness.mjs +174 -0
- package/lib/validators/skills.mjs +1 -1
- package/package.json +12 -8
- package/personas/construct.md +12 -3
- package/platforms/claude/CLAUDE.md +1 -1
- package/rules/common/beads-hygiene.md +52 -0
- package/rules/common/neurodivergent-output.md +4 -7
- package/rules/common/research.md +2 -0
- package/scripts/sync-specialists.mjs +269 -51
- package/skills/ai/prompt-optimizer.md +3 -3
- package/skills/brand/output-vibe.md +48 -0
- package/skills/docs/adr-workflow.md +3 -0
- package/skills/docs/backlog-proposal-workflow.md +3 -0
- package/skills/docs/codebase-research-workflow.md +5 -2
- package/skills/docs/customer-profile-workflow.md +3 -0
- package/skills/docs/document-ingest-workflow.md +3 -0
- package/skills/docs/evidence-ingest-workflow.md +4 -1
- package/skills/docs/init-project.md +1 -1
- package/skills/docs/prd-workflow.md +1 -1
- package/skills/docs/prfaq-workflow.md +3 -0
- package/skills/docs/product-intelligence-workflow.md +4 -1
- package/skills/docs/product-signal-workflow.md +3 -0
- package/skills/docs/research-workflow.md +13 -6
- package/skills/docs/runbook-workflow.md +3 -0
- package/skills/docs/strategy-workflow.md +3 -0
- package/skills/docs/user-research-workflow.md +6 -3
- package/skills/operating/orchestration-reference.md +1 -1
- package/skills/roles/{engineer.ai.md → ai-engineer.md} +2 -2
- package/skills/roles/architect.ai-systems.md +1 -1
- package/skills/roles/architect.data.md +1 -1
- package/skills/roles/architect.enterprise.md +1 -1
- package/skills/roles/architect.integration.md +1 -1
- package/skills/roles/architect.md +1 -1
- package/skills/roles/architect.platform.md +1 -1
- package/skills/roles/{product-manager.business-strategy.md → business-strategist.md} +3 -3
- package/skills/roles/data-analyst.experiment.md +1 -1
- package/skills/roles/data-analyst.md +1 -1
- package/skills/roles/data-analyst.product-intelligence.md +1 -1
- package/skills/roles/data-analyst.product.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/{engineer.data.md → data-engineer.md} +2 -2
- package/skills/roles/data-engineer.pipeline.md +2 -2
- package/skills/roles/data-engineer.vector-retrieval.md +2 -2
- package/skills/roles/data-engineer.warehouse.md +2 -2
- package/skills/roles/debugger.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +1 -1
- package/skills/roles/{reviewer.devil-advocate.md → devil-advocate.md} +2 -2
- package/skills/roles/{operator.docs.md → docs-keeper.md} +3 -3
- package/skills/roles/engineer.md +3 -7
- package/skills/roles/{reviewer.evaluator.md → evaluator.md} +2 -2
- package/skills/roles/{researcher.explorer.md → explorer.md} +2 -2
- package/skills/roles/{operator.md → operations.md} +2 -5
- package/skills/roles/orchestrator.md +1 -1
- package/skills/roles/{engineer.platform.md → platform-engineer.md} +2 -2
- package/skills/roles/product-manager.ai-product.md +1 -1
- package/skills/roles/product-manager.enterprise.md +1 -1
- package/skills/roles/product-manager.growth.md +1 -2
- package/skills/roles/product-manager.md +1 -2
- package/skills/roles/product-manager.platform.md +1 -1
- package/skills/roles/product-manager.product.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -1
- package/skills/roles/qa.api-contract.md +1 -1
- package/skills/roles/qa.data-pipeline.md +1 -1
- package/skills/roles/qa.md +1 -1
- package/skills/roles/qa.web-ui.md +1 -1
- package/skills/roles/{operator.release.md → release-manager.md} +3 -3
- package/skills/roles/researcher.md +1 -1
- package/skills/roles/reviewer.md +1 -1
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.appsec.md +1 -1
- package/skills/roles/security.cloud.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +1 -1
- package/skills/roles/security.privacy.md +1 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/{operator.sre.md → sre.md} +3 -3
- package/skills/roles/{qa.test-automation.md → test-automation.md} +2 -2
- package/skills/roles/{reviewer.trace.md → trace-reviewer.md} +2 -2
- package/skills/roles/{researcher.ux.md → ux-researcher.md} +2 -2
- package/skills/routing.json +18 -0
- package/skills/routing.md +1 -1
- package/specialists/artifact-manifest.json +2 -1
- package/specialists/audit-enrichments.json +14 -14
- package/specialists/org/contracts/accessibility-to-qa.json +37 -0
- package/specialists/org/contracts/any-to-business-strategist.json +57 -0
- package/specialists/org/contracts/any-to-debugger.json +38 -0
- package/specialists/org/contracts/any-to-designer.json +33 -0
- package/specialists/org/contracts/any-to-docs-keeper.json +34 -0
- package/specialists/org/contracts/any-to-explorer.json +39 -0
- package/specialists/org/contracts/any-to-sre-incident.json +33 -0
- package/specialists/org/contracts/any-to-trace-reviewer.json +32 -0
- package/specialists/org/contracts/architect-to-ai-engineer.json +32 -0
- package/specialists/org/contracts/architect-to-data-engineer.json +52 -0
- package/specialists/org/contracts/architect-to-devil-advocate.json +38 -0
- package/specialists/org/contracts/architect-to-engineer.json +34 -0
- package/specialists/org/contracts/architect-to-evaluator.json +59 -0
- package/specialists/org/contracts/architect-to-legal-compliance.json +55 -0
- package/specialists/org/contracts/architect-to-operations.json +45 -0
- package/specialists/org/contracts/architect-to-platform-engineer.json +42 -0
- package/specialists/org/contracts/business-strategist-to-product-manager.json +39 -0
- package/specialists/org/contracts/construct-to-orchestrator.json +36 -0
- package/specialists/org/contracts/construct-to-rd-lead.json +53 -0
- package/specialists/org/contracts/data-analyst-to-product-manager.json +43 -0
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +36 -0
- package/specialists/org/contracts/designer-to-accessibility.json +36 -0
- package/specialists/org/contracts/engineer-to-qa.json +34 -0
- package/specialists/org/contracts/engineer-to-reviewer.json +52 -0
- package/specialists/org/contracts/explorer-to-engineer.json +38 -0
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +43 -0
- package/specialists/org/contracts/platform-engineer-to-engineer.json +31 -0
- package/specialists/org/contracts/product-manager-to-architect.json +71 -0
- package/specialists/org/contracts/product-manager-to-data-analyst.json +49 -0
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +51 -0
- package/specialists/org/contracts/qa-to-release-manager.json +42 -0
- package/specialists/org/contracts/qa-to-test-automation.json +42 -0
- package/specialists/org/contracts/rd-lead-to-architect.json +38 -0
- package/specialists/org/contracts/researcher-to-architect.json +40 -0
- package/specialists/org/contracts/researcher-to-product-manager.json +57 -0
- package/specialists/org/contracts/reviewer-to-security.json +41 -0
- package/specialists/org/contracts/sre-to-release-manager.json +36 -0
- package/specialists/org/contracts/test-automation-to-engineer.json +34 -0
- package/specialists/org/contracts/trace-reviewer-to-sre.json +41 -0
- package/specialists/org/contracts/user-to-construct.json +30 -0
- package/specialists/org/groups/engineering-group.json +43 -0
- package/specialists/org/groups/governance-group.json +38 -0
- package/specialists/org/groups/operations-group.json +41 -0
- package/specialists/org/groups/product-group.json +46 -0
- package/specialists/org/groups/quality-group.json +42 -0
- package/specialists/org/groups/strategy-group.json +41 -0
- package/specialists/org/policies/action-approval.json +13 -0
- package/specialists/org/policies/agents-routing.json +13 -0
- package/specialists/org/policies/architecture.json +20 -0
- package/specialists/org/policies/bash-safety.json +13 -0
- package/specialists/org/policies/beads-hygiene.json +13 -0
- package/specialists/org/policies/bootstrap-state.json +13 -0
- package/specialists/org/policies/code-review.json +13 -0
- package/specialists/org/policies/coding-style.json +13 -0
- package/specialists/org/policies/comments.json +13 -0
- package/specialists/org/policies/commit-approval.json +13 -0
- package/specialists/org/policies/contract-preconditions.json +13 -0
- package/specialists/org/policies/deployment.json +20 -0
- package/specialists/org/policies/description.json +14 -0
- package/specialists/org/policies/design-approval.json +19 -0
- package/specialists/org/policies/doc-ownership.json +13 -0
- package/specialists/org/policies/file-path-fence.json +13 -0
- package/specialists/org/policies/framing.json +13 -0
- package/specialists/org/policies/git-workflow.json +13 -0
- package/specialists/org/policies/incident-response.json +15 -0
- package/specialists/org/policies/intake-triage.json +15 -0
- package/specialists/org/policies/neurodivergent-output.json +13 -0
- package/specialists/org/policies/no-fabrication.json +13 -0
- package/specialists/org/policies/patterns.json +13 -0
- package/specialists/org/policies/quality-gate-approval.json +17 -0
- package/specialists/org/policies/release-gates.json +13 -0
- package/specialists/org/policies/research-evidence.json +13 -0
- package/specialists/org/policies/review-before-change.json +13 -0
- package/specialists/org/policies/rollback.json +15 -0
- package/specialists/org/policies/scope-change.json +20 -0
- package/specialists/org/policies/secret-scan.json +13 -0
- package/specialists/org/policies/security-approval.json +17 -0
- package/specialists/org/policies/security.json +13 -0
- package/specialists/org/policies/session-efficiency.json +13 -0
- package/specialists/org/policies/skill-routing.json +13 -0
- package/specialists/org/policies/strategic-prioritization.json +17 -0
- package/specialists/org/policies/testing.json +13 -0
- package/specialists/org/policies/tool-invisibility.json +14 -0
- package/specialists/org/scopes/creative.json +54 -0
- package/specialists/org/scopes/operations.json +51 -0
- package/specialists/org/scopes/research.json +60 -0
- package/specialists/org/scopes/rnd.json +81 -0
- package/specialists/org/specialists/cx-accessibility.json +69 -0
- package/specialists/org/specialists/cx-ai-engineer.json +75 -0
- package/specialists/org/specialists/cx-architect.json +89 -0
- package/specialists/org/specialists/cx-business-strategist.json +72 -0
- package/specialists/org/specialists/cx-data-analyst.json +68 -0
- package/specialists/org/specialists/cx-data-engineer.json +71 -0
- package/specialists/org/specialists/cx-debugger.json +75 -0
- package/specialists/org/specialists/cx-designer.json +85 -0
- package/specialists/org/specialists/cx-devil-advocate.json +71 -0
- package/specialists/org/specialists/cx-docs-keeper.json +82 -0
- package/specialists/org/specialists/cx-engineer.json +106 -0
- package/specialists/org/specialists/cx-evaluator.json +69 -0
- package/specialists/org/specialists/cx-explorer.json +69 -0
- package/specialists/org/specialists/cx-legal-compliance.json +74 -0
- package/specialists/org/specialists/cx-operations.json +72 -0
- package/specialists/org/specialists/cx-oracle.json +46 -0
- package/specialists/org/specialists/cx-orchestrator.json +81 -0
- package/specialists/org/specialists/cx-platform-engineer.json +80 -0
- package/specialists/org/specialists/cx-product-manager.json +102 -0
- package/specialists/org/specialists/cx-qa.json +79 -0
- package/specialists/org/specialists/cx-rd-lead.json +73 -0
- package/specialists/org/specialists/cx-release-manager.json +77 -0
- package/specialists/org/specialists/cx-researcher.json +82 -0
- package/specialists/org/specialists/cx-reviewer.json +71 -0
- package/specialists/org/specialists/cx-security.json +91 -0
- package/specialists/org/specialists/cx-sre.json +94 -0
- package/specialists/org/specialists/cx-test-automation.json +69 -0
- package/specialists/org/specialists/cx-trace-reviewer.json +69 -0
- package/specialists/org/specialists/cx-ux-researcher.json +68 -0
- package/specialists/org/teams/accessibility-team.json +39 -0
- package/specialists/org/teams/design-team.json +40 -0
- package/specialists/org/teams/engineering-team.json +50 -0
- package/specialists/org/teams/governance-team.json +41 -0
- package/specialists/org/teams/operations-team.json +46 -0
- package/specialists/org/teams/product-management-team.json +45 -0
- package/specialists/org/teams/quality-team.json +49 -0
- package/specialists/org/teams/research-team.json +39 -0
- package/specialists/org/teams/strategy-team.json +48 -0
- package/specialists/org/teams/ux-research-team.json +39 -0
- package/specialists/prompts/_shared/validation-contract.md +1 -1
- package/specialists/prompts/_team-template.md +10 -0
- package/specialists/prompts/cx-accessibility.md +1 -0
- package/specialists/prompts/cx-ai-engineer.md +1 -1
- package/specialists/prompts/cx-business-strategist.md +1 -1
- package/specialists/prompts/cx-data-engineer.md +1 -1
- package/specialists/prompts/cx-designer.md +1 -0
- package/specialists/prompts/cx-devil-advocate.md +1 -1
- package/specialists/prompts/cx-docs-keeper.md +1 -1
- package/specialists/prompts/cx-evaluator.md +1 -1
- package/specialists/prompts/cx-explorer.md +1 -1
- package/specialists/prompts/cx-operations.md +1 -1
- package/specialists/prompts/cx-oracle.md +7 -3
- package/specialists/prompts/cx-orchestrator.md +17 -1
- package/specialists/prompts/cx-platform-engineer.md +1 -1
- package/specialists/prompts/cx-product-manager.md +2 -0
- package/specialists/prompts/cx-release-manager.md +1 -1
- package/specialists/prompts/cx-researcher.md +7 -4
- package/specialists/prompts/cx-sre.md +1 -1
- package/specialists/prompts/cx-test-automation.md +2 -2
- package/specialists/prompts/cx-trace-reviewer.md +3 -3
- package/specialists/prompts/cx-ux-researcher.md +2 -1
- package/templates/demos/scripts/architecture-review-adr.json +30 -0
- package/templates/demos/scripts/capability-contract.json +25 -0
- package/templates/demos/scripts/intake-triage.json +25 -0
- package/templates/demos/scripts/profile-doctor-health.json +25 -0
- package/templates/demos/tapes/agentic-platforms-prd.tape +2 -2
- package/templates/demos/tapes/architecture-review-adr.tape +44 -0
- package/templates/demos/tapes/capability-contract.tape +39 -0
- package/templates/demos/tapes/intake-triage.tape +39 -0
- package/templates/demos/tapes/profile-doctor-health.tape +39 -0
- package/templates/distribution/construct-brand.typ +23 -18
- package/templates/distribution/construct-decision.typ +1 -1
- package/templates/distribution/construct-pdf.typ +1 -1
- package/templates/distribution/construct-prd.typ +1 -1
- package/templates/distribution/construct-research.typ +1 -1
- package/templates/distribution/mermaid-puppeteer.json +8 -0
- package/templates/docs/persona-artifact.md +1 -1
- package/templates/docs/prd.md +6 -0
- package/apps/chat/engine/ai-sdk-agent.mjs +0 -183
- package/apps/chat/engine/loop-driver.mjs +0 -211
- package/apps/chat/engine/models.mjs +0 -122
- package/apps/chat/engine/provider-adapters.mjs +0 -171
- package/apps/chat/engine/tools/permission.mjs +0 -54
- package/apps/chat/engine/tools/primitives.mjs +0 -180
- package/apps/chat/engine/tools/registry.mjs +0 -122
- package/apps/chat/engine/turn-controls.mjs +0 -70
- package/lib/boundary.mjs +0 -127
- package/lib/certification/dashboard-api.mjs +0 -71
- package/lib/chat/cli.mjs +0 -333
- package/lib/chat/command-suggest.mjs +0 -161
- package/lib/chat/commands.mjs +0 -215
- package/lib/chat/config.mjs +0 -142
- package/lib/chat/context-compactor.mjs +0 -250
- package/lib/chat/context-continuation.mjs +0 -253
- package/lib/chat/continuation-source.mjs +0 -58
- package/lib/chat/demo-guide.mjs +0 -61
- package/lib/chat/design-tokens.mjs +0 -91
- package/lib/chat/desktop-binary.mjs +0 -81
- package/lib/chat/desktop-build.mjs +0 -130
- package/lib/chat/desktop-launcher.mjs +0 -133
- package/lib/chat/evidence.mjs +0 -145
- package/lib/chat/export.mjs +0 -74
- package/lib/chat/harness/driver.mjs +0 -91
- package/lib/chat/list-picker.mjs +0 -112
- package/lib/chat/model-picker.mjs +0 -356
- package/lib/chat/openrouter-fallback.mjs +0 -151
- package/lib/chat/permission-prompt.mjs +0 -33
- package/lib/chat/picker-catalog.mjs +0 -45
- package/lib/chat/policy-telemetry.mjs +0 -34
- package/lib/chat/present.mjs +0 -246
- package/lib/chat/session-context.mjs +0 -39
- package/lib/chat/session-persist.mjs +0 -73
- package/lib/chat/session-restore.mjs +0 -71
- package/lib/chat/session-settings.mjs +0 -53
- package/lib/chat/system-prompt.mjs +0 -52
- package/lib/chat/transparency.mjs +0 -93
- package/lib/chat/tui/color-scheme.mjs +0 -42
- package/lib/chat/tui/markdown.mjs +0 -123
- package/lib/chat/tui/presentation.mjs +0 -100
- package/lib/chat/tui/render.mjs +0 -500
- package/lib/chat/tui/turn-block.mjs +0 -284
- package/lib/chat/tui/turn-present.mjs +0 -18
- package/lib/chat/tui/turn-state.mjs +0 -88
- package/lib/chat/tui/usage.mjs +0 -122
- package/lib/chat/web-commands.mjs +0 -146
- package/lib/chat/web-launcher.mjs +0 -63
- package/lib/chat/web-picker-keys.mjs +0 -46
- package/lib/chat/web-session.mjs +0 -159
- package/lib/config/alias.mjs +0 -57
- package/lib/dashboard-demo.mjs +0 -71
- package/lib/dashboard-static.mjs +0 -175
- package/lib/install/desktop-binary-download.mjs +0 -88
- package/lib/profiles/loader.mjs +0 -123
- package/lib/profiles/rebrand.mjs +0 -46
- package/lib/server/auth.mjs +0 -169
- package/lib/server/chat-loop.mjs +0 -622
- package/lib/server/chat.mjs +0 -336
- package/lib/server/cors.mjs +0 -77
- package/lib/server/csrf.mjs +0 -103
- package/lib/server/demo-preview.mjs +0 -63
- package/lib/server/index.mjs +0 -2641
- package/lib/server/insights.mjs +0 -780
- package/lib/server/langfuse-login.mjs +0 -58
- package/lib/server/rate-limit.mjs +0 -93
- package/lib/server/telemetry-login.mjs +0 -100
- package/lib/server/webhook.mjs +0 -512
- package/specialists/contracts.json +0 -1032
- package/specialists/contracts.schema.json +0 -83
- package/specialists/policy-inventory.json +0 -188
- package/specialists/registry.json +0 -1412
- package/specialists/role-manifests.json +0 -217
- package/specialists/teams.json +0 -94
package/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
|
-
|
|
132
|
-
// Bare-command landing — show the same core-command listing as `construct
|
|
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
150
|
|
|
140
|
-
|
|
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' },
|
|
@@ -658,12 +672,17 @@ async function cmdDoctor() {
|
|
|
658
672
|
// configured without a manual copy. Applied to process.env too so the check
|
|
659
673
|
// below sees the just-migrated values in the same run.
|
|
660
674
|
try {
|
|
661
|
-
const { migrateLegacyModelConfig } = await import('../lib/config/legacy-config-migration.mjs');
|
|
675
|
+
const { migrateLegacyModelConfig, migrateLegacyCredentialConfig } = await import('../lib/config/legacy-config-migration.mjs');
|
|
662
676
|
const migration = migrateLegacyModelConfig({ homeDir: HOME });
|
|
663
677
|
if (migration.performed) {
|
|
664
678
|
Object.assign(process.env, migration.migrated);
|
|
665
679
|
println(` fix: migrated ${Object.keys(migration.migrated).join(', ')} from ${migration.legacyPath} → ${migration.xdgPath}`);
|
|
666
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
|
+
}
|
|
667
686
|
} catch (err) {
|
|
668
687
|
add(`Models — legacy config migration failed: ${err.message}`, false, true);
|
|
669
688
|
}
|
|
@@ -680,7 +699,7 @@ async function cmdDoctor() {
|
|
|
680
699
|
if (resolved.complete && !resolved.errors) {
|
|
681
700
|
add('Models — all tiers configured', true);
|
|
682
701
|
} else if (resolved.configured === 0) {
|
|
683
|
-
add('Models — no tier configured (
|
|
702
|
+
add('Models — no tier configured (run `construct models --apply`)', false, true);
|
|
684
703
|
} else {
|
|
685
704
|
const missing = Object.entries(resolved.models)
|
|
686
705
|
.filter(([, v]) => !v)
|
|
@@ -862,19 +881,28 @@ async function cmdDoctor() {
|
|
|
862
881
|
|
|
863
882
|
// Validate promptFile references and skill bindings
|
|
864
883
|
try {
|
|
865
|
-
const registry =
|
|
866
|
-
const brokenPromptFiles = (registry.specialists ??
|
|
884
|
+
const registry = loadRegistry({ rootDir: ROOT_DIR });
|
|
885
|
+
const brokenPromptFiles = Object.values(registry.specialists ?? {}).filter((s) => {
|
|
867
886
|
if (!s.promptFile) return false;
|
|
868
887
|
return !fs.existsSync(path.join(ROOT_DIR, s.promptFile));
|
|
869
888
|
});
|
|
870
889
|
add('All specialist promptFiles resolve', brokenPromptFiles.length === 0);
|
|
871
890
|
const headhuntClassifierAgents = ['cx-docs-keeper', 'cx-researcher', 'cx-architect', 'cx-security', 'cx-engineer'];
|
|
872
|
-
const registryNames = new Set((registry.specialists ??
|
|
891
|
+
const registryNames = new Set(Object.values(registry.specialists ?? {}).map((s) => `cx-${s.name}`));
|
|
873
892
|
const classifierOrphans = headhuntClassifierAgents.filter((n) => !registryNames.has(n));
|
|
874
893
|
add('Headhunt classifier agents exist in registry', classifierOrphans.length === 0);
|
|
875
894
|
const skillAudit = auditSkills({ rootDir: ROOT_DIR, silent: true });
|
|
876
895
|
add('No declared skills missing on disk', skillAudit.pass, false);
|
|
877
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
|
+
);
|
|
878
906
|
const specialistAudit = auditSpecialists({ rootDir: ROOT_DIR, silent: true });
|
|
879
907
|
add('Specialist/skill audit cross-checks', specialistAudit.pass, false);
|
|
880
908
|
|
|
@@ -919,7 +947,8 @@ async function cmdDoctor() {
|
|
|
919
947
|
if (fs.existsSync(beadsPreCommit) && !inCI) {
|
|
920
948
|
const hp = spawnSync('git', ['config', '--get', 'core.hooksPath'], { cwd: ROOT_DIR, encoding: 'utf8' });
|
|
921
949
|
const value = hp.status === 0 ? (hp.stdout || '').trim() : '';
|
|
922
|
-
const
|
|
950
|
+
const absEquiv = path.join(ROOT_DIR, '.beads', 'hooks');
|
|
951
|
+
const wired = value === '.beads/hooks' || value === absEquiv;
|
|
923
952
|
const label = wired
|
|
924
953
|
? 'Git hooks wired (core.hooksPath = .beads/hooks)'
|
|
925
954
|
: value
|
|
@@ -1422,7 +1451,6 @@ async function cmdDev(args = []) {
|
|
|
1422
1451
|
}
|
|
1423
1452
|
|
|
1424
1453
|
println('');
|
|
1425
|
-
println('Dashboard: http://127.0.0.1:4242');
|
|
1426
1454
|
println('Run \x1b[32mconstruct\x1b[0m for commands');
|
|
1427
1455
|
println('');
|
|
1428
1456
|
|
|
@@ -1449,29 +1477,6 @@ async function cmdStop() {
|
|
|
1449
1477
|
} catch { /* best effort */ }
|
|
1450
1478
|
}
|
|
1451
1479
|
|
|
1452
|
-
async function cmdDashboard() {
|
|
1453
|
-
if (restArgsCache.includes('--token')) {
|
|
1454
|
-
const { generateToken, setDashboardToken, getDashboardToken } = await import('../lib/server/auth.mjs');
|
|
1455
|
-
const existing = getDashboardToken();
|
|
1456
|
-
if (existing) {
|
|
1457
|
-
println(`Dashboard token already set. To rotate it, remove CONSTRUCT_DASHBOARD_TOKEN from ~/.construct/config.env and re-run.`);
|
|
1458
|
-
return;
|
|
1459
|
-
}
|
|
1460
|
-
const token = generateToken();
|
|
1461
|
-
setDashboardToken(token);
|
|
1462
|
-
println(`Dashboard token generated and saved to ~/.construct/config.env`);
|
|
1463
|
-
println(`CONSTRUCT_DASHBOARD_TOKEN=${token}`);
|
|
1464
|
-
println(`Pass this token in the dashboard login prompt or as: Authorization: Bearer <token>`);
|
|
1465
|
-
return;
|
|
1466
|
-
}
|
|
1467
|
-
const dashboard = await startDashboard({ rootDir: ROOT_DIR, homeDir: HOME });
|
|
1468
|
-
if (dashboard.reused) {
|
|
1469
|
-
println(`Construct dashboard already running on ${dashboard.url} (pid ${dashboard.pid})`);
|
|
1470
|
-
} else {
|
|
1471
|
-
println(`Started Construct dashboard on ${dashboard.url} (pid ${dashboard.pid})`);
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
1480
|
async function cmdVersion() {
|
|
1476
1481
|
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'package.json'), 'utf8'));
|
|
1477
1482
|
println(`construct v${pkg.version}`);
|
|
@@ -1486,9 +1491,7 @@ async function cmdMigrate(args) {
|
|
|
1486
1491
|
const candidates = pathArg
|
|
1487
1492
|
? [pathArg]
|
|
1488
1493
|
: [
|
|
1489
|
-
path.join(ROOT_DIR, 'specialists', 'registry.json'),
|
|
1490
|
-
path.join(ROOT_DIR, 'specialists', 'contracts.json'),
|
|
1491
|
-
path.join(ROOT_DIR, 'specialists', 'role-manifests.json'),
|
|
1494
|
+
path.join(ROOT_DIR, 'specialists', 'unified-registry.json'),
|
|
1492
1495
|
path.join(process.cwd(), '.cx', 'config.json'),
|
|
1493
1496
|
];
|
|
1494
1497
|
|
|
@@ -1615,7 +1618,7 @@ async function cmdExport(args) {
|
|
|
1615
1618
|
inputPath: resolvedIn,
|
|
1616
1619
|
outputPath: resolvedOut,
|
|
1617
1620
|
format: toFlag,
|
|
1618
|
-
figures
|
|
1621
|
+
figures,
|
|
1619
1622
|
branding,
|
|
1620
1623
|
repoRoot: ROOT_DIR,
|
|
1621
1624
|
});
|
|
@@ -1635,19 +1638,17 @@ async function cmdPublish(args) {
|
|
|
1635
1638
|
async function cmdTools(args) {
|
|
1636
1639
|
const [sub, ...rest] = args;
|
|
1637
1640
|
if (sub !== 'detect') {
|
|
1638
|
-
errorln('Usage: construct tools detect [--json] [--figures] [--no-figures] [--demo=NAME]
|
|
1641
|
+
errorln('Usage: construct tools detect [--json] [--figures] [--no-figures] [--demo=NAME]');
|
|
1639
1642
|
process.exit(1);
|
|
1640
1643
|
}
|
|
1641
1644
|
const { detectPublishPipeline, formatToolsDetectReport } = await import('../lib/publish-tooling.mjs');
|
|
1642
1645
|
const json = rest.includes('--json');
|
|
1643
1646
|
const figures = !rest.includes('--no-figures');
|
|
1644
1647
|
const demos = rest.filter((a) => a.startsWith('--demo=')).map((a) => a.slice(7));
|
|
1645
|
-
const dashboardDemos = rest.filter((a) => a.startsWith('--dashboard-demo=')).map((a) => a.slice(17));
|
|
1646
1648
|
const detection = detectPublishPipeline({
|
|
1647
1649
|
format: 'pdf',
|
|
1648
1650
|
includeFigures: figures,
|
|
1649
1651
|
includeTerminalDemo: demos.length > 0,
|
|
1650
|
-
includeDashboardDemo: dashboardDemos.length > 0,
|
|
1651
1652
|
cwd: process.cwd(),
|
|
1652
1653
|
repoRoot: ROOT_DIR,
|
|
1653
1654
|
});
|
|
@@ -2106,7 +2107,7 @@ function printActiveConfig() {
|
|
|
2106
2107
|
}
|
|
2107
2108
|
|
|
2108
2109
|
async function printIntakeHelp() {
|
|
2109
|
-
const { getRebrand } = await import('../lib/
|
|
2110
|
+
const { getRebrand } = await import('../lib/scopes/rebrand.mjs');
|
|
2110
2111
|
const { intakeQueueLabel, signalNoun } = getRebrand(process.cwd());
|
|
2111
2112
|
println(`Construct intake — inspect and process the ${intakeQueueLabel}.`);
|
|
2112
2113
|
println('');
|
|
@@ -2224,7 +2225,7 @@ async function cmdIntake(args) {
|
|
|
2224
2225
|
}
|
|
2225
2226
|
|
|
2226
2227
|
const { createIntakeQueue } = await import('../lib/intake/queue.mjs');
|
|
2227
|
-
const { getRebrand } = await import('../lib/
|
|
2228
|
+
const { getRebrand } = await import('../lib/scopes/rebrand.mjs');
|
|
2228
2229
|
const cwd = process.cwd();
|
|
2229
2230
|
const rebrand = getRebrand(cwd);
|
|
2230
2231
|
const queue = createIntakeQueue(cwd, process.env);
|
|
@@ -3652,7 +3653,7 @@ async function cmdUpgrade(args) {
|
|
|
3652
3653
|
}
|
|
3653
3654
|
}
|
|
3654
3655
|
async function cmdValidate() {
|
|
3655
|
-
const reg =
|
|
3656
|
+
const reg = loadRegistry({ rootDir: ROOT_DIR });
|
|
3656
3657
|
const result = validateRegistry(reg, { rootDir: ROOT_DIR });
|
|
3657
3658
|
if (result.valid) {
|
|
3658
3659
|
println(`✓ Registry valid (${result.summary})`);
|
|
@@ -3695,9 +3696,9 @@ async function cmdOrchestrate(args) {
|
|
|
3695
3696
|
// running in-process, proving the engine-as-service contract editors/CI use.
|
|
3696
3697
|
if (rest.includes('--remote')) {
|
|
3697
3698
|
const remoteVal = flag('--remote');
|
|
3698
|
-
const base = (remoteVal && /^https?:\/\//.test(remoteVal)) ? remoteVal.replace(/\/$/, '') :
|
|
3699
|
-
|
|
3700
|
-
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;
|
|
3701
3702
|
const headers = { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) };
|
|
3702
3703
|
const positional = rest.filter((a) => !a.startsWith('--'));
|
|
3703
3704
|
try {
|
|
@@ -3727,7 +3728,7 @@ async function cmdOrchestrate(args) {
|
|
|
3727
3728
|
return;
|
|
3728
3729
|
}
|
|
3729
3730
|
} catch (err) {
|
|
3730
|
-
errorln(`Could not reach the orchestration
|
|
3731
|
+
errorln(`Could not reach the orchestration service at ${base}: ${err.message}. Check CONSTRUCT_ORCHESTRATION_URL.`);
|
|
3731
3732
|
process.exit(1);
|
|
3732
3733
|
}
|
|
3733
3734
|
}
|
|
@@ -3839,10 +3840,9 @@ async function cmdModels(args) {
|
|
|
3839
3840
|
if (sub === null) {
|
|
3840
3841
|
errorln('Note: `construct models --tier=… --set=…` is a deprecated alias for `construct models set --tier=… --model=…`.');
|
|
3841
3842
|
}
|
|
3842
|
-
const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'specialists', 'registry.json'), 'utf8'));
|
|
3843
3843
|
const preferFree = args.includes('--prefer-free');
|
|
3844
3844
|
const preferFreeSameFamily = args.includes('--prefer-free-same-family');
|
|
3845
|
-
const inferred = setModelWithTierInference(envPath, tier, setModel,
|
|
3845
|
+
const inferred = setModelWithTierInference(envPath, tier, setModel, {}, { preferFree, preferFreeSameFamily });
|
|
3846
3846
|
println(`Set ${tier} -> ${setModel} in ${envPath}`);
|
|
3847
3847
|
if (preferFreeSameFamily) println('Prefer-free-same-family mode: enabled');
|
|
3848
3848
|
if (preferFree) println('Prefer-free mode: enabled');
|
|
@@ -3873,10 +3873,9 @@ async function cmdModels(args) {
|
|
|
3873
3873
|
if (args.includes('--apply')) {
|
|
3874
3874
|
const apiKey = process.env.OPENROUTER_API_KEY || readOpenRouterApiKeyFromOpenCodeConfig();
|
|
3875
3875
|
const models = await pollFreeModels(apiKey);
|
|
3876
|
-
const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'specialists', 'registry.json'), 'utf8'));
|
|
3877
3876
|
const selections = {};
|
|
3878
3877
|
for (const currentTier of ['reasoning', 'standard', 'fast']) {
|
|
3879
|
-
const fallbacks =
|
|
3878
|
+
const fallbacks = [];
|
|
3880
3879
|
const id = selectForTier(models, currentTier, fallbacks);
|
|
3881
3880
|
if (id) selections[currentTier] = id;
|
|
3882
3881
|
}
|
|
@@ -3925,9 +3924,8 @@ async function cmdModels(args) {
|
|
|
3925
3924
|
}
|
|
3926
3925
|
if (args.includes('--list') || sub === 'list') {
|
|
3927
3926
|
const { getProviderModelCatalog } = await import('../lib/model-router.mjs');
|
|
3928
|
-
const { listChatModels } = await import('../apps/chat/engine/models.mjs');
|
|
3929
3927
|
const catalog = getProviderModelCatalog({ env: process.env, cwd: process.cwd() });
|
|
3930
|
-
const models =
|
|
3928
|
+
const models = listAvailableModels({ env: process.env, cwd: process.cwd() });
|
|
3931
3929
|
if (args.includes('--json')) {
|
|
3932
3930
|
println(JSON.stringify({ catalog, models }, null, 2));
|
|
3933
3931
|
return;
|
|
@@ -3939,8 +3937,7 @@ async function cmdModels(args) {
|
|
|
3939
3937
|
}
|
|
3940
3938
|
return;
|
|
3941
3939
|
}
|
|
3942
|
-
const
|
|
3943
|
-
const result = readCurrentModels(envPath, registry.models ?? {});
|
|
3940
|
+
const result = readCurrentModels(envPath, {});
|
|
3944
3941
|
const nothingSelected = ['reasoning', 'standard', 'fast'].every((t) => !result[t]);
|
|
3945
3942
|
println('Current model assignments:');
|
|
3946
3943
|
for (const currentTier of ['reasoning', 'standard', 'fast']) {
|
|
@@ -3952,7 +3949,7 @@ async function cmdModels(args) {
|
|
|
3952
3949
|
println('');
|
|
3953
3950
|
println('No tier has a model selected. Construct ships with no default.');
|
|
3954
3951
|
println('Pick one of:');
|
|
3955
|
-
println(' •
|
|
3952
|
+
println(' • `construct models` (single source of truth)');
|
|
3956
3953
|
println(' • construct models set --tier=<reasoning|standard|fast> --model=<provider/model-id>');
|
|
3957
3954
|
println(' • construct models --apply Poll OpenRouter free catalog and seed all tiers');
|
|
3958
3955
|
}
|
|
@@ -4480,12 +4477,6 @@ async function cmdDocsSite(args = []) {
|
|
|
4480
4477
|
}
|
|
4481
4478
|
}
|
|
4482
4479
|
|
|
4483
|
-
async function cmdDashboardSync(args) {
|
|
4484
|
-
const { runDashboardStaticCli } = await import('../lib/dashboard-static.mjs');
|
|
4485
|
-
const exitCode = await runDashboardStaticCli(args, { rootDir: ROOT_DIR });
|
|
4486
|
-
if (exitCode) process.exit(exitCode);
|
|
4487
|
-
}
|
|
4488
|
-
|
|
4489
4480
|
async function cmdBootstrap(args) {
|
|
4490
4481
|
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
4491
4482
|
const { runBootstrap } = await import('../lib/bootstrap.mjs');
|
|
@@ -4823,14 +4814,15 @@ async function cmdLintResearch() {
|
|
|
4823
4814
|
}
|
|
4824
4815
|
|
|
4825
4816
|
async function cmdLintAgents() {
|
|
4826
|
-
const {
|
|
4827
|
-
const
|
|
4828
|
-
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 });
|
|
4829
4821
|
if (errors.length === 0) {
|
|
4830
|
-
console.log(`specialists/
|
|
4822
|
+
console.log(`specialists/org: 0 errors`);
|
|
4831
4823
|
return;
|
|
4832
4824
|
}
|
|
4833
|
-
console.error(`specialists/
|
|
4825
|
+
console.error(`specialists/org: ${errors.length} error(s)`);
|
|
4834
4826
|
for (const err of errors) console.error(` ${err}`);
|
|
4835
4827
|
process.exit(1);
|
|
4836
4828
|
}
|
|
@@ -4839,10 +4831,10 @@ async function cmdLintContracts() {
|
|
|
4839
4831
|
const { validateContractsFile } = await import('../lib/contracts/validate.mjs');
|
|
4840
4832
|
const result = validateContractsFile({ repoRoot: ROOT_DIR });
|
|
4841
4833
|
if (result.ok) {
|
|
4842
|
-
console.log('specialists/
|
|
4834
|
+
console.log('specialists/unified-registry.json contracts: 0 errors');
|
|
4843
4835
|
return;
|
|
4844
4836
|
}
|
|
4845
|
-
console.error(`specialists/
|
|
4837
|
+
console.error(`specialists/unified-registry.json contracts: ${result.errors.length} error(s)`);
|
|
4846
4838
|
for (const err of result.errors) console.error(` ${err}`);
|
|
4847
4839
|
process.exit(1);
|
|
4848
4840
|
}
|
|
@@ -4870,7 +4862,7 @@ async function cmdSpecialist(args) {
|
|
|
4870
4862
|
const { createSpecialistDraft } = await import('../lib/specialists/scaffold.mjs');
|
|
4871
4863
|
try {
|
|
4872
4864
|
const { relPath } = createSpecialistDraft({ rootDir: ROOT_DIR, role });
|
|
4873
|
-
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\`.`);
|
|
4874
4866
|
} catch (e) { errorln(e.message); process.exit(1); }
|
|
4875
4867
|
return;
|
|
4876
4868
|
}
|
|
@@ -5176,7 +5168,7 @@ async function cmdCreds(args) {
|
|
|
5176
5168
|
for (const p of providers) {
|
|
5177
5169
|
println(` ${p.configured ? '✓' : '·'} ${p.id}`);
|
|
5178
5170
|
}
|
|
5179
|
-
println(
|
|
5171
|
+
println(`\n Set keys as values or op:// refs in ${getUserEnvPath(HOME)}; run \`construct creds login copilot\` for GitHub Copilot.`);
|
|
5180
5172
|
} catch { /* readiness view is informational */ }
|
|
5181
5173
|
return;
|
|
5182
5174
|
}
|
|
@@ -5184,6 +5176,13 @@ async function cmdCreds(args) {
|
|
|
5184
5176
|
if (sub === 'test') {
|
|
5185
5177
|
const provider = args[1];
|
|
5186
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
|
+
}
|
|
5187
5186
|
const { resolveProviders } = await import('../lib/providers/registry.mjs');
|
|
5188
5187
|
const { providers } = await resolveProviders({ rootDir: ROOT_DIR, env: process.env });
|
|
5189
5188
|
if (!providers[provider]) { errorln(`Unknown provider: ${provider}`); process.exit(1); }
|
|
@@ -5561,7 +5560,6 @@ async function readStdin() {
|
|
|
5561
5560
|
const handlers = new Map([
|
|
5562
5561
|
// Core
|
|
5563
5562
|
['dev', cmdDev],
|
|
5564
|
-
['dashboard', cmdDashboard],
|
|
5565
5563
|
['stop', cmdStop],
|
|
5566
5564
|
['status', cmdStatus],
|
|
5567
5565
|
['install', cmdInstall],
|
|
@@ -5593,6 +5591,12 @@ const handlers = new Map([
|
|
|
5593
5591
|
['registry:status', cmdRegistryStatus],
|
|
5594
5592
|
['registry:validate', cmdRegistryValidate],
|
|
5595
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],
|
|
5596
5600
|
['rules', cmdRulesUsage],
|
|
5597
5601
|
// Pricing / cost readouts are stubbed out: the ledger writes, model-pricing
|
|
5598
5602
|
// catalog, and per-turn accounting still run, but no CLI surface exposes
|
|
@@ -5634,10 +5638,6 @@ const handlers = new Map([
|
|
|
5634
5638
|
const { runAcpServer } = await import('../lib/acp/server.mjs');
|
|
5635
5639
|
runAcpServer({ input: process.stdin, output: process.stdout, env: process.env, defaultCwd: process.cwd() });
|
|
5636
5640
|
}],
|
|
5637
|
-
['chat', async (args) => {
|
|
5638
|
-
const { runChat } = await import('../lib/chat/cli.mjs');
|
|
5639
|
-
process.exitCode = await runChat(args, { env: process.env, cwd: process.cwd() });
|
|
5640
|
-
}],
|
|
5641
5641
|
['beads:stats', async (args) => {
|
|
5642
5642
|
const { getContentionStats, getHumanStatus } = await import('../lib/beads-optimistic.mjs');
|
|
5643
5643
|
const jsonOutput = args.includes('--json');
|
|
@@ -5686,14 +5686,8 @@ const handlers = new Map([
|
|
|
5686
5686
|
return;
|
|
5687
5687
|
}
|
|
5688
5688
|
if (sub === 'show') {
|
|
5689
|
-
const {
|
|
5690
|
-
const
|
|
5691
|
-
const inventoryPath = join(ROOT_DIR, 'specialists', 'policy-inventory.json');
|
|
5692
|
-
if (!existsSync(inventoryPath)) {
|
|
5693
|
-
errorln('specialists/policy-inventory.json not found');
|
|
5694
|
-
process.exit(1);
|
|
5695
|
-
}
|
|
5696
|
-
const { policies } = JSON.parse(readFileSync(inventoryPath, 'utf8'));
|
|
5689
|
+
const registry = loadRegistry({ rootDir: ROOT_DIR });
|
|
5690
|
+
const policies = Object.values(registry.policies ?? {});
|
|
5697
5691
|
if (args.includes('--json')) {
|
|
5698
5692
|
println(JSON.stringify(policies, null, 2));
|
|
5699
5693
|
return;
|
|
@@ -5762,7 +5756,6 @@ const handlers = new Map([
|
|
|
5762
5756
|
['docs:update', cmdDocsUpdate],
|
|
5763
5757
|
['docs:check', cmdDocsCheck],
|
|
5764
5758
|
['docs:site', cmdDocsSite],
|
|
5765
|
-
['dashboard:sync', cmdDashboardSync],
|
|
5766
5759
|
['lint:comments', cmdLintComments],
|
|
5767
5760
|
['lint:templates', cmdLintTemplates],
|
|
5768
5761
|
['gates:audit', cmdGatesAudit],
|
|
@@ -5852,7 +5845,12 @@ const handlers = new Map([
|
|
|
5852
5845
|
const sub = args[0];
|
|
5853
5846
|
if (sub === 'review') return runTeamReviewCli(args.slice(1));
|
|
5854
5847
|
if (sub === 'templates') { listTeamTemplates(); return; }
|
|
5855
|
-
|
|
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`);
|
|
5856
5854
|
process.exit(1);
|
|
5857
5855
|
}],
|
|
5858
5856
|
['role', async (args) => {
|
|
@@ -6238,6 +6236,22 @@ const handlers = new Map([
|
|
|
6238
6236
|
}],
|
|
6239
6237
|
['skills', async (args) => {
|
|
6240
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
|
+
}
|
|
6241
6255
|
if (sub === 'scope' || !sub) {
|
|
6242
6256
|
const { runSkillsScopeCli } = await import('../lib/skills-scope.mjs');
|
|
6243
6257
|
return runSkillsScopeCli(args.slice(sub === 'scope' ? 1 : 0));
|
|
@@ -6249,7 +6263,7 @@ const handlers = new Map([
|
|
|
6249
6263
|
if (sub === 'usage' || sub === 'orphans' || sub === 'hot' || sub === 'correlate-quality' || sub === 'backfill-postgres') {
|
|
6250
6264
|
return cmdSkillsUsage(sub, args.slice(1));
|
|
6251
6265
|
}
|
|
6252
|
-
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`);
|
|
6253
6267
|
process.exit(1);
|
|
6254
6268
|
}],
|
|
6255
6269
|
['ask', async (args) => {
|
|
@@ -6418,18 +6432,18 @@ const handlers = new Map([
|
|
|
6418
6432
|
errorln(`Unknown sandbox subcommand: ${sub}. Available: create, list, delete, prune`);
|
|
6419
6433
|
process.exit(1);
|
|
6420
6434
|
}],
|
|
6421
|
-
['
|
|
6435
|
+
['scope', async (args) => {
|
|
6422
6436
|
const sub = args[0] || 'show';
|
|
6423
|
-
const {
|
|
6437
|
+
const { listScopes, loadScope, resolveActiveScope } = await import('../lib/scopes/loader.mjs');
|
|
6424
6438
|
if (sub === 'list') {
|
|
6425
|
-
for (const id of
|
|
6426
|
-
const p =
|
|
6439
|
+
for (const id of listScopes()) {
|
|
6440
|
+
const p = loadScope(id);
|
|
6427
6441
|
process.stdout.write(` ${id.padEnd(14)} ${p?.displayName || ''}\n`);
|
|
6428
6442
|
}
|
|
6429
6443
|
return;
|
|
6430
6444
|
}
|
|
6431
6445
|
if (sub === 'show') {
|
|
6432
|
-
const active =
|
|
6446
|
+
const active = resolveActiveScope(process.cwd());
|
|
6433
6447
|
process.stdout.write(JSON.stringify({
|
|
6434
6448
|
id: active.id,
|
|
6435
6449
|
displayName: active.displayName,
|
|
@@ -6442,12 +6456,12 @@ const handlers = new Map([
|
|
|
6442
6456
|
if (sub === 'set') {
|
|
6443
6457
|
const id = args[1];
|
|
6444
6458
|
if (!id) {
|
|
6445
|
-
errorln('Usage: construct
|
|
6459
|
+
errorln('Usage: construct scope set <id> [--yes] [--dry-run]');
|
|
6446
6460
|
process.exit(1);
|
|
6447
6461
|
}
|
|
6448
|
-
const target =
|
|
6462
|
+
const target = loadScope(id);
|
|
6449
6463
|
if (!target) {
|
|
6450
|
-
errorln(`Unknown
|
|
6464
|
+
errorln(`Unknown scope: ${id}. Available: ${listScopes().join(', ')}`);
|
|
6451
6465
|
process.exit(1);
|
|
6452
6466
|
}
|
|
6453
6467
|
const skipConfirm = args.includes('--yes') || args.includes('-y') || args.includes('--non-interactive');
|
|
@@ -6465,12 +6479,12 @@ const handlers = new Map([
|
|
|
6465
6479
|
const cfg = loaded?.raw ? structuredClone(loaded.raw) : { version: 1 };
|
|
6466
6480
|
if (!cfg.version) cfg.version = 1;
|
|
6467
6481
|
|
|
6468
|
-
const currentId = cfg.
|
|
6482
|
+
const currentId = cfg.scope || 'rnd';
|
|
6469
6483
|
if (currentId === id) {
|
|
6470
|
-
println(`
|
|
6484
|
+
println(`Scope already set to ${id} (${target.displayName}). Nothing to do.`);
|
|
6471
6485
|
return;
|
|
6472
6486
|
}
|
|
6473
|
-
const current =
|
|
6487
|
+
const current = loadScope(currentId) || { id: currentId, displayName: currentId, roles: [], intake: { types: [], stages: [] }, departments: [] };
|
|
6474
6488
|
|
|
6475
6489
|
// Render a structural diff so the operator sees what loop they are switching into.
|
|
6476
6490
|
const setDiff = (label, before, after) => {
|
|
@@ -6486,7 +6500,7 @@ const handlers = new Map([
|
|
|
6486
6500
|
};
|
|
6487
6501
|
|
|
6488
6502
|
println('');
|
|
6489
|
-
println(`About to switch active
|
|
6503
|
+
println(`About to switch active scope:`);
|
|
6490
6504
|
println(` from: ${currentId} (${current.displayName})`);
|
|
6491
6505
|
println(` to: ${id} (${target.displayName})`);
|
|
6492
6506
|
println('');
|
|
@@ -6499,10 +6513,10 @@ const handlers = new Map([
|
|
|
6499
6513
|
setDiff('departments', cDept, tDept);
|
|
6500
6514
|
println('');
|
|
6501
6515
|
println(`Files that will change:`);
|
|
6502
|
-
println(` ${pathMod.relative(process.cwd(), cfgPath)} (
|
|
6516
|
+
println(` ${pathMod.relative(process.cwd(), cfgPath)} (scope field: ${currentId} -> ${id})`);
|
|
6503
6517
|
println('');
|
|
6504
|
-
println(`The change only writes the
|
|
6505
|
-
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\`.`);
|
|
6506
6520
|
println('');
|
|
6507
6521
|
|
|
6508
6522
|
if (dryRun) {
|
|
@@ -6512,7 +6526,7 @@ const handlers = new Map([
|
|
|
6512
6526
|
if (!skipConfirm && isTTY) {
|
|
6513
6527
|
const readline = await import('node:readline');
|
|
6514
6528
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
6515
|
-
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())));
|
|
6516
6530
|
rl.close();
|
|
6517
6531
|
if (answer && answer !== 'y' && answer !== 'yes') {
|
|
6518
6532
|
println('Aborted. No config was changed.');
|
|
@@ -6520,15 +6534,15 @@ const handlers = new Map([
|
|
|
6520
6534
|
}
|
|
6521
6535
|
}
|
|
6522
6536
|
|
|
6523
|
-
cfg.
|
|
6537
|
+
cfg.scope = id;
|
|
6524
6538
|
writeProjectConfig(cfgPath, cfg, { validate: true });
|
|
6525
|
-
process.stdout.write(`✓
|
|
6539
|
+
process.stdout.write(`✓ scope set to ${id} (${target.displayName})\n`);
|
|
6526
6540
|
return;
|
|
6527
6541
|
}
|
|
6528
6542
|
if (sub === 'create') {
|
|
6529
6543
|
const id = args[1];
|
|
6530
6544
|
if (!id) {
|
|
6531
|
-
errorln('Usage: construct
|
|
6545
|
+
errorln('Usage: construct scope create <id> [--display="..."] [--role=<id>...] [--department=<id>:<displayName>...] [--yes]');
|
|
6532
6546
|
errorln(' Run with no flags for interactive mode; pass --yes to skip the confirmation prompt.');
|
|
6533
6547
|
process.exit(1);
|
|
6534
6548
|
}
|
|
@@ -6566,8 +6580,8 @@ const handlers = new Map([
|
|
|
6566
6580
|
const ask = (q, def = '') => new Promise((res) => rl.question(`${q}${def ? ` [${def}]` : ''} `, (a) => res(a.trim() || def)));
|
|
6567
6581
|
|
|
6568
6582
|
println('');
|
|
6569
|
-
println(`Scaffolding a new
|
|
6570
|
-
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`);
|
|
6571
6585
|
println('');
|
|
6572
6586
|
if (!displayName) displayName = await ask('Display name?', id);
|
|
6573
6587
|
if (seedDepartments.length === 0) {
|
|
@@ -6586,23 +6600,23 @@ const handlers = new Map([
|
|
|
6586
6600
|
if (!displayName) displayName = id;
|
|
6587
6601
|
|
|
6588
6602
|
const pathMod = await import('node:path');
|
|
6589
|
-
const draftRoot = pathMod.join(process.cwd(), '.cx', '
|
|
6603
|
+
const draftRoot = pathMod.join(process.cwd(), '.cx', 'scopes', `draft-${id}`);
|
|
6590
6604
|
|
|
6591
6605
|
println('');
|
|
6592
|
-
println(`About to scaffold a draft
|
|
6606
|
+
println(`About to scaffold a draft scope:`);
|
|
6593
6607
|
println(` id: ${id}`);
|
|
6594
6608
|
println(` displayName: ${displayName}`);
|
|
6595
6609
|
println(` departments: ${seedDepartments.length}${seedDepartments.length ? ` (${seedDepartments.map((d) => d.id).join(', ')})` : ''}`);
|
|
6596
6610
|
println(` roles: ${seedRoles.length}${seedRoles.length ? ` (${seedRoles.join(', ')})` : ''}`);
|
|
6597
6611
|
println('');
|
|
6598
6612
|
println(`Files that will be written under ${pathMod.relative(process.cwd(), draftRoot)}/:`);
|
|
6599
|
-
println(`
|
|
6613
|
+
println(` scope.json structural manifest`);
|
|
6600
6614
|
println(` requirements.md research brief naming each cx-* specialist`);
|
|
6601
6615
|
for (const r of seedRoles) println(` personas/${r}.md`.padEnd(40) + ` persona artifact (filled during discover)`);
|
|
6602
6616
|
for (const d of seedDepartments) println(` departments/${d.id}.md`.padEnd(40) + ` department charter (filled during frame)`);
|
|
6603
6617
|
println('');
|
|
6604
|
-
println(`Nothing else is touched. No config is changed, no active
|
|
6605
|
-
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}`);
|
|
6606
6620
|
println('');
|
|
6607
6621
|
|
|
6608
6622
|
if (dryRun) {
|
|
@@ -6620,16 +6634,16 @@ const handlers = new Map([
|
|
|
6620
6634
|
}
|
|
6621
6635
|
}
|
|
6622
6636
|
|
|
6623
|
-
const {
|
|
6637
|
+
const { createDraftScope } = await import('../lib/scopes/lifecycle.mjs');
|
|
6624
6638
|
try {
|
|
6625
|
-
const result =
|
|
6639
|
+
const result = createDraftScope({ cwd: process.cwd(), id, displayName, seedRoles, seedDepartments });
|
|
6626
6640
|
println('');
|
|
6627
6641
|
println(`✓ draft created at ${pathMod.relative(process.cwd(), result.dir)}`);
|
|
6628
6642
|
println('');
|
|
6629
|
-
println(`Next steps (in order, per docs/guides/concepts/
|
|
6643
|
+
println(`Next steps (in order, per docs/guides/concepts/scope-lifecycle.md):`);
|
|
6630
6644
|
println(` 1. Discover (cx-ux-researcher): fill personas/<role>.md from interviews + primary sources.`);
|
|
6631
6645
|
println(` 2. Frame (cx-product-manager): fill departments/<dept>.md charters and intake taxonomy.`);
|
|
6632
|
-
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.`);
|
|
6633
6647
|
println(` 4. Validate (cx-evaluator): run persona-eval and classifier-eval against representative signals.`);
|
|
6634
6648
|
println('');
|
|
6635
6649
|
println(`Inspect the brief: open ${pathMod.relative(process.cwd(), result.briefPath)}`);
|
|
@@ -6640,11 +6654,11 @@ const handlers = new Map([
|
|
|
6640
6654
|
}
|
|
6641
6655
|
}
|
|
6642
6656
|
if (sub === 'drafts') {
|
|
6643
|
-
const { listDrafts } = await import('../lib/
|
|
6657
|
+
const { listDrafts } = await import('../lib/scopes/lifecycle.mjs');
|
|
6644
6658
|
const drafts = listDrafts(process.cwd());
|
|
6645
6659
|
if (drafts.length === 0) { process.stdout.write(' (no drafts)\n'); return; }
|
|
6646
6660
|
for (const d of drafts) {
|
|
6647
|
-
const flags = [d.hasBrief ? 'brief' : null, d.
|
|
6661
|
+
const flags = [d.hasBrief ? 'brief' : null, d.hasScope ? 'scope' : null].filter(Boolean).join(', ');
|
|
6648
6662
|
process.stdout.write(` ${d.id.padEnd(20)} ${flags}\n`);
|
|
6649
6663
|
}
|
|
6650
6664
|
return;
|
|
@@ -6654,7 +6668,7 @@ const handlers = new Map([
|
|
|
6654
6668
|
const reasonArg = args.find((a) => a.startsWith('--reason='));
|
|
6655
6669
|
let reason = reasonArg ? reasonArg.split('=')[1] : '';
|
|
6656
6670
|
if (!id) {
|
|
6657
|
-
errorln('Usage: construct
|
|
6671
|
+
errorln('Usage: construct scope archive <id> --reason="..." [--yes] [--dry-run]');
|
|
6658
6672
|
process.exit(1);
|
|
6659
6673
|
}
|
|
6660
6674
|
const skipConfirm = args.includes('--yes') || args.includes('-y') || args.includes('--non-interactive');
|
|
@@ -6663,18 +6677,18 @@ const handlers = new Map([
|
|
|
6663
6677
|
|
|
6664
6678
|
const pathMod = await import('node:path');
|
|
6665
6679
|
const fsMod = await import('node:fs');
|
|
6666
|
-
const target =
|
|
6680
|
+
const target = loadScope(id);
|
|
6667
6681
|
if (!target) {
|
|
6668
|
-
errorln(`Unknown
|
|
6682
|
+
errorln(`Unknown scope: ${id}. Curated catalog: ${listScopes().join(', ')}`);
|
|
6669
6683
|
process.exit(1);
|
|
6670
6684
|
}
|
|
6671
6685
|
const fileURL = await import('node:url');
|
|
6672
6686
|
const moduleDir = pathMod.dirname(fileURL.fileURLToPath(import.meta.url));
|
|
6673
6687
|
const repoRoot = pathMod.resolve(moduleDir, '..');
|
|
6674
|
-
const
|
|
6688
|
+
const srcScope = pathMod.join(repoRoot, 'specialists', 'org', 'scopes', `${id}.json`);
|
|
6675
6689
|
const tableRel = typeof target?.intake?.classificationTable === 'string' ? target.intake.classificationTable : null;
|
|
6676
6690
|
const srcTable = tableRel ? pathMod.join(repoRoot, tableRel) : null;
|
|
6677
|
-
const dstDir = pathMod.join(repoRoot, 'archive', '
|
|
6691
|
+
const dstDir = pathMod.join(repoRoot, 'archive', 'scopes', id);
|
|
6678
6692
|
|
|
6679
6693
|
// Gather reason interactively if missing. The lifecycle module already
|
|
6680
6694
|
// enforces a >= 8 char reason, but the operator should see why before
|
|
@@ -6691,13 +6705,13 @@ const handlers = new Map([
|
|
|
6691
6705
|
}
|
|
6692
6706
|
|
|
6693
6707
|
println('');
|
|
6694
|
-
println(`About to archive curated
|
|
6708
|
+
println(`About to archive curated scope:`);
|
|
6695
6709
|
println(` id: ${id}`);
|
|
6696
6710
|
println(` displayName: ${target.displayName || id}`);
|
|
6697
6711
|
println(` reason: ${reason}`);
|
|
6698
6712
|
println('');
|
|
6699
6713
|
println(`Files that will move:`);
|
|
6700
|
-
println(` ${pathMod.relative(process.cwd(),
|
|
6714
|
+
println(` ${pathMod.relative(process.cwd(), srcScope)}`);
|
|
6701
6715
|
println(` -> ${pathMod.relative(process.cwd(), pathMod.join(dstDir, `${id}.json`))}`);
|
|
6702
6716
|
if (srcTable && fsMod.existsSync(srcTable)) {
|
|
6703
6717
|
println(` ${pathMod.relative(process.cwd(), srcTable)}`);
|
|
@@ -6708,7 +6722,7 @@ const handlers = new Map([
|
|
|
6708
6722
|
println(`What stays:`);
|
|
6709
6723
|
println(` Observations under .cx/observations/ are not touched.`);
|
|
6710
6724
|
println(` Outcomes under .cx/outcomes/ are not touched.`);
|
|
6711
|
-
println(` Any project still configured with
|
|
6725
|
+
println(` Any project still configured with scope=${id} will fall back to rnd on next load.`);
|
|
6712
6726
|
println('');
|
|
6713
6727
|
|
|
6714
6728
|
if (dryRun) {
|
|
@@ -6726,9 +6740,9 @@ const handlers = new Map([
|
|
|
6726
6740
|
}
|
|
6727
6741
|
}
|
|
6728
6742
|
|
|
6729
|
-
const {
|
|
6743
|
+
const { archiveScope } = await import('../lib/scopes/lifecycle.mjs');
|
|
6730
6744
|
try {
|
|
6731
|
-
const { archived } =
|
|
6745
|
+
const { archived } = archiveScope({ id, reason });
|
|
6732
6746
|
process.stdout.write(`✓ archived ${id} to ${pathMod.relative(process.cwd(), archived)}\n`);
|
|
6733
6747
|
return;
|
|
6734
6748
|
} catch (err) {
|
|
@@ -6738,25 +6752,19 @@ const handlers = new Map([
|
|
|
6738
6752
|
}
|
|
6739
6753
|
if (sub === 'health') {
|
|
6740
6754
|
const id = args[1];
|
|
6741
|
-
if (!id) { errorln('Usage: construct
|
|
6755
|
+
if (!id) { errorln('Usage: construct scope health <id> [--days=N]'); process.exit(1); }
|
|
6742
6756
|
const daysArg = args.find((a) => a.startsWith('--days='));
|
|
6743
6757
|
const windowDays = daysArg ? Number(daysArg.split('=')[1]) || 30 : 30;
|
|
6744
|
-
const {
|
|
6745
|
-
const report =
|
|
6758
|
+
const { scopeHealth } = await import('../lib/scopes/lifecycle.mjs');
|
|
6759
|
+
const report = scopeHealth(process.cwd(), id, { windowDays });
|
|
6746
6760
|
process.stdout.write(JSON.stringify(report, null, 2) + '\n');
|
|
6747
6761
|
return;
|
|
6748
6762
|
}
|
|
6749
|
-
errorln(`Unknown
|
|
6763
|
+
errorln(`Unknown scope subcommand: ${sub}. Available: list, show, set, create, drafts, archive, health`);
|
|
6750
6764
|
process.exit(1);
|
|
6751
6765
|
}],
|
|
6752
6766
|
]);
|
|
6753
6767
|
|
|
6754
|
-
// No command provided → show interactive menu
|
|
6755
|
-
if (!command) {
|
|
6756
|
-
await showInteractiveMenu();
|
|
6757
|
-
process.exit(0);
|
|
6758
|
-
}
|
|
6759
|
-
|
|
6760
6768
|
// Help flags → show usage
|
|
6761
6769
|
if (command === '--help' || command === '-h' || command === 'help') {
|
|
6762
6770
|
usage();
|
|
@@ -6777,6 +6785,19 @@ if (command === '--version' || command === '-V') {
|
|
|
6777
6785
|
process.exit(0);
|
|
6778
6786
|
}
|
|
6779
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
|
+
|
|
6780
6801
|
// Unknown-flag guidance for commands that fully declare their flags (strictFlags
|
|
6781
6802
|
// in the spec). A stray or typo'd flag on these is a user error, not a no-op:
|
|
6782
6803
|
// reject it with the nearest declared flag and the command's help, matching how
|
|
@@ -6840,7 +6861,12 @@ function validateKnownFlags(name, argv) {
|
|
|
6840
6861
|
|
|
6841
6862
|
const handler = handlers.get(command);
|
|
6842
6863
|
if (!handler) {
|
|
6843
|
-
|
|
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 }));
|
|
6844
6870
|
// Three-tier suggestion: prefix match → substring match → Levenshtein
|
|
6845
6871
|
// distance ≤ 2. Catches single-letter typos, missing-letter typos, and
|
|
6846
6872
|
// transpositions ('sycn' → 'sync') without dragging in a full fuzzy lib.
|
|
@@ -6871,9 +6897,9 @@ if (!handler) {
|
|
|
6871
6897
|
if (d < bestDist) { bestDist = d; suggestion = name; }
|
|
6872
6898
|
}
|
|
6873
6899
|
}
|
|
6874
|
-
if (suggestion) errorln(
|
|
6900
|
+
if (suggestion) errorln(`${COLORS.muted}Did you mean${COLORS.reset} ${COLORS.highlight}construct ${suggestion}${COLORS.reset}?`);
|
|
6875
6901
|
println('');
|
|
6876
|
-
println(`${COLORS.dim}Run
|
|
6902
|
+
println(`${COLORS.dim}Run ${COLORS.reset}construct --help${COLORS.dim} for available commands${COLORS.reset}`);
|
|
6877
6903
|
process.exit(1);
|
|
6878
6904
|
}
|
|
6879
6905
|
|