@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
|
@@ -4,9 +4,24 @@
|
|
|
4
4
|
* Invokes a named Construct workflow non-interactively and returns a provenanced
|
|
5
5
|
* execution plan: the selected roles (auto/explicit/constrained), each role's
|
|
6
6
|
* rationale, the skills they apply, the resolved model, the evidence the chain
|
|
7
|
-
* requires,
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* requires, the bound persona reasoning framework (ADR-0062), and the output
|
|
8
|
+
* contract to be produced. Construct supplies the orchestration plan and
|
|
9
|
+
* provenance; the host agent runtime performs the specialist reasoning, so
|
|
10
|
+
* the contract never fabricates specialist output.
|
|
11
|
+
*
|
|
12
|
+
* Framework wiring (LMCP-F8, ADR-0062 §3): the primary role's framework is
|
|
13
|
+
* resolved through the same pack registry and tier precedence (E1/ADR-0055)
|
|
14
|
+
* that resolves persona prompts (lib/orchestration/worker.mjs), by matching a
|
|
15
|
+
* pack-declared framework's `appliesToRole` against the role. The resolved
|
|
16
|
+
* framework's ordered `steps` become prompt fragments in the plan, and its
|
|
17
|
+
* `emits` tokens (in step order) are the output contract's required fields —
|
|
18
|
+
* the host runtime that performs the reasoning must return one labeled output
|
|
19
|
+
* per token, in order. A role with no matching framework never falls back to
|
|
20
|
+
* silent generic reasoning: the plan carries `framework: { available: false,
|
|
21
|
+
* degraded: 'framework-missing', role }`, a visible flag a caller can branch
|
|
22
|
+
* on. Traces record `{frameworkId, version, source}` alongside the existing
|
|
23
|
+
* F1 provenance fields so a downstream audit can tell which reasoning
|
|
24
|
+
* procedure (if any) governed the run.
|
|
10
25
|
*
|
|
11
26
|
* Durable side effects are gated by approval mode (resolveWriteGate):
|
|
12
27
|
* - proposal-only → status 'proposed', zero writes
|
|
@@ -19,10 +34,15 @@
|
|
|
19
34
|
*/
|
|
20
35
|
|
|
21
36
|
import { randomUUID } from 'node:crypto';
|
|
37
|
+
import { dirname, join, resolve } from 'node:path';
|
|
38
|
+
import { fileURLToPath } from 'node:url';
|
|
22
39
|
|
|
23
40
|
import { getDeploymentMode } from '../deployment-mode.mjs';
|
|
24
41
|
import { addObservation } from '../observation-store.mjs';
|
|
25
42
|
import { recordApprovalRequest } from '../roles/approval-surface.mjs';
|
|
43
|
+
import { loadAllPacks } from '../packs/loader.mjs';
|
|
44
|
+
import { resolveFramework } from '../frameworks/loader.mjs';
|
|
45
|
+
import { parseFrameworkFile } from '../frameworks/schema.mjs';
|
|
26
46
|
import { getWorkflowDef, WORKFLOW_TYPES } from './workflow-defs.mjs';
|
|
27
47
|
import { roleMap, roleRationale, skillsForChain, contractFacts } from './role-facts.mjs';
|
|
28
48
|
import { resolveEmbeddedModel } from './model-resolve.mjs';
|
|
@@ -31,6 +51,89 @@ import { resolveWriteGate, newTraceId, DEFAULT_APPROVAL_MODE } from './audit.mjs
|
|
|
31
51
|
|
|
32
52
|
const VALID_STRATEGIES = ['auto', 'explicit', 'constrained'];
|
|
33
53
|
|
|
54
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
55
|
+
const PACKAGE_ROOT = resolve(HERE, '..', '..');
|
|
56
|
+
|
|
57
|
+
// Mirrors lib/orchestration/worker.mjs sortByTierPrecedence: a project pack's
|
|
58
|
+
// framework for a role must win over a builtin pack's framework for the same
|
|
59
|
+
// role, not just over another version of the same pack id (ADR-0055).
|
|
60
|
+
|
|
61
|
+
const PACK_TIER_RANK = { project: 0, user: 1, builtin: 2, unknown: 3 };
|
|
62
|
+
|
|
63
|
+
function sortByTierPrecedence(packs) {
|
|
64
|
+
return [...packs].sort((a, b) => (PACK_TIER_RANK[a._tier] ?? 3) - (PACK_TIER_RANK[b._tier] ?? 3));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Find the frameworkId a pack registry binds to a role, honoring tier
|
|
69
|
+
* precedence. A framework equips a role via its own frontmatter
|
|
70
|
+
* `appliesToRole` (ADR-0062 §2) — there is no separate specialist→framework
|
|
71
|
+
* map to consult, so packs are walked in precedence order and the first
|
|
72
|
+
* framework file whose parsed `appliesToRole` matches wins.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} roleId bare role id, e.g. 'product-manager'
|
|
75
|
+
* @param {object[]} packs tier-precedence ordered pack list
|
|
76
|
+
* @param {string} packageRoot
|
|
77
|
+
* @returns {string|null}
|
|
78
|
+
*/
|
|
79
|
+
function frameworkIdForRole(roleId, packs, packageRoot) {
|
|
80
|
+
for (const pack of packs) {
|
|
81
|
+
const frameworks = pack?.frameworks || {};
|
|
82
|
+
const root = pack?._packDir || packageRoot;
|
|
83
|
+
for (const [frameworkId, relPath] of Object.entries(frameworks)) {
|
|
84
|
+
if (typeof relPath !== 'string' || !relPath) continue;
|
|
85
|
+
const parsed = parseFrameworkFile(join(root, relPath));
|
|
86
|
+
if (parsed.valid && parsed.frontmatter?.appliesToRole === roleId) return frameworkId;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Resolve the persona reasoning framework bound to a role through the pack
|
|
94
|
+
* registry (E1 precedence), or a visible degradation flag when no pack
|
|
95
|
+
* declares one for this role — never a silent fallback to generic reasoning
|
|
96
|
+
* (ADR-0062 §3).
|
|
97
|
+
*
|
|
98
|
+
* @param {string} roleId
|
|
99
|
+
* @param {object} opts
|
|
100
|
+
* @param {object} opts.env
|
|
101
|
+
* @param {string} opts.cwd
|
|
102
|
+
* @returns {{available:true, frameworkId:string, version:*, source:string, steps:object[], requiredOutputFields:string[]} | {available:false, degraded:'framework-missing', role:string}}
|
|
103
|
+
*/
|
|
104
|
+
function resolveRoleFramework(roleId, { env, cwd }) {
|
|
105
|
+
const deploymentMode = getDeploymentMode(env, { cwd });
|
|
106
|
+
const { packs } = loadAllPacks({ deploymentMode, env, rootDir: cwd, packageRoot: PACKAGE_ROOT });
|
|
107
|
+
const ordered = sortByTierPrecedence(packs);
|
|
108
|
+
|
|
109
|
+
const frameworkId = frameworkIdForRole(roleId, ordered, PACKAGE_ROOT);
|
|
110
|
+
if (!frameworkId) {
|
|
111
|
+
return { available: false, degraded: 'framework-missing', role: roleId };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const resolved = resolveFramework(frameworkId, { packs: ordered, packageRoot: PACKAGE_ROOT });
|
|
115
|
+
if (!resolved.found) {
|
|
116
|
+
return { available: false, degraded: 'framework-missing', role: roleId };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const steps = (resolved.frontmatter.steps || []).map((step) => ({
|
|
120
|
+
id: step.id,
|
|
121
|
+
move: step.move,
|
|
122
|
+
question: step.question,
|
|
123
|
+
emits: step.emits,
|
|
124
|
+
cites: step.cites,
|
|
125
|
+
}));
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
available: true,
|
|
129
|
+
frameworkId: resolved.frontmatter.id,
|
|
130
|
+
version: resolved.frontmatter.version,
|
|
131
|
+
source: resolved.packId,
|
|
132
|
+
steps,
|
|
133
|
+
requiredOutputFields: steps.map((s) => s.emits),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
34
137
|
function selectRoles({ strategy, defChain, requestedRoles, map, warnings }) {
|
|
35
138
|
const filterKnown = (ids) => ids.filter((id) => {
|
|
36
139
|
if (map.has(id)) return true;
|
|
@@ -63,6 +166,7 @@ function errorResult({ workflowId, traceId, approvalMode, code, message, warning
|
|
|
63
166
|
roleRationale: [],
|
|
64
167
|
skillsApplied: [],
|
|
65
168
|
modelResolution: null,
|
|
169
|
+
framework: null,
|
|
66
170
|
outputs: null,
|
|
67
171
|
recommendations: [],
|
|
68
172
|
evidence: { requirements: [], satisfied: [], traceId },
|
|
@@ -71,6 +175,7 @@ function errorResult({ workflowId, traceId, approvalMode, code, message, warning
|
|
|
71
175
|
approvalMode: approvalMode || DEFAULT_APPROVAL_MODE,
|
|
72
176
|
durableWritesPerformed: [],
|
|
73
177
|
traceId,
|
|
178
|
+
trace: { role: null, frameworkId: null, frameworkVersion: null, frameworkSource: null, degraded: null },
|
|
74
179
|
errors: [{ code, message }],
|
|
75
180
|
warnings,
|
|
76
181
|
};
|
|
@@ -154,6 +259,15 @@ export async function invokeWorkflow(request = {}, { env = process.env, cwd = pr
|
|
|
154
259
|
const skillsApplied = skillsForChain(selectedRoles, map);
|
|
155
260
|
const rationale = roleRationale(selectedRoles, map);
|
|
156
261
|
|
|
262
|
+
// The bound framework equips the plan's primary role only — the role the
|
|
263
|
+
// output contract and evidence requirements are already scoped to
|
|
264
|
+
// (contractFacts(primaryOwner) above) — not every role in the chain.
|
|
265
|
+
|
|
266
|
+
const framework = resolveRoleFramework(primaryOwner, { env, cwd });
|
|
267
|
+
if (!framework.available) {
|
|
268
|
+
warnings.push(`framework: no reasoning framework bound for role '${primaryOwner}'; proceeding without a reasoning scaffold (degraded: framework-missing).`);
|
|
269
|
+
}
|
|
270
|
+
|
|
157
271
|
// Evidence is satisfied only when a requirement key is actually present in the
|
|
158
272
|
// caller-supplied context; missing requirements are reported, never assumed.
|
|
159
273
|
|
|
@@ -165,9 +279,14 @@ export async function invokeWorkflow(request = {}, { env = process.env, cwd = pr
|
|
|
165
279
|
const mode = approvalMode || def.defaultApprovalMode;
|
|
166
280
|
const gate = resolveWriteGate({ approvalMode: mode, deploymentMode });
|
|
167
281
|
|
|
282
|
+
// requiredOutputFields carries the framework's `emits` tokens IN STEP ORDER
|
|
283
|
+
// (ADR-0062 §3): the host runtime must return one labeled output per token,
|
|
284
|
+
// in that order, so the reasoning procedure is checkable, not just prose.
|
|
285
|
+
|
|
168
286
|
const outputs = {
|
|
169
287
|
schema: def.outputSchema,
|
|
170
288
|
expected: facts.expectedOutputs,
|
|
289
|
+
requiredOutputFields: framework.available ? framework.requiredOutputFields : [],
|
|
171
290
|
note: 'Construct returns the orchestration plan and output contract; specialist reasoning is performed by the host agent runtime.',
|
|
172
291
|
};
|
|
173
292
|
const recommendations = [`Run roles in order: ${selectedRoles.map((r) => `cx-${r}`).join(' → ')}.`];
|
|
@@ -176,7 +295,11 @@ export async function invokeWorkflow(request = {}, { env = process.env, cwd = pr
|
|
|
176
295
|
const riskFactors = [];
|
|
177
296
|
if (missingEvidence.length) riskFactors.push(`missing required evidence: ${missingEvidence.join(', ')}`);
|
|
178
297
|
if (modelResolution.error) riskFactors.push(`model could not be resolved: ${modelResolution.error.reason}`);
|
|
179
|
-
|
|
298
|
+
if (!framework.available) riskFactors.push(`no reasoning framework bound for role '${primaryOwner}' (degraded: framework-missing)`);
|
|
299
|
+
const risks = {
|
|
300
|
+
level: missingEvidence.length || modelResolution.error || !framework.available ? 'medium' : 'low',
|
|
301
|
+
factors: riskFactors,
|
|
302
|
+
};
|
|
180
303
|
|
|
181
304
|
const durableWritesPerformed = [];
|
|
182
305
|
const errors = [];
|
|
@@ -214,6 +337,21 @@ export async function invokeWorkflow(request = {}, { env = process.env, cwd = pr
|
|
|
214
337
|
status = 'proposed';
|
|
215
338
|
}
|
|
216
339
|
|
|
340
|
+
// Trace provenance (LMCP-F1 alignment): a downstream audit reads
|
|
341
|
+
// {frameworkId, frameworkVersion, frameworkSource} off the trace to know
|
|
342
|
+
// which reasoning procedure (if any) governed this run, the same way F1
|
|
343
|
+
// already records {specialistId, packId, promptVersion} for the persona
|
|
344
|
+
// that ran. traceId itself is already carried (and correlated) at the top
|
|
345
|
+
// level, so it is not duplicated here.
|
|
346
|
+
|
|
347
|
+
const traceProvenance = {
|
|
348
|
+
role: primaryOwner,
|
|
349
|
+
frameworkId: framework.available ? framework.frameworkId : null,
|
|
350
|
+
frameworkVersion: framework.available ? framework.version : null,
|
|
351
|
+
frameworkSource: framework.available ? framework.source : null,
|
|
352
|
+
degraded: framework.available ? null : framework.degraded,
|
|
353
|
+
};
|
|
354
|
+
|
|
217
355
|
return {
|
|
218
356
|
workflowId,
|
|
219
357
|
workflowType,
|
|
@@ -225,6 +363,7 @@ export async function invokeWorkflow(request = {}, { env = process.env, cwd = pr
|
|
|
225
363
|
skillsApplied,
|
|
226
364
|
modelResolution,
|
|
227
365
|
execution,
|
|
366
|
+
framework,
|
|
228
367
|
outputs,
|
|
229
368
|
recommendations,
|
|
230
369
|
evidence: { requirements: facts.evidenceRequirements, satisfied, missing: missingEvidence, traceId },
|
|
@@ -233,6 +372,7 @@ export async function invokeWorkflow(request = {}, { env = process.env, cwd = pr
|
|
|
233
372
|
approvalMode: gate.approvalMode,
|
|
234
373
|
durableWritesPerformed,
|
|
235
374
|
traceId,
|
|
375
|
+
trace: traceProvenance,
|
|
236
376
|
errors,
|
|
237
377
|
warnings,
|
|
238
378
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "architecture-review",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "linear",
|
|
5
|
+
"modes": ["solo", "team"],
|
|
6
|
+
"surfaces": ["mcp", "cli"],
|
|
7
|
+
"roleChain": ["architect", "security", "reviewer"],
|
|
8
|
+
"defaultApprovalMode": "requires-human-approval",
|
|
9
|
+
"outputSchema": { "artifact": "review-report" },
|
|
10
|
+
"durableStateModel": "none",
|
|
11
|
+
"tier": "reasoning",
|
|
12
|
+
"description": "Review an architecture or design for trade-offs, failure modes, and security exposure.",
|
|
13
|
+
"owner": "construct-core",
|
|
14
|
+
"compatVersion": 1
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "data-structure",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "linear",
|
|
5
|
+
"modes": ["solo", "team"],
|
|
6
|
+
"surfaces": ["mcp", "cli"],
|
|
7
|
+
"roleChain": ["data-analyst", "engineer"],
|
|
8
|
+
"defaultApprovalMode": "proposal-only",
|
|
9
|
+
"durableStateModel": "none",
|
|
10
|
+
"tier": "standard",
|
|
11
|
+
"description": "Parse, validate, and profile a raw dataset into a structured, described shape.",
|
|
12
|
+
"owner": "construct-core",
|
|
13
|
+
"compatVersion": 1
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "evidence-ingest",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "linear",
|
|
5
|
+
"modes": ["solo", "team"],
|
|
6
|
+
"surfaces": ["mcp", "cli"],
|
|
7
|
+
"roleChain": ["researcher", "data-analyst"],
|
|
8
|
+
"defaultApprovalMode": "proposal-only",
|
|
9
|
+
"durableStateModel": "none",
|
|
10
|
+
"tier": "fast",
|
|
11
|
+
"description": "Ingest and structure raw evidence (notes, documents, signals) into a normalized summary.",
|
|
12
|
+
"owner": "construct-core",
|
|
13
|
+
"compatVersion": 1
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "memo-draft",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "linear",
|
|
5
|
+
"modes": ["solo", "team"],
|
|
6
|
+
"surfaces": ["mcp", "cli"],
|
|
7
|
+
"roleChain": ["operations", "reviewer"],
|
|
8
|
+
"defaultApprovalMode": "proposal-only",
|
|
9
|
+
"durableStateModel": "none",
|
|
10
|
+
"tier": "fast",
|
|
11
|
+
"description": "Draft a decision or status memo from a problem statement and context.",
|
|
12
|
+
"owner": "construct-core",
|
|
13
|
+
"compatVersion": 1
|
|
14
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "operations-triage",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "embed",
|
|
5
|
+
"defaultApprovalMode": "proposal-only",
|
|
6
|
+
"compatVersion": 1,
|
|
7
|
+
"owner": "construct-core",
|
|
8
|
+
"description": "Triage preset: the operations specialist reviews new/changed Jira tickets, detects duplicates and under-specified issues, then proposes triage actions (duplicate-link, needs-info comment) held for approval. Never auto-executes writes.",
|
|
9
|
+
"embed": {
|
|
10
|
+
"specialist": "cx-operations",
|
|
11
|
+
"providerBindings": ["atlassian-jira", "atlassian-confluence", "directory"],
|
|
12
|
+
"framework": "cx-ops-dependency-sequencing",
|
|
13
|
+
"outputContract": "operations-triage",
|
|
14
|
+
"proposalAuthority": "propose-only",
|
|
15
|
+
"runtime": "auto",
|
|
16
|
+
"cadence": { "every": "PT2H" }
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "operations",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "embed",
|
|
5
|
+
"defaultApprovalMode": "proposal-only",
|
|
6
|
+
"compatVersion": 1,
|
|
7
|
+
"owner": "construct-core",
|
|
8
|
+
"description": "TPM preset: the operations specialist reads Jira, product docs (Confluence), and Slack, then produces a recurring briefing — work missing versus the PRDs, timeline-risk findings with evidence, and proposed Jira tickets held for approval. Never auto-creates issues.",
|
|
9
|
+
"embed": {
|
|
10
|
+
"specialist": "cx-operations",
|
|
11
|
+
"providerBindings": ["atlassian-jira", "atlassian-confluence", "slack"],
|
|
12
|
+
"framework": "cx-ops-dependency-sequencing",
|
|
13
|
+
"outputContract": "operations-tpm-briefing",
|
|
14
|
+
"proposalAuthority": "propose-only",
|
|
15
|
+
"runtime": "auto",
|
|
16
|
+
"cadence": { "every": "PT4H" }
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "pm-feedback",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "embed",
|
|
5
|
+
"defaultApprovalMode": "proposal-only",
|
|
6
|
+
"compatVersion": 1,
|
|
7
|
+
"owner": "construct-core",
|
|
8
|
+
"description": "PM feedback preset: the product-manager specialist reads customer feedback and PRDs (Confluence), clusters feedback by theme, scores each theme with the user-value rubric, cross-references PRD requirements, and emits a requirements-candidates artifact. Artifact-only — proposes no provider write.",
|
|
9
|
+
"embed": {
|
|
10
|
+
"specialist": "cx-product-manager",
|
|
11
|
+
"providerBindings": ["feedback", "atlassian-confluence"],
|
|
12
|
+
"framework": "cx-pm-value-tradeoff",
|
|
13
|
+
"outputContract": "pm-requirements-candidates",
|
|
14
|
+
"proposalAuthority": "propose-only",
|
|
15
|
+
"runtime": "auto",
|
|
16
|
+
"cadence": { "every": "PT24H" }
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "prd-draft",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "linear",
|
|
5
|
+
"modes": ["solo", "team"],
|
|
6
|
+
"surfaces": ["mcp", "cli"],
|
|
7
|
+
"roleChain": ["product-manager", "architect"],
|
|
8
|
+
"defaultApprovalMode": "proposal-only",
|
|
9
|
+
"outputSchema": { "artifact": "decision" },
|
|
10
|
+
"durableStateModel": "none",
|
|
11
|
+
"tier": "standard",
|
|
12
|
+
"description": "Draft a product requirements document from a problem statement and supporting evidence.",
|
|
13
|
+
"owner": "construct-core",
|
|
14
|
+
"compatVersion": 1
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "proposal-review",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "linear",
|
|
5
|
+
"modes": ["solo", "team"],
|
|
6
|
+
"surfaces": ["mcp", "cli"],
|
|
7
|
+
"roleChain": ["reviewer"],
|
|
8
|
+
"defaultApprovalMode": "requires-human-approval",
|
|
9
|
+
"outputSchema": { "artifact": "review-report" },
|
|
10
|
+
"durableStateModel": "none",
|
|
11
|
+
"tier": "standard",
|
|
12
|
+
"description": "Review a proposal for correctness, risk, and hidden assumptions before acceptance.",
|
|
13
|
+
"owner": "construct-core",
|
|
14
|
+
"compatVersion": 1
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "research-synthesis",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "linear",
|
|
5
|
+
"modes": ["solo", "team"],
|
|
6
|
+
"surfaces": ["mcp", "cli"],
|
|
7
|
+
"roleChain": ["researcher", "data-analyst", "reviewer"],
|
|
8
|
+
"defaultApprovalMode": "proposal-only",
|
|
9
|
+
"durableStateModel": "none",
|
|
10
|
+
"tier": "reasoning",
|
|
11
|
+
"description": "Synthesize multiple sources into a cited, evidence-graded research summary.",
|
|
12
|
+
"owner": "construct-core",
|
|
13
|
+
"compatVersion": 1
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "risk-review",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "linear",
|
|
5
|
+
"modes": ["solo", "team"],
|
|
6
|
+
"surfaces": ["mcp", "cli"],
|
|
7
|
+
"roleChain": ["reviewer", "security"],
|
|
8
|
+
"defaultApprovalMode": "requires-human-approval",
|
|
9
|
+
"outputSchema": { "artifact": "review-report" },
|
|
10
|
+
"durableStateModel": "none",
|
|
11
|
+
"tier": "reasoning",
|
|
12
|
+
"description": "Stress-test a plan for risk: failure modes, security, and compliance exposure.",
|
|
13
|
+
"owner": "construct-core",
|
|
14
|
+
"compatVersion": 1
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "structure-notes",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "linear",
|
|
5
|
+
"modes": ["solo", "team"],
|
|
6
|
+
"surfaces": ["mcp", "cli"],
|
|
7
|
+
"roleChain": ["orchestrator", "researcher"],
|
|
8
|
+
"defaultApprovalMode": "proposal-only",
|
|
9
|
+
"durableStateModel": "none",
|
|
10
|
+
"tier": "fast",
|
|
11
|
+
"description": "Structure an unclassified brain-dump or rough notes into a normalized summary with extracted intents.",
|
|
12
|
+
"owner": "construct-core",
|
|
13
|
+
"compatVersion": 1
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "transcript-process",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "linear",
|
|
5
|
+
"modes": ["solo", "team"],
|
|
6
|
+
"surfaces": ["mcp", "cli"],
|
|
7
|
+
"roleChain": ["researcher", "data-analyst"],
|
|
8
|
+
"defaultApprovalMode": "proposal-only",
|
|
9
|
+
"durableStateModel": "none",
|
|
10
|
+
"tier": "fast",
|
|
11
|
+
"description": "Process a meeting/call transcript into a summary, decisions, and action items.",
|
|
12
|
+
"owner": "construct-core",
|
|
13
|
+
"compatVersion": 1
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "triage",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "linear",
|
|
5
|
+
"modes": ["solo"],
|
|
6
|
+
"surfaces": ["mcp", "cli"],
|
|
7
|
+
"roleChain": ["orchestrator", "researcher"],
|
|
8
|
+
"defaultApprovalMode": "proposal-only",
|
|
9
|
+
"durableStateModel": "none",
|
|
10
|
+
"tier": "fast",
|
|
11
|
+
"description": "Classify and route unstructured input to the appropriate workflow.",
|
|
12
|
+
"owner": "construct-core",
|
|
13
|
+
"compatVersion": 1
|
|
14
|
+
}
|
package/lib/env-config.mjs
CHANGED
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
* order — project .env over config.env — so a key resolves the same way on both
|
|
9
9
|
* paths. Backs setup, model-router, and the MCP server when they resolve API keys
|
|
10
10
|
* and model overrides without leaking secrets.
|
|
11
|
+
*
|
|
12
|
+
* The `rootDir` param is the project .env tier's location and must be the
|
|
13
|
+
* user's project root, not the toolkit install checkout — bin/construct and
|
|
14
|
+
* lib/mcp/server.mjs both resolve it via lib/roots.mjs's resolveProjectRoot()
|
|
15
|
+
* (walk up from cwd to a `.cx/` or `package.json` marker) before calling
|
|
16
|
+
* loadConstructEnv/prepareConstructEnv, so a `.env` in the user's repo loads
|
|
17
|
+
* on both surfaces.
|
|
11
18
|
*/
|
|
12
19
|
import fs from 'node:fs';
|
|
13
20
|
import os from 'node:os';
|
|
@@ -16,10 +23,15 @@ import { configDir } from './config/xdg.mjs';
|
|
|
16
23
|
import { API_KEY_CREDENTIALS } from './providers/credential-catalog.mjs';
|
|
17
24
|
import { extractOpRef } from './providers/secret-resolver.mjs';
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
// The single dotenv-line parser: strips a leading and/or trailing quote
|
|
27
|
+
// independently (an unpaired quote — `KEY="abc` — still loses its one quote
|
|
28
|
+
// character rather than being left untouched). lib/providers/secret-resolver.mjs's
|
|
29
|
+
// file tier calls this same function so a value never differs between the two
|
|
30
|
+
// dotenv readers.
|
|
31
|
+
|
|
32
|
+
export function parseEnvContent(content) {
|
|
21
33
|
const env = {};
|
|
22
|
-
for (const rawLine of
|
|
34
|
+
for (const rawLine of String(content ?? '').split(/\r?\n/)) {
|
|
23
35
|
const line = rawLine.trim();
|
|
24
36
|
if (!line || line.startsWith('#')) continue;
|
|
25
37
|
const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
@@ -30,6 +42,11 @@ export function parseEnvFile(filePath) {
|
|
|
30
42
|
return env;
|
|
31
43
|
}
|
|
32
44
|
|
|
45
|
+
export function parseEnvFile(filePath) {
|
|
46
|
+
if (!filePath || !fs.existsSync(filePath)) return {};
|
|
47
|
+
return parseEnvContent(fs.readFileSync(filePath, 'utf8'));
|
|
48
|
+
}
|
|
49
|
+
|
|
33
50
|
export function getUserConfigDir(homeDir = os.homedir()) {
|
|
34
51
|
return configDir(homeDir);
|
|
35
52
|
}
|
|
@@ -52,7 +69,15 @@ export function writeEnvValues(filePath, values = {}) {
|
|
|
52
69
|
.filter(([, value]) => value !== undefined && value !== null && value !== '')
|
|
53
70
|
.map(([key, value]) => `${key}=${value}`)
|
|
54
71
|
.join('\n');
|
|
55
|
-
fs.writeFileSync(filePath, `${content}\n`, 'utf8');
|
|
72
|
+
fs.writeFileSync(filePath, `${content}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
73
|
+
|
|
74
|
+
// config.env can hold plaintext credentials. writeFileSync keeps an existing inode's
|
|
75
|
+
// mode, so the mode option does not tighten a file an earlier write (or a caller that
|
|
76
|
+
// skipped its own chmod) left 0644; re-apply 0600 on every write so the credential
|
|
77
|
+
// file is never group/world readable.
|
|
78
|
+
if (process.platform !== 'win32') {
|
|
79
|
+
try { fs.chmodSync(filePath, 0o600); } catch { /* exotic filesystem */ }
|
|
80
|
+
}
|
|
56
81
|
}
|
|
57
82
|
|
|
58
83
|
function cleanEnvValue(value) {
|
|
@@ -103,7 +128,7 @@ function isOpReference(value) {
|
|
|
103
128
|
return Boolean(extractOpRef(value));
|
|
104
129
|
}
|
|
105
130
|
|
|
106
|
-
function preferInjectedCredential(envValue, fileValue) {
|
|
131
|
+
export function preferInjectedCredential(envValue, fileValue) {
|
|
107
132
|
return Boolean(
|
|
108
133
|
envValue
|
|
109
134
|
&& !isOpReference(envValue)
|
|
@@ -111,7 +136,35 @@ function preferInjectedCredential(envValue, fileValue) {
|
|
|
111
136
|
);
|
|
112
137
|
}
|
|
113
138
|
|
|
114
|
-
|
|
139
|
+
// The canonical resolver for a non-negative numeric env setting (milliseconds,
|
|
140
|
+
// byte caps, counts, loop bounds). An explicit 0 is a valid, meaningful value
|
|
141
|
+
// (disable / no-cap), so only null/undefined/blank or a non-finite/negative parse
|
|
142
|
+
// falls back — `Number(env) || fallback` would silently rewrite an explicit 0.
|
|
143
|
+
// Mirrors the "0 disables" convention documented at lib/mcp/server.mjs.
|
|
144
|
+
|
|
145
|
+
export function resolveNonNegativeSetting(env, key, fallback) {
|
|
146
|
+
const val = env?.[key];
|
|
147
|
+
if (val === undefined || val === null) return fallback;
|
|
148
|
+
if (typeof val === 'string' && val.trim() === '') return fallback;
|
|
149
|
+
const raw = Number(val);
|
|
150
|
+
return Number.isFinite(raw) && raw >= 0 ? raw : fallback;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// The env-vs-file shadow winner differs by surface: the MCP server and other
|
|
154
|
+
// callers use loadConstructEnv's file-wins return, but bin/construct copies only
|
|
155
|
+
// the keys missing from process.env, so on a conflict the shell value wins there.
|
|
156
|
+
// The caller declares which value actually wins so the warning names the truth
|
|
157
|
+
// rather than asserting file-wins on every surface.
|
|
158
|
+
|
|
159
|
+
export function shadowWarningMessage(key, shadowWinner = 'file') {
|
|
160
|
+
const winnerClause = shadowWinner === 'shell'
|
|
161
|
+
? 'The shell value will be used'
|
|
162
|
+
: 'The config file will be used';
|
|
163
|
+
return `[construct] WARNING: process.env.${key} differs from the config file value. `
|
|
164
|
+
+ `${winnerClause}. To silence: unset the shell variable or update the config file to match.`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function loadConstructEnv({ rootDir, homeDir = os.homedir(), env = process.env, warn = true, shadowWinner = 'file' } = {}) {
|
|
115
168
|
const rootEnv = rootDir ? parseEnvFile(path.join(rootDir, '.env')) : {};
|
|
116
169
|
const userEnv = parseEnvFile(getUserEnvPath(homeDir));
|
|
117
170
|
|
|
@@ -145,11 +198,7 @@ export function loadConstructEnv({ rootDir, homeDir = os.homedir(), env = proces
|
|
|
145
198
|
if (!env[key]) continue;
|
|
146
199
|
if (key in fileEnv && fileEnv[key] !== env[key]) {
|
|
147
200
|
if (preferInjectedCredential(env[key], fileEnv[key])) continue;
|
|
148
|
-
process.stderr.write(
|
|
149
|
-
`[construct] WARNING: process.env.${key} differs from the config file value. ` +
|
|
150
|
-
`The config file will be used. To silence: unset the shell variable or ` +
|
|
151
|
-
`update the config file to match.\n`,
|
|
152
|
-
);
|
|
201
|
+
process.stderr.write(`${shadowWarningMessage(key, shadowWinner)}\n`);
|
|
153
202
|
}
|
|
154
203
|
}
|
|
155
204
|
}
|
package/lib/export-validate.mjs
CHANGED
|
@@ -86,21 +86,53 @@ export function contentRoundtrip({ exportPath, format, sourceMarkdown, env = pro
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
// Local image and link targets must resolve on disk; remote URLs, anchors, and mailto are out of
|
|
89
|
-
// scope for an on-disk integrity check.
|
|
89
|
+
// scope for an on-disk integrity check. Three reference forms in source are covered: ![]() syntax,
|
|
90
|
+
// raw inline <img src> HTML, and reference-style definitions ([id]: ./path). The produced HTML
|
|
91
|
+
// file's own <img src> references are also scanned, catching assets referenced only in the export.
|
|
90
92
|
|
|
91
|
-
export function referenceIntegrity(sourceMarkdown, baseDir) {
|
|
93
|
+
export function referenceIntegrity(sourceMarkdown, baseDir, exportPath = null) {
|
|
92
94
|
const body = sourceMarkdown.replace(/```[\s\S]*?```/g, '');
|
|
93
95
|
const missingImages = [];
|
|
94
96
|
const brokenLinks = [];
|
|
97
|
+
|
|
98
|
+
// Standard Markdown image syntax: 
|
|
95
99
|
for (const match of body.matchAll(/!\[[^\]]*\]\(([^)]+)\)/g)) {
|
|
96
100
|
const ref = match[1].split(/\s+/)[0];
|
|
97
101
|
if (/^https?:|^data:/.test(ref)) continue;
|
|
98
102
|
if (!fs.existsSync(path.resolve(baseDir, ref))) missingImages.push(ref);
|
|
99
103
|
}
|
|
104
|
+
|
|
105
|
+
// Raw inline HTML <img src> in source markdown
|
|
106
|
+
for (const match of body.matchAll(/<img[^>]+src\s*=\s*["']([^"']+)["']/gi)) {
|
|
107
|
+
const ref = match[1];
|
|
108
|
+
if (/^https?:|^data:|^#/.test(ref)) continue;
|
|
109
|
+
if (!fs.existsSync(path.resolve(baseDir, ref))) missingImages.push(ref);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Reference-style image/link definitions: [id]: ./path
|
|
113
|
+
for (const match of body.matchAll(/^\[[^\]]+\]:\s+(\S+)/gm)) {
|
|
114
|
+
const ref = match[1];
|
|
115
|
+
if (/^https?:|^#|^mailto:/.test(ref)) continue;
|
|
116
|
+
if (!fs.existsSync(path.resolve(baseDir, ref))) missingImages.push(ref);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Standard Markdown link syntax: [text](path) (non-image)
|
|
100
120
|
for (const match of body.matchAll(/(?<!!)\[[^\]]*\]\(([^)]+)\)/g)) {
|
|
101
121
|
const ref = match[1].split(/\s+/)[0];
|
|
102
122
|
if (/^https?:|^#|^mailto:/.test(ref)) continue;
|
|
103
123
|
if (!fs.existsSync(path.resolve(baseDir, ref))) brokenLinks.push(ref);
|
|
104
124
|
}
|
|
125
|
+
|
|
126
|
+
// Produced HTML file: scan internal <img src> references that may not appear in source markdown
|
|
127
|
+
if (exportPath && exportPath.endsWith('.html') && fs.existsSync(exportPath)) {
|
|
128
|
+
let html = '';
|
|
129
|
+
try { html = fs.readFileSync(exportPath, 'utf8'); } catch { /* skip */ }
|
|
130
|
+
for (const match of html.matchAll(/<img[^>]+src\s*=\s*["']([^"']+)["']/gi)) {
|
|
131
|
+
const ref = match[1];
|
|
132
|
+
if (/^https?:|^data:|^#/.test(ref)) continue;
|
|
133
|
+
if (!fs.existsSync(path.resolve(baseDir, ref))) missingImages.push(ref);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
105
137
|
return { ok: missingImages.length === 0 && brokenLinks.length === 0, missingImages, brokenLinks };
|
|
106
138
|
}
|