@geraldmaron/construct 1.4.2 → 1.5.1
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 +57 -45
- 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 +273 -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 +104 -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
|
@@ -13,12 +13,14 @@
|
|
|
13
13
|
import { createIntakeQueue } from './queue.mjs';
|
|
14
14
|
import { formatTriageLine } from './classify.mjs';
|
|
15
15
|
import { isBrokered } from '../mcp/broker.mjs';
|
|
16
|
+
import { getDeploymentMode } from '../deployment-mode.mjs';
|
|
16
17
|
import { getRebrand } from '../scopes/rebrand.mjs';
|
|
17
18
|
import { homedir as osHomedir } from 'node:os';
|
|
18
19
|
import { readLastTick } from '../oracle/index.mjs';
|
|
19
20
|
import { listPending } from '../oracle/actions.mjs';
|
|
20
21
|
import { readLatestVerdict } from '../oracle/verdicts.mjs';
|
|
21
22
|
import { isVerdictOnlyGap } from '../oracle/policy.mjs';
|
|
23
|
+
import { heartbeatFreshness, formatAge } from '../oracle/heartbeat.mjs';
|
|
22
24
|
|
|
23
25
|
export function buildIntakePrelude({ cwd, env = process.env } = {}) {
|
|
24
26
|
if (!cwd) return '';
|
|
@@ -41,9 +43,9 @@ export function buildIntakePrelude({ cwd, env = process.env } = {}) {
|
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
export function buildBrokerStatusLine({ env = process.env } = {}) {
|
|
45
|
-
const active = isBrokered(env);
|
|
46
|
-
const mode = env
|
|
46
|
+
export function buildBrokerStatusLine({ env = process.env, cwd } = {}) {
|
|
47
|
+
const active = isBrokered(env, { cwd });
|
|
48
|
+
const mode = getDeploymentMode(env, { cwd });
|
|
47
49
|
if (!active) {
|
|
48
50
|
return `MCP broker: off · deployment mode: ${mode} (set CONSTRUCT_MCP_BROKER=on to engage in solo mode).`;
|
|
49
51
|
}
|
|
@@ -52,7 +54,7 @@ export function buildBrokerStatusLine({ env = process.env } = {}) {
|
|
|
52
54
|
|
|
53
55
|
export function buildSessionPrelude({ cwd, env = process.env } = {}) {
|
|
54
56
|
const intake = buildIntakePrelude({ cwd, env });
|
|
55
|
-
const broker = buildBrokerStatusLine({ env });
|
|
57
|
+
const broker = buildBrokerStatusLine({ env, cwd });
|
|
56
58
|
const oracle = buildOraclePrelude({ cwd, env });
|
|
57
59
|
if (!intake && !broker && !oracle) return '';
|
|
58
60
|
const parts = [];
|
|
@@ -62,6 +64,26 @@ export function buildSessionPrelude({ cwd, env = process.env } = {}) {
|
|
|
62
64
|
return parts.join('\n\n');
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
/**
|
|
68
|
+
* lib/oracle/synthesize.mjs renders gap detail text against a 24h lookback
|
|
69
|
+
* window ("in the last 24h"); a verdict older than that window describes a
|
|
70
|
+
* period disjoint from the present, so the same threshold marks it stale.
|
|
71
|
+
* Missing/unparseable `at` resolves to Infinity (stale, not fresh) rather
|
|
72
|
+
* than defaulting to zero age.
|
|
73
|
+
*/
|
|
74
|
+
const VERDICT_STALE_MS = 24 * 60 * 60 * 1000;
|
|
75
|
+
|
|
76
|
+
function verdictFreshness(verdict, now = Date.now()) {
|
|
77
|
+
const at = verdict?.at;
|
|
78
|
+
const parsed = at ? Date.parse(at) : NaN;
|
|
79
|
+
const ageMs = Number.isFinite(parsed) ? now - parsed : Infinity;
|
|
80
|
+
return {
|
|
81
|
+
ageMs,
|
|
82
|
+
stale: !Number.isFinite(ageMs) || ageMs > VERDICT_STALE_MS,
|
|
83
|
+
asOf: Number.isFinite(parsed) ? new Date(parsed).toISOString() : null,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
65
87
|
/**
|
|
66
88
|
* Oracle verdict + pending queue for session-start (mirror doctor warnings).
|
|
67
89
|
*/
|
|
@@ -75,7 +97,19 @@ export function buildOraclePrelude({ cwd, env = process.env, homeDir } = {}) {
|
|
|
75
97
|
const pending = listPending(cwd).filter((p) => p.status === 'pending');
|
|
76
98
|
const v = verdict?.verdict ?? last?.verdict ?? 'unknown';
|
|
77
99
|
if (v === 'healthy' && pending.length === 0) return '';
|
|
78
|
-
const
|
|
100
|
+
const source = verdict ?? last;
|
|
101
|
+
const { ageMs, stale, asOf } = verdictFreshness(source);
|
|
102
|
+
const verdictHead = v === 'unknown'
|
|
103
|
+
? `\n## Oracle overseer · verdict: **${v}**`
|
|
104
|
+
: `\n## Oracle overseer · verdict: **${v}** (as of ${asOf ?? 'unknown'}, ${formatAge(ageMs)} ago)`;
|
|
105
|
+
const lines = [verdictHead];
|
|
106
|
+
if (v !== 'unknown' && stale) {
|
|
107
|
+
lines.push(`- STALE: verdict is older than ${formatAge(VERDICT_STALE_MS)}; gap details below describe the verdict date, not now.`);
|
|
108
|
+
}
|
|
109
|
+
const heartbeat = heartbeatFreshness(last);
|
|
110
|
+
if (heartbeat.stale && pending.length > 0) {
|
|
111
|
+
lines.push(`- [high] oracle-producer-stalled: no oracle tick in ${formatAge(heartbeat.ageMs)} while ${pending.length} approval(s) wait — the daemon looks stalled, not idle; run \`construct oracle status\` / \`construct oracle start\`.`);
|
|
112
|
+
}
|
|
79
113
|
const gapSource = verdict?.gaps ?? last?.gaps ?? [];
|
|
80
114
|
const top = gapSource.filter((g) => g.severity === 'high').slice(0, 3);
|
|
81
115
|
for (const g of top) {
|
|
@@ -83,7 +117,7 @@ export function buildOraclePrelude({ cwd, env = process.env, homeDir } = {}) {
|
|
|
83
117
|
const hint = g.id === 'beads-hygiene'
|
|
84
118
|
? 'run `construct beads drift`'
|
|
85
119
|
: g.id === 'workflow-misaligned'
|
|
86
|
-
? 'run `construct workflow
|
|
120
|
+
? 'run `construct workflow new` or `construct init`'
|
|
87
121
|
: 'see `construct oracle gaps`';
|
|
88
122
|
lines.push(`- [${g.severity}] ${g.id} (verdict-only): ${g.detail} — ${hint}`);
|
|
89
123
|
} else {
|
|
@@ -102,7 +136,7 @@ export function buildOraclePrelude({ cwd, env = process.env, homeDir } = {}) {
|
|
|
102
136
|
|
|
103
137
|
export function readOracleDockState({ cwd, env = process.env, homeDir } = {}) {
|
|
104
138
|
if (!cwd || env.CONSTRUCT_ORACLE === 'off' || env.CONSTRUCT_ORACLE === '0') {
|
|
105
|
-
return { visible: false, verdict: null, pendingCount: 0, topGaps: [], summary: '' };
|
|
139
|
+
return { visible: false, verdict: null, pendingCount: 0, topGaps: [], summary: '', asOf: null, stale: false, producerStalled: false, producerAgeMs: null };
|
|
106
140
|
}
|
|
107
141
|
try {
|
|
108
142
|
const home = homeDir ?? osHomedir();
|
|
@@ -113,23 +147,38 @@ export function readOracleDockState({ cwd, env = process.env, homeDir } = {}) {
|
|
|
113
147
|
const gapSource = verdictDoc?.gaps ?? last?.gaps ?? [];
|
|
114
148
|
const topGaps = gapSource.filter((g) => g.severity === 'high').slice(0, 3);
|
|
115
149
|
const visible = verdict !== 'healthy' || pending.length > 0;
|
|
150
|
+
const { stale, asOf } = verdictFreshness(verdictDoc ?? last);
|
|
151
|
+
const heartbeat = heartbeatFreshness(last);
|
|
152
|
+
const producerStalled = heartbeat.stale && pending.length > 0;
|
|
116
153
|
const parts = [`verdict ${verdict}`];
|
|
117
154
|
if (pending.length) parts.push(`${pending.length} pending`);
|
|
155
|
+
if (verdict !== 'unknown' && stale) parts.push('STALE');
|
|
156
|
+
if (producerStalled) parts.push('PRODUCER STALLED');
|
|
118
157
|
return {
|
|
119
158
|
visible,
|
|
120
159
|
verdict,
|
|
121
160
|
pendingCount: pending.length,
|
|
122
161
|
topGaps,
|
|
123
162
|
summary: parts.join(' · '),
|
|
163
|
+
asOf,
|
|
164
|
+
stale: verdict !== 'unknown' && stale,
|
|
165
|
+
producerStalled,
|
|
166
|
+
producerAgeMs: heartbeat.ageMs,
|
|
124
167
|
};
|
|
125
168
|
} catch {
|
|
126
|
-
return { visible: false, verdict: null, pendingCount: 0, topGaps: [], summary: '' };
|
|
169
|
+
return { visible: false, verdict: null, pendingCount: 0, topGaps: [], summary: '', asOf: null, stale: false, producerStalled: false, producerAgeMs: null };
|
|
127
170
|
}
|
|
128
171
|
}
|
|
129
172
|
|
|
130
173
|
export function formatOracleDockDetail(state) {
|
|
131
174
|
if (!state?.visible) return 'Oracle: healthy — no pending approvals.';
|
|
132
175
|
const lines = [`Oracle overseer · ${state.summary}`];
|
|
176
|
+
if (state.stale) {
|
|
177
|
+
lines.push(` STALE: verdict as of ${state.asOf ?? 'unknown'}; gap details describe the verdict date, not now.`);
|
|
178
|
+
}
|
|
179
|
+
if (state.producerStalled) {
|
|
180
|
+
lines.push(` PRODUCER STALLED: no oracle tick in ${formatAge(state.producerAgeMs)} while ${state.pendingCount} approval(s) wait — the daemon may be down.`);
|
|
181
|
+
}
|
|
133
182
|
for (const g of state.topGaps || []) {
|
|
134
183
|
if (isVerdictOnlyGap(g)) {
|
|
135
184
|
lines.push(` [${g.severity}] ${g.id} (verdict-only): ${g.detail}`);
|
|
@@ -19,7 +19,7 @@ import { join } from 'node:path';
|
|
|
19
19
|
import { homedir } from 'node:os';
|
|
20
20
|
import { spawnSync } from 'node:child_process';
|
|
21
21
|
import { configDir } from '../config/xdg.mjs';
|
|
22
|
-
import { resolveOpRef } from '../providers/secret-resolver.mjs';
|
|
22
|
+
import { resolveOpRef, hasSecret, hasAnySecret, peekRawCredential } from '../providers/secret-resolver.mjs';
|
|
23
23
|
|
|
24
24
|
// ── Multi-method auth helpers ────────────────────────────────────────────
|
|
25
25
|
|
|
@@ -588,8 +588,13 @@ export function tagIntakeWithExternalRef(rootDir, intakeId, system, externalUrl,
|
|
|
588
588
|
}
|
|
589
589
|
|
|
590
590
|
/**
|
|
591
|
-
*
|
|
592
|
-
*
|
|
591
|
+
* Credential resolver for the actual publish paths (createGitHubIssue,
|
|
592
|
+
* createJiraTicket, publishArtifactToConfluence) that need a real, materialized
|
|
593
|
+
* value to make an API call. Checks env → .env → config.env → shell rc,
|
|
594
|
+
* resolving a 1Password op:// reference through resolveOpRef when the rc tier
|
|
595
|
+
* carries one. Not for status/detection — detectIntegrationConfig below uses
|
|
596
|
+
* hasSecret/hasAnySecret/peekRawCredential instead, which never resolve op:// or
|
|
597
|
+
* source a shell rc file (ADR-0049 §2: presence-first in short-lived paths).
|
|
593
598
|
*/
|
|
594
599
|
function resolveCredentialSync(varName, homeDir) {
|
|
595
600
|
if (process.env[varName]) return process.env[varName];
|
|
@@ -640,126 +645,71 @@ function resolveCredentialSync(varName, homeDir) {
|
|
|
640
645
|
// ── Configuration detection ──────────────────────────────────────────────
|
|
641
646
|
|
|
642
647
|
/**
|
|
643
|
-
* Check which integrations are configured.
|
|
644
|
-
*
|
|
645
|
-
*
|
|
648
|
+
* Check which integrations are configured. A status/detection surface must
|
|
649
|
+
* never materialize a credential (ADR-0049 §2: "never to merely list or
|
|
650
|
+
* check") — this reports presence by shape only: env var set, a project/config/
|
|
651
|
+
* home .env entry exists, or a shell rc `export VAR=...` line matches. It never
|
|
652
|
+
* calls resolveOpRef and never sources a shell rc file (contrast
|
|
653
|
+
* resolveCredentialSync above, used only when an actual API call needs the
|
|
654
|
+
* real value). `gh auth status` / `gh repo view` are read-only CLI queries,
|
|
655
|
+
* not secret materialization — gh manages its own token internally and never
|
|
656
|
+
* hands Construct a plaintext value here.
|
|
646
657
|
*
|
|
647
658
|
* @param {object} [opts]
|
|
648
659
|
* @param {string} [opts.homeDir]
|
|
649
|
-
* @returns {{ github: boolean, jira: boolean, confluence: boolean,
|
|
660
|
+
* @returns {{ github: boolean, jira: boolean, confluence: boolean, githubMethod: string|null, githubRepo: string|null, jiraHost: string|null, confluenceSpace: string|null }}
|
|
650
661
|
*/
|
|
651
662
|
export function detectIntegrationConfig({ homeDir } = {}) {
|
|
652
663
|
const hd = homeDir || homedir();
|
|
653
664
|
|
|
654
|
-
// GitHub
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
// Jira
|
|
658
|
-
const jiraHost = resolveCredentialSync('JIRA_HOST', hd);
|
|
659
|
-
const jiraUser = resolveCredentialSync('JIRA_USER', hd);
|
|
660
|
-
const jiraToken = resolveCredentialSync('JIRA_API_TOKEN', hd);
|
|
661
|
-
const jiraProject = resolveCredentialSync('JIRA_PROJECT', hd);
|
|
662
|
-
|
|
663
|
-
// Confluence
|
|
664
|
-
const confHost = resolveCredentialSync('CONFLUENCE_HOST', hd);
|
|
665
|
-
const confUser = resolveCredentialSync('CONFLUENCE_USER', hd);
|
|
666
|
-
const confToken = resolveCredentialSync('CONFLUENCE_API_TOKEN', hd);
|
|
667
|
-
const confSpace = resolveCredentialSync('CONFLUENCE_SPACE', hd);
|
|
668
|
-
|
|
669
|
-
return {
|
|
670
|
-
github: ghAuth.method === 'gh' || Boolean(ghAuth.token && ghAuth.repo),
|
|
671
|
-
githubMethod: ghAuth.method || null,
|
|
672
|
-
githubRepo: ghAuth.repo || null,
|
|
673
|
-
jira: Boolean(jiraHost && jiraUser && jiraToken && jiraProject),
|
|
674
|
-
jiraHost: jiraHost || null,
|
|
675
|
-
confluence: Boolean(confHost && confUser && confToken && confSpace),
|
|
676
|
-
confluenceSpace: confSpace || null,
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
/**
|
|
681
|
-
* Resolve a credential from env, .env files, config.env, or shell rc files.
|
|
682
|
-
* Mirrors the approach in lib/health-check.mjs.
|
|
683
|
-
*/
|
|
684
|
-
function resolveCredential(varName, homeDir) {
|
|
685
|
-
if (process.env[varName]) return process.env[varName];
|
|
686
|
-
|
|
687
|
-
// Project .env
|
|
665
|
+
// GitHub
|
|
666
|
+
let ghCliAuthenticated = false;
|
|
688
667
|
try {
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
const m = content.match(new RegExp(`^${varName}=["']?(.+?)["']?$`, 'm'));
|
|
693
|
-
if (m) return m[1].trim();
|
|
694
|
-
}
|
|
695
|
-
} catch { /* skip */ }
|
|
668
|
+
const status = spawnSync('gh', ['auth', 'status'], { encoding: 'utf8', timeout: 5000 });
|
|
669
|
+
ghCliAuthenticated = status.status === 0;
|
|
670
|
+
} catch { /* gh not available */ }
|
|
696
671
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
if (existsSync(homeEnv)) {
|
|
701
|
-
const content = readFileSync(homeEnv, 'utf8');
|
|
702
|
-
const m = content.match(new RegExp(`^${varName}=["']?(.+?)["']?$`, 'm'));
|
|
703
|
-
if (m) return m[1].trim();
|
|
704
|
-
}
|
|
705
|
-
} catch { /* skip */ }
|
|
672
|
+
const githubTokenPresent = hasAnySecret(['GITHUB_TOKEN', 'GH_TOKEN'], { home: hd });
|
|
673
|
+
let githubRepo = process.env.GITHUB_REPO || peekRawCredential('GITHUB_REPO', { home: hd })?.raw || null;
|
|
674
|
+
let githubMethod = null;
|
|
706
675
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
676
|
+
if (ghCliAuthenticated) {
|
|
677
|
+
githubMethod = 'gh';
|
|
678
|
+
if (!githubRepo) {
|
|
679
|
+
try {
|
|
680
|
+
const remote = spawnSync('gh', ['repo', 'view', '--json', 'nameWithOwner', '--jq', '.nameWithOwner'], { encoding: 'utf8', timeout: 5000 });
|
|
681
|
+
if (remote.status === 0 && remote.stdout?.trim()) githubRepo = remote.stdout.trim();
|
|
682
|
+
} catch { /* gh not available */ }
|
|
714
683
|
}
|
|
715
|
-
}
|
|
684
|
+
} else if (githubTokenPresent) {
|
|
685
|
+
githubMethod = 'env';
|
|
686
|
+
}
|
|
716
687
|
|
|
717
|
-
//
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
const directRe = new RegExp(`^\\s*export\\s+${varName}="?([^"'\\$\\(]+)"?$`, 'm');
|
|
726
|
-
const directMatch = content.match(directRe);
|
|
727
|
-
if (directMatch) return directMatch[1].trim();
|
|
728
|
-
|
|
729
|
-
// op:// ref — try 1Password CLI
|
|
730
|
-
const opRe = new RegExp(`export\\s+${varName}=["']?\\$\\(op read '([^']+)'\\)["']?`, 'm');
|
|
731
|
-
const opMatch = content.match(opRe);
|
|
732
|
-
if (opMatch) {
|
|
733
|
-
// op read failures often mean the rc-file field name doesn't match the
|
|
734
|
-
// actual 1Password item field; surface them under the debug flag.
|
|
735
|
-
try {
|
|
736
|
-
return resolveOpRef(opMatch[1]);
|
|
737
|
-
} catch (err) {
|
|
738
|
-
if (process.env.CONSTRUCT_DEBUG_CREDENTIALS === '1') {
|
|
739
|
-
process.stderr.write(`[credentials] op read failed for ${varName}: ${String(err.message).slice(0, 200)}\n`);
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
}
|
|
688
|
+
// Jira
|
|
689
|
+
const jiraConfigured = Boolean(
|
|
690
|
+
hasSecret('JIRA_HOST', { home: hd })
|
|
691
|
+
&& hasSecret('JIRA_USER', { home: hd })
|
|
692
|
+
&& hasSecret('JIRA_API_TOKEN', { home: hd })
|
|
693
|
+
&& hasSecret('JIRA_PROJECT', { home: hd }),
|
|
694
|
+
);
|
|
695
|
+
const jiraHost = peekRawCredential('JIRA_HOST', { home: hd })?.raw || null;
|
|
743
696
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
encoding: 'utf8', timeout: 5000,
|
|
753
|
-
});
|
|
754
|
-
if (shellResult.status === 0 && shellResult.stdout?.trim()) {
|
|
755
|
-
const val = shellResult.stdout.trim().split('\n').pop()?.trim();
|
|
756
|
-
if (val && !val.includes('ERROR') && !val.includes('could not read')) return val;
|
|
757
|
-
}
|
|
758
|
-
} catch { /* shell eval failed */ }
|
|
759
|
-
}
|
|
760
|
-
} catch { /* skip unreadable */ }
|
|
761
|
-
}
|
|
762
|
-
} catch { /* skip */ }
|
|
697
|
+
// Confluence
|
|
698
|
+
const confluenceConfigured = Boolean(
|
|
699
|
+
hasSecret('CONFLUENCE_HOST', { home: hd })
|
|
700
|
+
&& hasSecret('CONFLUENCE_USER', { home: hd })
|
|
701
|
+
&& hasSecret('CONFLUENCE_API_TOKEN', { home: hd })
|
|
702
|
+
&& hasSecret('CONFLUENCE_SPACE', { home: hd }),
|
|
703
|
+
);
|
|
704
|
+
const confluenceSpace = peekRawCredential('CONFLUENCE_SPACE', { home: hd })?.raw || null;
|
|
763
705
|
|
|
764
|
-
return
|
|
706
|
+
return {
|
|
707
|
+
github: ghCliAuthenticated || Boolean(githubTokenPresent && githubRepo),
|
|
708
|
+
githubMethod,
|
|
709
|
+
githubRepo,
|
|
710
|
+
jira: jiraConfigured,
|
|
711
|
+
jiraHost,
|
|
712
|
+
confluence: confluenceConfigured,
|
|
713
|
+
confluenceSpace,
|
|
714
|
+
};
|
|
765
715
|
}
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
import crypto from 'node:crypto';
|
|
28
28
|
import { readCurrentModels, readOpenRouterApiKeyFromOpenCodeConfig } from './model-router.mjs';
|
|
29
29
|
import { getUserEnvPath } from './env-config.mjs';
|
|
30
|
+
import { extractOpRef, hasSecret, resolveOpRefAsync, resolveSecretAsync } from './providers/secret-resolver.mjs';
|
|
30
31
|
import { logIntentVerification } from './telemetry/intent-verifications.mjs';
|
|
31
32
|
|
|
32
33
|
export const INTENT_VERIFY_TIMEOUT_MS = 3000;
|
|
@@ -66,12 +67,49 @@ export function resetCache() {
|
|
|
66
67
|
CACHE.clear();
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
async function resolveSecretOrNull(varName, { env = process.env, allowAmbient = true } = {}) {
|
|
71
|
+
try {
|
|
72
|
+
return await resolveSecretAsync(varName, { env, allowAmbient });
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function materializeConfiguredKey(rawValue) {
|
|
79
|
+
if (typeof rawValue !== 'string' || rawValue.length === 0) return null;
|
|
80
|
+
const ref = extractOpRef(rawValue);
|
|
81
|
+
try {
|
|
82
|
+
return ref ? await resolveOpRefAsync(ref) : rawValue;
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function resolveOpenRouterApiKey() {
|
|
89
|
+
const envKey = await resolveSecretOrNull('OPENROUTER_API_KEY');
|
|
90
|
+
if (envKey) return envKey;
|
|
91
|
+
try {
|
|
92
|
+
return await materializeConfiguredKey(readOpenRouterApiKeyFromOpenCodeConfig());
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function resolveAnthropicApiKey() {
|
|
99
|
+
return resolveSecretOrNull('ANTHROPIC_API_KEY');
|
|
100
|
+
}
|
|
101
|
+
|
|
69
102
|
function isApiKeyConfigured() {
|
|
70
|
-
|
|
71
|
-
|
|
103
|
+
// Presence gate only — hasSecret detects a configured key (including an op://
|
|
104
|
+
// reference) without materializing it, so deciding whether to run the background
|
|
105
|
+
// intent verifier never spawns `op read` or a 1Password biometric prompt on the
|
|
106
|
+
// routing hot path. Materialization happens lazily in defaultModelCaller when a
|
|
107
|
+
// verification call is actually made.
|
|
108
|
+
if (hasSecret('ANTHROPIC_API_KEY')) return true;
|
|
109
|
+
if (hasSecret('OPENROUTER_API_KEY')) return true;
|
|
72
110
|
try {
|
|
73
111
|
if (readOpenRouterApiKeyFromOpenCodeConfig()) return true;
|
|
74
|
-
} catch { /*
|
|
112
|
+
} catch { /* OpenCode provider config is optional */ }
|
|
75
113
|
return false;
|
|
76
114
|
}
|
|
77
115
|
|
|
@@ -115,8 +153,8 @@ async function fetchWithTimeout(url, options, timeoutMs = INTENT_VERIFY_TIMEOUT_
|
|
|
115
153
|
}
|
|
116
154
|
|
|
117
155
|
async function defaultModelCaller({ system, user }) {
|
|
118
|
-
const orKey =
|
|
119
|
-
const anthropicKey =
|
|
156
|
+
const orKey = await resolveOpenRouterApiKey();
|
|
157
|
+
const anthropicKey = await resolveAnthropicApiKey();
|
|
120
158
|
if (!orKey && !anthropicKey) throw new Error('no model api key configured');
|
|
121
159
|
|
|
122
160
|
const envPath = getUserEnvPath();
|
|
@@ -15,26 +15,26 @@ const SOFFICE_CANDIDATES = [
|
|
|
15
15
|
'/Applications/LibreOffice.app/Contents/MacOS/soffice',
|
|
16
16
|
];
|
|
17
17
|
|
|
18
|
-
function whichBin(name) {
|
|
18
|
+
function whichBin(name, env) {
|
|
19
19
|
const cmd = process.platform === 'win32' ? 'where' : 'which';
|
|
20
|
-
const result = spawnSync(cmd, [name], { encoding: 'utf8' });
|
|
20
|
+
const result = spawnSync(cmd, [name], { encoding: 'utf8', env });
|
|
21
21
|
if (result.status !== 0) return null;
|
|
22
22
|
return (result.stdout || '').trim().split('\n')[0] || null;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export function resolveLibreOfficeBin(env = process.env) {
|
|
25
|
+
export function resolveLibreOfficeBin(env = process.env, { existsSyncFn = fs.existsSync } = {}) {
|
|
26
26
|
const fromEnv = (env.CONSTRUCT_LIBREOFFICE_BIN || env.SOFFICE_BIN || '').trim();
|
|
27
|
-
if (fromEnv &&
|
|
27
|
+
if (fromEnv && existsSyncFn(fromEnv)) return fromEnv;
|
|
28
28
|
for (const candidate of SOFFICE_CANDIDATES) {
|
|
29
|
-
if (candidate.includes('/') &&
|
|
30
|
-
const found = whichBin(candidate);
|
|
29
|
+
if (candidate.includes('/') && existsSyncFn(candidate)) return candidate;
|
|
30
|
+
const found = whichBin(candidate, env);
|
|
31
31
|
if (found) return found;
|
|
32
32
|
}
|
|
33
33
|
return null;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export function libreOfficePresent(env = process.env) {
|
|
37
|
-
return Boolean(resolveLibreOfficeBin(env));
|
|
36
|
+
export function libreOfficePresent(env = process.env, opts) {
|
|
37
|
+
return Boolean(resolveLibreOfficeBin(env, opts));
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export function libreOfficeInstallHint() {
|
package/lib/logging/rotate.mjs
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* lib/logging/rotate.mjs — shared file-rotation primitive.
|
|
3
3
|
*
|
|
4
4
|
* Two consumers need bounded log files: the trace writer
|
|
5
|
-
* (
|
|
6
|
-
*
|
|
5
|
+
* (state-root `traces/<date>.jsonl` — ADR-0066, machine-scoped, not
|
|
6
|
+
* project-local — capped to avoid >100MB single-file commits that GitHub
|
|
7
|
+
* would reject if ever committed) and the embed daemon stdout log
|
|
7
8
|
* (`~/.cx/runtime/embed-daemon.log`, capped so a stuck "Telemetry skipped"
|
|
8
9
|
* message can't fill the disk).
|
|
9
10
|
*
|
|
@@ -183,8 +184,8 @@ export function appendWithRotationSync(filePath, line, { maxBytes, maxSegments =
|
|
|
183
184
|
* background daemons).
|
|
184
185
|
*/
|
|
185
186
|
export const LIMITS = {
|
|
186
|
-
// Trace shards under
|
|
187
|
-
// 100 MB single-file ceiling. Bead construct-1vv5.
|
|
187
|
+
// Trace shards under the state-root's traces/<date>.jsonl — capped below
|
|
188
|
+
// GitHub's 100 MB single-file ceiling. Bead construct-1vv5.
|
|
188
189
|
|
|
189
190
|
trace: {
|
|
190
191
|
maxBytes: 100 * 1024 * 1024,
|