@geraldmaron/construct 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +36 -0
- package/README.md +41 -7
- package/bin/construct +1029 -65
- package/bin/construct-postinstall.mjs +27 -2
- package/config/tag-vocabulary.json +264 -0
- package/examples/distribution/sources/deck-one-pager.md +1 -1
- package/lib/acp/server.mjs +21 -7
- package/lib/adapters-sync.mjs +2 -1
- package/lib/audit-trail.mjs +185 -2
- package/lib/beads/auto-close.mjs +2 -1
- package/lib/beads/drift.mjs +2 -1
- package/lib/bridges/copilot-proxy.mjs +20 -5
- package/lib/certification/skill-inventory.mjs +10 -1
- package/lib/cli/approvals.mjs +147 -0
- package/lib/cli-commands.mjs +105 -14
- package/lib/comment-lint.mjs +127 -8
- package/lib/config/schema.mjs +7 -30
- package/lib/config/source-target-registry.mjs +70 -0
- package/lib/config/source-targets.mjs +179 -205
- package/lib/contracts/coverage.mjs +77 -0
- package/lib/contracts/validate.mjs +102 -13
- package/lib/contracts/violation-log.mjs +50 -4
- package/lib/db/migrate.mjs +69 -0
- package/lib/db/migrations/001_orchestration_runs.sql +9 -0
- package/lib/db/migrations/002_queue_provider.sql +50 -0
- package/lib/db/migrations/003_worker_registry.sql +17 -0
- package/lib/db/migrations/004_trace_events.sql +16 -0
- package/lib/db/migrations/005_shared_memory.sql +15 -0
- package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
- package/lib/decisions/enforced-baseline.json +0 -2
- package/lib/decisions/registry.mjs +3 -2
- package/lib/deployment/parity-contract.mjs +1 -1
- package/lib/deployment-mode.mjs +78 -2
- package/lib/diagram-export.mjs +10 -1
- package/lib/distill.mjs +5 -2
- package/lib/docs-verify.mjs +2 -1
- package/lib/doctor/cli.mjs +1 -0
- package/lib/doctor/command-on-path.mjs +24 -0
- package/lib/doctor/diagnosis.mjs +109 -0
- package/lib/doctor/embedding-health.mjs +50 -0
- package/lib/doctor/engine-health.mjs +93 -0
- package/lib/doctor/graph-validate.mjs +47 -0
- package/lib/doctor/index.mjs +23 -4
- package/lib/doctor/sidecar-providers.mjs +56 -0
- package/lib/doctor/watchers/consistency.mjs +93 -1
- package/lib/doctor/watchers/cx-budget.mjs +1 -1
- package/lib/doctor/watchers/graph-staleness.mjs +1 -1
- package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
- package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
- package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
- package/lib/doctor/watchers/provider-breaker.mjs +78 -0
- package/lib/document-export.mjs +52 -27
- package/lib/document-extract/docling-client.mjs +9 -5
- package/lib/document-extract/docling-sidecar.py +30 -0
- package/lib/document-extract.mjs +1 -1
- package/lib/document-ingest.mjs +9 -0
- package/lib/embed/approval-queue.mjs +184 -88
- package/lib/embed/authority-guard.mjs +65 -2
- package/lib/embed/capability-jobs.mjs +365 -0
- package/lib/embed/capability-lifecycle.mjs +219 -0
- package/lib/embed/capability-loader.mjs +303 -0
- package/lib/embed/capability-runtime.mjs +58 -0
- package/lib/embed/cli.mjs +185 -8
- package/lib/embed/config.mjs +4 -0
- package/lib/embed/daemon.mjs +125 -6
- package/lib/embed/demand-fetch.mjs +190 -54
- package/lib/embed/inbox.mjs +12 -10
- package/lib/embed/presets/ops-triage.mjs +236 -0
- package/lib/embed/presets/pm-feedback.mjs +341 -0
- package/lib/embed/presets/tpm.mjs +401 -0
- package/lib/embed/providers/jira.mjs +72 -1
- package/lib/embed/providers/registry.mjs +140 -33
- package/lib/embed/worker.mjs +5 -0
- package/lib/embedded-contract/capability.mjs +4 -0
- package/lib/embedded-contract/execution.mjs +1 -1
- package/lib/embedded-contract/model-resolve.mjs +53 -3
- package/lib/embedded-contract/workflow-defs.mjs +48 -73
- package/lib/embedded-contract/workflow-invoke.mjs +144 -4
- package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
- package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
- package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
- package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
- package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
- package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
- package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
- package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
- package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
- package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
- package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
- package/lib/env-config.mjs +60 -11
- package/lib/export-validate.mjs +34 -2
- package/lib/extensions/index.mjs +27 -0
- package/lib/extensions/loader.mjs +120 -0
- package/lib/extensions/manifest-schema.mjs +141 -0
- package/lib/extensions/manifests/anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
- package/lib/extensions/manifests/directory.manifest.json +12 -0
- package/lib/extensions/manifests/docling.manifest.json +37 -0
- package/lib/extensions/manifests/echo.manifest.json +8 -0
- package/lib/extensions/manifests/feedback.manifest.json +12 -0
- package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
- package/lib/extensions/manifests/github.manifest.json +52 -0
- package/lib/extensions/manifests/linear.manifest.json +50 -0
- package/lib/extensions/manifests/local.manifest.json +12 -0
- package/lib/extensions/manifests/ollama.manifest.json +12 -0
- package/lib/extensions/manifests/openai.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter.manifest.json +12 -0
- package/lib/extensions/manifests/postgres.manifest.json +12 -0
- package/lib/extensions/manifests/salesforce.manifest.json +12 -0
- package/lib/extensions/manifests/slack.manifest.json +58 -0
- package/lib/extensions/manifests/whisper.manifest.json +36 -0
- package/lib/extensions/validate.mjs +175 -0
- package/lib/features.mjs +13 -9
- package/lib/flows/checkpoint.mjs +184 -0
- package/lib/flows/constants.mjs +27 -0
- package/lib/flows/define.mjs +146 -0
- package/lib/flows/engine.mjs +249 -0
- package/lib/flows/errors.mjs +19 -0
- package/lib/flows/index.mjs +27 -0
- package/lib/flows/joins.mjs +18 -0
- package/lib/flows/schema.mjs +90 -0
- package/lib/flows/state.mjs +31 -0
- package/lib/frameworks/loader.mjs +99 -0
- package/lib/frameworks/schema.mjs +157 -0
- package/lib/graph/build-from-corpus.mjs +67 -0
- package/lib/graph/build-from-embed.mjs +82 -0
- package/lib/graph/build-from-registry.mjs +164 -3
- package/lib/graph/cli.mjs +456 -12
- package/lib/graph/gap-queries.mjs +156 -0
- package/lib/graph/gaps.mjs +41 -0
- package/lib/graph/impacted.mjs +129 -0
- package/lib/graph/runtime-evidence.mjs +177 -0
- package/lib/graph/security-coverage.mjs +113 -0
- package/lib/graph/staleness.mjs +241 -10
- package/lib/graph/store.mjs +24 -7
- package/lib/graph/validate.mjs +161 -0
- package/lib/headhunt.mjs +9 -8
- package/lib/health-check.mjs +15 -7
- package/lib/hook-health.mjs +1 -1
- package/lib/hooks/agent-tracker.mjs +10 -4
- package/lib/hooks/edit-guard.mjs +3 -3
- package/lib/hooks/graph-impact-advisory.mjs +2 -2
- package/lib/hooks/guard-bash.mjs +41 -15
- package/lib/hooks/mcp-health-check.mjs +11 -4
- package/lib/hooks/model-fallback.mjs +50 -6
- package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
- package/lib/hooks/pre-compact.mjs +181 -173
- package/lib/hooks/rule-verifier.mjs +2 -1
- package/lib/hooks/session-reflect.mjs +2 -1
- package/lib/hooks/session-start.mjs +3 -3
- package/lib/host/readiness.mjs +109 -0
- package/lib/host-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +29 -8
- package/lib/identity.mjs +92 -0
- package/lib/ingest/degraded-extract.mjs +96 -0
- package/lib/ingest/docling-remote.mjs +2 -1
- package/lib/ingest/provider-extract.mjs +7 -19
- package/lib/ingest/sidecar-providers.mjs +196 -0
- package/lib/ingest-tooling.mjs +11 -5
- package/lib/init-unified.mjs +55 -38
- package/lib/init-update.mjs +2 -1
- package/lib/install/legacy-global-cleanup.mjs +25 -0
- package/lib/install/stage-project.mjs +41 -4
- package/lib/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- package/lib/intake/prepare.mjs +2 -0
- package/lib/intake/queue-registry.mjs +50 -0
- package/lib/intake/queue.mjs +133 -17
- package/lib/intake/session-prelude.mjs +57 -8
- package/lib/integrations/intake-integrations.mjs +61 -111
- package/lib/intent-classifier.mjs +43 -5
- package/lib/libreoffice-export.mjs +8 -8
- package/lib/logging/rotate.mjs +5 -4
- package/lib/mcp/broker.mjs +332 -17
- package/lib/mcp/denied-store.mjs +95 -0
- package/lib/mcp/destructive-approval.mjs +57 -0
- package/lib/mcp/destructive-gate.mjs +30 -0
- package/lib/mcp/dispatch-envelope.mjs +199 -0
- package/lib/mcp/memory-bridge.mjs +2 -1
- package/lib/mcp/server.mjs +185 -1268
- package/lib/mcp/tool-definitions-memory.mjs +376 -0
- package/lib/mcp/tool-definitions-project.mjs +271 -0
- package/lib/mcp/tool-definitions-skills.mjs +311 -0
- package/lib/mcp/tool-definitions-workflow.mjs +398 -0
- package/lib/mcp/tool-definitions.mjs +25 -0
- package/lib/mcp/tool-rate-limit.mjs +47 -0
- package/lib/mcp/tool-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +95 -0
- package/lib/mcp/tool-surface-parity.mjs +60 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +197 -19
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- package/lib/mcp/tools/project.mjs +25 -8
- package/lib/mcp/tools/provider-write.mjs +187 -0
- package/lib/mcp/tools/scope.mjs +0 -3
- package/lib/mcp/tools/skills.mjs +1 -1
- package/lib/mcp/tools/storage.mjs +2 -2
- package/lib/mcp/tools/web-search-governance.mjs +96 -0
- package/lib/mcp/tools/web-search.mjs +5 -76
- package/lib/mcp/transport/auth.mjs +238 -0
- package/lib/mcp/transport/http.mjs +136 -0
- package/lib/mcp/transport/mode.mjs +31 -0
- package/lib/mcp/transport/stdio.mjs +22 -0
- package/lib/mcp-catalog.json +4 -4
- package/lib/mcp-manager.mjs +34 -23
- package/lib/mcp-platform-config.mjs +53 -20
- package/lib/mode-capabilities.mjs +109 -0
- package/lib/model-router.mjs +42 -9
- package/lib/models/catalog.mjs +40 -1
- package/lib/net-guard.mjs +247 -0
- package/lib/observation-store.mjs +6 -2
- package/lib/ollama/provision-context.mjs +2 -1
- package/lib/opencode-config.mjs +22 -3
- package/lib/opencode-runtime-plugin.mjs +120 -2
- package/lib/oracle/actions.mjs +204 -10
- package/lib/oracle/cli.mjs +24 -3
- package/lib/oracle/daemon-entry.mjs +6 -0
- package/lib/oracle/dispatch.mjs +2 -1
- package/lib/oracle/execute.mjs +2 -2
- package/lib/oracle/gaps.mjs +3 -3
- package/lib/oracle/heartbeat.mjs +53 -0
- package/lib/oracle/read-model.mjs +69 -13
- package/lib/oracle/reconcile.mjs +3 -46
- package/lib/oracle/routing.mjs +37 -31
- package/lib/oracle/synthesize.mjs +1 -1
- package/lib/orchestration/classification.mjs +434 -0
- package/lib/orchestration/delegation-flow.mjs +132 -0
- package/lib/orchestration/flow-selection.mjs +418 -0
- package/lib/orchestration/gates.mjs +244 -0
- package/lib/orchestration/host-sampling.mjs +121 -0
- package/lib/orchestration/policy-constants.mjs +48 -0
- package/lib/orchestration/provider-outcome.mjs +197 -0
- package/lib/orchestration/readiness.mjs +114 -13
- package/lib/orchestration/run-store-postgres.mjs +32 -26
- package/lib/orchestration/run-store-sqlite.mjs +8 -14
- package/lib/orchestration/run-store.mjs +25 -12
- package/lib/orchestration/runtime.mjs +508 -62
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +60 -0
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +763 -80
- package/lib/orchestration-policy.mjs +67 -1111
- package/lib/output-quality.mjs +61 -2
- package/lib/packs/cli.mjs +144 -0
- package/lib/packs/core-pack.mjs +112 -0
- package/lib/packs/enablement.mjs +187 -0
- package/lib/packs/index.mjs +23 -0
- package/lib/packs/loader.mjs +176 -0
- package/lib/packs/manifest-schema.mjs +58 -0
- package/lib/packs/prompts.mjs +120 -0
- package/lib/packs/validate.mjs +208 -0
- package/lib/path-policy.mjs +56 -0
- package/lib/plugin-registry.mjs +4 -5
- package/lib/policy/audit-gate.mjs +42 -0
- package/lib/policy/consumption-budget.mjs +149 -0
- package/lib/policy/engine.mjs +81 -6
- package/lib/policy/role-authority.mjs +89 -0
- package/lib/prompt-composer.js +19 -3
- package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
- package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
- package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
- package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
- package/lib/providers/contract/adapters/github/index.mjs +38 -3
- package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
- package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
- package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
- package/lib/providers/contract/adapters/jira/manifest.json +17 -0
- package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
- package/lib/providers/contract.mjs +207 -0
- package/lib/providers/credential-bootstrap.mjs +7 -4
- package/lib/providers/credential-sources.mjs +14 -2
- package/lib/providers/directory/index.mjs +261 -0
- package/lib/providers/feedback/index.mjs +392 -0
- package/lib/providers/filter-audit.mjs +68 -0
- package/lib/providers/filter-schema.mjs +54 -0
- package/lib/providers/github/index.mjs +31 -0
- package/lib/providers/instance-config.mjs +215 -0
- package/lib/providers/op-locate.mjs +96 -0
- package/lib/providers/op-run.mjs +64 -9
- package/lib/providers/registry.mjs +26 -3
- package/lib/providers/secret-audit-wiring.mjs +41 -18
- package/lib/providers/secret-resolver.mjs +263 -31
- package/lib/publish-template.mjs +6 -3
- package/lib/publish-tooling.mjs +4 -0
- package/lib/publish.mjs +32 -4
- package/lib/queue/pg-queue.mjs +395 -0
- package/lib/reflect.mjs +2 -1
- package/lib/registry/assemble.mjs +28 -2
- package/lib/registry/catalog.mjs +6 -0
- package/lib/registry/consolidation.mjs +8 -1
- package/lib/registry/custom-scaffold.mjs +200 -0
- package/lib/registry/custom-schema.mjs +137 -0
- package/lib/registry/loader.mjs +15 -4
- package/lib/registry/manifests/format-engines.default.json +15 -0
- package/lib/registry/manifests/surface-map.default.json +46 -0
- package/lib/registry/retired-paths.mjs +1 -0
- package/lib/registry/surface-map.mjs +100 -50
- package/lib/registry/validate.mjs +3 -3
- package/lib/render-visual-check.mjs +240 -0
- package/lib/resources/budget.mjs +40 -17
- package/lib/roles/flavor-bindings.mjs +36 -14
- package/lib/roles/gateway.mjs +15 -8
- package/lib/roots.mjs +107 -0
- package/lib/runtime/uv-bootstrap.mjs +32 -6
- package/lib/runtime/whisper-bootstrap.mjs +11 -3
- package/lib/runtime-env.mjs +5 -2
- package/lib/sandbox.mjs +1 -1
- package/lib/scheduler/index.mjs +8 -20
- package/lib/schema-infer.mjs +31 -2
- package/lib/scopes/lifecycle.mjs +29 -25
- package/lib/scopes/loader.mjs +49 -12
- package/lib/scopes/teams.mjs +1 -1
- package/lib/security/ingest-boundary.mjs +80 -0
- package/lib/security/recall-wrapper.mjs +99 -0
- package/lib/security/trust.mjs +132 -0
- package/lib/service-manager.mjs +93 -24
- package/lib/setup.mjs +41 -23
- package/lib/skills-apply.mjs +4 -2
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/state-root.mjs +147 -0
- package/lib/status.mjs +597 -6
- package/lib/storage/admin.mjs +84 -7
- package/lib/storage/backend-registry.mjs +73 -0
- package/lib/storage/backend.mjs +63 -9
- package/lib/storage/embeddings-openai.mjs +12 -2
- package/lib/storage/hybrid-query.mjs +11 -3
- package/lib/storage/retrieval-hardening.mjs +384 -0
- package/lib/storage/shared-memory.mjs +158 -0
- package/lib/storage/vector-client.mjs +69 -2
- package/lib/team/health.mjs +72 -0
- package/lib/telemetry/backends/local.mjs +9 -3
- package/lib/telemetry/client.mjs +3 -7
- package/lib/template-registry.mjs +10 -12
- package/lib/tenant/context.mjs +82 -0
- package/lib/tenant/isolation.mjs +129 -0
- package/lib/test-corpus-inventory.mjs +103 -2
- package/lib/uninstall/uninstall.mjs +78 -12
- package/lib/validator.mjs +4 -6
- package/lib/worker/entrypoint.mjs +2 -1
- package/lib/worker/run.mjs +2 -2
- package/lib/worker/trace.mjs +5 -11
- package/lib/workflow-state.mjs +52 -8
- package/lib/workflows/liveness.mjs +203 -0
- package/lib/workflows/loader.mjs +177 -0
- package/lib/workflows/manifest-schema.mjs +71 -0
- package/lib/workflows/surface-parity.mjs +118 -0
- package/lib/workflows/validate.mjs +127 -0
- package/lib/writes/control-plane.mjs +140 -0
- package/lib/writes/envelope.mjs +206 -0
- package/lib/writes/sent-log.mjs +86 -0
- package/lib/writes/write-intent.mjs +109 -0
- package/package.json +20 -7
- package/personas/construct.md +12 -3
- package/registry/agent-manifest.json +117 -0
- package/registry/capabilities.json +1987 -0
- package/rules/common/comments.md +1 -0
- package/schemas/brand-voice.schema.json +24 -0
- package/schemas/capability-registry.schema.json +72 -0
- package/schemas/certification-run.schema.json +130 -0
- package/schemas/demo-recording.schema.json +46 -0
- package/schemas/eval-dataset.schema.json +79 -0
- package/schemas/execution-capability-profile.schema.json +46 -0
- package/schemas/execution-policy.schema.json +114 -0
- package/schemas/improvement-proposal.schema.json +65 -0
- package/schemas/mcp-tool-output.schema.json +61 -0
- package/schemas/platform-capabilities.schema.json +83 -0
- package/schemas/project-config.schema.json +215 -0
- package/schemas/project-demo.schema.json +60 -0
- package/schemas/provider-behavior-matrix.schema.json +91 -0
- package/schemas/scope.schema.json +197 -0
- package/schemas/specialist-trace.schema.json +107 -0
- package/schemas/team.schema.json +99 -0
- package/schemas/unified-registry.schema.json +546 -0
- package/scripts/sync-specialists.mjs +336 -106
- package/skills/docs/adr-workflow.md +1 -1
- package/skills/docs/codebase-research-workflow.md +3 -3
- package/skills/docs/prd-workflow.md +5 -6
- package/skills/docs/runbook-workflow.md +2 -2
- package/skills/docs/user-research-workflow.md +3 -3
- package/skills/operating/fleet-health-routing.md +77 -0
- package/skills/roles/ai-engineer.md +1 -1
- package/skills/roles/architect.md +0 -1
- package/skills/roles/business-strategist.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/data-engineer.md +1 -1
- package/skills/roles/data-engineer.pipeline.md +1 -1
- package/skills/roles/data-engineer.vector-retrieval.md +1 -2
- package/skills/roles/data-engineer.warehouse.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +0 -1
- package/skills/roles/devil-advocate.md +1 -1
- package/skills/roles/docs-keeper.md +1 -1
- package/skills/roles/evaluator.md +1 -1
- package/skills/roles/explorer.md +1 -1
- package/skills/roles/platform-engineer.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -2
- package/skills/roles/qa.api-contract.md +0 -1
- package/skills/roles/qa.data-pipeline.md +0 -1
- package/skills/roles/qa.md +0 -1
- package/skills/roles/qa.web-ui.md +0 -1
- package/skills/roles/release-manager.md +1 -1
- package/skills/roles/researcher.md +0 -2
- package/skills/roles/reviewer.md +0 -3
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +0 -1
- package/skills/roles/security.privacy.md +0 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/sre.md +1 -1
- package/skills/roles/test-automation.md +1 -1
- package/skills/roles/trace-reviewer.md +1 -1
- package/skills/roles/ux-researcher.md +1 -1
- package/specialists/artifact-manifest.json +36 -36
- package/specialists/org/contracts/accessibility-to-qa.json +11 -3
- package/specialists/org/contracts/any-to-business-strategist.json +19 -7
- package/specialists/org/contracts/any-to-debugger.json +7 -1
- package/specialists/org/contracts/any-to-designer.json +7 -1
- package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
- package/specialists/org/contracts/any-to-explorer.json +13 -4
- package/specialists/org/contracts/any-to-sre-incident.json +6 -2
- package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
- package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
- package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
- package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
- package/specialists/org/contracts/architect-to-engineer.json +20 -4
- package/specialists/org/contracts/architect-to-evaluator.json +15 -5
- package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
- package/specialists/org/contracts/architect-to-operations.json +17 -4
- package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
- package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
- package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
- package/specialists/org/contracts/engineer-to-qa.json +20 -4
- package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
- package/specialists/org/contracts/explorer-to-engineer.json +11 -3
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
- package/specialists/org/contracts/operations-to-user.json +53 -0
- package/specialists/org/contracts/operations-triage-output.json +53 -0
- package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
- package/specialists/org/contracts/product-manager-to-architect.json +30 -10
- package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
- package/specialists/org/contracts/qa-to-release-manager.json +11 -3
- package/specialists/org/contracts/researcher-to-architect.json +10 -2
- package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
- package/specialists/org/contracts/reviewer-to-security.json +17 -3
- package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
- package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
- package/specialists/org/contracts/user-to-construct.json +11 -4
- package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
- package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
- package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
- package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
- package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
- package/specialists/org/groups/engineering-group.json +2 -6
- package/specialists/org/groups/governance-group.json +2 -3
- package/specialists/org/groups/operations-group.json +5 -8
- package/specialists/org/groups/product-group.json +4 -8
- package/specialists/org/groups/quality-group.json +3 -7
- package/specialists/org/groups/strategy-group.json +6 -9
- package/specialists/org/models.json.example +8 -0
- package/specialists/org/scopes/creative.json +6 -1
- package/specialists/org/scopes/operations.json +7 -1
- package/specialists/org/scopes/research.json +6 -1
- package/specialists/org/scopes/rnd.json +7 -1
- package/specialists/org/specialists/cx-architect.json +27 -8
- package/specialists/org/specialists/cx-designer.json +22 -8
- package/specialists/org/specialists/cx-engineer.json +71 -7
- package/specialists/org/specialists/cx-operations.json +89 -15
- package/specialists/org/specialists/cx-orchestrator.json +16 -4
- package/specialists/org/specialists/cx-product-manager.json +28 -9
- package/specialists/org/specialists/cx-qa.json +16 -6
- package/specialists/org/specialists/cx-researcher.json +41 -7
- package/specialists/org/specialists/cx-reviewer.json +61 -11
- package/specialists/org/specialists/cx-security.json +30 -10
- package/specialists/org/teams/design-team.json +3 -4
- package/specialists/org/teams/engineering-team.json +3 -10
- package/specialists/org/teams/governance-team.json +3 -5
- package/specialists/org/teams/operations-team.json +6 -12
- package/specialists/org/teams/product-management-team.json +2 -3
- package/specialists/org/teams/quality-team.json +4 -12
- package/specialists/org/teams/research-team.json +3 -3
- package/specialists/org/teams/strategy-team.json +7 -14
- package/specialists/prompts/cx-architect.md +20 -1
- package/specialists/prompts/cx-data-analyst.md +1 -1
- package/specialists/prompts/cx-designer.md +12 -4
- package/specialists/prompts/cx-engineer.md +15 -1
- package/specialists/prompts/cx-operations.md +14 -2
- package/specialists/prompts/cx-orchestrator.md +9 -5
- package/specialists/prompts/cx-product-manager.md +5 -1
- package/specialists/prompts/cx-qa.md +6 -2
- package/specialists/prompts/cx-researcher.md +27 -31
- package/specialists/prompts/cx-reviewer.md +19 -1
- package/specialists/prompts/cx-security.md +5 -1
- package/templates/distribution/construct-brand.typ +123 -59
- package/templates/distribution/construct-decision.typ +6 -3
- package/templates/distribution/construct-pdf.typ +11 -4
- package/templates/distribution/construct-prd.typ +6 -3
- package/templates/distribution/construct-research.typ +7 -4
- package/vendor/pandoc-ext/README.md +3 -0
- package/vendor/pandoc-ext/diagram.lua +687 -0
- package/lib/providers/contract/adapters/git/index.mjs +0 -115
- package/lib/providers/contract/adapters/slack/index.mjs +0 -175
- package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
- package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
- package/specialists/org/contracts/designer-to-accessibility.json +0 -36
- package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
- package/specialists/org/contracts/qa-to-test-automation.json +0 -42
- package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
- package/specialists/org/contracts/sre-to-release-manager.json +0 -36
- package/specialists/org/specialists/cx-accessibility.json +0 -69
- package/specialists/org/specialists/cx-ai-engineer.json +0 -75
- package/specialists/org/specialists/cx-business-strategist.json +0 -72
- package/specialists/org/specialists/cx-data-engineer.json +0 -71
- package/specialists/org/specialists/cx-devil-advocate.json +0 -71
- package/specialists/org/specialists/cx-docs-keeper.json +0 -82
- package/specialists/org/specialists/cx-evaluator.json +0 -69
- package/specialists/org/specialists/cx-explorer.json +0 -69
- package/specialists/org/specialists/cx-legal-compliance.json +0 -74
- package/specialists/org/specialists/cx-oracle.json +0 -46
- package/specialists/org/specialists/cx-platform-engineer.json +0 -80
- package/specialists/org/specialists/cx-rd-lead.json +0 -73
- package/specialists/org/specialists/cx-release-manager.json +0 -77
- package/specialists/org/specialists/cx-sre.json +0 -94
- package/specialists/org/specialists/cx-test-automation.json +0 -69
- package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
- package/specialists/org/specialists/cx-ux-researcher.json +0 -68
- package/specialists/org/teams/accessibility-team.json +0 -39
- package/specialists/org/teams/ux-research-team.json +0 -39
- package/specialists/prompts/cx-accessibility.md +0 -55
- package/specialists/prompts/cx-ai-engineer.md +0 -124
- package/specialists/prompts/cx-business-strategist.md +0 -58
- package/specialists/prompts/cx-data-engineer.md +0 -55
- package/specialists/prompts/cx-devil-advocate.md +0 -59
- package/specialists/prompts/cx-docs-keeper.md +0 -164
- package/specialists/prompts/cx-evaluator.md +0 -49
- package/specialists/prompts/cx-explorer.md +0 -71
- package/specialists/prompts/cx-legal-compliance.md +0 -58
- package/specialists/prompts/cx-oracle.md +0 -98
- package/specialists/prompts/cx-platform-engineer.md +0 -97
- package/specialists/prompts/cx-rd-lead.md +0 -59
- package/specialists/prompts/cx-release-manager.md +0 -54
- package/specialists/prompts/cx-sre.md +0 -111
- package/specialists/prompts/cx-test-automation.md +0 -55
- package/specialists/prompts/cx-trace-reviewer.md +0 -101
- package/specialists/prompts/cx-ux-researcher.md +0 -53
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/providers/directory/index.mjs — Local directory data-source provider.
|
|
3
|
+
*
|
|
4
|
+
* Provides read/search capabilities over a configured local directory with
|
|
5
|
+
* include/exclude glob patterns and file size limits. Security: all paths
|
|
6
|
+
* resolve and remain within the configured root; symlink escape and
|
|
7
|
+
* traversal (../) are rejected.
|
|
8
|
+
*
|
|
9
|
+
* Capabilities: read, search.
|
|
10
|
+
*
|
|
11
|
+
* Config (per call):
|
|
12
|
+
* - root: absolute or relative path to the directory root (required)
|
|
13
|
+
* - include: array of minimatch glob patterns (optional, defaults to ["**\/*"])
|
|
14
|
+
* - exclude: array of minimatch glob patterns to filter out (optional)
|
|
15
|
+
* - maxFileKB: integer max file size in KB (default 1024, max 10240)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { existsSync, statSync, readdirSync, readFileSync } from 'node:fs';
|
|
19
|
+
import { resolve, relative, isAbsolute } from 'node:path';
|
|
20
|
+
import { minimatch } from 'minimatch';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Verify that path is within root and not a symlink escape.
|
|
24
|
+
* Returns { ok: true } or { ok: false, reason: '...' }.
|
|
25
|
+
*/
|
|
26
|
+
function validatePathSecurity(root, checkPath) {
|
|
27
|
+
try {
|
|
28
|
+
const realRoot = statSync(root).ino;
|
|
29
|
+
const stats = statSync(checkPath);
|
|
30
|
+
|
|
31
|
+
if (stats.isSymbolicLink()) {
|
|
32
|
+
return { ok: false, reason: 'symlink not allowed' };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const realPath = statSync(checkPath).ino;
|
|
36
|
+
if (realPath !== realRoot) {
|
|
37
|
+
const rel = relative(root, checkPath);
|
|
38
|
+
if (rel.startsWith('..')) {
|
|
39
|
+
return { ok: false, reason: 'path escape detected' };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { ok: true };
|
|
44
|
+
} catch (err) {
|
|
45
|
+
return { ok: false, reason: err.message };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Resolve and validate a path within root. Returns { ok, path } or
|
|
51
|
+
* { ok: false, reason }.
|
|
52
|
+
*/
|
|
53
|
+
function resolvePath(root, candidate) {
|
|
54
|
+
const rootPath = isAbsolute(root) ? root : resolve(process.cwd(), root);
|
|
55
|
+
|
|
56
|
+
if (!existsSync(rootPath)) {
|
|
57
|
+
return { ok: false, reason: `root does not exist: ${rootPath}` };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const stat = statSync(rootPath);
|
|
61
|
+
if (!stat.isDirectory()) {
|
|
62
|
+
return { ok: false, reason: `root is not a directory: ${rootPath}` };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const resolved = isAbsolute(candidate)
|
|
66
|
+
? candidate
|
|
67
|
+
: resolve(rootPath, candidate);
|
|
68
|
+
|
|
69
|
+
const validation = validatePathSecurity(rootPath, resolved);
|
|
70
|
+
if (!validation.ok) {
|
|
71
|
+
return { ok: false, reason: validation.reason };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { ok: true, path: resolved, root: rootPath };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Walk directory recursively, matching include/exclude globs.
|
|
79
|
+
*/
|
|
80
|
+
function walkDir(dirPath, { include = ['**/*'], exclude = [] }) {
|
|
81
|
+
const results = [];
|
|
82
|
+
const stat = statSync(dirPath);
|
|
83
|
+
if (!stat.isDirectory()) return results;
|
|
84
|
+
|
|
85
|
+
function walk(dir, prefix = '') {
|
|
86
|
+
let entries;
|
|
87
|
+
try {
|
|
88
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
89
|
+
} catch {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const entry of entries) {
|
|
94
|
+
const fullPath = resolve(dir, entry.name);
|
|
95
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
96
|
+
|
|
97
|
+
if (entry.isDirectory()) {
|
|
98
|
+
walk(fullPath, relativePath);
|
|
99
|
+
} else if (entry.isFile()) {
|
|
100
|
+
const matches = include.some((pat) => minimatch(relativePath, pat, { matchBase: true }));
|
|
101
|
+
const excludes = exclude.some((pat) => minimatch(relativePath, pat, { matchBase: true }));
|
|
102
|
+
|
|
103
|
+
if (matches && !excludes) {
|
|
104
|
+
results.push({ path: fullPath, relativePath, name: entry.name });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
walk(dirPath);
|
|
111
|
+
return results;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Filter files by size constraint.
|
|
116
|
+
*/
|
|
117
|
+
function filterBySize(items, maxFileSizeKB) {
|
|
118
|
+
const maxBytes = maxFileSizeKB * 1024;
|
|
119
|
+
return items.filter((item) => {
|
|
120
|
+
try {
|
|
121
|
+
const stat = statSync(item.path);
|
|
122
|
+
return stat.size <= maxBytes;
|
|
123
|
+
} catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Read all matching files from the directory.
|
|
131
|
+
*/
|
|
132
|
+
async function readAllFiles(config) {
|
|
133
|
+
const root = config?.root;
|
|
134
|
+
if (!root) throw new Error('directory.read: config.root required');
|
|
135
|
+
|
|
136
|
+
const { ok, path, root: rootPath } = resolvePath(root, '.');
|
|
137
|
+
if (!ok) throw new Error(`directory.read: ${path}`);
|
|
138
|
+
|
|
139
|
+
const include = Array.isArray(config.include) ? config.include : ['**/*'];
|
|
140
|
+
const exclude = Array.isArray(config.exclude) ? config.exclude : [];
|
|
141
|
+
const maxKB = Math.min(config?.maxFileKB || 1024, 10240);
|
|
142
|
+
|
|
143
|
+
const items = walkDir(rootPath, { include, exclude });
|
|
144
|
+
const filtered = filterBySize(items, maxKB);
|
|
145
|
+
|
|
146
|
+
return filtered.map((item) => ({
|
|
147
|
+
path: item.relativePath,
|
|
148
|
+
name: item.name,
|
|
149
|
+
mtime: statSync(item.path).mtimeMs,
|
|
150
|
+
size: statSync(item.path).size,
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Search for a substring within matching files.
|
|
156
|
+
*/
|
|
157
|
+
async function searchFiles(config, query) {
|
|
158
|
+
if (!query || typeof query !== 'string') {
|
|
159
|
+
throw new Error('directory.search: query required (substring or glob pattern)');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const root = config?.root;
|
|
163
|
+
if (!root) throw new Error('directory.search: config.root required');
|
|
164
|
+
|
|
165
|
+
const { ok, path, root: rootPath } = resolvePath(root, '.');
|
|
166
|
+
if (!ok) throw new Error(`directory.search: ${path}`);
|
|
167
|
+
|
|
168
|
+
const include = Array.isArray(config.include) ? config.include : ['**/*'];
|
|
169
|
+
const exclude = Array.isArray(config.exclude) ? config.exclude : [];
|
|
170
|
+
const maxKB = Math.min(config?.maxFileKB || 1024, 10240);
|
|
171
|
+
|
|
172
|
+
const items = walkDir(rootPath, { include, exclude });
|
|
173
|
+
const filtered = filterBySize(items, maxKB);
|
|
174
|
+
|
|
175
|
+
const results = [];
|
|
176
|
+
|
|
177
|
+
for (const item of filtered) {
|
|
178
|
+
try {
|
|
179
|
+
const content = readFileSync(item.path, 'utf8');
|
|
180
|
+
if (content.includes(query)) {
|
|
181
|
+
results.push({
|
|
182
|
+
path: item.relativePath,
|
|
183
|
+
name: item.name,
|
|
184
|
+
mtime: statSync(item.path).mtimeMs,
|
|
185
|
+
size: statSync(item.path).size,
|
|
186
|
+
preview: content.slice(0, 200),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
// Skip files we cannot read
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return results;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function create({ env = process.env } = {}) {
|
|
198
|
+
return {
|
|
199
|
+
meta: {
|
|
200
|
+
id: 'directory',
|
|
201
|
+
displayName: 'Local Directory',
|
|
202
|
+
capabilities: ['read', 'search'],
|
|
203
|
+
description: 'Read and search files in a local directory.',
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
configSchema: {
|
|
207
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
208
|
+
type: 'object',
|
|
209
|
+
properties: {
|
|
210
|
+
root: {
|
|
211
|
+
type: 'string',
|
|
212
|
+
description: 'Path to the root directory (absolute or relative to cwd)',
|
|
213
|
+
},
|
|
214
|
+
include: {
|
|
215
|
+
type: 'array',
|
|
216
|
+
items: { type: 'string' },
|
|
217
|
+
description: 'Glob patterns to include (default: ["**/*"])',
|
|
218
|
+
default: ['**/*'],
|
|
219
|
+
},
|
|
220
|
+
exclude: {
|
|
221
|
+
type: 'array',
|
|
222
|
+
items: { type: 'string' },
|
|
223
|
+
description: 'Glob patterns to exclude (default: [])',
|
|
224
|
+
default: [],
|
|
225
|
+
},
|
|
226
|
+
maxFileKB: {
|
|
227
|
+
type: 'integer',
|
|
228
|
+
minimum: 1,
|
|
229
|
+
maximum: 10240,
|
|
230
|
+
default: 1024,
|
|
231
|
+
description: 'Maximum file size in KB (default 1024)',
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
required: ['root'],
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
async health(config) {
|
|
238
|
+
const root = config?.root;
|
|
239
|
+
if (!root) {
|
|
240
|
+
return { ok: false, detail: 'config.root not set' };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const { ok, reason, root: rootPath } = resolvePath(root, '.');
|
|
244
|
+
if (!ok) {
|
|
245
|
+
return { ok: false, detail: reason };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const entries = readdirSync(rootPath);
|
|
250
|
+
return { ok: true, detail: `directory readable (${entries.length} entries)` };
|
|
251
|
+
} catch (err) {
|
|
252
|
+
return { ok: false, detail: err.message };
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
read: readAllFiles,
|
|
257
|
+
search: searchFiles,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export default create;
|
|
@@ -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;
|