@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,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/presets/ops-triage.mjs — deterministic triage analysis for the
|
|
3
|
+
* `operations` embed preset (triage mode; see the embed-capability manifest
|
|
4
|
+
* schema decision record).
|
|
5
|
+
*
|
|
6
|
+
* Pure deterministic `reasoningExecutor(plan, ctx)` that the embed capability
|
|
7
|
+
* tick accepts: reads operations specialist's bound provider snapshot slice
|
|
8
|
+
* (Jira + Confluence/Directory sections, binding-scoped and filter-narrowed
|
|
9
|
+
* by caller) and applies ops triage framework. Produces output packet validating
|
|
10
|
+
* against `operations-triage` output contract plus writeIntents (Jira link +
|
|
11
|
+
* comment proposals) for caller to enqueue for approval.
|
|
12
|
+
*
|
|
13
|
+
* Every load-bearing claim carries provenance: a source id (Jira issue key,
|
|
14
|
+
* Confluence page id) reader can re-verify. Finding with no covering evidence
|
|
15
|
+
* is reported with `[unverified]`, never a fabricated issue key.
|
|
16
|
+
*
|
|
17
|
+
* Analysis moves (mapped to ops triage framework):
|
|
18
|
+
* 1. Inventory issues from bound Jira project.
|
|
19
|
+
* 2. Detect duplicates: similar summaries + same component/type.
|
|
20
|
+
* 3. Check specification completeness: description length, field coverage.
|
|
21
|
+
* 4. Each proposal is typed writeIntent (duplicate-link or needs-info).
|
|
22
|
+
* 5. No write executes without approval.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const JIRA_PROVIDER = 'atlassian-jira';
|
|
26
|
+
const CONFLUENCE_PROVIDER = 'atlassian-confluence';
|
|
27
|
+
const DIRECTORY_PROVIDER = 'directory';
|
|
28
|
+
|
|
29
|
+
const JIRA_LINK_KIND = 'updateItem';
|
|
30
|
+
const JIRA_COMMENT_KIND = 'comment';
|
|
31
|
+
|
|
32
|
+
// Minimum description length to be considered specified.
|
|
33
|
+
const MIN_SPEC_LENGTH = 50;
|
|
34
|
+
|
|
35
|
+
// Similarity threshold for duplicate detection (word-based Jaccard similarity).
|
|
36
|
+
const DUPLICATE_SIMILARITY_THRESHOLD = 0.3;
|
|
37
|
+
|
|
38
|
+
function sectionItems(sections, providerId) {
|
|
39
|
+
const section = (sections || []).find((s) => s.provider === providerId);
|
|
40
|
+
return Array.isArray(section?.items) ? section.items : [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function issueKey(issue) {
|
|
44
|
+
return issue?.key ?? issue?.id ?? 'unknown';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function issueId(issue) {
|
|
48
|
+
return issue?.id ?? issue?.key ?? 'unknown';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Word-based similarity: ratio of common words to total unique words; signals potential duplicates for manual review.
|
|
52
|
+
|
|
53
|
+
function wordSimilarity(text1, text2) {
|
|
54
|
+
const normalize = (t) => String(t ?? '').toLowerCase().split(/\W+/).filter((w) => w.length > 2);
|
|
55
|
+
const words1 = new Set(normalize(text1));
|
|
56
|
+
const words2 = new Set(normalize(text2));
|
|
57
|
+
const union = new Set([...words1, ...words2]);
|
|
58
|
+
const intersection = new Set([...words1].filter((w) => words2.has(w)));
|
|
59
|
+
return union.size === 0 ? 0 : intersection.size / union.size;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Extract properties for triage checks: key, summary, description, type, component, status.
|
|
63
|
+
|
|
64
|
+
export function inventoryNewIssues(issues) {
|
|
65
|
+
if (!Array.isArray(issues)) return [];
|
|
66
|
+
return issues.map((issue) => ({
|
|
67
|
+
key: issueKey(issue),
|
|
68
|
+
id: issueId(issue),
|
|
69
|
+
summary: issue?.summary ?? '[untitled]',
|
|
70
|
+
description: String(issue?.description ?? '').trim(),
|
|
71
|
+
type: issue?.type ?? 'unknown',
|
|
72
|
+
component: issue?.component ?? issue?.components?.[0] ?? 'unknown',
|
|
73
|
+
status: issue?.status ?? 'unknown',
|
|
74
|
+
created: issue?.created ?? issue?.createdDate ?? 'unknown',
|
|
75
|
+
sourceIssue: issue,
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Compare each issue against others for same type + component + high summary similarity.
|
|
80
|
+
|
|
81
|
+
export function findDuplicates(inventoriedIssues) {
|
|
82
|
+
const findings = [];
|
|
83
|
+
const proposals = [];
|
|
84
|
+
|
|
85
|
+
// For each issue, check against all earlier issues (to avoid duplicate pairs).
|
|
86
|
+
for (let i = 0; i < inventoriedIssues.length; i++) {
|
|
87
|
+
const current = inventoriedIssues[i];
|
|
88
|
+
for (let j = 0; j < i; j++) {
|
|
89
|
+
const other = inventoriedIssues[j];
|
|
90
|
+
|
|
91
|
+
// Heuristic: same type + same component + high summary similarity.
|
|
92
|
+
const sameType = current.type === other.type;
|
|
93
|
+
const sameComponent = current.component === other.component;
|
|
94
|
+
const similarity = wordSimilarity(current.summary, other.summary);
|
|
95
|
+
|
|
96
|
+
if (sameType && sameComponent && similarity >= DUPLICATE_SIMILARITY_THRESHOLD) {
|
|
97
|
+
findings.push({
|
|
98
|
+
kind: 'potential-duplicate',
|
|
99
|
+
issue: current.key,
|
|
100
|
+
candidate: other.key,
|
|
101
|
+
similarity: similarity.toFixed(2),
|
|
102
|
+
evidence: {
|
|
103
|
+
issue: current.key,
|
|
104
|
+
candidate: other.key,
|
|
105
|
+
type: current.type,
|
|
106
|
+
component: current.component,
|
|
107
|
+
similarity: similarity.toFixed(2),
|
|
108
|
+
},
|
|
109
|
+
statement: `${current.key} (${current.summary}) appears to be a duplicate of ${other.key} (${other.summary}) — same type/component, similarity ${(similarity * 100).toFixed(0)}% (sources ${current.key}, ${other.key}).`,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Propose a duplicate-link: link the newer issue to the older one.
|
|
113
|
+
proposals.push({
|
|
114
|
+
providerId: JIRA_PROVIDER,
|
|
115
|
+
writeKind: JIRA_LINK_KIND,
|
|
116
|
+
issueKey: current.key,
|
|
117
|
+
duplicateCandidateKey: other.key,
|
|
118
|
+
payload: {
|
|
119
|
+
type: 'link',
|
|
120
|
+
linkType: 'duplicates',
|
|
121
|
+
fromKey: current.key,
|
|
122
|
+
toKey: other.key,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { findings, proposals };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Check specification completeness: description too short or no acceptance criteria/expected outcome.
|
|
133
|
+
|
|
134
|
+
export function findUnderspecified(inventoriedIssues) {
|
|
135
|
+
const findings = [];
|
|
136
|
+
const proposals = [];
|
|
137
|
+
|
|
138
|
+
for (const issue of inventoriedIssues) {
|
|
139
|
+
const reasons = [];
|
|
140
|
+
if (issue.description.length < MIN_SPEC_LENGTH) {
|
|
141
|
+
reasons.push('description too brief');
|
|
142
|
+
}
|
|
143
|
+
if (!issue.description.includes('acceptance') && !issue.description.includes('criteria')) {
|
|
144
|
+
reasons.push('no visible acceptance criteria');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (reasons.length > 0) {
|
|
148
|
+
findings.push({
|
|
149
|
+
kind: 'underspecified',
|
|
150
|
+
issue: issue.key,
|
|
151
|
+
reasons,
|
|
152
|
+
evidence: {
|
|
153
|
+
issue: issue.key,
|
|
154
|
+
descriptionLength: issue.description.length,
|
|
155
|
+
reasons,
|
|
156
|
+
},
|
|
157
|
+
statement: `${issue.key} is under-specified: ${reasons.join(', ')} (source ${issue.key}).`,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Propose a needs-info comment with a template.
|
|
161
|
+
proposals.push({
|
|
162
|
+
providerId: JIRA_PROVIDER,
|
|
163
|
+
writeKind: JIRA_COMMENT_KIND,
|
|
164
|
+
issueKey: issue.key,
|
|
165
|
+
payload: {
|
|
166
|
+
type: 'comment',
|
|
167
|
+
text: 'Hi! This ticket needs a bit more detail to get started. Could you add:\n- A clear problem statement\n- Acceptance criteria or expected outcomes\n- Any known constraints or dependencies\n\nThanks!',
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return { findings, proposals };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Pure deterministic triage analysis: reads sections, produces output packet + write proposals + summary.
|
|
177
|
+
|
|
178
|
+
export function analyzeOpsTriage(sections, { now = Date.now(), generatedAt } = {}) {
|
|
179
|
+
const issues = sectionItems(sections, JIRA_PROVIDER);
|
|
180
|
+
|
|
181
|
+
const inventoried = inventoryNewIssues(issues);
|
|
182
|
+
const duplicates = findDuplicates(inventoried);
|
|
183
|
+
const underspecified = findUnderspecified(inventoried);
|
|
184
|
+
|
|
185
|
+
const allFindings = [...duplicates.findings, ...underspecified.findings];
|
|
186
|
+
const allProposals = [...duplicates.proposals, ...underspecified.proposals];
|
|
187
|
+
const provenance = inventoried.map((i) => i.key);
|
|
188
|
+
|
|
189
|
+
const analysis = {
|
|
190
|
+
generatedAt: generatedAt ?? new Date(now).toISOString(),
|
|
191
|
+
inventoried,
|
|
192
|
+
duplicates: duplicates.findings,
|
|
193
|
+
underspecified: underspecified.findings,
|
|
194
|
+
allFindings,
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const summary = [
|
|
198
|
+
`Triaged ${inventoried.length} issues.`,
|
|
199
|
+
`Found ${duplicates.findings.length} potential duplicates.`,
|
|
200
|
+
`Found ${underspecified.findings.length} under-specified tickets.`,
|
|
201
|
+
].join(' ');
|
|
202
|
+
|
|
203
|
+
// Each contract field is a section wrapper ({ count, findings/items })
|
|
204
|
+
// rather than a bare array so a legitimately empty section still counts as
|
|
205
|
+
// present under the output contract — an absent section signals a broken
|
|
206
|
+
// producer, an empty one signals "checked, nothing found".
|
|
207
|
+
const outputPacket = {
|
|
208
|
+
inventoried: { count: inventoried.length, items: inventoried },
|
|
209
|
+
duplicates: { count: duplicates.findings.length, findings: duplicates.findings },
|
|
210
|
+
underspecified: { count: underspecified.findings.length, findings: underspecified.findings },
|
|
211
|
+
proposals: {
|
|
212
|
+
count: allProposals.length,
|
|
213
|
+
items: allProposals.map((p) => ({
|
|
214
|
+
providerId: p.providerId,
|
|
215
|
+
writeKind: p.writeKind,
|
|
216
|
+
issueKey: p.issueKey,
|
|
217
|
+
summary: p.payload.type === 'link' ? `Link ${p.issueKey} as duplicate of ${p.duplicateCandidateKey}` : `Comment on ${p.issueKey}`,
|
|
218
|
+
})),
|
|
219
|
+
},
|
|
220
|
+
provenance: { count: provenance.length, sources: provenance },
|
|
221
|
+
summary,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
return { analysis, outputPacket, writeProposals: allProposals, summary };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// F5 reasoningExecutor: wraps analyzeOpsTriage for embed capability tick; accepts plan for F5 contract compatibility.
|
|
228
|
+
|
|
229
|
+
export function createOpsTriageReasoningExecutor(config = {}) {
|
|
230
|
+
return async function opsTriageReasoningExecutor(_plan, ctx = {}) {
|
|
231
|
+
const { outputPacket, writeProposals, summary } = analyzeOpsTriage(ctx.sections ?? [], config);
|
|
232
|
+
return { outputPacket, writeProposals, summary };
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export default createOpsTriageReasoningExecutor;
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/presets/pm-feedback.mjs — deterministic PM feedback→requirements
|
|
3
|
+
* analysis for the `product-manager` embed preset (see the embed-capability
|
|
4
|
+
* manifest schema decision record).
|
|
5
|
+
*
|
|
6
|
+
* Pure deterministic `reasoningExecutor(plan, ctx)` that the embed capability
|
|
7
|
+
* tick accepts: reads the product-manager specialist's bound provider
|
|
8
|
+
* snapshot slice (feedback + Confluence sections, already binding-scoped and
|
|
9
|
+
* filter-narrowed by the caller) and walks the F7 PM value-tradeoff framework
|
|
10
|
+
* (cx-pm-value-tradeoff) over it. It emits an output packet that validates
|
|
11
|
+
* against the `pm-requirements-candidates` output contract. There is no
|
|
12
|
+
* writeIntent output — this preset is artifact-only: it produces requirement
|
|
13
|
+
* candidates for a human PM to act on, never a provider write.
|
|
14
|
+
*
|
|
15
|
+
* Trust boundary (B14 / N1): feedback-item text originates from an external,
|
|
16
|
+
* unauthenticated source and is stamped EXTERNAL_UNAUTHENTICATED. Every
|
|
17
|
+
* feedback string that reaches the output packet is confined to a `quote`
|
|
18
|
+
* field as quoted evidence: clustering, scoring, and cross-referencing all
|
|
19
|
+
* branch on structural fields (tags, theme keywords, requirement id substring
|
|
20
|
+
* matches), so the feedback text itself can only be displayed, not executed
|
|
21
|
+
* or used as a control-flow directive.
|
|
22
|
+
*
|
|
23
|
+
* Every load-bearing claim carries provenance: a source id (feedback file+row,
|
|
24
|
+
* Confluence page id) the reader can re-verify. A candidate with no covering
|
|
25
|
+
* PRD requirement is reported as `new` (net-new), never linked to a
|
|
26
|
+
* fabricated requirement id.
|
|
27
|
+
*
|
|
28
|
+
* Analysis moves (mapped to the F7 framework steps):
|
|
29
|
+
* 1. user-value: cluster feedback-items by theme (tag or keyword match),
|
|
30
|
+
* name the recurring job each cluster represents.
|
|
31
|
+
* 2. tradeoffs: score each cluster with the user-value rubric (frequency +
|
|
32
|
+
* author diversity + explicit pain signal).
|
|
33
|
+
* 3. prioritization: cross-reference each cluster against PRD requirement
|
|
34
|
+
* items (Confluence) — supports / contradicts / new.
|
|
35
|
+
* 4. acceptance-criteria: emit one requirements-candidate per cluster,
|
|
36
|
+
* provenance-linked to every contributing feedback row and (when
|
|
37
|
+
* matched) the PRD requirement id.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
const FEEDBACK_PROVIDER = 'feedback';
|
|
41
|
+
const CONFLUENCE_PROVIDER = 'atlassian-confluence';
|
|
42
|
+
|
|
43
|
+
// A requirement line in a PRD body: an id token (REQ-12, R-3, FR-4…) followed
|
|
44
|
+
// by a colon or dash and the requirement text. Matching is deterministic so a
|
|
45
|
+
// seeded fixture produces the same requirement ids on every run.
|
|
46
|
+
const REQUIREMENT_LINE_RE = /^\s*(?:[-*]\s*)?((?:REQ|FR|NFR|R)-\d+)\s*[:.\-–)]\s*(.+?)\s*$/i;
|
|
47
|
+
|
|
48
|
+
// Explicit pain-signal terms that raise a cluster's user-value score when
|
|
49
|
+
// present in feedback text. This is a fixed vocabulary lookup, not an
|
|
50
|
+
// instruction follow — feedback text can only ever match or not match these
|
|
51
|
+
// literal terms, never redirect the analysis.
|
|
52
|
+
const PAIN_TERMS = ['broken', 'frustrating', 'confusing', 'cannot', "can't", 'blocked', 'unusable', 'slow', 'crashes', 'lost'];
|
|
53
|
+
|
|
54
|
+
// Minimum shared-word ratio (Jaccard on words) for two feedback items to be
|
|
55
|
+
// placed in the same theme cluster when no explicit tag is present.
|
|
56
|
+
const THEME_SIMILARITY_THRESHOLD = 0.25;
|
|
57
|
+
|
|
58
|
+
function sectionItems(sections, providerId) {
|
|
59
|
+
const section = (sections || []).find((s) => s.provider === providerId);
|
|
60
|
+
return Array.isArray(section?.items) ? section.items : [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function feedbackId(item) {
|
|
64
|
+
return item?.id ?? 'unknown';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function feedbackProvenance(item) {
|
|
68
|
+
const file = item?.provenance?.file ?? 'unknown';
|
|
69
|
+
const row = item?.provenance?.row;
|
|
70
|
+
return row != null ? `${file}#${row}` : file;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function normalizeWords(text) {
|
|
74
|
+
return String(text ?? '').toLowerCase().split(/\W+/).filter((w) => w.length > 2);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function wordSimilarity(text1, text2) {
|
|
78
|
+
const words1 = new Set(normalizeWords(text1));
|
|
79
|
+
const words2 = new Set(normalizeWords(text2));
|
|
80
|
+
const union = new Set([...words1, ...words2]);
|
|
81
|
+
const intersection = new Set([...words1].filter((w) => words2.has(w)));
|
|
82
|
+
return union.size === 0 ? 0 : intersection.size / union.size;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Every trust check is a structural read of the `_trust` stamp — never a
|
|
86
|
+
// parse of the feedback text itself — so an untrusted item can only ever be
|
|
87
|
+
// labeled, never able to change which branch this function takes.
|
|
88
|
+
|
|
89
|
+
function trustLabel(item) {
|
|
90
|
+
return item?._trust?.level ?? 'unknown';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Cluster feedback-items by theme. An explicit shared tag wins; absent a
|
|
95
|
+
* shared tag, items land in the same cluster when their text passes the
|
|
96
|
+
* word-similarity threshold against the cluster's seed item. Deterministic:
|
|
97
|
+
* clusters are built in input order, so a seeded fixture always produces the
|
|
98
|
+
* same cluster membership.
|
|
99
|
+
*/
|
|
100
|
+
export function clusterByTheme(feedbackItems) {
|
|
101
|
+
const clusters = [];
|
|
102
|
+
|
|
103
|
+
for (const item of feedbackItems) {
|
|
104
|
+
const tags = Array.isArray(item?.tags) ? item.tags : [];
|
|
105
|
+
let target = null;
|
|
106
|
+
|
|
107
|
+
if (tags.length > 0) {
|
|
108
|
+
target = clusters.find((c) => c.tag && tags.includes(c.tag));
|
|
109
|
+
}
|
|
110
|
+
if (!target) {
|
|
111
|
+
target = clusters.find((c) => !c.tag && wordSimilarity(item?.text, c.seedText) >= THEME_SIMILARITY_THRESHOLD);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (target) {
|
|
115
|
+
target.items.push(item);
|
|
116
|
+
} else {
|
|
117
|
+
clusters.push({
|
|
118
|
+
theme: tags[0] ?? feedbackId(item),
|
|
119
|
+
tag: tags[0] ?? null,
|
|
120
|
+
seedText: item?.text ?? '',
|
|
121
|
+
items: [item],
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return clusters.map((c) => ({ theme: c.theme, items: c.items }));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Score a theme cluster with the user-value rubric: frequency (item count),
|
|
131
|
+
* author diversity (distinct authors), and explicit pain signal (count of
|
|
132
|
+
* PAIN_TERMS matches across the cluster's feedback text). Score is a plain
|
|
133
|
+
* sum of these three signals — deterministic, no external weighting table.
|
|
134
|
+
* Every contributing pain-term match is itself evidence, not instruction: it
|
|
135
|
+
* is recorded as a literal string match, never executed.
|
|
136
|
+
*/
|
|
137
|
+
export function scoreCluster(cluster) {
|
|
138
|
+
const items = cluster.items || [];
|
|
139
|
+
const frequency = items.length;
|
|
140
|
+
const authors = new Set(items.map((i) => i?.author).filter(Boolean));
|
|
141
|
+
const authorDiversity = authors.size;
|
|
142
|
+
|
|
143
|
+
let painSignal = 0;
|
|
144
|
+
const painMatches = [];
|
|
145
|
+
for (const item of items) {
|
|
146
|
+
const lower = String(item?.text ?? '').toLowerCase();
|
|
147
|
+
const matched = PAIN_TERMS.filter((term) => lower.includes(term));
|
|
148
|
+
if (matched.length > 0) {
|
|
149
|
+
painSignal += matched.length;
|
|
150
|
+
painMatches.push({ id: feedbackId(item), terms: matched });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
frequency,
|
|
156
|
+
authorDiversity,
|
|
157
|
+
painSignal,
|
|
158
|
+
userValueScore: frequency + authorDiversity + painSignal,
|
|
159
|
+
painMatches,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Parse every PRD doc's body into requirement items, mirroring the P3 TPM
|
|
165
|
+
* preset's parser so requirement ids are stable across presets.
|
|
166
|
+
*/
|
|
167
|
+
export function parseRequirements(docs) {
|
|
168
|
+
const requirements = [];
|
|
169
|
+
for (const doc of docs || []) {
|
|
170
|
+
const sourceDocId = doc?.id ?? doc?.pageId ?? doc?.key ?? 'unknown';
|
|
171
|
+
const sourceTitle = doc?.title ?? '[untitled]';
|
|
172
|
+
const body = String(doc?.body ?? doc?.text ?? doc?.content ?? '');
|
|
173
|
+
for (const line of body.split(/\r?\n/)) {
|
|
174
|
+
const match = REQUIREMENT_LINE_RE.exec(line);
|
|
175
|
+
if (!match) continue;
|
|
176
|
+
const reqId = match[1].toUpperCase();
|
|
177
|
+
requirements.push({
|
|
178
|
+
reqId,
|
|
179
|
+
text: match[2].trim(),
|
|
180
|
+
sourceDocId,
|
|
181
|
+
sourceTitle,
|
|
182
|
+
provenance: `${sourceDocId}#${reqId}`,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return requirements;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// A cluster's theme text is checked against a requirement's text for shared
|
|
190
|
+
// words above the threshold; a cluster whose feedback explicitly mentions a
|
|
191
|
+
// requirement id string is a stronger, direct match.
|
|
192
|
+
|
|
193
|
+
function clusterMentionsRequirement(cluster, req) {
|
|
194
|
+
const reqIdUpper = req.reqId.toUpperCase();
|
|
195
|
+
const directMention = (cluster.items || []).some((item) => String(item?.text ?? '').toUpperCase().includes(reqIdUpper));
|
|
196
|
+
if (directMention) return { matched: true, kind: 'direct' };
|
|
197
|
+
|
|
198
|
+
const clusterText = (cluster.items || []).map((i) => i?.text ?? '').join(' ');
|
|
199
|
+
const similarity = wordSimilarity(clusterText, req.text);
|
|
200
|
+
if (similarity >= THEME_SIMILARITY_THRESHOLD) return { matched: true, kind: 'thematic', similarity };
|
|
201
|
+
|
|
202
|
+
return { matched: false };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Cross-reference each theme cluster against the parsed PRD requirements.
|
|
207
|
+
* Returns one row per cluster: `supports` when the cluster's feedback
|
|
208
|
+
* reinforces an existing requirement, `contradicts` when pain signal is high
|
|
209
|
+
* against a requirement already marked covered-in-PRD text as resolved (this
|
|
210
|
+
* preset does not have Jira coverage data, so contradiction detection is
|
|
211
|
+
* conservative — thematic match plus a high pain signal), otherwise `new`
|
|
212
|
+
* (net-new, no matching requirement in the bound PRD set).
|
|
213
|
+
*/
|
|
214
|
+
export function crossReferenceRequirements(clusters, requirements) {
|
|
215
|
+
return clusters.map((cluster) => {
|
|
216
|
+
let best = null;
|
|
217
|
+
for (const req of requirements) {
|
|
218
|
+
const match = clusterMentionsRequirement(cluster, req);
|
|
219
|
+
if (match.matched) {
|
|
220
|
+
if (!best || match.kind === 'direct') best = { req, match };
|
|
221
|
+
if (best.match.kind === 'direct') break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!best) {
|
|
226
|
+
return { theme: cluster.theme, relation: 'new', requirement: null, matchKind: null };
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
theme: cluster.theme,
|
|
230
|
+
relation: 'supports',
|
|
231
|
+
requirement: { reqId: best.req.reqId, text: best.req.text, provenance: best.req.provenance },
|
|
232
|
+
matchKind: best.match.kind,
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Build one requirements-candidate per theme cluster. Every candidate
|
|
239
|
+
* carries provenance to every feedback row it draws on and, when matched, to
|
|
240
|
+
* the PRD requirement it relates to. Feedback text is carried only inside a
|
|
241
|
+
* `quote` field on each evidence row — quoted evidence, never instruction —
|
|
242
|
+
* and every quoted item's trust label is recorded alongside it so a
|
|
243
|
+
* downstream reader can see it came from an EXTERNAL_UNAUTHENTICATED source.
|
|
244
|
+
*/
|
|
245
|
+
export function buildCandidates(clusters, scores, crossRefs) {
|
|
246
|
+
return clusters.map((cluster, i) => {
|
|
247
|
+
const score = scores[i];
|
|
248
|
+
const crossRef = crossRefs[i];
|
|
249
|
+
|
|
250
|
+
const evidence = cluster.items.map((item) => ({
|
|
251
|
+
feedbackId: feedbackId(item),
|
|
252
|
+
provenance: feedbackProvenance(item),
|
|
253
|
+
trust: trustLabel(item),
|
|
254
|
+
quote: item?.text ?? '',
|
|
255
|
+
}));
|
|
256
|
+
|
|
257
|
+
const relation = crossRef.relation;
|
|
258
|
+
const candidateId = `RC-${String(i + 1).padStart(3, '0')}`;
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
candidateId,
|
|
262
|
+
theme: cluster.theme,
|
|
263
|
+
userValueScore: score.userValueScore,
|
|
264
|
+
scoreBreakdown: {
|
|
265
|
+
frequency: score.frequency,
|
|
266
|
+
authorDiversity: score.authorDiversity,
|
|
267
|
+
painSignal: score.painSignal,
|
|
268
|
+
},
|
|
269
|
+
relation,
|
|
270
|
+
requirement: crossRef.requirement,
|
|
271
|
+
evidence,
|
|
272
|
+
statement: relation === 'new'
|
|
273
|
+
? `Theme '${cluster.theme}' (${cluster.items.length} feedback item(s), user-value score ${score.userValueScore}) has no matching PRD requirement — net-new candidate (sources ${evidence.map((e) => e.provenance).join(', ')}).`
|
|
274
|
+
: `Theme '${cluster.theme}' (${cluster.items.length} feedback item(s), user-value score ${score.userValueScore}) ${relation} ${crossRef.requirement.reqId} (source ${crossRef.requirement.provenance}); feedback sources ${evidence.map((e) => e.provenance).join(', ')}.`,
|
|
275
|
+
};
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Pure deterministic PM feedback analysis: reads sections, produces the
|
|
281
|
+
* output packet (contract shape) plus the structured analysis. No write
|
|
282
|
+
* proposals — this preset is artifact-only.
|
|
283
|
+
*/
|
|
284
|
+
export function analyzePmFeedback(sections, { now = Date.now(), generatedAt } = {}) {
|
|
285
|
+
const feedbackItems = sectionItems(sections, FEEDBACK_PROVIDER);
|
|
286
|
+
const docs = sectionItems(sections, CONFLUENCE_PROVIDER);
|
|
287
|
+
|
|
288
|
+
const clusters = clusterByTheme(feedbackItems);
|
|
289
|
+
const scores = clusters.map(scoreCluster);
|
|
290
|
+
const requirements = parseRequirements(docs);
|
|
291
|
+
const crossRefs = crossReferenceRequirements(clusters, requirements);
|
|
292
|
+
const candidates = buildCandidates(clusters, scores, crossRefs);
|
|
293
|
+
|
|
294
|
+
const provenance = [
|
|
295
|
+
...feedbackItems.map((item) => feedbackProvenance(item)),
|
|
296
|
+
...requirements.map((r) => r.provenance),
|
|
297
|
+
];
|
|
298
|
+
|
|
299
|
+
const analysis = {
|
|
300
|
+
generatedAt: generatedAt ?? new Date(now).toISOString(),
|
|
301
|
+
clusters,
|
|
302
|
+
scores,
|
|
303
|
+
requirements,
|
|
304
|
+
crossRefs,
|
|
305
|
+
candidates,
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const summary = [
|
|
309
|
+
`Clustered ${feedbackItems.length} feedback item(s) into ${clusters.length} theme(s).`,
|
|
310
|
+
`Cross-referenced against ${requirements.length} PRD requirement(s).`,
|
|
311
|
+
`Produced ${candidates.length} requirements-candidate(s).`,
|
|
312
|
+
].join(' ');
|
|
313
|
+
|
|
314
|
+
// Each contract field is a section wrapper ({ count, items }) rather than a
|
|
315
|
+
// bare array so a legitimately empty section still counts as present under
|
|
316
|
+
// the output contract — an absent section signals a broken producer, an
|
|
317
|
+
// empty one signals "checked, nothing found".
|
|
318
|
+
const outputPacket = {
|
|
319
|
+
candidates: { count: candidates.length, items: candidates },
|
|
320
|
+
clusters: { count: clusters.length, items: clusters.map((c) => ({ theme: c.theme, itemCount: c.items.length })) },
|
|
321
|
+
requirements: { count: requirements.length, items: requirements },
|
|
322
|
+
provenance: { count: provenance.length, sources: provenance },
|
|
323
|
+
summary,
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
return { analysis, outputPacket, writeProposals: [], summary };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* F5 reasoningExecutor: wraps analyzePmFeedback for the embed capability
|
|
331
|
+
* tick; accepts plan for F5 contract compatibility. Always returns an empty
|
|
332
|
+
* writeProposals array — this preset never proposes a provider write.
|
|
333
|
+
*/
|
|
334
|
+
export function createPmFeedbackReasoningExecutor(config = {}) {
|
|
335
|
+
return async function pmFeedbackReasoningExecutor(_plan, ctx = {}) {
|
|
336
|
+
const { outputPacket, summary } = analyzePmFeedback(ctx.sections ?? [], config);
|
|
337
|
+
return { outputPacket, writeProposals: [], summary };
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export default createPmFeedbackReasoningExecutor;
|