@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
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
* 2. MCP registration ↔ impl: every `name === '<tool>'` branch in the MCP
|
|
8
8
|
* dispatch resolves to an imported function, and every exported tool
|
|
9
9
|
* function in lib/mcp/tools/*.mjs is wired into the dispatch.
|
|
10
|
-
* 3. Roles ↔ registry: every persona key in specialists/
|
|
11
|
-
* resolves to a persona or agent name in specialists/
|
|
10
|
+
* 3. Roles ↔ registry: every persona key in specialists/org
|
|
11
|
+
* resolves to a persona or agent name in specialists/org.
|
|
12
12
|
* 4. Persona promptFiles: every registry.personas[].promptFile and
|
|
13
13
|
* registry.agents[].promptFile exists on disk.
|
|
14
|
-
* 5. Contracts ↔ schemas: specialists/
|
|
14
|
+
* 5. Contracts ↔ schemas: specialists/org passes validateContractsFile
|
|
15
15
|
* (cross-file refs, producer/consumer names, schema paths).
|
|
16
16
|
*
|
|
17
17
|
* Each violation produces an audit record. Blocking severity escalates to a
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
* directory listings, no network.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import { existsSync, readFileSync,
|
|
22
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
23
|
+
import { loadRegistry } from '../../registry/loader.mjs';
|
|
23
24
|
import { join, dirname, resolve } from 'node:path';
|
|
24
25
|
import os from 'node:os';
|
|
25
26
|
import { fileURLToPath } from 'node:url';
|
|
@@ -89,18 +90,39 @@ export async function runAllChecks({ repoRoot = REPO_ROOT } = {}) {
|
|
|
89
90
|
const hosts = checkHostSurfaces({ repoRoot });
|
|
90
91
|
collect(hosts, findings, passed, 'host-surfaces');
|
|
91
92
|
|
|
93
|
+
const { runCredentialParityChecks } = await import('./credential-parity.mjs');
|
|
94
|
+
const credentials = await runCredentialParityChecks({ rootDir: repoRoot });
|
|
95
|
+
collect(credentials, findings, passed, 'credential-parity');
|
|
96
|
+
|
|
92
97
|
return { findings, passed };
|
|
93
98
|
}
|
|
94
99
|
|
|
100
|
+
// Drift categories split into two operator tiers. `actionable` findings are
|
|
101
|
+
// project state a user can fix and surface by default. `internal` findings are
|
|
102
|
+
// package/maintainer diagnostics about Construct's own registry and MCP wiring —
|
|
103
|
+
// never user-actionable in a consumer project (this watcher resolves REPO_ROOT to
|
|
104
|
+
// the installed package), so they stay behind `doctor consistency --strict`.
|
|
105
|
+
|
|
106
|
+
const CATEGORY_TIERS = {
|
|
107
|
+
'mcp-drift': 'internal',
|
|
108
|
+
'roles-drift': 'internal',
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
function tierFor(category) {
|
|
112
|
+
return CATEGORY_TIERS[category] || 'actionable';
|
|
113
|
+
}
|
|
114
|
+
|
|
95
115
|
function collect(result, findings, passed, defaultCategory) {
|
|
96
116
|
if (result.violations.length === 0) {
|
|
97
|
-
passed.push({ category: defaultCategory, summary: result.summary });
|
|
117
|
+
passed.push({ category: defaultCategory, summary: result.summary, tier: tierFor(defaultCategory) });
|
|
98
118
|
return;
|
|
99
119
|
}
|
|
100
120
|
for (const v of result.violations) {
|
|
121
|
+
const category = v.category || defaultCategory;
|
|
101
122
|
findings.push({
|
|
102
|
-
category
|
|
123
|
+
category,
|
|
103
124
|
severity: v.severity || 'warning',
|
|
125
|
+
tier: v.tier || tierFor(category),
|
|
104
126
|
summary: v.summary,
|
|
105
127
|
target: v.target || null,
|
|
106
128
|
details: v.details || null,
|
|
@@ -179,38 +201,51 @@ function checkMcpDrift({ repoRoot }) {
|
|
|
179
201
|
}
|
|
180
202
|
|
|
181
203
|
const serverSource = readFileSync(serverPath, 'utf8');
|
|
182
|
-
const dispatchedTools = new Set();
|
|
183
|
-
for (const m of serverSource.matchAll(/name === ['"]([a-z][a-z0-9_]*)['"]/g)) {
|
|
184
|
-
dispatchedTools.add(m[1]);
|
|
185
|
-
}
|
|
186
204
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
205
|
+
// Candidate tool handlers are exactly the identifiers server.mjs imports from
|
|
206
|
+
// ./tools/*.mjs. Tool modules also export private helpers (exec, readJson) that
|
|
207
|
+
// are never MCP tools; scanning every `export function` swept those into the
|
|
208
|
+
// signal. Scoping to the server's own imports is the real contract surface.
|
|
209
|
+
|
|
210
|
+
const importedHandlers = new Set();
|
|
211
|
+
for (const m of serverSource.matchAll(/import\s*(?:type\s*)?\{([^}]*)\}\s*from\s*['"]\.\/tools\/[^'"]+['"]/g)) {
|
|
212
|
+
for (const ident of m[1].split(',')) {
|
|
213
|
+
const local = ident.trim().split(/\s+as\s+/).pop().trim();
|
|
214
|
+
if (local) importedHandlers.add(local);
|
|
194
215
|
}
|
|
195
216
|
}
|
|
196
217
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
218
|
+
// A handler is wired when the dispatcher invokes it, regardless of the
|
|
219
|
+
// registered tool name. Handlers follow xxxTool→'xxx' and construct_xxx naming
|
|
220
|
+
// conventions, so matching exported names against `name === '<tool>'` strings is
|
|
221
|
+
// unreliable; matching the invoked identifier is convention-agnostic.
|
|
222
|
+
|
|
223
|
+
const invoked = new Set();
|
|
224
|
+
for (const m of serverSource.matchAll(/\b([A-Za-z_$][\w$]*)\s*\(/g)) {
|
|
225
|
+
invoked.add(m[1]);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
for (const handler of importedHandlers) {
|
|
229
|
+
const snake = camelToSnake(handler);
|
|
230
|
+
if (KNOWN_NON_DISPATCH_TOOLS.has(handler) || KNOWN_NON_DISPATCH_TOOLS.has(snake)) continue;
|
|
231
|
+
if (!invoked.has(handler)) {
|
|
200
232
|
violations.push({
|
|
201
233
|
category: 'mcp-drift',
|
|
202
234
|
severity: 'warning',
|
|
203
|
-
|
|
204
|
-
|
|
235
|
+
tier: 'internal',
|
|
236
|
+
target: snake,
|
|
237
|
+
summary: `MCP tool handler imported but never dispatched: ${handler}`,
|
|
205
238
|
});
|
|
206
239
|
}
|
|
207
240
|
}
|
|
208
241
|
|
|
209
|
-
return { summary: `mcp: ${
|
|
242
|
+
return { summary: `mcp: ${importedHandlers.size} handlers, ${violations.length} drift`, violations };
|
|
210
243
|
}
|
|
211
244
|
|
|
245
|
+
// Handlers the server may import for re-export or test surface but intentionally
|
|
246
|
+
// never dispatches. Matched by camelCase identifier or snake_case form.
|
|
247
|
+
|
|
212
248
|
const KNOWN_NON_DISPATCH_TOOLS = new Set([
|
|
213
|
-
// Internal helpers exported for tests but never wired through MCP.
|
|
214
249
|
'workflow_status_bound',
|
|
215
250
|
'create_needs_main_input_packet',
|
|
216
251
|
]);
|
|
@@ -219,22 +254,82 @@ function camelToSnake(s) {
|
|
|
219
254
|
return s.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
|
|
220
255
|
}
|
|
221
256
|
|
|
257
|
+
function readRegistrySnapshot(repoRoot) {
|
|
258
|
+
const registryPath = join(repoRoot, 'specialists', 'org');
|
|
259
|
+
if (!existsSync(registryPath)) return null;
|
|
260
|
+
const stat = statSync(registryPath);
|
|
261
|
+
if (stat.isDirectory()) return loadRegistry({ rootDir: repoRoot });
|
|
262
|
+
return JSON.parse(readFileSync(registryPath, 'utf8'));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function personaKeysFromRegistry(registry) {
|
|
266
|
+
const keys = new Set();
|
|
267
|
+
|
|
268
|
+
for (const [specId, spec] of Object.entries(registry?.specialists || {})) {
|
|
269
|
+
if (specId) keys.add(specId.replace(/^cx-/, ''));
|
|
270
|
+
if (spec?.name) keys.add(String(spec.name).replace(/^cx-/, ''));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (Array.isArray(registry?.specialists)) {
|
|
274
|
+
for (const spec of registry.specialists) {
|
|
275
|
+
if (spec?.id) keys.add(String(spec.id).replace(/^cx-/, ''));
|
|
276
|
+
if (spec?.name) keys.add(String(spec.name).replace(/^cx-/, ''));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (registry?.orchestrator?.id) keys.add(String(registry.orchestrator.id).replace(/^cx-/, ''));
|
|
281
|
+
if (registry?.orchestrator?.name) keys.add(String(registry.orchestrator.name).replace(/^cx-/, ''));
|
|
282
|
+
|
|
283
|
+
return [...keys].sort();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function personaOwnersFromRegistry(registry) {
|
|
287
|
+
const owners = new Map();
|
|
288
|
+
|
|
289
|
+
function addOwner(rawId, owner) {
|
|
290
|
+
if (!rawId) return;
|
|
291
|
+
const personaId = String(rawId).replace(/^cx-/, '');
|
|
292
|
+
if (!owners.has(personaId)) owners.set(personaId, new Set());
|
|
293
|
+
owners.get(personaId).add(owner);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// One registry entity owns a persona once. A specialist's id and its own name
|
|
297
|
+
// normalize to the same persona by design (cx-architect / architect), so they
|
|
298
|
+
// must share a single owner token keyed on the entity — otherwise every
|
|
299
|
+
// specialist self-collides and reports a spurious "ambiguous" drift. Genuine
|
|
300
|
+
// ambiguity (two distinct specialists, or a specialist and the orchestrator,
|
|
301
|
+
// normalizing to the same persona) still yields owners.size > 1.
|
|
302
|
+
|
|
303
|
+
for (const [specId, spec] of Object.entries(registry?.specialists || {})) {
|
|
304
|
+
addOwner(specId, `specialist:${specId}`);
|
|
305
|
+
addOwner(spec?.name, `specialist:${specId}`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (Array.isArray(registry?.specialists)) {
|
|
309
|
+
for (const spec of registry.specialists) {
|
|
310
|
+
const owner = `specialist:${spec?.id || spec?.name || 'unknown'}`;
|
|
311
|
+
addOwner(spec?.id, owner);
|
|
312
|
+
addOwner(spec?.name, owner);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
addOwner(registry?.orchestrator?.id, 'orchestrator');
|
|
317
|
+
addOwner(registry?.orchestrator?.name, 'orchestrator');
|
|
318
|
+
return owners;
|
|
319
|
+
}
|
|
320
|
+
|
|
222
321
|
// ── Check 3: roles ↔ registry ──────────────────────────────────────────────
|
|
223
322
|
|
|
224
323
|
function checkRolesDrift({ repoRoot }) {
|
|
225
324
|
const violations = [];
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (!existsSync(manifestsPath) || !existsSync(registryPath)) {
|
|
230
|
-
return { summary: 'role manifests or registry missing', violations: [] };
|
|
325
|
+
if (!existsSync(join(repoRoot, 'specialists', 'org'))) {
|
|
326
|
+
return { summary: 'registry missing', violations: [] };
|
|
231
327
|
}
|
|
232
328
|
|
|
233
|
-
const
|
|
234
|
-
const registry = JSON.parse(readFileSync(registryPath, 'utf8'));
|
|
329
|
+
const registry = readRegistrySnapshot(repoRoot);
|
|
235
330
|
|
|
236
331
|
const known = new Set();
|
|
237
|
-
for (const s of registry.specialists ||
|
|
332
|
+
for (const s of Object.values(registry.specialists || {})) {
|
|
238
333
|
if (s?.name) { known.add(s.name); known.add(`cx-${s.name}`); }
|
|
239
334
|
}
|
|
240
335
|
if (registry.orchestrator?.name) {
|
|
@@ -242,35 +337,43 @@ function checkRolesDrift({ repoRoot }) {
|
|
|
242
337
|
known.add(`cx-${registry.orchestrator.name}`);
|
|
243
338
|
}
|
|
244
339
|
|
|
245
|
-
const personaKeys =
|
|
340
|
+
const personaKeys = personaKeysFromRegistry(registry);
|
|
341
|
+
const personaOwners = personaOwnersFromRegistry(registry);
|
|
246
342
|
for (const key of personaKeys) {
|
|
247
|
-
if (key === 'version' || key === 'description' || key === 'schemaVersion') continue;
|
|
248
343
|
if (!known.has(key) && !known.has(`cx-${key}`)) {
|
|
249
344
|
violations.push({
|
|
250
345
|
category: 'roles-drift',
|
|
251
346
|
severity: 'warning',
|
|
252
347
|
target: key,
|
|
253
|
-
summary: `role
|
|
348
|
+
summary: `role manifest '${key}' does not resolve to a registry persona/agent`,
|
|
254
349
|
});
|
|
255
350
|
}
|
|
256
351
|
}
|
|
257
352
|
|
|
258
|
-
|
|
353
|
+
for (const [personaId, owners] of personaOwners.entries()) {
|
|
354
|
+
if (owners.size <= 1) continue;
|
|
355
|
+
violations.push({
|
|
356
|
+
category: 'roles-drift',
|
|
357
|
+
severity: 'warning',
|
|
358
|
+
target: personaId,
|
|
359
|
+
summary: `role manifest '${personaId}' is ambiguous after normalization: ${[...owners].join(', ')}`,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return { summary: `roles: ${personaKeys.length} personas, ${violations.length} drift`, violations };
|
|
259
364
|
}
|
|
260
365
|
|
|
261
366
|
// ── Check 4: persona promptFile existence ──────────────────────────────────
|
|
262
367
|
|
|
263
368
|
function checkPersonaPrompts({ repoRoot }) {
|
|
264
369
|
const violations = [];
|
|
265
|
-
const
|
|
266
|
-
if (!
|
|
267
|
-
|
|
268
|
-
const registry = JSON.parse(readFileSync(registryPath, 'utf8'));
|
|
370
|
+
const registry = readRegistrySnapshot(repoRoot);
|
|
371
|
+
if (!registry) return { summary: 'registry missing', violations: [] };
|
|
269
372
|
let checked = 0;
|
|
270
373
|
|
|
271
374
|
for (const list of [
|
|
272
375
|
registry.orchestrator ? [registry.orchestrator] : [],
|
|
273
|
-
registry.specialists ||
|
|
376
|
+
Object.values(registry.specialists || {}),
|
|
274
377
|
]) {
|
|
275
378
|
for (const entry of list) {
|
|
276
379
|
const ref = entry?.promptFile;
|
|
@@ -297,7 +400,9 @@ async function checkContractsDrift({ repoRoot }) {
|
|
|
297
400
|
const violations = [];
|
|
298
401
|
try {
|
|
299
402
|
const { validateContractsFile } = await import('../../contracts/validate.mjs');
|
|
300
|
-
const
|
|
403
|
+
const legacyContractsPath = join(repoRoot, 'specialists', 'contracts.json');
|
|
404
|
+
const opts = existsSync(legacyContractsPath) ? { contractsPath: legacyContractsPath, repoRoot } : { repoRoot };
|
|
405
|
+
const result = validateContractsFile(opts);
|
|
301
406
|
if (!result.ok) {
|
|
302
407
|
for (const err of result.errors) {
|
|
303
408
|
violations.push({
|
|
@@ -327,7 +432,7 @@ function getVSCodeSettingsDir(homeDir) {
|
|
|
327
432
|
return join(appData, 'Code', 'User');
|
|
328
433
|
}
|
|
329
434
|
|
|
330
|
-
function checkHostSurfaces({ repoRoot }) {
|
|
435
|
+
function checkHostSurfaces({ repoRoot: _repoRoot }) {
|
|
331
436
|
const violations = [];
|
|
332
437
|
const homeDir = os.homedir();
|
|
333
438
|
const surfaces = [
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/doctor/watchers/credential-parity.mjs — advisory credential alignment checks.
|
|
3
|
+
*
|
|
4
|
+
* Surfaces when a pinned OpenRouter model or OpenCode default expects
|
|
5
|
+
* OPENROUTER_API_KEY but the canonical store and op-run catalog both lack it,
|
|
6
|
+
* or when OpenCode's openrouter provider has no auth wiring.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
|
|
13
|
+
import { parseEnvFile, getUserEnvPath } from '../../env-config.mjs';
|
|
14
|
+
import { hasSecret } from '../../providers/secret-resolver.mjs';
|
|
15
|
+
import { ensureOpenRouterProviderAuth } from '../../opencode-config.mjs';
|
|
16
|
+
import { findOpenCodeConfigPath } from '../../opencode-config.mjs';
|
|
17
|
+
|
|
18
|
+
export const name = 'credential-parity';
|
|
19
|
+
export const intervalMs = 30 * 60 * 1000;
|
|
20
|
+
|
|
21
|
+
function openRouterModelId(modelId) {
|
|
22
|
+
return typeof modelId === 'string' && /^openrouter\//.test(modelId);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function openCodeNeedsOpenRouter(homeDir) {
|
|
26
|
+
const configPath = findOpenCodeConfigPath();
|
|
27
|
+
if (!fs.existsSync(configPath)) return { needs: false, configPath };
|
|
28
|
+
try {
|
|
29
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
30
|
+
const defaultModel = config.model || config.defaultModel || '';
|
|
31
|
+
return { needs: openRouterModelId(defaultModel), configPath, config };
|
|
32
|
+
} catch {
|
|
33
|
+
return { needs: false, configPath };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function openRouterAuthWired(config) {
|
|
38
|
+
const probe = { provider: { openrouter: config?.provider?.openrouter || {} } };
|
|
39
|
+
ensureOpenRouterProviderAuth(probe);
|
|
40
|
+
const provider = probe.provider.openrouter;
|
|
41
|
+
return Boolean(
|
|
42
|
+
provider.apiKey
|
|
43
|
+
|| provider.options?.headers?.Authorization,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function runCredentialParityChecks({ rootDir = process.cwd(), homeDir = os.homedir(), env = process.env } = {}) {
|
|
48
|
+
const violations = [];
|
|
49
|
+
const configPath = getUserEnvPath(homeDir);
|
|
50
|
+
const openRouterConfigured = hasSecret('OPENROUTER_API_KEY', { env, cwd: rootDir });
|
|
51
|
+
|
|
52
|
+
const openCode = openCodeNeedsOpenRouter(homeDir);
|
|
53
|
+
if (openCode.needs && !openRouterConfigured) {
|
|
54
|
+
violations.push({
|
|
55
|
+
category: 'credential-parity',
|
|
56
|
+
severity: 'advisory',
|
|
57
|
+
summary: `OpenCode default model uses OpenRouter but OPENROUTER_API_KEY is not configured (${openCode.configPath})`,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (openCode.config && openCode.needs && !openRouterAuthWired(openCode.config)) {
|
|
62
|
+
violations.push({
|
|
63
|
+
category: 'credential-parity',
|
|
64
|
+
severity: 'advisory',
|
|
65
|
+
summary: 'OpenCode provider.openrouter has no apiKey env ref — run `construct sync --global`',
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const xdgEnv = parseEnvFile(configPath);
|
|
70
|
+
const legacyEnv = parseEnvFile(path.join(homeDir, '.construct', 'config.env'));
|
|
71
|
+
if (legacyEnv.CONSTRUCT_OP_ENV_FILE && !xdgEnv.CONSTRUCT_OP_ENV_FILE) {
|
|
72
|
+
violations.push({
|
|
73
|
+
category: 'credential-parity',
|
|
74
|
+
severity: 'advisory',
|
|
75
|
+
summary: 'CONSTRUCT_OP_ENV_FILE is still in ~/.construct/config.env — run `construct install --scope=user` to migrate to XDG config',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
summary: violations.length
|
|
81
|
+
? `credential-parity: ${violations.length} advisory finding(s)`
|
|
82
|
+
: 'credential-parity: ok',
|
|
83
|
+
violations,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function tick({ rootDir = process.cwd(), homeDir = os.homedir(), env = process.env } = {}) {
|
|
88
|
+
const result = await runCredentialParityChecks({ rootDir, homeDir, env });
|
|
89
|
+
return {
|
|
90
|
+
actions: [],
|
|
91
|
+
escalations: [],
|
|
92
|
+
findings: result.violations,
|
|
93
|
+
passed: result.violations.length === 0
|
|
94
|
+
? [{ category: 'credential-parity', summary: result.summary }]
|
|
95
|
+
: [],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* `initialize`, parse the response, send `tools/list`, report tool count.
|
|
7
7
|
* cass-memory v0.2.x (the original cause of construct-1qt) rejects the
|
|
8
8
|
* handshake outright; this watcher flags servers that fail the handshake so
|
|
9
|
-
*
|
|
9
|
+
* doctor surfaces a protocol problem rather than a port-up false
|
|
10
10
|
* positive.
|
|
11
11
|
*
|
|
12
12
|
* Severity defaults to warning. Set `CONSTRUCT_MCP_PROTOCOL_BLOCKING=1` to
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/doctor/watchers/service-health.mjs — continuous service probes.
|
|
3
3
|
*
|
|
4
|
-
* Probes the services `construct dev` starts (postgres,
|
|
5
|
-
*
|
|
4
|
+
* Probes the services `construct dev` starts (postgres, cm).
|
|
5
|
+
* On 2 consecutive probe failures, attempts a restart of
|
|
6
6
|
* docker-backed services (postgres). On 2 consecutive failed
|
|
7
7
|
* restarts, escalates `service.down` for cx-sre. Non-docker services log +
|
|
8
8
|
* escalate after 3 consecutive failures — auto-restart needs original spawn
|
|
@@ -13,12 +13,11 @@
|
|
|
13
13
|
|
|
14
14
|
import { spawnSync, spawn } from 'node:child_process';
|
|
15
15
|
import { existsSync, readFileSync, openSync, mkdirSync } from 'node:fs';
|
|
16
|
-
import {
|
|
17
|
-
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { join } from 'node:path';
|
|
18
17
|
|
|
19
18
|
import { record } from '../audit.mjs';
|
|
20
19
|
import { escalate } from '../escalate.mjs';
|
|
21
|
-
import { configDir,
|
|
20
|
+
import { configDir, cacheDir } from '../../config/xdg.mjs';
|
|
22
21
|
|
|
23
22
|
export const name = 'service-health';
|
|
24
23
|
export const intervalMs = 60 * 1000;
|
|
@@ -26,9 +25,6 @@ export const intervalMs = 60 * 1000;
|
|
|
26
25
|
const failureCounts = {};
|
|
27
26
|
const restartAttempts = {};
|
|
28
27
|
|
|
29
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
30
|
-
const REPO_ROOT = join(__dirname, '..', '..', '..');
|
|
31
|
-
|
|
32
28
|
const services = [
|
|
33
29
|
{
|
|
34
30
|
id: 'postgres',
|
|
@@ -36,12 +32,6 @@ const services = [
|
|
|
36
32
|
restart: () => dockerRestart('construct-postgres'),
|
|
37
33
|
restartable: true,
|
|
38
34
|
},
|
|
39
|
-
{
|
|
40
|
-
id: 'dashboard',
|
|
41
|
-
probe: () => probeDashboardPort(),
|
|
42
|
-
restart: () => respawnViaConstructUp('dashboard'),
|
|
43
|
-
restartable: true,
|
|
44
|
-
},
|
|
45
35
|
{
|
|
46
36
|
id: 'cm',
|
|
47
37
|
probe: () => probeMemoryHealth(),
|
|
@@ -55,18 +45,6 @@ function probeTcp(host, port, timeoutMs = 1500) {
|
|
|
55
45
|
return r.status === 0;
|
|
56
46
|
}
|
|
57
47
|
|
|
58
|
-
function probeDashboardPort() {
|
|
59
|
-
const statePath = join(stateDir(), 'dashboard.json');
|
|
60
|
-
if (!existsSync(statePath)) return true;
|
|
61
|
-
try {
|
|
62
|
-
const state = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
63
|
-
if (!state.port) return true;
|
|
64
|
-
return probeTcp('127.0.0.1', state.port);
|
|
65
|
-
} catch {
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
48
|
function probeMemoryHealth() {
|
|
71
49
|
const envPath = join(configDir(), 'config.env');
|
|
72
50
|
if (!existsSync(envPath)) return true;
|
|
@@ -99,12 +77,6 @@ function detachSpawn(command, args, logName) {
|
|
|
99
77
|
}
|
|
100
78
|
}
|
|
101
79
|
|
|
102
|
-
function respawnViaConstructUp() {
|
|
103
|
-
const bin = join(REPO_ROOT, 'bin', 'construct');
|
|
104
|
-
if (!existsSync(bin)) return { ok: false, error: 'bin/construct not found' };
|
|
105
|
-
return detachSpawn('node', [bin, 'up'], 'doctor-respawn-up.log');
|
|
106
|
-
}
|
|
107
|
-
|
|
108
80
|
function respawnCm() {
|
|
109
81
|
const envPath = join(configDir(), 'config.env');
|
|
110
82
|
let port = '4231';
|
package/lib/document-export.mjs
CHANGED
|
@@ -105,6 +105,50 @@ export function pdfRenderedDiagrams(pdfPath, sourceContent) {
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
function rawDiagramSourcePattern() {
|
|
109
|
+
return /(?:flowchart\s+(?:TD|LR|BT|RL)|sequenceDiagram|classDiagram|stateDiagram|erDiagram|journey|gantt|pie\s+title|direction:\s*(?:right|down|left|up))/i;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function zipEntryText(zipPath, entryName, env = process.env) {
|
|
113
|
+
if (!whichBin('unzip', env)) return null;
|
|
114
|
+
const result = spawnSync('unzip', ['-p', zipPath, entryName], { encoding: 'utf8', env });
|
|
115
|
+
return result.status === 0 ? String(result.stdout || '') : null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function zipEntryNames(zipPath, env = process.env) {
|
|
119
|
+
if (!whichBin('unzip', env)) return [];
|
|
120
|
+
const result = spawnSync('unzip', ['-Z1', zipPath], { encoding: 'utf8', env });
|
|
121
|
+
return result.status === 0
|
|
122
|
+
? String(result.stdout || '').split('\n').map((line) => line.trim()).filter(Boolean)
|
|
123
|
+
: [];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function docxRenderedDiagrams(docxPath, sourceContent, env = process.env) {
|
|
127
|
+
const expected = countDiagramFences(sourceContent);
|
|
128
|
+
if (expected === 0) return true;
|
|
129
|
+
const documentXml = zipEntryText(docxPath, 'word/document.xml', env);
|
|
130
|
+
if (!documentXml) return false;
|
|
131
|
+
if (rawDiagramSourcePattern().test(documentXml)) return false;
|
|
132
|
+
const rels = zipEntryText(docxPath, 'word/_rels/document.xml.rels', env) || '';
|
|
133
|
+
const entries = zipEntryNames(docxPath, env);
|
|
134
|
+
const mediaCount = entries.filter((entry) => entry.startsWith('word/media/')).length;
|
|
135
|
+
return mediaCount >= expected || /relationships\/image/i.test(rels);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function htmlRenderedDiagrams(htmlPath, sourceContent) {
|
|
139
|
+
const expected = countDiagramFences(sourceContent);
|
|
140
|
+
if (expected === 0) return true;
|
|
141
|
+
let html = '';
|
|
142
|
+
try {
|
|
143
|
+
html = fs.readFileSync(htmlPath, 'utf8');
|
|
144
|
+
} catch {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
if (rawDiagramSourcePattern().test(html)) return false;
|
|
148
|
+
const renderedCount = (html.match(/<(?:img|svg)\b/gi) || []).length;
|
|
149
|
+
return renderedCount >= expected || /data:image\//i.test(html);
|
|
150
|
+
}
|
|
151
|
+
|
|
108
152
|
export { pdfUsesBundledBrandSans } from './brand-fonts.mjs';
|
|
109
153
|
|
|
110
154
|
/** @deprecated use pdfUsesBundledBrandSans */
|
|
@@ -441,17 +485,37 @@ export function exportMarkdown({
|
|
|
441
485
|
};
|
|
442
486
|
}
|
|
443
487
|
|
|
444
|
-
if (
|
|
488
|
+
if (figures) {
|
|
445
489
|
const source = fs.readFileSync(inputPath, 'utf8');
|
|
446
490
|
const expectedDiagrams = countDiagramFences(source);
|
|
447
|
-
if (expectedDiagrams > 0
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
491
|
+
if (expectedDiagrams > 0) {
|
|
492
|
+
if (format === 'pdf' && !pdfRenderedDiagrams(target, source)) {
|
|
493
|
+
return {
|
|
494
|
+
ok: false,
|
|
495
|
+
format,
|
|
496
|
+
inputPath,
|
|
497
|
+
outputPath: target,
|
|
498
|
+
message: `Export wrote PDF but rendered 0/${expectedDiagrams} diagram(s). Ensure d2 and mmdc are installed; mmdc needs Chrome (set PUPPETEER_EXECUTABLE_PATH or install Google Chrome). Pandoc stderr: ${(result.stderr || '').trim().slice(0, 300)}`,
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
if (format === 'docx' && !docxRenderedDiagrams(target, source, effectiveEnv)) {
|
|
502
|
+
return {
|
|
503
|
+
ok: false,
|
|
504
|
+
format,
|
|
505
|
+
inputPath,
|
|
506
|
+
outputPath: target,
|
|
507
|
+
message: `Export wrote DOCX but left diagram source unresolved. Ensure d2 and mermaid-cli rendered the figures before distributing the document. Pandoc stderr: ${(result.stderr || '').trim().slice(0, 300)}`,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
if ((format === 'html' || format === 'deck') && !htmlRenderedDiagrams(target, source)) {
|
|
511
|
+
return {
|
|
512
|
+
ok: false,
|
|
513
|
+
format,
|
|
514
|
+
inputPath,
|
|
515
|
+
outputPath: target,
|
|
516
|
+
message: `Export wrote ${format.toUpperCase()} but left diagram source unresolved. Ensure d2 and mermaid-cli rendered the figures before distributing the document. Pandoc stderr: ${(result.stderr || '').trim().slice(0, 300)}`,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
455
519
|
}
|
|
456
520
|
}
|
|
457
521
|
|