@geraldmaron/construct 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +36 -0
- package/README.md +41 -7
- package/bin/construct +1027 -64
- package/examples/distribution/sources/deck-one-pager.md +1 -1
- package/lib/acp/server.mjs +21 -7
- package/lib/adapters-sync.mjs +2 -1
- package/lib/audit-trail.mjs +185 -2
- package/lib/beads/auto-close.mjs +2 -1
- package/lib/beads/drift.mjs +2 -1
- package/lib/bridges/copilot-proxy.mjs +20 -5
- package/lib/certification/skill-inventory.mjs +10 -1
- package/lib/cli/approvals.mjs +147 -0
- package/lib/cli-commands.mjs +105 -14
- package/lib/comment-lint.mjs +127 -8
- package/lib/config/schema.mjs +6 -29
- package/lib/config/source-target-registry.mjs +70 -0
- package/lib/config/source-targets.mjs +179 -205
- package/lib/contracts/coverage.mjs +77 -0
- package/lib/contracts/validate.mjs +102 -13
- package/lib/contracts/violation-log.mjs +50 -4
- package/lib/db/migrate.mjs +69 -0
- package/lib/db/migrations/001_orchestration_runs.sql +9 -0
- package/lib/db/migrations/002_queue_provider.sql +50 -0
- package/lib/db/migrations/003_worker_registry.sql +17 -0
- package/lib/db/migrations/004_trace_events.sql +16 -0
- package/lib/db/migrations/005_shared_memory.sql +15 -0
- package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
- package/lib/decisions/enforced-baseline.json +0 -2
- package/lib/decisions/registry.mjs +3 -2
- package/lib/deployment/parity-contract.mjs +1 -1
- package/lib/deployment-mode.mjs +78 -2
- package/lib/diagram-export.mjs +10 -1
- package/lib/distill.mjs +5 -2
- package/lib/docs-verify.mjs +2 -1
- package/lib/doctor/cli.mjs +1 -0
- package/lib/doctor/command-on-path.mjs +24 -0
- package/lib/doctor/diagnosis.mjs +89 -0
- package/lib/doctor/embedding-health.mjs +50 -0
- package/lib/doctor/engine-health.mjs +93 -0
- package/lib/doctor/graph-validate.mjs +47 -0
- package/lib/doctor/index.mjs +18 -4
- package/lib/doctor/sidecar-providers.mjs +56 -0
- package/lib/doctor/watchers/consistency.mjs +93 -1
- package/lib/doctor/watchers/cx-budget.mjs +1 -1
- package/lib/doctor/watchers/graph-staleness.mjs +1 -1
- package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
- package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
- package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
- package/lib/doctor/watchers/provider-breaker.mjs +78 -0
- package/lib/document-export.mjs +52 -27
- package/lib/document-extract/docling-client.mjs +9 -5
- package/lib/document-extract/docling-sidecar.py +30 -0
- package/lib/document-extract.mjs +1 -1
- package/lib/document-ingest.mjs +9 -0
- package/lib/embed/approval-queue.mjs +184 -88
- package/lib/embed/authority-guard.mjs +65 -2
- package/lib/embed/capability-jobs.mjs +365 -0
- package/lib/embed/capability-lifecycle.mjs +219 -0
- package/lib/embed/capability-loader.mjs +303 -0
- package/lib/embed/capability-runtime.mjs +58 -0
- package/lib/embed/cli.mjs +185 -8
- package/lib/embed/config.mjs +4 -0
- package/lib/embed/daemon.mjs +125 -6
- package/lib/embed/demand-fetch.mjs +190 -54
- package/lib/embed/inbox.mjs +12 -10
- package/lib/embed/presets/ops-triage.mjs +236 -0
- package/lib/embed/presets/pm-feedback.mjs +341 -0
- package/lib/embed/presets/tpm.mjs +401 -0
- package/lib/embed/providers/jira.mjs +72 -1
- package/lib/embed/providers/registry.mjs +140 -33
- package/lib/embedded-contract/capability.mjs +4 -0
- package/lib/embedded-contract/execution.mjs +1 -1
- package/lib/embedded-contract/model-resolve.mjs +53 -3
- package/lib/embedded-contract/workflow-defs.mjs +48 -73
- package/lib/embedded-contract/workflow-invoke.mjs +144 -4
- package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
- package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
- package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
- package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
- package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
- package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
- package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
- package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
- package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
- package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
- package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
- package/lib/env-config.mjs +50 -9
- package/lib/extensions/index.mjs +27 -0
- package/lib/extensions/loader.mjs +120 -0
- package/lib/extensions/manifest-schema.mjs +141 -0
- package/lib/extensions/manifests/anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
- package/lib/extensions/manifests/directory.manifest.json +12 -0
- package/lib/extensions/manifests/docling.manifest.json +37 -0
- package/lib/extensions/manifests/echo.manifest.json +8 -0
- package/lib/extensions/manifests/feedback.manifest.json +12 -0
- package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
- package/lib/extensions/manifests/github.manifest.json +52 -0
- package/lib/extensions/manifests/linear.manifest.json +50 -0
- package/lib/extensions/manifests/local.manifest.json +12 -0
- package/lib/extensions/manifests/ollama.manifest.json +12 -0
- package/lib/extensions/manifests/openai.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter.manifest.json +12 -0
- package/lib/extensions/manifests/postgres.manifest.json +12 -0
- package/lib/extensions/manifests/salesforce.manifest.json +12 -0
- package/lib/extensions/manifests/slack.manifest.json +58 -0
- package/lib/extensions/manifests/whisper.manifest.json +36 -0
- package/lib/extensions/validate.mjs +175 -0
- package/lib/features.mjs +13 -9
- package/lib/flows/checkpoint.mjs +184 -0
- package/lib/flows/constants.mjs +27 -0
- package/lib/flows/define.mjs +146 -0
- package/lib/flows/engine.mjs +249 -0
- package/lib/flows/errors.mjs +19 -0
- package/lib/flows/index.mjs +27 -0
- package/lib/flows/joins.mjs +18 -0
- package/lib/flows/schema.mjs +90 -0
- package/lib/flows/state.mjs +31 -0
- package/lib/frameworks/loader.mjs +99 -0
- package/lib/frameworks/schema.mjs +157 -0
- package/lib/graph/build-from-corpus.mjs +67 -0
- package/lib/graph/build-from-embed.mjs +82 -0
- package/lib/graph/build-from-registry.mjs +164 -3
- package/lib/graph/cli.mjs +456 -12
- package/lib/graph/gap-queries.mjs +156 -0
- package/lib/graph/gaps.mjs +41 -0
- package/lib/graph/impacted.mjs +129 -0
- package/lib/graph/runtime-evidence.mjs +177 -0
- package/lib/graph/security-coverage.mjs +113 -0
- package/lib/graph/staleness.mjs +241 -10
- package/lib/graph/store.mjs +24 -7
- package/lib/graph/validate.mjs +161 -0
- package/lib/headhunt.mjs +9 -8
- package/lib/health-check.mjs +15 -7
- package/lib/hook-health.mjs +1 -1
- package/lib/hooks/agent-tracker.mjs +10 -4
- package/lib/hooks/edit-guard.mjs +3 -3
- package/lib/hooks/graph-impact-advisory.mjs +2 -2
- package/lib/hooks/guard-bash.mjs +41 -15
- package/lib/hooks/mcp-health-check.mjs +11 -4
- package/lib/hooks/model-fallback.mjs +50 -6
- package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
- package/lib/hooks/pre-compact.mjs +181 -173
- package/lib/hooks/rule-verifier.mjs +2 -1
- package/lib/hooks/session-reflect.mjs +2 -1
- package/lib/hooks/session-start.mjs +3 -3
- package/lib/host-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +19 -7
- package/lib/identity.mjs +92 -0
- package/lib/ingest/degraded-extract.mjs +96 -0
- package/lib/ingest/docling-remote.mjs +2 -1
- package/lib/ingest/provider-extract.mjs +7 -19
- package/lib/ingest/sidecar-providers.mjs +196 -0
- package/lib/ingest-tooling.mjs +11 -5
- package/lib/init-unified.mjs +55 -38
- package/lib/init-update.mjs +2 -1
- package/lib/install/legacy-global-cleanup.mjs +25 -0
- package/lib/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- package/lib/intake/queue-registry.mjs +50 -0
- package/lib/intake/queue.mjs +133 -17
- package/lib/intake/session-prelude.mjs +57 -8
- package/lib/integrations/intake-integrations.mjs +61 -111
- package/lib/intent-classifier.mjs +43 -5
- package/lib/libreoffice-export.mjs +8 -8
- package/lib/logging/rotate.mjs +5 -4
- package/lib/mcp/broker.mjs +332 -17
- package/lib/mcp/denied-store.mjs +95 -0
- package/lib/mcp/destructive-gate.mjs +30 -0
- package/lib/mcp/dispatch-envelope.mjs +199 -0
- package/lib/mcp/memory-bridge.mjs +2 -1
- package/lib/mcp/server.mjs +154 -1411
- package/lib/mcp/tool-definitions-memory.mjs +376 -0
- package/lib/mcp/tool-definitions-project.mjs +271 -0
- package/lib/mcp/tool-definitions-skills.mjs +311 -0
- package/lib/mcp/tool-definitions-workflow.mjs +398 -0
- package/lib/mcp/tool-definitions.mjs +25 -0
- package/lib/mcp/tool-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +1 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +165 -25
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- package/lib/mcp/tools/provider-write.mjs +187 -0
- package/lib/mcp/tools/scope.mjs +0 -3
- package/lib/mcp/tools/skills.mjs +1 -1
- package/lib/mcp/tools/storage.mjs +0 -8
- package/lib/mcp/transport/auth.mjs +238 -0
- package/lib/mcp/transport/http.mjs +136 -0
- package/lib/mcp/transport/mode.mjs +31 -0
- package/lib/mcp/transport/stdio.mjs +22 -0
- package/lib/mcp-catalog.json +4 -4
- package/lib/mcp-manager.mjs +34 -23
- package/lib/mcp-platform-config.mjs +31 -7
- package/lib/mode-capabilities.mjs +109 -0
- package/lib/model-router.mjs +42 -9
- package/lib/models/catalog.mjs +40 -1
- package/lib/net-guard.mjs +247 -0
- package/lib/observation-store.mjs +6 -2
- package/lib/ollama/provision-context.mjs +2 -1
- package/lib/opencode-config.mjs +22 -3
- package/lib/opencode-runtime-plugin.mjs +120 -2
- package/lib/oracle/actions.mjs +204 -10
- package/lib/oracle/cli.mjs +24 -3
- package/lib/oracle/dispatch.mjs +2 -1
- package/lib/oracle/execute.mjs +2 -2
- package/lib/oracle/gaps.mjs +3 -3
- package/lib/oracle/heartbeat.mjs +53 -0
- package/lib/oracle/read-model.mjs +69 -13
- package/lib/oracle/reconcile.mjs +3 -46
- package/lib/oracle/routing.mjs +37 -31
- package/lib/oracle/synthesize.mjs +1 -1
- package/lib/orchestration/classification.mjs +434 -0
- package/lib/orchestration/delegation-flow.mjs +132 -0
- package/lib/orchestration/flow-selection.mjs +418 -0
- package/lib/orchestration/gates.mjs +244 -0
- package/lib/orchestration/host-sampling.mjs +121 -0
- package/lib/orchestration/policy-constants.mjs +48 -0
- package/lib/orchestration/provider-outcome.mjs +197 -0
- package/lib/orchestration/readiness.mjs +114 -13
- package/lib/orchestration/run-store-postgres.mjs +32 -26
- package/lib/orchestration/run-store-sqlite.mjs +8 -14
- package/lib/orchestration/run-store.mjs +25 -12
- package/lib/orchestration/runtime.mjs +446 -39
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +3 -2
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +528 -89
- package/lib/orchestration-policy.mjs +67 -1111
- package/lib/packs/cli.mjs +144 -0
- package/lib/packs/core-pack.mjs +112 -0
- package/lib/packs/enablement.mjs +187 -0
- package/lib/packs/index.mjs +23 -0
- package/lib/packs/loader.mjs +176 -0
- package/lib/packs/manifest-schema.mjs +58 -0
- package/lib/packs/prompts.mjs +120 -0
- package/lib/packs/validate.mjs +208 -0
- package/lib/plugin-registry.mjs +4 -5
- package/lib/policy/audit-gate.mjs +42 -0
- package/lib/policy/consumption-budget.mjs +149 -0
- package/lib/policy/engine.mjs +81 -6
- package/lib/policy/role-authority.mjs +89 -0
- package/lib/prompt-composer.js +19 -3
- package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
- package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
- package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
- package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
- package/lib/providers/contract/adapters/github/index.mjs +38 -3
- package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
- package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
- package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
- package/lib/providers/contract/adapters/jira/manifest.json +17 -0
- package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
- package/lib/providers/contract.mjs +207 -0
- package/lib/providers/credential-bootstrap.mjs +7 -4
- package/lib/providers/credential-sources.mjs +14 -2
- package/lib/providers/directory/index.mjs +261 -0
- package/lib/providers/feedback/index.mjs +392 -0
- package/lib/providers/filter-audit.mjs +68 -0
- package/lib/providers/filter-schema.mjs +54 -0
- package/lib/providers/github/index.mjs +31 -0
- package/lib/providers/instance-config.mjs +215 -0
- package/lib/providers/op-locate.mjs +96 -0
- package/lib/providers/op-run.mjs +64 -9
- package/lib/providers/registry.mjs +26 -3
- package/lib/providers/secret-audit-wiring.mjs +6 -0
- package/lib/providers/secret-resolver.mjs +240 -23
- package/lib/publish-template.mjs +6 -3
- package/lib/publish-tooling.mjs +4 -0
- package/lib/publish.mjs +32 -4
- package/lib/queue/pg-queue.mjs +395 -0
- package/lib/reflect.mjs +2 -1
- package/lib/registry/assemble.mjs +28 -2
- package/lib/registry/consolidation.mjs +8 -1
- package/lib/registry/custom-scaffold.mjs +200 -0
- package/lib/registry/custom-schema.mjs +137 -0
- package/lib/registry/loader.mjs +8 -3
- package/lib/registry/manifests/format-engines.default.json +15 -0
- package/lib/registry/manifests/surface-map.default.json +46 -0
- package/lib/registry/retired-paths.mjs +1 -0
- package/lib/registry/surface-map.mjs +100 -50
- package/lib/render-visual-check.mjs +240 -0
- package/lib/resources/budget.mjs +40 -17
- package/lib/roles/flavor-bindings.mjs +36 -14
- package/lib/roles/gateway.mjs +15 -8
- package/lib/roots.mjs +107 -0
- package/lib/runtime/uv-bootstrap.mjs +32 -6
- package/lib/runtime/whisper-bootstrap.mjs +11 -3
- package/lib/runtime-env.mjs +5 -2
- package/lib/scheduler/index.mjs +8 -20
- package/lib/schema-infer.mjs +31 -2
- package/lib/scopes/lifecycle.mjs +29 -25
- package/lib/scopes/loader.mjs +49 -12
- package/lib/scopes/teams.mjs +1 -1
- package/lib/security/ingest-boundary.mjs +80 -0
- package/lib/security/recall-wrapper.mjs +99 -0
- package/lib/security/trust.mjs +132 -0
- package/lib/service-manager.mjs +38 -19
- package/lib/setup.mjs +41 -23
- package/lib/skills-apply.mjs +4 -2
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/state-root.mjs +147 -0
- package/lib/status.mjs +597 -6
- package/lib/storage/admin.mjs +77 -5
- package/lib/storage/backend-registry.mjs +73 -0
- package/lib/storage/backend.mjs +63 -9
- package/lib/storage/embeddings-openai.mjs +12 -2
- package/lib/storage/hybrid-query.mjs +11 -3
- package/lib/storage/retrieval-hardening.mjs +384 -0
- package/lib/storage/shared-memory.mjs +158 -0
- package/lib/storage/vector-client.mjs +69 -2
- package/lib/team/health.mjs +72 -0
- package/lib/telemetry/backends/local.mjs +9 -3
- package/lib/telemetry/client.mjs +3 -7
- package/lib/template-registry.mjs +10 -12
- package/lib/tenant/context.mjs +82 -0
- package/lib/tenant/isolation.mjs +129 -0
- package/lib/test-corpus-inventory.mjs +103 -2
- package/lib/uninstall/uninstall.mjs +78 -12
- package/lib/validator.mjs +4 -6
- package/lib/worker/entrypoint.mjs +2 -1
- package/lib/worker/run.mjs +2 -2
- package/lib/worker/trace.mjs +5 -11
- package/lib/workflow-state.mjs +52 -8
- package/lib/workflows/liveness.mjs +203 -0
- package/lib/workflows/loader.mjs +177 -0
- package/lib/workflows/manifest-schema.mjs +71 -0
- package/lib/workflows/surface-parity.mjs +118 -0
- package/lib/workflows/validate.mjs +127 -0
- package/lib/writes/control-plane.mjs +140 -0
- package/lib/writes/envelope.mjs +206 -0
- package/lib/writes/sent-log.mjs +86 -0
- package/lib/writes/write-intent.mjs +109 -0
- package/package.json +16 -8
- package/personas/construct.md +12 -3
- package/registry/capabilities.json +143 -36
- package/rules/common/comments.md +1 -0
- package/schemas/project-config.schema.json +0 -12
- package/schemas/unified-registry.schema.json +2 -4
- package/scripts/sync-specialists.mjs +284 -81
- package/skills/docs/adr-workflow.md +1 -1
- package/skills/docs/codebase-research-workflow.md +3 -3
- package/skills/docs/prd-workflow.md +5 -6
- package/skills/docs/runbook-workflow.md +2 -2
- package/skills/docs/user-research-workflow.md +3 -3
- package/skills/operating/fleet-health-routing.md +77 -0
- package/skills/roles/ai-engineer.md +1 -1
- package/skills/roles/architect.md +0 -1
- package/skills/roles/business-strategist.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/data-engineer.md +1 -1
- package/skills/roles/data-engineer.pipeline.md +1 -1
- package/skills/roles/data-engineer.vector-retrieval.md +1 -2
- package/skills/roles/data-engineer.warehouse.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +0 -1
- package/skills/roles/devil-advocate.md +1 -1
- package/skills/roles/docs-keeper.md +1 -1
- package/skills/roles/evaluator.md +1 -1
- package/skills/roles/explorer.md +1 -1
- package/skills/roles/platform-engineer.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -2
- package/skills/roles/qa.api-contract.md +0 -1
- package/skills/roles/qa.data-pipeline.md +0 -1
- package/skills/roles/qa.md +0 -1
- package/skills/roles/qa.web-ui.md +0 -1
- package/skills/roles/release-manager.md +1 -1
- package/skills/roles/researcher.md +0 -2
- package/skills/roles/reviewer.md +0 -3
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +0 -1
- package/skills/roles/security.privacy.md +0 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/sre.md +1 -1
- package/skills/roles/test-automation.md +1 -1
- package/skills/roles/trace-reviewer.md +1 -1
- package/skills/roles/ux-researcher.md +1 -1
- package/specialists/artifact-manifest.json +36 -36
- package/specialists/org/contracts/accessibility-to-qa.json +11 -3
- package/specialists/org/contracts/any-to-business-strategist.json +19 -7
- package/specialists/org/contracts/any-to-debugger.json +7 -1
- package/specialists/org/contracts/any-to-designer.json +7 -1
- package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
- package/specialists/org/contracts/any-to-explorer.json +13 -4
- package/specialists/org/contracts/any-to-sre-incident.json +6 -2
- package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
- package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
- package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
- package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
- package/specialists/org/contracts/architect-to-engineer.json +20 -4
- package/specialists/org/contracts/architect-to-evaluator.json +15 -5
- package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
- package/specialists/org/contracts/architect-to-operations.json +17 -4
- package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
- package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
- package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
- package/specialists/org/contracts/engineer-to-qa.json +20 -4
- package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
- package/specialists/org/contracts/explorer-to-engineer.json +11 -3
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
- package/specialists/org/contracts/operations-to-user.json +53 -0
- package/specialists/org/contracts/operations-triage-output.json +53 -0
- package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
- package/specialists/org/contracts/product-manager-to-architect.json +30 -10
- package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
- package/specialists/org/contracts/qa-to-release-manager.json +11 -3
- package/specialists/org/contracts/researcher-to-architect.json +10 -2
- package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
- package/specialists/org/contracts/reviewer-to-security.json +17 -3
- package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
- package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
- package/specialists/org/contracts/user-to-construct.json +11 -4
- package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
- package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
- package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
- package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
- package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
- package/specialists/org/groups/engineering-group.json +2 -6
- package/specialists/org/groups/governance-group.json +2 -3
- package/specialists/org/groups/operations-group.json +5 -8
- package/specialists/org/groups/product-group.json +4 -8
- package/specialists/org/groups/quality-group.json +3 -7
- package/specialists/org/groups/strategy-group.json +6 -9
- package/specialists/org/models.json.example +8 -0
- package/specialists/org/scopes/creative.json +6 -1
- package/specialists/org/scopes/operations.json +7 -1
- package/specialists/org/scopes/research.json +6 -1
- package/specialists/org/scopes/rnd.json +7 -1
- package/specialists/org/specialists/cx-architect.json +27 -8
- package/specialists/org/specialists/cx-designer.json +22 -8
- package/specialists/org/specialists/cx-engineer.json +71 -7
- package/specialists/org/specialists/cx-operations.json +89 -15
- package/specialists/org/specialists/cx-orchestrator.json +16 -4
- package/specialists/org/specialists/cx-product-manager.json +28 -9
- package/specialists/org/specialists/cx-qa.json +16 -6
- package/specialists/org/specialists/cx-researcher.json +40 -7
- package/specialists/org/specialists/cx-reviewer.json +61 -11
- package/specialists/org/specialists/cx-security.json +30 -10
- package/specialists/org/teams/design-team.json +3 -4
- package/specialists/org/teams/engineering-team.json +3 -10
- package/specialists/org/teams/governance-team.json +3 -5
- package/specialists/org/teams/operations-team.json +6 -12
- package/specialists/org/teams/product-management-team.json +2 -3
- package/specialists/org/teams/quality-team.json +4 -12
- package/specialists/org/teams/research-team.json +3 -3
- package/specialists/org/teams/strategy-team.json +7 -14
- package/specialists/prompts/cx-architect.md +20 -1
- package/specialists/prompts/cx-data-analyst.md +1 -1
- package/specialists/prompts/cx-designer.md +12 -4
- package/specialists/prompts/cx-engineer.md +15 -1
- package/specialists/prompts/cx-operations.md +14 -2
- package/specialists/prompts/cx-orchestrator.md +9 -5
- package/specialists/prompts/cx-product-manager.md +5 -1
- package/specialists/prompts/cx-qa.md +6 -2
- package/specialists/prompts/cx-researcher.md +25 -30
- package/specialists/prompts/cx-reviewer.md +19 -1
- package/specialists/prompts/cx-security.md +5 -1
- package/templates/distribution/construct-brand.typ +123 -59
- package/templates/distribution/construct-decision.typ +6 -3
- package/templates/distribution/construct-pdf.typ +11 -4
- package/templates/distribution/construct-prd.typ +6 -3
- package/templates/distribution/construct-research.typ +7 -4
- package/lib/providers/contract/adapters/git/index.mjs +0 -115
- package/lib/providers/contract/adapters/slack/index.mjs +0 -175
- package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
- package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
- package/specialists/org/contracts/designer-to-accessibility.json +0 -36
- package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
- package/specialists/org/contracts/qa-to-test-automation.json +0 -42
- package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
- package/specialists/org/contracts/sre-to-release-manager.json +0 -36
- package/specialists/org/specialists/cx-accessibility.json +0 -69
- package/specialists/org/specialists/cx-ai-engineer.json +0 -75
- package/specialists/org/specialists/cx-business-strategist.json +0 -72
- package/specialists/org/specialists/cx-data-engineer.json +0 -71
- package/specialists/org/specialists/cx-devil-advocate.json +0 -71
- package/specialists/org/specialists/cx-docs-keeper.json +0 -82
- package/specialists/org/specialists/cx-evaluator.json +0 -69
- package/specialists/org/specialists/cx-explorer.json +0 -69
- package/specialists/org/specialists/cx-legal-compliance.json +0 -74
- package/specialists/org/specialists/cx-oracle.json +0 -46
- package/specialists/org/specialists/cx-platform-engineer.json +0 -80
- package/specialists/org/specialists/cx-rd-lead.json +0 -73
- package/specialists/org/specialists/cx-release-manager.json +0 -77
- package/specialists/org/specialists/cx-sre.json +0 -94
- package/specialists/org/specialists/cx-test-automation.json +0 -69
- package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
- package/specialists/org/specialists/cx-ux-researcher.json +0 -68
- package/specialists/org/teams/accessibility-team.json +0 -39
- package/specialists/org/teams/ux-research-team.json +0 -39
- package/specialists/prompts/cx-accessibility.md +0 -55
- package/specialists/prompts/cx-ai-engineer.md +0 -124
- package/specialists/prompts/cx-business-strategist.md +0 -58
- package/specialists/prompts/cx-data-engineer.md +0 -55
- package/specialists/prompts/cx-devil-advocate.md +0 -59
- package/specialists/prompts/cx-docs-keeper.md +0 -164
- package/specialists/prompts/cx-evaluator.md +0 -49
- package/specialists/prompts/cx-explorer.md +0 -71
- package/specialists/prompts/cx-legal-compliance.md +0 -58
- package/specialists/prompts/cx-oracle.md +0 -98
- package/specialists/prompts/cx-platform-engineer.md +0 -97
- package/specialists/prompts/cx-rd-lead.md +0 -59
- package/specialists/prompts/cx-release-manager.md +0 -54
- package/specialists/prompts/cx-sre.md +0 -111
- package/specialists/prompts/cx-test-automation.md +0 -55
- package/specialists/prompts/cx-trace-reviewer.md +0 -101
- package/specialists/prompts/cx-ux-researcher.md +0 -53
|
@@ -25,15 +25,20 @@
|
|
|
25
25
|
* value lives only in this module's cache and is never logged. allowAmbient lets
|
|
26
26
|
* a hermetic caller (tests, embedded callers that inject their own env) suppress
|
|
27
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.
|
|
28
32
|
*/
|
|
29
33
|
|
|
30
34
|
import fs from 'node:fs';
|
|
31
35
|
import path from 'node:path';
|
|
32
36
|
import os from 'node:os';
|
|
33
|
-
import { spawnSync } from 'node:child_process';
|
|
37
|
+
import { spawnSync, spawn } from 'node:child_process';
|
|
34
38
|
import { discoverAlternateRawForVar } from './credential-sources.mjs';
|
|
39
|
+
import { locateOpBinary } from './op-locate.mjs';
|
|
35
40
|
import { configDir } from '../config/xdg.mjs';
|
|
36
|
-
import { preferInjectedCredential } from '../env-config.mjs';
|
|
41
|
+
import { preferInjectedCredential, parseEnvContent } from '../env-config.mjs';
|
|
37
42
|
|
|
38
43
|
// The 1Password desktop integration prompts for biometric unlock on the first
|
|
39
44
|
// read after the vault locks, and that interactive round-trip routinely takes
|
|
@@ -91,30 +96,145 @@ export function extractOpRef(rawValue) {
|
|
|
91
96
|
return m ? m[2] : null;
|
|
92
97
|
}
|
|
93
98
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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) {
|
|
97
159
|
throw new SecretResolutionError('1Password CLI not found — install `op` or set the key directly.', 'OP_NOT_INSTALLED');
|
|
98
160
|
}
|
|
99
|
-
if (
|
|
100
|
-
const
|
|
101
|
-
if (/sign in|signin|session|not currently|authenticate|authorization|no account/.test(
|
|
102
|
-
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');
|
|
103
165
|
}
|
|
104
|
-
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');
|
|
105
167
|
}
|
|
106
|
-
const secret = String(
|
|
168
|
+
const secret = String(stdout || '').trim();
|
|
107
169
|
if (!secret) throw new SecretResolutionError(`op read returned an empty value for ${opRef}`, 'OP_EMPTY');
|
|
108
170
|
return secret;
|
|
109
171
|
}
|
|
110
172
|
|
|
111
|
-
|
|
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 } = {}) {
|
|
112
232
|
if (opCache.has(opRef)) {
|
|
113
233
|
emitAudit({ event: 'secret.op_read', ref: opRef, cacheHit: true, ok: true });
|
|
114
234
|
return opCache.get(opRef);
|
|
115
235
|
}
|
|
116
236
|
try {
|
|
117
|
-
const secret = opRead(opRef);
|
|
237
|
+
const secret = opRead(opRef, { env });
|
|
118
238
|
opCache.set(opRef, secret);
|
|
119
239
|
emitAudit({ event: 'secret.op_read', ref: opRef, cacheHit: false, ok: true });
|
|
120
240
|
return secret;
|
|
@@ -129,11 +249,17 @@ function materialize(rawValue, opts) {
|
|
|
129
249
|
return ref ? resolveOpRef(ref, opts) : unquote(rawValue);
|
|
130
250
|
}
|
|
131
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
|
+
|
|
132
258
|
function readDotenvVar(file, varName) {
|
|
133
259
|
try {
|
|
134
260
|
if (!fs.existsSync(file)) return null;
|
|
135
|
-
const
|
|
136
|
-
return
|
|
261
|
+
const parsed = parseEnvContent(fs.readFileSync(file, 'utf8'));
|
|
262
|
+
return Object.prototype.hasOwnProperty.call(parsed, varName) ? parsed[varName] : null;
|
|
137
263
|
} catch {
|
|
138
264
|
return null;
|
|
139
265
|
}
|
|
@@ -224,7 +350,7 @@ export function resolveSecret(varName, { env = process.env, cwd = process.cwd(),
|
|
|
224
350
|
// Hermetic mode: only the directly-injected env is consulted, no file/rc discovery,
|
|
225
351
|
// so a developer key never bleeds into an isolated run.
|
|
226
352
|
if (!allowAmbient) {
|
|
227
|
-
if (hasDirect) return resolveAndAudit(varName, direct, 'env', { opRead });
|
|
353
|
+
if (hasDirect) return resolveAndAudit(varName, direct, 'env', { opRead, env });
|
|
228
354
|
emitAudit({ event: 'secret.resolve', varName, source: null, isOpRef: false, ok: false });
|
|
229
355
|
return null;
|
|
230
356
|
}
|
|
@@ -237,12 +363,12 @@ export function resolveSecret(varName, { env = process.env, cwd = process.cwd(),
|
|
|
237
363
|
// reference so a single `op run` auth suffices.
|
|
238
364
|
if (found) {
|
|
239
365
|
if (hasDirect && preferInjectedCredential(direct, found.raw)) {
|
|
240
|
-
return resolveAndAudit(varName, direct, 'env', { opRead });
|
|
366
|
+
return resolveAndAudit(varName, direct, 'env', { opRead, env });
|
|
241
367
|
}
|
|
242
|
-
return resolveAndAudit(varName, found.raw, found.source, { opRead });
|
|
368
|
+
return resolveAndAudit(varName, found.raw, found.source, { opRead, env });
|
|
243
369
|
}
|
|
244
370
|
|
|
245
|
-
if (hasDirect) return resolveAndAudit(varName, direct, 'env', { opRead });
|
|
371
|
+
if (hasDirect) return resolveAndAudit(varName, direct, 'env', { opRead, env });
|
|
246
372
|
emitAudit({ event: 'secret.resolve', varName, source: null, isOpRef: false, ok: false });
|
|
247
373
|
return null;
|
|
248
374
|
}
|
|
@@ -255,15 +381,81 @@ export function resolveFirstSecret(varNames, opts = {}) {
|
|
|
255
381
|
return null;
|
|
256
382
|
}
|
|
257
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
|
+
|
|
258
448
|
// Presence check that never runs `op read`: a stored op:// reference counts as
|
|
259
|
-
// 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.
|
|
260
452
|
|
|
261
|
-
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 } = {}) {
|
|
262
454
|
const direct = env?.[varName];
|
|
263
455
|
if (typeof direct === 'string' && direct.length > 0) return true;
|
|
264
456
|
if (!allowAmbient) return false;
|
|
265
|
-
const
|
|
266
|
-
const found = rawCandidate(varName, { env, cwd, home });
|
|
457
|
+
const resolvedHome = home || os.homedir();
|
|
458
|
+
const found = rawCandidate(varName, { env, cwd, home: resolvedHome });
|
|
267
459
|
return Boolean(found && typeof found.raw === 'string' && found.raw.length > 0);
|
|
268
460
|
}
|
|
269
461
|
|
|
@@ -271,6 +463,31 @@ export function hasAnySecret(varNames, opts = {}) {
|
|
|
271
463
|
return varNames.some((name) => hasSecret(name, opts));
|
|
272
464
|
}
|
|
273
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
|
+
|
|
274
481
|
export function __clearSecretCache() {
|
|
275
482
|
opCache.clear();
|
|
276
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
|
}
|