@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/host-disposition.mjs
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/host-disposition.mjs — single source of truth for the disposition of
|
|
3
|
-
* every artifact Construct creates in a host project (ADR-0027 §1
|
|
3
|
+
* every artifact Construct creates in a host project (ADR-0027 §1, extended
|
|
4
|
+
* by ADR-0066 §"Decision" for the machine-scoped heavy-state split).
|
|
4
5
|
*
|
|
5
6
|
* Scope model (see docs/guides/concepts/project-scopes.md):
|
|
6
|
-
* templates/, docs/
|
|
7
|
-
* .construct/
|
|
8
|
-
* .cx/
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* templates/, docs/ — package/repo durable sources (committed)
|
|
8
|
+
* .construct/ — per-project launcher (ignored)
|
|
9
|
+
* .cx/ — per-project config-layer + small runtime
|
|
10
|
+
* markers: context.md/json, workflow.json,
|
|
11
|
+
* intake, oracle, observations, knowledge,
|
|
12
|
+
* graph, demos (ignored)
|
|
13
|
+
* ~/.construct/projects/<key>/ — per-project machine-scoped heavy state:
|
|
14
|
+
* traces, orchestration runs, worker logs,
|
|
15
|
+
* the LanceDB vector index, docling/whisper
|
|
16
|
+
* bootstrap venvs (ADR-0066)
|
|
17
|
+
* ~/.construct/ — user credentials and doctor state
|
|
18
|
+
* ~/.cx/ — user-scope telemetry fallback
|
|
11
19
|
*
|
|
12
20
|
* `IGNORED_PATTERNS` enumerates machine-specific, sync-regenerated artifacts
|
|
13
21
|
* that must never be committed; the init `.gitignore` writer and the
|
|
14
22
|
* `gitignore-coverage` reconciliation both read from here, and uninstall /
|
|
15
23
|
* adapter-prune consume `ADAPTER_DIRS`. Keep this 1:1 with the host-relevant
|
|
16
24
|
* subset of Construct's own repo .gitignore so forward-fix and backward-repair
|
|
17
|
-
* never diverge.
|
|
25
|
+
* never diverge. `.cx/` covers both the config-layer files and the small
|
|
26
|
+
* runtime markers that remain project-local post-ADR-0066 — the heavy
|
|
27
|
+
* categories that moved to `~/.construct/projects/<key>/` are resolved
|
|
28
|
+
* through `lib/state-root.mjs`, not listed here, since they were never
|
|
29
|
+
* project-relative paths to gitignore in the first place.
|
|
18
30
|
*/
|
|
19
31
|
|
|
20
32
|
import fs from 'node:fs';
|
|
@@ -41,13 +53,22 @@ export const IGNORED_PATTERNS = [
|
|
|
41
53
|
'.construct/',
|
|
42
54
|
'.mcp.json',
|
|
43
55
|
'.github/prompts/',
|
|
44
|
-
'.github/
|
|
56
|
+
'.github/agents/',
|
|
45
57
|
'config.env',
|
|
46
58
|
'embed.yaml',
|
|
47
59
|
'plan.md',
|
|
48
60
|
'inbox/',
|
|
49
61
|
];
|
|
50
62
|
|
|
63
|
+
// User-owned files Construct mutates only via replaceManagedBlock (ADR-0027 §2).
|
|
64
|
+
// These are durable repo source, not ignored cache — they must NOT appear in
|
|
65
|
+
// IGNORED_PATTERNS, but they still need an explicit disposition so consumers
|
|
66
|
+
// know not to gitignore or delete them on uninstall.
|
|
67
|
+
|
|
68
|
+
export const MANAGED_PATTERNS = [
|
|
69
|
+
'.github/copilot-instructions.md',
|
|
70
|
+
];
|
|
71
|
+
|
|
51
72
|
// A pattern counts as already-ignored when an existing line matches it exactly,
|
|
52
73
|
// matches its bare/slashed form, or is a broader `*` / `**` catch-all.
|
|
53
74
|
|
package/lib/identity.mjs
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/identity.mjs — actor and service identity for the control plane.
|
|
3
|
+
*
|
|
4
|
+
* Resolves human vs service identity from request headers (X-Construct-Actor-Id,
|
|
5
|
+
* -Tenant-Id, -Session-Id) or env fallback, and validates it per deployment mode
|
|
6
|
+
* (team/enterprise reject implicit-solo identity). Feeds policy, approval, and audit.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { getDeploymentMode } from './deployment-mode.mjs';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
|
|
12
|
+
export class IdentityError extends Error {
|
|
13
|
+
constructor(message) { super(message); this.name = 'IdentityError'; }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function serviceIdentity({ serviceId, deploymentMode, role, source } = {}) {
|
|
17
|
+
return {
|
|
18
|
+
type: 'service',
|
|
19
|
+
serviceId: serviceId || 'construct-mcp',
|
|
20
|
+
deploymentMode: deploymentMode || 'solo',
|
|
21
|
+
pid: process.pid,
|
|
22
|
+
role: role || null,
|
|
23
|
+
source: source || 'implicit-solo',
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function humanIdentity({ userId, tenantId, sessionId, role, source } = {}) {
|
|
28
|
+
return {
|
|
29
|
+
type: 'human',
|
|
30
|
+
userId: userId || null,
|
|
31
|
+
tenantId: tenantId || null,
|
|
32
|
+
sessionId: sessionId || null,
|
|
33
|
+
role: role || null,
|
|
34
|
+
source: source || 'headers',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function resolveIdentity(params = {}, { env = process.env, cwd } = {}) {
|
|
39
|
+
const mode = getDeploymentMode(env, { cwd });
|
|
40
|
+
const meta = params?._meta || {};
|
|
41
|
+
|
|
42
|
+
const actorId = meta['X-Construct-Actor-Id'] || '';
|
|
43
|
+
const tenantId = meta['X-Construct-Tenant-Id'] || '';
|
|
44
|
+
const sessionId = meta['X-Construct-Session-Id'] || '';
|
|
45
|
+
const hasHeaders = Boolean(actorId);
|
|
46
|
+
|
|
47
|
+
if ((mode === 'team' || mode === 'enterprise') && !hasHeaders) {
|
|
48
|
+
const role = env.CONSTRUCT_ROLE;
|
|
49
|
+
if (role) {
|
|
50
|
+
return serviceIdentity({ serviceId: `role:${role}`, deploymentMode: mode, role, source: 'env-fallback' });
|
|
51
|
+
}
|
|
52
|
+
throw new IdentityError(`Missing actor identity in ${mode} mode. Set X-Construct-Actor-Id header or CONSTRUCT_ROLE env var.`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (hasHeaders) {
|
|
56
|
+
const isHuman = actorId.includes('@');
|
|
57
|
+
if (isHuman) {
|
|
58
|
+
return humanIdentity({ userId: actorId, tenantId: tenantId || null, sessionId: sessionId || null, role: env.CONSTRUCT_ROLE || null, source: 'headers' });
|
|
59
|
+
}
|
|
60
|
+
return serviceIdentity({ serviceId: actorId, deploymentMode: mode, role: env.CONSTRUCT_ROLE || null, source: 'headers' });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return serviceIdentity({ serviceId: `local-${os.hostname()}`, deploymentMode: mode, role: env.CONSTRUCT_ROLE || null, source: 'implicit-solo' });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function identityRole(identity) {
|
|
67
|
+
return identity?.role || null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function validateIdentity(identity, mode) {
|
|
71
|
+
if (!identity) return 'Identity is required';
|
|
72
|
+
if (!identity.type) return 'Identity type is required';
|
|
73
|
+
if (identity.type === 'human' && !identity.userId) return 'Human identity requires userId';
|
|
74
|
+
if (identity.type === 'service' && !identity.serviceId) return 'Service identity requires serviceId';
|
|
75
|
+
if ((mode === 'team' || mode === 'enterprise') && identity.source === 'implicit-solo') {
|
|
76
|
+
return `Implicit identity not allowed in ${mode} mode`;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function identityToRecord(identity) {
|
|
82
|
+
if (!identity) return {};
|
|
83
|
+
return {
|
|
84
|
+
type: identity.type,
|
|
85
|
+
userId: identity.userId || null,
|
|
86
|
+
serviceId: identity.serviceId || null,
|
|
87
|
+
tenantId: identity.tenantId || null,
|
|
88
|
+
sessionId: identity.sessionId || null,
|
|
89
|
+
role: identity.role || null,
|
|
90
|
+
deploymentMode: identity.deploymentMode || null,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/ingest/degraded-extract.mjs — manifest-governed extraction with a declared fallback chain.
|
|
3
|
+
*
|
|
4
|
+
* lib/document-ingest.mjs already bounds a docling attempt with a timeout and
|
|
5
|
+
* falls back to the node-native extractor on any failure; extractWithChain adds
|
|
6
|
+
* the governance piece LMCP-K2 requires — the fallback path comes from the
|
|
7
|
+
* docling ingestion-provider manifest's `degradation.chain` (docling ->
|
|
8
|
+
* node-native -> refuse) rather than being implicit in the calling code, and
|
|
9
|
+
* every result is stamped `degraded` so a caller can tell "docling ran" from
|
|
10
|
+
* "docling was unavailable and a lower-fidelity extractor stood in." A file
|
|
11
|
+
* type outside the node-native backend's coverage (xlsx/pptx/odt/ods) walks the
|
|
12
|
+
* chain straight to `refuse` and throws, per the manifest's terminal step.
|
|
13
|
+
*/
|
|
14
|
+
import { degradationChain, probeInstall } from './sidecar-providers.mjs';
|
|
15
|
+
|
|
16
|
+
const NODE_NATIVE_EXTS = new Set(['.pdf', '.docx']);
|
|
17
|
+
|
|
18
|
+
function degradedResult(extracted, { chainStep, reason }) {
|
|
19
|
+
return {
|
|
20
|
+
...extracted,
|
|
21
|
+
degraded: true,
|
|
22
|
+
degradationStep: chainStep,
|
|
23
|
+
droppedInfo: [
|
|
24
|
+
...(extracted.droppedInfo ?? []),
|
|
25
|
+
{
|
|
26
|
+
kind: 'ingestion-provider-degraded',
|
|
27
|
+
count: 1,
|
|
28
|
+
reason,
|
|
29
|
+
recoverable: true,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* extractWithChain(filePath, opts) — run docling extraction governed by the
|
|
37
|
+
* docling manifest's declared degradation chain.
|
|
38
|
+
*
|
|
39
|
+
* @param {object} opts
|
|
40
|
+
* @param {string} opts.extension lowercased file extension (drives node-native eligibility)
|
|
41
|
+
* @param {Function} opts.doclingExtract async (filePath) => extraction result; throws on failure
|
|
42
|
+
* @param {Function} [opts.nodeNativeExtract] async (filePath) => extraction result; throws on failure
|
|
43
|
+
* @param {Function} [opts.installProbeImpl] injectable probeInstall (fake probes for tests)
|
|
44
|
+
* @param {boolean} [opts.skipInstallProbe] when true, always attempts doclingExtract
|
|
45
|
+
* regardless of the install probe (used when the caller already knows
|
|
46
|
+
* docling ran, e.g. contract-test doubles)
|
|
47
|
+
* @returns {Promise<object>} extraction result; `degraded: true` when the chain fell through
|
|
48
|
+
*/
|
|
49
|
+
export async function extractWithChain(filePath, {
|
|
50
|
+
extension,
|
|
51
|
+
doclingExtract,
|
|
52
|
+
nodeNativeExtract = null,
|
|
53
|
+
installProbeImpl = probeInstall,
|
|
54
|
+
skipInstallProbe = false,
|
|
55
|
+
} = {}) {
|
|
56
|
+
const chain = degradationChain('docling');
|
|
57
|
+
if (!chain) {
|
|
58
|
+
throw new Error('docling ingestion-provider manifest is missing or has no degradation chain declared');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const installed = skipInstallProbe || installProbeImpl('docling').installed;
|
|
62
|
+
if (installed) {
|
|
63
|
+
try {
|
|
64
|
+
const extracted = await doclingExtract(filePath);
|
|
65
|
+
return { ...extracted, degraded: false, degradationStep: 'docling' };
|
|
66
|
+
} catch (err) {
|
|
67
|
+
return fallToNodeNative(filePath, { extension, nodeNativeExtract, reason: `docling extraction failed: ${err.message}` });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return fallToNodeNative(filePath, { extension, nodeNativeExtract, reason: 'docling is not installed' });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function fallToNodeNative(filePath, { extension, nodeNativeExtract, reason }) {
|
|
75
|
+
const nodeNativeEligible = NODE_NATIVE_EXTS.has(extension) && typeof nodeNativeExtract === 'function';
|
|
76
|
+
if (!nodeNativeEligible) {
|
|
77
|
+
const err = new Error(
|
|
78
|
+
`${reason}; no node-native fallback covers '${extension}' — refusing per the docling degradation chain. ` +
|
|
79
|
+
'Run `construct provider test docling` for diagnosis, or `construct install --with-docling`.',
|
|
80
|
+
);
|
|
81
|
+
err.code = 'INGESTION_PROVIDER_REFUSED';
|
|
82
|
+
err.chainStep = 'refuse';
|
|
83
|
+
throw err;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const extracted = await nodeNativeExtract(filePath);
|
|
88
|
+
return degradedResult(extracted, { chainStep: 'node-native', reason: `${reason}; used the node-native extractor (${extracted.extractionMethod}).` });
|
|
89
|
+
} catch (err) {
|
|
90
|
+
const refuseErr = new Error(`${reason}; node-native fallback also failed: ${err.message} — refusing.`);
|
|
91
|
+
refuseErr.code = 'INGESTION_PROVIDER_REFUSED';
|
|
92
|
+
refuseErr.chainStep = 'refuse';
|
|
93
|
+
refuseErr.cause = err;
|
|
94
|
+
throw refuseErr;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import fs from 'node:fs';
|
|
16
16
|
import path from 'node:path';
|
|
17
|
+
import { resolveNonNegativeSetting } from '../env-config.mjs';
|
|
17
18
|
|
|
18
19
|
const DEFAULT_TIMEOUT_MS = 600_000;
|
|
19
20
|
|
|
@@ -38,7 +39,7 @@ export async function extractViaDoclingRemote({ filePath, maxChars = null, env =
|
|
|
38
39
|
throw err;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
const budget = timeoutMs ?? (
|
|
42
|
+
const budget = timeoutMs ?? resolveNonNegativeSetting(env, 'CONSTRUCT_DOCLING_TIMEOUT_MS', DEFAULT_TIMEOUT_MS);
|
|
42
43
|
const controller = new AbortController();
|
|
43
44
|
const timer = setTimeout(() => controller.abort(), budget);
|
|
44
45
|
try {
|
|
@@ -21,10 +21,8 @@
|
|
|
21
21
|
* end against a mock provider without a live key.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import { readFileSync
|
|
25
|
-
import { extname
|
|
26
|
-
import { homedir } from 'node:os';
|
|
27
|
-
import { configDir } from '../config/xdg.mjs';
|
|
24
|
+
import { readFileSync } from 'node:fs';
|
|
25
|
+
import { extname } from 'node:path';
|
|
28
26
|
|
|
29
27
|
import {
|
|
30
28
|
AUDIO_VIDEO_EXTS,
|
|
@@ -32,6 +30,7 @@ import {
|
|
|
32
30
|
TRANSCRIPT_EXTS,
|
|
33
31
|
CALENDAR_EXTS,
|
|
34
32
|
} from '../document-extract.mjs';
|
|
33
|
+
import { resolveSecret } from '../providers/secret-resolver.mjs';
|
|
35
34
|
|
|
36
35
|
export const PROVIDER_IMAGE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp']);
|
|
37
36
|
|
|
@@ -67,23 +66,12 @@ function isAnthropic(provider, model) {
|
|
|
67
66
|
return /anthropic|claude/i.test(provider || '') || /claude/i.test(model || '');
|
|
68
67
|
}
|
|
69
68
|
|
|
70
|
-
// Key resolution mirrors the daemon's cheap sources (env, then the two dotenv
|
|
71
|
-
// files) without the shell-rc/1Password walk, which belongs to long-running
|
|
72
|
-
// processes rather than a per-file extraction call. Ambient dotenv discovery
|
|
73
|
-
// only augments the real process env; a caller that injects an explicit env
|
|
74
|
-
// object (embedded callers, tests) is authoritative and hermetic.
|
|
75
|
-
|
|
76
69
|
function resolveKey(varName, env, allowAmbient) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (!existsSync(file)) continue;
|
|
82
|
-
const m = readFileSync(file, 'utf8').match(new RegExp(`^${varName}=["']?(.+?)["']?$`, 'm'));
|
|
83
|
-
if (m && m[1]) return m[1].trim();
|
|
84
|
-
} catch { /* unreadable dotenv is not authoritative */ }
|
|
70
|
+
try {
|
|
71
|
+
return resolveSecret(varName, { env, allowAmbient });
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
85
74
|
}
|
|
86
|
-
return null;
|
|
87
75
|
}
|
|
88
76
|
|
|
89
77
|
async function bodyExcerpt(res) {
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/ingest/sidecar-providers.mjs — governed install/health probes for ingestion sidecars.
|
|
3
|
+
*
|
|
4
|
+
* docling (document extraction, provisioned via uv venv) and whisper (audio
|
|
5
|
+
* transcription, provisioned as a system/cached binary) are ingestion providers
|
|
6
|
+
* declared as `ingestion-provider` manifests under lib/extensions/manifests/.
|
|
7
|
+
* The single place that turns a manifest's `installProbe` and `healthCheck`
|
|
8
|
+
* declarations into an actual probe result, and exposes the manifest's
|
|
9
|
+
* `degradation.chain` for the ingest pipeline to consume.
|
|
10
|
+
*
|
|
11
|
+
* Every probe accepts an injectable `exec`/`fs` seam so doctor and CLI tests can
|
|
12
|
+
* exercise the present AND absent code paths without depending on the real uv
|
|
13
|
+
* venv or whisper-cli binary being installed on the test machine (FAKE probes).
|
|
14
|
+
* Nothing here throws on absence — a missing sidecar is a reportable state
|
|
15
|
+
* (`installed: false`), never a crash.
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
18
|
+
import { spawnSync } from 'node:child_process';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
|
|
21
|
+
import { loadManifestsFromDir, resolveManifestDirs } from '../extensions/loader.mjs';
|
|
22
|
+
|
|
23
|
+
export const SIDECAR_PROVIDER_IDS = ['docling', 'whisper'];
|
|
24
|
+
|
|
25
|
+
function loadManifest(id) {
|
|
26
|
+
const { builtin } = resolveManifestDirs();
|
|
27
|
+
const { manifests, errors } = loadManifestsFromDir(builtin);
|
|
28
|
+
const manifest = manifests.find((m) => m.id === id && m.kind === 'ingestion-provider');
|
|
29
|
+
if (!manifest) {
|
|
30
|
+
const relevantError = errors.find((e) => e.includes(`${id}.manifest.json`));
|
|
31
|
+
return { manifest: null, error: relevantError || `no ingestion-provider manifest found for '${id}'` };
|
|
32
|
+
}
|
|
33
|
+
return { manifest, error: null };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function which(bin, { execImpl = spawnSync } = {}) {
|
|
37
|
+
const checker = process.platform === 'win32' ? 'where' : 'which';
|
|
38
|
+
const result = execImpl(checker, [bin], { encoding: 'utf8' });
|
|
39
|
+
if (result.status !== 0) return null;
|
|
40
|
+
return result.stdout.trim().split('\n')[0] || null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// docling's install probe reads the venv marker the same way
|
|
44
|
+
// lib/runtime/uv-bootstrap.mjs does, but through an injectable fs seam so tests
|
|
45
|
+
// can simulate "marker present, venv missing" (stale install) and "nothing
|
|
46
|
+
// present" without touching the real filesystem.
|
|
47
|
+
|
|
48
|
+
function probeDoclingInstall({ runtimeDir, existsImpl = existsSync, readFileImpl = readFileSync } = {}) {
|
|
49
|
+
const pythonBin = join(runtimeDir, '.venv', 'bin', 'python');
|
|
50
|
+
const pythonBin3 = join(runtimeDir, '.venv', 'bin', 'python3');
|
|
51
|
+
const venvPython = existsImpl(pythonBin) ? pythonBin : (existsImpl(pythonBin3) ? pythonBin3 : null);
|
|
52
|
+
const markerPath = join(runtimeDir, '.install-marker.json');
|
|
53
|
+
|
|
54
|
+
if (!existsImpl(markerPath)) {
|
|
55
|
+
return { installed: false, version: null, reason: 'no install marker at ' + markerPath };
|
|
56
|
+
}
|
|
57
|
+
let marker;
|
|
58
|
+
try {
|
|
59
|
+
marker = JSON.parse(readFileImpl(markerPath, 'utf8'));
|
|
60
|
+
} catch (err) {
|
|
61
|
+
return { installed: false, version: null, reason: `install marker unreadable: ${err.message}` };
|
|
62
|
+
}
|
|
63
|
+
if (!venvPython) {
|
|
64
|
+
return { installed: false, version: marker.doclingVersion || null, reason: 'install marker present but venv python binary missing (stale install)' };
|
|
65
|
+
}
|
|
66
|
+
return { installed: true, version: marker.doclingVersion || null, reason: null, pythonBin: venvPython, runtimeDir };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// whisper's install probe mirrors lib/runtime/whisper-bootstrap.mjs's PATH +
|
|
70
|
+
// cached-binary lookup, again through an injectable exec/fs seam.
|
|
71
|
+
|
|
72
|
+
function probeWhisperInstall({ runtimeDir, candidates, cachedBinaryRel, execImpl = spawnSync, existsImpl = existsSync } = {}) {
|
|
73
|
+
for (const candidate of candidates) {
|
|
74
|
+
const found = which(candidate, { execImpl });
|
|
75
|
+
if (found) return { installed: true, version: null, reason: null, binary: found, source: 'system' };
|
|
76
|
+
}
|
|
77
|
+
const cached = join(runtimeDir, cachedBinaryRel);
|
|
78
|
+
if (existsImpl(cached)) {
|
|
79
|
+
return { installed: true, version: null, reason: null, binary: cached, source: 'cached' };
|
|
80
|
+
}
|
|
81
|
+
return { installed: false, version: null, reason: `not found on PATH (${candidates.join(', ')}) or cached at ${cached}` };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* probeInstall(id, opts) — install probe for a governed ingestion provider.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} id 'docling' | 'whisper'
|
|
88
|
+
* @param {object} [opts]
|
|
89
|
+
* @param {string} [opts.runtimeDir] override runtime dir (defaults to manifest's declared default under cwd)
|
|
90
|
+
* @param {string} [opts.cwd]
|
|
91
|
+
* @param {Function} [opts.existsImpl] injectable fs.existsSync (fake probes)
|
|
92
|
+
* @param {Function} [opts.readFileImpl] injectable fs.readFileSync (fake probes)
|
|
93
|
+
* @param {Function} [opts.execImpl] injectable spawnSync (fake probes)
|
|
94
|
+
* @returns {{ ok: boolean, installed: boolean, version: (string|null), reason: (string|null), manifest: object }}
|
|
95
|
+
*/
|
|
96
|
+
export function probeInstall(id, { runtimeDir = null, cwd = process.cwd(), existsImpl = existsSync, readFileImpl = readFileSync, execImpl = spawnSync } = {}) {
|
|
97
|
+
const { manifest, error } = loadManifest(id);
|
|
98
|
+
if (!manifest) return { ok: false, installed: false, version: null, reason: error, manifest: null };
|
|
99
|
+
|
|
100
|
+
const probeSpec = manifest.installProbe;
|
|
101
|
+
const resolvedRuntimeDir = runtimeDir || join(cwd, probeSpec.defaultRuntimeDir);
|
|
102
|
+
|
|
103
|
+
if (probeSpec.kind === 'venv-marker') {
|
|
104
|
+
const result = probeDoclingInstall({ runtimeDir: resolvedRuntimeDir, existsImpl, readFileImpl });
|
|
105
|
+
return { ok: true, ...result, manifest };
|
|
106
|
+
}
|
|
107
|
+
if (probeSpec.kind === 'binary-on-path') {
|
|
108
|
+
const result = probeWhisperInstall({
|
|
109
|
+
runtimeDir: resolvedRuntimeDir,
|
|
110
|
+
candidates: probeSpec.candidates,
|
|
111
|
+
cachedBinaryRel: probeSpec.cachedBinary,
|
|
112
|
+
execImpl,
|
|
113
|
+
existsImpl,
|
|
114
|
+
});
|
|
115
|
+
return { ok: true, ...result, manifest };
|
|
116
|
+
}
|
|
117
|
+
return { ok: false, installed: false, version: null, reason: `unknown installProbe.kind '${probeSpec.kind}'`, manifest };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* probeHealth(id, opts) — health check for a governed ingestion provider.
|
|
122
|
+
*
|
|
123
|
+
* Runs only when the install probe reports `installed: true`; a not-installed
|
|
124
|
+
* provider is unhealthy by definition and this short-circuits without
|
|
125
|
+
* spawning a process (so a missing binary never masquerades as an
|
|
126
|
+
* exec failure).
|
|
127
|
+
*
|
|
128
|
+
* @param {string} id 'docling' | 'whisper'
|
|
129
|
+
* @param {object} [opts] same injectable seams as probeInstall, plus:
|
|
130
|
+
* @param {Function} [opts.execImpl] injectable spawnSync for the health subprocess (fake probes)
|
|
131
|
+
* @returns {{ ok: boolean, healthy: boolean, detail: string, installProbe: object }}
|
|
132
|
+
*/
|
|
133
|
+
export function probeHealth(id, { runtimeDir = null, cwd = process.cwd(), existsImpl = existsSync, readFileImpl = readFileSync, execImpl = spawnSync } = {}) {
|
|
134
|
+
const installProbe = probeInstall(id, { runtimeDir, cwd, existsImpl, readFileImpl, execImpl });
|
|
135
|
+
if (!installProbe.ok) {
|
|
136
|
+
return { ok: false, healthy: false, detail: installProbe.reason, installProbe };
|
|
137
|
+
}
|
|
138
|
+
if (!installProbe.installed) {
|
|
139
|
+
return { ok: true, healthy: false, detail: `${id} not installed: ${installProbe.reason}`, installProbe };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const check = installProbe.manifest.healthCheck;
|
|
143
|
+
if (check.kind !== 'subprocess-version') {
|
|
144
|
+
return { ok: false, healthy: false, detail: `unknown healthCheck.kind '${check.kind}'`, installProbe };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const command = check.command === '<resolvedBinary>'
|
|
148
|
+
? installProbe.binary
|
|
149
|
+
: check.command.replace('<runtimeDir>', installProbe.runtimeDir);
|
|
150
|
+
const args = check.args.map((a) => (a === '<runtimeDir>' ? installProbe.runtimeDir : a));
|
|
151
|
+
const result = execImpl(command, args, { encoding: 'utf8', timeout: check.timeoutMs });
|
|
152
|
+
|
|
153
|
+
if (result.error) {
|
|
154
|
+
return { ok: true, healthy: false, detail: `${id} health check failed to spawn: ${result.error.message}`, installProbe };
|
|
155
|
+
}
|
|
156
|
+
if (result.status !== 0) {
|
|
157
|
+
return { ok: true, healthy: false, detail: `${id} health check exited ${result.status}: ${(result.stderr || result.stdout || '').trim().slice(0, 200)}`, installProbe };
|
|
158
|
+
}
|
|
159
|
+
const output = (result.stdout || result.stderr || '').trim();
|
|
160
|
+
return { ok: true, healthy: true, detail: output ? `${id} ${output.split('\n')[0]}` : `${id} healthy`, installProbe };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* degradationChain(id) — the manifest-declared fallback chain for a provider.
|
|
165
|
+
*
|
|
166
|
+
* @param {string} id
|
|
167
|
+
* @returns {{ chain: Array<{id:string, mode:string, description:string}>, terminal: string, resultMarking: string }|null}
|
|
168
|
+
*/
|
|
169
|
+
export function degradationChain(id) {
|
|
170
|
+
const { manifest } = loadManifest(id);
|
|
171
|
+
return manifest?.degradation || null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* testProvider(id, opts) — combined install+health probe shaped for CLI/doctor
|
|
176
|
+
* consumption (`construct provider test docling`). Never throws: an absent
|
|
177
|
+
* provider degrades loudly (`ok: true, healthy: false`) rather than crashing.
|
|
178
|
+
*
|
|
179
|
+
* @param {string} id
|
|
180
|
+
* @param {object} [opts] same injectable seams as probeHealth
|
|
181
|
+
* @returns {{ id: string, installed: boolean, healthy: boolean, version: (string|null), detail: string, degradation: object|null }}
|
|
182
|
+
*/
|
|
183
|
+
export function testProvider(id, opts = {}) {
|
|
184
|
+
if (!SIDECAR_PROVIDER_IDS.includes(id)) {
|
|
185
|
+
return { id, installed: false, healthy: false, version: null, detail: `unknown ingestion provider '${id}'; known: ${SIDECAR_PROVIDER_IDS.join(', ')}`, degradation: null };
|
|
186
|
+
}
|
|
187
|
+
const health = probeHealth(id, opts);
|
|
188
|
+
return {
|
|
189
|
+
id,
|
|
190
|
+
installed: !!health.installProbe?.installed,
|
|
191
|
+
healthy: health.healthy,
|
|
192
|
+
version: health.installProbe?.version || null,
|
|
193
|
+
detail: health.detail,
|
|
194
|
+
degradation: degradationChain(id),
|
|
195
|
+
};
|
|
196
|
+
}
|
package/lib/ingest-tooling.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import fs from 'node:fs';
|
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { spawnSync } from 'node:child_process';
|
|
11
11
|
import { resolveDoclingServeUrl } from './ingest/docling-remote.mjs';
|
|
12
|
+
import { defaultRuntimeDir } from './runtime/uv-bootstrap.mjs';
|
|
12
13
|
|
|
13
14
|
function whichBin(name) {
|
|
14
15
|
const cmd = process.platform === 'win32' ? 'where' : 'which';
|
|
@@ -17,12 +18,17 @@ function whichBin(name) {
|
|
|
17
18
|
return (result.stdout || '').trim().split('\n')[0] || null;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
// One shared, machine-scoped venv (construct-rf26.16), not project-keyed —
|
|
22
|
+
// no cwd/project argument, so every caller on this machine resolves the
|
|
23
|
+
// identical path. `defaultRuntimeDir()` (lib/runtime/uv-bootstrap.mjs) is
|
|
24
|
+
// the single source of truth for where the venv lives.
|
|
25
|
+
|
|
26
|
+
export function doclingVenvPath() {
|
|
27
|
+
return path.join(defaultRuntimeDir(), '.venv');
|
|
22
28
|
}
|
|
23
29
|
|
|
24
|
-
export function detectDoclingSidecar(
|
|
25
|
-
const venv = doclingVenvPath(
|
|
30
|
+
export function detectDoclingSidecar() {
|
|
31
|
+
const venv = doclingVenvPath();
|
|
26
32
|
const python = process.platform === 'win32'
|
|
27
33
|
? path.join(venv, 'Scripts', 'python.exe')
|
|
28
34
|
: path.join(venv, 'bin', 'python');
|
|
@@ -79,7 +85,7 @@ export function detectDoclingRemote(env = process.env) {
|
|
|
79
85
|
|
|
80
86
|
export function detectIngestPipeline({ cwd = process.cwd(), env = process.env, repoRoot = cwd } = {}) {
|
|
81
87
|
const steps = {
|
|
82
|
-
docling: detectDoclingSidecar(
|
|
88
|
+
docling: detectDoclingSidecar(),
|
|
83
89
|
nodeNative: detectNodeNativeDeps({ repoRoot }),
|
|
84
90
|
whisper: detectWhisper(env),
|
|
85
91
|
doclingRemote: detectDoclingRemote(env),
|