@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,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),
|
package/lib/init-unified.mjs
CHANGED
|
@@ -26,6 +26,7 @@ import { missingIgnorePatterns, isConstructPackageRepo } from './host-dispositio
|
|
|
26
26
|
import { HOST_KEYS, displayNameToKey } from './platforms/capabilities.mjs';
|
|
27
27
|
import { DOC_LANES, LANE_ORDER, DOC_PRESETS } from './init/doc-lanes.mjs';
|
|
28
28
|
import { initWorkflow } from './workflow-state.mjs';
|
|
29
|
+
import { isMainModule } from './roots.mjs';
|
|
29
30
|
|
|
30
31
|
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
31
32
|
const ROOT_DIR = path.join(__dirname, "..");
|
|
@@ -45,6 +46,7 @@ const withPostmortemsFlag = args.includes("--with-postmortems");
|
|
|
45
46
|
const withArchitectureFlag = args.includes("--with-architecture");
|
|
46
47
|
const withReadmeFlag = args.includes("--with-readme");
|
|
47
48
|
const withDevcontainerFlag = args.includes("--devcontainer");
|
|
49
|
+
const withSeedIndexFlag = args.includes("--seed-index");
|
|
48
50
|
|
|
49
51
|
// Behavior flags
|
|
50
52
|
const verbose = args.includes("--verbose") || args.includes("-v");
|
|
@@ -56,6 +58,10 @@ const skipInteractive = !interactive;
|
|
|
56
58
|
// templates/ already exist (issue #97).
|
|
57
59
|
const forceScaffold = args.includes("--force");
|
|
58
60
|
|
|
61
|
+
// --no-beads skips tracker initialization entirely and marks beads as disabled
|
|
62
|
+
// in the result. Use when the host environment cannot run bd (e.g. ephemeral CI).
|
|
63
|
+
const noBeads = args.includes("--no-beads");
|
|
64
|
+
|
|
59
65
|
// `bd init` is git-native and unconditionally commits its bootstrap; ADR-0027
|
|
60
66
|
// §3 requires init to leave the host repo's commit history untouched unless the
|
|
61
67
|
// user opts in. --commit-bootstrap keeps that commit; the default withholds it.
|
|
@@ -986,41 +992,49 @@ async function main() {
|
|
|
986
992
|
skipped.push('.cx/construct_guide.md');
|
|
987
993
|
}
|
|
988
994
|
|
|
989
|
-
// Auto-init beads
|
|
995
|
+
// Auto-init beads. Fail-closed: a tracker failure leaves the project in an
|
|
996
|
+
// inconsistent state (no hooks, no issue tracking) — better to stop early than
|
|
997
|
+
// silently proceed. Pass --no-beads to opt out for ephemeral/CI environments.
|
|
990
998
|
const beadsMeta = path.join(target, ".beads", "metadata.json");
|
|
991
|
-
|
|
999
|
+
let beadsReady = false;
|
|
1000
|
+
if (noBeads) {
|
|
1001
|
+
console.log(' ⚠️ Skipping task tracker initialization (--no-beads).');
|
|
1002
|
+
skipped.push('.beads/ (--no-beads)');
|
|
1003
|
+
} else if (!fs.existsSync(beadsMeta) && !isToolRepo) {
|
|
992
1004
|
console.log('');
|
|
993
1005
|
console.log('Initializing the task tracker (beads — local-first, git-synced, works offline)…');
|
|
994
1006
|
fs.mkdirSync(path.join(target, ".beads"), { recursive: true });
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
console.log(" Bootstrap files left uncommitted (commit them yourself, or re-run with --commit-bootstrap).");
|
|
1002
|
-
}
|
|
1003
|
-
} catch (e) {
|
|
1004
|
-
console.warn("⚠️ Beads init failed:", e.message);
|
|
1007
|
+
const beads = initializeBeadsTracker(target, { commitBootstrap });
|
|
1008
|
+
created.push(".beads/ (initialized)");
|
|
1009
|
+
const prefixNote = beads?.prefix ? ` — issues prefixed \`${beads.prefix}-\`` : "";
|
|
1010
|
+
console.log(` ✓ Task tracker ready${prefixNote}. Run \`bd quickstart\` to start.`);
|
|
1011
|
+
if (beads?.withheldCommit) {
|
|
1012
|
+
console.log(" Bootstrap files left uncommitted (commit them yourself, or re-run with --commit-bootstrap).");
|
|
1005
1013
|
}
|
|
1006
1014
|
console.log('');
|
|
1015
|
+
beadsReady = true;
|
|
1016
|
+
} else if (fs.existsSync(beadsMeta)) {
|
|
1017
|
+
beadsReady = true;
|
|
1007
1018
|
}
|
|
1008
1019
|
|
|
1009
1020
|
// Wire the project's git hooks to .beads/hooks so Construct's pre-commit
|
|
1010
1021
|
// secret-scan and policy gates activate. Owned by init, not install (ADR-0027
|
|
1011
1022
|
// §3): install must never mutate the cwd repo. Idempotent — no-op when
|
|
1012
1023
|
// .beads/hooks/pre-commit is absent or a non-default custom path is set.
|
|
1024
|
+
// Skipped when beads is disabled or was not successfully initialized.
|
|
1013
1025
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1026
|
+
if (beadsReady) {
|
|
1027
|
+
try {
|
|
1028
|
+
const { ensureGitHooksPath } = await import('./git-hooks-path.mjs');
|
|
1029
|
+
const hooks = ensureGitHooksPath({ cwd: target });
|
|
1030
|
+
if (hooks.status === 'set') {
|
|
1031
|
+
created.push('git core.hooksPath → .beads/hooks');
|
|
1032
|
+
if (!quiet) console.log(` ✓ ${hooks.message}`);
|
|
1033
|
+
} else if (hooks.status === 'warning' && !quiet) {
|
|
1034
|
+
console.log(` ⚠️ ${hooks.message}`);
|
|
1035
|
+
}
|
|
1036
|
+
} catch { /* git-hooks wiring is advisory; init proceeds */ }
|
|
1037
|
+
}
|
|
1024
1038
|
|
|
1025
1039
|
// Inject Construct's integration guidance into AGENTS.md and CLAUDE.md as a
|
|
1026
1040
|
// versioned, hash-stamped marker block (ADR-0027 §2). Runs after bd init so
|
|
@@ -1154,22 +1168,25 @@ async function main() {
|
|
|
1154
1168
|
}
|
|
1155
1169
|
}
|
|
1156
1170
|
|
|
1157
|
-
//
|
|
1158
|
-
// can compare new intake against established PRDs/RFCs/ADRs
|
|
1159
|
-
//
|
|
1160
|
-
//
|
|
1161
|
-
//
|
|
1171
|
+
// Vector-index seeding (embedding the project's existing docs so the agent
|
|
1172
|
+
// can compare new intake against established PRDs/RFCs/ADRs) is opt-in via
|
|
1173
|
+
// --seed-index (construct-rf26.17): the default init path performs zero
|
|
1174
|
+
// embedding work and never touches LanceDB, so a project that never uses
|
|
1175
|
+
// semantic search never pays for the model cache or the index directory.
|
|
1176
|
+
// `construct ingest` provisions both lazily on first real use.
|
|
1177
|
+
|
|
1178
|
+
if (withSeedIndexFlag) {
|
|
1179
|
+
try {
|
|
1180
|
+
const { syncFileStateToSql } = await import('./storage/sync.mjs');
|
|
1181
|
+
const projectName = path.basename(target);
|
|
1182
|
+
const seed = await syncFileStateToSql(target, { env: process.env, project: projectName });
|
|
1183
|
+
if (seed?.status === 'ok' && (seed.documentsSynced || 0) > 0) {
|
|
1184
|
+
console.log(`\n🔍 Indexed existing project material: ${seed.documentsSynced} doc(s), ${seed.embeddingsSynced || 0} embeddings.`);
|
|
1185
|
+
console.log(` The agent can now compare new intake (inbox/) against your existing PRDs, ADRs, and notes.`);
|
|
1186
|
+
}
|
|
1187
|
+
} catch { /* silent — corpus seeding is best-effort, not a setup blocker */ }
|
|
1188
|
+
}
|
|
1162
1189
|
|
|
1163
|
-
try {
|
|
1164
|
-
const { syncFileStateToSql } = await import('./storage/sync.mjs');
|
|
1165
|
-
const projectName = path.basename(target);
|
|
1166
|
-
const seed = await syncFileStateToSql(target, { env: process.env, project: projectName });
|
|
1167
|
-
if (seed?.status === 'ok' && (seed.documentsSynced || 0) > 0) {
|
|
1168
|
-
console.log(`\n🔍 Indexed existing project material: ${seed.documentsSynced} doc(s), ${seed.embeddingsSynced || 0} embeddings.`);
|
|
1169
|
-
console.log(` The agent can now compare new intake (inbox/) against your existing PRDs, ADRs, and notes.`);
|
|
1170
|
-
}
|
|
1171
|
-
} catch { /* silent — corpus seeding is best-effort, not a setup blocker */ }
|
|
1172
|
-
|
|
1173
1190
|
// ── Devcontainer recipe ────────────────────────────────────────────────
|
|
1174
1191
|
if (withDevcontainerFlag) {
|
|
1175
1192
|
const dcDir = path.join(target, '.devcontainer');
|
|
@@ -1252,7 +1269,7 @@ async function main() {
|
|
|
1252
1269
|
}
|
|
1253
1270
|
}
|
|
1254
1271
|
|
|
1255
|
-
if (import.meta.url
|
|
1272
|
+
if (isMainModule(import.meta.url)) {
|
|
1256
1273
|
main().catch((error) => {
|
|
1257
1274
|
console.error(`Error: ${error.message}`);
|
|
1258
1275
|
process.exit(1);
|
package/lib/init-update.mjs
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
needsConstructGuideUpdate,
|
|
22
22
|
writeConstructGuideProposal,
|
|
23
23
|
} from "./init-update-guide.mjs";
|
|
24
|
+
import { isMainModule } from './roots.mjs';
|
|
24
25
|
|
|
25
26
|
const args = process.argv.slice(2);
|
|
26
27
|
const dryRun = args.includes("--dry-run");
|
|
@@ -226,7 +227,7 @@ async function main() {
|
|
|
226
227
|
console.log("To replace .cx/construct_guide.md with the shipped template: construct init:update --apply-guide");
|
|
227
228
|
}
|
|
228
229
|
|
|
229
|
-
if (import.meta.url
|
|
230
|
+
if (isMainModule(import.meta.url)) {
|
|
230
231
|
main().catch((error) => {
|
|
231
232
|
console.error(`Error: ${error.message}`);
|
|
232
233
|
process.exit(1);
|
|
@@ -62,6 +62,30 @@ function stripClaudeSettings(homeDir, opts) {
|
|
|
62
62
|
if (changed) writeJson(filePath, settings, { ...opts, label: '~/.claude/settings.json Construct entries' });
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
// construct-ranh moved Claude Code's MCP write path off ~/.claude/settings.json
|
|
66
|
+
// (never read by the CLI for server definitions) to ~/.claude.json's top-level
|
|
67
|
+
// `mcpServers`. A post-fix install registers MCP servers there, so this must be
|
|
68
|
+
// stripped alongside settings.json for the memory/cass/construct-mcp bridges to
|
|
69
|
+
// be fully removed.
|
|
70
|
+
|
|
71
|
+
function stripClaudeUserConfig(homeDir, opts) {
|
|
72
|
+
const filePath = path.join(homeDir, '.claude.json');
|
|
73
|
+
const config = readJson(filePath);
|
|
74
|
+
if (!config || typeof config !== 'object') return;
|
|
75
|
+
if (!config.mcpServers || typeof config.mcpServers !== 'object') return;
|
|
76
|
+
|
|
77
|
+
let changed = false;
|
|
78
|
+
for (const key of CONSTRUCT_MCP_KEYS) {
|
|
79
|
+
if (key in config.mcpServers) {
|
|
80
|
+
delete config.mcpServers[key];
|
|
81
|
+
changed = true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
|
|
85
|
+
|
|
86
|
+
if (changed) writeJson(filePath, config, { ...opts, label: '~/.claude.json Construct MCP entries' });
|
|
87
|
+
}
|
|
88
|
+
|
|
65
89
|
function stripOpenCode(homeDir, opts) {
|
|
66
90
|
const filePath = path.join(homeDir, '.config', 'opencode', 'opencode.json');
|
|
67
91
|
const config = readJson(filePath);
|
|
@@ -173,6 +197,7 @@ export function cleanupLegacyGlobalConstruct({ homeDir, dryRun = false } = {}) {
|
|
|
173
197
|
});
|
|
174
198
|
|
|
175
199
|
stripClaudeSettings(homeDir, opts);
|
|
200
|
+
stripClaudeUserConfig(homeDir, opts);
|
|
176
201
|
stripOpenCode(homeDir, opts);
|
|
177
202
|
stripVsCode(homeDir, opts);
|
|
178
203
|
stripCodex(homeDir, opts);
|