@geraldmaron/construct 1.4.2 → 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 +1027 -64
- 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 +6 -29
- 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 +89 -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 +18 -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/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 +50 -9
- 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-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +19 -7
- 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/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- 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-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 +154 -1411
- 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-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +1 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +165 -25
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- 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 +0 -8
- 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 +31 -7
- 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/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 +446 -39
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +3 -2
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +528 -89
- package/lib/orchestration-policy.mjs +67 -1111
- 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/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 +6 -0
- package/lib/providers/secret-resolver.mjs +240 -23
- 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/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 +8 -3
- 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/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/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 +38 -19
- 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 +77 -5
- 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 +16 -8
- package/personas/construct.md +12 -3
- package/registry/capabilities.json +143 -36
- package/rules/common/comments.md +1 -0
- package/schemas/project-config.schema.json +0 -12
- package/schemas/unified-registry.schema.json +2 -4
- package/scripts/sync-specialists.mjs +284 -81
- 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 +40 -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 +25 -30
- 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/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
|
@@ -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
|
+
}
|
package/lib/doctor/diagnosis.mjs
CHANGED
|
@@ -17,4 +17,93 @@ export const DOCTOR_STATES = {
|
|
|
17
17
|
'entrypoint-missing': 'A binary or script the runtime expects (construct-mcp, construct) is not found on PATH.',
|
|
18
18
|
'healthy': 'The check passed with no issues detected.',
|
|
19
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.',
|
|
20
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
|
@@ -26,15 +26,28 @@ import * as consistency from './watchers/consistency.mjs';
|
|
|
26
26
|
import * as mcpProtocol from './watchers/mcp-protocol.mjs';
|
|
27
27
|
import * as cxBudget from './watchers/cx-budget.mjs';
|
|
28
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';
|
|
29
32
|
import { stateDir } from '../config/xdg.mjs';
|
|
33
|
+
import { isMainModule } from '../roots.mjs';
|
|
30
34
|
|
|
31
35
|
const STATE_PATH = join(stateDir(), 'doctor.json');
|
|
32
|
-
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];
|
|
33
37
|
|
|
34
38
|
let running = false;
|
|
35
39
|
let timers = [];
|
|
36
40
|
let lastTickAt = {};
|
|
37
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
|
+
|
|
38
51
|
function writeState(state) {
|
|
39
52
|
const dir = stateDir();
|
|
40
53
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
@@ -68,7 +81,7 @@ async function runWatcher(watcher) {
|
|
|
68
81
|
if (!running) return;
|
|
69
82
|
const startedAt = Date.now();
|
|
70
83
|
try {
|
|
71
|
-
const result = await watcher.tick();
|
|
84
|
+
const result = await watcher.tick({ projectDir: currentProjectDir });
|
|
72
85
|
lastTickAt[watcher.name] = startedAt;
|
|
73
86
|
const tookMs = Date.now() - startedAt;
|
|
74
87
|
if (process.env.CONSTRUCT_DOCTOR_VERBOSE === '1') {
|
|
@@ -87,10 +100,11 @@ async function runWatcher(watcher) {
|
|
|
87
100
|
}
|
|
88
101
|
}
|
|
89
102
|
|
|
90
|
-
export async function start() {
|
|
103
|
+
export async function start({ projectDir = process.cwd() } = {}) {
|
|
91
104
|
if (running) return;
|
|
92
105
|
running = true;
|
|
93
106
|
lastTickAt = {};
|
|
107
|
+
currentProjectDir = projectDir;
|
|
94
108
|
|
|
95
109
|
writeState({ pid: process.pid, startedAt: Date.now(), watchers: WATCHERS.map((w) => w.name) });
|
|
96
110
|
record({
|
|
@@ -138,7 +152,7 @@ export function statusSnapshot() {
|
|
|
138
152
|
}
|
|
139
153
|
|
|
140
154
|
const isMain = (() => {
|
|
141
|
-
try { return import.meta.url
|
|
155
|
+
try { return isMainModule(import.meta.url); } catch { return false; }
|
|
142
156
|
})();
|
|
143
157
|
if (isMain) {
|
|
144
158
|
// A long-lived daemon in its own process: wire the audit sink at the entry so any
|
|
@@ -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
|
+
}
|
|
@@ -65,7 +65,7 @@ export async function tick() {
|
|
|
65
65
|
kind: 'action',
|
|
66
66
|
watcher: name,
|
|
67
67
|
action: 'emergency-reclaim',
|
|
68
|
-
target: '
|
|
68
|
+
target: 'state-root:traces|state-root:runtime/worker',
|
|
69
69
|
summary: `emergency reclaim removed ${emergencyResult.removed.length} file(s), freed ${emergencyResult.bytesFreed} bytes`,
|
|
70
70
|
context: emergencyResult,
|
|
71
71
|
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* lib/doctor/watchers/graph-staleness.mjs — dependency graph freshness watcher.
|
|
3
3
|
*
|
|
4
4
|
* Ticks every 30 minutes. When registry/contracts/workflow seeds changed since
|
|
5
|
-
* the last `construct
|
|
5
|
+
* the last `construct matrix build`, records an audit entry so doctor surfaces
|
|
6
6
|
* stale-graph drift without waiting for the next Oracle tick.
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -35,10 +35,27 @@ function readJson(file) {
|
|
|
35
35
|
|
|
36
36
|
export function collectMcpServers({ home = homedir() } = {}) {
|
|
37
37
|
const servers = [];
|
|
38
|
+
const seenClaudeIds = new Set();
|
|
39
|
+
|
|
40
|
+
// Claude Code's user-scope MCP servers live in ~/.claude.json's top-level
|
|
41
|
+
// `mcpServers`, not settings.json (settings.json carries hooks/permissions
|
|
42
|
+
// only — construct-ranh). ~/.claude/settings.json is still checked so a
|
|
43
|
+
// legacy pre-migration install's (inert, but still worth flagging) entries
|
|
44
|
+
// are not silently dropped from the probe sweep.
|
|
45
|
+
|
|
46
|
+
const claudeUserConfigPath = join(home, '.claude.json');
|
|
47
|
+
if (existsSync(claudeUserConfigPath)) {
|
|
48
|
+
const cfg = readJson(claudeUserConfigPath);
|
|
49
|
+
for (const [id, entry] of Object.entries(cfg?.mcpServers ?? {})) {
|
|
50
|
+
servers.push({ host: 'claude', id, entry });
|
|
51
|
+
seenClaudeIds.add(id);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
38
54
|
const claudePath = join(home, '.claude', 'settings.json');
|
|
39
55
|
if (existsSync(claudePath)) {
|
|
40
56
|
const cfg = readJson(claudePath);
|
|
41
57
|
for (const [id, entry] of Object.entries(cfg?.mcpServers ?? {})) {
|
|
58
|
+
if (seenClaudeIds.has(id)) continue;
|
|
42
59
|
servers.push({ host: 'claude', id, entry });
|
|
43
60
|
}
|
|
44
61
|
}
|