@geraldmaron/construct 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +36 -0
- package/README.md +41 -7
- package/bin/construct +1027 -64
- package/examples/distribution/sources/deck-one-pager.md +1 -1
- package/lib/acp/server.mjs +21 -7
- package/lib/adapters-sync.mjs +2 -1
- package/lib/audit-trail.mjs +185 -2
- package/lib/beads/auto-close.mjs +2 -1
- package/lib/beads/drift.mjs +2 -1
- package/lib/bridges/copilot-proxy.mjs +20 -5
- package/lib/certification/skill-inventory.mjs +10 -1
- package/lib/cli/approvals.mjs +147 -0
- package/lib/cli-commands.mjs +105 -14
- package/lib/comment-lint.mjs +127 -8
- package/lib/config/schema.mjs +6 -29
- package/lib/config/source-target-registry.mjs +70 -0
- package/lib/config/source-targets.mjs +179 -205
- package/lib/contracts/coverage.mjs +77 -0
- package/lib/contracts/validate.mjs +102 -13
- package/lib/contracts/violation-log.mjs +50 -4
- package/lib/db/migrate.mjs +69 -0
- package/lib/db/migrations/001_orchestration_runs.sql +9 -0
- package/lib/db/migrations/002_queue_provider.sql +50 -0
- package/lib/db/migrations/003_worker_registry.sql +17 -0
- package/lib/db/migrations/004_trace_events.sql +16 -0
- package/lib/db/migrations/005_shared_memory.sql +15 -0
- package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
- package/lib/decisions/enforced-baseline.json +0 -2
- package/lib/decisions/registry.mjs +3 -2
- package/lib/deployment/parity-contract.mjs +1 -1
- package/lib/deployment-mode.mjs +78 -2
- package/lib/diagram-export.mjs +10 -1
- package/lib/distill.mjs +5 -2
- package/lib/docs-verify.mjs +2 -1
- package/lib/doctor/cli.mjs +1 -0
- package/lib/doctor/command-on-path.mjs +24 -0
- package/lib/doctor/diagnosis.mjs +89 -0
- package/lib/doctor/embedding-health.mjs +50 -0
- package/lib/doctor/engine-health.mjs +93 -0
- package/lib/doctor/graph-validate.mjs +47 -0
- package/lib/doctor/index.mjs +18 -4
- package/lib/doctor/sidecar-providers.mjs +56 -0
- package/lib/doctor/watchers/consistency.mjs +93 -1
- package/lib/doctor/watchers/cx-budget.mjs +1 -1
- package/lib/doctor/watchers/graph-staleness.mjs +1 -1
- package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
- package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
- package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
- package/lib/doctor/watchers/provider-breaker.mjs +78 -0
- package/lib/document-export.mjs +52 -27
- package/lib/document-extract/docling-client.mjs +9 -5
- package/lib/document-extract/docling-sidecar.py +30 -0
- package/lib/document-extract.mjs +1 -1
- package/lib/document-ingest.mjs +9 -0
- package/lib/embed/approval-queue.mjs +184 -88
- package/lib/embed/authority-guard.mjs +65 -2
- package/lib/embed/capability-jobs.mjs +365 -0
- package/lib/embed/capability-lifecycle.mjs +219 -0
- package/lib/embed/capability-loader.mjs +303 -0
- package/lib/embed/capability-runtime.mjs +58 -0
- package/lib/embed/cli.mjs +185 -8
- package/lib/embed/config.mjs +4 -0
- package/lib/embed/daemon.mjs +125 -6
- package/lib/embed/demand-fetch.mjs +190 -54
- package/lib/embed/inbox.mjs +12 -10
- package/lib/embed/presets/ops-triage.mjs +236 -0
- package/lib/embed/presets/pm-feedback.mjs +341 -0
- package/lib/embed/presets/tpm.mjs +401 -0
- package/lib/embed/providers/jira.mjs +72 -1
- package/lib/embed/providers/registry.mjs +140 -33
- package/lib/embedded-contract/capability.mjs +4 -0
- package/lib/embedded-contract/execution.mjs +1 -1
- package/lib/embedded-contract/model-resolve.mjs +53 -3
- package/lib/embedded-contract/workflow-defs.mjs +48 -73
- package/lib/embedded-contract/workflow-invoke.mjs +144 -4
- package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
- package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
- package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
- package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
- package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
- package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
- package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
- package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
- package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
- package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
- package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
- package/lib/env-config.mjs +50 -9
- package/lib/extensions/index.mjs +27 -0
- package/lib/extensions/loader.mjs +120 -0
- package/lib/extensions/manifest-schema.mjs +141 -0
- package/lib/extensions/manifests/anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
- package/lib/extensions/manifests/directory.manifest.json +12 -0
- package/lib/extensions/manifests/docling.manifest.json +37 -0
- package/lib/extensions/manifests/echo.manifest.json +8 -0
- package/lib/extensions/manifests/feedback.manifest.json +12 -0
- package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
- package/lib/extensions/manifests/github.manifest.json +52 -0
- package/lib/extensions/manifests/linear.manifest.json +50 -0
- package/lib/extensions/manifests/local.manifest.json +12 -0
- package/lib/extensions/manifests/ollama.manifest.json +12 -0
- package/lib/extensions/manifests/openai.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter.manifest.json +12 -0
- package/lib/extensions/manifests/postgres.manifest.json +12 -0
- package/lib/extensions/manifests/salesforce.manifest.json +12 -0
- package/lib/extensions/manifests/slack.manifest.json +58 -0
- package/lib/extensions/manifests/whisper.manifest.json +36 -0
- package/lib/extensions/validate.mjs +175 -0
- package/lib/features.mjs +13 -9
- package/lib/flows/checkpoint.mjs +184 -0
- package/lib/flows/constants.mjs +27 -0
- package/lib/flows/define.mjs +146 -0
- package/lib/flows/engine.mjs +249 -0
- package/lib/flows/errors.mjs +19 -0
- package/lib/flows/index.mjs +27 -0
- package/lib/flows/joins.mjs +18 -0
- package/lib/flows/schema.mjs +90 -0
- package/lib/flows/state.mjs +31 -0
- package/lib/frameworks/loader.mjs +99 -0
- package/lib/frameworks/schema.mjs +157 -0
- package/lib/graph/build-from-corpus.mjs +67 -0
- package/lib/graph/build-from-embed.mjs +82 -0
- package/lib/graph/build-from-registry.mjs +164 -3
- package/lib/graph/cli.mjs +456 -12
- package/lib/graph/gap-queries.mjs +156 -0
- package/lib/graph/gaps.mjs +41 -0
- package/lib/graph/impacted.mjs +129 -0
- package/lib/graph/runtime-evidence.mjs +177 -0
- package/lib/graph/security-coverage.mjs +113 -0
- package/lib/graph/staleness.mjs +241 -10
- package/lib/graph/store.mjs +24 -7
- package/lib/graph/validate.mjs +161 -0
- package/lib/headhunt.mjs +9 -8
- package/lib/health-check.mjs +15 -7
- package/lib/hook-health.mjs +1 -1
- package/lib/hooks/agent-tracker.mjs +10 -4
- package/lib/hooks/edit-guard.mjs +3 -3
- package/lib/hooks/graph-impact-advisory.mjs +2 -2
- package/lib/hooks/guard-bash.mjs +41 -15
- package/lib/hooks/mcp-health-check.mjs +11 -4
- package/lib/hooks/model-fallback.mjs +50 -6
- package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
- package/lib/hooks/pre-compact.mjs +181 -173
- package/lib/hooks/rule-verifier.mjs +2 -1
- package/lib/hooks/session-reflect.mjs +2 -1
- package/lib/hooks/session-start.mjs +3 -3
- package/lib/host-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +19 -7
- package/lib/identity.mjs +92 -0
- package/lib/ingest/degraded-extract.mjs +96 -0
- package/lib/ingest/docling-remote.mjs +2 -1
- package/lib/ingest/provider-extract.mjs +7 -19
- package/lib/ingest/sidecar-providers.mjs +196 -0
- package/lib/ingest-tooling.mjs +11 -5
- package/lib/init-unified.mjs +55 -38
- package/lib/init-update.mjs +2 -1
- package/lib/install/legacy-global-cleanup.mjs +25 -0
- package/lib/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- package/lib/intake/queue-registry.mjs +50 -0
- package/lib/intake/queue.mjs +133 -17
- package/lib/intake/session-prelude.mjs +57 -8
- package/lib/integrations/intake-integrations.mjs +61 -111
- package/lib/intent-classifier.mjs +43 -5
- package/lib/libreoffice-export.mjs +8 -8
- package/lib/logging/rotate.mjs +5 -4
- package/lib/mcp/broker.mjs +332 -17
- package/lib/mcp/denied-store.mjs +95 -0
- package/lib/mcp/destructive-gate.mjs +30 -0
- package/lib/mcp/dispatch-envelope.mjs +199 -0
- package/lib/mcp/memory-bridge.mjs +2 -1
- package/lib/mcp/server.mjs +154 -1411
- package/lib/mcp/tool-definitions-memory.mjs +376 -0
- package/lib/mcp/tool-definitions-project.mjs +271 -0
- package/lib/mcp/tool-definitions-skills.mjs +311 -0
- package/lib/mcp/tool-definitions-workflow.mjs +398 -0
- package/lib/mcp/tool-definitions.mjs +25 -0
- package/lib/mcp/tool-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +1 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +165 -25
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- package/lib/mcp/tools/provider-write.mjs +187 -0
- package/lib/mcp/tools/scope.mjs +0 -3
- package/lib/mcp/tools/skills.mjs +1 -1
- package/lib/mcp/tools/storage.mjs +0 -8
- package/lib/mcp/transport/auth.mjs +238 -0
- package/lib/mcp/transport/http.mjs +136 -0
- package/lib/mcp/transport/mode.mjs +31 -0
- package/lib/mcp/transport/stdio.mjs +22 -0
- package/lib/mcp-catalog.json +4 -4
- package/lib/mcp-manager.mjs +34 -23
- package/lib/mcp-platform-config.mjs +31 -7
- package/lib/mode-capabilities.mjs +109 -0
- package/lib/model-router.mjs +42 -9
- package/lib/models/catalog.mjs +40 -1
- package/lib/net-guard.mjs +247 -0
- package/lib/observation-store.mjs +6 -2
- package/lib/ollama/provision-context.mjs +2 -1
- package/lib/opencode-config.mjs +22 -3
- package/lib/opencode-runtime-plugin.mjs +120 -2
- package/lib/oracle/actions.mjs +204 -10
- package/lib/oracle/cli.mjs +24 -3
- package/lib/oracle/dispatch.mjs +2 -1
- package/lib/oracle/execute.mjs +2 -2
- package/lib/oracle/gaps.mjs +3 -3
- package/lib/oracle/heartbeat.mjs +53 -0
- package/lib/oracle/read-model.mjs +69 -13
- package/lib/oracle/reconcile.mjs +3 -46
- package/lib/oracle/routing.mjs +37 -31
- package/lib/oracle/synthesize.mjs +1 -1
- package/lib/orchestration/classification.mjs +434 -0
- package/lib/orchestration/delegation-flow.mjs +132 -0
- package/lib/orchestration/flow-selection.mjs +418 -0
- package/lib/orchestration/gates.mjs +244 -0
- package/lib/orchestration/host-sampling.mjs +121 -0
- package/lib/orchestration/policy-constants.mjs +48 -0
- package/lib/orchestration/provider-outcome.mjs +197 -0
- package/lib/orchestration/readiness.mjs +114 -13
- package/lib/orchestration/run-store-postgres.mjs +32 -26
- package/lib/orchestration/run-store-sqlite.mjs +8 -14
- package/lib/orchestration/run-store.mjs +25 -12
- package/lib/orchestration/runtime.mjs +446 -39
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +3 -2
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +528 -89
- package/lib/orchestration-policy.mjs +67 -1111
- package/lib/packs/cli.mjs +144 -0
- package/lib/packs/core-pack.mjs +112 -0
- package/lib/packs/enablement.mjs +187 -0
- package/lib/packs/index.mjs +23 -0
- package/lib/packs/loader.mjs +176 -0
- package/lib/packs/manifest-schema.mjs +58 -0
- package/lib/packs/prompts.mjs +120 -0
- package/lib/packs/validate.mjs +208 -0
- package/lib/plugin-registry.mjs +4 -5
- package/lib/policy/audit-gate.mjs +42 -0
- package/lib/policy/consumption-budget.mjs +149 -0
- package/lib/policy/engine.mjs +81 -6
- package/lib/policy/role-authority.mjs +89 -0
- package/lib/prompt-composer.js +19 -3
- package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
- package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
- package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
- package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
- package/lib/providers/contract/adapters/github/index.mjs +38 -3
- package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
- package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
- package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
- package/lib/providers/contract/adapters/jira/manifest.json +17 -0
- package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
- package/lib/providers/contract.mjs +207 -0
- package/lib/providers/credential-bootstrap.mjs +7 -4
- package/lib/providers/credential-sources.mjs +14 -2
- package/lib/providers/directory/index.mjs +261 -0
- package/lib/providers/feedback/index.mjs +392 -0
- package/lib/providers/filter-audit.mjs +68 -0
- package/lib/providers/filter-schema.mjs +54 -0
- package/lib/providers/github/index.mjs +31 -0
- package/lib/providers/instance-config.mjs +215 -0
- package/lib/providers/op-locate.mjs +96 -0
- package/lib/providers/op-run.mjs +64 -9
- package/lib/providers/registry.mjs +26 -3
- package/lib/providers/secret-audit-wiring.mjs +6 -0
- package/lib/providers/secret-resolver.mjs +240 -23
- package/lib/publish-template.mjs +6 -3
- package/lib/publish-tooling.mjs +4 -0
- package/lib/publish.mjs +32 -4
- package/lib/queue/pg-queue.mjs +395 -0
- package/lib/reflect.mjs +2 -1
- package/lib/registry/assemble.mjs +28 -2
- package/lib/registry/consolidation.mjs +8 -1
- package/lib/registry/custom-scaffold.mjs +200 -0
- package/lib/registry/custom-schema.mjs +137 -0
- package/lib/registry/loader.mjs +8 -3
- package/lib/registry/manifests/format-engines.default.json +15 -0
- package/lib/registry/manifests/surface-map.default.json +46 -0
- package/lib/registry/retired-paths.mjs +1 -0
- package/lib/registry/surface-map.mjs +100 -50
- package/lib/render-visual-check.mjs +240 -0
- package/lib/resources/budget.mjs +40 -17
- package/lib/roles/flavor-bindings.mjs +36 -14
- package/lib/roles/gateway.mjs +15 -8
- package/lib/roots.mjs +107 -0
- package/lib/runtime/uv-bootstrap.mjs +32 -6
- package/lib/runtime/whisper-bootstrap.mjs +11 -3
- package/lib/runtime-env.mjs +5 -2
- package/lib/scheduler/index.mjs +8 -20
- package/lib/schema-infer.mjs +31 -2
- package/lib/scopes/lifecycle.mjs +29 -25
- package/lib/scopes/loader.mjs +49 -12
- package/lib/scopes/teams.mjs +1 -1
- package/lib/security/ingest-boundary.mjs +80 -0
- package/lib/security/recall-wrapper.mjs +99 -0
- package/lib/security/trust.mjs +132 -0
- package/lib/service-manager.mjs +38 -19
- package/lib/setup.mjs +41 -23
- package/lib/skills-apply.mjs +4 -2
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/state-root.mjs +147 -0
- package/lib/status.mjs +597 -6
- package/lib/storage/admin.mjs +77 -5
- package/lib/storage/backend-registry.mjs +73 -0
- package/lib/storage/backend.mjs +63 -9
- package/lib/storage/embeddings-openai.mjs +12 -2
- package/lib/storage/hybrid-query.mjs +11 -3
- package/lib/storage/retrieval-hardening.mjs +384 -0
- package/lib/storage/shared-memory.mjs +158 -0
- package/lib/storage/vector-client.mjs +69 -2
- package/lib/team/health.mjs +72 -0
- package/lib/telemetry/backends/local.mjs +9 -3
- package/lib/telemetry/client.mjs +3 -7
- package/lib/template-registry.mjs +10 -12
- package/lib/tenant/context.mjs +82 -0
- package/lib/tenant/isolation.mjs +129 -0
- package/lib/test-corpus-inventory.mjs +103 -2
- package/lib/uninstall/uninstall.mjs +78 -12
- package/lib/validator.mjs +4 -6
- package/lib/worker/entrypoint.mjs +2 -1
- package/lib/worker/run.mjs +2 -2
- package/lib/worker/trace.mjs +5 -11
- package/lib/workflow-state.mjs +52 -8
- package/lib/workflows/liveness.mjs +203 -0
- package/lib/workflows/loader.mjs +177 -0
- package/lib/workflows/manifest-schema.mjs +71 -0
- package/lib/workflows/surface-parity.mjs +118 -0
- package/lib/workflows/validate.mjs +127 -0
- package/lib/writes/control-plane.mjs +140 -0
- package/lib/writes/envelope.mjs +206 -0
- package/lib/writes/sent-log.mjs +86 -0
- package/lib/writes/write-intent.mjs +109 -0
- package/package.json +16 -8
- package/personas/construct.md +12 -3
- package/registry/capabilities.json +143 -36
- package/rules/common/comments.md +1 -0
- package/schemas/project-config.schema.json +0 -12
- package/schemas/unified-registry.schema.json +2 -4
- package/scripts/sync-specialists.mjs +284 -81
- package/skills/docs/adr-workflow.md +1 -1
- package/skills/docs/codebase-research-workflow.md +3 -3
- package/skills/docs/prd-workflow.md +5 -6
- package/skills/docs/runbook-workflow.md +2 -2
- package/skills/docs/user-research-workflow.md +3 -3
- package/skills/operating/fleet-health-routing.md +77 -0
- package/skills/roles/ai-engineer.md +1 -1
- package/skills/roles/architect.md +0 -1
- package/skills/roles/business-strategist.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/data-engineer.md +1 -1
- package/skills/roles/data-engineer.pipeline.md +1 -1
- package/skills/roles/data-engineer.vector-retrieval.md +1 -2
- package/skills/roles/data-engineer.warehouse.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +0 -1
- package/skills/roles/devil-advocate.md +1 -1
- package/skills/roles/docs-keeper.md +1 -1
- package/skills/roles/evaluator.md +1 -1
- package/skills/roles/explorer.md +1 -1
- package/skills/roles/platform-engineer.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -2
- package/skills/roles/qa.api-contract.md +0 -1
- package/skills/roles/qa.data-pipeline.md +0 -1
- package/skills/roles/qa.md +0 -1
- package/skills/roles/qa.web-ui.md +0 -1
- package/skills/roles/release-manager.md +1 -1
- package/skills/roles/researcher.md +0 -2
- package/skills/roles/reviewer.md +0 -3
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +0 -1
- package/skills/roles/security.privacy.md +0 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/sre.md +1 -1
- package/skills/roles/test-automation.md +1 -1
- package/skills/roles/trace-reviewer.md +1 -1
- package/skills/roles/ux-researcher.md +1 -1
- package/specialists/artifact-manifest.json +36 -36
- package/specialists/org/contracts/accessibility-to-qa.json +11 -3
- package/specialists/org/contracts/any-to-business-strategist.json +19 -7
- package/specialists/org/contracts/any-to-debugger.json +7 -1
- package/specialists/org/contracts/any-to-designer.json +7 -1
- package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
- package/specialists/org/contracts/any-to-explorer.json +13 -4
- package/specialists/org/contracts/any-to-sre-incident.json +6 -2
- package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
- package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
- package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
- package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
- package/specialists/org/contracts/architect-to-engineer.json +20 -4
- package/specialists/org/contracts/architect-to-evaluator.json +15 -5
- package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
- package/specialists/org/contracts/architect-to-operations.json +17 -4
- package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
- package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
- package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
- package/specialists/org/contracts/engineer-to-qa.json +20 -4
- package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
- package/specialists/org/contracts/explorer-to-engineer.json +11 -3
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
- package/specialists/org/contracts/operations-to-user.json +53 -0
- package/specialists/org/contracts/operations-triage-output.json +53 -0
- package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
- package/specialists/org/contracts/product-manager-to-architect.json +30 -10
- package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
- package/specialists/org/contracts/qa-to-release-manager.json +11 -3
- package/specialists/org/contracts/researcher-to-architect.json +10 -2
- package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
- package/specialists/org/contracts/reviewer-to-security.json +17 -3
- package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
- package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
- package/specialists/org/contracts/user-to-construct.json +11 -4
- package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
- package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
- package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
- package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
- package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
- package/specialists/org/groups/engineering-group.json +2 -6
- package/specialists/org/groups/governance-group.json +2 -3
- package/specialists/org/groups/operations-group.json +5 -8
- package/specialists/org/groups/product-group.json +4 -8
- package/specialists/org/groups/quality-group.json +3 -7
- package/specialists/org/groups/strategy-group.json +6 -9
- package/specialists/org/models.json.example +8 -0
- package/specialists/org/scopes/creative.json +6 -1
- package/specialists/org/scopes/operations.json +7 -1
- package/specialists/org/scopes/research.json +6 -1
- package/specialists/org/scopes/rnd.json +7 -1
- package/specialists/org/specialists/cx-architect.json +27 -8
- package/specialists/org/specialists/cx-designer.json +22 -8
- package/specialists/org/specialists/cx-engineer.json +71 -7
- package/specialists/org/specialists/cx-operations.json +89 -15
- package/specialists/org/specialists/cx-orchestrator.json +16 -4
- package/specialists/org/specialists/cx-product-manager.json +28 -9
- package/specialists/org/specialists/cx-qa.json +16 -6
- package/specialists/org/specialists/cx-researcher.json +40 -7
- package/specialists/org/specialists/cx-reviewer.json +61 -11
- package/specialists/org/specialists/cx-security.json +30 -10
- package/specialists/org/teams/design-team.json +3 -4
- package/specialists/org/teams/engineering-team.json +3 -10
- package/specialists/org/teams/governance-team.json +3 -5
- package/specialists/org/teams/operations-team.json +6 -12
- package/specialists/org/teams/product-management-team.json +2 -3
- package/specialists/org/teams/quality-team.json +4 -12
- package/specialists/org/teams/research-team.json +3 -3
- package/specialists/org/teams/strategy-team.json +7 -14
- package/specialists/prompts/cx-architect.md +20 -1
- package/specialists/prompts/cx-data-analyst.md +1 -1
- package/specialists/prompts/cx-designer.md +12 -4
- package/specialists/prompts/cx-engineer.md +15 -1
- package/specialists/prompts/cx-operations.md +14 -2
- package/specialists/prompts/cx-orchestrator.md +9 -5
- package/specialists/prompts/cx-product-manager.md +5 -1
- package/specialists/prompts/cx-qa.md +6 -2
- package/specialists/prompts/cx-researcher.md +25 -30
- package/specialists/prompts/cx-reviewer.md +19 -1
- package/specialists/prompts/cx-security.md +5 -1
- package/templates/distribution/construct-brand.typ +123 -59
- package/templates/distribution/construct-decision.typ +6 -3
- package/templates/distribution/construct-pdf.typ +11 -4
- package/templates/distribution/construct-prd.typ +6 -3
- package/templates/distribution/construct-research.typ +7 -4
- package/lib/providers/contract/adapters/git/index.mjs +0 -115
- package/lib/providers/contract/adapters/slack/index.mjs +0 -175
- package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
- package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
- package/specialists/org/contracts/designer-to-accessibility.json +0 -36
- package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
- package/specialists/org/contracts/qa-to-test-automation.json +0 -42
- package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
- package/specialists/org/contracts/sre-to-release-manager.json +0 -36
- package/specialists/org/specialists/cx-accessibility.json +0 -69
- package/specialists/org/specialists/cx-ai-engineer.json +0 -75
- package/specialists/org/specialists/cx-business-strategist.json +0 -72
- package/specialists/org/specialists/cx-data-engineer.json +0 -71
- package/specialists/org/specialists/cx-devil-advocate.json +0 -71
- package/specialists/org/specialists/cx-docs-keeper.json +0 -82
- package/specialists/org/specialists/cx-evaluator.json +0 -69
- package/specialists/org/specialists/cx-explorer.json +0 -69
- package/specialists/org/specialists/cx-legal-compliance.json +0 -74
- package/specialists/org/specialists/cx-oracle.json +0 -46
- package/specialists/org/specialists/cx-platform-engineer.json +0 -80
- package/specialists/org/specialists/cx-rd-lead.json +0 -73
- package/specialists/org/specialists/cx-release-manager.json +0 -77
- package/specialists/org/specialists/cx-sre.json +0 -94
- package/specialists/org/specialists/cx-test-automation.json +0 -69
- package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
- package/specialists/org/specialists/cx-ux-researcher.json +0 -68
- package/specialists/org/teams/accessibility-team.json +0 -39
- package/specialists/org/teams/ux-research-team.json +0 -39
- package/specialists/prompts/cx-accessibility.md +0 -55
- package/specialists/prompts/cx-ai-engineer.md +0 -124
- package/specialists/prompts/cx-business-strategist.md +0 -58
- package/specialists/prompts/cx-data-engineer.md +0 -55
- package/specialists/prompts/cx-devil-advocate.md +0 -59
- package/specialists/prompts/cx-docs-keeper.md +0 -164
- package/specialists/prompts/cx-evaluator.md +0 -49
- package/specialists/prompts/cx-explorer.md +0 -71
- package/specialists/prompts/cx-legal-compliance.md +0 -58
- package/specialists/prompts/cx-oracle.md +0 -98
- package/specialists/prompts/cx-platform-engineer.md +0 -97
- package/specialists/prompts/cx-rd-lead.md +0 -59
- package/specialists/prompts/cx-release-manager.md +0 -54
- package/specialists/prompts/cx-sre.md +0 -111
- package/specialists/prompts/cx-test-automation.md +0 -55
- package/specialists/prompts/cx-trace-reviewer.md +0 -101
- package/specialists/prompts/cx-ux-researcher.md +0 -53
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/providers/feedback/index.mjs — Feedback / customer-input data-source provider.
|
|
3
|
+
*
|
|
4
|
+
* Reads a drop-directory of JSONL or CSV files (survey exports, support-ticket
|
|
5
|
+
* dumps, user-research notes) and normalizes every row into a `feedback-item`
|
|
6
|
+
* record: { id, text, author, date, source, tags, provenance }. Field mapping
|
|
7
|
+
* is caller-configured so arbitrary export schemas can map onto the same
|
|
8
|
+
* shape; only `id` and `text` are required in the mapping, the rest default
|
|
9
|
+
* to null/empty when unmapped or absent.
|
|
10
|
+
*
|
|
11
|
+
* Capabilities: read, search.
|
|
12
|
+
*
|
|
13
|
+
* Config (per call):
|
|
14
|
+
* - root: absolute or relative path to the drop-directory (required)
|
|
15
|
+
* - format: 'jsonl' | 'csv' (required)
|
|
16
|
+
* - fields: { id, text, author?, date?, source?, tags? } column/key mapping (required)
|
|
17
|
+
*
|
|
18
|
+
* Malformed rows (missing required mapped fields, unparsable JSON/CSV lines)
|
|
19
|
+
* are skipped, never thrown — each skip increments a per-file counter and a
|
|
20
|
+
* single warning summarizing the count is emitted per file via console.warn.
|
|
21
|
+
*
|
|
22
|
+
* Trust: feedback content originates from external, unauthenticated sources
|
|
23
|
+
* (customers, survey respondents) with no authorship verification available
|
|
24
|
+
* to Construct. Every item is stamped EXTERNAL_UNAUTHENTICATED per the N1
|
|
25
|
+
* trust taxonomy (lib/security/trust.mjs) — highest injection risk, must be
|
|
26
|
+
* wrapped before entering model context.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { existsSync, statSync, readdirSync, readFileSync } from 'node:fs';
|
|
30
|
+
import { resolve, isAbsolute, join, extname } from 'node:path';
|
|
31
|
+
import { TRUST_LEVELS, stampTrust } from '../../security/trust.mjs';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Resolve and validate the configured drop-directory root.
|
|
35
|
+
*/
|
|
36
|
+
function resolveRoot(root) {
|
|
37
|
+
if (!root) return { ok: false, reason: 'config.root required' };
|
|
38
|
+
const rootPath = isAbsolute(root) ? root : resolve(process.cwd(), root);
|
|
39
|
+
if (!existsSync(rootPath)) {
|
|
40
|
+
return { ok: false, reason: `root does not exist: ${rootPath}` };
|
|
41
|
+
}
|
|
42
|
+
const stat = statSync(rootPath);
|
|
43
|
+
if (!stat.isDirectory()) {
|
|
44
|
+
return { ok: false, reason: `root is not a directory: ${rootPath}` };
|
|
45
|
+
}
|
|
46
|
+
return { ok: true, path: rootPath };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* List files under root matching the configured format's extension.
|
|
51
|
+
*/
|
|
52
|
+
function listFormatFiles(rootPath, format) {
|
|
53
|
+
const ext = format === 'csv' ? '.csv' : '.jsonl';
|
|
54
|
+
let entries;
|
|
55
|
+
try {
|
|
56
|
+
entries = readdirSync(rootPath, { withFileTypes: true });
|
|
57
|
+
} catch {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
return entries
|
|
61
|
+
.filter((e) => e.isFile() && extname(e.name).toLowerCase() === ext)
|
|
62
|
+
.map((e) => e.name)
|
|
63
|
+
.sort();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Minimal RFC-4180-ish CSV line splitter supporting quoted fields, escaped
|
|
68
|
+
* quotes (""), and commas/newlines within quotes. No external dependency —
|
|
69
|
+
* the repo has none for CSV, and drop-dir exports are expected to be simple.
|
|
70
|
+
*/
|
|
71
|
+
function parseCsv(content) {
|
|
72
|
+
const rows = [];
|
|
73
|
+
let row = [];
|
|
74
|
+
let field = '';
|
|
75
|
+
let inQuotes = false;
|
|
76
|
+
let i = 0;
|
|
77
|
+
|
|
78
|
+
while (i < content.length) {
|
|
79
|
+
const ch = content[i];
|
|
80
|
+
|
|
81
|
+
if (inQuotes) {
|
|
82
|
+
if (ch === '"') {
|
|
83
|
+
if (content[i + 1] === '"') {
|
|
84
|
+
field += '"';
|
|
85
|
+
i += 2;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
inQuotes = false;
|
|
89
|
+
i += 1;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
field += ch;
|
|
93
|
+
i += 1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (ch === '"') {
|
|
98
|
+
inQuotes = true;
|
|
99
|
+
i += 1;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (ch === ',') {
|
|
103
|
+
row.push(field);
|
|
104
|
+
field = '';
|
|
105
|
+
i += 1;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (ch === '\r') {
|
|
109
|
+
i += 1;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (ch === '\n') {
|
|
113
|
+
row.push(field);
|
|
114
|
+
rows.push(row);
|
|
115
|
+
row = [];
|
|
116
|
+
field = '';
|
|
117
|
+
i += 1;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
field += ch;
|
|
121
|
+
i += 1;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (field.length > 0 || row.length > 0) {
|
|
125
|
+
row.push(field);
|
|
126
|
+
rows.push(row);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return rows;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Extract a mapped field from a source object; returns undefined when the
|
|
134
|
+
* mapping key is unset or the source object lacks that key.
|
|
135
|
+
*/
|
|
136
|
+
function mapField(sourceObj, mappingKey) {
|
|
137
|
+
if (!mappingKey) return undefined;
|
|
138
|
+
return sourceObj[mappingKey];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Normalize a raw mapped row into the canonical feedback-item shape, minus
|
|
143
|
+
* provenance and trust (added by the caller). Returns null when required
|
|
144
|
+
* fields (id, text) are missing — caller counts this as a skip.
|
|
145
|
+
*/
|
|
146
|
+
function normalizeRow(rawObj, fields) {
|
|
147
|
+
const id = mapField(rawObj, fields.id);
|
|
148
|
+
const text = mapField(rawObj, fields.text);
|
|
149
|
+
|
|
150
|
+
if (id === undefined || id === null || String(id).trim() === '') return null;
|
|
151
|
+
if (text === undefined || text === null || String(text).trim() === '') return null;
|
|
152
|
+
|
|
153
|
+
const author = mapField(rawObj, fields.author);
|
|
154
|
+
const date = mapField(rawObj, fields.date);
|
|
155
|
+
const source = mapField(rawObj, fields.source);
|
|
156
|
+
const rawTags = fields.tags ? mapField(rawObj, fields.tags) : undefined;
|
|
157
|
+
|
|
158
|
+
let tags = [];
|
|
159
|
+
if (Array.isArray(rawTags)) {
|
|
160
|
+
tags = rawTags.map(String);
|
|
161
|
+
} else if (typeof rawTags === 'string' && rawTags.trim() !== '') {
|
|
162
|
+
tags = rawTags.split(/[;,|]/).map((t) => t.trim()).filter(Boolean);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
id: String(id),
|
|
167
|
+
text: String(text),
|
|
168
|
+
author: author != null ? String(author) : null,
|
|
169
|
+
date: date != null ? String(date) : null,
|
|
170
|
+
source: source != null ? String(source) : null,
|
|
171
|
+
tags,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Parse a single JSONL file into feedback-items. Malformed lines (unparsable
|
|
177
|
+
* JSON, missing required fields) are skipped; the count is returned so the
|
|
178
|
+
* caller can emit one warning per file.
|
|
179
|
+
*/
|
|
180
|
+
function readJsonlFile(filePath, fields) {
|
|
181
|
+
const items = [];
|
|
182
|
+
let skipped = 0;
|
|
183
|
+
const content = readFileSync(filePath, 'utf8');
|
|
184
|
+
const lines = content.split(/\r?\n/);
|
|
185
|
+
|
|
186
|
+
for (let lineNo = 0; lineNo < lines.length; lineNo++) {
|
|
187
|
+
const line = lines[lineNo].trim();
|
|
188
|
+
if (line === '') continue;
|
|
189
|
+
|
|
190
|
+
let parsed;
|
|
191
|
+
try {
|
|
192
|
+
parsed = JSON.parse(line);
|
|
193
|
+
} catch {
|
|
194
|
+
skipped += 1;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
199
|
+
skipped += 1;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const normalized = normalizeRow(parsed, fields);
|
|
204
|
+
if (!normalized) {
|
|
205
|
+
skipped += 1;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
items.push({ ...normalized, row: lineNo + 1 });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return { items, skipped };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Parse a single CSV file into feedback-items. First row is the header.
|
|
217
|
+
* Malformed rows (column-count mismatch, missing required fields) are
|
|
218
|
+
* skipped; the count is returned so the caller can emit one warning per file.
|
|
219
|
+
*/
|
|
220
|
+
function readCsvFile(filePath, fields) {
|
|
221
|
+
const items = [];
|
|
222
|
+
let skipped = 0;
|
|
223
|
+
const content = readFileSync(filePath, 'utf8');
|
|
224
|
+
const rows = parseCsv(content);
|
|
225
|
+
|
|
226
|
+
if (rows.length === 0) return { items, skipped };
|
|
227
|
+
|
|
228
|
+
const header = rows[0];
|
|
229
|
+
|
|
230
|
+
for (let rowNo = 1; rowNo < rows.length; rowNo++) {
|
|
231
|
+
const row = rows[rowNo];
|
|
232
|
+
if (row.length === 1 && row[0] === '') continue;
|
|
233
|
+
|
|
234
|
+
if (row.length !== header.length) {
|
|
235
|
+
skipped += 1;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const rawObj = {};
|
|
240
|
+
for (let c = 0; c < header.length; c++) {
|
|
241
|
+
rawObj[header[c]] = row[c];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const normalized = normalizeRow(rawObj, fields);
|
|
245
|
+
if (!normalized) {
|
|
246
|
+
skipped += 1;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
items.push({ ...normalized, row: rowNo + 1 });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return { items, skipped };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Validate the required config shape shared by read/search.
|
|
258
|
+
*/
|
|
259
|
+
function validateConfig(config) {
|
|
260
|
+
const root = config?.root;
|
|
261
|
+
const format = config?.format;
|
|
262
|
+
const fields = config?.fields;
|
|
263
|
+
|
|
264
|
+
const resolved = resolveRoot(root);
|
|
265
|
+
if (!resolved.ok) throw new Error(`feedback: ${resolved.reason}`);
|
|
266
|
+
|
|
267
|
+
if (format !== 'jsonl' && format !== 'csv') {
|
|
268
|
+
throw new Error(`feedback: config.format must be 'jsonl' or 'csv' (got '${format}')`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (!fields || typeof fields !== 'object' || !fields.id || !fields.text) {
|
|
272
|
+
throw new Error('feedback: config.fields.id and config.fields.text are required');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return { rootPath: resolved.path, format, fields };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Read every file of the configured format under root, normalize rows to
|
|
280
|
+
* feedback-items, stamp provenance + N1 trust, and warn once per file with
|
|
281
|
+
* any skipped-row count.
|
|
282
|
+
*/
|
|
283
|
+
async function readAllFeedback(config) {
|
|
284
|
+
const { rootPath, format, fields } = validateConfig(config);
|
|
285
|
+
|
|
286
|
+
const files = listFormatFiles(rootPath, format);
|
|
287
|
+
const results = [];
|
|
288
|
+
|
|
289
|
+
for (const fileName of files) {
|
|
290
|
+
const filePath = join(rootPath, fileName);
|
|
291
|
+
const { items, skipped } = format === 'csv'
|
|
292
|
+
? readCsvFile(filePath, fields)
|
|
293
|
+
: readJsonlFile(filePath, fields);
|
|
294
|
+
|
|
295
|
+
if (skipped > 0) {
|
|
296
|
+
console.warn(`feedback provider: skipped ${skipped} malformed row(s) in ${fileName}`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
for (const item of items) {
|
|
300
|
+
const { row, ...rest } = item;
|
|
301
|
+
results.push(stampTrust(
|
|
302
|
+
{
|
|
303
|
+
...rest,
|
|
304
|
+
provenance: { file: fileName, row },
|
|
305
|
+
},
|
|
306
|
+
TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED,
|
|
307
|
+
`feedback:${fileName}`,
|
|
308
|
+
));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return results;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Search feedback-item text for a substring match (case-insensitive).
|
|
317
|
+
*/
|
|
318
|
+
async function searchFeedback(config, query) {
|
|
319
|
+
if (!query || typeof query !== 'string') {
|
|
320
|
+
throw new Error('feedback.search: query required (substring)');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const items = await readAllFeedback(config);
|
|
324
|
+
const needle = query.toLowerCase();
|
|
325
|
+
return items.filter((item) => item.text.toLowerCase().includes(needle));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function create({ env = process.env } = {}) {
|
|
329
|
+
return {
|
|
330
|
+
meta: {
|
|
331
|
+
id: 'feedback',
|
|
332
|
+
displayName: 'Feedback / Customer Input',
|
|
333
|
+
capabilities: ['read', 'search'],
|
|
334
|
+
description: 'Read and search customer feedback exports (JSONL/CSV) from a drop-directory.',
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
configSchema: {
|
|
338
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
339
|
+
type: 'object',
|
|
340
|
+
properties: {
|
|
341
|
+
root: {
|
|
342
|
+
type: 'string',
|
|
343
|
+
description: 'Path to the feedback drop-directory (absolute or relative to cwd)',
|
|
344
|
+
},
|
|
345
|
+
format: {
|
|
346
|
+
type: 'string',
|
|
347
|
+
enum: ['jsonl', 'csv'],
|
|
348
|
+
description: 'Format of files to read from the drop-directory',
|
|
349
|
+
},
|
|
350
|
+
fields: {
|
|
351
|
+
type: 'object',
|
|
352
|
+
description: 'Field mapping from source column/key names to feedback-item shape',
|
|
353
|
+
properties: {
|
|
354
|
+
id: { type: 'string', description: 'Source key/column mapped to feedback-item id' },
|
|
355
|
+
text: { type: 'string', description: 'Source key/column mapped to feedback-item text' },
|
|
356
|
+
author: { type: 'string', description: 'Source key/column mapped to feedback-item author' },
|
|
357
|
+
date: { type: 'string', description: 'Source key/column mapped to feedback-item date' },
|
|
358
|
+
source: { type: 'string', description: 'Source key/column mapped to feedback-item source' },
|
|
359
|
+
tags: { type: 'string', description: 'Source key/column mapped to feedback-item tags' },
|
|
360
|
+
},
|
|
361
|
+
required: ['id', 'text'],
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
required: ['root', 'format', 'fields'],
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
async health(config) {
|
|
368
|
+
const root = config?.root;
|
|
369
|
+
if (!root) {
|
|
370
|
+
return { ok: false, detail: 'config.root not set' };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const resolved = resolveRoot(root);
|
|
374
|
+
if (!resolved.ok) {
|
|
375
|
+
return { ok: false, detail: resolved.reason };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
const format = config?.format === 'csv' ? 'csv' : 'jsonl';
|
|
380
|
+
const files = listFormatFiles(resolved.path, format);
|
|
381
|
+
return { ok: true, detail: `drop-directory readable (${files.length} ${format} file(s))` };
|
|
382
|
+
} catch (err) {
|
|
383
|
+
return { ok: false, detail: err.message };
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
|
|
387
|
+
read: readAllFeedback,
|
|
388
|
+
search: searchFeedback,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export default create;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/providers/filter-audit.mjs — per-poll provider filter audit log.
|
|
3
|
+
*
|
|
4
|
+
* ADR-0060 requires every poll to record, per source, the filter that was
|
|
5
|
+
* enforced and how many items it dropped. Appends one JSONL line per source
|
|
6
|
+
* per poll to `<rootDir>/.cx/providers/filter-audit.jsonl` with the
|
|
7
|
+
* acceptance-criteria field names `{provider, instance, filterHash, matched,
|
|
8
|
+
* dropped}`, plus the ADR's own `fetched`/`admitted` and `pushdown` fields
|
|
9
|
+
* for the fuller audit shape. Rotation uses `appendWithRotationSync`
|
|
10
|
+
* directly (not the shared `appendBounded` channel registry) to keep this
|
|
11
|
+
* change scoped to `lib/providers/*`.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
|
|
16
|
+
import { appendWithRotationSync } from '../logging/rotate.mjs';
|
|
17
|
+
|
|
18
|
+
const AUDIT_SUBDIR = ['.cx', 'providers'];
|
|
19
|
+
const AUDIT_FILE = 'filter-audit.jsonl';
|
|
20
|
+
|
|
21
|
+
// 10 MB per shard, 5 shards kept — a filter-audit line is written once per
|
|
22
|
+
// source per poll, so this comfortably covers years of daemon uptime.
|
|
23
|
+
|
|
24
|
+
const MAX_BYTES = 10 * 1024 * 1024;
|
|
25
|
+
const MAX_SEGMENTS = 5;
|
|
26
|
+
|
|
27
|
+
export function filterAuditPath(rootDir) {
|
|
28
|
+
return join(rootDir, ...AUDIT_SUBDIR, AUDIT_FILE);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Append one filter-audit record. `matched`/`dropped` are the
|
|
33
|
+
* acceptance-criteria field names; `fetched`/`admitted` mirror the ADR-0060
|
|
34
|
+
* audit shape (`admitted` === `matched`) so both vocabularies are queryable
|
|
35
|
+
* from the same line without a reader needing to know which one shipped.
|
|
36
|
+
*/
|
|
37
|
+
export function recordFilterAudit(rootDir, {
|
|
38
|
+
provider,
|
|
39
|
+
instance = null,
|
|
40
|
+
filterHash: hash = null,
|
|
41
|
+
scope = null,
|
|
42
|
+
predicates = null,
|
|
43
|
+
nativeQuery = null,
|
|
44
|
+
pushdown = false,
|
|
45
|
+
fetched,
|
|
46
|
+
matched,
|
|
47
|
+
}) {
|
|
48
|
+
const record = {
|
|
49
|
+
ts: new Date().toISOString(),
|
|
50
|
+
provider,
|
|
51
|
+
instance,
|
|
52
|
+
filterHash: hash,
|
|
53
|
+
filterApplied: { scope, predicates, nativeQuery, pushdown, fetched, admitted: matched },
|
|
54
|
+
matched,
|
|
55
|
+
dropped: fetched - matched,
|
|
56
|
+
};
|
|
57
|
+
appendWithRotationSync(filterAuditPath(rootDir), JSON.stringify(record) + '\n', {
|
|
58
|
+
maxBytes: MAX_BYTES,
|
|
59
|
+
maxSegments: MAX_SEGMENTS,
|
|
60
|
+
});
|
|
61
|
+
return record;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function readFilterAudit(rootDir) {
|
|
65
|
+
const path = filterAuditPath(rootDir);
|
|
66
|
+
if (!existsSync(path)) return [];
|
|
67
|
+
return readFileSync(path, 'utf8').split('\n').filter(Boolean).map((line) => JSON.parse(line));
|
|
68
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/providers/filter-schema.mjs — provider filter block JSON schema (ADR-0060).
|
|
3
|
+
*
|
|
4
|
+
* Ships the normalized filter grammar every provider kind may accept:
|
|
5
|
+
* `scope` (per-kind container allowlist), `predicates` (portable field-level
|
|
6
|
+
* filter core), and `nativeQuery` (provider-native passthrough escape hatch).
|
|
7
|
+
* Per-kind manifest `configSchema` fields reference this via `$ref`; per-kind
|
|
8
|
+
* `SCOPE_FIELDS_BY_PROVIDER` in `lib/providers/contract.mjs` declares which
|
|
9
|
+
* `scope` keys a given provider kind actually honors.
|
|
10
|
+
*
|
|
11
|
+
* Data-only module (schema + enums), consumed by `validateFilterConfig()`
|
|
12
|
+
* in `lib/providers/contract.mjs`. No JSON-Schema engine runs here — the
|
|
13
|
+
* shape below is the source of truth the hand-rolled validator checks
|
|
14
|
+
* against, keeping schema and validator from drifting silently apart.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export const FILTER_SCHEMA = Object.freeze({
|
|
18
|
+
$id: 'construct:provider-filter',
|
|
19
|
+
type: 'object',
|
|
20
|
+
additionalProperties: false,
|
|
21
|
+
properties: {
|
|
22
|
+
scope: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
additionalProperties: false,
|
|
25
|
+
properties: {
|
|
26
|
+
projects: { type: 'array', items: { type: 'string' } },
|
|
27
|
+
repos: { type: 'array', items: { type: 'string' } },
|
|
28
|
+
repoGlob: { type: 'array', items: { type: 'string' } },
|
|
29
|
+
channels: { type: 'array', items: { type: 'string' } },
|
|
30
|
+
spaces: { type: 'array', items: { type: 'string' } },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
predicates: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
additionalProperties: false,
|
|
36
|
+
properties: {
|
|
37
|
+
assignee: { type: 'array', items: { type: 'string' } },
|
|
38
|
+
statusCategory: { type: 'array', items: { enum: ['to-do', 'in-progress', 'done'] } },
|
|
39
|
+
priority: { type: 'array', items: { type: 'string' } },
|
|
40
|
+
label: { type: 'array', items: { type: 'string' } },
|
|
41
|
+
updatedSince: { type: 'string' },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
nativeQuery: { type: 'string' },
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export const SCOPE_KEYS = Object.freeze(['projects', 'repos', 'repoGlob', 'channels', 'spaces']);
|
|
49
|
+
|
|
50
|
+
export const PREDICATE_KEYS = Object.freeze(['assignee', 'statusCategory', 'priority', 'label', 'updatedSince']);
|
|
51
|
+
|
|
52
|
+
export const STATUS_CATEGORIES = Object.freeze(['to-do', 'in-progress', 'done']);
|
|
53
|
+
|
|
54
|
+
export const TOP_LEVEL_KEYS = Object.freeze(['scope', 'predicates', 'nativeQuery']);
|
|
@@ -14,10 +14,21 @@
|
|
|
14
14
|
* The provider is intentionally narrow in v1 — issue + PR + code search +
|
|
15
15
|
* a single repo metadata read. Webhook signature verification is in place
|
|
16
16
|
* so consumers can mount a receiver without re-implementing it.
|
|
17
|
+
*
|
|
18
|
+
* Scope enforcement: when `repoAllowlist` or `repoAllowGlob` is present in
|
|
19
|
+
* config, `read()` validates `config.repo` and `search()` validates every
|
|
20
|
+
* `repo:` qualifier in the query through `validateAllowlist()`
|
|
21
|
+
* (lib/providers/contract.mjs) before any network call, throwing a typed
|
|
22
|
+
* `OUT_OF_SCOPE` error on a blocked target. A search query with no `repo:`
|
|
23
|
+
* qualifier is rejected outright when an allowlist is configured — an
|
|
24
|
+
* unscoped query could otherwise span the whole credential's visible
|
|
25
|
+
* surface and defeat the allowlist.
|
|
17
26
|
*/
|
|
18
27
|
|
|
19
28
|
import crypto from 'node:crypto';
|
|
20
29
|
|
|
30
|
+
import { validateAllowlist } from '../contract.mjs';
|
|
31
|
+
|
|
21
32
|
const API = 'https://api.github.com';
|
|
22
33
|
|
|
23
34
|
function authHeader(env) {
|
|
@@ -25,6 +36,10 @@ function authHeader(env) {
|
|
|
25
36
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
26
37
|
}
|
|
27
38
|
|
|
39
|
+
function outOfScopeError(reason) {
|
|
40
|
+
return Object.assign(new Error(reason), { code: 'OUT_OF_SCOPE' });
|
|
41
|
+
}
|
|
42
|
+
|
|
28
43
|
async function ghFetch(pathOrUrl, env, init = {}) {
|
|
29
44
|
const url = pathOrUrl.startsWith('http') ? pathOrUrl : `${API}${pathOrUrl}`;
|
|
30
45
|
const headers = {
|
|
@@ -83,6 +98,8 @@ export function create({ env = process.env } = {}) {
|
|
|
83
98
|
|
|
84
99
|
async read(config) {
|
|
85
100
|
if (!config?.repo) throw new Error('github.read: config.repo required (owner/name)');
|
|
101
|
+
const check = validateAllowlist('github', config.repo, config);
|
|
102
|
+
if (!check.allowed) throw outOfScopeError(check.reason);
|
|
86
103
|
return ghFetch(`/repos/${config.repo}`, env);
|
|
87
104
|
},
|
|
88
105
|
|
|
@@ -90,6 +107,20 @@ export function create({ env = process.env } = {}) {
|
|
|
90
107
|
const kind = config?.kind || 'issues';
|
|
91
108
|
const query = config?.query;
|
|
92
109
|
if (!query) throw new Error('github.search: config.query required');
|
|
110
|
+
|
|
111
|
+
const hasAllowlistConfig = (Array.isArray(config?.repoAllowlist) && config.repoAllowlist.length > 0)
|
|
112
|
+
|| (typeof config?.repoAllowGlob === 'string' && config.repoAllowGlob.length > 0);
|
|
113
|
+
const repoQualifiers = [...query.matchAll(/\brepo:(\S+)/g)].map((m) => m[1]);
|
|
114
|
+
|
|
115
|
+
if (hasAllowlistConfig && repoQualifiers.length === 0) {
|
|
116
|
+
throw outOfScopeError('github.search: repoAllowlist/repoAllowGlob is configured; query must include a repo: qualifier to enforce scope');
|
|
117
|
+
}
|
|
118
|
+
for (const repo of repoQualifiers) {
|
|
119
|
+
const target = repo.includes('/') ? repo.split('/').pop() : repo;
|
|
120
|
+
const check = validateAllowlist('github', target, config);
|
|
121
|
+
if (!check.allowed) throw outOfScopeError(check.reason);
|
|
122
|
+
}
|
|
123
|
+
|
|
93
124
|
const path = kind === 'code'
|
|
94
125
|
? `/search/code?q=${encodeURIComponent(query)}`
|
|
95
126
|
: kind === 'prs'
|