@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
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/runtime/uv-bootstrap.mjs — idempotent uv + docling venv provisioner.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* One venv per machine, not one per project (construct-rf26.16; ADR-0066
|
|
5
|
+
* called this out as the docling venv's terminal shape). On first use:
|
|
6
|
+
* downloads uv via the official Astral installer, creates
|
|
7
|
+
* `~/.construct/runtime/docling/.venv` (or `$CX_TOOLKIT_DIR/runtime/docling/.venv`)
|
|
8
|
+
* via `resolveSharedRuntimeDir`, installs the pinned docling release.
|
|
9
|
+
* Subsequent calls from any project on this machine are no-ops once the
|
|
10
|
+
* venv is present — there is no project key anywhere in this path.
|
|
11
|
+
*
|
|
12
|
+
* Version pin is the lockfile: `.install-marker.json`'s `doclingVersion`
|
|
13
|
+
* field is compared against DOCLING_PIN on every call. A mismatch (a
|
|
14
|
+
* construct upgrade that bumps the pin) is not silent drift — it clears the
|
|
15
|
+
* stale venv directory and re-provisions from scratch under `force`
|
|
16
|
+
* semantics, rather than layering a new install over an old one.
|
|
7
17
|
*
|
|
8
18
|
* Best-practice notes (2026-06):
|
|
9
19
|
* - uv (Astral) is the consensus Python tooling, ~10× faster than pip, single
|
|
@@ -16,19 +26,25 @@
|
|
|
16
26
|
* Returns the path to the venv's python binary so callers can spawn the
|
|
17
27
|
* sidecar without relying on PATH state.
|
|
18
28
|
*/
|
|
19
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
29
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
|
|
20
30
|
import { spawn, spawnSync } from 'node:child_process';
|
|
21
31
|
import path from 'node:path';
|
|
22
32
|
import os from 'node:os';
|
|
23
33
|
|
|
34
|
+
import { resolveSharedRuntimeDir } from '../state-root.mjs';
|
|
35
|
+
|
|
24
36
|
const DOCLING_PIN = '2.45.0';
|
|
25
37
|
const UV_INSTALL_URL = 'https://astral.sh/uv/install.sh';
|
|
26
38
|
const UV_TIMEOUT_MS = 120_000;
|
|
27
39
|
const VENV_TIMEOUT_MS = 240_000;
|
|
28
40
|
const INSTALL_TIMEOUT_MS = 600_000;
|
|
29
41
|
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
// ensureDir:false — shared by the provisioner (ensureDoclingVenv, which
|
|
43
|
+
// already mkdirs before it writes) and the read-only detector
|
|
44
|
+
// (describeDoclingRuntime); a detect-only call must not conjure the dir.
|
|
45
|
+
|
|
46
|
+
export function defaultRuntimeDir() {
|
|
47
|
+
return resolveSharedRuntimeDir('docling', { ensureDir: false });
|
|
32
48
|
}
|
|
33
49
|
|
|
34
50
|
function which(bin) {
|
|
@@ -127,6 +143,16 @@ export async function ensureDoclingVenv({ runtimeDir = defaultRuntimeDir(), forc
|
|
|
127
143
|
return { pythonBin: existingPython, venvDir, runtimeDir, fresh: false };
|
|
128
144
|
}
|
|
129
145
|
|
|
146
|
+
// A version-pin mismatch (existingMarker present but doclingVersion !== DOCLING_PIN)
|
|
147
|
+
// is a controlled re-provision, not silent drift: clear the stale venv outright
|
|
148
|
+
// rather than trust `uv venv` to reconcile an existing directory left by a
|
|
149
|
+
// different docling/Python pin.
|
|
150
|
+
|
|
151
|
+
if (existingMarker && existingMarker.doclingVersion !== DOCLING_PIN) {
|
|
152
|
+
progress(`Pinned docling version changed (${existingMarker.doclingVersion} → ${DOCLING_PIN}) — re-provisioning.`);
|
|
153
|
+
try { rmSync(venvDir, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
154
|
+
}
|
|
155
|
+
|
|
130
156
|
progress('Provisioning the docling document extractor (first run). This downloads a Python runtime and ML dependencies and can take several minutes; later runs are instant.');
|
|
131
157
|
const uv = await ensureUv(runtimeDir);
|
|
132
158
|
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Model files are downloaded directly from Hugging Face the first time the
|
|
14
14
|
* default `base.en` (or env-overridden) model is requested. Models cache at
|
|
15
|
-
* <
|
|
16
|
-
*
|
|
15
|
+
* <stateRoot>/runtime/whisper/models/ (ADR-0066: machine-scoped, not
|
|
16
|
+
* project-local). base.en is ~150MB; smaller than the small.en or medium.en
|
|
17
|
+
* model that callers can opt into via CONSTRUCT_WHISPER_MODEL.
|
|
17
18
|
*
|
|
18
19
|
* No bundled distribution: whisper.cpp upstream does not publish stable
|
|
19
20
|
* pre-built CLI binaries across platforms, and building from source via
|
|
@@ -25,12 +26,19 @@ import { pipeline } from 'node:stream/promises';
|
|
|
25
26
|
import path from 'node:path';
|
|
26
27
|
import os from 'node:os';
|
|
27
28
|
|
|
29
|
+
import { resolveStateDir } from '../state-root.mjs';
|
|
30
|
+
|
|
28
31
|
const HF_MODEL_BASE_URL = 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main';
|
|
29
32
|
const DEFAULT_MODEL = process.env.CONSTRUCT_WHISPER_MODEL || 'base.en';
|
|
30
33
|
const KNOWN_MODELS = new Set(['tiny', 'tiny.en', 'base', 'base.en', 'small', 'small.en', 'medium', 'medium.en', 'large-v3', 'large-v3-turbo']);
|
|
31
34
|
|
|
35
|
+
// ensureDir:false — shared by the provisioner (ensureWhisperRuntime, which
|
|
36
|
+
// already mkdirs before it writes) and the read-only detector
|
|
37
|
+
// (describeWhisperRuntime/locateWhisperBinary); a detect-only call must not
|
|
38
|
+
// conjure the dir.
|
|
39
|
+
|
|
32
40
|
export function defaultRuntimeDir(cwd = process.cwd()) {
|
|
33
|
-
return
|
|
41
|
+
return resolveStateDir(cwd, 'runtime', 'whisper', { ensureDir: false });
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
function which(bin) {
|
package/lib/runtime-env.mjs
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/runtime-env.mjs — Construct runtime environment with credential bootstrap.
|
|
3
3
|
*
|
|
4
|
-
* prepareConstructEnv merges ~/.config/construct/config.env
|
|
5
|
-
*
|
|
4
|
+
* prepareConstructEnv merges the user's ~/.config/construct/config.env and,
|
|
5
|
+
* when the caller passes `rootDir` (the resolved project root, not the
|
|
6
|
+
* toolkit install checkout), the project .env into the process environment —
|
|
7
|
+
* project .env wins. Credential auto-link from 1Password runs only when
|
|
8
|
+
* autoLink:true (setup-credentials).
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
import { loadConstructEnv } from './env-config.mjs';
|
package/lib/sandbox.mjs
CHANGED
|
@@ -55,7 +55,7 @@ export function createSandbox({ profile = null, seedContext = null } = {}) {
|
|
|
55
55
|
fs.writeFileSync(path.join(root, '.cx', 'context.md'), ctx);
|
|
56
56
|
|
|
57
57
|
const cfg = { version: 1, alias: 'Construct', deployment: { mode: 'solo' } };
|
|
58
|
-
if (profile) cfg.
|
|
58
|
+
if (profile) cfg.scope = profile;
|
|
59
59
|
fs.writeFileSync(path.join(root, 'construct.config.json'), JSON.stringify(cfg, null, 2) + '\n');
|
|
60
60
|
|
|
61
61
|
spawnSync('git', ['init', '--quiet'], { cwd: root, stdio: 'ignore' });
|
package/lib/scheduler/index.mjs
CHANGED
|
@@ -8,11 +8,15 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Jobs registered here: 'tag-candidate-mining', 'skill-usage-rollup', 'doc-hygiene-scan'
|
|
10
10
|
*
|
|
11
|
-
* Deployment mode is
|
|
11
|
+
* Deployment mode is resolved through the canonical lib/deployment-mode.mjs
|
|
12
|
+
* getDeploymentMode (CONSTRUCT_DEPLOYMENT_MODE env, then project config, then solo),
|
|
13
|
+
* so the scheduler agrees with the broker, session prelude, and status surfaces.
|
|
12
14
|
* Registration is in-memory: no persistence across process restarts beyond the
|
|
13
15
|
* native trigger files written by solo.mjs or the DB rows in 009_scheduler.sql.
|
|
14
16
|
*/
|
|
15
17
|
|
|
18
|
+
import { getDeploymentMode } from '../deployment-mode.mjs';
|
|
19
|
+
|
|
16
20
|
// ---------------------------------------------------------------------------
|
|
17
21
|
// In-memory job registry
|
|
18
22
|
// ---------------------------------------------------------------------------
|
|
@@ -57,22 +61,6 @@ export function listJobs() {
|
|
|
57
61
|
}));
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
/**
|
|
61
|
-
* Derives the deployment mode from environment variables.
|
|
62
|
-
* Returns 'enterprise' | 'team' | 'solo'.
|
|
63
|
-
*
|
|
64
|
-
* CONSTRUCT_DEPLOYMENT=enterprise or CONSTRUCT_ENTERPRISE=1 => enterprise
|
|
65
|
-
* CONSTRUCT_DEPLOYMENT=team or CONSTRUCT_TEAM=1 => team
|
|
66
|
-
* Otherwise => solo
|
|
67
|
-
*/
|
|
68
|
-
export function getDeploymentMode(env = process.env) {
|
|
69
|
-
const explicit = env.CONSTRUCT_DEPLOYMENT;
|
|
70
|
-
if (explicit === 'enterprise' || env.CONSTRUCT_ENTERPRISE === '1') return 'enterprise';
|
|
71
|
-
if (explicit === 'team' || env.CONSTRUCT_TEAM === '1') return 'team';
|
|
72
|
-
if (explicit === 'solo') return 'solo';
|
|
73
|
-
return 'solo';
|
|
74
|
-
}
|
|
75
|
-
|
|
76
64
|
// ---------------------------------------------------------------------------
|
|
77
65
|
// Built-in job registrations
|
|
78
66
|
// ---------------------------------------------------------------------------
|
|
@@ -104,8 +92,8 @@ registerJob({
|
|
|
104
92
|
const DOC_HYGIENE_SOLO_CRON = '0 2 * * *';
|
|
105
93
|
const DOC_HYGIENE_TEAM_CRON = '0 * * * *';
|
|
106
94
|
|
|
107
|
-
export function resolveDocHygieneSchedule(env = process.env) {
|
|
108
|
-
const mode = getDeploymentMode(env);
|
|
95
|
+
export function resolveDocHygieneSchedule(env = process.env, { cwd } = {}) {
|
|
96
|
+
const mode = getDeploymentMode(env, { cwd });
|
|
109
97
|
const isTeamish = mode === 'team' || mode === 'enterprise';
|
|
110
98
|
return {
|
|
111
99
|
mode: isTeamish ? 'team' : 'solo',
|
|
@@ -121,7 +109,7 @@ registerJob({
|
|
|
121
109
|
mode: docHygiene.mode,
|
|
122
110
|
handler: async ({ cwd, env }) => {
|
|
123
111
|
const { findHygieneCandidates } = await import('../hygiene/scan.mjs');
|
|
124
|
-
const { limit } = resolveDocHygieneSchedule(env);
|
|
112
|
+
const { limit } = resolveDocHygieneSchedule(env, { cwd });
|
|
125
113
|
const candidates = findHygieneCandidates({ cwd, limit });
|
|
126
114
|
return {
|
|
127
115
|
status: 'ok',
|
package/lib/schema-infer.mjs
CHANGED
|
@@ -25,6 +25,7 @@ import { extractDocumentTextNodeNative, extractDocumentTextAsync } from './docum
|
|
|
25
25
|
import { readCurrentModels, readOpenRouterApiKeyFromOpenCodeConfig } from './model-router.mjs';
|
|
26
26
|
import { getUserEnvPath } from './env-config.mjs';
|
|
27
27
|
import { pollFreeModels, topForTier } from './model-free-selector.mjs';
|
|
28
|
+
import { extractOpRef, resolveOpRef, resolveSecret } from './providers/secret-resolver.mjs';
|
|
28
29
|
import { execSync } from 'node:child_process';
|
|
29
30
|
|
|
30
31
|
// ---------------------------------------------------------------------------
|
|
@@ -187,9 +188,37 @@ async function fetchWithTimeout(url, options) {
|
|
|
187
188
|
}
|
|
188
189
|
}
|
|
189
190
|
|
|
191
|
+
function resolveSecretOrNull(varName, { env = process.env, allowAmbient = true } = {}) {
|
|
192
|
+
try {
|
|
193
|
+
return resolveSecret(varName, { env, allowAmbient });
|
|
194
|
+
} catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function materializeConfiguredKey(rawValue) {
|
|
200
|
+
if (typeof rawValue !== 'string' || rawValue.length === 0) return null;
|
|
201
|
+
const ref = extractOpRef(rawValue);
|
|
202
|
+
try {
|
|
203
|
+
return ref ? resolveOpRef(ref) : rawValue;
|
|
204
|
+
} catch {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function resolveOpenRouterApiKey() {
|
|
210
|
+
const envKey = resolveSecretOrNull('OPENROUTER_API_KEY');
|
|
211
|
+
if (envKey) return envKey;
|
|
212
|
+
try {
|
|
213
|
+
return materializeConfiguredKey(readOpenRouterApiKeyFromOpenCodeConfig());
|
|
214
|
+
} catch {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
190
219
|
async function callModel(userContent, { traceId } = {}) {
|
|
191
|
-
const orKey =
|
|
192
|
-
const anthropicKey =
|
|
220
|
+
const orKey = resolveOpenRouterApiKey();
|
|
221
|
+
const anthropicKey = resolveSecretOrNull('ANTHROPIC_API_KEY');
|
|
193
222
|
|
|
194
223
|
if (!orKey && !anthropicKey) {
|
|
195
224
|
throw new Error('No API key found. Set ANTHROPIC_API_KEY or OPENROUTER_API_KEY to use schema inference.');
|
package/lib/scopes/lifecycle.mjs
CHANGED
|
@@ -10,7 +10,11 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Lifecycle stages:
|
|
12
12
|
* draft - in `.cx/scopes/draft-<id>/` with requirements.md + scope.json
|
|
13
|
-
* active - in `specialists/org/scopes/<id>.json` (curated) or
|
|
13
|
+
* active - in `specialists/org/scopes/<id>.json` (curated), or a named
|
|
14
|
+
* custom profile under the construct-rf26.13 config-layer tiers
|
|
15
|
+
* (`~/.construct/org/scopes/<id>.json` or
|
|
16
|
+
* `<project>/.cx/org/scopes/<id>.json`), or the single-file
|
|
17
|
+
* `.cx/scope.json` escape hatch (custom, anonymous)
|
|
14
18
|
* archived - in `archive/scopes/<id>/` with the final state and an
|
|
15
19
|
* archive-note.md explaining why it was retired
|
|
16
20
|
*
|
|
@@ -41,23 +45,22 @@ function archiveDir(cwd, id) {
|
|
|
41
45
|
|
|
42
46
|
const REQUIREMENTS_BRIEF = (id, displayName) => `# Scope requirements: ${displayName} (${id})
|
|
43
47
|
|
|
44
|
-
This brief is a discovery and design contract for a new Construct scope. Methodology lives in \`docs/guides/concepts/persona-research.md\` and lifecycle in \`docs/guides/concepts/
|
|
48
|
+
This brief is a discovery and design contract for a new Construct scope. Methodology lives in \`docs/guides/concepts/persona-research.md\` and lifecycle in \`docs/guides/concepts/profile-lifecycle.md\`. Treat each section as a task to dispatch to the named specialist. A draft scope is not ready to promote until every section has evidence backing it. The aim is research-grounded, not opinion-grounded.
|
|
45
49
|
|
|
46
50
|
Specialists used here are the existing Construct cx-* personas (already in the registry). They are dispatched the standard way; this doc just names which questions each one owns.
|
|
47
51
|
|
|
52
|
+
A scope selects flows and skill emphasis over the shared, fixed 12-role core roster (\`specialists/org/specialists/\`) — it does not invent new roles or departments. If this org genuinely needs a role absent from that roster, author it as a custom specialist via the construct-rf26.13 config layer (\`construct specialist create <id> --custom\`, docs/guides/cookbook/custom-specialists-and-teams.md) rather than declaring it inline here.
|
|
53
|
+
|
|
48
54
|
This draft scaffolding produces:
|
|
49
|
-
- \`scope.json\` — the structural definition (
|
|
55
|
+
- \`scope.json\` — the structural definition (intake taxonomy, doc templates, skill emphasis)
|
|
50
56
|
- \`requirements.md\` — this brief
|
|
51
|
-
- \`personas/<role>.md\` —
|
|
52
|
-
- \`departments/<dept>.md\` — one charter doc per department (filled during the frame phase)
|
|
53
|
-
|
|
54
|
-
The persona and department files start as templates. Filling them from evidence is the actual work; the JSON is just a manifest.
|
|
57
|
+
- \`personas/<role>.md\` — optional, only when \`seedRoles\` names a role genuinely absent from the core roster (rare; most orgs route entirely through the existing 12)
|
|
55
58
|
|
|
56
|
-
## 1. Discovery (cx-
|
|
59
|
+
## 1. Discovery (cx-researcher)
|
|
57
60
|
|
|
58
61
|
Goal: characterize the people, the work, and the outputs of this org type.
|
|
59
62
|
|
|
60
|
-
-
|
|
63
|
+
- Which of the 12 core roles (architect, reviewer, engineer, debugger, qa, security, operations, product-manager, data-analyst, designer, researcher, orchestrator) are primary for this org, and which are rarely if ever needed?
|
|
61
64
|
- What is the dominant work loop? Describe in 5 to 8 stages.
|
|
62
65
|
- What are the recurring signals that enter the loop (briefs, bug reports, customer messages, etc.)?
|
|
63
66
|
- What are the canonical output artifacts (campaigns, runbooks, papers, etc.)?
|
|
@@ -67,23 +70,23 @@ Goal: characterize the people, the work, and the outputs of this org type.
|
|
|
67
70
|
|
|
68
71
|
Goal: turn discovery into an intake taxonomy and a stage sequence.
|
|
69
72
|
|
|
70
|
-
- Propose intake types (max 24). Each must be: distinct, observable, and routable to a primary owner.
|
|
73
|
+
- Propose intake types (max 24). Each must be: distinct, observable, and routable to a primary owner among the 12 core roles.
|
|
71
74
|
- Propose stages (max 12). Order matters; each stage must answer "what changed?" not just "what happened next?".
|
|
72
|
-
- For each intake type, name the primary owner role and the recommended chain (max 3 hops).
|
|
75
|
+
- For each intake type, name the primary owner role (one of the 12) and the recommended chain (max 3 hops).
|
|
73
76
|
- For each stage, name the dominant artifact produced.
|
|
74
77
|
- Acceptance: a representative real signal classifies into a single intake type with confidence > 0.6.
|
|
75
78
|
|
|
76
|
-
## 3.
|
|
79
|
+
## 3. Skill emphasis (cx-architect)
|
|
77
80
|
|
|
78
|
-
Goal:
|
|
81
|
+
Goal: pick the flows and skill bundles this profile should emphasize from the shared registry — not a new role set.
|
|
79
82
|
|
|
80
|
-
-
|
|
81
|
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
- Acceptance: every
|
|
83
|
+
- For each doc template the profile ships, confirm a docTemplates entry exists and name its owning role.
|
|
84
|
+
- List the \`defaultSkills\` (skill ids under \`skills/\`) this org's work loop leans on most — these become the profile's baseline skill entitlement (lib/skills/router.mjs), on top of whatever an individual specialist already carries.
|
|
85
|
+
- Set \`researchProfiles\` per intake type (external, user, codebase, market, compliance) and \`toneDefaults\` per doc template.
|
|
86
|
+
- Name the orchestrator's role in this loop (routing/dispatch is always cx-orchestrator; this just confirms nothing here bypasses it).
|
|
87
|
+
- Acceptance: every \`defaultSkills\` entry resolves to a real file under \`skills/\`; every doc template maps to an owning role among the 12.
|
|
85
88
|
|
|
86
|
-
## 4. Validation (cx-
|
|
89
|
+
## 4. Validation (cx-reviewer)
|
|
87
90
|
|
|
88
91
|
Goal: prove the draft works on real signals before promotion.
|
|
89
92
|
|
|
@@ -93,13 +96,14 @@ Goal: prove the draft works on real signals before promotion.
|
|
|
93
96
|
|
|
94
97
|
## 5. Promotion (operator decision)
|
|
95
98
|
|
|
96
|
-
Goal: move the draft into the active catalog.
|
|
99
|
+
Goal: move the draft into the active catalog, or leave it as a durable custom profile.
|
|
97
100
|
|
|
98
101
|
- Curated path: hand-edit \`specialists/org/scopes/${id}.json\` from this draft, open a PR, run \`npm run lint:scopes\`.
|
|
99
|
-
- Custom path: copy the draft scope.json to \`<project>/.cx/
|
|
100
|
-
-
|
|
102
|
+
- Custom path (named, reusable): copy the draft scope.json to \`<project>/.cx/org/scopes/${id}.json\` (project tier, git-tracked) or \`~/.construct/org/scopes/${id}.json\` (user tier, shared across projects) — the same builtin -> user -> project precedence construct-rf26.13 gives custom specialists and teams. Then \`construct scope set ${id}\` switches to it exactly like a curated scope.
|
|
103
|
+
- Custom path (anonymous, one-off): copy the draft scope.json to \`<project>/.cx/scope.json\` with \`"custom": true\` — the pre-rf26.13 escape hatch, still supported for a single ad hoc override.
|
|
104
|
+
- Any path requires the validation acceptance criteria above to be met.
|
|
101
105
|
|
|
102
|
-
## 6. Health monitoring (cx-
|
|
106
|
+
## 6. Health monitoring (cx-reviewer)
|
|
103
107
|
|
|
104
108
|
Goal: keep the scope honest after it ships.
|
|
105
109
|
|
|
@@ -107,7 +111,7 @@ Goal: keep the scope honest after it ships.
|
|
|
107
111
|
- Any role with success-rate < 0.5 across 10+ runs triggers a review: is the role wrong, the prompt wrong, or the routing wrong?
|
|
108
112
|
- Health data is the input for future scope revisions; never edit a scope without a health report first.
|
|
109
113
|
|
|
110
|
-
## 7. Archive (cx-
|
|
114
|
+
## 7. Archive (cx-operations + operator)
|
|
111
115
|
|
|
112
116
|
Goal: retire a scope cleanly without losing the learning.
|
|
113
117
|
|
|
@@ -397,7 +401,7 @@ export function scopeHealth(cwd, scopeId, { windowDays = 30 } = {}) {
|
|
|
397
401
|
|
|
398
402
|
return {
|
|
399
403
|
scope: scopeId,
|
|
400
|
-
scopeExists: !!loadScope(scopeId),
|
|
404
|
+
scopeExists: !!loadScope(scopeId, { cwd }),
|
|
401
405
|
windowDays,
|
|
402
406
|
observationCount,
|
|
403
407
|
roles: roleSummary,
|
package/lib/scopes/loader.mjs
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/scopes/loader.mjs — Work-scope loader (intake lens + org enrichment).
|
|
3
3
|
*
|
|
4
|
-
* Curated scopes live in specialists/org/scopes/<id>.json.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Curated scopes live in specialists/org/scopes/<id>.json. Named custom
|
|
5
|
+
* scopes reuse the builtin -> user -> project precedence construct-rf26.13
|
|
6
|
+
* established for specialists/teams (lib/registry/assemble.mjs): an id also
|
|
7
|
+
* resolves from ~/.construct/org/scopes/<id>.json (shared across every
|
|
8
|
+
* project on the machine) and <cwd>/.cx/org/scopes/<id>.json (git-tracked,
|
|
9
|
+
* highest precedence), instead of a scope-specific mechanism. A later tier's
|
|
10
|
+
* fields shallow-merge over an earlier tier's on id collision.
|
|
7
11
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
12
|
+
* The single-file <cwd>/.cx/scope.json escape hatch (custom: true) still
|
|
13
|
+
* works for an anonymous, one-off override — it predates rf26.13 and stays
|
|
14
|
+
* for the "just this project, no id to remember" case — but a named, reusable
|
|
15
|
+
* custom profile should live under the tiered org/scopes/ layer so it can be
|
|
16
|
+
* switched to with `construct scope set <id>` like a curated one.
|
|
17
|
+
*
|
|
18
|
+
* construct.config.json's scope field selects the active curated/custom id
|
|
19
|
+
* (default rnd). Teams and roles are not authored in scope files — enrich.mjs
|
|
20
|
+
* attaches them from specialists/org at load time.
|
|
10
21
|
*/
|
|
11
22
|
|
|
12
23
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
@@ -14,6 +25,7 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
14
25
|
import { fileURLToPath } from 'node:url';
|
|
15
26
|
|
|
16
27
|
import { enrichScope } from './enrich.mjs';
|
|
28
|
+
import { homeDir } from '../paths.mjs';
|
|
17
29
|
|
|
18
30
|
const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
19
31
|
const REPO_ROOT = resolve(MODULE_DIR, '..', '..');
|
|
@@ -21,18 +33,43 @@ const SCOPES_DIR = join(REPO_ROOT, 'specialists', 'org', 'scopes');
|
|
|
21
33
|
|
|
22
34
|
export const DEFAULT_SCOPE_ID = 'rnd';
|
|
23
35
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
function userScopesDir() {
|
|
37
|
+
return join(homeDir(), '.construct', 'org', 'scopes');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function projectScopesDir(cwd) {
|
|
41
|
+
return join(cwd, '.cx', 'org', 'scopes');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readScopeTier(dir, id) {
|
|
45
|
+
const p = join(dir, `${id}.json`);
|
|
46
|
+
if (!existsSync(p)) return null;
|
|
28
47
|
try {
|
|
29
|
-
|
|
30
|
-
return enrichScope(raw, opts);
|
|
48
|
+
return JSON.parse(readFileSync(p, 'utf8'));
|
|
31
49
|
} catch {
|
|
32
50
|
return null;
|
|
33
51
|
}
|
|
34
52
|
}
|
|
35
53
|
|
|
54
|
+
function resolveScopeRaw(id, cwd) {
|
|
55
|
+
const tiers = [SCOPES_DIR, userScopesDir(), projectScopesDir(cwd)];
|
|
56
|
+
let merged = null;
|
|
57
|
+
for (const dir of tiers) {
|
|
58
|
+
const raw = readScopeTier(dir, id);
|
|
59
|
+
if (!raw) continue;
|
|
60
|
+
merged = merged ? { ...merged, ...raw } : raw;
|
|
61
|
+
}
|
|
62
|
+
return merged;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function loadScope(id, opts = {}) {
|
|
66
|
+
if (!id || typeof id !== 'string') return null;
|
|
67
|
+
const cwd = opts.cwd || process.cwd();
|
|
68
|
+
const raw = resolveScopeRaw(id, cwd);
|
|
69
|
+
if (!raw) return null;
|
|
70
|
+
return enrichScope(raw, opts);
|
|
71
|
+
}
|
|
72
|
+
|
|
36
73
|
export function listScopes() {
|
|
37
74
|
if (!existsSync(SCOPES_DIR)) return [];
|
|
38
75
|
return readdirSync(SCOPES_DIR)
|
|
@@ -64,7 +101,7 @@ export function loadCustomScope(cwd) {
|
|
|
64
101
|
|
|
65
102
|
export function resolveActiveScope(cwd, configScopeId = null) {
|
|
66
103
|
const rootDir = REPO_ROOT;
|
|
67
|
-
const enrichOpts = { rootDir };
|
|
104
|
+
const enrichOpts = { rootDir, cwd };
|
|
68
105
|
if (configScopeId) {
|
|
69
106
|
const s = loadScope(configScopeId, enrichOpts);
|
|
70
107
|
if (s) return s;
|
package/lib/scopes/teams.mjs
CHANGED
|
@@ -10,7 +10,7 @@ const OPERATIONS_OBJECTIVE_RULES = [
|
|
|
10
10
|
{
|
|
11
11
|
teamId: 'operations-team',
|
|
12
12
|
focus: 'reliability',
|
|
13
|
-
attachTo: ['cx-
|
|
13
|
+
attachTo: ['cx-operations'],
|
|
14
14
|
pattern: /\b(incident|outage|deploy|deployment|rollback|reliability|runbook|postmortem|sre|on-?call)\b/i,
|
|
15
15
|
},
|
|
16
16
|
{
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/security/ingest-boundary.mjs — Source-kind → trust-level mapping at the
|
|
3
|
+
* ingest boundary.
|
|
4
|
+
*
|
|
5
|
+
* Maps named source kinds (provider type, pipeline stage, ingestion method)
|
|
6
|
+
* to canonical trust levels and stamps them onto records using the primitives
|
|
7
|
+
* in lib/security/trust.mjs.
|
|
8
|
+
*
|
|
9
|
+
* The module is additive — existing ingest, embed, or storage code is not modified.
|
|
10
|
+
* Callers that want labeling import `stampIngestBoundary` and
|
|
11
|
+
* apply it at the point where external content enters Construct's data layer.
|
|
12
|
+
*
|
|
13
|
+
* References: CX-AUDIT-LLMSEC-001, construct-9oi4.14.1
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { TRUST_LEVELS, stampTrust } from './trust.mjs';
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Source-kind registry
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Mapping from well-known source kind strings to trust levels.
|
|
24
|
+
* All entries are lowercase; callers should normalise before lookup.
|
|
25
|
+
*/
|
|
26
|
+
const SOURCE_KIND_MAP = {
|
|
27
|
+
// Built-in / internal
|
|
28
|
+
'builtin-prompt': TRUST_LEVELS.TRUSTED_INTERNAL,
|
|
29
|
+
'validated-pack': TRUST_LEVELS.TRUSTED_INTERNAL,
|
|
30
|
+
|
|
31
|
+
// Team-authored local content
|
|
32
|
+
'team-authored': TRUST_LEVELS.TEAM_AUTHORED,
|
|
33
|
+
'local-committed': TRUST_LEVELS.TEAM_AUTHORED,
|
|
34
|
+
|
|
35
|
+
// Authenticated external providers
|
|
36
|
+
'github-issue': TRUST_LEVELS.EXTERNAL_AUTHENTICATED,
|
|
37
|
+
'github-pr': TRUST_LEVELS.EXTERNAL_AUTHENTICATED,
|
|
38
|
+
'github-comment': TRUST_LEVELS.EXTERNAL_AUTHENTICATED,
|
|
39
|
+
'jira-ticket': TRUST_LEVELS.EXTERNAL_AUTHENTICATED,
|
|
40
|
+
'jira-comment': TRUST_LEVELS.EXTERNAL_AUTHENTICATED,
|
|
41
|
+
'confluence-page': TRUST_LEVELS.EXTERNAL_AUTHENTICATED,
|
|
42
|
+
'linear-issue': TRUST_LEVELS.EXTERNAL_AUTHENTICATED,
|
|
43
|
+
|
|
44
|
+
// Unauthenticated / parsed / scraped content
|
|
45
|
+
'docling-parsed': TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED,
|
|
46
|
+
'web-fetched': TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED,
|
|
47
|
+
'web-search-result': TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED,
|
|
48
|
+
'pdf-extracted': TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED,
|
|
49
|
+
'unknown': TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolve the trust level for a given source kind string.
|
|
54
|
+
* Unrecognised kinds default to EXTERNAL_UNAUTHENTICATED (fail-safe).
|
|
55
|
+
*
|
|
56
|
+
* @param {string} sourceKind
|
|
57
|
+
* @returns {string} TRUST_LEVELS value
|
|
58
|
+
*/
|
|
59
|
+
export function resolveTrustLevel(sourceKind) {
|
|
60
|
+
const key = (sourceKind ?? 'unknown').toLowerCase();
|
|
61
|
+
return SOURCE_KIND_MAP[key] ?? TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Stamp a trust label onto a record at the ingest boundary.
|
|
66
|
+
*
|
|
67
|
+
* Maps `sourceKind` to the appropriate trust level and delegates to
|
|
68
|
+
* `stampTrust`. The resulting record has a `_trust` field that downstream
|
|
69
|
+
* consumers (recall wrappers, context assemblers) can inspect.
|
|
70
|
+
*
|
|
71
|
+
* @param {Record<string, unknown>} record The record being ingested.
|
|
72
|
+
* @param {string} sourceKind Well-known source kind string.
|
|
73
|
+
* @param {{ sourceRef?: string }} [options] Optional extra metadata.
|
|
74
|
+
* @returns {Record<string, unknown>} Stamped record (source record not mutated).
|
|
75
|
+
*/
|
|
76
|
+
export function stampIngestBoundary(record, sourceKind, options = {}) {
|
|
77
|
+
const level = resolveTrustLevel(sourceKind);
|
|
78
|
+
const source = options.sourceRef ?? sourceKind ?? 'unknown';
|
|
79
|
+
return stampTrust(record, level, source);
|
|
80
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/security/recall-wrapper.mjs — Trust-aware context assembly for recalled
|
|
3
|
+
* records.
|
|
4
|
+
*
|
|
5
|
+
* Before a set of recalled observations or documents is assembled into model
|
|
6
|
+
* context, callers pass them through `wrapForContextAssembly`. Records
|
|
7
|
+
* stamped as external (authenticated or unauthenticated) are wrapped with
|
|
8
|
+
* explicit untrusted delimiters. Internal and team-authored records pass
|
|
9
|
+
* through unchanged. Unstamped records are warned and treated as the most
|
|
10
|
+
* restrictive level: EXTERNAL_UNAUTHENTICATED.
|
|
11
|
+
*
|
|
12
|
+
* The module is additive — existing memory, recall, or context assembly code is not modified.
|
|
13
|
+
* Integration is left to follow-on beads (N2, N4).
|
|
14
|
+
*
|
|
15
|
+
* References: CX-AUDIT-LLMSEC-001, construct-9oi4.14.1
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { TRUST_LEVELS, recallTrustGrade, stampTrust, wrapUntrusted } from './trust.mjs';
|
|
19
|
+
|
|
20
|
+
// Trust levels that require the untrusted-delimiter treatment.
|
|
21
|
+
const UNTRUSTED_LEVELS = new Set([
|
|
22
|
+
TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED,
|
|
23
|
+
TRUST_LEVELS.EXTERNAL_AUTHENTICATED,
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Wrap a single recalled record for safe context assembly.
|
|
28
|
+
*
|
|
29
|
+
* - TRUSTED_INTERNAL / TEAM_AUTHORED → returned as-is.
|
|
30
|
+
* - EXTERNAL_AUTHENTICATED / EXTERNAL_UNAUTHENTICATED → content wrapped with
|
|
31
|
+
* `[UNTRUSTED:…]` delimiters in both the `content` field (if present) and
|
|
32
|
+
* as a `_wrappedContent` convenience field.
|
|
33
|
+
* - No `_trust` stamp → warned, treated as EXTERNAL_UNAUTHENTICATED.
|
|
34
|
+
*
|
|
35
|
+
* @param {Record<string, unknown>} record A recalled observation/document.
|
|
36
|
+
* @param {{ warn?: (msg: string) => void }} [opts]
|
|
37
|
+
* @returns {Record<string, unknown>} Wrapped or passthrough record.
|
|
38
|
+
*/
|
|
39
|
+
export function wrapRecordForContext(record, opts = {}) {
|
|
40
|
+
const warn = opts.warn ?? ((msg) => console.warn('[recall-wrapper]', msg));
|
|
41
|
+
let trustMeta = recallTrustGrade(record);
|
|
42
|
+
|
|
43
|
+
if (trustMeta === null) {
|
|
44
|
+
warn(
|
|
45
|
+
`Recalled record has no _trust stamp — treating as ${TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED}. ` +
|
|
46
|
+
`Record keys: ${Object.keys(record ?? {}).join(', ') || '(empty)'}`,
|
|
47
|
+
);
|
|
48
|
+
// Re-stamp defensively so downstream consumers always see _trust.
|
|
49
|
+
const stamped = stampTrust(
|
|
50
|
+
record,
|
|
51
|
+
TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED,
|
|
52
|
+
'unstamped-recall',
|
|
53
|
+
);
|
|
54
|
+
trustMeta = stamped._trust;
|
|
55
|
+
record = stamped;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!UNTRUSTED_LEVELS.has(trustMeta.level)) {
|
|
59
|
+
// TRUSTED_INTERNAL or TEAM_AUTHORED — pass through unchanged.
|
|
60
|
+
return record;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// External content: wrap any present content field and attach _wrappedContent.
|
|
64
|
+
const rawContent = record.content ?? record.text ?? record.body ?? null;
|
|
65
|
+
const wrapped = rawContent != null
|
|
66
|
+
? wrapUntrusted(String(rawContent), trustMeta)
|
|
67
|
+
: null;
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
...record,
|
|
71
|
+
...(wrapped != null ? { _wrappedContent: wrapped } : {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Wrap an array of recalled records for safe context assembly.
|
|
77
|
+
*
|
|
78
|
+
* Iterates each record through `wrapRecordForContext`. The returned array
|
|
79
|
+
* preserves original order. Non-object entries are returned as-is with a
|
|
80
|
+
* warning (defensive).
|
|
81
|
+
*
|
|
82
|
+
* @param {unknown[]} records Array of recalled observations or documents.
|
|
83
|
+
* @param {{ warn?: (msg: string) => void }} [opts]
|
|
84
|
+
* @returns {unknown[]} Array of wrapped/passthrough records.
|
|
85
|
+
*/
|
|
86
|
+
export function wrapForContextAssembly(records, opts = {}) {
|
|
87
|
+
if (!Array.isArray(records)) {
|
|
88
|
+
throw new TypeError('wrapForContextAssembly: records must be an array');
|
|
89
|
+
}
|
|
90
|
+
const warn = opts.warn ?? ((msg) => console.warn('[recall-wrapper]', msg));
|
|
91
|
+
|
|
92
|
+
return records.map((record, idx) => {
|
|
93
|
+
if (record === null || typeof record !== 'object') {
|
|
94
|
+
warn(`Record at index ${idx} is not an object — skipping wrap (type: ${typeof record})`);
|
|
95
|
+
return record;
|
|
96
|
+
}
|
|
97
|
+
return wrapRecordForContext(record, { warn });
|
|
98
|
+
});
|
|
99
|
+
}
|