@geraldmaron/construct 1.2.3 → 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 +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/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/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 +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/apps/chat/engine/ai-sdk-agent.mjs +0 -183
- package/apps/chat/engine/loop-driver.mjs +0 -211
- package/apps/chat/engine/models.mjs +0 -122
- package/apps/chat/engine/provider-adapters.mjs +0 -171
- package/apps/chat/engine/tools/permission.mjs +0 -54
- package/apps/chat/engine/tools/primitives.mjs +0 -180
- package/apps/chat/engine/tools/registry.mjs +0 -122
- package/apps/chat/engine/turn-controls.mjs +0 -70
- package/lib/boundary.mjs +0 -127
- package/lib/certification/dashboard-api.mjs +0 -71
- package/lib/chat/cli.mjs +0 -333
- package/lib/chat/command-suggest.mjs +0 -161
- package/lib/chat/commands.mjs +0 -215
- package/lib/chat/config.mjs +0 -142
- package/lib/chat/context-compactor.mjs +0 -250
- package/lib/chat/context-continuation.mjs +0 -253
- package/lib/chat/continuation-source.mjs +0 -58
- package/lib/chat/demo-guide.mjs +0 -61
- package/lib/chat/design-tokens.mjs +0 -91
- package/lib/chat/desktop-binary.mjs +0 -81
- package/lib/chat/desktop-build.mjs +0 -130
- package/lib/chat/desktop-launcher.mjs +0 -133
- package/lib/chat/evidence.mjs +0 -145
- package/lib/chat/export.mjs +0 -74
- package/lib/chat/harness/driver.mjs +0 -91
- package/lib/chat/list-picker.mjs +0 -112
- package/lib/chat/model-picker.mjs +0 -356
- package/lib/chat/openrouter-fallback.mjs +0 -151
- package/lib/chat/permission-prompt.mjs +0 -33
- package/lib/chat/picker-catalog.mjs +0 -45
- package/lib/chat/policy-telemetry.mjs +0 -34
- package/lib/chat/present.mjs +0 -246
- package/lib/chat/session-context.mjs +0 -39
- package/lib/chat/session-persist.mjs +0 -73
- package/lib/chat/session-restore.mjs +0 -71
- package/lib/chat/session-settings.mjs +0 -53
- package/lib/chat/system-prompt.mjs +0 -52
- package/lib/chat/transparency.mjs +0 -93
- package/lib/chat/tui/color-scheme.mjs +0 -42
- package/lib/chat/tui/markdown.mjs +0 -123
- package/lib/chat/tui/presentation.mjs +0 -100
- package/lib/chat/tui/render.mjs +0 -500
- package/lib/chat/tui/turn-block.mjs +0 -284
- package/lib/chat/tui/turn-present.mjs +0 -18
- package/lib/chat/tui/turn-state.mjs +0 -88
- package/lib/chat/tui/usage.mjs +0 -122
- package/lib/chat/web-commands.mjs +0 -146
- package/lib/chat/web-launcher.mjs +0 -63
- package/lib/chat/web-picker-keys.mjs +0 -46
- package/lib/chat/web-session.mjs +0 -159
- package/lib/config/alias.mjs +0 -57
- package/lib/dashboard-demo.mjs +0 -71
- package/lib/dashboard-static.mjs +0 -175
- package/lib/install/desktop-binary-download.mjs +0 -88
- package/lib/profiles/loader.mjs +0 -123
- package/lib/profiles/rebrand.mjs +0 -46
- package/lib/server/auth.mjs +0 -169
- package/lib/server/chat-loop.mjs +0 -622
- package/lib/server/chat.mjs +0 -336
- package/lib/server/cors.mjs +0 -77
- package/lib/server/csrf.mjs +0 -103
- package/lib/server/demo-preview.mjs +0 -63
- package/lib/server/index.mjs +0 -2641
- package/lib/server/insights.mjs +0 -780
- package/lib/server/langfuse-login.mjs +0 -58
- package/lib/server/rate-limit.mjs +0 -93
- package/lib/server/telemetry-login.mjs +0 -100
- package/lib/server/webhook.mjs +0 -512
- package/specialists/contracts.json +0 -1032
- package/specialists/contracts.schema.json +0 -83
- package/specialists/policy-inventory.json +0 -188
- package/specialists/registry.json +0 -1412
- package/specialists/role-manifests.json +0 -217
- package/specialists/teams.json +0 -94
package/lib/service-manager.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/service-manager.mjs — start, stop, and describe Construct runtime services.
|
|
3
3
|
*
|
|
4
|
-
* Manages the
|
|
4
|
+
* Manages the memory server (cm), OpenCode, and other runtime services.
|
|
5
5
|
* startServices() is the single entry point for `construct dev` —
|
|
6
6
|
* spawns whatever is available and returns a result per service.
|
|
7
7
|
*/
|
|
@@ -23,18 +23,12 @@ import { heartbeatPath } from './oracle/index.mjs';
|
|
|
23
23
|
import { stateDir } from './config/xdg.mjs';
|
|
24
24
|
import { doctorRoot } from './config/xdg.mjs';
|
|
25
25
|
|
|
26
|
-
const DASHBOARD_STATE_FILE = 'dashboard.json';
|
|
27
|
-
|
|
28
26
|
const INSTALL_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
29
27
|
|
|
30
28
|
function runtimeStateDir(homeDir = os.homedir()) {
|
|
31
29
|
return path.join(stateDir(homeDir), 'runtime');
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
function dashboardStatePath(homeDir = os.homedir()) {
|
|
35
|
-
return path.join(runtimeStateDir(homeDir), DASHBOARD_STATE_FILE);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
32
|
function readJson(filePath) {
|
|
39
33
|
if (!fs.existsSync(filePath)) return null;
|
|
40
34
|
try {
|
|
@@ -149,10 +143,6 @@ async function probeRuntimeHttp(url, {
|
|
|
149
143
|
}
|
|
150
144
|
}
|
|
151
145
|
|
|
152
|
-
async function isDashboardRunning(port) {
|
|
153
|
-
return probeRuntimeHttp(`http://127.0.0.1:${port}/`, { timeoutMs: 1000 });
|
|
154
|
-
}
|
|
155
|
-
|
|
156
146
|
async function isMemoryRunning(port) {
|
|
157
147
|
const mcpReady = await probeRuntimeHttp(`http://127.0.0.1:${port}/`, {
|
|
158
148
|
method: 'POST',
|
|
@@ -187,35 +177,6 @@ function spawnDetached(command, args, homeDir, logFile, options = {}) {
|
|
|
187
177
|
return { child, logPath };
|
|
188
178
|
}
|
|
189
179
|
|
|
190
|
-
export function readDashboardState(homeDir = os.homedir()) {
|
|
191
|
-
const state = readJson(dashboardStatePath(homeDir));
|
|
192
|
-
if (!state) return null;
|
|
193
|
-
if (!processExists(state.pid)) return null;
|
|
194
|
-
return state;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export function clearDashboardState(homeDir = os.homedir()) {
|
|
198
|
-
const filePath = dashboardStatePath(homeDir);
|
|
199
|
-
if (fs.existsSync(filePath)) fs.rmSync(filePath, { force: true });
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export function stopDashboard(homeDir = os.homedir()) {
|
|
203
|
-
const state = readJson(dashboardStatePath(homeDir));
|
|
204
|
-
if (!state?.pid) {
|
|
205
|
-
clearDashboardState(homeDir);
|
|
206
|
-
return { stopped: false, reason: 'not-running' };
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (!processExists(state.pid)) {
|
|
210
|
-
clearDashboardState(homeDir);
|
|
211
|
-
return { stopped: false, reason: 'stale-state' };
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
process.kill(Number(state.pid), 'SIGTERM');
|
|
215
|
-
clearDashboardState(homeDir);
|
|
216
|
-
return { stopped: true, pid: Number(state.pid), port: state.port };
|
|
217
|
-
}
|
|
218
|
-
|
|
219
180
|
function doctorStatePath(homeDir) {
|
|
220
181
|
return path.join(stateDir(homeDir), 'doctor.json');
|
|
221
182
|
}
|
|
@@ -273,67 +234,11 @@ export function startOracle({ rootDir, homeDir = os.homedir(), projectDir = root
|
|
|
273
234
|
return { started: true, logPath };
|
|
274
235
|
}
|
|
275
236
|
|
|
276
|
-
export async function startDashboard({ rootDir, homeDir = os.homedir(), preferredPort = 4242 } = {}) {
|
|
277
|
-
const existing = readDashboardState(homeDir);
|
|
278
|
-
if (existing && await isDashboardRunning(existing.port)) {
|
|
279
|
-
return { started: false, reused: true, pid: existing.pid, port: existing.port, url: existing.url };
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
if (existing) clearDashboardState(homeDir);
|
|
283
|
-
|
|
284
|
-
const port = await findAvailablePort(preferredPort);
|
|
285
|
-
const outPath = path.join(runtimeStateDir(homeDir), 'dashboard.log');
|
|
286
|
-
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
287
|
-
const out = fs.openSync(outPath, 'a');
|
|
288
|
-
const err = fs.openSync(outPath, 'a');
|
|
289
|
-
|
|
290
|
-
// Opt-in: with CONSTRUCT_OP_ENV_FILE set, launch the daemon under `op run` so
|
|
291
|
-
// 1Password op:// refs resolve once at startup into its env. The biometric
|
|
292
|
-
// unlock prompt makes first launch slower, so widen the readiness window then.
|
|
293
|
-
const { command, args, wrapped } = wrapWithOpRun(
|
|
294
|
-
process.execPath,
|
|
295
|
-
[path.join(INSTALL_ROOT, 'lib', 'server', 'index.mjs')],
|
|
296
|
-
{ env: process.env },
|
|
297
|
-
);
|
|
298
|
-
const child = spawn(command, args, {
|
|
299
|
-
detached: true,
|
|
300
|
-
stdio: ['ignore', out, err],
|
|
301
|
-
env: { ...process.env, PORT: String(port) },
|
|
302
|
-
});
|
|
303
|
-
child.unref();
|
|
304
|
-
|
|
305
|
-
let ready = false;
|
|
306
|
-
const maxTries = wrapped ? 150 : 30;
|
|
307
|
-
for (let i = 0; i < maxTries; i += 1) {
|
|
308
|
-
if (await probeRuntimePort(port)) { ready = true; break; }
|
|
309
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (!ready) {
|
|
313
|
-
let logTail = '';
|
|
314
|
-
try { logTail = fs.readFileSync(outPath, 'utf8').split('\n').slice(-12).join('\n'); } catch { /* log unreadable */ }
|
|
315
|
-
const window = wrapped ? '30s (op run awaits 1Password unlock)' : '6s';
|
|
316
|
-
return { started: false, failed: true, port, logPath: outPath, error: `dashboard did not bind 127.0.0.1:${port} within ${window}`, logTail };
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const state = {
|
|
320
|
-
pid: child.pid,
|
|
321
|
-
port,
|
|
322
|
-
url: `http://127.0.0.1:${port}`,
|
|
323
|
-
startedAt: new Date().toISOString(),
|
|
324
|
-
logPath: outPath,
|
|
325
|
-
};
|
|
326
|
-
writeJson(dashboardStatePath(homeDir), state);
|
|
327
|
-
return { started: true, reused: false, ...state };
|
|
328
|
-
}
|
|
329
|
-
|
|
330
237
|
export async function getRuntimePorts(homeDir = os.homedir(), {
|
|
331
|
-
dashboardProbeFn = isDashboardRunning,
|
|
332
238
|
memoryProbeFn = isMemoryRunning,
|
|
333
239
|
openCodeProbeFn = isBridgeRunning,
|
|
334
240
|
findAvailablePortFn = findAvailablePort,
|
|
335
241
|
} = {}) {
|
|
336
|
-
const dashboard = readDashboardState(homeDir);
|
|
337
242
|
const existing = parseEnvFile(getUserEnvPath(homeDir));
|
|
338
243
|
|
|
339
244
|
async function resolvePort(key, fallback, probe = async (port) => probeRuntimePort(port)) {
|
|
@@ -345,7 +250,6 @@ export async function getRuntimePorts(homeDir = os.homedir(), {
|
|
|
345
250
|
}
|
|
346
251
|
|
|
347
252
|
return {
|
|
348
|
-
dashboard: dashboard?.port ?? await resolvePort('DASHBOARD_PORT', 4242, dashboardProbeFn),
|
|
349
253
|
memory: await resolvePort('MEMORY_PORT', derivedMemoryPort(), memoryProbeFn),
|
|
350
254
|
bridge: await resolvePort('BRIDGE_PORT', 5173, openCodeProbeFn),
|
|
351
255
|
copilotBridge: await resolvePort('COPILOT_BRIDGE_PORT', 5174, probeRuntimePort),
|
|
@@ -370,7 +274,6 @@ export async function describeRuntimeSupport() {
|
|
|
370
274
|
}
|
|
371
275
|
|
|
372
276
|
export const SELECTABLE_SERVICES = Object.freeze([
|
|
373
|
-
{ key: 'dashboard', label: 'Dashboard', description: 'Local operations dashboard on http://127.0.0.1:4242.' },
|
|
374
277
|
{ key: 'telemetry', label: 'Telemetry', description: 'Trace export / local JSONL traces.' },
|
|
375
278
|
{ key: 'memory', label: 'Memory (cm)', description: 'Persistent memory service (cm).' },
|
|
376
279
|
{ key: 'opencode', label: 'OpenCode', description: 'OpenCode bridge server.' },
|
|
@@ -393,7 +296,6 @@ export async function startServices({
|
|
|
393
296
|
selected = null,
|
|
394
297
|
describeRuntimeSupportFn = describeRuntimeSupport,
|
|
395
298
|
getRuntimePortsFn = getRuntimePorts,
|
|
396
|
-
startDashboardFn = startDashboard,
|
|
397
299
|
loadConstructEnvFn = loadConstructEnv,
|
|
398
300
|
spawnDetachedFn = spawnDetached,
|
|
399
301
|
memoryProbeFn = isMemoryRunning,
|
|
@@ -407,7 +309,6 @@ export async function startServices({
|
|
|
407
309
|
const wants = (key) => selected === null || selected.has(key);
|
|
408
310
|
|
|
409
311
|
writeEnvValues(envPath, {
|
|
410
|
-
DASHBOARD_PORT: String(ports.dashboard),
|
|
411
312
|
MEMORY_PORT: String(ports.memory),
|
|
412
313
|
BRIDGE_PORT: String(ports.bridge),
|
|
413
314
|
COPILOT_BRIDGE_PORT: String(ports.copilotBridge),
|
|
@@ -429,14 +330,6 @@ export async function startServices({
|
|
|
429
330
|
});
|
|
430
331
|
}
|
|
431
332
|
|
|
432
|
-
// Dashboard
|
|
433
|
-
if (wants('dashboard')) {
|
|
434
|
-
const dashboard = await startDashboardFn({ rootDir, homeDir, preferredPort: ports.dashboard });
|
|
435
|
-
results.push(dashboard.failed
|
|
436
|
-
? { name: 'Dashboard', status: 'failed', note: `${dashboard.error} — see ${dashboard.logPath}` }
|
|
437
|
-
: { name: 'Dashboard', url: dashboard.url, status: dashboard.reused ? 'reused' : 'started' });
|
|
438
|
-
}
|
|
439
|
-
|
|
440
333
|
const telemetryUrl = liveEnv.CONSTRUCT_TELEMETRY_URL ?? '';
|
|
441
334
|
const traceBackend = resolveTraceBackend(liveEnv);
|
|
442
335
|
if (wants('telemetry')) {
|
|
@@ -590,15 +483,6 @@ export async function stopServices({
|
|
|
590
483
|
results.push({ name: 'Doctor', status: 'cleaned', note: 'stale state file removed' });
|
|
591
484
|
}
|
|
592
485
|
|
|
593
|
-
const dashboard = stopDashboard(homeDir);
|
|
594
|
-
if (dashboard.stopped) {
|
|
595
|
-
results.push({ name: 'Dashboard', status: 'stopped', note: `pid ${dashboard.pid}, port ${dashboard.port}` });
|
|
596
|
-
} else if (dashboard.reason === 'stale-state') {
|
|
597
|
-
results.push({ name: 'Dashboard', status: 'cleaned', note: 'stale state file removed (process already gone)' });
|
|
598
|
-
} else {
|
|
599
|
-
results.push({ name: 'Dashboard', status: 'not-running' });
|
|
600
|
-
}
|
|
601
|
-
|
|
602
486
|
const envPath = getUserEnvPath(homeDir);
|
|
603
487
|
const envValues = parseEnvFile(envPath);
|
|
604
488
|
const memoryPort = Number(envValues.MEMORY_PORT) || derivedMemoryPort();
|
package/lib/setup-prompts.mjs
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* 4. non-TTY → false (CI / scripts — caller can use --yes to opt in)
|
|
14
14
|
* 5. interactive → prompt [Y/n], cache the answer
|
|
15
15
|
*
|
|
16
|
-
* Consent is stored as BOOTSTRAP_<NAME>=yes|no in ~/.construct/config.env
|
|
16
|
+
* Consent is stored as BOOTSTRAP_<NAME>=yes|no in ~/.config/construct/config.env
|
|
17
17
|
* so subsequent runs of `construct init` don't re-prompt for the same
|
|
18
18
|
* services. Pass `force = true` to skip the cache and re-prompt.
|
|
19
19
|
*/
|
package/lib/setup.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import path from 'node:path';
|
|
|
22
22
|
import { spawn, spawnSync } from 'node:child_process';
|
|
23
23
|
|
|
24
24
|
import { ensureUserConfigDir, getUserEnvPath, writeEnvValues } from './env-config.mjs';
|
|
25
|
-
import { migrateLegacyModelConfig } from './config/legacy-config-migration.mjs';
|
|
25
|
+
import { migrateLegacyModelConfig, migrateLegacyCredentialConfig } from './config/legacy-config-migration.mjs';
|
|
26
26
|
import { configDir, stateDir, doctorRoot } from './config/xdg.mjs';
|
|
27
27
|
import { DEFAULT_WORKSPACE_PATH, WORKSPACE_DOCS_LANES } from './embed/config.mjs';
|
|
28
28
|
import {
|
|
@@ -35,8 +35,6 @@ import { getEmbeddingModelInfo, warmupEmbeddingModel } from './storage/embedding
|
|
|
35
35
|
import { buildPressureGuardValues, installPressureGuardLaunchAgent, loadPressureGuardLaunchAgent } from './runtime-pressure.mjs';
|
|
36
36
|
import { consentToInstall } from './setup-prompts.mjs';
|
|
37
37
|
import { cleanupLegacyGlobalConstruct } from './install/legacy-global-cleanup.mjs';
|
|
38
|
-
import { downloadDesktopBinary } from './install/desktop-binary-download.mjs';
|
|
39
|
-
import { buildDesktopBinary, hasDesktopSource } from './chat/desktop-build.mjs';
|
|
40
38
|
|
|
41
39
|
const ROOT_DIR = path.resolve(import.meta.dirname, '..');
|
|
42
40
|
const HOME = os.homedir();
|
|
@@ -355,7 +353,6 @@ function printInstallPlan({ scope, homeDir, withDocling, noLaunchAgent, cleanupL
|
|
|
355
353
|
console.log(` · ${path.dirname(defaultVectorIndexPath(homeDir))} (LanceDB vector store dir)`);
|
|
356
354
|
console.log('\nWould check / install runtime tooling:');
|
|
357
355
|
console.log(' · cm (memory CLI) and cass (session search) when available on PATH');
|
|
358
|
-
console.log(` · construct-chat desktop binary (from GitHub releases → ${path.join(stateDir(homeDir), 'bin')})`);
|
|
359
356
|
console.log('\nWould reach the network / external services:');
|
|
360
357
|
console.log(' · embedding model warmup (loads from local cache; offline-safe, no download)');
|
|
361
358
|
if (withDocling) console.log(' · docling Python venv provisioning via uv (~10 min)');
|
|
@@ -417,6 +414,11 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
|
|
|
417
414
|
console.log(`Migrated ${Object.keys(modelMigration.migrated).join(', ')} from ${modelMigration.legacyPath} → ${modelMigration.xdgPath}`);
|
|
418
415
|
}
|
|
419
416
|
|
|
417
|
+
const credentialMigration = migrateLegacyCredentialConfig({ homeDir });
|
|
418
|
+
if (credentialMigration.performed) {
|
|
419
|
+
console.log(`Migrated credentials ${Object.keys(credentialMigration.migrated).join(', ')} from ${credentialMigration.legacyPath} → ${credentialMigration.xdgPath}`);
|
|
420
|
+
}
|
|
421
|
+
|
|
420
422
|
if (cleanupLegacyGlobal) {
|
|
421
423
|
runLegacyGlobalCleanup({ homeDir, dryRun: false });
|
|
422
424
|
if (scope === 'project') {
|
|
@@ -466,28 +468,6 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
|
|
|
466
468
|
if (cassInstall.installCommand) console.log(` Install with: ${cassInstall.installCommand}`);
|
|
467
469
|
}
|
|
468
470
|
|
|
469
|
-
try {
|
|
470
|
-
if (hasDesktopSource(ROOT_DIR)) {
|
|
471
|
-
const built = buildDesktopBinary({ repoRoot: ROOT_DIR, env: process.env });
|
|
472
|
-
if (built.ok) {
|
|
473
|
-
console.log(`Desktop binary: built from source → ${built.binaryPath}`);
|
|
474
|
-
} else {
|
|
475
|
-
console.log(`Desktop binary: build skipped (${built.reason}) — runs on first \`construct chat\``);
|
|
476
|
-
}
|
|
477
|
-
} else {
|
|
478
|
-
const desktop = await downloadDesktopBinary({ homeDir, env: process.env });
|
|
479
|
-
if (desktop.status === 'downloaded') {
|
|
480
|
-
console.log(`Desktop binary: ${desktop.version} → ${desktop.dest}`);
|
|
481
|
-
} else if (desktop.status === 'failed') {
|
|
482
|
-
console.log(`Desktop binary: download failed (${desktop.reason}) — run \`construct install --scope=user\` again to retry`);
|
|
483
|
-
} else {
|
|
484
|
-
console.log(`Desktop binary: ${desktop.reason}`);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
} catch (err) {
|
|
488
|
-
console.log(`Desktop binary: skipped (${err?.message || 'unknown error'})`);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
471
|
const telemetryResult = process.env.CONSTRUCT_TELEMETRY_URL
|
|
492
472
|
? { status: 'configured', note: `remote export configured (${process.env.CONSTRUCT_TELEMETRY_URL})` }
|
|
493
473
|
: { status: 'local', note: 'local JSONL traces in .cx/traces; remote export optional' };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/skills/composition-graph.mjs — Skill routing and workflow composition audit.
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
import { loadRegistry } from '../registry/loader.mjs';
|
|
8
|
+
import { listScopes } from '../scopes/loader.mjs';
|
|
9
|
+
|
|
10
|
+
const GET_SKILL_RE = /get_skill\("([^"]+)"\)/g;
|
|
11
|
+
|
|
12
|
+
function walkSkills(skillsDir, prefix = '') {
|
|
13
|
+
const out = new Set();
|
|
14
|
+
if (!fs.existsSync(skillsDir)) return out;
|
|
15
|
+
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
16
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
17
|
+
const full = path.join(skillsDir, entry.name);
|
|
18
|
+
if (entry.isDirectory()) {
|
|
19
|
+
for (const s of walkSkills(full, rel)) out.add(s);
|
|
20
|
+
} else if (entry.name.endsWith('.md') && entry.name !== 'routing.md') {
|
|
21
|
+
out.add(rel.replace(/\.md$/, ''));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function collectRegistrySkills(registry) {
|
|
28
|
+
const set = new Set();
|
|
29
|
+
for (const agent of Object.values(registry.specialists || {})) {
|
|
30
|
+
for (const s of agent.skills || []) set.add(s);
|
|
31
|
+
}
|
|
32
|
+
return set;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function collectRoutingTargets(root) {
|
|
36
|
+
const set = new Set();
|
|
37
|
+
const jsonPath = path.join(root, 'skills', 'routing.json');
|
|
38
|
+
if (fs.existsSync(jsonPath)) {
|
|
39
|
+
try {
|
|
40
|
+
const data = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
|
41
|
+
for (const route of data.routes || data || []) {
|
|
42
|
+
const target = route.path || route.skill || route.skillId || route.target;
|
|
43
|
+
if (target) set.add(target.replace(/^skills\//, ''));
|
|
44
|
+
}
|
|
45
|
+
} catch { /* skip */ }
|
|
46
|
+
}
|
|
47
|
+
return set;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function collectWorkflowRefs(root) {
|
|
51
|
+
const set = new Set();
|
|
52
|
+
const docsDir = path.join(root, 'skills', 'docs');
|
|
53
|
+
if (!fs.existsSync(docsDir)) return set;
|
|
54
|
+
for (const f of fs.readdirSync(docsDir)) {
|
|
55
|
+
if (!f.endsWith('-workflow.md')) continue;
|
|
56
|
+
const body = fs.readFileSync(path.join(docsDir, f), 'utf8');
|
|
57
|
+
for (const m of body.matchAll(GET_SKILL_RE)) set.add(m[1]);
|
|
58
|
+
}
|
|
59
|
+
return set;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function auditSkillComposition({ rootDir } = {}) {
|
|
63
|
+
const root = rootDir || path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..');
|
|
64
|
+
const allOnDisk = walkSkills(path.join(root, 'skills'));
|
|
65
|
+
const registry = loadRegistry({ rootDir: root });
|
|
66
|
+
const entitled = collectRegistrySkills(registry);
|
|
67
|
+
const routed = collectRoutingTargets(root);
|
|
68
|
+
const workflowRefs = collectWorkflowRefs(root);
|
|
69
|
+
|
|
70
|
+
const reachable = new Set([...entitled, ...routed, ...workflowRefs]);
|
|
71
|
+
for (const id of entitled) {
|
|
72
|
+
if (id.startsWith('roles/')) reachable.add(id);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const unreachable = [...allOnDisk].filter((id) => {
|
|
76
|
+
if (id.startsWith('roles/')) return false;
|
|
77
|
+
if (id.startsWith('brand/')) return false;
|
|
78
|
+
return !reachable.has(id);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const blocking = [];
|
|
82
|
+
for (const id of unreachable) {
|
|
83
|
+
if (id.startsWith('docs/') && id.endsWith('-workflow')) continue;
|
|
84
|
+
blocking.push(`unreachable skill: ${id} (not in registry, routing.json, or workflow chain)`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
pass: blocking.length === 0,
|
|
89
|
+
blocking,
|
|
90
|
+
stats: {
|
|
91
|
+
onDisk: allOnDisk.size,
|
|
92
|
+
entitled: entitled.size,
|
|
93
|
+
routed: routed.size,
|
|
94
|
+
workflowRefs: workflowRefs.size,
|
|
95
|
+
unreachable: unreachable.length,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/skills/router.mjs — keyword + entitlement skill suggestion for agents.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { loadRegistry } from '../registry/loader.mjs';
|
|
9
|
+
import { loadArtifactManifest } from '../artifact-manifest.mjs';
|
|
10
|
+
import { resolveActiveScope } from '../scopes/loader.mjs';
|
|
11
|
+
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const ROOT = path.resolve(__dirname, '..', '..');
|
|
14
|
+
|
|
15
|
+
let _routes = null;
|
|
16
|
+
|
|
17
|
+
function loadRoutes(rootDir = ROOT) {
|
|
18
|
+
if (_routes) return _routes;
|
|
19
|
+
const p = path.join(rootDir, 'skills', 'routing.json');
|
|
20
|
+
_routes = JSON.parse(fs.readFileSync(p, 'utf8')).routes || [];
|
|
21
|
+
return _routes;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function tokenize(text) {
|
|
25
|
+
return String(text || '').toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function scoreRoute(route, tokens) {
|
|
29
|
+
let score = route.priority || 1;
|
|
30
|
+
for (const kw of route.keywords || []) {
|
|
31
|
+
const parts = kw.toLowerCase().split(/\s+/);
|
|
32
|
+
if (parts.every((p) => tokens.some((t) => t.includes(p) || p.includes(t)))) score += 10;
|
|
33
|
+
else if (parts.some((p) => tokens.includes(p))) score += 4;
|
|
34
|
+
}
|
|
35
|
+
return score;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function suggestSkills({ intent = '', specialistId = null, limit = 5, rootDir = ROOT, cwd = null } = {}) {
|
|
39
|
+
const routes = loadRoutes(rootDir);
|
|
40
|
+
const tokens = tokenize(intent);
|
|
41
|
+
const scored = routes
|
|
42
|
+
.map((route) => ({ ...route, score: scoreRoute(route, tokens) }))
|
|
43
|
+
.filter((r) => r.score > (r.priority || 1))
|
|
44
|
+
.sort((a, b) => b.score - a.score);
|
|
45
|
+
|
|
46
|
+
let entitled = null;
|
|
47
|
+
if (specialistId) {
|
|
48
|
+
const registry = loadRegistry({ rootDir });
|
|
49
|
+
const id = specialistId.startsWith('cx-') ? specialistId : `cx-${specialistId}`;
|
|
50
|
+
entitled = new Set(registry.specialists?.[id]?.skills || []);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const profile = cwd ? resolveActiveScope(cwd) : null;
|
|
54
|
+
const baseline = new Set(profile?.defaultSkills || []);
|
|
55
|
+
|
|
56
|
+
const manifest = loadArtifactManifest({ rootDir });
|
|
57
|
+
for (const entry of Object.values(manifest.artifacts || {})) {
|
|
58
|
+
if (entry.workflowSkill && tokens.some((t) => t.includes('prd') || t.includes('adr'))) {
|
|
59
|
+
scored.push({ path: entry.workflowSkill, domain: 'workflow', score: 12, priority: 12 });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const merged = new Map();
|
|
64
|
+
for (const item of scored) {
|
|
65
|
+
const prev = merged.get(item.path);
|
|
66
|
+
if (!prev || item.score > prev.score) merged.set(item.path, item);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const results = [...merged.values()]
|
|
70
|
+
.sort((a, b) => b.score - a.score)
|
|
71
|
+
.slice(0, limit)
|
|
72
|
+
.map((r) => ({
|
|
73
|
+
path: r.path,
|
|
74
|
+
domain: r.domain,
|
|
75
|
+
score: r.score,
|
|
76
|
+
entitled: entitled ? entitled.has(r.path) || baseline.has(r.path) : true,
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
return { intent, suggestions: results };
|
|
80
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/specialist-contracts.mjs — loader + query layer for specialists/contracts.
|
|
2
|
+
* lib/specialist-contracts.mjs — loader + query layer for specialists/org contracts.
|
|
3
3
|
*
|
|
4
4
|
* Agent contracts are explicit service contracts between Construct roles —
|
|
5
5
|
* "when cx-product-manager hands off to cx-architect, the packet MUST contain
|
|
@@ -14,27 +14,15 @@
|
|
|
14
14
|
* to introspect their inputs and expected outputs at handoff time.
|
|
15
15
|
* - future: handoff validators that check packet shape before dispatch.
|
|
16
16
|
*/
|
|
17
|
-
import {
|
|
18
|
-
import { dirname, join, resolve } from 'node:path';
|
|
19
|
-
import { fileURLToPath } from 'node:url';
|
|
20
|
-
|
|
21
|
-
const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
22
|
-
const REPO_ROOT = resolve(MODULE_DIR, '..');
|
|
23
|
-
const CONTRACTS_PATH = join(REPO_ROOT, 'specialists', 'contracts.json');
|
|
17
|
+
import { loadRegistry } from './registry/loader.mjs';
|
|
24
18
|
|
|
25
19
|
let _cache = null;
|
|
26
20
|
|
|
27
21
|
function loadRaw() {
|
|
28
22
|
if (_cache) return _cache;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
try {
|
|
34
|
-
_cache = JSON.parse(readFileSync(CONTRACTS_PATH, 'utf8'));
|
|
35
|
-
} catch (err) {
|
|
36
|
-
_cache = { version: 0, contracts: [], parseError: err?.message || String(err) };
|
|
37
|
-
}
|
|
23
|
+
const registry = loadRegistry();
|
|
24
|
+
const contracts = Object.values(registry.contracts || {});
|
|
25
|
+
_cache = { version: 2, contracts };
|
|
38
26
|
return _cache;
|
|
39
27
|
}
|
|
40
28
|
|
|
@@ -46,6 +34,13 @@ export function getContractById(id) {
|
|
|
46
34
|
return getAllContracts().find((c) => c.id === id) || null;
|
|
47
35
|
}
|
|
48
36
|
|
|
37
|
+
export function getContractSkillHints(contractOrId) {
|
|
38
|
+
const contract = typeof contractOrId === 'string'
|
|
39
|
+
? getContractById(contractOrId)
|
|
40
|
+
: contractOrId;
|
|
41
|
+
return Array.isArray(contract?.skillHints) ? [...contract.skillHints] : [];
|
|
42
|
+
}
|
|
43
|
+
|
|
49
44
|
/**
|
|
50
45
|
* Returns the contract for a specific producer→consumer pair. If multiple
|
|
51
46
|
* contracts match (e.g. wildcard producer "*"), returns the most specific one.
|
|
@@ -152,7 +147,11 @@ export function resolveContractChain(ctx = {}) {
|
|
|
152
147
|
const stage = contract.trigger?.postConsumer ? 'followup'
|
|
153
148
|
: contract.producer === 'user' ? 'precheck'
|
|
154
149
|
: 'handoff';
|
|
155
|
-
chain.push({
|
|
150
|
+
chain.push({
|
|
151
|
+
contract,
|
|
152
|
+
stage,
|
|
153
|
+
skillHints: getContractSkillHints(contract),
|
|
154
|
+
});
|
|
156
155
|
}
|
|
157
156
|
return chain;
|
|
158
157
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/specialists/postconditions.mjs — hard binary postconditions per producer persona.
|
|
3
3
|
*
|
|
4
|
-
* `specialists/
|
|
4
|
+
* `specialists/org` carries free-text postconditions for documentation
|
|
5
5
|
* purposes. This module adds binary, programmatically-validated assertions
|
|
6
6
|
* that the persona's output packet must satisfy before a handoff is allowed
|
|
7
7
|
* to proceed. Failure throws `ContractViolationError` with reason
|
|
@@ -40,7 +40,7 @@ function isLaterOrEqual(a, b) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
* Rule table. Keyed by producer name (matching specialists/
|
|
43
|
+
* Rule table. Keyed by producer name (matching specialists/org).
|
|
44
44
|
* Each rule:
|
|
45
45
|
* id — stable identifier for violation logs
|
|
46
46
|
* description — single-line documentation
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import fs from 'node:fs';
|
|
18
18
|
import path from 'node:path';
|
|
19
19
|
import yaml from 'js-yaml';
|
|
20
|
+
import { loadRegistry as loadRuntimeRegistry } from '../registry/loader.mjs';
|
|
20
21
|
|
|
21
22
|
export const REQUIRED_FRONTMATTER = ['name', 'role', 'version', 'perspective'];
|
|
22
23
|
export const PERSPECTIVE_FIELDS = ['bias', 'tension', 'openingQuestion', 'failureMode'];
|
|
@@ -124,8 +125,8 @@ export function validatePromptFiles({ rootDir = process.cwd(), registry } = {})
|
|
|
124
125
|
let total = 0;
|
|
125
126
|
let converted = 0;
|
|
126
127
|
|
|
127
|
-
const reg = registry ??
|
|
128
|
-
const specialists = Array.isArray(reg?.specialists) ? reg.specialists :
|
|
128
|
+
const reg = registry ?? loadPromptRegistry(rootDir);
|
|
129
|
+
const specialists = Array.isArray(reg?.specialists) ? reg.specialists : Object.values(reg?.specialists || {});
|
|
129
130
|
const byName = new Map(specialists.map((s) => [s.name, s]));
|
|
130
131
|
|
|
131
132
|
const seen = new Set();
|
|
@@ -145,9 +146,9 @@ export function validatePromptFiles({ rootDir = process.cwd(), registry } = {})
|
|
|
145
146
|
return { errors, warnings, total, converted };
|
|
146
147
|
}
|
|
147
148
|
|
|
148
|
-
function
|
|
149
|
+
function loadPromptRegistry(rootDir) {
|
|
149
150
|
try {
|
|
150
|
-
return
|
|
151
|
+
return loadRuntimeRegistry({ rootDir });
|
|
151
152
|
} catch {
|
|
152
153
|
return { specialists: [] };
|
|
153
154
|
}
|
|
@@ -159,7 +160,7 @@ function loadRegistry(rootDir) {
|
|
|
159
160
|
* @returns {object|null}
|
|
160
161
|
*/
|
|
161
162
|
export function resolvePerspectiveFromPrompt(agentName, { rootDir = process.cwd(), registry } = {}) {
|
|
162
|
-
const reg = registry ??
|
|
163
|
+
const reg = registry ?? loadPromptRegistry(rootDir);
|
|
163
164
|
const normalized = String(agentName ?? '').trim().replace(/^cx-/, '');
|
|
164
165
|
const entry = (reg.specialists ?? []).find((s) => s.name === normalized);
|
|
165
166
|
if (!entry?.promptFile) return null;
|
|
@@ -6,17 +6,10 @@
|
|
|
6
6
|
* than inlined into synced orchestrator prompts. Each catalog entry carries id and
|
|
7
7
|
* whenToUse for routing discovery without an always-on prompt block.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
10
|
-
import { dirname, join } from 'node:path';
|
|
11
|
-
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { loadRegistry } from '../registry/loader.mjs';
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
function loadRegistry(rootDir = null) {
|
|
16
|
-
const path = rootDir
|
|
17
|
-
? join(rootDir, 'specialists', 'registry.json')
|
|
18
|
-
: REGISTRY_PATH;
|
|
19
|
-
return JSON.parse(readFileSync(path, 'utf8'));
|
|
11
|
+
function loadRegistryForRoot(rootDir = null) {
|
|
12
|
+
return loadRegistry(rootDir ? { rootDir } : {});
|
|
20
13
|
}
|
|
21
14
|
|
|
22
15
|
/**
|
|
@@ -24,9 +17,11 @@ function loadRegistry(rootDir = null) {
|
|
|
24
17
|
* the cx-prefixed id and the routing hint (when_to_use when present, else description).
|
|
25
18
|
*/
|
|
26
19
|
export function buildSpecialistCatalog({ rootDir = null, includeInternal = true } = {}) {
|
|
27
|
-
const registry =
|
|
20
|
+
const registry = loadRegistryForRoot(rootDir);
|
|
28
21
|
const prefix = registry.prefix || 'cx';
|
|
29
|
-
const specialists = Array.isArray(registry.specialists)
|
|
22
|
+
const specialists = Array.isArray(registry.specialists)
|
|
23
|
+
? registry.specialists
|
|
24
|
+
: Object.values(registry.specialists || {});
|
|
30
25
|
return specialists
|
|
31
26
|
.filter((s) => includeInternal || !s.internal)
|
|
32
27
|
.map((s) => ({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/specialists/schema.mjs — validation contract for specialists/
|
|
2
|
+
* lib/specialists/schema.mjs — validation contract for specialists/org entries.
|
|
3
3
|
*
|
|
4
4
|
* Catches drift before it ships: missing description, unknown tool names,
|
|
5
5
|
* promptFile pointing at a path that does not resolve, unrecognized
|
|
@@ -23,7 +23,7 @@ export const ALLOWED_TOOLS = new Set([
|
|
|
23
23
|
'WebSearch', 'WebFetch', 'TodoWrite', 'NotebookEdit', 'Task',
|
|
24
24
|
// Construct MCP tools (from construct-mcp server)
|
|
25
25
|
'list_skills', 'get_skill', 'search_skills',
|
|
26
|
-
'list_teams', '
|
|
26
|
+
'list_teams', 'suggest_skills',
|
|
27
27
|
'list_templates', 'get_template',
|
|
28
28
|
'agent_contract', 'orchestration_policy',
|
|
29
29
|
'cx_trace', 'cx_score',
|
|
@@ -90,13 +90,16 @@ export function validateAgentRecord(record, { rootDir, registryKey } = {}) {
|
|
|
90
90
|
* Validate every agent in a registry. Returns { errors: string[], agentCount }.
|
|
91
91
|
*/
|
|
92
92
|
export function validateRegistry(registry, { rootDir } = {}) {
|
|
93
|
-
if (!registry
|
|
93
|
+
if (!registry) {
|
|
94
94
|
return { errors: ['registry.specialists is missing or not an array'], agentCount: 0 };
|
|
95
95
|
}
|
|
96
|
+
const specialists = Array.isArray(registry.specialists)
|
|
97
|
+
? registry.specialists
|
|
98
|
+
: Object.values(registry.specialists || {});
|
|
96
99
|
const errors = [];
|
|
97
100
|
const seenNames = new Set();
|
|
98
|
-
for (let i = 0; i <
|
|
99
|
-
const record =
|
|
101
|
+
for (let i = 0; i < specialists.length; i += 1) {
|
|
102
|
+
const record = specialists[i];
|
|
100
103
|
const recordErrors = validateAgentRecord(record, { rootDir, registryKey: i });
|
|
101
104
|
errors.push(...recordErrors);
|
|
102
105
|
if (record?.name) {
|
|
@@ -104,7 +107,7 @@ export function validateRegistry(registry, { rootDir } = {}) {
|
|
|
104
107
|
seenNames.add(record.name);
|
|
105
108
|
}
|
|
106
109
|
}
|
|
107
|
-
return { errors, agentCount:
|
|
110
|
+
return { errors, agentCount: specialists.length };
|
|
108
111
|
}
|
|
109
112
|
|
|
110
113
|
/**
|