@geraldmaron/construct 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +36 -0
- package/README.md +41 -7
- package/bin/construct +1029 -65
- package/bin/construct-postinstall.mjs +27 -2
- package/config/tag-vocabulary.json +264 -0
- package/examples/distribution/sources/deck-one-pager.md +1 -1
- package/lib/acp/server.mjs +21 -7
- package/lib/adapters-sync.mjs +2 -1
- package/lib/audit-trail.mjs +185 -2
- package/lib/beads/auto-close.mjs +2 -1
- package/lib/beads/drift.mjs +2 -1
- package/lib/bridges/copilot-proxy.mjs +20 -5
- package/lib/certification/skill-inventory.mjs +10 -1
- package/lib/cli/approvals.mjs +147 -0
- package/lib/cli-commands.mjs +105 -14
- package/lib/comment-lint.mjs +127 -8
- package/lib/config/schema.mjs +7 -30
- package/lib/config/source-target-registry.mjs +70 -0
- package/lib/config/source-targets.mjs +179 -205
- package/lib/contracts/coverage.mjs +77 -0
- package/lib/contracts/validate.mjs +102 -13
- package/lib/contracts/violation-log.mjs +50 -4
- package/lib/db/migrate.mjs +69 -0
- package/lib/db/migrations/001_orchestration_runs.sql +9 -0
- package/lib/db/migrations/002_queue_provider.sql +50 -0
- package/lib/db/migrations/003_worker_registry.sql +17 -0
- package/lib/db/migrations/004_trace_events.sql +16 -0
- package/lib/db/migrations/005_shared_memory.sql +15 -0
- package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
- package/lib/decisions/enforced-baseline.json +0 -2
- package/lib/decisions/registry.mjs +3 -2
- package/lib/deployment/parity-contract.mjs +1 -1
- package/lib/deployment-mode.mjs +78 -2
- package/lib/diagram-export.mjs +10 -1
- package/lib/distill.mjs +5 -2
- package/lib/docs-verify.mjs +2 -1
- package/lib/doctor/cli.mjs +1 -0
- package/lib/doctor/command-on-path.mjs +24 -0
- package/lib/doctor/diagnosis.mjs +109 -0
- package/lib/doctor/embedding-health.mjs +50 -0
- package/lib/doctor/engine-health.mjs +93 -0
- package/lib/doctor/graph-validate.mjs +47 -0
- package/lib/doctor/index.mjs +23 -4
- package/lib/doctor/sidecar-providers.mjs +56 -0
- package/lib/doctor/watchers/consistency.mjs +93 -1
- package/lib/doctor/watchers/cx-budget.mjs +1 -1
- package/lib/doctor/watchers/graph-staleness.mjs +1 -1
- package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
- package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
- package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
- package/lib/doctor/watchers/provider-breaker.mjs +78 -0
- package/lib/document-export.mjs +52 -27
- package/lib/document-extract/docling-client.mjs +9 -5
- package/lib/document-extract/docling-sidecar.py +30 -0
- package/lib/document-extract.mjs +1 -1
- package/lib/document-ingest.mjs +9 -0
- package/lib/embed/approval-queue.mjs +184 -88
- package/lib/embed/authority-guard.mjs +65 -2
- package/lib/embed/capability-jobs.mjs +365 -0
- package/lib/embed/capability-lifecycle.mjs +219 -0
- package/lib/embed/capability-loader.mjs +303 -0
- package/lib/embed/capability-runtime.mjs +58 -0
- package/lib/embed/cli.mjs +185 -8
- package/lib/embed/config.mjs +4 -0
- package/lib/embed/daemon.mjs +125 -6
- package/lib/embed/demand-fetch.mjs +190 -54
- package/lib/embed/inbox.mjs +12 -10
- package/lib/embed/presets/ops-triage.mjs +236 -0
- package/lib/embed/presets/pm-feedback.mjs +341 -0
- package/lib/embed/presets/tpm.mjs +401 -0
- package/lib/embed/providers/jira.mjs +72 -1
- package/lib/embed/providers/registry.mjs +140 -33
- package/lib/embed/worker.mjs +5 -0
- package/lib/embedded-contract/capability.mjs +4 -0
- package/lib/embedded-contract/execution.mjs +1 -1
- package/lib/embedded-contract/model-resolve.mjs +53 -3
- package/lib/embedded-contract/workflow-defs.mjs +48 -73
- package/lib/embedded-contract/workflow-invoke.mjs +144 -4
- package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
- package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
- package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
- package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
- package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
- package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
- package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
- package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
- package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
- package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
- package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
- package/lib/env-config.mjs +60 -11
- package/lib/export-validate.mjs +34 -2
- package/lib/extensions/index.mjs +27 -0
- package/lib/extensions/loader.mjs +120 -0
- package/lib/extensions/manifest-schema.mjs +141 -0
- package/lib/extensions/manifests/anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
- package/lib/extensions/manifests/directory.manifest.json +12 -0
- package/lib/extensions/manifests/docling.manifest.json +37 -0
- package/lib/extensions/manifests/echo.manifest.json +8 -0
- package/lib/extensions/manifests/feedback.manifest.json +12 -0
- package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
- package/lib/extensions/manifests/github.manifest.json +52 -0
- package/lib/extensions/manifests/linear.manifest.json +50 -0
- package/lib/extensions/manifests/local.manifest.json +12 -0
- package/lib/extensions/manifests/ollama.manifest.json +12 -0
- package/lib/extensions/manifests/openai.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter.manifest.json +12 -0
- package/lib/extensions/manifests/postgres.manifest.json +12 -0
- package/lib/extensions/manifests/salesforce.manifest.json +12 -0
- package/lib/extensions/manifests/slack.manifest.json +58 -0
- package/lib/extensions/manifests/whisper.manifest.json +36 -0
- package/lib/extensions/validate.mjs +175 -0
- package/lib/features.mjs +13 -9
- package/lib/flows/checkpoint.mjs +184 -0
- package/lib/flows/constants.mjs +27 -0
- package/lib/flows/define.mjs +146 -0
- package/lib/flows/engine.mjs +249 -0
- package/lib/flows/errors.mjs +19 -0
- package/lib/flows/index.mjs +27 -0
- package/lib/flows/joins.mjs +18 -0
- package/lib/flows/schema.mjs +90 -0
- package/lib/flows/state.mjs +31 -0
- package/lib/frameworks/loader.mjs +99 -0
- package/lib/frameworks/schema.mjs +157 -0
- package/lib/graph/build-from-corpus.mjs +67 -0
- package/lib/graph/build-from-embed.mjs +82 -0
- package/lib/graph/build-from-registry.mjs +164 -3
- package/lib/graph/cli.mjs +456 -12
- package/lib/graph/gap-queries.mjs +156 -0
- package/lib/graph/gaps.mjs +41 -0
- package/lib/graph/impacted.mjs +129 -0
- package/lib/graph/runtime-evidence.mjs +177 -0
- package/lib/graph/security-coverage.mjs +113 -0
- package/lib/graph/staleness.mjs +241 -10
- package/lib/graph/store.mjs +24 -7
- package/lib/graph/validate.mjs +161 -0
- package/lib/headhunt.mjs +9 -8
- package/lib/health-check.mjs +15 -7
- package/lib/hook-health.mjs +1 -1
- package/lib/hooks/agent-tracker.mjs +10 -4
- package/lib/hooks/edit-guard.mjs +3 -3
- package/lib/hooks/graph-impact-advisory.mjs +2 -2
- package/lib/hooks/guard-bash.mjs +41 -15
- package/lib/hooks/mcp-health-check.mjs +11 -4
- package/lib/hooks/model-fallback.mjs +50 -6
- package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
- package/lib/hooks/pre-compact.mjs +181 -173
- package/lib/hooks/rule-verifier.mjs +2 -1
- package/lib/hooks/session-reflect.mjs +2 -1
- package/lib/hooks/session-start.mjs +3 -3
- package/lib/host/readiness.mjs +109 -0
- package/lib/host-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +29 -8
- package/lib/identity.mjs +92 -0
- package/lib/ingest/degraded-extract.mjs +96 -0
- package/lib/ingest/docling-remote.mjs +2 -1
- package/lib/ingest/provider-extract.mjs +7 -19
- package/lib/ingest/sidecar-providers.mjs +196 -0
- package/lib/ingest-tooling.mjs +11 -5
- package/lib/init-unified.mjs +55 -38
- package/lib/init-update.mjs +2 -1
- package/lib/install/legacy-global-cleanup.mjs +25 -0
- package/lib/install/stage-project.mjs +41 -4
- package/lib/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- package/lib/intake/prepare.mjs +2 -0
- package/lib/intake/queue-registry.mjs +50 -0
- package/lib/intake/queue.mjs +133 -17
- package/lib/intake/session-prelude.mjs +57 -8
- package/lib/integrations/intake-integrations.mjs +61 -111
- package/lib/intent-classifier.mjs +43 -5
- package/lib/libreoffice-export.mjs +8 -8
- package/lib/logging/rotate.mjs +5 -4
- package/lib/mcp/broker.mjs +332 -17
- package/lib/mcp/denied-store.mjs +95 -0
- package/lib/mcp/destructive-approval.mjs +57 -0
- package/lib/mcp/destructive-gate.mjs +30 -0
- package/lib/mcp/dispatch-envelope.mjs +199 -0
- package/lib/mcp/memory-bridge.mjs +2 -1
- package/lib/mcp/server.mjs +185 -1268
- package/lib/mcp/tool-definitions-memory.mjs +376 -0
- package/lib/mcp/tool-definitions-project.mjs +271 -0
- package/lib/mcp/tool-definitions-skills.mjs +311 -0
- package/lib/mcp/tool-definitions-workflow.mjs +398 -0
- package/lib/mcp/tool-definitions.mjs +25 -0
- package/lib/mcp/tool-rate-limit.mjs +47 -0
- package/lib/mcp/tool-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +95 -0
- package/lib/mcp/tool-surface-parity.mjs +60 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +197 -19
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- package/lib/mcp/tools/project.mjs +25 -8
- package/lib/mcp/tools/provider-write.mjs +187 -0
- package/lib/mcp/tools/scope.mjs +0 -3
- package/lib/mcp/tools/skills.mjs +1 -1
- package/lib/mcp/tools/storage.mjs +2 -2
- package/lib/mcp/tools/web-search-governance.mjs +96 -0
- package/lib/mcp/tools/web-search.mjs +5 -76
- package/lib/mcp/transport/auth.mjs +238 -0
- package/lib/mcp/transport/http.mjs +136 -0
- package/lib/mcp/transport/mode.mjs +31 -0
- package/lib/mcp/transport/stdio.mjs +22 -0
- package/lib/mcp-catalog.json +4 -4
- package/lib/mcp-manager.mjs +34 -23
- package/lib/mcp-platform-config.mjs +53 -20
- package/lib/mode-capabilities.mjs +109 -0
- package/lib/model-router.mjs +42 -9
- package/lib/models/catalog.mjs +40 -1
- package/lib/net-guard.mjs +247 -0
- package/lib/observation-store.mjs +6 -2
- package/lib/ollama/provision-context.mjs +2 -1
- package/lib/opencode-config.mjs +22 -3
- package/lib/opencode-runtime-plugin.mjs +120 -2
- package/lib/oracle/actions.mjs +204 -10
- package/lib/oracle/cli.mjs +24 -3
- package/lib/oracle/daemon-entry.mjs +6 -0
- package/lib/oracle/dispatch.mjs +2 -1
- package/lib/oracle/execute.mjs +2 -2
- package/lib/oracle/gaps.mjs +3 -3
- package/lib/oracle/heartbeat.mjs +53 -0
- package/lib/oracle/read-model.mjs +69 -13
- package/lib/oracle/reconcile.mjs +3 -46
- package/lib/oracle/routing.mjs +37 -31
- package/lib/oracle/synthesize.mjs +1 -1
- package/lib/orchestration/classification.mjs +434 -0
- package/lib/orchestration/delegation-flow.mjs +132 -0
- package/lib/orchestration/flow-selection.mjs +418 -0
- package/lib/orchestration/gates.mjs +244 -0
- package/lib/orchestration/host-sampling.mjs +121 -0
- package/lib/orchestration/policy-constants.mjs +48 -0
- package/lib/orchestration/provider-outcome.mjs +197 -0
- package/lib/orchestration/readiness.mjs +114 -13
- package/lib/orchestration/run-store-postgres.mjs +32 -26
- package/lib/orchestration/run-store-sqlite.mjs +8 -14
- package/lib/orchestration/run-store.mjs +25 -12
- package/lib/orchestration/runtime.mjs +508 -62
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +60 -0
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +763 -80
- package/lib/orchestration-policy.mjs +67 -1111
- package/lib/output-quality.mjs +61 -2
- package/lib/packs/cli.mjs +144 -0
- package/lib/packs/core-pack.mjs +112 -0
- package/lib/packs/enablement.mjs +187 -0
- package/lib/packs/index.mjs +23 -0
- package/lib/packs/loader.mjs +176 -0
- package/lib/packs/manifest-schema.mjs +58 -0
- package/lib/packs/prompts.mjs +120 -0
- package/lib/packs/validate.mjs +208 -0
- package/lib/path-policy.mjs +56 -0
- package/lib/plugin-registry.mjs +4 -5
- package/lib/policy/audit-gate.mjs +42 -0
- package/lib/policy/consumption-budget.mjs +149 -0
- package/lib/policy/engine.mjs +81 -6
- package/lib/policy/role-authority.mjs +89 -0
- package/lib/prompt-composer.js +19 -3
- package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
- package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
- package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
- package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
- package/lib/providers/contract/adapters/github/index.mjs +38 -3
- package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
- package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
- package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
- package/lib/providers/contract/adapters/jira/manifest.json +17 -0
- package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
- package/lib/providers/contract.mjs +207 -0
- package/lib/providers/credential-bootstrap.mjs +7 -4
- package/lib/providers/credential-sources.mjs +14 -2
- package/lib/providers/directory/index.mjs +261 -0
- package/lib/providers/feedback/index.mjs +392 -0
- package/lib/providers/filter-audit.mjs +68 -0
- package/lib/providers/filter-schema.mjs +54 -0
- package/lib/providers/github/index.mjs +31 -0
- package/lib/providers/instance-config.mjs +215 -0
- package/lib/providers/op-locate.mjs +96 -0
- package/lib/providers/op-run.mjs +64 -9
- package/lib/providers/registry.mjs +26 -3
- package/lib/providers/secret-audit-wiring.mjs +41 -18
- package/lib/providers/secret-resolver.mjs +263 -31
- package/lib/publish-template.mjs +6 -3
- package/lib/publish-tooling.mjs +4 -0
- package/lib/publish.mjs +32 -4
- package/lib/queue/pg-queue.mjs +395 -0
- package/lib/reflect.mjs +2 -1
- package/lib/registry/assemble.mjs +28 -2
- package/lib/registry/catalog.mjs +6 -0
- package/lib/registry/consolidation.mjs +8 -1
- package/lib/registry/custom-scaffold.mjs +200 -0
- package/lib/registry/custom-schema.mjs +137 -0
- package/lib/registry/loader.mjs +15 -4
- package/lib/registry/manifests/format-engines.default.json +15 -0
- package/lib/registry/manifests/surface-map.default.json +46 -0
- package/lib/registry/retired-paths.mjs +1 -0
- package/lib/registry/surface-map.mjs +100 -50
- package/lib/registry/validate.mjs +3 -3
- package/lib/render-visual-check.mjs +240 -0
- package/lib/resources/budget.mjs +40 -17
- package/lib/roles/flavor-bindings.mjs +36 -14
- package/lib/roles/gateway.mjs +15 -8
- package/lib/roots.mjs +107 -0
- package/lib/runtime/uv-bootstrap.mjs +32 -6
- package/lib/runtime/whisper-bootstrap.mjs +11 -3
- package/lib/runtime-env.mjs +5 -2
- package/lib/sandbox.mjs +1 -1
- package/lib/scheduler/index.mjs +8 -20
- package/lib/schema-infer.mjs +31 -2
- package/lib/scopes/lifecycle.mjs +29 -25
- package/lib/scopes/loader.mjs +49 -12
- package/lib/scopes/teams.mjs +1 -1
- package/lib/security/ingest-boundary.mjs +80 -0
- package/lib/security/recall-wrapper.mjs +99 -0
- package/lib/security/trust.mjs +132 -0
- package/lib/service-manager.mjs +93 -24
- package/lib/setup.mjs +41 -23
- package/lib/skills-apply.mjs +4 -2
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/state-root.mjs +147 -0
- package/lib/status.mjs +597 -6
- package/lib/storage/admin.mjs +84 -7
- package/lib/storage/backend-registry.mjs +73 -0
- package/lib/storage/backend.mjs +63 -9
- package/lib/storage/embeddings-openai.mjs +12 -2
- package/lib/storage/hybrid-query.mjs +11 -3
- package/lib/storage/retrieval-hardening.mjs +384 -0
- package/lib/storage/shared-memory.mjs +158 -0
- package/lib/storage/vector-client.mjs +69 -2
- package/lib/team/health.mjs +72 -0
- package/lib/telemetry/backends/local.mjs +9 -3
- package/lib/telemetry/client.mjs +3 -7
- package/lib/template-registry.mjs +10 -12
- package/lib/tenant/context.mjs +82 -0
- package/lib/tenant/isolation.mjs +129 -0
- package/lib/test-corpus-inventory.mjs +103 -2
- package/lib/uninstall/uninstall.mjs +78 -12
- package/lib/validator.mjs +4 -6
- package/lib/worker/entrypoint.mjs +2 -1
- package/lib/worker/run.mjs +2 -2
- package/lib/worker/trace.mjs +5 -11
- package/lib/workflow-state.mjs +52 -8
- package/lib/workflows/liveness.mjs +203 -0
- package/lib/workflows/loader.mjs +177 -0
- package/lib/workflows/manifest-schema.mjs +71 -0
- package/lib/workflows/surface-parity.mjs +118 -0
- package/lib/workflows/validate.mjs +127 -0
- package/lib/writes/control-plane.mjs +140 -0
- package/lib/writes/envelope.mjs +206 -0
- package/lib/writes/sent-log.mjs +86 -0
- package/lib/writes/write-intent.mjs +109 -0
- package/package.json +20 -7
- package/personas/construct.md +12 -3
- package/registry/agent-manifest.json +117 -0
- package/registry/capabilities.json +1987 -0
- package/rules/common/comments.md +1 -0
- package/schemas/brand-voice.schema.json +24 -0
- package/schemas/capability-registry.schema.json +72 -0
- package/schemas/certification-run.schema.json +130 -0
- package/schemas/demo-recording.schema.json +46 -0
- package/schemas/eval-dataset.schema.json +79 -0
- package/schemas/execution-capability-profile.schema.json +46 -0
- package/schemas/execution-policy.schema.json +114 -0
- package/schemas/improvement-proposal.schema.json +65 -0
- package/schemas/mcp-tool-output.schema.json +61 -0
- package/schemas/platform-capabilities.schema.json +83 -0
- package/schemas/project-config.schema.json +215 -0
- package/schemas/project-demo.schema.json +60 -0
- package/schemas/provider-behavior-matrix.schema.json +91 -0
- package/schemas/scope.schema.json +197 -0
- package/schemas/specialist-trace.schema.json +107 -0
- package/schemas/team.schema.json +99 -0
- package/schemas/unified-registry.schema.json +546 -0
- package/scripts/sync-specialists.mjs +336 -106
- package/skills/docs/adr-workflow.md +1 -1
- package/skills/docs/codebase-research-workflow.md +3 -3
- package/skills/docs/prd-workflow.md +5 -6
- package/skills/docs/runbook-workflow.md +2 -2
- package/skills/docs/user-research-workflow.md +3 -3
- package/skills/operating/fleet-health-routing.md +77 -0
- package/skills/roles/ai-engineer.md +1 -1
- package/skills/roles/architect.md +0 -1
- package/skills/roles/business-strategist.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/data-engineer.md +1 -1
- package/skills/roles/data-engineer.pipeline.md +1 -1
- package/skills/roles/data-engineer.vector-retrieval.md +1 -2
- package/skills/roles/data-engineer.warehouse.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +0 -1
- package/skills/roles/devil-advocate.md +1 -1
- package/skills/roles/docs-keeper.md +1 -1
- package/skills/roles/evaluator.md +1 -1
- package/skills/roles/explorer.md +1 -1
- package/skills/roles/platform-engineer.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -2
- package/skills/roles/qa.api-contract.md +0 -1
- package/skills/roles/qa.data-pipeline.md +0 -1
- package/skills/roles/qa.md +0 -1
- package/skills/roles/qa.web-ui.md +0 -1
- package/skills/roles/release-manager.md +1 -1
- package/skills/roles/researcher.md +0 -2
- package/skills/roles/reviewer.md +0 -3
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +0 -1
- package/skills/roles/security.privacy.md +0 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/sre.md +1 -1
- package/skills/roles/test-automation.md +1 -1
- package/skills/roles/trace-reviewer.md +1 -1
- package/skills/roles/ux-researcher.md +1 -1
- package/specialists/artifact-manifest.json +36 -36
- package/specialists/org/contracts/accessibility-to-qa.json +11 -3
- package/specialists/org/contracts/any-to-business-strategist.json +19 -7
- package/specialists/org/contracts/any-to-debugger.json +7 -1
- package/specialists/org/contracts/any-to-designer.json +7 -1
- package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
- package/specialists/org/contracts/any-to-explorer.json +13 -4
- package/specialists/org/contracts/any-to-sre-incident.json +6 -2
- package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
- package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
- package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
- package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
- package/specialists/org/contracts/architect-to-engineer.json +20 -4
- package/specialists/org/contracts/architect-to-evaluator.json +15 -5
- package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
- package/specialists/org/contracts/architect-to-operations.json +17 -4
- package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
- package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
- package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
- package/specialists/org/contracts/engineer-to-qa.json +20 -4
- package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
- package/specialists/org/contracts/explorer-to-engineer.json +11 -3
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
- package/specialists/org/contracts/operations-to-user.json +53 -0
- package/specialists/org/contracts/operations-triage-output.json +53 -0
- package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
- package/specialists/org/contracts/product-manager-to-architect.json +30 -10
- package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
- package/specialists/org/contracts/qa-to-release-manager.json +11 -3
- package/specialists/org/contracts/researcher-to-architect.json +10 -2
- package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
- package/specialists/org/contracts/reviewer-to-security.json +17 -3
- package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
- package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
- package/specialists/org/contracts/user-to-construct.json +11 -4
- package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
- package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
- package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
- package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
- package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
- package/specialists/org/groups/engineering-group.json +2 -6
- package/specialists/org/groups/governance-group.json +2 -3
- package/specialists/org/groups/operations-group.json +5 -8
- package/specialists/org/groups/product-group.json +4 -8
- package/specialists/org/groups/quality-group.json +3 -7
- package/specialists/org/groups/strategy-group.json +6 -9
- package/specialists/org/models.json.example +8 -0
- package/specialists/org/scopes/creative.json +6 -1
- package/specialists/org/scopes/operations.json +7 -1
- package/specialists/org/scopes/research.json +6 -1
- package/specialists/org/scopes/rnd.json +7 -1
- package/specialists/org/specialists/cx-architect.json +27 -8
- package/specialists/org/specialists/cx-designer.json +22 -8
- package/specialists/org/specialists/cx-engineer.json +71 -7
- package/specialists/org/specialists/cx-operations.json +89 -15
- package/specialists/org/specialists/cx-orchestrator.json +16 -4
- package/specialists/org/specialists/cx-product-manager.json +28 -9
- package/specialists/org/specialists/cx-qa.json +16 -6
- package/specialists/org/specialists/cx-researcher.json +41 -7
- package/specialists/org/specialists/cx-reviewer.json +61 -11
- package/specialists/org/specialists/cx-security.json +30 -10
- package/specialists/org/teams/design-team.json +3 -4
- package/specialists/org/teams/engineering-team.json +3 -10
- package/specialists/org/teams/governance-team.json +3 -5
- package/specialists/org/teams/operations-team.json +6 -12
- package/specialists/org/teams/product-management-team.json +2 -3
- package/specialists/org/teams/quality-team.json +4 -12
- package/specialists/org/teams/research-team.json +3 -3
- package/specialists/org/teams/strategy-team.json +7 -14
- package/specialists/prompts/cx-architect.md +20 -1
- package/specialists/prompts/cx-data-analyst.md +1 -1
- package/specialists/prompts/cx-designer.md +12 -4
- package/specialists/prompts/cx-engineer.md +15 -1
- package/specialists/prompts/cx-operations.md +14 -2
- package/specialists/prompts/cx-orchestrator.md +9 -5
- package/specialists/prompts/cx-product-manager.md +5 -1
- package/specialists/prompts/cx-qa.md +6 -2
- package/specialists/prompts/cx-researcher.md +27 -31
- package/specialists/prompts/cx-reviewer.md +19 -1
- package/specialists/prompts/cx-security.md +5 -1
- package/templates/distribution/construct-brand.typ +123 -59
- package/templates/distribution/construct-decision.typ +6 -3
- package/templates/distribution/construct-pdf.typ +11 -4
- package/templates/distribution/construct-prd.typ +6 -3
- package/templates/distribution/construct-research.typ +7 -4
- package/vendor/pandoc-ext/README.md +3 -0
- package/vendor/pandoc-ext/diagram.lua +687 -0
- package/lib/providers/contract/adapters/git/index.mjs +0 -115
- package/lib/providers/contract/adapters/slack/index.mjs +0 -175
- package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
- package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
- package/specialists/org/contracts/designer-to-accessibility.json +0 -36
- package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
- package/specialists/org/contracts/qa-to-test-automation.json +0 -42
- package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
- package/specialists/org/contracts/sre-to-release-manager.json +0 -36
- package/specialists/org/specialists/cx-accessibility.json +0 -69
- package/specialists/org/specialists/cx-ai-engineer.json +0 -75
- package/specialists/org/specialists/cx-business-strategist.json +0 -72
- package/specialists/org/specialists/cx-data-engineer.json +0 -71
- package/specialists/org/specialists/cx-devil-advocate.json +0 -71
- package/specialists/org/specialists/cx-docs-keeper.json +0 -82
- package/specialists/org/specialists/cx-evaluator.json +0 -69
- package/specialists/org/specialists/cx-explorer.json +0 -69
- package/specialists/org/specialists/cx-legal-compliance.json +0 -74
- package/specialists/org/specialists/cx-oracle.json +0 -46
- package/specialists/org/specialists/cx-platform-engineer.json +0 -80
- package/specialists/org/specialists/cx-rd-lead.json +0 -73
- package/specialists/org/specialists/cx-release-manager.json +0 -77
- package/specialists/org/specialists/cx-sre.json +0 -94
- package/specialists/org/specialists/cx-test-automation.json +0 -69
- package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
- package/specialists/org/specialists/cx-ux-researcher.json +0 -68
- package/specialists/org/teams/accessibility-team.json +0 -39
- package/specialists/org/teams/ux-research-team.json +0 -39
- package/specialists/prompts/cx-accessibility.md +0 -55
- package/specialists/prompts/cx-ai-engineer.md +0 -124
- package/specialists/prompts/cx-business-strategist.md +0 -58
- package/specialists/prompts/cx-data-engineer.md +0 -55
- package/specialists/prompts/cx-devil-advocate.md +0 -59
- package/specialists/prompts/cx-docs-keeper.md +0 -164
- package/specialists/prompts/cx-evaluator.md +0 -49
- package/specialists/prompts/cx-explorer.md +0 -71
- package/specialists/prompts/cx-legal-compliance.md +0 -58
- package/specialists/prompts/cx-oracle.md +0 -98
- package/specialists/prompts/cx-platform-engineer.md +0 -97
- package/specialists/prompts/cx-rd-lead.md +0 -59
- package/specialists/prompts/cx-release-manager.md +0 -54
- package/specialists/prompts/cx-sre.md +0 -111
- package/specialists/prompts/cx-test-automation.md +0 -55
- package/specialists/prompts/cx-trace-reviewer.md +0 -101
- package/specialists/prompts/cx-ux-researcher.md +0 -53
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/providers/contract/adapters/jira/governed-write.mjs — envelope-shaped
|
|
3
|
+
* Jira write adapter.
|
|
4
|
+
*
|
|
5
|
+
* Wraps a Jira REST transport in the provider shape lib/writes/envelope.mjs
|
|
6
|
+
* expects: write(config, payload), meta.id, search(). This is the only
|
|
7
|
+
* exposed entry point for Jira writes — specialists and CLI callers route
|
|
8
|
+
* through writeWithEnvelope(), never through a raw Jira client, so dedup,
|
|
9
|
+
* retry, dry-run, approval, and audit stay centralized in the envelope
|
|
10
|
+
* (LMCP-J2) rather than being reimplemented per-adapter.
|
|
11
|
+
*
|
|
12
|
+
* Adds the two behaviors createmeta + ADF require that a generic envelope
|
|
13
|
+
* cannot provide:
|
|
14
|
+
*
|
|
15
|
+
* - createmeta validation before submit: fetches
|
|
16
|
+
* `issue/createmeta?projectKeys=X&issuetypeNames=Y` (via the injected
|
|
17
|
+
* `jiraTransport`) and checks required fields and ADF-vs-plain field
|
|
18
|
+
* shape before making the create/comment call, so a bad project key,
|
|
19
|
+
* unknown issue type, missing required field, or wrong field shape
|
|
20
|
+
* surfaces as an actionable client-side error rather than a raw Jira
|
|
21
|
+
* 400.
|
|
22
|
+
* - ADF body construction: `description` (issue create) and `body`
|
|
23
|
+
* (comment) are converted from plain text to Atlassian Document Format
|
|
24
|
+
* via lib/providers/contract/adapters/jira/adf.mjs before submit.
|
|
25
|
+
*
|
|
26
|
+
* Only `type: 'issue'` and `type: 'comment'` writes get createmeta + ADF
|
|
27
|
+
* treatment; other write types are rejected — this adapter does not
|
|
28
|
+
* support transitions or field updates (non-goal per LMCP-J3).
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { AuthError } from '../../errors.mjs';
|
|
32
|
+
import { buildAdfDocument, renderAdfPreview } from './adf.mjs';
|
|
33
|
+
import { validateAgainstCreatemeta } from './createmeta.mjs';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {object} opts
|
|
37
|
+
* @param {object} opts.jiraTransport - underlying Jira REST transport; must implement
|
|
38
|
+
* `fetchCreatemeta(projectKey, issueTypeName)`, `createIssue(fields)`,
|
|
39
|
+
* `createComment(issueKey, adfBody)`, and `searchIssues(jql)`.
|
|
40
|
+
*/
|
|
41
|
+
export function createGovernedJiraProvider({ jiraTransport } = {}) {
|
|
42
|
+
if (!jiraTransport) throw new Error('createGovernedJiraProvider: jiraTransport is required');
|
|
43
|
+
|
|
44
|
+
async function writeIssue(payload) {
|
|
45
|
+
const { project, issueType = 'Task', summary, description, labels, assignee } = payload;
|
|
46
|
+
if (!project) throw new Error('jira governed write: payload.project is required for type "issue"');
|
|
47
|
+
if (!summary) throw new Error('jira governed write: payload.summary is required for type "issue"');
|
|
48
|
+
|
|
49
|
+
const descriptionAdf = description !== undefined ? buildAdfDocument(description) : undefined;
|
|
50
|
+
|
|
51
|
+
const validation = await validateAgainstCreatemeta({
|
|
52
|
+
fetchCreatemeta: jiraTransport.fetchCreatemeta.bind(jiraTransport),
|
|
53
|
+
projectKey: project,
|
|
54
|
+
issueTypeName: issueType,
|
|
55
|
+
fieldValues: {
|
|
56
|
+
summary,
|
|
57
|
+
description: descriptionAdf,
|
|
58
|
+
labels,
|
|
59
|
+
assignee,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (!validation.ok) {
|
|
64
|
+
const err = new Error(validation.errors.join(' '));
|
|
65
|
+
err.code = validation.reason;
|
|
66
|
+
err.jiraValidation = validation;
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const result = await jiraTransport.createIssue({
|
|
72
|
+
project,
|
|
73
|
+
issueType,
|
|
74
|
+
summary,
|
|
75
|
+
description: descriptionAdf,
|
|
76
|
+
labels,
|
|
77
|
+
assignee,
|
|
78
|
+
});
|
|
79
|
+
return { type: 'issue-created', id: result.id, key: result.key, url: result.url };
|
|
80
|
+
} catch (err) {
|
|
81
|
+
throw mapWriteTransportError(err, { project });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function writeComment(payload) {
|
|
86
|
+
const { issueKey, body } = payload;
|
|
87
|
+
if (!issueKey) throw new Error('jira governed write: payload.issueKey is required for type "comment"');
|
|
88
|
+
if (!body) throw new Error('jira governed write: payload.body is required for type "comment"');
|
|
89
|
+
|
|
90
|
+
const bodyAdf = buildAdfDocument(body);
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const result = await jiraTransport.createComment(issueKey, bodyAdf);
|
|
94
|
+
return { type: 'comment-created', id: result.id, issueKey, url: result.url };
|
|
95
|
+
} catch (err) {
|
|
96
|
+
throw mapWriteTransportError(err, { issueKey });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
meta: {
|
|
102
|
+
id: 'jira',
|
|
103
|
+
displayName: 'Jira (governed)',
|
|
104
|
+
capabilities: ['write', 'search'],
|
|
105
|
+
description: 'Envelope-routed Jira writes with createmeta pre-validation and ADF body construction.',
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
async write(config, payload) {
|
|
109
|
+
if (payload?.type === 'issue') return writeIssue(payload);
|
|
110
|
+
if (payload?.type === 'comment') return writeComment(payload);
|
|
111
|
+
throw new Error(`jira governed write: unsupported type "${payload?.type}" (only 'issue' and 'comment' are supported)`);
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
async search(config, query) {
|
|
115
|
+
return jiraTransport.searchIssues(query);
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Render the ADF-shaped payload the envelope would submit, without
|
|
120
|
+
* calling createmeta or the transport. Feeds the envelope's dry-run
|
|
121
|
+
* path (lib/writes/envelope.mjs `dryRun: true`), giving a human
|
|
122
|
+
* reviewing a pending write both the raw ADF tree and a flattened
|
|
123
|
+
* preview string, not just the plain-text input.
|
|
124
|
+
*
|
|
125
|
+
* @param {object} payload
|
|
126
|
+
* @returns {{ type: string, fields: object, adfPreview: Record<string,string> }}
|
|
127
|
+
*/
|
|
128
|
+
renderDryRun(payload) {
|
|
129
|
+
if (payload?.type === 'issue') {
|
|
130
|
+
const descriptionAdf = payload.description !== undefined ? buildAdfDocument(payload.description) : undefined;
|
|
131
|
+
return {
|
|
132
|
+
type: 'issue',
|
|
133
|
+
fields: {
|
|
134
|
+
project: { key: payload.project },
|
|
135
|
+
issuetype: { name: payload.issueType ?? 'Task' },
|
|
136
|
+
summary: payload.summary,
|
|
137
|
+
...(descriptionAdf ? { description: descriptionAdf } : {}),
|
|
138
|
+
...(payload.labels ? { labels: payload.labels } : {}),
|
|
139
|
+
...(payload.assignee ? { assignee: payload.assignee } : {}),
|
|
140
|
+
},
|
|
141
|
+
adfPreview: descriptionAdf ? { description: renderAdfPreview(descriptionAdf) } : {},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (payload?.type === 'comment') {
|
|
145
|
+
const bodyAdf = buildAdfDocument(payload.body);
|
|
146
|
+
return {
|
|
147
|
+
type: 'comment',
|
|
148
|
+
fields: { issueKey: payload.issueKey, body: bodyAdf },
|
|
149
|
+
adfPreview: { body: renderAdfPreview(bodyAdf) },
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
throw new Error(`jira governed write: cannot render dry-run for unsupported type "${payload?.type}"`);
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Map a create/comment transport failure (401/403/404) to an actionable
|
|
159
|
+
* message. Distinct from mapCreatemetaTransportError since it covers the
|
|
160
|
+
* write call itself, not the pre-flight createmeta fetch.
|
|
161
|
+
*
|
|
162
|
+
* @param {Error & {status?: number}} err
|
|
163
|
+
* @param {{ project?: string, issueKey?: string }} ctx
|
|
164
|
+
* @returns {Error}
|
|
165
|
+
*/
|
|
166
|
+
function mapWriteTransportError(err, ctx) {
|
|
167
|
+
const status = err?.status;
|
|
168
|
+
if (status === 401) {
|
|
169
|
+
return new AuthError('Jira authentication failed. Check JIRA_EMAIL / JIRA_TOKEN.', { provider: 'jira' });
|
|
170
|
+
}
|
|
171
|
+
if (status === 403) {
|
|
172
|
+
const target = ctx.project ? `project "${ctx.project}"` : `issue "${ctx.issueKey}"`;
|
|
173
|
+
return new Error(`Forbidden: the authenticated account lacks permission to write to ${target}.`);
|
|
174
|
+
}
|
|
175
|
+
if (status === 404) {
|
|
176
|
+
const target = ctx.project ? `Project "${ctx.project}"` : `Issue "${ctx.issueKey}"`;
|
|
177
|
+
return new Error(`${target} was not found, or the authenticated account cannot access it.`);
|
|
178
|
+
}
|
|
179
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export default createGovernedJiraProvider;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "jira-write",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"kind": "data-source",
|
|
5
|
+
"capabilities": ["write", "search"],
|
|
6
|
+
"secretEnvKeys": ["JIRA_URL", "JIRA_EMAIL", "JIRA_TOKEN"],
|
|
7
|
+
"operations": ["write:issue", "write:comment", "search"],
|
|
8
|
+
"securityClassification": "trusted",
|
|
9
|
+
"owner": "construct-core",
|
|
10
|
+
"compatVersion": 1,
|
|
11
|
+
"installSource": "builtin",
|
|
12
|
+
"exposure": "envelope-only",
|
|
13
|
+
"envelopeModule": "lib/writes/envelope.mjs",
|
|
14
|
+
"adapterModule": "lib/providers/contract/adapters/jira/governed-write.mjs",
|
|
15
|
+
"transportModule": "lib/providers/contract/adapters/jira/transport.mjs",
|
|
16
|
+
"notes": "Colocated write-capability manifest for LMCP-J3. Follows the lib/extensions/manifests/*.manifest.json schema (see atlassian-jira.manifest.json, which declares read/search only). Wiring this into the shared lib/extensions/manifests/ discovery directory is out of scope for this bead; capabilities here are reachable only via writeWithEnvelope(), never a direct provider-registry lookup."
|
|
17
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/providers/contract/adapters/jira/transport.mjs — real Jira Cloud REST
|
|
3
|
+
* v3 transport for the governed write adapter.
|
|
4
|
+
*
|
|
5
|
+
* Implements the `jiraTransport` shape governed-write.mjs depends on:
|
|
6
|
+
* `fetchCreatemeta`, `createIssue`, `createComment`, `searchIssues`. Kept
|
|
7
|
+
* separate from governed-write.mjs so tests can inject a fake transport
|
|
8
|
+
* (tests/fakes/fake-jira.mjs) without any network dependency, and separate
|
|
9
|
+
* from the read-oriented adapter in ./index.mjs so this write path never
|
|
10
|
+
* shares mutable state with the read path.
|
|
11
|
+
*
|
|
12
|
+
* Auth: API token via JIRA_URL + JIRA_EMAIL + JIRA_TOKEN env vars, or
|
|
13
|
+
* passed directly via config.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { AuthError, RateLimitError } from '../../errors.mjs';
|
|
17
|
+
import { guardedFetch } from '../../../../net-guard.mjs';
|
|
18
|
+
|
|
19
|
+
export function createJiraTransport(config = {}) {
|
|
20
|
+
const baseUrl = (config.baseUrl ?? process.env.JIRA_URL ?? '').replace(/\/$/, '');
|
|
21
|
+
const email = config.email ?? process.env.JIRA_EMAIL;
|
|
22
|
+
const token = config.token ?? process.env.JIRA_TOKEN;
|
|
23
|
+
|
|
24
|
+
if (!baseUrl || !email || !token) {
|
|
25
|
+
throw new AuthError(
|
|
26
|
+
'Jira transport requires JIRA_URL, JIRA_EMAIL, and JIRA_TOKEN (or config.baseUrl/email/token)',
|
|
27
|
+
{ provider: 'jira' },
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const auth = `Basic ${Buffer.from(`${email}:${token}`).toString('base64')}`;
|
|
32
|
+
|
|
33
|
+
// Egress runs through the N7 guard (SSRF/rebinding). A self-hosted instance on
|
|
34
|
+
// a private address is the only legitimate private target, so it is an
|
|
35
|
+
// explicit, audited opt-in — never the default.
|
|
36
|
+
const allowPrivate = config.allowPrivateEgress ?? process.env.CONSTRUCT_NET_ALLOW_PRIVATE_EGRESS === '1';
|
|
37
|
+
|
|
38
|
+
async function request(path, { method = 'GET', body } = {}) {
|
|
39
|
+
const res = await guardedFetch(`${baseUrl}${path}`, {
|
|
40
|
+
method,
|
|
41
|
+
headers: {
|
|
42
|
+
Authorization: auth,
|
|
43
|
+
'Content-Type': 'application/json',
|
|
44
|
+
Accept: 'application/json',
|
|
45
|
+
},
|
|
46
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
47
|
+
}, { allowPrivate });
|
|
48
|
+
|
|
49
|
+
if (res.status === 429) {
|
|
50
|
+
const retryAfter = parseInt(res.headers.get('Retry-After') ?? '60', 10);
|
|
51
|
+
throw new RateLimitError('Jira rate limit hit', { provider: 'jira', retryAfter });
|
|
52
|
+
}
|
|
53
|
+
if (!res.ok) {
|
|
54
|
+
const text = await res.text().catch(() => '');
|
|
55
|
+
const err = new Error(`Jira API ${method} ${path} failed: ${res.status} ${text.slice(0, 300)}`);
|
|
56
|
+
err.status = res.status;
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
if (res.status === 204) return null;
|
|
60
|
+
return res.json();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
async fetchCreatemeta(projectKey, issueTypeName) {
|
|
65
|
+
const params = new URLSearchParams({
|
|
66
|
+
projectKeys: projectKey,
|
|
67
|
+
issuetypeNames: issueTypeName,
|
|
68
|
+
expand: 'projects.issuetypes.fields',
|
|
69
|
+
});
|
|
70
|
+
return request(`/rest/api/3/issue/createmeta?${params.toString()}`);
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
async createIssue({ project, issueType, summary, description, labels, assignee }) {
|
|
74
|
+
const body = {
|
|
75
|
+
fields: {
|
|
76
|
+
project: { key: project },
|
|
77
|
+
issuetype: { name: issueType ?? 'Task' },
|
|
78
|
+
summary,
|
|
79
|
+
...(description !== undefined ? { description } : {}),
|
|
80
|
+
...(assignee ? { assignee: { accountId: assignee } } : {}),
|
|
81
|
+
...(labels ? { labels } : {}),
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
const data = await request('/rest/api/3/issue', { method: 'POST', body });
|
|
85
|
+
return {
|
|
86
|
+
id: data.id,
|
|
87
|
+
key: data.key,
|
|
88
|
+
url: `${baseUrl}/browse/${data.key}`,
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
async createComment(issueKey, adfBody) {
|
|
93
|
+
const data = await request(`/rest/api/3/issue/${issueKey}/comment`, {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
body: { body: adfBody },
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
id: data.id,
|
|
99
|
+
url: `${baseUrl}/browse/${issueKey}?focusedCommentId=${data.id}`,
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
async searchIssues(jql) {
|
|
104
|
+
const query = typeof jql === 'string' ? jql : (jql?.jql ?? '');
|
|
105
|
+
const data = await request('/rest/api/3/search', {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
body: { jql: query, maxResults: 50, fields: ['summary', 'status'] },
|
|
108
|
+
});
|
|
109
|
+
return (data.issues ?? []).map((issue) => ({
|
|
110
|
+
id: issue.id,
|
|
111
|
+
key: issue.key,
|
|
112
|
+
summary: issue.fields?.summary,
|
|
113
|
+
url: `${baseUrl}/browse/${issue.key}`,
|
|
114
|
+
}));
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export default createJiraTransport;
|
|
@@ -42,8 +42,23 @@
|
|
|
42
42
|
*
|
|
43
43
|
* Glob matching for `*Glob` fields follows minimatch-style single-segment
|
|
44
44
|
* patterns where `*` matches any sequence of non-slash characters.
|
|
45
|
+
*
|
|
46
|
+
* ADR-0060 filter block (LMCP-B11): `validateFilterConfig(providerName,
|
|
47
|
+
* filter)` validates a `{ scope, predicates, nativeQuery }` block against
|
|
48
|
+
* `SCOPE_FIELDS_BY_PROVIDER` and the normalized predicate keys in
|
|
49
|
+
* `lib/providers/filter-schema.mjs`, throwing on any key the provider kind
|
|
50
|
+
* does not declare — fail closed at poll time, never a silent no-op.
|
|
51
|
+
* `matchesFilter(item, filter, providerName)` re-evaluates `scope` and
|
|
52
|
+
* `predicates` plane-side against a fetched item; this is the mandatory
|
|
53
|
+
* post-fetch backstop even when a provider pushed the same filter down
|
|
54
|
+
* server-side (Jira JQL, etc.). `filterHash(filter)` derives the stable
|
|
55
|
+
* digest recorded in the per-poll audit line.
|
|
45
56
|
*/
|
|
46
57
|
|
|
58
|
+
import crypto from 'node:crypto';
|
|
59
|
+
|
|
60
|
+
import { PREDICATE_KEYS, SCOPE_KEYS, STATUS_CATEGORIES, TOP_LEVEL_KEYS } from './filter-schema.mjs';
|
|
61
|
+
|
|
47
62
|
export const ALLOWLIST_SCHEMA = Object.freeze({
|
|
48
63
|
github: { fields: ['org', 'repoAllowlist', 'repoAllowGlob'] },
|
|
49
64
|
'atlassian-jira': { fields: ['instance', 'projectAllowlist'] },
|
|
@@ -98,6 +113,198 @@ export function validateAllowlist(providerName, target, config = {}) {
|
|
|
98
113
|
};
|
|
99
114
|
}
|
|
100
115
|
|
|
116
|
+
// ─── ADR-0060 provider filter block (scope + predicates + nativeQuery) ──────
|
|
117
|
+
|
|
118
|
+
// Which `scope` container keys and passthrough capability each provider kind
|
|
119
|
+
// declares. Mirrors the dead *Allowlist fields ALLOWLIST_SCHEMA carried
|
|
120
|
+
// per-provider, now under the one normalized `scope` container.
|
|
121
|
+
|
|
122
|
+
export const SCOPE_FIELDS_BY_PROVIDER = Object.freeze({
|
|
123
|
+
github: { scope: ['repos', 'repoGlob'], nativeQuery: true },
|
|
124
|
+
'atlassian-jira': { scope: ['projects'], nativeQuery: true },
|
|
125
|
+
jira: { scope: ['projects'], nativeQuery: true },
|
|
126
|
+
'atlassian-confluence': { scope: ['spaces'], nativeQuery: false },
|
|
127
|
+
slack: { scope: ['channels'], nativeQuery: false },
|
|
128
|
+
salesforce: { scope: ['projects'], nativeQuery: true },
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
function isPlainObject(value) {
|
|
132
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Validate an ADR-0060 filter block against the scope fields and predicate
|
|
137
|
+
* keys the given provider kind declares. Any unknown top-level key, unknown
|
|
138
|
+
* scope key, unknown predicate key, or `nativeQuery` on a provider that
|
|
139
|
+
* doesn't support passthrough throws — fail closed, never a silent no-op.
|
|
140
|
+
* Returns the block unchanged (for chaining) when it is valid.
|
|
141
|
+
*/
|
|
142
|
+
export function validateFilterConfig(providerName, filter) {
|
|
143
|
+
if (filter == null) return filter;
|
|
144
|
+
if (!isPlainObject(filter)) {
|
|
145
|
+
throw new Error(`provider filter (${providerName}): filter must be an object`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
for (const key of Object.keys(filter)) {
|
|
149
|
+
if (!TOP_LEVEL_KEYS.includes(key)) {
|
|
150
|
+
throw new Error(`provider filter (${providerName}): unknown filter key "${key}". Allowed: ${TOP_LEVEL_KEYS.join(', ')}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const allowed = SCOPE_FIELDS_BY_PROVIDER[providerName] ?? { scope: SCOPE_KEYS, nativeQuery: true };
|
|
155
|
+
|
|
156
|
+
if (filter.scope !== undefined) {
|
|
157
|
+
if (!isPlainObject(filter.scope)) {
|
|
158
|
+
throw new Error(`provider filter (${providerName}): scope must be an object`);
|
|
159
|
+
}
|
|
160
|
+
for (const key of Object.keys(filter.scope)) {
|
|
161
|
+
if (!SCOPE_KEYS.includes(key)) {
|
|
162
|
+
throw new Error(`provider filter (${providerName}): unknown scope key "${key}". Allowed: ${SCOPE_KEYS.join(', ')}`);
|
|
163
|
+
}
|
|
164
|
+
if (!allowed.scope.includes(key)) {
|
|
165
|
+
throw new Error(`provider filter (${providerName}): scope key "${key}" is not supported by this provider kind. Allowed: ${allowed.scope.join(', ') || '(none)'}`);
|
|
166
|
+
}
|
|
167
|
+
if (!Array.isArray(filter.scope[key])) {
|
|
168
|
+
throw new Error(`provider filter (${providerName}): scope.${key} must be an array`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (filter.predicates !== undefined) {
|
|
174
|
+
if (!isPlainObject(filter.predicates)) {
|
|
175
|
+
throw new Error(`provider filter (${providerName}): predicates must be an object`);
|
|
176
|
+
}
|
|
177
|
+
for (const key of Object.keys(filter.predicates)) {
|
|
178
|
+
if (!PREDICATE_KEYS.includes(key)) {
|
|
179
|
+
throw new Error(`provider filter (${providerName}): unknown predicate key "${key}". Allowed: ${PREDICATE_KEYS.join(', ')}`);
|
|
180
|
+
}
|
|
181
|
+
if (key === 'statusCategory') {
|
|
182
|
+
const values = filter.predicates[key];
|
|
183
|
+
if (!Array.isArray(values) || values.some((v) => !STATUS_CATEGORIES.includes(v))) {
|
|
184
|
+
throw new Error(`provider filter (${providerName}): predicates.statusCategory must be an array drawn from ${STATUS_CATEGORIES.join(', ')}`);
|
|
185
|
+
}
|
|
186
|
+
} else if (key === 'updatedSince') {
|
|
187
|
+
if (typeof filter.predicates[key] !== 'string' || !filter.predicates[key]) {
|
|
188
|
+
throw new Error(`provider filter (${providerName}): predicates.updatedSince must be a non-empty string`);
|
|
189
|
+
}
|
|
190
|
+
} else if (!Array.isArray(filter.predicates[key])) {
|
|
191
|
+
throw new Error(`provider filter (${providerName}): predicates.${key} must be an array`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (filter.nativeQuery !== undefined) {
|
|
197
|
+
if (typeof filter.nativeQuery !== 'string' || !filter.nativeQuery) {
|
|
198
|
+
throw new Error(`provider filter (${providerName}): nativeQuery must be a non-empty string`);
|
|
199
|
+
}
|
|
200
|
+
if (!allowed.nativeQuery) {
|
|
201
|
+
throw new Error(`provider filter (${providerName}): nativeQuery passthrough is not supported by this provider kind`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return filter;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Stable content hash of an effective filter block, recorded in the per-poll
|
|
210
|
+
* audit line so operators can tell whether the enforced filter changed
|
|
211
|
+
* between polls without diffing the full block.
|
|
212
|
+
*/
|
|
213
|
+
export function filterHash(filter) {
|
|
214
|
+
const normalized = filter == null ? {} : filter;
|
|
215
|
+
return crypto.createHash('sha256').update(JSON.stringify(normalized, Object.keys(normalized).sort())).digest('hex').slice(0, 16);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function parseUpdatedSince(value) {
|
|
219
|
+
if (!value) return null;
|
|
220
|
+
const isoDuration = /^P(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?)?$/.exec(value);
|
|
221
|
+
if (isoDuration) {
|
|
222
|
+
const days = Number(isoDuration[1] ?? 0);
|
|
223
|
+
const hours = Number(isoDuration[2] ?? 0);
|
|
224
|
+
const minutes = Number(isoDuration[3] ?? 0);
|
|
225
|
+
const ms = ((days * 24 + hours) * 60 + minutes) * 60_000;
|
|
226
|
+
return Date.now() - ms;
|
|
227
|
+
}
|
|
228
|
+
const absolute = Date.parse(value);
|
|
229
|
+
return Number.isNaN(absolute) ? null : absolute;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function scopeContainerValue(item, key) {
|
|
233
|
+
if (key === 'projects') return item.project ?? null;
|
|
234
|
+
if (key === 'repos' || key === 'repoGlob') return item.repo ?? null;
|
|
235
|
+
if (key === 'channels') return item.channelId ?? item.channel ?? null;
|
|
236
|
+
if (key === 'spaces') return item.space ?? item.spaceKey ?? null;
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function matchesScope(item, scope) {
|
|
241
|
+
if (!scope) return true;
|
|
242
|
+
for (const key of SCOPE_KEYS) {
|
|
243
|
+
const constraint = scope[key];
|
|
244
|
+
if (!Array.isArray(constraint) || constraint.length === 0) continue;
|
|
245
|
+
const value = scopeContainerValue(item, key);
|
|
246
|
+
if (value == null) return false;
|
|
247
|
+
if (key === 'repoGlob') {
|
|
248
|
+
if (!constraint.some((pattern) => globMatch(pattern, value))) return false;
|
|
249
|
+
} else if (!constraint.includes(value)) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function matchesPredicates(item, predicates) {
|
|
257
|
+
if (!predicates) return true;
|
|
258
|
+
|
|
259
|
+
if (Array.isArray(predicates.assignee) && predicates.assignee.length > 0) {
|
|
260
|
+
if (!predicates.assignee.includes(item.assignee)) return false;
|
|
261
|
+
}
|
|
262
|
+
if (Array.isArray(predicates.statusCategory) && predicates.statusCategory.length > 0) {
|
|
263
|
+
const normalized = normalizeStatusCategory(item.statusCategory ?? item.status);
|
|
264
|
+
if (!predicates.statusCategory.includes(normalized)) return false;
|
|
265
|
+
}
|
|
266
|
+
if (Array.isArray(predicates.priority) && predicates.priority.length > 0) {
|
|
267
|
+
if (!predicates.priority.includes(item.priority)) return false;
|
|
268
|
+
}
|
|
269
|
+
if (Array.isArray(predicates.label) && predicates.label.length > 0) {
|
|
270
|
+
const itemLabels = Array.isArray(item.labels) ? item.labels : [];
|
|
271
|
+
if (!predicates.label.some((l) => itemLabels.includes(l))) return false;
|
|
272
|
+
}
|
|
273
|
+
if (typeof predicates.updatedSince === 'string' && predicates.updatedSince) {
|
|
274
|
+
const threshold = parseUpdatedSince(predicates.updatedSince);
|
|
275
|
+
const updatedAt = item.updatedAt ? Date.parse(item.updatedAt) : NaN;
|
|
276
|
+
if (threshold != null && (Number.isNaN(updatedAt) || updatedAt < threshold)) return false;
|
|
277
|
+
}
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Normalize a provider-native status string to the ADR-0060 statusCategory
|
|
283
|
+
* enum (to-do | in-progress | done) so the same predicate expresses
|
|
284
|
+
* identically across Jira, GitHub, and Salesforce.
|
|
285
|
+
*/
|
|
286
|
+
export function normalizeStatusCategory(status) {
|
|
287
|
+
if (!status) return null;
|
|
288
|
+
const s = String(status).toLowerCase();
|
|
289
|
+
if (/done|closed|resolved|complete/.test(s)) return 'done';
|
|
290
|
+
if (/progress|review|doing|active/.test(s)) return 'in-progress';
|
|
291
|
+
if (/to.?do|open|backlog|new|triage/.test(s)) return 'to-do';
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Plane-side re-evaluation of an ADR-0060 filter against a fetched item.
|
|
297
|
+
* Mandatory universal fallback: runs even when the provider already pushed
|
|
298
|
+
* the same filter down server-side, and is the sole enforcement point for
|
|
299
|
+
* providers with no server-side filtering. `nativeQuery` has no plane-side
|
|
300
|
+
* equivalent (opaque passthrough) — its effect is assumed already applied
|
|
301
|
+
* by the provider fetch; `scope` and `predicates` are conjoined here.
|
|
302
|
+
*/
|
|
303
|
+
export function matchesFilter(item, filter) {
|
|
304
|
+
if (filter == null) return true;
|
|
305
|
+
return matchesScope(item, filter.scope) && matchesPredicates(item, filter.predicates);
|
|
306
|
+
}
|
|
307
|
+
|
|
101
308
|
export const CAPABILITIES = Object.freeze([
|
|
102
309
|
'read',
|
|
103
310
|
'search',
|
|
@@ -11,6 +11,7 @@ import fs from 'node:fs';
|
|
|
11
11
|
import os from 'node:os';
|
|
12
12
|
import { spawnSync } from 'node:child_process';
|
|
13
13
|
import { hasAnySecret } from './secret-resolver.mjs';
|
|
14
|
+
import { locateOpBinary } from './op-locate.mjs';
|
|
14
15
|
import { API_KEY_CREDENTIALS, primaryEnvVar } from './credential-catalog.mjs';
|
|
15
16
|
import {
|
|
16
17
|
discoverAlternateRawForCredential,
|
|
@@ -36,7 +37,9 @@ export function commandExists(command) {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
function defaultOpRun(args) {
|
|
39
|
-
|
|
40
|
+
const opPath = locateOpBinary();
|
|
41
|
+
if (!opPath) return { status: 1, stdout: '', stderr: 'op not found' };
|
|
42
|
+
return spawnSync(opPath, args, { encoding: 'utf8', timeout: 12_000 });
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
export function listOpItems({ opRun = defaultOpRun } = {}) {
|
|
@@ -122,17 +125,17 @@ export function ensureConstructCredentials({
|
|
|
122
125
|
|
|
123
126
|
const missing = missingCredentials({ env, cwd, home });
|
|
124
127
|
if (missing.length === 0) {
|
|
125
|
-
lastBootstrapResult = { linked: [], opAvailable:
|
|
128
|
+
lastBootstrapResult = { linked: [], opAvailable: locateOpBinary() !== null };
|
|
126
129
|
return lastBootstrapResult;
|
|
127
130
|
}
|
|
128
131
|
|
|
129
132
|
if (!autoLink) {
|
|
130
|
-
lastBootstrapResult = { linked: [], opAvailable:
|
|
133
|
+
lastBootstrapResult = { linked: [], opAvailable: locateOpBinary() !== null, pending: missing.map((e) => e.id) };
|
|
131
134
|
return lastBootstrapResult;
|
|
132
135
|
}
|
|
133
136
|
|
|
134
137
|
const linked = [];
|
|
135
|
-
const opAvailable = opRun !== defaultOpRun ||
|
|
138
|
+
const opAvailable = opRun !== defaultOpRun || locateOpBinary() !== null;
|
|
136
139
|
if (!opAvailable) {
|
|
137
140
|
lastBootstrapResult = { linked, opAvailable: false };
|
|
138
141
|
return lastBootstrapResult;
|
|
@@ -15,8 +15,20 @@ export function openCodeConfigPath(homeDir = os.homedir()) {
|
|
|
15
15
|
return path.join(homeDir, '.config', 'opencode', 'opencode.json');
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
// Matches the literal reference forms Construct and OpenCode write into
|
|
19
|
+
// config when no real credential is resolved yet, so presence checks don't
|
|
20
|
+
// mistake an unresolved placeholder for a configured key.
|
|
21
|
+
const PLACEHOLDER_PATTERNS = [
|
|
22
|
+
/^__[A-Z0-9_]+__$/,
|
|
23
|
+
/^\{env:[A-Z0-9_]+\}$/,
|
|
24
|
+
/^\$\{[A-Z0-9_]+\}$/,
|
|
25
|
+
/^\$\{env:[A-Z0-9_]+\}$/,
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export function isPlaceholder(value) {
|
|
29
|
+
if (!value) return true;
|
|
30
|
+
const trimmed = String(value).trim();
|
|
31
|
+
return PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(trimmed));
|
|
20
32
|
}
|
|
21
33
|
|
|
22
34
|
export function readRawFromOpenCodeProvider(providerName, configPath = openCodeConfigPath()) {
|