@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
|
@@ -3,30 +3,53 @@
|
|
|
3
3
|
* audit trail.
|
|
4
4
|
*
|
|
5
5
|
* enableSecretAuditTrail routes the value-free events from secret-resolver into the
|
|
6
|
-
* append-only audit log (lib/audit-trail.mjs).
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* append-only audit log (lib/audit-trail.mjs). Two event types are recorded:
|
|
7
|
+
* - secret.op_read: an actual op:// CLI read (prompt-bearing, never cached)
|
|
8
|
+
* - secret.resolve (non-op): any plaintext tier resolution (project-env, config-env,
|
|
9
|
+
* home-env, shell-rc, alt-store) — records the source tier, never the value
|
|
10
|
+
* Cache hits on op reads are skipped; op-ref resolve events are skipped because
|
|
11
|
+
* their paired op_read event carries the canonical record. The materialized secret
|
|
12
|
+
* value is never available to this layer and is never written.
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
import { appendAuditRecord } from '../audit-trail.mjs';
|
|
15
16
|
import { setSecretAuditSink } from './secret-resolver.mjs';
|
|
16
17
|
|
|
17
18
|
export function enableSecretAuditTrail({ file } = {}) {
|
|
19
|
+
const sink = file ? { file } : {};
|
|
18
20
|
setSecretAuditSink((event) => {
|
|
19
|
-
if (event.event
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
if (event.event === 'secret.op_read') {
|
|
22
|
+
if (event.cacheHit) return;
|
|
23
|
+
|
|
24
|
+
// Persisting the op:// ref (vault/item/field — not the value) into
|
|
25
|
+
// Construct's own audit JSONL is a recorded design decision (remediation
|
|
26
|
+
// plan Epic 6, construct-trxz.6), distinct from the mcp-platform-config.mjs
|
|
27
|
+
// prohibition on writing op:// refs into host-readable MCP config files.
|
|
28
|
+
|
|
29
|
+
appendAuditRecord(
|
|
30
|
+
{
|
|
31
|
+
ts: new Date().toISOString(),
|
|
32
|
+
tool: 'secret-resolver',
|
|
33
|
+
action: 'op_read',
|
|
34
|
+
ref: event.ref,
|
|
35
|
+
ok: event.ok,
|
|
36
|
+
code: event.code ?? null,
|
|
37
|
+
},
|
|
38
|
+
sink,
|
|
39
|
+
);
|
|
40
|
+
} else if (event.event === 'secret.resolve' && !event.isOpRef) {
|
|
41
|
+
appendAuditRecord(
|
|
42
|
+
{
|
|
43
|
+
ts: new Date().toISOString(),
|
|
44
|
+
tool: 'secret-resolver',
|
|
45
|
+
action: 'resolve',
|
|
46
|
+
varName: event.varName,
|
|
47
|
+
source: event.source ?? null,
|
|
48
|
+
ok: event.ok,
|
|
49
|
+
code: event.code ?? null,
|
|
50
|
+
},
|
|
51
|
+
sink,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
31
54
|
});
|
|
32
55
|
}
|
|
@@ -12,11 +12,12 @@
|
|
|
12
12
|
* `$(op read 'op://...')` — it is resolved through the `op` CLI and cached for
|
|
13
13
|
* the process lifetime so a single launch prompts at most once per reference.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
15
|
+
* The process.env tier follows one precedence ladder shared with loadConstructEnv:
|
|
16
|
+
* when file discovery is enabled (allowAmbient), the file tiers win over an ambient
|
|
17
|
+
* shell export so a stale export cannot shadow a managed config value, with the same
|
|
18
|
+
* carve-out that a materialized credential injected on the env beats a stored op://
|
|
19
|
+
* reference (preferInjectedCredential). A hermetic caller sets allowAmbient:false to
|
|
20
|
+
* suppress all file/rc discovery and use only the directly-injected env.
|
|
20
21
|
*
|
|
21
22
|
* resolveSecret performs the `op read` (and may surface a structured
|
|
22
23
|
* SecretResolutionError); hasSecret only checks presence and never invokes the
|
|
@@ -24,14 +25,20 @@
|
|
|
24
25
|
* value lives only in this module's cache and is never logged. allowAmbient lets
|
|
25
26
|
* a hermetic caller (tests, embedded callers that inject their own env) suppress
|
|
26
27
|
* file/rc discovery so a developer key never bleeds into an isolated run.
|
|
28
|
+
* peekRawCredential shares the same tiers for a non-secret display value (a repo
|
|
29
|
+
* slug, a host URL) — shape-only, never resolving an op:// reference. Any
|
|
30
|
+
* status/detection surface (ADR-0049 §2) should use hasSecret/hasAnySecret/
|
|
31
|
+
* peekRawCredential rather than reimplementing file or shell-rc scanning.
|
|
27
32
|
*/
|
|
28
33
|
|
|
29
34
|
import fs from 'node:fs';
|
|
30
35
|
import path from 'node:path';
|
|
31
36
|
import os from 'node:os';
|
|
32
|
-
import { spawnSync } from 'node:child_process';
|
|
37
|
+
import { spawnSync, spawn } from 'node:child_process';
|
|
33
38
|
import { discoverAlternateRawForVar } from './credential-sources.mjs';
|
|
39
|
+
import { locateOpBinary } from './op-locate.mjs';
|
|
34
40
|
import { configDir } from '../config/xdg.mjs';
|
|
41
|
+
import { preferInjectedCredential, parseEnvContent } from '../env-config.mjs';
|
|
35
42
|
|
|
36
43
|
// The 1Password desktop integration prompts for biometric unlock on the first
|
|
37
44
|
// read after the vault locks, and that interactive round-trip routinely takes
|
|
@@ -89,30 +96,145 @@ export function extractOpRef(rawValue) {
|
|
|
89
96
|
return m ? m[2] : null;
|
|
90
97
|
}
|
|
91
98
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
99
|
+
// A 1Password service account (OP_SERVICE_ACCOUNT_TOKEN) makes `op read`/`op run`
|
|
100
|
+
// non-interactive — no desktop app, no biometric, no `op signin` — which fits a
|
|
101
|
+
// multi-process / daemon / headless runtime and CI. It is opt-in: with no token the
|
|
102
|
+
// desktop app integration remains the default. The `op` CLI auto-detects the token
|
|
103
|
+
// from its process env; forwarding it from the caller's env (not just the ambient
|
|
104
|
+
// shell) lets Construct source it from the OS keychain at runtime rather than
|
|
105
|
+
// storing it in plaintext. Note: a service account cannot read the built-in
|
|
106
|
+
// Personal/Private vault and is rate-limited (1k reads/day on the Individual tier),
|
|
107
|
+
// so it pairs with resolve-once, not per-process op read.
|
|
108
|
+
// Docs: https://developer.1password.com/docs/service-accounts/
|
|
109
|
+
|
|
110
|
+
export function opServiceAccountToken(env = process.env) {
|
|
111
|
+
const token = env?.OP_SERVICE_ACCOUNT_TOKEN;
|
|
112
|
+
return typeof token === 'string' && token.trim() ? token.trim() : null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function defaultOpWhoami() {
|
|
116
|
+
const opPath = locateOpBinary();
|
|
117
|
+
if (!opPath) return { installed: false, signedIn: false };
|
|
118
|
+
const result = spawnSync(opPath, ['whoami'], { encoding: 'utf8', timeout: 5000 });
|
|
119
|
+
if (result.error && result.error.code === 'ENOENT') return { installed: false, signedIn: false };
|
|
120
|
+
return { installed: true, signedIn: result.status === 0 };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Which 1Password auth mode `op` will use for a resolution, for doctor/diagnostics.
|
|
124
|
+
// A service-account token is non-interactive; otherwise `op` relies on the desktop
|
|
125
|
+
// app session, and `op whoami` reveals whether that session is live (a cold session
|
|
126
|
+
// is the usual cause of repeated biometric prompts). `whoami` is checked, not `read`,
|
|
127
|
+
// so it never prompts. Injectable for tests.
|
|
128
|
+
|
|
129
|
+
export function describeOpAuthMode(env = process.env, { whoami = defaultOpWhoami } = {}) {
|
|
130
|
+
if (opServiceAccountToken(env)) {
|
|
131
|
+
return { mode: 'service-account', signedIn: true, detail: 'OP_SERVICE_ACCOUNT_TOKEN set — op read/op run authenticate non-interactively.' };
|
|
132
|
+
}
|
|
133
|
+
const state = whoami();
|
|
134
|
+
if (!state.installed) {
|
|
135
|
+
return { mode: 'op-absent', signedIn: false, detail: '1Password CLI (op) not installed — set keys directly or install op.' };
|
|
136
|
+
}
|
|
137
|
+
if (state.signedIn) {
|
|
138
|
+
return { mode: 'desktop-session', signedIn: true, detail: 'op is signed in via the desktop app session.' };
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
mode: 'desktop-session',
|
|
142
|
+
signedIn: false,
|
|
143
|
+
detail: 'op is not signed in — enable Settings → Developer → Integrate with 1Password CLI, or set OP_SERVICE_ACCOUNT_TOKEN for non-interactive auth.',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function opReadChildEnv(env) {
|
|
148
|
+
const token = opServiceAccountToken(env);
|
|
149
|
+
return token ? { ...process.env, OP_SERVICE_ACCOUNT_TOKEN: token } : process.env;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Map an `op read` outcome — from the sync spawnSync reader or the async spawn reader —
|
|
153
|
+
// to the resolved secret or a classified SecretResolutionError. Both readers funnel
|
|
154
|
+
// through here so their error taxonomy (OP_NOT_INSTALLED / OP_NOT_SIGNED_IN /
|
|
155
|
+
// OP_READ_FAILED / OP_EMPTY) cannot drift apart.
|
|
156
|
+
|
|
157
|
+
function interpretOpReadResult(opRef, { enoent, status, stdout, stderr }) {
|
|
158
|
+
if (enoent) {
|
|
95
159
|
throw new SecretResolutionError('1Password CLI not found — install `op` or set the key directly.', 'OP_NOT_INSTALLED');
|
|
96
160
|
}
|
|
97
|
-
if (
|
|
98
|
-
const
|
|
99
|
-
if (/sign in|signin|session|not currently|authenticate|authorization|no account/.test(
|
|
100
|
-
throw new SecretResolutionError('1Password CLI is not signed in —
|
|
161
|
+
if (status !== 0) {
|
|
162
|
+
const err = String(stderr || '').toLowerCase();
|
|
163
|
+
if (/sign in|signin|session|not currently|authenticate|authorization|no account/.test(err)) {
|
|
164
|
+
throw new SecretResolutionError('1Password CLI is not signed in — enable the desktop app CLI integration (Settings → Developer) and unlock, or set OP_SERVICE_ACCOUNT_TOKEN for non-interactive access.', 'OP_NOT_SIGNED_IN');
|
|
101
165
|
}
|
|
102
|
-
throw new SecretResolutionError(`op read failed for ${opRef}: ${String(
|
|
166
|
+
throw new SecretResolutionError(`op read failed for ${opRef}: ${String(stderr || '').trim().slice(0, 160)}`, 'OP_READ_FAILED');
|
|
103
167
|
}
|
|
104
|
-
const secret = String(
|
|
168
|
+
const secret = String(stdout || '').trim();
|
|
105
169
|
if (!secret) throw new SecretResolutionError(`op read returned an empty value for ${opRef}`, 'OP_EMPTY');
|
|
106
170
|
return secret;
|
|
107
171
|
}
|
|
108
172
|
|
|
109
|
-
|
|
173
|
+
function defaultOpRead(opRef, { env = process.env } = {}) {
|
|
174
|
+
const opPath = locateOpBinary();
|
|
175
|
+
if (!opPath) {
|
|
176
|
+
throw new SecretResolutionError('1Password CLI not found — install `op` or set the key directly.', 'OP_NOT_INSTALLED');
|
|
177
|
+
}
|
|
178
|
+
const result = spawnSync(opPath, ['read', opRef], { encoding: 'utf8', timeout: OP_READ_TIMEOUT_MS, env: opReadChildEnv(env) });
|
|
179
|
+
return interpretOpReadResult(opRef, {
|
|
180
|
+
enoent: Boolean(result.error && result.error.code === 'ENOENT'),
|
|
181
|
+
status: result.status,
|
|
182
|
+
stdout: result.stdout,
|
|
183
|
+
stderr: result.stderr,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// The MCP CallTool path resolves provider keys inside the single-threaded server, so
|
|
188
|
+
// a spawnSync `op read` freezes the event loop — and its own Promise.race timeout
|
|
189
|
+
// guard — for up to OP_READ_TIMEOUT_MS on the first op:// touch. This async reader
|
|
190
|
+
// uses spawn so the loop keeps turning, killing the child at the same timeout and
|
|
191
|
+
// mapping the outcome through the same interpretOpReadResult.
|
|
192
|
+
|
|
193
|
+
function defaultOpReadAsync(opRef, { env = process.env } = {}) {
|
|
194
|
+
return new Promise((resolve, reject) => {
|
|
195
|
+
const opPath = locateOpBinary();
|
|
196
|
+
if (!opPath) {
|
|
197
|
+
reject(new SecretResolutionError('1Password CLI not found — install `op` or set the key directly.', 'OP_NOT_INSTALLED'));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const child = spawn(opPath, ['read', opRef], { env: opReadChildEnv(env) });
|
|
201
|
+
let stdout = '';
|
|
202
|
+
let stderr = '';
|
|
203
|
+
let settled = false;
|
|
204
|
+
let timer = null;
|
|
205
|
+
const finish = (fn) => {
|
|
206
|
+
if (settled) return;
|
|
207
|
+
settled = true;
|
|
208
|
+
if (timer) clearTimeout(timer);
|
|
209
|
+
fn();
|
|
210
|
+
};
|
|
211
|
+
timer = setTimeout(() => {
|
|
212
|
+
try { child.kill('SIGKILL'); } catch { /* already exited */ }
|
|
213
|
+
finish(() => reject(new SecretResolutionError(`op read failed for ${opRef}: timed out after ${OP_READ_TIMEOUT_MS}ms`, 'OP_READ_FAILED')));
|
|
214
|
+
}, OP_READ_TIMEOUT_MS);
|
|
215
|
+
child.stdout?.on('data', (d) => { stdout += String(d); });
|
|
216
|
+
child.stderr?.on('data', (d) => { stderr += String(d); });
|
|
217
|
+
child.on('error', (opErr) => finish(() => {
|
|
218
|
+
if (opErr && opErr.code === 'ENOENT') {
|
|
219
|
+
reject(new SecretResolutionError('1Password CLI not found — install `op` or set the key directly.', 'OP_NOT_INSTALLED'));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
reject(opErr);
|
|
223
|
+
}));
|
|
224
|
+
child.on('close', (code) => finish(() => {
|
|
225
|
+
try { resolve(interpretOpReadResult(opRef, { enoent: false, status: code, stdout, stderr })); }
|
|
226
|
+
catch (err) { reject(err); }
|
|
227
|
+
}));
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function resolveOpRef(opRef, { opRead = defaultOpRead, env = process.env } = {}) {
|
|
110
232
|
if (opCache.has(opRef)) {
|
|
111
233
|
emitAudit({ event: 'secret.op_read', ref: opRef, cacheHit: true, ok: true });
|
|
112
234
|
return opCache.get(opRef);
|
|
113
235
|
}
|
|
114
236
|
try {
|
|
115
|
-
const secret = opRead(opRef);
|
|
237
|
+
const secret = opRead(opRef, { env });
|
|
116
238
|
opCache.set(opRef, secret);
|
|
117
239
|
emitAudit({ event: 'secret.op_read', ref: opRef, cacheHit: false, ok: true });
|
|
118
240
|
return secret;
|
|
@@ -127,11 +249,17 @@ function materialize(rawValue, opts) {
|
|
|
127
249
|
return ref ? resolveOpRef(ref, opts) : unquote(rawValue);
|
|
128
250
|
}
|
|
129
251
|
|
|
252
|
+
// Delegates to env-config's parseEnvContent so a dotenv value never diverges
|
|
253
|
+
// from loadConstructEnv's reading of the same file (construct-192h.10) — in
|
|
254
|
+
// particular an unpaired quote (`KEY="abc`) resolves to the same byte-identical
|
|
255
|
+
// value on both paths instead of the resolver's own quote-pairing regex leaving
|
|
256
|
+
// the stray quote in place.
|
|
257
|
+
|
|
130
258
|
function readDotenvVar(file, varName) {
|
|
131
259
|
try {
|
|
132
260
|
if (!fs.existsSync(file)) return null;
|
|
133
|
-
const
|
|
134
|
-
return
|
|
261
|
+
const parsed = parseEnvContent(fs.readFileSync(file, 'utf8'));
|
|
262
|
+
return Object.prototype.hasOwnProperty.call(parsed, varName) ? parsed[varName] : null;
|
|
135
263
|
} catch {
|
|
136
264
|
return null;
|
|
137
265
|
}
|
|
@@ -181,9 +309,8 @@ function readFromOpEnvCatalog(varName, { env, home }) {
|
|
|
181
309
|
}
|
|
182
310
|
|
|
183
311
|
function rawCandidate(varName, { env, cwd, home }) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
312
|
+
// Pure file/store discovery: the ambient env tier is ranked by the callers
|
|
313
|
+
// (resolveSecret / hasSecret) so one precedence ladder governs env-vs-file.
|
|
187
314
|
// File tier matches loadConstructEnv: the project .env (closest to the work)
|
|
188
315
|
// wins over the machine-wide XDG config.env, which wins over a generic ~/.env.
|
|
189
316
|
const files = [
|
|
@@ -218,18 +345,32 @@ function resolveAndAudit(varName, raw, source, opts) {
|
|
|
218
345
|
|
|
219
346
|
export function resolveSecret(varName, { env = process.env, cwd = process.cwd(), allowAmbient = true, opRead } = {}) {
|
|
220
347
|
const direct = env?.[varName];
|
|
221
|
-
|
|
348
|
+
const hasDirect = typeof direct === 'string' && direct.length > 0;
|
|
349
|
+
|
|
350
|
+
// Hermetic mode: only the directly-injected env is consulted, no file/rc discovery,
|
|
351
|
+
// so a developer key never bleeds into an isolated run.
|
|
222
352
|
if (!allowAmbient) {
|
|
353
|
+
if (hasDirect) return resolveAndAudit(varName, direct, 'env', { opRead, env });
|
|
223
354
|
emitAudit({ event: 'secret.resolve', varName, source: null, isOpRef: false, ok: false });
|
|
224
355
|
return null;
|
|
225
356
|
}
|
|
357
|
+
|
|
226
358
|
const home = os.homedir();
|
|
227
359
|
const found = rawCandidate(varName, { env, cwd, home });
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
360
|
+
|
|
361
|
+
// Precedence parity with loadConstructEnv: the file tiers win over an ambient shell
|
|
362
|
+
// export, except a materialized credential injected on the env beats a stored op://
|
|
363
|
+
// reference so a single `op run` auth suffices.
|
|
364
|
+
if (found) {
|
|
365
|
+
if (hasDirect && preferInjectedCredential(direct, found.raw)) {
|
|
366
|
+
return resolveAndAudit(varName, direct, 'env', { opRead, env });
|
|
367
|
+
}
|
|
368
|
+
return resolveAndAudit(varName, found.raw, found.source, { opRead, env });
|
|
231
369
|
}
|
|
232
|
-
|
|
370
|
+
|
|
371
|
+
if (hasDirect) return resolveAndAudit(varName, direct, 'env', { opRead, env });
|
|
372
|
+
emitAudit({ event: 'secret.resolve', varName, source: null, isOpRef: false, ok: false });
|
|
373
|
+
return null;
|
|
233
374
|
}
|
|
234
375
|
|
|
235
376
|
export function resolveFirstSecret(varNames, opts = {}) {
|
|
@@ -240,15 +381,81 @@ export function resolveFirstSecret(varNames, opts = {}) {
|
|
|
240
381
|
return null;
|
|
241
382
|
}
|
|
242
383
|
|
|
384
|
+
// Async twins for the MCP CallTool hot path: file/rc discovery stays synchronous
|
|
385
|
+
// (small local fs reads, not the blocking step), but op:// materialization uses
|
|
386
|
+
// defaultOpReadAsync (spawn) instead of spawnSync so the single-threaded MCP server's
|
|
387
|
+
// event loop keeps turning during a slow `op read`. The shared opCache means a value
|
|
388
|
+
// resolved by either the sync or async path serves both, so a warm ref never re-spawns.
|
|
389
|
+
|
|
390
|
+
export async function resolveOpRefAsync(opRef, { opRead = defaultOpReadAsync, env = process.env } = {}) {
|
|
391
|
+
if (opCache.has(opRef)) {
|
|
392
|
+
emitAudit({ event: 'secret.op_read', ref: opRef, cacheHit: true, ok: true });
|
|
393
|
+
return opCache.get(opRef);
|
|
394
|
+
}
|
|
395
|
+
try {
|
|
396
|
+
const secret = await opRead(opRef, { env });
|
|
397
|
+
opCache.set(opRef, secret);
|
|
398
|
+
emitAudit({ event: 'secret.op_read', ref: opRef, cacheHit: false, ok: true });
|
|
399
|
+
return secret;
|
|
400
|
+
} catch (err) {
|
|
401
|
+
emitAudit({ event: 'secret.op_read', ref: opRef, cacheHit: false, ok: false, code: err?.code });
|
|
402
|
+
throw err;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async function materializeAsync(rawValue, opts) {
|
|
407
|
+
const ref = extractOpRef(rawValue);
|
|
408
|
+
return ref ? resolveOpRefAsync(ref, opts) : unquote(rawValue);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async function resolveAndAuditAsync(varName, raw, source, opts) {
|
|
412
|
+
const isOpRef = Boolean(extractOpRef(raw));
|
|
413
|
+
try {
|
|
414
|
+
const value = await materializeAsync(raw, opts);
|
|
415
|
+
emitAudit({ event: 'secret.resolve', varName, source, isOpRef, ok: true });
|
|
416
|
+
return value;
|
|
417
|
+
} catch (err) {
|
|
418
|
+
emitAudit({ event: 'secret.resolve', varName, source, isOpRef, ok: false, code: err?.code });
|
|
419
|
+
throw err;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export async function resolveSecretAsync(varName, { env = process.env, cwd = process.cwd(), allowAmbient = true, opRead } = {}) {
|
|
424
|
+
const direct = env?.[varName];
|
|
425
|
+
const hasDirect = typeof direct === 'string' && direct.length > 0;
|
|
426
|
+
|
|
427
|
+
if (!allowAmbient) {
|
|
428
|
+
if (hasDirect) return resolveAndAuditAsync(varName, direct, 'env', { opRead, env });
|
|
429
|
+
emitAudit({ event: 'secret.resolve', varName, source: null, isOpRef: false, ok: false });
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const home = os.homedir();
|
|
434
|
+
const found = rawCandidate(varName, { env, cwd, home });
|
|
435
|
+
|
|
436
|
+
if (found) {
|
|
437
|
+
if (hasDirect && preferInjectedCredential(direct, found.raw)) {
|
|
438
|
+
return resolveAndAuditAsync(varName, direct, 'env', { opRead, env });
|
|
439
|
+
}
|
|
440
|
+
return resolveAndAuditAsync(varName, found.raw, found.source, { opRead, env });
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (hasDirect) return resolveAndAuditAsync(varName, direct, 'env', { opRead, env });
|
|
444
|
+
emitAudit({ event: 'secret.resolve', varName, source: null, isOpRef: false, ok: false });
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
|
|
243
448
|
// Presence check that never runs `op read`: a stored op:// reference counts as
|
|
244
|
-
// configured because the plaintext is resolved lazily at call time.
|
|
449
|
+
// configured because the plaintext is resolved lazily at call time. `home`
|
|
450
|
+
// defaults to os.homedir() but is overridable so a status/detection caller can
|
|
451
|
+
// point at an injected homeDir without mutating process.env.HOME.
|
|
245
452
|
|
|
246
|
-
export function hasSecret(varName, { env = process.env, cwd = process.cwd(), allowAmbient = true } = {}) {
|
|
453
|
+
export function hasSecret(varName, { env = process.env, cwd = process.cwd(), allowAmbient = true, home } = {}) {
|
|
247
454
|
const direct = env?.[varName];
|
|
248
455
|
if (typeof direct === 'string' && direct.length > 0) return true;
|
|
249
456
|
if (!allowAmbient) return false;
|
|
250
|
-
const
|
|
251
|
-
const found = rawCandidate(varName, { env, cwd, home });
|
|
457
|
+
const resolvedHome = home || os.homedir();
|
|
458
|
+
const found = rawCandidate(varName, { env, cwd, home: resolvedHome });
|
|
252
459
|
return Boolean(found && typeof found.raw === 'string' && found.raw.length > 0);
|
|
253
460
|
}
|
|
254
461
|
|
|
@@ -256,6 +463,31 @@ export function hasAnySecret(varNames, opts = {}) {
|
|
|
256
463
|
return varNames.some((name) => hasSecret(name, opts));
|
|
257
464
|
}
|
|
258
465
|
|
|
466
|
+
// A non-secret configuration value (a repo slug, a Jira host URL, a Confluence
|
|
467
|
+
// space key) still needs shape-based detection — file/rc presence and its raw
|
|
468
|
+
// text — without ever resolving an op:// reference, so a display-only read
|
|
469
|
+
// shares the same tiers as hasSecret rather than a caller re-implementing
|
|
470
|
+
// dotenv/rc scanning. Returns null when nothing is found; never invokes `op read`.
|
|
471
|
+
|
|
472
|
+
export function peekRawCredential(varName, { env = process.env, cwd = process.cwd(), allowAmbient = true, home } = {}) {
|
|
473
|
+
const direct = env?.[varName];
|
|
474
|
+
if (typeof direct === 'string' && direct.length > 0) return { raw: unquote(direct), source: 'env' };
|
|
475
|
+
if (!allowAmbient) return null;
|
|
476
|
+
const resolvedHome = home || os.homedir();
|
|
477
|
+
const found = rawCandidate(varName, { env, cwd, home: resolvedHome });
|
|
478
|
+
return found ? { raw: unquote(found.raw), source: found.source } : null;
|
|
479
|
+
}
|
|
480
|
+
|
|
259
481
|
export function __clearSecretCache() {
|
|
260
482
|
opCache.clear();
|
|
261
483
|
}
|
|
484
|
+
|
|
485
|
+
// Read-only accessor for the materialized op:// cache. Callers that redact
|
|
486
|
+
// durable output (lib/audit-trail.mjs) need to compare candidate strings
|
|
487
|
+
// against known-resolved secret values without this module logging or
|
|
488
|
+
// otherwise changing its own resolution behavior; this returns a snapshot
|
|
489
|
+
// array, never the live Map, so a consumer cannot mutate the cache.
|
|
490
|
+
|
|
491
|
+
export function __resolvedSecretValues() {
|
|
492
|
+
return Array.from(opCache.values());
|
|
493
|
+
}
|
package/lib/publish-template.mjs
CHANGED
|
@@ -102,7 +102,7 @@ function stripCoverDuplicatesFromBody(body, metadata = {}) {
|
|
|
102
102
|
out = out.replace(h1Re, '');
|
|
103
103
|
}
|
|
104
104
|
const lines = out.split('\n');
|
|
105
|
-
while (lines.length && /^-\s+\*\*(Date|Owner|Status)
|
|
105
|
+
while (lines.length && /^-\s+\*\*(Date|Owner|Status):?\*\*:?/i.test(lines[0].trim())) {
|
|
106
106
|
lines.shift();
|
|
107
107
|
}
|
|
108
108
|
while (lines.length && lines[0].trim() === '') lines.shift();
|
|
@@ -135,9 +135,12 @@ export function parseArtifactMetadata(inputPath) {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
// Authors write both `**Date**: x` and `**Date:** x`; the contract accepts
|
|
139
|
+
// either so the masthead never silently loses a field to colon placement.
|
|
140
|
+
|
|
138
141
|
const h1 = body.match(/^#\s+(.+)$/m);
|
|
139
|
-
const ownerMatch = body.match(/\*\*Owner
|
|
140
|
-
const dateMatch = body.match(/\*\*Date
|
|
142
|
+
const ownerMatch = body.match(/\*\*Owner:?\*\*:?\s*(\S+)/i);
|
|
143
|
+
const dateMatch = body.match(/\*\*Date:?\*\*:?\s*(\S+)/i);
|
|
141
144
|
|
|
142
145
|
return {
|
|
143
146
|
title: fm.title || (h1 ? h1[1].trim() : path.basename(inputPath, path.extname(inputPath))),
|
package/lib/publish-tooling.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import { detect as detectExport } from './document-export.mjs';
|
|
|
14
14
|
import { locateRenderer } from './diagram.mjs';
|
|
15
15
|
import { locateRecorder } from './demo.mjs';
|
|
16
16
|
import { detectIngestPipeline } from './ingest-tooling.mjs';
|
|
17
|
+
import { resolvePuppeteerExecutable } from './diagram-export.mjs';
|
|
17
18
|
|
|
18
19
|
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
19
20
|
|
|
@@ -32,11 +33,13 @@ export function detectFigureBinaries(env = process.env) {
|
|
|
32
33
|
const d2 = whichBin('d2');
|
|
33
34
|
const mmdc = whichBin('mmdc');
|
|
34
35
|
const dot = whichBin('dot');
|
|
36
|
+
const chrome = resolvePuppeteerExecutable(env);
|
|
35
37
|
const filter = fs.existsSync(diagramFilterPath());
|
|
36
38
|
const missing = [];
|
|
37
39
|
if (!filter) missing.push('diagram.lua (vendor/pandoc-ext/diagram.lua)');
|
|
38
40
|
if (!d2 && !dot) missing.push('d2 or graphviz dot');
|
|
39
41
|
if (!mmdc) missing.push('mmdc (@mermaid-js/mermaid-cli)');
|
|
42
|
+
if (mmdc && !chrome) missing.push('Chrome for mermaid-cli (set PUPPETEER_EXECUTABLE_PATH or install Google Chrome)');
|
|
40
43
|
return {
|
|
41
44
|
ok: true,
|
|
42
45
|
present: missing.length === 0,
|
|
@@ -44,6 +47,7 @@ export function detectFigureBinaries(env = process.env) {
|
|
|
44
47
|
d2,
|
|
45
48
|
dot,
|
|
46
49
|
mmdc,
|
|
50
|
+
chrome,
|
|
47
51
|
missing,
|
|
48
52
|
message: missing.length === 0
|
|
49
53
|
? 'Figure tooling ready (pandoc-ext/diagram + d2/mmdc)'
|
package/lib/publish.mjs
CHANGED
|
@@ -193,13 +193,21 @@ export function runPublish({
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
const exportOk = ledger.export?.ok !== false;
|
|
197
|
+
const validationOk = ledger.validation?.ok !== false;
|
|
198
|
+
const relOutput = ledger.export?.outputPath ? path.relative(cwd, ledger.export.outputPath) : null;
|
|
199
|
+
|
|
196
200
|
return {
|
|
197
|
-
ok:
|
|
201
|
+
ok: exportOk && validationOk,
|
|
198
202
|
ledger,
|
|
199
203
|
message: ledger.export?.skipped
|
|
200
204
|
? 'Publish source-only complete'
|
|
201
|
-
:
|
|
205
|
+
: exportOk && validationOk
|
|
202
206
|
? `Published ${path.relative(cwd, ledger.export.outputPath)}`
|
|
207
|
+
: !validationOk && relOutput
|
|
208
|
+
? preview
|
|
209
|
+
? `Preview rendered with validation failures: ${relOutput}`
|
|
210
|
+
: `Publish blocked: output validation failed for ${relOutput}`
|
|
203
211
|
: ledger.export?.message || detection.message,
|
|
204
212
|
};
|
|
205
213
|
}
|
|
@@ -235,12 +243,17 @@ Examples:
|
|
|
235
243
|
`);
|
|
236
244
|
}
|
|
237
245
|
|
|
238
|
-
|
|
246
|
+
// Every flag the publish spec declares maps to a behavior here; an unrecognized
|
|
247
|
+
// `--flag` is collected into `unknown` so the CLI can warn instead of silently
|
|
248
|
+
// dropping it (the pre-fix parser discarded --recording/--figures without a trace).
|
|
249
|
+
|
|
250
|
+
export function parsePublishArgs(argv = []) {
|
|
239
251
|
const options = {
|
|
240
252
|
format: 'pdf',
|
|
241
253
|
output: null,
|
|
242
254
|
type: null,
|
|
243
255
|
demos: [],
|
|
256
|
+
recordings: [],
|
|
244
257
|
figures: true,
|
|
245
258
|
strict: true,
|
|
246
259
|
gate: true,
|
|
@@ -249,6 +262,7 @@ export async function runPublishCli(argv = [], { cwd = process.cwd(), repoRoot }
|
|
|
249
262
|
preview: false,
|
|
250
263
|
help: false,
|
|
251
264
|
input: null,
|
|
265
|
+
unknown: [],
|
|
252
266
|
};
|
|
253
267
|
|
|
254
268
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -257,6 +271,7 @@ export async function runPublishCli(argv = [], { cwd = process.cwd(), repoRoot }
|
|
|
257
271
|
if (arg === '--strict') { options.strict = true; continue; }
|
|
258
272
|
if (arg === '--no-strict') { options.strict = false; continue; }
|
|
259
273
|
if (arg === '--source-only') { options.sourceOnly = true; continue; }
|
|
274
|
+
if (arg === '--figures') { options.figures = true; continue; }
|
|
260
275
|
if (arg === '--no-figures') { options.figures = false; continue; }
|
|
261
276
|
if (arg === '--no-gate') { options.gate = false; continue; }
|
|
262
277
|
if (arg === '--preview') { options.preview = true; continue; }
|
|
@@ -269,8 +284,17 @@ export async function runPublishCli(argv = [], { cwd = process.cwd(), repoRoot }
|
|
|
269
284
|
if (arg.startsWith('--type=')) { options.type = arg.slice(7); continue; }
|
|
270
285
|
if (arg === '--demo') { options.demos.push(argv[++i]); continue; }
|
|
271
286
|
if (arg.startsWith('--demo=')) { options.demos.push(arg.slice(7)); continue; }
|
|
272
|
-
if (
|
|
287
|
+
if (arg === '--recording') { options.recordings.push(argv[++i]); continue; }
|
|
288
|
+
if (arg.startsWith('--recording=')) { options.recordings.push(arg.slice('--recording='.length)); continue; }
|
|
289
|
+
if (!arg.startsWith('--')) { options.input = arg; continue; }
|
|
290
|
+
options.unknown.push(arg);
|
|
273
291
|
}
|
|
292
|
+
return options;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export async function runPublishCli(argv = [], { cwd = process.cwd(), repoRoot } = {}) {
|
|
296
|
+
const options = parsePublishArgs(argv);
|
|
297
|
+
for (const flag of options.unknown) console.error(`[publish] Ignoring unknown flag: ${flag}`);
|
|
274
298
|
|
|
275
299
|
if (options.help) {
|
|
276
300
|
printPublishHelp();
|
|
@@ -299,6 +323,7 @@ export async function runPublishCli(argv = [], { cwd = process.cwd(), repoRoot }
|
|
|
299
323
|
format: options.format,
|
|
300
324
|
outputPath: options.output,
|
|
301
325
|
demos: options.demos,
|
|
326
|
+
recordings: options.recordings,
|
|
302
327
|
figures: options.figures,
|
|
303
328
|
strict: options.strict,
|
|
304
329
|
gate: options.gate,
|
|
@@ -346,6 +371,9 @@ export async function runPublishCli(argv = [], { cwd = process.cwd(), repoRoot }
|
|
|
346
371
|
}
|
|
347
372
|
|
|
348
373
|
console.error(result.message);
|
|
374
|
+
if (result.ledger?.validation?.ok === false) {
|
|
375
|
+
for (const failure of result.ledger.validation.failures || []) console.error(` - ${failure}`);
|
|
376
|
+
}
|
|
349
377
|
const exitCode = result.strict && (result.missing?.length || result.gateBlocked) ? 2 : 1;
|
|
350
378
|
return { exitCode, result };
|
|
351
379
|
}
|