@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/embed/daemon.mjs
CHANGED
|
@@ -31,17 +31,21 @@ import { dirname, join } from 'node:path';
|
|
|
31
31
|
import { loadEmbedConfig } from './config.mjs';
|
|
32
32
|
import { Scheduler } from './scheduler.mjs';
|
|
33
33
|
import { rotateIfOversized } from '../logging/rotate.mjs';
|
|
34
|
+
import { resolveStatePath } from '../state-root.mjs';
|
|
34
35
|
import { SnapshotEngine, renderMarkdown } from './snapshot.mjs';
|
|
35
36
|
import { ApprovalQueue } from './approval-queue.mjs';
|
|
36
37
|
import { dispatchOutputs } from './output.mjs';
|
|
37
38
|
import { AuthorityGuard } from './authority-guard.mjs';
|
|
38
39
|
import { ProviderRegistry } from './providers/registry.mjs';
|
|
39
40
|
import { addObservation, searchObservations } from '../observation-store.mjs';
|
|
41
|
+
import { filterHash, matchesFilter, validateFilterConfig } from '../providers/contract.mjs';
|
|
42
|
+
import { recordFilterAudit } from '../providers/filter-audit.mjs';
|
|
40
43
|
import { listSessions, loadSession } from '../session-store.mjs';
|
|
41
44
|
import { syncEvalDatasets } from '../telemetry/eval-datasets.mjs';
|
|
42
45
|
import { runLLMJudgeEvaluations } from '../telemetry/llm-judge.mjs';
|
|
43
46
|
import { InboxWatcher } from './inbox.mjs';
|
|
44
47
|
import { InboxLiveWatcher } from './inbox-live-watcher.mjs';
|
|
48
|
+
import { registerEmbedCapabilityJobs } from './capability-jobs.mjs';
|
|
45
49
|
import { generateRoadmap } from './roadmap.mjs';
|
|
46
50
|
import { runDocsLifecycle } from './docs-lifecycle.mjs';
|
|
47
51
|
import { emitEmbedNotification, notifySlack } from './notifications.mjs';
|
|
@@ -49,6 +53,7 @@ import { intentToCategory } from './providers/slack.mjs';
|
|
|
49
53
|
import { runTelemetrySetup } from '../telemetry/setup.mjs';
|
|
50
54
|
import { emitTraceEvent, newTraceId, newSpanId } from '../worker/trace.mjs';
|
|
51
55
|
import { doctorRoot } from '../config/xdg.mjs';
|
|
56
|
+
import { resolveNonNegativeSetting } from '../env-config.mjs';
|
|
52
57
|
|
|
53
58
|
/**
|
|
54
59
|
* Log a non-fatal error to stderr. Use instead of empty catch {} blocks
|
|
@@ -117,7 +122,7 @@ function resolveLockPath(env = process.env) {
|
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
function resolveDaemonStatePath(env = process.env) {
|
|
120
|
-
return
|
|
125
|
+
return resolveStatePath(resolveRootDir(env), 'runtime', 'embed-daemon.json');
|
|
121
126
|
}
|
|
122
127
|
|
|
123
128
|
// ─── Self-repair helpers ────────────────────────────────────────────────────
|
|
@@ -221,12 +226,101 @@ function distillRecentSessions(rootDir) {
|
|
|
221
226
|
return distilled;
|
|
222
227
|
}
|
|
223
228
|
|
|
229
|
+
// ─── ADR-0060 filter enforcement (LMCP-B11) ─────────────────────────────────
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Match each snapshot section back to the source record that produced it.
|
|
233
|
+
* Sections are pushed in config.sources order but a source with no
|
|
234
|
+
* registered provider is skipped upstream (lib/embed/snapshot.mjs), so
|
|
235
|
+
* position alone is unreliable — walk both lists in lockstep, consuming one
|
|
236
|
+
* unmatched source per section by provider name.
|
|
237
|
+
*/
|
|
238
|
+
function matchSourceForSections(sources) {
|
|
239
|
+
const remaining = [...(sources ?? [])];
|
|
240
|
+
return (providerName) => {
|
|
241
|
+
const idx = remaining.findIndex((s) => s.provider === providerName);
|
|
242
|
+
if (idx === -1) return null;
|
|
243
|
+
return remaining.splice(idx, 1)[0];
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Enforce the ADR-0060 filter block on every snapshot section before any
|
|
249
|
+
* item reaches observation distillation. Validates the source's `filter`
|
|
250
|
+
* config first (fail closed: an unknown/invalid key throws, the section's
|
|
251
|
+
* items are dropped entirely, and the failure is recorded as a source
|
|
252
|
+
* error visible in the snapshot). Valid filters run the plane-side
|
|
253
|
+
* `matchesFilter` predicate over every item — the mandatory backstop even
|
|
254
|
+
* when the provider already pushed the filter down server-side — and a
|
|
255
|
+
* `{provider, instance, filterHash, matched, dropped}` audit line is
|
|
256
|
+
* appended per section, matched or not.
|
|
257
|
+
*
|
|
258
|
+
* Mutates `snapshot.sections[].items` in place and appends to
|
|
259
|
+
* `snapshot.errors` on a filter config failure. Returns nothing.
|
|
260
|
+
*/
|
|
261
|
+
export function enforceSectionFilters(rootDir, snapshot, sources) {
|
|
262
|
+
const nextSourceFor = matchSourceForSections(sources);
|
|
263
|
+
|
|
264
|
+
for (const section of snapshot.sections ?? []) {
|
|
265
|
+
const source = nextSourceFor(section.provider);
|
|
266
|
+
const filter = source?.filter ?? null;
|
|
267
|
+
const fetched = section.items?.length ?? 0;
|
|
268
|
+
|
|
269
|
+
if (filter == null) {
|
|
270
|
+
recordFilterAudit(rootDir, {
|
|
271
|
+
provider: section.provider,
|
|
272
|
+
instance: source?.instance ?? null,
|
|
273
|
+
filterHash: null,
|
|
274
|
+
pushdown: false,
|
|
275
|
+
fetched,
|
|
276
|
+
matched: fetched,
|
|
277
|
+
});
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
validateFilterConfig(section.provider, filter);
|
|
283
|
+
} catch (err) {
|
|
284
|
+
section.items = [];
|
|
285
|
+
snapshot.errors = snapshot.errors ?? [];
|
|
286
|
+
snapshot.errors.push({ source: section.provider, error: `filter config: ${err.message}` });
|
|
287
|
+
recordFilterAudit(rootDir, {
|
|
288
|
+
provider: section.provider,
|
|
289
|
+
instance: source?.instance ?? null,
|
|
290
|
+
filterHash: filterHash(filter),
|
|
291
|
+
scope: filter.scope ?? null,
|
|
292
|
+
predicates: filter.predicates ?? null,
|
|
293
|
+
nativeQuery: filter.nativeQuery ?? null,
|
|
294
|
+
pushdown: false,
|
|
295
|
+
fetched,
|
|
296
|
+
matched: 0,
|
|
297
|
+
});
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const admitted = (section.items ?? []).filter((item) => matchesFilter(item, filter));
|
|
302
|
+
section.items = admitted;
|
|
303
|
+
|
|
304
|
+
recordFilterAudit(rootDir, {
|
|
305
|
+
provider: section.provider,
|
|
306
|
+
instance: source?.instance ?? null,
|
|
307
|
+
filterHash: filterHash(filter),
|
|
308
|
+
scope: filter.scope ?? null,
|
|
309
|
+
predicates: filter.predicates ?? null,
|
|
310
|
+
nativeQuery: filter.nativeQuery ?? null,
|
|
311
|
+
pushdown: Boolean(filter.nativeQuery) || Boolean(filter.scope?.projects?.length),
|
|
312
|
+
fetched,
|
|
313
|
+
matched: admitted.length,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
224
318
|
/**
|
|
225
319
|
* Distill snapshot items (issues, PRs, messages) into the observation store.
|
|
226
320
|
* Skips items already recorded in this snapshot cycle using a dedup key.
|
|
227
321
|
* Returns the count of new observations written.
|
|
228
322
|
*/
|
|
229
|
-
function distillSnapshotItems(rootDir, sections) {
|
|
323
|
+
export async function distillSnapshotItems(rootDir, sections) {
|
|
230
324
|
let written = 0;
|
|
231
325
|
for (const section of sections ?? []) {
|
|
232
326
|
for (const item of section.items ?? []) {
|
|
@@ -235,7 +329,7 @@ function distillSnapshotItems(rootDir, sections) {
|
|
|
235
329
|
if (!dedupKey) continue;
|
|
236
330
|
|
|
237
331
|
// Check if we already have a recent observation for this exact item key
|
|
238
|
-
const existing = searchObservations(rootDir, dedupKey, { limit: 1 });
|
|
332
|
+
const existing = await searchObservations(rootDir, dedupKey, { limit: 1 });
|
|
239
333
|
if (existing.some((o) => o.tags?.includes(`item:${dedupKey}`))) continue;
|
|
240
334
|
|
|
241
335
|
const summary = buildItemSummary(item, section.provider);
|
|
@@ -363,6 +457,7 @@ export class EmbedDaemon {
|
|
|
363
457
|
#daemonStatePath = null;
|
|
364
458
|
#inboxWatcher = null;
|
|
365
459
|
#inboxLiveWatcher = null;
|
|
460
|
+
#capabilityIds = [];
|
|
366
461
|
#providerHealth = new ProviderHealthTracker();
|
|
367
462
|
#daemonTraceId = null;
|
|
368
463
|
|
|
@@ -429,6 +524,11 @@ export class EmbedDaemon {
|
|
|
429
524
|
this.#config.snapshot.intervalMs,
|
|
430
525
|
async () => {
|
|
431
526
|
const snapshot = await this.#snapshotEngine.generate();
|
|
527
|
+
|
|
528
|
+
// ADR-0060: drop out-of-filter items before anything downstream
|
|
529
|
+
// (outputs, distillation, roadmap) can see them.
|
|
530
|
+
enforceSectionFilters(this.#rootDir, snapshot, this.#config.sources);
|
|
531
|
+
|
|
432
532
|
this.#lastSnapshot = snapshot;
|
|
433
533
|
await dispatchOutputs(snapshot, this.#config.outputs, this.#registry, this.#authorityGuard, this.#rootDir);
|
|
434
534
|
process.stderr.write(`[embed] Snapshot generated at ${snapshot.completedAt} — ${snapshot.summary.totalItems} items, ${snapshot.summary.errorCount} errors\n`);
|
|
@@ -457,7 +557,7 @@ export class EmbedDaemon {
|
|
|
457
557
|
}
|
|
458
558
|
|
|
459
559
|
// Distill snapshot items (issues, PRs, messages) into observation store
|
|
460
|
-
const itemsWritten = distillSnapshotItems(this.#rootDir, snapshot.sections ?? []);
|
|
560
|
+
const itemsWritten = await distillSnapshotItems(this.#rootDir, snapshot.sections ?? []);
|
|
461
561
|
if (itemsWritten) {
|
|
462
562
|
process.stderr.write(`[embed] Distilled ${itemsWritten} snapshot item(s) into observation store\n`);
|
|
463
563
|
}
|
|
@@ -564,9 +664,9 @@ export class EmbedDaemon {
|
|
|
564
664
|
60_000,
|
|
565
665
|
async () => {
|
|
566
666
|
const logPath = join(doctorRoot(), 'runtime', 'embed-daemon.log');
|
|
567
|
-
const capMb =
|
|
667
|
+
const capMb = resolveNonNegativeSetting(process.env, 'CONSTRUCT_EMBED_LOG_MAX_MB', 50);
|
|
568
668
|
const maxBytes = Math.floor(capMb * 1024 * 1024);
|
|
569
|
-
const maxSegments =
|
|
669
|
+
const maxSegments = resolveNonNegativeSetting(process.env, 'CONSTRUCT_EMBED_LOG_MAX_SEGMENTS', 5);
|
|
570
670
|
await rotateIfOversized(logPath, { maxBytes, maxSegments, gzip: true });
|
|
571
671
|
},
|
|
572
672
|
{ runImmediately: true },
|
|
@@ -1125,6 +1225,24 @@ this.#scheduler.register(
|
|
|
1125
1225
|
);
|
|
1126
1226
|
}
|
|
1127
1227
|
|
|
1228
|
+
// ── Job 16: embed-capabilities (ADR-0061 / LMCP-P2/F5) ───────────────────
|
|
1229
|
+
// One scheduled job per enabled embed-capability manifest — exactly the
|
|
1230
|
+
// enabled set, read fresh at startup so `construct embed enable/disable`
|
|
1231
|
+
// takes effect on the next daemon restart. The job body (capability-jobs.mjs
|
|
1232
|
+
// runCapabilityTick) composes the bound provider slice off this daemon's
|
|
1233
|
+
// own last snapshot, asks workflow-invoke.mjs for the plan, and enqueues
|
|
1234
|
+
// any resulting proposals into this same approval queue — never through
|
|
1235
|
+
// a provider write adapter. No reasoningExecutor is wired in here because
|
|
1236
|
+
// no in-process/external reasoning engine ships in this repo yet; every
|
|
1237
|
+
// tick is therefore an honest skipped-with-reason(reasoning-executor-not-available)
|
|
1238
|
+
// until one is wired in, per ADR-0061 §3.
|
|
1239
|
+
this.#capabilityIds = registerEmbedCapabilityJobs(this.#scheduler, {
|
|
1240
|
+
rootDir: this.#rootDir,
|
|
1241
|
+
env: this.#env,
|
|
1242
|
+
getSnapshot: () => this.#lastSnapshot,
|
|
1243
|
+
approvalQueue: this.#approvalQueue,
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1128
1246
|
this.#scheduler.start();
|
|
1129
1247
|
|
|
1130
1248
|
// Reactive inbox watching: low-latency trigger backed by the scheduler
|
|
@@ -1200,6 +1318,7 @@ this.#scheduler.register(
|
|
|
1200
1318
|
schedulerTasks: this.#scheduler?.status() ?? [],
|
|
1201
1319
|
providerHealth: this.#providerHealth.summary(),
|
|
1202
1320
|
inboxDirs: this.#inboxWatcher?.dirs() ?? [],
|
|
1321
|
+
embedCapabilities: this.#capabilityIds,
|
|
1203
1322
|
};
|
|
1204
1323
|
}
|
|
1205
1324
|
|
|
@@ -15,15 +15,20 @@
|
|
|
15
15
|
* Does NOT require the daemon to be running.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import { existsSync } from 'node:fs';
|
|
18
19
|
import { homedir } from 'node:os';
|
|
19
20
|
import { join } from 'node:path';
|
|
20
21
|
import { loadConstructEnv } from '../env-config.mjs';
|
|
21
22
|
import { addObservation } from '../observation-store.mjs';
|
|
22
23
|
import { loadProjectConfig } from '../config/project-config.mjs';
|
|
24
|
+
import { configDir } from '../config/xdg.mjs';
|
|
25
|
+
import { loadEmbedConfig } from './config.mjs';
|
|
26
|
+
import { matchesFilter, validateFilterConfig } from '../providers/contract.mjs';
|
|
23
27
|
import {
|
|
24
28
|
resolveEffectiveSourceTargetsFromConfig,
|
|
25
29
|
resolveKnownSourcesFromTargets,
|
|
26
30
|
} from '../config/source-targets.mjs';
|
|
31
|
+
import { getSourceTargetDescriptor, renderTemplate } from '../config/source-target-registry.mjs';
|
|
27
32
|
|
|
28
33
|
// ─── Self-query detection ────────────────────────────────────────────────────
|
|
29
34
|
|
|
@@ -136,6 +141,75 @@ export function matchSourceFromQuery(query, env = process.env, cwd = process.cwd
|
|
|
136
141
|
return matches[0];
|
|
137
142
|
}
|
|
138
143
|
|
|
144
|
+
// ─── ADR-0060 filter enforcement (construct-737t) ────────────────────────────
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Load embed.yaml's sources[] list — the only place an ADR-0060 filter block
|
|
148
|
+
* lives (ADR-0060 §2; enforced on the poll path by daemon.mjs's
|
|
149
|
+
* enforceSectionFilters). demand-fetch resolves its own source/target
|
|
150
|
+
* records from construct.config.json and legacy env vars, which carry no
|
|
151
|
+
* filter field, so this reads the same embed config the daemon would load,
|
|
152
|
+
* purely to look up a governing filter. Missing/unreadable embed.yaml is not
|
|
153
|
+
* an error here — demand-fetch works without the daemon running.
|
|
154
|
+
*/
|
|
155
|
+
function loadEmbedSourcesForFilter(env) {
|
|
156
|
+
try {
|
|
157
|
+
const configPath = join(configDir(undefined, env), 'embed.yaml');
|
|
158
|
+
if (!existsSync(configPath)) return [];
|
|
159
|
+
return loadEmbedConfig(configPath).sources ?? [];
|
|
160
|
+
} catch {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Find the embed.yaml source entry that declares `ref` among its repo/
|
|
167
|
+
* project/channel/team values for `provider`, using the same plural-array/
|
|
168
|
+
* singular-scalar fallback (`opts.repos ?? [opts.repo]`) the real providers
|
|
169
|
+
* use to resolve their own config. A `ref` of null (a broad, unscoped fetch
|
|
170
|
+
* spanning every configured value) never resolves to one governing source,
|
|
171
|
+
* so it is left unfiltered rather than guessing.
|
|
172
|
+
*/
|
|
173
|
+
function findGoverningEmbedSource(embedSources, provider, ref) {
|
|
174
|
+
if (ref == null) return null;
|
|
175
|
+
const descriptor = getSourceTargetDescriptor(provider);
|
|
176
|
+
const embed = descriptor?.embed;
|
|
177
|
+
if (!embed) return null;
|
|
178
|
+
|
|
179
|
+
const pluralField = embed.valueKind === 'array' ? embed.valueField : `${embed.valueField}s`;
|
|
180
|
+
const singularField = embed.valueKind === 'array' ? embed.valueField.replace(/s$/, '') : embed.valueField;
|
|
181
|
+
const target = String(ref).trim().toLowerCase();
|
|
182
|
+
|
|
183
|
+
return embedSources.find((src) => {
|
|
184
|
+
if (src.provider !== provider) return false;
|
|
185
|
+
const declared = Array.isArray(src[pluralField])
|
|
186
|
+
? src[pluralField]
|
|
187
|
+
: (src[singularField] ? [src[singularField]] : []);
|
|
188
|
+
return declared.some((v) => String(v).trim().toLowerCase() === target);
|
|
189
|
+
}) ?? null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Apply the governing embed.yaml filter (if any) to items already fetched
|
|
194
|
+
* from `provider`/`ref`, mirroring daemon.mjs's enforceSectionFilters so the
|
|
195
|
+
* on-demand path and the poll path never disagree on which items are in
|
|
196
|
+
* scope. Fails closed on an invalid filter config: every item is dropped and
|
|
197
|
+
* the reason surfaces as an error, never silently ingested.
|
|
198
|
+
*/
|
|
199
|
+
function applyEmbedFilter(items, provider, ref, env) {
|
|
200
|
+
const source = findGoverningEmbedSource(loadEmbedSourcesForFilter(env), provider, ref);
|
|
201
|
+
const filter = source?.filter ?? null;
|
|
202
|
+
if (filter == null) return { items, error: null };
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
validateFilterConfig(provider, filter);
|
|
206
|
+
} catch (err) {
|
|
207
|
+
return { items: [], error: err.message };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return { items: items.filter((item) => matchesFilter(item, filter)), error: null };
|
|
211
|
+
}
|
|
212
|
+
|
|
139
213
|
// ─── On-demand fetch ─────────────────────────────────────────────────────────
|
|
140
214
|
|
|
141
215
|
/**
|
|
@@ -261,7 +335,7 @@ export async function demandFetch({ query, rootDir, env, cwd, teamId = null, tar
|
|
|
261
335
|
|
|
262
336
|
// Separate real items from error sentinels the provider may have returned
|
|
263
337
|
const errorItems = rawItems.filter(i => i.type === 'error');
|
|
264
|
-
|
|
338
|
+
let dataItems = rawItems.filter(i => i.type !== 'error');
|
|
265
339
|
|
|
266
340
|
if (!dataItems.length && errorItems.length) {
|
|
267
341
|
const errMsg = errorItems.map(i => i.message).join('; ');
|
|
@@ -275,6 +349,21 @@ export async function demandFetch({ query, rootDir, env, cwd, teamId = null, tar
|
|
|
275
349
|
};
|
|
276
350
|
}
|
|
277
351
|
|
|
352
|
+
// ADR-0060: drop out-of-scope items the same way enforceSectionFilters does
|
|
353
|
+
// on the poll path, so a source whose refs/repo list is broader than its
|
|
354
|
+
// filter.scope narrows identically on the on-demand path.
|
|
355
|
+
const filtered = applyEmbedFilter(dataItems, match.provider, match.ref, resolvedEnv);
|
|
356
|
+
if (filtered.error) {
|
|
357
|
+
return {
|
|
358
|
+
ok: false,
|
|
359
|
+
reason: 'filter_config_error',
|
|
360
|
+
message: `${match.display} filter config error: ${filtered.error}`,
|
|
361
|
+
items: [],
|
|
362
|
+
match,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
dataItems = filtered.items;
|
|
366
|
+
|
|
278
367
|
// Write each item as an observation so future queries find them in the knowledge base
|
|
279
368
|
let written = 0;
|
|
280
369
|
for (const item of dataItems) {
|
|
@@ -376,7 +465,22 @@ async function demandFetchAll({ query, rootDir, env, cwd } = {}) {
|
|
|
376
465
|
if (!readCalls.length) continue;
|
|
377
466
|
for (const { ref, opts, display } of readCalls) {
|
|
378
467
|
try {
|
|
379
|
-
const
|
|
468
|
+
const rawItems = await provider.read(ref, opts) ?? [];
|
|
469
|
+
|
|
470
|
+
// ADR-0060: same governing-source filter lookup as the single-match
|
|
471
|
+
// path, keyed on this source's own ref so a broader universal fetch
|
|
472
|
+
// narrows identically to what enforceSectionFilters does at poll
|
|
473
|
+
// time. Error sentinels bypass the filter — matchesFilter would
|
|
474
|
+
// otherwise drop them for lacking a scope field, hiding a real
|
|
475
|
+
// provider error behind a filter that was never meant to judge it.
|
|
476
|
+
const errorSentinels = rawItems.filter((i) => i.type === 'error');
|
|
477
|
+
const filtered = applyEmbedFilter(rawItems.filter((i) => i.type !== 'error'), providerName, src.ref, env);
|
|
478
|
+
if (filtered.error) {
|
|
479
|
+
errors.push({ provider: providerName, ref: src.ref ?? null, error: `filter config: ${filtered.error}` });
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
const items = [...errorSentinels, ...filtered.items];
|
|
483
|
+
|
|
380
484
|
if (items?.length) {
|
|
381
485
|
allItems.push(...items);
|
|
382
486
|
for (const item of items) {
|
|
@@ -431,78 +535,94 @@ async function demandFetchAll({ query, rootDir, env, cwd } = {}) {
|
|
|
431
535
|
/**
|
|
432
536
|
* Build a list of { ref, opts } pairs to pass to provider.read(ref, opts).
|
|
433
537
|
*
|
|
538
|
+
* Dispatches on the matched provider's manifest-derived `sourceTarget.demandFetch`
|
|
539
|
+
* descriptor (lib/config/source-target-registry.mjs) rather than naming
|
|
540
|
+
* providers — a provider whose descriptor declares `queryDynamicJql` gets
|
|
541
|
+
* jira's recency-guarded/targeted JQL construction, one with `queryOptsKind:
|
|
542
|
+
* 'scalar'` gets linear's single-value opts, one with `querySupported: false`
|
|
543
|
+
* (or no `demandFetch` block at all) is skipped, and everything else gets
|
|
544
|
+
* github's fixed-array-opts, multi-ref shape.
|
|
545
|
+
*
|
|
434
546
|
* @param {{ provider: string, ref: string|null, display: string }} match
|
|
435
547
|
* @param {object} env
|
|
436
548
|
* @returns {{ ref: string, opts: object, display: string }[]}
|
|
437
549
|
*/
|
|
438
550
|
function buildReadCalls(match, env, cwd = process.cwd()) {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
{ ref: 'prs', opts, display: match.display },
|
|
446
|
-
{ ref: 'issues', opts, display: match.display },
|
|
447
|
-
{ ref: 'commits', opts, display: match.display },
|
|
448
|
-
];
|
|
449
|
-
}
|
|
450
|
-
if (match.provider === 'jira') {
|
|
551
|
+
const descriptor = getSourceTargetDescriptor(match.provider);
|
|
552
|
+
const df = descriptor?.demandFetch;
|
|
553
|
+
if (!df || df.querySupported === false) return [];
|
|
554
|
+
|
|
555
|
+
if (df.queryDynamicJql) {
|
|
556
|
+
const dj = df.queryDynamicJql;
|
|
451
557
|
const { config } = loadProjectConfig(cwd, env);
|
|
452
|
-
const
|
|
453
|
-
.filter((t) => t.provider ===
|
|
454
|
-
.map((t) => t.selector.
|
|
455
|
-
const
|
|
456
|
-
?
|
|
457
|
-
: (env.
|
|
458
|
-
const recencyDays = parseInt(env.
|
|
558
|
+
const projects = resolveEffectiveSourceTargetsFromConfig(config, env)
|
|
559
|
+
.filter((t) => t.provider === match.provider)
|
|
560
|
+
.map((t) => t.selector[descriptor.selector.field]);
|
|
561
|
+
const projectList = projects.length
|
|
562
|
+
? projects
|
|
563
|
+
: (env[dj.legacyProjectsEnvVar] ?? '').split(',').map((p) => p.trim()).filter(Boolean);
|
|
564
|
+
const recencyDays = parseInt(env[dj.recencyEnvVar], 10) || dj.recencyDefaultDays;
|
|
459
565
|
|
|
460
566
|
let jql;
|
|
461
567
|
if (match.ref) {
|
|
462
|
-
// Targeted
|
|
463
|
-
jql =
|
|
464
|
-
} else if (
|
|
465
|
-
// Broad/fallback fetch — scoped to configured
|
|
466
|
-
const
|
|
467
|
-
jql =
|
|
568
|
+
// Targeted fetch — no recency cutoff, user asked for this value specifically
|
|
569
|
+
jql = renderTemplate(dj.singleTemplate, { value: match.ref });
|
|
570
|
+
} else if (projectList.length > 0) {
|
|
571
|
+
// Broad/fallback fetch — scoped to configured values with recency guard
|
|
572
|
+
const quoted = projectList.map((p) => `"${p}"`).join(', ');
|
|
573
|
+
jql = renderTemplate(dj.broadTemplate, { values: quoted, recencyDays });
|
|
468
574
|
} else {
|
|
469
|
-
//
|
|
575
|
+
// Nothing configured — skip rather than defaulting to an unscoped query
|
|
470
576
|
return [];
|
|
471
577
|
}
|
|
472
|
-
return [{ ref:
|
|
578
|
+
return [{ ref: df.targetRefs[0], opts: { jql, limit: df.queryDefaultLimit }, display: match.display }];
|
|
473
579
|
}
|
|
474
|
-
|
|
475
|
-
|
|
580
|
+
|
|
581
|
+
if (df.queryOptsKind === 'scalar') {
|
|
582
|
+
const value = df.queryOptsNullable ? (match.ref ?? null) : match.ref;
|
|
583
|
+
return [{ ref: df.queryRefs[0], opts: { [df.queryOptsField]: value, limit: df.queryDefaultLimit }, display: match.display }];
|
|
476
584
|
}
|
|
477
|
-
|
|
585
|
+
|
|
586
|
+
const opts = { [df.queryOptsField]: [match.ref], limit: df.queryDefaultLimit };
|
|
587
|
+
return df.queryRefs.map((ref) => ({ ref, opts, display: match.display }));
|
|
478
588
|
}
|
|
479
589
|
|
|
480
590
|
// Read calls for one resolved team source, honoring the target's own filters
|
|
481
591
|
// (jira jql, github refs/limit, slack oldest) rather than the generic defaults.
|
|
592
|
+
// Same manifest-derived dispatch as buildReadCalls, keyed on target.provider.
|
|
482
593
|
|
|
483
594
|
function buildReadCallsForTarget(target) {
|
|
595
|
+
const descriptor = getSourceTargetDescriptor(target.provider);
|
|
484
596
|
const sel = target.selector ?? {};
|
|
485
597
|
const f = target.filters ?? {};
|
|
486
|
-
const display = `${target.provider}/${
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
598
|
+
const display = `${target.provider}/${descriptor ? (sel[descriptor.selector.field] ?? target.id) : target.id}`;
|
|
599
|
+
const df = descriptor?.demandFetch;
|
|
600
|
+
if (!df) return [];
|
|
601
|
+
|
|
602
|
+
const limit = Number.isInteger(f.limit) ? f.limit : df.targetDefaultLimit;
|
|
603
|
+
|
|
604
|
+
if (df.targetJqlTemplate) {
|
|
605
|
+
const jql = typeof f[df.targetJqlFilterKey] === 'string'
|
|
606
|
+
? f[df.targetJqlFilterKey]
|
|
607
|
+
: renderTemplate(df.targetJqlTemplate, { value: sel[descriptor.selector.field] });
|
|
608
|
+
return [{ ref: df.targetRefs[0], opts: { jql, limit }, display }];
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (df.targetOptsKind === 'scalar') {
|
|
612
|
+
return [{ ref: df.targetRefs[0], opts: { [df.targetOptsField]: sel[descriptor.selector.field], limit }, display }];
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (df.targetRefsFilterKey) {
|
|
616
|
+
const refs = Array.isArray(f[df.targetRefsFilterKey]) ? f[df.targetRefsFilterKey] : df.targetRefs;
|
|
617
|
+
const opts = { [df.targetOptsField]: [sel[descriptor.selector.field]], limit };
|
|
618
|
+
return refs.map((ref) => ({ ref, opts, display }));
|
|
505
619
|
}
|
|
620
|
+
|
|
621
|
+
const opts = { [df.targetOptsField]: [sel[descriptor.selector.field]], limit };
|
|
622
|
+
for (const key of df.targetExtraFilterKeys ?? []) {
|
|
623
|
+
if (key === 'oldest') opts.oldest = Number.isInteger(f.oldest) ? f.oldest : df.targetOldestDefault;
|
|
624
|
+
}
|
|
625
|
+
return [{ ref: df.targetRefs[0], opts, display }];
|
|
506
626
|
}
|
|
507
627
|
|
|
508
628
|
// Drive the fetch from a team's resolved sources: one provider read per target,
|
|
@@ -526,11 +646,27 @@ async function demandFetchTeam({ teamId, targets, rootDir, env, providerRegistry
|
|
|
526
646
|
errors.push({ provider: target.provider, targetId: target.id, message: `provider "${target.provider}" not registered` });
|
|
527
647
|
continue;
|
|
528
648
|
}
|
|
649
|
+
const targetDescriptor = getSourceTargetDescriptor(target.provider);
|
|
650
|
+
const targetRef = targetDescriptor ? (target.selector ?? {})[targetDescriptor.selector.field] : null;
|
|
529
651
|
for (const { ref, opts, display } of buildReadCallsForTarget(target)) {
|
|
530
652
|
try {
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
653
|
+
const rawItems = await provider.read(ref, opts) ?? [];
|
|
654
|
+
|
|
655
|
+
// ADR-0060: same governing-source filter lookup, keyed on the team
|
|
656
|
+
// target's own selector value, so a team-scoped fetch narrows
|
|
657
|
+
// identically to what enforceSectionFilters does at poll time. Error
|
|
658
|
+
// sentinels bypass the filter — they never carry a scope field, so
|
|
659
|
+
// matchesFilter would otherwise drop them and hide a real fetch error.
|
|
660
|
+
const errorSentinels = rawItems.filter((i) => i.type === 'error');
|
|
661
|
+
const filtered = applyEmbedFilter(rawItems.filter((i) => i.type !== 'error'), target.provider, targetRef, env);
|
|
662
|
+
if (filtered.error) {
|
|
663
|
+
errors.push({ provider: target.provider, targetId: target.id, message: `filter config: ${filtered.error}` });
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
for (const item of errorSentinels) errors.push(item);
|
|
668
|
+
|
|
669
|
+
for (const item of filtered.items) {
|
|
534
670
|
allItems.push(item);
|
|
535
671
|
try {
|
|
536
672
|
addObservation(rootDir, {
|
package/lib/embed/inbox.mjs
CHANGED
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
* two stats, so a partially-written drop is never consumed mid-write.
|
|
17
17
|
*
|
|
18
18
|
* State tracking:
|
|
19
|
-
* <
|
|
20
|
-
* Prevents re-processing the
|
|
19
|
+
* <stateRoot>/runtime/inbox-state.json (ADR-0066: machine-scoped, not
|
|
20
|
+
* project-local) — maps filePath → processedAt. Prevents re-processing the
|
|
21
|
+
* same file across daemon restarts.
|
|
21
22
|
*
|
|
22
23
|
* Usage:
|
|
23
24
|
* const watcher = new InboxWatcher({ rootDir, env });
|
|
@@ -40,8 +41,9 @@ import { loadManifest, recordFile, hasFile, sha256Of, MANIFEST_REL_PATH } from '
|
|
|
40
41
|
import { gatherAttribution } from '../intake/attribution.mjs';
|
|
41
42
|
import { acquirePollLock, releasePollLock } from '../intake/poll-lock.mjs';
|
|
42
43
|
import { doctorRoot } from '../config/xdg.mjs';
|
|
44
|
+
import { resolveStatePath } from '../state-root.mjs';
|
|
43
45
|
|
|
44
|
-
const
|
|
46
|
+
const STATE_FILE_REL = join('runtime', 'inbox-state.json');
|
|
45
47
|
const ROOT_INBOX_SUBDIR = 'inbox';
|
|
46
48
|
const STAGING_SUBDIR = '.staging';
|
|
47
49
|
const MAX_FILE_SIZE_BYTES = 50 * 1024 * 1024; // 50 MB hard cap
|
|
@@ -116,26 +118,26 @@ function pruneStaleState(rootDir, state) {
|
|
|
116
118
|
// ─── State helpers ────────────────────────────────────────────────────────────
|
|
117
119
|
|
|
118
120
|
function statePath(rootDir) {
|
|
119
|
-
return
|
|
121
|
+
return resolveStatePath(rootDir, STATE_FILE_REL);
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
function readState(rootDir) {
|
|
125
|
+
if (!shouldCreateCx(rootDir)) return {};
|
|
123
126
|
const p = statePath(rootDir);
|
|
124
127
|
if (!existsSync(p)) return {};
|
|
125
128
|
try { return JSON.parse(readFileSync(p, 'utf8')); } catch (err) { process.stderr.write('[inbox.mjs] readState: ' + (err?.message ?? String(err)) + '\n'); return {}; }
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
function writeState(rootDir, state) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
//
|
|
132
|
+
// Only persist state if this is an initialized project — an uninitialized
|
|
133
|
+
// cwd shouldn't accumulate machine-scoped state for a directory that isn't
|
|
134
|
+
// really a Construct project.
|
|
135
|
+
|
|
132
136
|
if (shouldCreateCx(rootDir)) {
|
|
133
137
|
ensureCxDir(rootDir);
|
|
134
|
-
|
|
138
|
+
const p = statePath(rootDir);
|
|
135
139
|
writeFileSync(p, JSON.stringify(state, null, 2) + '\n');
|
|
136
140
|
}
|
|
137
|
-
// If not an initialized project, don't create .cx - state will be lost
|
|
138
|
-
// but that's okay since we shouldn't be processing files in uninitialized dirs
|
|
139
141
|
}
|
|
140
142
|
|
|
141
143
|
// ─── Directory resolution ─────────────────────────────────────────────────────
|