@geraldmaron/construct 1.4.1 → 1.5.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/.env.example +36 -0
- package/README.md +41 -7
- package/bin/construct +1029 -65
- package/bin/construct-postinstall.mjs +27 -2
- package/config/tag-vocabulary.json +264 -0
- package/examples/distribution/sources/deck-one-pager.md +1 -1
- package/lib/acp/server.mjs +21 -7
- package/lib/adapters-sync.mjs +2 -1
- package/lib/audit-trail.mjs +185 -2
- package/lib/beads/auto-close.mjs +2 -1
- package/lib/beads/drift.mjs +2 -1
- package/lib/bridges/copilot-proxy.mjs +20 -5
- package/lib/certification/skill-inventory.mjs +10 -1
- package/lib/cli/approvals.mjs +147 -0
- package/lib/cli-commands.mjs +105 -14
- package/lib/comment-lint.mjs +127 -8
- package/lib/config/schema.mjs +7 -30
- package/lib/config/source-target-registry.mjs +70 -0
- package/lib/config/source-targets.mjs +179 -205
- package/lib/contracts/coverage.mjs +77 -0
- package/lib/contracts/validate.mjs +102 -13
- package/lib/contracts/violation-log.mjs +50 -4
- package/lib/db/migrate.mjs +69 -0
- package/lib/db/migrations/001_orchestration_runs.sql +9 -0
- package/lib/db/migrations/002_queue_provider.sql +50 -0
- package/lib/db/migrations/003_worker_registry.sql +17 -0
- package/lib/db/migrations/004_trace_events.sql +16 -0
- package/lib/db/migrations/005_shared_memory.sql +15 -0
- package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
- package/lib/decisions/enforced-baseline.json +0 -2
- package/lib/decisions/registry.mjs +3 -2
- package/lib/deployment/parity-contract.mjs +1 -1
- package/lib/deployment-mode.mjs +78 -2
- package/lib/diagram-export.mjs +10 -1
- package/lib/distill.mjs +5 -2
- package/lib/docs-verify.mjs +2 -1
- package/lib/doctor/cli.mjs +1 -0
- package/lib/doctor/command-on-path.mjs +24 -0
- package/lib/doctor/diagnosis.mjs +109 -0
- package/lib/doctor/embedding-health.mjs +50 -0
- package/lib/doctor/engine-health.mjs +93 -0
- package/lib/doctor/graph-validate.mjs +47 -0
- package/lib/doctor/index.mjs +23 -4
- package/lib/doctor/sidecar-providers.mjs +56 -0
- package/lib/doctor/watchers/consistency.mjs +93 -1
- package/lib/doctor/watchers/cx-budget.mjs +1 -1
- package/lib/doctor/watchers/graph-staleness.mjs +1 -1
- package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
- package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
- package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
- package/lib/doctor/watchers/provider-breaker.mjs +78 -0
- package/lib/document-export.mjs +52 -27
- package/lib/document-extract/docling-client.mjs +9 -5
- package/lib/document-extract/docling-sidecar.py +30 -0
- package/lib/document-extract.mjs +1 -1
- package/lib/document-ingest.mjs +9 -0
- package/lib/embed/approval-queue.mjs +184 -88
- package/lib/embed/authority-guard.mjs +65 -2
- package/lib/embed/capability-jobs.mjs +365 -0
- package/lib/embed/capability-lifecycle.mjs +219 -0
- package/lib/embed/capability-loader.mjs +303 -0
- package/lib/embed/capability-runtime.mjs +58 -0
- package/lib/embed/cli.mjs +185 -8
- package/lib/embed/config.mjs +4 -0
- package/lib/embed/daemon.mjs +125 -6
- package/lib/embed/demand-fetch.mjs +190 -54
- package/lib/embed/inbox.mjs +12 -10
- package/lib/embed/presets/ops-triage.mjs +236 -0
- package/lib/embed/presets/pm-feedback.mjs +341 -0
- package/lib/embed/presets/tpm.mjs +401 -0
- package/lib/embed/providers/jira.mjs +72 -1
- package/lib/embed/providers/registry.mjs +140 -33
- package/lib/embed/worker.mjs +5 -0
- package/lib/embedded-contract/capability.mjs +4 -0
- package/lib/embedded-contract/execution.mjs +1 -1
- package/lib/embedded-contract/model-resolve.mjs +53 -3
- package/lib/embedded-contract/workflow-defs.mjs +48 -73
- package/lib/embedded-contract/workflow-invoke.mjs +144 -4
- package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
- package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
- package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
- package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
- package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
- package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
- package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
- package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
- package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
- package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
- package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
- package/lib/env-config.mjs +60 -11
- package/lib/export-validate.mjs +34 -2
- package/lib/extensions/index.mjs +27 -0
- package/lib/extensions/loader.mjs +120 -0
- package/lib/extensions/manifest-schema.mjs +141 -0
- package/lib/extensions/manifests/anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
- package/lib/extensions/manifests/directory.manifest.json +12 -0
- package/lib/extensions/manifests/docling.manifest.json +37 -0
- package/lib/extensions/manifests/echo.manifest.json +8 -0
- package/lib/extensions/manifests/feedback.manifest.json +12 -0
- package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
- package/lib/extensions/manifests/github.manifest.json +52 -0
- package/lib/extensions/manifests/linear.manifest.json +50 -0
- package/lib/extensions/manifests/local.manifest.json +12 -0
- package/lib/extensions/manifests/ollama.manifest.json +12 -0
- package/lib/extensions/manifests/openai.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter.manifest.json +12 -0
- package/lib/extensions/manifests/postgres.manifest.json +12 -0
- package/lib/extensions/manifests/salesforce.manifest.json +12 -0
- package/lib/extensions/manifests/slack.manifest.json +58 -0
- package/lib/extensions/manifests/whisper.manifest.json +36 -0
- package/lib/extensions/validate.mjs +175 -0
- package/lib/features.mjs +13 -9
- package/lib/flows/checkpoint.mjs +184 -0
- package/lib/flows/constants.mjs +27 -0
- package/lib/flows/define.mjs +146 -0
- package/lib/flows/engine.mjs +249 -0
- package/lib/flows/errors.mjs +19 -0
- package/lib/flows/index.mjs +27 -0
- package/lib/flows/joins.mjs +18 -0
- package/lib/flows/schema.mjs +90 -0
- package/lib/flows/state.mjs +31 -0
- package/lib/frameworks/loader.mjs +99 -0
- package/lib/frameworks/schema.mjs +157 -0
- package/lib/graph/build-from-corpus.mjs +67 -0
- package/lib/graph/build-from-embed.mjs +82 -0
- package/lib/graph/build-from-registry.mjs +164 -3
- package/lib/graph/cli.mjs +456 -12
- package/lib/graph/gap-queries.mjs +156 -0
- package/lib/graph/gaps.mjs +41 -0
- package/lib/graph/impacted.mjs +129 -0
- package/lib/graph/runtime-evidence.mjs +177 -0
- package/lib/graph/security-coverage.mjs +113 -0
- package/lib/graph/staleness.mjs +241 -10
- package/lib/graph/store.mjs +24 -7
- package/lib/graph/validate.mjs +161 -0
- package/lib/headhunt.mjs +9 -8
- package/lib/health-check.mjs +15 -7
- package/lib/hook-health.mjs +1 -1
- package/lib/hooks/agent-tracker.mjs +10 -4
- package/lib/hooks/edit-guard.mjs +3 -3
- package/lib/hooks/graph-impact-advisory.mjs +2 -2
- package/lib/hooks/guard-bash.mjs +41 -15
- package/lib/hooks/mcp-health-check.mjs +11 -4
- package/lib/hooks/model-fallback.mjs +50 -6
- package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
- package/lib/hooks/pre-compact.mjs +181 -173
- package/lib/hooks/rule-verifier.mjs +2 -1
- package/lib/hooks/session-reflect.mjs +2 -1
- package/lib/hooks/session-start.mjs +3 -3
- package/lib/host/readiness.mjs +109 -0
- package/lib/host-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +29 -8
- package/lib/identity.mjs +92 -0
- package/lib/ingest/degraded-extract.mjs +96 -0
- package/lib/ingest/docling-remote.mjs +2 -1
- package/lib/ingest/provider-extract.mjs +7 -19
- package/lib/ingest/sidecar-providers.mjs +196 -0
- package/lib/ingest-tooling.mjs +11 -5
- package/lib/init-unified.mjs +55 -38
- package/lib/init-update.mjs +2 -1
- package/lib/install/legacy-global-cleanup.mjs +25 -0
- package/lib/install/stage-project.mjs +41 -4
- package/lib/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- package/lib/intake/prepare.mjs +2 -0
- package/lib/intake/queue-registry.mjs +50 -0
- package/lib/intake/queue.mjs +133 -17
- package/lib/intake/session-prelude.mjs +57 -8
- package/lib/integrations/intake-integrations.mjs +61 -111
- package/lib/intent-classifier.mjs +43 -5
- package/lib/libreoffice-export.mjs +8 -8
- package/lib/logging/rotate.mjs +5 -4
- package/lib/mcp/broker.mjs +332 -17
- package/lib/mcp/denied-store.mjs +95 -0
- package/lib/mcp/destructive-approval.mjs +57 -0
- package/lib/mcp/destructive-gate.mjs +30 -0
- package/lib/mcp/dispatch-envelope.mjs +199 -0
- package/lib/mcp/memory-bridge.mjs +2 -1
- package/lib/mcp/server.mjs +185 -1268
- package/lib/mcp/tool-definitions-memory.mjs +376 -0
- package/lib/mcp/tool-definitions-project.mjs +271 -0
- package/lib/mcp/tool-definitions-skills.mjs +311 -0
- package/lib/mcp/tool-definitions-workflow.mjs +398 -0
- package/lib/mcp/tool-definitions.mjs +25 -0
- package/lib/mcp/tool-rate-limit.mjs +47 -0
- package/lib/mcp/tool-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +95 -0
- package/lib/mcp/tool-surface-parity.mjs +60 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +197 -19
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- package/lib/mcp/tools/project.mjs +25 -8
- package/lib/mcp/tools/provider-write.mjs +187 -0
- package/lib/mcp/tools/scope.mjs +0 -3
- package/lib/mcp/tools/skills.mjs +1 -1
- package/lib/mcp/tools/storage.mjs +2 -2
- package/lib/mcp/tools/web-search-governance.mjs +96 -0
- package/lib/mcp/tools/web-search.mjs +5 -76
- package/lib/mcp/transport/auth.mjs +238 -0
- package/lib/mcp/transport/http.mjs +136 -0
- package/lib/mcp/transport/mode.mjs +31 -0
- package/lib/mcp/transport/stdio.mjs +22 -0
- package/lib/mcp-catalog.json +4 -4
- package/lib/mcp-manager.mjs +34 -23
- package/lib/mcp-platform-config.mjs +53 -20
- package/lib/mode-capabilities.mjs +109 -0
- package/lib/model-router.mjs +42 -9
- package/lib/models/catalog.mjs +40 -1
- package/lib/net-guard.mjs +247 -0
- package/lib/observation-store.mjs +6 -2
- package/lib/ollama/provision-context.mjs +2 -1
- package/lib/opencode-config.mjs +22 -3
- package/lib/opencode-runtime-plugin.mjs +120 -2
- package/lib/oracle/actions.mjs +204 -10
- package/lib/oracle/cli.mjs +24 -3
- package/lib/oracle/daemon-entry.mjs +6 -0
- package/lib/oracle/dispatch.mjs +2 -1
- package/lib/oracle/execute.mjs +2 -2
- package/lib/oracle/gaps.mjs +3 -3
- package/lib/oracle/heartbeat.mjs +53 -0
- package/lib/oracle/read-model.mjs +69 -13
- package/lib/oracle/reconcile.mjs +3 -46
- package/lib/oracle/routing.mjs +37 -31
- package/lib/oracle/synthesize.mjs +1 -1
- package/lib/orchestration/classification.mjs +434 -0
- package/lib/orchestration/delegation-flow.mjs +132 -0
- package/lib/orchestration/flow-selection.mjs +418 -0
- package/lib/orchestration/gates.mjs +244 -0
- package/lib/orchestration/host-sampling.mjs +121 -0
- package/lib/orchestration/policy-constants.mjs +48 -0
- package/lib/orchestration/provider-outcome.mjs +197 -0
- package/lib/orchestration/readiness.mjs +114 -13
- package/lib/orchestration/run-store-postgres.mjs +32 -26
- package/lib/orchestration/run-store-sqlite.mjs +8 -14
- package/lib/orchestration/run-store.mjs +25 -12
- package/lib/orchestration/runtime.mjs +508 -62
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +60 -0
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +763 -80
- package/lib/orchestration-policy.mjs +67 -1111
- package/lib/output-quality.mjs +61 -2
- package/lib/packs/cli.mjs +144 -0
- package/lib/packs/core-pack.mjs +112 -0
- package/lib/packs/enablement.mjs +187 -0
- package/lib/packs/index.mjs +23 -0
- package/lib/packs/loader.mjs +176 -0
- package/lib/packs/manifest-schema.mjs +58 -0
- package/lib/packs/prompts.mjs +120 -0
- package/lib/packs/validate.mjs +208 -0
- package/lib/path-policy.mjs +56 -0
- package/lib/plugin-registry.mjs +4 -5
- package/lib/policy/audit-gate.mjs +42 -0
- package/lib/policy/consumption-budget.mjs +149 -0
- package/lib/policy/engine.mjs +81 -6
- package/lib/policy/role-authority.mjs +89 -0
- package/lib/prompt-composer.js +19 -3
- package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
- package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
- package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
- package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
- package/lib/providers/contract/adapters/github/index.mjs +38 -3
- package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
- package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
- package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
- package/lib/providers/contract/adapters/jira/manifest.json +17 -0
- package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
- package/lib/providers/contract.mjs +207 -0
- package/lib/providers/credential-bootstrap.mjs +7 -4
- package/lib/providers/credential-sources.mjs +14 -2
- package/lib/providers/directory/index.mjs +261 -0
- package/lib/providers/feedback/index.mjs +392 -0
- package/lib/providers/filter-audit.mjs +68 -0
- package/lib/providers/filter-schema.mjs +54 -0
- package/lib/providers/github/index.mjs +31 -0
- package/lib/providers/instance-config.mjs +215 -0
- package/lib/providers/op-locate.mjs +96 -0
- package/lib/providers/op-run.mjs +64 -9
- package/lib/providers/registry.mjs +26 -3
- package/lib/providers/secret-audit-wiring.mjs +41 -18
- package/lib/providers/secret-resolver.mjs +263 -31
- package/lib/publish-template.mjs +6 -3
- package/lib/publish-tooling.mjs +4 -0
- package/lib/publish.mjs +32 -4
- package/lib/queue/pg-queue.mjs +395 -0
- package/lib/reflect.mjs +2 -1
- package/lib/registry/assemble.mjs +28 -2
- package/lib/registry/catalog.mjs +6 -0
- package/lib/registry/consolidation.mjs +8 -1
- package/lib/registry/custom-scaffold.mjs +200 -0
- package/lib/registry/custom-schema.mjs +137 -0
- package/lib/registry/loader.mjs +15 -4
- package/lib/registry/manifests/format-engines.default.json +15 -0
- package/lib/registry/manifests/surface-map.default.json +46 -0
- package/lib/registry/retired-paths.mjs +1 -0
- package/lib/registry/surface-map.mjs +100 -50
- package/lib/registry/validate.mjs +3 -3
- package/lib/render-visual-check.mjs +240 -0
- package/lib/resources/budget.mjs +40 -17
- package/lib/roles/flavor-bindings.mjs +36 -14
- package/lib/roles/gateway.mjs +15 -8
- package/lib/roots.mjs +107 -0
- package/lib/runtime/uv-bootstrap.mjs +32 -6
- package/lib/runtime/whisper-bootstrap.mjs +11 -3
- package/lib/runtime-env.mjs +5 -2
- package/lib/sandbox.mjs +1 -1
- package/lib/scheduler/index.mjs +8 -20
- package/lib/schema-infer.mjs +31 -2
- package/lib/scopes/lifecycle.mjs +29 -25
- package/lib/scopes/loader.mjs +49 -12
- package/lib/scopes/teams.mjs +1 -1
- package/lib/security/ingest-boundary.mjs +80 -0
- package/lib/security/recall-wrapper.mjs +99 -0
- package/lib/security/trust.mjs +132 -0
- package/lib/service-manager.mjs +93 -24
- package/lib/setup.mjs +41 -23
- package/lib/skills-apply.mjs +4 -2
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/state-root.mjs +147 -0
- package/lib/status.mjs +597 -6
- package/lib/storage/admin.mjs +84 -7
- package/lib/storage/backend-registry.mjs +73 -0
- package/lib/storage/backend.mjs +63 -9
- package/lib/storage/embeddings-openai.mjs +12 -2
- package/lib/storage/hybrid-query.mjs +11 -3
- package/lib/storage/retrieval-hardening.mjs +384 -0
- package/lib/storage/shared-memory.mjs +158 -0
- package/lib/storage/vector-client.mjs +69 -2
- package/lib/team/health.mjs +72 -0
- package/lib/telemetry/backends/local.mjs +9 -3
- package/lib/telemetry/client.mjs +3 -7
- package/lib/template-registry.mjs +10 -12
- package/lib/tenant/context.mjs +82 -0
- package/lib/tenant/isolation.mjs +129 -0
- package/lib/test-corpus-inventory.mjs +103 -2
- package/lib/uninstall/uninstall.mjs +78 -12
- package/lib/validator.mjs +4 -6
- package/lib/worker/entrypoint.mjs +2 -1
- package/lib/worker/run.mjs +2 -2
- package/lib/worker/trace.mjs +5 -11
- package/lib/workflow-state.mjs +52 -8
- package/lib/workflows/liveness.mjs +203 -0
- package/lib/workflows/loader.mjs +177 -0
- package/lib/workflows/manifest-schema.mjs +71 -0
- package/lib/workflows/surface-parity.mjs +118 -0
- package/lib/workflows/validate.mjs +127 -0
- package/lib/writes/control-plane.mjs +140 -0
- package/lib/writes/envelope.mjs +206 -0
- package/lib/writes/sent-log.mjs +86 -0
- package/lib/writes/write-intent.mjs +109 -0
- package/package.json +20 -7
- package/personas/construct.md +12 -3
- package/registry/agent-manifest.json +117 -0
- package/registry/capabilities.json +1987 -0
- package/rules/common/comments.md +1 -0
- package/schemas/brand-voice.schema.json +24 -0
- package/schemas/capability-registry.schema.json +72 -0
- package/schemas/certification-run.schema.json +130 -0
- package/schemas/demo-recording.schema.json +46 -0
- package/schemas/eval-dataset.schema.json +79 -0
- package/schemas/execution-capability-profile.schema.json +46 -0
- package/schemas/execution-policy.schema.json +114 -0
- package/schemas/improvement-proposal.schema.json +65 -0
- package/schemas/mcp-tool-output.schema.json +61 -0
- package/schemas/platform-capabilities.schema.json +83 -0
- package/schemas/project-config.schema.json +215 -0
- package/schemas/project-demo.schema.json +60 -0
- package/schemas/provider-behavior-matrix.schema.json +91 -0
- package/schemas/scope.schema.json +197 -0
- package/schemas/specialist-trace.schema.json +107 -0
- package/schemas/team.schema.json +99 -0
- package/schemas/unified-registry.schema.json +546 -0
- package/scripts/sync-specialists.mjs +336 -106
- package/skills/docs/adr-workflow.md +1 -1
- package/skills/docs/codebase-research-workflow.md +3 -3
- package/skills/docs/prd-workflow.md +5 -6
- package/skills/docs/runbook-workflow.md +2 -2
- package/skills/docs/user-research-workflow.md +3 -3
- package/skills/operating/fleet-health-routing.md +77 -0
- package/skills/roles/ai-engineer.md +1 -1
- package/skills/roles/architect.md +0 -1
- package/skills/roles/business-strategist.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/data-engineer.md +1 -1
- package/skills/roles/data-engineer.pipeline.md +1 -1
- package/skills/roles/data-engineer.vector-retrieval.md +1 -2
- package/skills/roles/data-engineer.warehouse.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +0 -1
- package/skills/roles/devil-advocate.md +1 -1
- package/skills/roles/docs-keeper.md +1 -1
- package/skills/roles/evaluator.md +1 -1
- package/skills/roles/explorer.md +1 -1
- package/skills/roles/platform-engineer.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -2
- package/skills/roles/qa.api-contract.md +0 -1
- package/skills/roles/qa.data-pipeline.md +0 -1
- package/skills/roles/qa.md +0 -1
- package/skills/roles/qa.web-ui.md +0 -1
- package/skills/roles/release-manager.md +1 -1
- package/skills/roles/researcher.md +0 -2
- package/skills/roles/reviewer.md +0 -3
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +0 -1
- package/skills/roles/security.privacy.md +0 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/sre.md +1 -1
- package/skills/roles/test-automation.md +1 -1
- package/skills/roles/trace-reviewer.md +1 -1
- package/skills/roles/ux-researcher.md +1 -1
- package/specialists/artifact-manifest.json +36 -36
- package/specialists/org/contracts/accessibility-to-qa.json +11 -3
- package/specialists/org/contracts/any-to-business-strategist.json +19 -7
- package/specialists/org/contracts/any-to-debugger.json +7 -1
- package/specialists/org/contracts/any-to-designer.json +7 -1
- package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
- package/specialists/org/contracts/any-to-explorer.json +13 -4
- package/specialists/org/contracts/any-to-sre-incident.json +6 -2
- package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
- package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
- package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
- package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
- package/specialists/org/contracts/architect-to-engineer.json +20 -4
- package/specialists/org/contracts/architect-to-evaluator.json +15 -5
- package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
- package/specialists/org/contracts/architect-to-operations.json +17 -4
- package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
- package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
- package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
- package/specialists/org/contracts/engineer-to-qa.json +20 -4
- package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
- package/specialists/org/contracts/explorer-to-engineer.json +11 -3
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
- package/specialists/org/contracts/operations-to-user.json +53 -0
- package/specialists/org/contracts/operations-triage-output.json +53 -0
- package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
- package/specialists/org/contracts/product-manager-to-architect.json +30 -10
- package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
- package/specialists/org/contracts/qa-to-release-manager.json +11 -3
- package/specialists/org/contracts/researcher-to-architect.json +10 -2
- package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
- package/specialists/org/contracts/reviewer-to-security.json +17 -3
- package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
- package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
- package/specialists/org/contracts/user-to-construct.json +11 -4
- package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
- package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
- package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
- package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
- package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
- package/specialists/org/groups/engineering-group.json +2 -6
- package/specialists/org/groups/governance-group.json +2 -3
- package/specialists/org/groups/operations-group.json +5 -8
- package/specialists/org/groups/product-group.json +4 -8
- package/specialists/org/groups/quality-group.json +3 -7
- package/specialists/org/groups/strategy-group.json +6 -9
- package/specialists/org/models.json.example +8 -0
- package/specialists/org/scopes/creative.json +6 -1
- package/specialists/org/scopes/operations.json +7 -1
- package/specialists/org/scopes/research.json +6 -1
- package/specialists/org/scopes/rnd.json +7 -1
- package/specialists/org/specialists/cx-architect.json +27 -8
- package/specialists/org/specialists/cx-designer.json +22 -8
- package/specialists/org/specialists/cx-engineer.json +71 -7
- package/specialists/org/specialists/cx-operations.json +89 -15
- package/specialists/org/specialists/cx-orchestrator.json +16 -4
- package/specialists/org/specialists/cx-product-manager.json +28 -9
- package/specialists/org/specialists/cx-qa.json +16 -6
- package/specialists/org/specialists/cx-researcher.json +41 -7
- package/specialists/org/specialists/cx-reviewer.json +61 -11
- package/specialists/org/specialists/cx-security.json +30 -10
- package/specialists/org/teams/design-team.json +3 -4
- package/specialists/org/teams/engineering-team.json +3 -10
- package/specialists/org/teams/governance-team.json +3 -5
- package/specialists/org/teams/operations-team.json +6 -12
- package/specialists/org/teams/product-management-team.json +2 -3
- package/specialists/org/teams/quality-team.json +4 -12
- package/specialists/org/teams/research-team.json +3 -3
- package/specialists/org/teams/strategy-team.json +7 -14
- package/specialists/prompts/cx-architect.md +20 -1
- package/specialists/prompts/cx-data-analyst.md +1 -1
- package/specialists/prompts/cx-designer.md +12 -4
- package/specialists/prompts/cx-engineer.md +15 -1
- package/specialists/prompts/cx-operations.md +14 -2
- package/specialists/prompts/cx-orchestrator.md +9 -5
- package/specialists/prompts/cx-product-manager.md +5 -1
- package/specialists/prompts/cx-qa.md +6 -2
- package/specialists/prompts/cx-researcher.md +27 -31
- package/specialists/prompts/cx-reviewer.md +19 -1
- package/specialists/prompts/cx-security.md +5 -1
- package/templates/distribution/construct-brand.typ +123 -59
- package/templates/distribution/construct-decision.typ +6 -3
- package/templates/distribution/construct-pdf.typ +11 -4
- package/templates/distribution/construct-prd.typ +6 -3
- package/templates/distribution/construct-research.typ +7 -4
- package/vendor/pandoc-ext/README.md +3 -0
- package/vendor/pandoc-ext/diagram.lua +687 -0
- package/lib/providers/contract/adapters/git/index.mjs +0 -115
- package/lib/providers/contract/adapters/slack/index.mjs +0 -175
- package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
- package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
- package/specialists/org/contracts/designer-to-accessibility.json +0 -36
- package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
- package/specialists/org/contracts/qa-to-test-automation.json +0 -42
- package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
- package/specialists/org/contracts/sre-to-release-manager.json +0 -36
- package/specialists/org/specialists/cx-accessibility.json +0 -69
- package/specialists/org/specialists/cx-ai-engineer.json +0 -75
- package/specialists/org/specialists/cx-business-strategist.json +0 -72
- package/specialists/org/specialists/cx-data-engineer.json +0 -71
- package/specialists/org/specialists/cx-devil-advocate.json +0 -71
- package/specialists/org/specialists/cx-docs-keeper.json +0 -82
- package/specialists/org/specialists/cx-evaluator.json +0 -69
- package/specialists/org/specialists/cx-explorer.json +0 -69
- package/specialists/org/specialists/cx-legal-compliance.json +0 -74
- package/specialists/org/specialists/cx-oracle.json +0 -46
- package/specialists/org/specialists/cx-platform-engineer.json +0 -80
- package/specialists/org/specialists/cx-rd-lead.json +0 -73
- package/specialists/org/specialists/cx-release-manager.json +0 -77
- package/specialists/org/specialists/cx-sre.json +0 -94
- package/specialists/org/specialists/cx-test-automation.json +0 -69
- package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
- package/specialists/org/specialists/cx-ux-researcher.json +0 -68
- package/specialists/org/teams/accessibility-team.json +0 -39
- package/specialists/org/teams/ux-research-team.json +0 -39
- package/specialists/prompts/cx-accessibility.md +0 -55
- package/specialists/prompts/cx-ai-engineer.md +0 -124
- package/specialists/prompts/cx-business-strategist.md +0 -58
- package/specialists/prompts/cx-data-engineer.md +0 -55
- package/specialists/prompts/cx-devil-advocate.md +0 -59
- package/specialists/prompts/cx-docs-keeper.md +0 -164
- package/specialists/prompts/cx-evaluator.md +0 -49
- package/specialists/prompts/cx-explorer.md +0 -71
- package/specialists/prompts/cx-legal-compliance.md +0 -58
- package/specialists/prompts/cx-oracle.md +0 -98
- package/specialists/prompts/cx-platform-engineer.md +0 -97
- package/specialists/prompts/cx-rd-lead.md +0 -59
- package/specialists/prompts/cx-release-manager.md +0 -54
- package/specialists/prompts/cx-sre.md +0 -111
- package/specialists/prompts/cx-test-automation.md +0 -55
- package/specialists/prompts/cx-trace-reviewer.md +0 -101
- package/specialists/prompts/cx-ux-researcher.md +0 -53
package/lib/diagram-export.mjs
CHANGED
|
@@ -53,7 +53,16 @@ const MERMAID_INIT = `%%{init: {
|
|
|
53
53
|
'look': 'handDrawn',
|
|
54
54
|
'fontFamily': '${MERMAID_FONT}',
|
|
55
55
|
'flowchart': {
|
|
56
|
-
'htmlLabels': false
|
|
56
|
+
'htmlLabels': false,
|
|
57
|
+
'nodeSpacing': 46,
|
|
58
|
+
'rankSpacing': 56,
|
|
59
|
+
'padding': 12
|
|
60
|
+
},
|
|
61
|
+
'quadrantChart': {
|
|
62
|
+
'chartWidth': 620,
|
|
63
|
+
'chartHeight': 460,
|
|
64
|
+
'pointLabelFontSize': 13,
|
|
65
|
+
'pointRadius': 4
|
|
57
66
|
},
|
|
58
67
|
'themeVariables': {
|
|
59
68
|
'fontFamily': '${MERMAID_FONT}',
|
package/lib/distill.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
UTF8_TEXT_EXTS,
|
|
18
18
|
extractDocumentText as extractSharedDocumentText,
|
|
19
19
|
} from './document-extract.mjs';
|
|
20
|
+
import { isMainModule } from './roots.mjs';
|
|
20
21
|
|
|
21
22
|
const TEXT_EXTS = EXTRACTABLE_DOCUMENT_EXTS;
|
|
22
23
|
|
|
@@ -359,7 +360,9 @@ export async function distill(dir, { format = 'summary', query = '', mode = 'aut
|
|
|
359
360
|
const result = await withGenAiSpan('chat', {
|
|
360
361
|
[GenAiAttrs.SYSTEM]: 'anthropic',
|
|
361
362
|
[GenAiAttrs.OPERATION_NAME]: 'chat',
|
|
362
|
-
|
|
363
|
+
// Telemetry label only — the real model is whatever the user's claude CLI
|
|
364
|
+
// resolves; never imply a pinned model id here.
|
|
365
|
+
[GenAiAttrs.REQUEST_MODEL]: process.env.CONSTRUCT_MODEL || 'claude-cli-default',
|
|
363
366
|
'construct.operation': 'distill',
|
|
364
367
|
'construct.prompt_chars': prompt.length,
|
|
365
368
|
}, async (span) => {
|
|
@@ -412,7 +415,7 @@ export async function runDistillCli(argv = process.argv.slice(2)) {
|
|
|
412
415
|
});
|
|
413
416
|
}
|
|
414
417
|
|
|
415
|
-
const isMain = import.meta.url
|
|
418
|
+
const isMain = isMainModule(import.meta.url);
|
|
416
419
|
if (isMain) {
|
|
417
420
|
const { positional, flags } = parseArgs(process.argv.slice(2));
|
|
418
421
|
const dir = positional[0];
|
package/lib/docs-verify.mjs
CHANGED
|
@@ -23,6 +23,7 @@ import { statSync, readFileSync, existsSync } from "node:fs";
|
|
|
23
23
|
import { spawnSync } from "node:child_process";
|
|
24
24
|
import { hasIntakeReference } from "./intake/traceability.mjs";
|
|
25
25
|
import { ensureCxDir } from "./project-init-shared.mjs";
|
|
26
|
+
import { isMainModule } from './roots.mjs';
|
|
26
27
|
|
|
27
28
|
const args = process.argv.slice(2);
|
|
28
29
|
const quickMode = args.includes("--quick");
|
|
@@ -463,7 +464,7 @@ function main() {
|
|
|
463
464
|
process.exit(0);
|
|
464
465
|
}
|
|
465
466
|
|
|
466
|
-
if (import.meta.url
|
|
467
|
+
if (isMainModule(import.meta.url)) {
|
|
467
468
|
main();
|
|
468
469
|
}
|
|
469
470
|
|
package/lib/doctor/cli.mjs
CHANGED
|
@@ -85,6 +85,7 @@ export async function runCli(args) {
|
|
|
85
85
|
import('./watchers/service-health.mjs'),
|
|
86
86
|
import('./watchers/bd-watch.mjs'),
|
|
87
87
|
import('./watchers/consistency.mjs'),
|
|
88
|
+
import('./watchers/orchestration-runs.mjs'),
|
|
88
89
|
]);
|
|
89
90
|
for (const w of watchers) {
|
|
90
91
|
const r = await w.tick();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/doctor/command-on-path.mjs — resolve a command's PATH location the way a
|
|
3
|
+
* user's terminal would, without requiring zsh.
|
|
4
|
+
*
|
|
5
|
+
* `command -v` needs a login-ish shell to see the same PATH a real terminal
|
|
6
|
+
* session would build (rc files, PATH managers, etc). zsh is the
|
|
7
|
+
* macOS/most-desktop-Linux default but is not guaranteed present (minimal
|
|
8
|
+
* containers, some CI images) — spawning it unconditionally turns a missing
|
|
9
|
+
* zsh into a false "not on PATH" result for every dependent check. Falling
|
|
10
|
+
* back to `sh -lc` on ENOENT keeps this a real PATH lookup instead.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { spawnSync } from 'node:child_process';
|
|
14
|
+
|
|
15
|
+
export function resolveCommandOnPath(command, { spawnSyncImpl = spawnSync, platform = process.platform } = {}) {
|
|
16
|
+
if (platform === 'win32') {
|
|
17
|
+
return spawnSyncImpl('where', [command], { encoding: 'utf8', stdio: 'pipe' });
|
|
18
|
+
}
|
|
19
|
+
const viaZsh = spawnSyncImpl('zsh', ['-lc', `command -v ${command}`], { encoding: 'utf8', stdio: 'pipe' });
|
|
20
|
+
if (viaZsh.error?.code === 'ENOENT') {
|
|
21
|
+
return spawnSyncImpl('sh', ['-lc', `command -v ${command}`], { encoding: 'utf8', stdio: 'pipe' });
|
|
22
|
+
}
|
|
23
|
+
return viaZsh;
|
|
24
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/doctor/diagnosis.mjs — typed failure-state taxonomy for the doctor command.
|
|
3
|
+
*
|
|
4
|
+
* Each constant names a distinct class of host / package / MCP / runtime / secrets
|
|
5
|
+
* failure so doctor findings carry an actionable code rather than a bare boolean.
|
|
6
|
+
* Consumers (bin/construct cmdDoctor, lib/host/readiness.mjs, tests) import these
|
|
7
|
+
* keys to ensure the full taxonomy is covered and no failure collapses to a generic
|
|
8
|
+
* "unhealthy" verdict.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const DOCTOR_STATES = {
|
|
12
|
+
'missing-config': 'A required configuration file or directory is absent.',
|
|
13
|
+
'stale-path': 'A registered path (toolkit, entrypoint, server) is outdated or points nowhere.',
|
|
14
|
+
'disabled': 'A feature or gate is explicitly disabled in config.',
|
|
15
|
+
'server-start-failure': 'The MCP or dashboard server failed to start or did not respond within the timeout.',
|
|
16
|
+
'secret-leak': 'A credential or secret is exposed in a tracked file or insecure location.',
|
|
17
|
+
'entrypoint-missing': 'A binary or script the runtime expects (construct-mcp, construct) is not found on PATH.',
|
|
18
|
+
'healthy': 'The check passed with no issues detected.',
|
|
19
|
+
'degraded': 'The check passed with warnings; functionality may be reduced.',
|
|
20
|
+
'stub': 'The capability exists but is a no-op or partial implementation; not production-safe.',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* STUB_ICON — the distinct display character rendered next to stub-tier diagnoses.
|
|
25
|
+
* Renderers that format doctor output should use this rather than hard-coding '~'.
|
|
26
|
+
*/
|
|
27
|
+
export const STUB_ICON = '~';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* classifyCapability(capabilityId, mode)
|
|
31
|
+
*
|
|
32
|
+
* Reads the capability registry for `mode` and maps the capability's status to one
|
|
33
|
+
* of three classification strings:
|
|
34
|
+
* 'implemented' — fully wired; production-safe.
|
|
35
|
+
* 'stub' — code path exists but is a no-op / partial (status: 'stub').
|
|
36
|
+
* 'not-implemented' — no code path exists yet (status: 'not-implemented').
|
|
37
|
+
*
|
|
38
|
+
* Returns 'not-implemented' when the capability is not found in the registry for
|
|
39
|
+
* the given mode (unknown capability IDs are treated as absent).
|
|
40
|
+
*
|
|
41
|
+
* Dynamic import of mode-capabilities happens on first call — in hot paths,
|
|
42
|
+
* prefer `classifyCapabilitySync` with a pre-fetched capabilities array.
|
|
43
|
+
*/
|
|
44
|
+
export async function classifyCapability(capabilityId, mode) {
|
|
45
|
+
let getCapabilities;
|
|
46
|
+
try {
|
|
47
|
+
({ getCapabilities } = await import('../mode-capabilities.mjs'));
|
|
48
|
+
} catch {
|
|
49
|
+
getCapabilities = () => [];
|
|
50
|
+
}
|
|
51
|
+
return classifyCapabilitySync(capabilityId, getCapabilities(mode));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* classifyCapabilitySync(capabilityId, caps)
|
|
56
|
+
*
|
|
57
|
+
* Synchronous variant that accepts a pre-fetched capabilities array. Avoids
|
|
58
|
+
* dynamic import overhead in hot paths (e.g., batch diagnosis loops).
|
|
59
|
+
*/
|
|
60
|
+
export function classifyCapabilitySync(capabilityId, caps) {
|
|
61
|
+
const cap = (caps ?? []).find((c) => c.id === capabilityId);
|
|
62
|
+
if (!cap) return 'not-implemented';
|
|
63
|
+
if (cap.status === 'implemented') return 'implemented';
|
|
64
|
+
if (cap.status === 'stub') return 'stub';
|
|
65
|
+
return 'not-implemented';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* diagnosisLevelForCapability(classification)
|
|
70
|
+
*
|
|
71
|
+
* Maps a classifyCapabilitySync result to the doctor diagnosis level:
|
|
72
|
+
* 'implemented' → 'healthy' (green)
|
|
73
|
+
* 'stub' → 'stub' (yellow ~ icon)
|
|
74
|
+
* 'not-implemented' → 'stub' (yellow ~ icon, "Not implemented" message)
|
|
75
|
+
*/
|
|
76
|
+
export function diagnosisLevelForCapability(classification) {
|
|
77
|
+
if (classification === 'implemented') return 'healthy';
|
|
78
|
+
return 'stub';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* diagnosisMessageForCapability(classification)
|
|
83
|
+
*
|
|
84
|
+
* Returns the human-readable message for a capability diagnosis entry.
|
|
85
|
+
*/
|
|
86
|
+
export function diagnosisMessageForCapability(classification) {
|
|
87
|
+
if (classification === 'implemented') return 'Fully implemented';
|
|
88
|
+
if (classification === 'stub') return 'Partial implementation';
|
|
89
|
+
return 'Not implemented';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* renderDiagnosisLevel(level)
|
|
94
|
+
*
|
|
95
|
+
* Returns the display icon for a given diagnosis level. Renderers that format
|
|
96
|
+
* doctor output should use this rather than embedding icon logic inline.
|
|
97
|
+
*
|
|
98
|
+
* Level → icon mapping:
|
|
99
|
+
* 'healthy' → '✓'
|
|
100
|
+
* 'degraded' → '⚠'
|
|
101
|
+
* 'stub' → '~'
|
|
102
|
+
* anything else → '✗'
|
|
103
|
+
*/
|
|
104
|
+
export function renderDiagnosisLevel(level) {
|
|
105
|
+
if (level === 'healthy') return '✓';
|
|
106
|
+
if (level === 'degraded') return '⚠';
|
|
107
|
+
if (level === 'stub') return STUB_ICON;
|
|
108
|
+
return '✗';
|
|
109
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/doctor/embedding-health.mjs — doctor visibility for the active embedding model.
|
|
3
|
+
*
|
|
4
|
+
* `local` (lib/storage/embeddings-local.mjs, @huggingface/transformers) is the
|
|
5
|
+
* embedding engine's DEFAULT_MODEL (lib/storage/embeddings-engine.mjs),
|
|
6
|
+
* backing knowledge-search and evidence-ingest by default rather than a
|
|
7
|
+
* rarely-used opt-in path. The adapter's runtime already degrades silently to
|
|
8
|
+
* the 256d hashing adapter when the model fails to load (embeddings-local.mjs
|
|
9
|
+
* catch block); doctor visibility surfaces that degradation before a
|
|
10
|
+
* retrieval-quality drop goes unexplained (LMCP-K4), implementing the
|
|
11
|
+
* healthCheck declared for @huggingface/transformers in deps/intent.json:
|
|
12
|
+
* "require this package — absent means local embedding unavailable; hashing
|
|
13
|
+
* fallback activates automatically." A module-resolution import is the probe
|
|
14
|
+
* rather than invoking the ONNX pipeline, since presence/absence — not model
|
|
15
|
+
* inference — is the doctor-relevant signal.
|
|
16
|
+
*/
|
|
17
|
+
import { getEmbeddingModelInfo } from '../storage/embeddings-engine.mjs';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* checkEmbeddingModelForDoctor(opts) — the active embedding model's doctor-shaped finding.
|
|
21
|
+
*
|
|
22
|
+
* Non-`local` models (openai, ollama, hashing) report their configuration as-is;
|
|
23
|
+
* only `local` depends on the optional @huggingface/transformers package, so
|
|
24
|
+
* only that path probes for it.
|
|
25
|
+
*
|
|
26
|
+
* @param {object} [opts]
|
|
27
|
+
* @param {NodeJS.ProcessEnv} [opts.env] injectable env (CONSTRUCT_EMBEDDING_MODEL) for tests
|
|
28
|
+
* @param {Function} [opts.importTransformers] injectable seam over the dynamic import, for tests
|
|
29
|
+
* @returns {Promise<{ ok: boolean, degraded: boolean, label: string, optional: boolean }>}
|
|
30
|
+
*/
|
|
31
|
+
export async function checkEmbeddingModelForDoctor(opts = {}) {
|
|
32
|
+
const { env = process.env, importTransformers = () => import('@huggingface/transformers') } = opts;
|
|
33
|
+
const info = await getEmbeddingModelInfo({ env });
|
|
34
|
+
|
|
35
|
+
if (info.id !== 'local') {
|
|
36
|
+
return { ok: true, degraded: false, label: `Embedding model: ${info.id} (${info.model}, ${info.dimensions}d)`, optional: true };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
await importTransformers();
|
|
41
|
+
return { ok: true, degraded: false, label: `Embedding model: local (${info.model}, ${info.dimensions}d) — @huggingface/transformers resolves`, optional: true };
|
|
42
|
+
} catch (err) {
|
|
43
|
+
return {
|
|
44
|
+
ok: true,
|
|
45
|
+
degraded: true,
|
|
46
|
+
label: `Embedding model: local requested but @huggingface/transformers is not installed (${err.message}) — degrades to hashing-bow-v1 (256d, no semantic understanding). Install with \`npm install @huggingface/transformers\`, or set CONSTRUCT_EMBEDDING_MODEL=hashing to opt into the degraded model explicitly.`,
|
|
47
|
+
optional: true,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/doctor/engine-health.mjs — doctor checks for the document-export toolchain.
|
|
3
|
+
*
|
|
4
|
+
* lib/document-export.mjs's detect() already probes pandoc/typst/libreoffice/
|
|
5
|
+
* pptxgenjs per export format and returns binary presence, version, and an
|
|
6
|
+
* install hint (ADR-0024). This module turns those probes into one doctor-shaped
|
|
7
|
+
* finding per engine — binary presence, version, and a degradation message naming
|
|
8
|
+
* the missing binary and the export formats it owns — so `construct doctor`
|
|
9
|
+
* predicts whether a pdf/doc/pptx export will work before a workflow relies on
|
|
10
|
+
* it (LMCP-K3), mirroring lib/doctor/sidecar-providers.mjs's pattern of a pure,
|
|
11
|
+
* directly-testable function rather than inline probe logic in the CLI
|
|
12
|
+
* entrypoint or the consistency watcher.
|
|
13
|
+
*
|
|
14
|
+
* branding: 'plain' isolates the pandoc/typst/libreoffice probes from bundled
|
|
15
|
+
* distribution-template presence (construct-pdf.typ, construct-web.html), which
|
|
16
|
+
* is a separate, already-surfaced doctor concern. pptxgenjs is probed with the
|
|
17
|
+
* default 'construct' branding because detect() only selects the pptxgenjs
|
|
18
|
+
* engine (vs. pandoc's native pptx writer) outside of 'plain' branding.
|
|
19
|
+
*/
|
|
20
|
+
import { detect, installHint } from '../document-export.mjs';
|
|
21
|
+
|
|
22
|
+
export const EXPORT_ENGINES = [
|
|
23
|
+
{
|
|
24
|
+
id: 'pandoc',
|
|
25
|
+
format: 'html',
|
|
26
|
+
branding: 'plain',
|
|
27
|
+
owningWorkflow: 'document-export (pdf, docx, doc, deck, html, rtf, odt, epub, tex, txt formats)',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'typst',
|
|
31
|
+
format: 'pdf',
|
|
32
|
+
branding: 'plain',
|
|
33
|
+
owningWorkflow: 'document-export (pdf format, pandoc --pdf-engine)',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'libreoffice',
|
|
37
|
+
format: 'doc',
|
|
38
|
+
branding: 'plain',
|
|
39
|
+
owningWorkflow: 'document-export (doc format, docx-to-doc down-conversion)',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'pptxgenjs',
|
|
43
|
+
format: 'pptx',
|
|
44
|
+
branding: 'construct',
|
|
45
|
+
owningWorkflow: 'document-export (pptx format)',
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* checkEngineHealthForDoctor(engineId, opts) — one export engine's doctor-shaped finding.
|
|
51
|
+
*
|
|
52
|
+
* Absence is reported as an optional (non-fatal) finding carrying the same
|
|
53
|
+
* install hint `detect()` returns to export preflight, so the message a user
|
|
54
|
+
* sees in `construct doctor` and the message export preflight refuses with
|
|
55
|
+
* are the same string.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} engineId 'pandoc' | 'typst' | 'libreoffice' | 'pptxgenjs'
|
|
58
|
+
* @param {object} [opts]
|
|
59
|
+
* @param {NodeJS.ProcessEnv} [opts.env] injectable PATH/env for tests
|
|
60
|
+
* @param {Function} [opts.detectFn] injectable seam for lib/document-export.mjs detect()
|
|
61
|
+
* @returns {{ ok: boolean, installed: boolean, label: string, optional: boolean }}
|
|
62
|
+
*/
|
|
63
|
+
export function checkEngineHealthForDoctor(engineId, opts = {}) {
|
|
64
|
+
const probe = EXPORT_ENGINES.find((p) => p.id === engineId);
|
|
65
|
+
if (!probe) throw new Error(`Unknown export engine: ${engineId}`);
|
|
66
|
+
|
|
67
|
+
const { env = process.env, detectFn = detect } = opts;
|
|
68
|
+
const result = detectFn(probe.format, env, { branding: probe.branding });
|
|
69
|
+
const installed = !result.missing.includes(engineId);
|
|
70
|
+
|
|
71
|
+
if (installed) {
|
|
72
|
+
const status = result.binaries?.find((b) => b.name === engineId);
|
|
73
|
+
const version = status?.version ? ` ${status.version}` : '';
|
|
74
|
+
return { ok: true, installed: true, label: `Export engine '${engineId}'${version} — ${probe.owningWorkflow}`, optional: true };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
ok: true,
|
|
79
|
+
installed: false,
|
|
80
|
+
label: `Export engine '${engineId}' not installed — ${probe.owningWorkflow} degrades. ${installHint(engineId)}`,
|
|
81
|
+
optional: true,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* checkEngineHealthAllForDoctor(opts) — pandoc + typst + libreoffice + pptxgenjs findings.
|
|
87
|
+
*
|
|
88
|
+
* @param {object} [opts] forwarded per-engine to checkEngineHealthForDoctor
|
|
89
|
+
* @returns {Array<{ ok: boolean, installed: boolean, label: string, optional: boolean }>}
|
|
90
|
+
*/
|
|
91
|
+
export function checkEngineHealthAllForDoctor(opts = {}) {
|
|
92
|
+
return EXPORT_ENGINES.map((p) => checkEngineHealthForDoctor(p.id, opts));
|
|
93
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/doctor/graph-validate.mjs — doctor check that runs `graph validate` (LMCP-C7).
|
|
3
|
+
*
|
|
4
|
+
* Wraps lib/graph/validate.mjs's validateGraph() into the doctor check shape
|
|
5
|
+
* ({ ok, label, warning, errors, warnings }) used across lib/doctor/*.mjs
|
|
6
|
+
* (see project-adapters.mjs for the same contract). Read-only: this module
|
|
7
|
+
* never writes to the graph store or the runtime-evidence store — it only
|
|
8
|
+
* classifies what validateGraph already reports, so `construct doctor` and
|
|
9
|
+
* `construct graph validate` can never disagree about whether the graph is
|
|
10
|
+
* clean.
|
|
11
|
+
*
|
|
12
|
+
* A missing graph (no `construct graph build` run yet) is reported as a
|
|
13
|
+
* warning, not a hard failure — the graph is an optional living-truth layer
|
|
14
|
+
* that a fresh project has not built yet, not a broken install.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { validateGraph } from '../graph/validate.mjs';
|
|
18
|
+
|
|
19
|
+
export function checkGraphValidateForDoctor({ rootDir, strict = false, deploymentMode } = {}) {
|
|
20
|
+
const result = validateGraph(rootDir, { strict, deploymentMode });
|
|
21
|
+
const noGraph = result.errors.some((e) => e.startsWith('no graph found'));
|
|
22
|
+
|
|
23
|
+
if (noGraph) {
|
|
24
|
+
return {
|
|
25
|
+
ok: true,
|
|
26
|
+
warning: true,
|
|
27
|
+
label: 'Living graph: not built yet (run `construct graph build`)',
|
|
28
|
+
errors: [],
|
|
29
|
+
warnings: [],
|
|
30
|
+
result,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const ok = result.valid;
|
|
35
|
+
const label = ok
|
|
36
|
+
? `Living graph valid (${result.warnings.length} warning${result.warnings.length === 1 ? '' : 's'})`
|
|
37
|
+
: `Living graph invalid: ${result.errors.length} error${result.errors.length === 1 ? '' : 's'} (run \`construct graph validate\`)`;
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
ok,
|
|
41
|
+
warning: false,
|
|
42
|
+
label,
|
|
43
|
+
errors: result.errors,
|
|
44
|
+
warnings: result.warnings,
|
|
45
|
+
result,
|
|
46
|
+
};
|
|
47
|
+
}
|
package/lib/doctor/index.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import { writeFileSync, existsSync, readFileSync, unlinkSync, mkdirSync } from '
|
|
|
15
15
|
import { join } from 'node:path';
|
|
16
16
|
|
|
17
17
|
import { record } from './audit.mjs';
|
|
18
|
+
import { enableSecretAuditTrail } from '../providers/secret-audit-wiring.mjs';
|
|
18
19
|
import * as disk from './watchers/disk.mjs';
|
|
19
20
|
import * as cost from './watchers/cost.mjs';
|
|
20
21
|
import * as processPressure from './watchers/process-pressure.mjs';
|
|
@@ -25,15 +26,28 @@ import * as consistency from './watchers/consistency.mjs';
|
|
|
25
26
|
import * as mcpProtocol from './watchers/mcp-protocol.mjs';
|
|
26
27
|
import * as cxBudget from './watchers/cx-budget.mjs';
|
|
27
28
|
import * as graphStaleness from './watchers/graph-staleness.mjs';
|
|
29
|
+
import * as providerBreaker from './watchers/provider-breaker.mjs';
|
|
30
|
+
import * as oracleLiveness from './watchers/oracle-liveness.mjs';
|
|
31
|
+
import * as orchestrationRuns from './watchers/orchestration-runs.mjs';
|
|
28
32
|
import { stateDir } from '../config/xdg.mjs';
|
|
33
|
+
import { isMainModule } from '../roots.mjs';
|
|
29
34
|
|
|
30
35
|
const STATE_PATH = join(stateDir(), 'doctor.json');
|
|
31
|
-
const WATCHERS = [disk, cost, processPressure, serviceHealth, bdWatch, handoffs, consistency, mcpProtocol, cxBudget, graphStaleness];
|
|
36
|
+
const WATCHERS = [disk, cost, processPressure, serviceHealth, bdWatch, handoffs, consistency, mcpProtocol, cxBudget, graphStaleness, providerBreaker, oracleLiveness, orchestrationRuns];
|
|
32
37
|
|
|
33
38
|
let running = false;
|
|
34
39
|
let timers = [];
|
|
35
40
|
let lastTickAt = {};
|
|
36
41
|
|
|
42
|
+
// Most watchers read only global/XDG state and never needed the project
|
|
43
|
+
// directory. oracle-liveness is the first that does (listPending(projectDir))
|
|
44
|
+
// — threaded here rather than each watcher reaching for process.cwd() itself,
|
|
45
|
+
// so a test can drive tick() against a fixture project without touching the
|
|
46
|
+
// process-wide cwd. start() defaults to process.cwd() because the daemon is
|
|
47
|
+
// always spawned with the project root as its own cwd (lib/service-manager.mjs).
|
|
48
|
+
|
|
49
|
+
let currentProjectDir = process.cwd();
|
|
50
|
+
|
|
37
51
|
function writeState(state) {
|
|
38
52
|
const dir = stateDir();
|
|
39
53
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
@@ -67,7 +81,7 @@ async function runWatcher(watcher) {
|
|
|
67
81
|
if (!running) return;
|
|
68
82
|
const startedAt = Date.now();
|
|
69
83
|
try {
|
|
70
|
-
const result = await watcher.tick();
|
|
84
|
+
const result = await watcher.tick({ projectDir: currentProjectDir });
|
|
71
85
|
lastTickAt[watcher.name] = startedAt;
|
|
72
86
|
const tookMs = Date.now() - startedAt;
|
|
73
87
|
if (process.env.CONSTRUCT_DOCTOR_VERBOSE === '1') {
|
|
@@ -86,10 +100,11 @@ async function runWatcher(watcher) {
|
|
|
86
100
|
}
|
|
87
101
|
}
|
|
88
102
|
|
|
89
|
-
export async function start() {
|
|
103
|
+
export async function start({ projectDir = process.cwd() } = {}) {
|
|
90
104
|
if (running) return;
|
|
91
105
|
running = true;
|
|
92
106
|
lastTickAt = {};
|
|
107
|
+
currentProjectDir = projectDir;
|
|
93
108
|
|
|
94
109
|
writeState({ pid: process.pid, startedAt: Date.now(), watchers: WATCHERS.map((w) => w.name) });
|
|
95
110
|
record({
|
|
@@ -137,8 +152,12 @@ export function statusSnapshot() {
|
|
|
137
152
|
}
|
|
138
153
|
|
|
139
154
|
const isMain = (() => {
|
|
140
|
-
try { return import.meta.url
|
|
155
|
+
try { return isMainModule(import.meta.url); } catch { return false; }
|
|
141
156
|
})();
|
|
142
157
|
if (isMain) {
|
|
158
|
+
// A long-lived daemon in its own process: wire the audit sink at the entry so any
|
|
159
|
+
// credential resolution a watcher performs is recorded on the shared trail rather
|
|
160
|
+
// than escaping it, matching the CLI entrypoint's own wiring.
|
|
161
|
+
enableSecretAuditTrail();
|
|
143
162
|
await start();
|
|
144
163
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/doctor/sidecar-providers.mjs — doctor checks for governed ingestion sidecars.
|
|
3
|
+
*
|
|
4
|
+
* docling and whisper are declared as `ingestion-provider` manifests
|
|
5
|
+
* (lib/extensions/manifests/docling.manifest.json, whisper.manifest.json) with
|
|
6
|
+
* an install probe, a health check, and a degradation chain. This module turns
|
|
7
|
+
* those probes into doctor-shaped findings so `construct doctor` reports
|
|
8
|
+
* install+health accurately whether or not the sidecar is present — mirroring
|
|
9
|
+
* lib/doctor/project-adapters.mjs's pattern of a pure, directly-testable
|
|
10
|
+
* function rather than inline logic in the CLI entrypoint.
|
|
11
|
+
*/
|
|
12
|
+
import { probeHealth } from '../ingest/sidecar-providers.mjs';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* checkSidecarProviderForDoctor(id, opts) — one provider's doctor-shaped finding.
|
|
16
|
+
*
|
|
17
|
+
* Absence is reported as an optional (non-fatal) finding with an actionable
|
|
18
|
+
* label; presence with a failing health check is reported as a non-optional
|
|
19
|
+
* finding since an installed-but-broken sidecar is a real defect, not an
|
|
20
|
+
* expected absence.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} id 'docling' | 'whisper'
|
|
23
|
+
* @param {object} [opts] forwarded to lib/ingest/sidecar-providers.mjs probeHealth (injectable seams for fake probes)
|
|
24
|
+
* @returns {{ ok: boolean, label: string, optional: boolean }}
|
|
25
|
+
*/
|
|
26
|
+
export function checkSidecarProviderForDoctor(id, opts = {}) {
|
|
27
|
+
const health = probeHealth(id, opts);
|
|
28
|
+
|
|
29
|
+
if (!health.ok) {
|
|
30
|
+
return { ok: false, label: `Ingestion provider '${id}': check failed (${health.detail})`, optional: false };
|
|
31
|
+
}
|
|
32
|
+
if (!health.installProbe.installed) {
|
|
33
|
+
return {
|
|
34
|
+
ok: true,
|
|
35
|
+
label: `Ingestion provider '${id}': not installed (${health.detail}) — ingest degrades per its declared fallback chain`,
|
|
36
|
+
optional: true,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (!health.healthy) {
|
|
40
|
+
return { ok: false, label: `Ingestion provider '${id}': installed but unhealthy (${health.detail})`, optional: false };
|
|
41
|
+
}
|
|
42
|
+
return { ok: true, label: `Ingestion provider '${id}': ${health.detail}`, optional: true };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* checkSidecarProvidersForDoctor(opts) — docling + whisper doctor findings.
|
|
47
|
+
*
|
|
48
|
+
* @param {object} [opts] forwarded per-provider to checkSidecarProviderForDoctor
|
|
49
|
+
* @returns {Array<{ ok: boolean, label: string, optional: boolean }>}
|
|
50
|
+
*/
|
|
51
|
+
export function checkSidecarProvidersForDoctor(opts = {}) {
|
|
52
|
+
return [
|
|
53
|
+
checkSidecarProviderForDoctor('docling', opts),
|
|
54
|
+
checkSidecarProviderForDoctor('whisper', opts),
|
|
55
|
+
];
|
|
56
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/doctor/watchers/consistency.mjs — cross-surface drift watcher.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Drift checks bundled at one cadence, including:
|
|
5
5
|
* 1. Hook manifest ↔ files: every entry in platforms/claude/settings.template.json
|
|
6
6
|
* points to a real .mjs file in lib/hooks/ that parses.
|
|
7
7
|
* 2. MCP registration ↔ impl: every `name === '<tool>'` branch in the MCP
|
|
@@ -13,6 +13,16 @@
|
|
|
13
13
|
* registry.agents[].promptFile exists on disk.
|
|
14
14
|
* 5. Contracts ↔ schemas: specialists/org passes validateContractsFile
|
|
15
15
|
* (cross-file refs, producer/consumer names, schema paths).
|
|
16
|
+
* 6. Postcondition coverage (construct-rf26.12): reports the executable-
|
|
17
|
+
* vs-advisory split across specialists/org/contracts/*.json postconditions
|
|
18
|
+
* (lib/contracts/coverage.mjs); a nonzero unclassified count is drift.
|
|
19
|
+
* 7. Host surfaces: adapter config present for installed AI hosts.
|
|
20
|
+
* 8. Export engine health: pandoc/typst/libreoffice/pptxgenjs presence via
|
|
21
|
+
* lib/doctor/engine-health.mjs (LMCP-K3).
|
|
22
|
+
* 9. Embedding model health: active embedding model, and whether the default
|
|
23
|
+
* local model's @huggingface/transformers dependency resolves, via
|
|
24
|
+
* lib/doctor/embedding-health.mjs (LMCP-K4).
|
|
25
|
+
* 10. Credential parity: see lib/doctor/watchers/credential-parity.mjs.
|
|
16
26
|
*
|
|
17
27
|
* Each violation produces an audit record. Blocking severity escalates to a
|
|
18
28
|
* role intake; advisory severity records only. Tick is cheap — reads JSON +
|
|
@@ -27,6 +37,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
27
37
|
|
|
28
38
|
import { record } from '../audit.mjs';
|
|
29
39
|
import { escalate } from '../escalate.mjs';
|
|
40
|
+
import { checkEngineHealthAllForDoctor } from '../engine-health.mjs';
|
|
41
|
+
import { checkEmbeddingModelForDoctor } from '../embedding-health.mjs';
|
|
30
42
|
|
|
31
43
|
export const name = 'consistency';
|
|
32
44
|
export const intervalMs = 15 * 60 * 1000;
|
|
@@ -87,9 +99,18 @@ export async function runAllChecks({ repoRoot = REPO_ROOT } = {}) {
|
|
|
87
99
|
const contracts = await checkContractsDrift({ repoRoot });
|
|
88
100
|
collect(contracts, findings, passed, 'contracts-drift');
|
|
89
101
|
|
|
102
|
+
const postconditionCoverage = await checkPostconditionCoverage({ repoRoot });
|
|
103
|
+
collect(postconditionCoverage, findings, passed, 'postcondition-coverage');
|
|
104
|
+
|
|
90
105
|
const hosts = checkHostSurfaces({ repoRoot });
|
|
91
106
|
collect(hosts, findings, passed, 'host-surfaces');
|
|
92
107
|
|
|
108
|
+
const engines = checkEngineHealthDrift();
|
|
109
|
+
collect(engines, findings, passed, 'export-engine-health');
|
|
110
|
+
|
|
111
|
+
const embeddingModel = await checkEmbeddingModelDrift();
|
|
112
|
+
collect(embeddingModel, findings, passed, 'embedding-model-health');
|
|
113
|
+
|
|
93
114
|
const { runCredentialParityChecks } = await import('./credential-parity.mjs');
|
|
94
115
|
const credentials = await runCredentialParityChecks({ rootDir: repoRoot });
|
|
95
116
|
collect(credentials, findings, passed, 'credential-parity');
|
|
@@ -422,6 +443,32 @@ async function checkContractsDrift({ repoRoot }) {
|
|
|
422
443
|
return { summary: `contracts: ${violations.length} drift`, violations };
|
|
423
444
|
}
|
|
424
445
|
|
|
446
|
+
// Reports executable-vs-advisory postcondition coverage (construct-rf26.12).
|
|
447
|
+
// Informational only — never fails on a low percentage, since advisory
|
|
448
|
+
// postconditions are a legitimate, deliberate classification, not drift. A
|
|
449
|
+
// nonzero `unclassified` count IS drift: a legacy bare-string postcondition
|
|
450
|
+
// that slipped past the rf26.12 migration.
|
|
451
|
+
|
|
452
|
+
async function checkPostconditionCoverage({ repoRoot }) {
|
|
453
|
+
try {
|
|
454
|
+
const { computePostconditionCoverage, formatCoverageSummary } = await import('../../contracts/coverage.mjs');
|
|
455
|
+
const coverage = computePostconditionCoverage({ repoRoot });
|
|
456
|
+
const violations = coverage.unclassified > 0
|
|
457
|
+
? [{
|
|
458
|
+
category: 'postcondition-coverage',
|
|
459
|
+
severity: 'warning',
|
|
460
|
+
summary: `${coverage.unclassified} postcondition(s) across contracts have no postconditionType (executable|advisory) classification`,
|
|
461
|
+
}]
|
|
462
|
+
: [];
|
|
463
|
+
return { summary: formatCoverageSummary(coverage), violations };
|
|
464
|
+
} catch (err) {
|
|
465
|
+
return {
|
|
466
|
+
summary: 'postcondition coverage unavailable',
|
|
467
|
+
violations: [{ category: 'postcondition-coverage', severity: 'warning', summary: `postcondition coverage check failed: ${err.message}` }],
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
425
472
|
// ── Check 6: host surfaces ─────────────────────────────────────────────────
|
|
426
473
|
|
|
427
474
|
function getVSCodeSettingsDir(homeDir) {
|
|
@@ -463,3 +510,48 @@ function checkHostSurfaces({ repoRoot: _repoRoot }) {
|
|
|
463
510
|
|
|
464
511
|
return { summary: `host-surfaces: ${surfaces.length - absent.length}/${surfaces.length} present`, violations };
|
|
465
512
|
}
|
|
513
|
+
|
|
514
|
+
// ── Check 7: export engine health ──────────────────────────────────────────
|
|
515
|
+
//
|
|
516
|
+
// pandoc/typst/libreoffice/pptxgenjs have no preflight today; failures surface
|
|
517
|
+
// mid-export. lib/doctor/engine-health.mjs probes the same detect() export
|
|
518
|
+
// preflight uses, so absence here and an export refusal carry the same
|
|
519
|
+
// install hint (LMCP-K3).
|
|
520
|
+
|
|
521
|
+
function checkEngineHealthDrift() {
|
|
522
|
+
const violations = [];
|
|
523
|
+
const findingsList = checkEngineHealthAllForDoctor();
|
|
524
|
+
const absent = findingsList.filter((f) => !f.installed);
|
|
525
|
+
|
|
526
|
+
for (const f of absent) {
|
|
527
|
+
violations.push({
|
|
528
|
+
category: 'export-engine-health',
|
|
529
|
+
severity: 'warning',
|
|
530
|
+
tier: 'actionable',
|
|
531
|
+
summary: f.label,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return {
|
|
536
|
+
summary: `export-engine-health: ${findingsList.length - absent.length}/${findingsList.length} engines present`,
|
|
537
|
+
violations,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// ── Check 8: embedding model health ────────────────────────────────────────
|
|
542
|
+
//
|
|
543
|
+
// `local` (@huggingface/transformers) is the embedding engine's default and
|
|
544
|
+
// degrades silently to hashing on failure; lib/doctor/embedding-health.mjs
|
|
545
|
+
// surfaces that degradation so it is visible before retrieval quality drops
|
|
546
|
+
// unexplained (LMCP-K4).
|
|
547
|
+
|
|
548
|
+
async function checkEmbeddingModelDrift() {
|
|
549
|
+
const finding = await checkEmbeddingModelForDoctor();
|
|
550
|
+
if (finding.degraded) {
|
|
551
|
+
return {
|
|
552
|
+
summary: finding.label,
|
|
553
|
+
violations: [{ category: 'embedding-model-health', severity: 'warning', tier: 'actionable', summary: finding.label }],
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
return { summary: finding.label, violations: [] };
|
|
557
|
+
}
|