@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,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/presets/tpm.mjs — deterministic TPM analysis for the `operations`
|
|
3
|
+
* embed preset (see the embed-capability manifest schema decision record).
|
|
4
|
+
*
|
|
5
|
+
* No reasoning engine ships in this repo yet, so this module is a pure,
|
|
6
|
+
* deterministic `reasoningExecutor(plan, ctx)` that the embed capability tick
|
|
7
|
+
* accepts unchanged: it reads the operations specialist's bound provider
|
|
8
|
+
* snapshot slice (Jira + Confluence + Slack sections, already binding-scoped
|
|
9
|
+
* and filter-narrowed by the caller) and composes the ops dependency-
|
|
10
|
+
* sequencing framework (cx-ops-dependency-sequencing) over it. It emits an
|
|
11
|
+
* output packet that
|
|
12
|
+
* validates against the `operations-tpm-briefing` output contract plus the
|
|
13
|
+
* writeIntents (draft Jira tickets) the caller enqueues for approval.
|
|
14
|
+
*
|
|
15
|
+
* Every load-bearing claim carries provenance — a source id the reader can
|
|
16
|
+
* re-verify (a Confluence page id, a Jira issue key, a Slack message id). A
|
|
17
|
+
* requirement with no covering issue is reported as a finding that cites the
|
|
18
|
+
* requirement id and the empty coverage, never a fabricated issue key.
|
|
19
|
+
* Nothing here writes to a provider: proposals are data, the approval queue is
|
|
20
|
+
* the only path out.
|
|
21
|
+
*
|
|
22
|
+
* Analysis moves (mapped to the five ops-framework steps):
|
|
23
|
+
* 1. Parse PRD docs (Confluence) into requirement items.
|
|
24
|
+
* 2. Map Jira epics/issues to requirements → coverage matrix.
|
|
25
|
+
* 3. Uncovered requirement → missing-work finding + a proposed draft issue.
|
|
26
|
+
* 4. Due-date / dependency-chain analysis → timeline-risk findings w/ evidence.
|
|
27
|
+
* 5. Slack signals cross-referenced for contradiction / blocker mentions.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const JIRA_PROVIDER = 'atlassian-jira';
|
|
31
|
+
const CONFLUENCE_PROVIDER = 'atlassian-confluence';
|
|
32
|
+
const SLACK_PROVIDER = 'slack';
|
|
33
|
+
|
|
34
|
+
const JIRA_WRITE_KIND = 'createIssue';
|
|
35
|
+
|
|
36
|
+
// A requirement line in a PRD body: an id token (REQ-12, R-3, FR-4…) followed
|
|
37
|
+
// by a colon or dash and the requirement text. Matching is deterministic so a
|
|
38
|
+
// seeded fixture produces the same requirement ids on every run.
|
|
39
|
+
const REQUIREMENT_LINE_RE = /^\s*(?:[-*]\s*)?((?:REQ|FR|NFR|R)-\d+)\s*[:.\-–)]\s*(.+?)\s*$/i;
|
|
40
|
+
|
|
41
|
+
// Slack signal keywords that flag a blocker or a contradiction against the
|
|
42
|
+
// plan. Presence alone is not a claim — the finding always cites the message id.
|
|
43
|
+
const BLOCKER_TERMS = ['blocked', 'blocker', 'stuck', 'cannot proceed', 'waiting on', 'slipping', 'at risk', 'wont make', "won't make", 'behind schedule'];
|
|
44
|
+
const CONTRADICTION_TERMS = ['descoped', 'de-scoped', 'cut from', 'dropped', 'no longer', 'reversed', 'changed our mind', 'not doing'];
|
|
45
|
+
|
|
46
|
+
function sectionItems(sections, providerId) {
|
|
47
|
+
const section = (sections || []).find((s) => s.provider === providerId);
|
|
48
|
+
return Array.isArray(section?.items) ? section.items : [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function docText(doc) {
|
|
52
|
+
return String(doc?.body ?? doc?.text ?? doc?.content ?? '');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function docId(doc) {
|
|
56
|
+
return doc?.id ?? doc?.pageId ?? doc?.key ?? 'unknown';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Parse every PRD doc's body into requirement items. Each item keeps a
|
|
61
|
+
* re-verifiable provenance pair (the page id + the requirement id) so a
|
|
62
|
+
* downstream finding never has to invent where a requirement came from.
|
|
63
|
+
*/
|
|
64
|
+
export function parseRequirements(docs) {
|
|
65
|
+
const requirements = [];
|
|
66
|
+
for (const doc of docs || []) {
|
|
67
|
+
const sourceDocId = docId(doc);
|
|
68
|
+
const sourceTitle = doc?.title ?? '[untitled]';
|
|
69
|
+
for (const line of docText(doc).split(/\r?\n/)) {
|
|
70
|
+
const match = REQUIREMENT_LINE_RE.exec(line);
|
|
71
|
+
if (!match) continue;
|
|
72
|
+
const reqId = match[1].toUpperCase();
|
|
73
|
+
requirements.push({
|
|
74
|
+
reqId,
|
|
75
|
+
text: match[2].trim(),
|
|
76
|
+
sourceDocId,
|
|
77
|
+
sourceTitle,
|
|
78
|
+
provenance: `${sourceDocId}#${reqId}`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return requirements;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function issueKey(issue) {
|
|
86
|
+
return issue?.key ?? issue?.id ?? 'unknown';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// An issue covers a requirement when it names the requirement id in its
|
|
90
|
+
// summary, description, or an explicit `requirements` field. Substring match on
|
|
91
|
+
// a word-bounded id keeps coverage deterministic and auditable.
|
|
92
|
+
function issueMentionsRequirement(issue, reqId) {
|
|
93
|
+
const declared = Array.isArray(issue?.requirements) ? issue.requirements.map((r) => String(r).toUpperCase()) : [];
|
|
94
|
+
if (declared.includes(reqId)) return true;
|
|
95
|
+
const haystack = `${issue?.summary ?? ''} ${issue?.description ?? ''}`.toUpperCase();
|
|
96
|
+
const bounded = new RegExp(`(^|[^A-Z0-9])${reqId.replace(/[-]/g, '\\-')}([^A-Z0-9]|$)`);
|
|
97
|
+
return bounded.test(haystack);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Map Jira issues to requirements. Returns a coverage matrix: one row per
|
|
102
|
+
* requirement listing the covering issue keys (possibly empty). Every row
|
|
103
|
+
* carries the requirement's provenance so the briefing can cite it whether the
|
|
104
|
+
* requirement is covered or not.
|
|
105
|
+
*/
|
|
106
|
+
export function buildCoverageMatrix(requirements, issues) {
|
|
107
|
+
return requirements.map((req) => {
|
|
108
|
+
const covering = (issues || [])
|
|
109
|
+
.filter((issue) => issueMentionsRequirement(issue, req.reqId))
|
|
110
|
+
.map((issue) => issueKey(issue));
|
|
111
|
+
return {
|
|
112
|
+
reqId: req.reqId,
|
|
113
|
+
text: req.text,
|
|
114
|
+
provenance: req.provenance,
|
|
115
|
+
coveredBy: covering,
|
|
116
|
+
covered: covering.length > 0,
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function draftTicketProposal(row, req) {
|
|
122
|
+
return {
|
|
123
|
+
providerId: JIRA_PROVIDER,
|
|
124
|
+
writeKind: JIRA_WRITE_KIND,
|
|
125
|
+
reqId: row.reqId,
|
|
126
|
+
payload: {
|
|
127
|
+
type: 'issue',
|
|
128
|
+
issuetype: 'Task',
|
|
129
|
+
summary: `Cover ${row.reqId}: ${req?.text ?? row.text}`,
|
|
130
|
+
description: `Auto-drafted by the operations TPM preset because ${row.reqId} (source ${row.provenance}) has no covering Jira issue. Requirement text: ${req?.text ?? row.text}. Verify and edit before approving; nothing is created without approval.`,
|
|
131
|
+
requirements: [row.reqId],
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Uncovered requirement → one missing-work finding + one proposed draft issue.
|
|
138
|
+
* The finding cites the requirement id and the empty coverage; the proposal is
|
|
139
|
+
* data only — the caller runs it through AuthorityGuard and the approval queue.
|
|
140
|
+
*/
|
|
141
|
+
export function findMissingWork(coverageMatrix, requirements) {
|
|
142
|
+
const findings = [];
|
|
143
|
+
const proposals = [];
|
|
144
|
+
const byId = new Map((requirements || []).map((r) => [r.reqId, r]));
|
|
145
|
+
for (const row of coverageMatrix) {
|
|
146
|
+
if (row.covered) continue;
|
|
147
|
+
const req = byId.get(row.reqId);
|
|
148
|
+
findings.push({
|
|
149
|
+
reqId: row.reqId,
|
|
150
|
+
text: row.text,
|
|
151
|
+
evidence: { requirement: row.provenance, coverage: 'none' },
|
|
152
|
+
statement: `${row.reqId} ("${row.text}") has no covering Jira issue (source ${row.provenance}).`,
|
|
153
|
+
});
|
|
154
|
+
proposals.push(draftTicketProposal(row, req));
|
|
155
|
+
}
|
|
156
|
+
return { findings, proposals };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function parseDate(value) {
|
|
160
|
+
if (!value) return null;
|
|
161
|
+
const t = Date.parse(value);
|
|
162
|
+
return Number.isNaN(t) ? null : t;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function issueDeps(issue) {
|
|
166
|
+
const raw = issue?.blockedBy ?? issue?.dependsOn ?? issue?.blocks ?? [];
|
|
167
|
+
return Array.isArray(raw) ? raw.map((d) => String(d)) : [];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Due-date / dependency-chain analysis. Walks each issue's blocking chain and
|
|
172
|
+
* registers a timeline-risk finding when a blocker is due no earlier than the
|
|
173
|
+
* dependent it gates (the dependent cannot finish on time), or when an issue is
|
|
174
|
+
* explicitly flagged blocked. Every finding cites its evidence — the issue
|
|
175
|
+
* keys, the due dates, and the chain — so no risk claim is unverifiable.
|
|
176
|
+
*
|
|
177
|
+
* `now` is injectable so a fixture is deterministic; it defaults to the current
|
|
178
|
+
* time only when a caller omits it.
|
|
179
|
+
*/
|
|
180
|
+
export function findTimelineRisks(issues, { now = Date.now() } = {}) {
|
|
181
|
+
const byKey = new Map((issues || []).map((i) => [issueKey(i), i]));
|
|
182
|
+
const findings = [];
|
|
183
|
+
|
|
184
|
+
for (const issue of issues || []) {
|
|
185
|
+
const key = issueKey(issue);
|
|
186
|
+
const due = parseDate(issue?.dueDate);
|
|
187
|
+
const status = String(issue?.status ?? '').toLowerCase();
|
|
188
|
+
|
|
189
|
+
if (status.includes('blocked')) {
|
|
190
|
+
findings.push({
|
|
191
|
+
issueKey: key,
|
|
192
|
+
kind: 'blocked-status',
|
|
193
|
+
evidence: { issue: key, status: issue?.status ?? 'unknown' },
|
|
194
|
+
statement: `${key} is marked '${issue?.status ?? 'unknown'}' — flagged as an active blocker (source ${key}).`,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
for (const depKey of issueDeps(issue)) {
|
|
199
|
+
const blocker = byKey.get(depKey);
|
|
200
|
+
if (!blocker) {
|
|
201
|
+
findings.push({
|
|
202
|
+
issueKey: key,
|
|
203
|
+
kind: 'unknown-blocker',
|
|
204
|
+
evidence: { issue: key, dependsOn: depKey, blockerDueDate: '[unverified]' },
|
|
205
|
+
statement: `${key} depends on ${depKey}, which is not in the bound snapshot — its schedule is [unverified] (source ${key}).`,
|
|
206
|
+
});
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const blockerDue = parseDate(blocker?.dueDate);
|
|
210
|
+
if (due != null && blockerDue != null && blockerDue >= due) {
|
|
211
|
+
findings.push({
|
|
212
|
+
issueKey: key,
|
|
213
|
+
kind: 'slipping-chain',
|
|
214
|
+
evidence: {
|
|
215
|
+
issue: key,
|
|
216
|
+
issueDueDate: issue.dueDate,
|
|
217
|
+
blocker: depKey,
|
|
218
|
+
blockerDueDate: blocker.dueDate,
|
|
219
|
+
},
|
|
220
|
+
statement: `${key} (due ${issue.dueDate}) is blocked by ${depKey} (due ${blocker.dueDate}); the blocker is due no earlier than the dependent, so ${key} cannot finish on time (sources ${key}, ${depKey}).`,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return findings;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function messageId(msg) {
|
|
229
|
+
return msg?.id ?? msg?.ts ?? msg?.permalink ?? 'unknown';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function matchTerms(text, terms) {
|
|
233
|
+
const lower = String(text ?? '').toLowerCase();
|
|
234
|
+
return terms.filter((term) => lower.includes(term));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Cross-reference Slack signals for blocker or contradiction mentions. Each
|
|
239
|
+
* finding is a citation, not an interpretation: it names the message id, the
|
|
240
|
+
* channel, and the exact terms that matched, and — when a requirement id or
|
|
241
|
+
* issue key appears in the message — links the signal to that artifact.
|
|
242
|
+
*/
|
|
243
|
+
export function findSlackSignals(messages, { requirements = [], issues = [] } = {}) {
|
|
244
|
+
const reqIds = requirements.map((r) => r.reqId);
|
|
245
|
+
const issueKeys = (issues || []).map((i) => issueKey(i));
|
|
246
|
+
const findings = [];
|
|
247
|
+
|
|
248
|
+
for (const msg of messages || []) {
|
|
249
|
+
const text = String(msg?.text ?? '');
|
|
250
|
+
const blockers = matchTerms(text, BLOCKER_TERMS);
|
|
251
|
+
const contradictions = matchTerms(text, CONTRADICTION_TERMS);
|
|
252
|
+
if (blockers.length === 0 && contradictions.length === 0) continue;
|
|
253
|
+
|
|
254
|
+
const upper = text.toUpperCase();
|
|
255
|
+
const refReqs = reqIds.filter((id) => upper.includes(id));
|
|
256
|
+
const refIssues = issueKeys.filter((k) => k !== 'unknown' && upper.includes(String(k).toUpperCase()));
|
|
257
|
+
|
|
258
|
+
findings.push({
|
|
259
|
+
messageId: messageId(msg),
|
|
260
|
+
channel: msg?.channel ?? 'unknown',
|
|
261
|
+
kind: contradictions.length > 0 ? 'contradiction' : 'blocker',
|
|
262
|
+
terms: [...blockers, ...contradictions],
|
|
263
|
+
references: { requirements: refReqs, issues: refIssues },
|
|
264
|
+
evidence: { message: messageId(msg), channel: msg?.channel ?? 'unknown' },
|
|
265
|
+
statement: `Slack message ${messageId(msg)} in ${msg?.channel ?? 'unknown'} mentions ${[...blockers, ...contradictions].map((t) => `'${t}'`).join(', ')}${refReqs.length || refIssues.length ? ` referencing ${[...refReqs, ...refIssues].join(', ')}` : ''} (source ${messageId(msg)}).`,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return findings;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function renderList(items, render) {
|
|
272
|
+
if (!items.length) return ['- _(none)_'];
|
|
273
|
+
return items.map(render);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Render the TPM briefing as markdown. Every load-bearing line ends with a
|
|
278
|
+
* parenthetical source id; a "Provenance index" section enumerates every source
|
|
279
|
+
* the briefing draws on so the reader can re-verify each claim.
|
|
280
|
+
*/
|
|
281
|
+
export function renderBriefing(analysis) {
|
|
282
|
+
const { coverageMatrix, missingWork, timelineRisks, slackSignals, provenance, generatedAt } = analysis;
|
|
283
|
+
|
|
284
|
+
const lines = [];
|
|
285
|
+
lines.push('# TPM Briefing');
|
|
286
|
+
lines.push('');
|
|
287
|
+
lines.push(`Generated ${generatedAt} by the operations specialist (embed preset: operations).`);
|
|
288
|
+
lines.push('Every load-bearing claim below cites a re-verifiable source id. Proposals are held for approval and never auto-created.');
|
|
289
|
+
lines.push('');
|
|
290
|
+
|
|
291
|
+
lines.push('## Requirement coverage');
|
|
292
|
+
lines.push('');
|
|
293
|
+
lines.push(...renderList(coverageMatrix, (row) => `- ${row.reqId}: ${row.covered ? `covered by ${row.coveredBy.join(', ')}` : 'NOT COVERED'} (source ${row.provenance})`));
|
|
294
|
+
lines.push('');
|
|
295
|
+
|
|
296
|
+
lines.push('## Missing work (uncovered requirements)');
|
|
297
|
+
lines.push('');
|
|
298
|
+
lines.push(...renderList(missingWork.findings, (f) => `- ${f.statement}`));
|
|
299
|
+
lines.push('');
|
|
300
|
+
|
|
301
|
+
lines.push('## Timeline risk');
|
|
302
|
+
lines.push('');
|
|
303
|
+
lines.push(...renderList(timelineRisks, (f) => `- [${f.kind}] ${f.statement}`));
|
|
304
|
+
lines.push('');
|
|
305
|
+
|
|
306
|
+
lines.push('## Misalignment signals (Slack)');
|
|
307
|
+
lines.push('');
|
|
308
|
+
lines.push(...renderList(slackSignals, (f) => `- [${f.kind}] ${f.statement}`));
|
|
309
|
+
lines.push('');
|
|
310
|
+
|
|
311
|
+
lines.push('## Proposed tickets (awaiting approval)');
|
|
312
|
+
lines.push('');
|
|
313
|
+
lines.push(...renderList(missingWork.proposals, (p) => `- ${p.providerId}.${p.writeKind} — ${p.payload.summary} (for ${p.reqId})`));
|
|
314
|
+
lines.push('');
|
|
315
|
+
|
|
316
|
+
lines.push('## Provenance index');
|
|
317
|
+
lines.push('');
|
|
318
|
+
lines.push(...renderList(provenance, (src) => `- ${src}`));
|
|
319
|
+
lines.push('');
|
|
320
|
+
|
|
321
|
+
return lines.join('\n');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function collectProvenance(requirements, issues, messages) {
|
|
325
|
+
const sources = new Set();
|
|
326
|
+
for (const r of requirements) sources.add(r.provenance);
|
|
327
|
+
for (const i of issues) sources.add(issueKey(i));
|
|
328
|
+
for (const m of messages) sources.add(messageId(m));
|
|
329
|
+
return [...sources];
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Run the deterministic TPM analysis over a bound snapshot slice. Pure — no
|
|
334
|
+
* provider calls, no queue writes. Returns the structured analysis, the output
|
|
335
|
+
* packet (contract shape), the write proposals, and the rendered briefing.
|
|
336
|
+
*
|
|
337
|
+
* @param {Array<{provider:string, items:object[]}>} sections
|
|
338
|
+
* @param {{ now?: number, generatedAt?: string }} [opts]
|
|
339
|
+
*/
|
|
340
|
+
export function analyzeTpm(sections, { now = Date.now(), generatedAt } = {}) {
|
|
341
|
+
const docs = sectionItems(sections, CONFLUENCE_PROVIDER);
|
|
342
|
+
const issues = sectionItems(sections, JIRA_PROVIDER);
|
|
343
|
+
const messages = sectionItems(sections, SLACK_PROVIDER);
|
|
344
|
+
|
|
345
|
+
const requirements = parseRequirements(docs);
|
|
346
|
+
const coverageMatrix = buildCoverageMatrix(requirements, issues);
|
|
347
|
+
const missingWork = findMissingWork(coverageMatrix, requirements);
|
|
348
|
+
const timelineRisks = findTimelineRisks(issues, { now });
|
|
349
|
+
const slackSignals = findSlackSignals(messages, { requirements, issues });
|
|
350
|
+
const provenance = collectProvenance(requirements, issues, messages);
|
|
351
|
+
|
|
352
|
+
const analysis = {
|
|
353
|
+
generatedAt: generatedAt ?? new Date(now).toISOString(),
|
|
354
|
+
requirements,
|
|
355
|
+
coverageMatrix,
|
|
356
|
+
missingWork,
|
|
357
|
+
timelineRisks,
|
|
358
|
+
slackSignals,
|
|
359
|
+
provenance,
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const briefing = renderBriefing(analysis);
|
|
363
|
+
|
|
364
|
+
// Each contract field is a section wrapper ({ count, findings/rows/sources })
|
|
365
|
+
// rather than a bare array so a legitimately empty section still counts as
|
|
366
|
+
// present under the output contract — an absent section signals a broken
|
|
367
|
+
// producer, an empty one signals "checked, nothing found".
|
|
368
|
+
const outputPacket = {
|
|
369
|
+
coverageMatrix: { count: coverageMatrix.length, rows: coverageMatrix },
|
|
370
|
+
missingWork: { count: missingWork.findings.length, findings: missingWork.findings },
|
|
371
|
+
timelineRisks: { count: timelineRisks.length, findings: timelineRisks },
|
|
372
|
+
misalignment: { count: slackSignals.length, findings: slackSignals },
|
|
373
|
+
proposals: {
|
|
374
|
+
count: missingWork.proposals.length,
|
|
375
|
+
items: missingWork.proposals.map((p) => ({ providerId: p.providerId, writeKind: p.writeKind, reqId: p.reqId, summary: p.payload.summary })),
|
|
376
|
+
},
|
|
377
|
+
provenance: { count: provenance.length, sources: provenance },
|
|
378
|
+
briefing,
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
return { analysis, outputPacket, writeProposals: missingWork.proposals, briefing };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* The F5 reasoningExecutor. `runCapabilityTick` calls this with
|
|
386
|
+
* `(plan, { manifest, sections, sliceErrors, specialistId })` and consumes the
|
|
387
|
+
* returned `{ outputPacket, writeProposals }`. The plan is not needed for the
|
|
388
|
+
* deterministic composition — the ops framework is already encoded in the
|
|
389
|
+
* analysis moves — but it is accepted to match the F5 contract exactly.
|
|
390
|
+
*
|
|
391
|
+
* @param {{ now?: number, generatedAt?: string }} [config] injected for deterministic tests
|
|
392
|
+
* @returns {(plan: object, ctx: object) => Promise<{outputPacket: object, writeProposals: object[], briefing: string}>}
|
|
393
|
+
*/
|
|
394
|
+
export function createTpmReasoningExecutor(config = {}) {
|
|
395
|
+
return async function tpmReasoningExecutor(_plan, ctx = {}) {
|
|
396
|
+
const { outputPacket, writeProposals, briefing } = analyzeTpm(ctx.sections ?? [], config);
|
|
397
|
+
return { outputPacket, writeProposals, briefing };
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export default createTpmReasoningExecutor;
|
|
@@ -18,6 +18,14 @@
|
|
|
18
18
|
* refs: [issues]
|
|
19
19
|
* limit: 50 # max issues (default: 50)
|
|
20
20
|
* fields: [summary,status,assignee,priority] # optional field subset
|
|
21
|
+
* filter: { scope, predicates, nativeQuery } # ADR-0060 filter block (LMCP-B11)
|
|
22
|
+
*
|
|
23
|
+
* When `filter` is present, `buildJqlFromFilter` compiles `scope.projects` +
|
|
24
|
+
* `predicates.*` + `nativeQuery` into JQL (server-side pushdown). Per
|
|
25
|
+
* ADR-0060 §3 pushdown is preferred but never sufficient on its own — the
|
|
26
|
+
* plane-side `matchesFilter` re-check in the poll loop (lib/embed/daemon.mjs)
|
|
27
|
+
* is the mandatory backstop and the enforcement of the predicates-AND-
|
|
28
|
+
* nativeQuery conjunction.
|
|
21
29
|
*/
|
|
22
30
|
|
|
23
31
|
export class JiraProvider {
|
|
@@ -64,7 +72,7 @@ export class JiraProvider {
|
|
|
64
72
|
|
|
65
73
|
async #listIssues(opts) {
|
|
66
74
|
const limit = Number(opts.limit ?? 50);
|
|
67
|
-
const jql = opts.jql ?? buildDefaultJql(opts);
|
|
75
|
+
const jql = opts.jql ?? (opts.filter ? buildJqlFromFilter(opts, opts.filter) : buildDefaultJql(opts));
|
|
68
76
|
const fields = opts.fields ?? ['summary', 'status', 'assignee', 'priority', 'issuetype', 'labels', 'updated', 'created'];
|
|
69
77
|
|
|
70
78
|
const data = await this.#post('/rest/api/3/search/jql', {
|
|
@@ -190,3 +198,66 @@ function buildDefaultJql(opts) {
|
|
|
190
198
|
: '';
|
|
191
199
|
return `${projectClause}statusCategory != Done ORDER BY updated DESC`;
|
|
192
200
|
}
|
|
201
|
+
|
|
202
|
+
const JIRA_STATUS_CATEGORY = Object.freeze({
|
|
203
|
+
'to-do': 'To Do',
|
|
204
|
+
'in-progress': 'In Progress',
|
|
205
|
+
done: 'Done',
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
function jqlQuote(value) {
|
|
209
|
+
return `"${String(value).replace(/"/g, '\\"')}"`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Compile an ADR-0060 filter block into JQL. `scope.projects` and
|
|
214
|
+
* `predicates.*` fold into AND-joined clauses; `opts.project`/`opts.projects`
|
|
215
|
+
* still apply when `scope.projects` is absent. `nativeQuery` is wrapped in
|
|
216
|
+
* parens and AND-joined with the compiled clauses — the conjunction the ADR
|
|
217
|
+
* requires, applied server-side here and re-verified plane-side afterward.
|
|
218
|
+
*/
|
|
219
|
+
export function buildJqlFromFilter(opts, filter) {
|
|
220
|
+
const clauses = [];
|
|
221
|
+
|
|
222
|
+
const scopeProjects = filter?.scope?.projects?.length ? filter.scope.projects : resolveProjects(opts);
|
|
223
|
+
if (scopeProjects.length) {
|
|
224
|
+
clauses.push(`project in (${scopeProjects.map(jqlQuote).join(', ')})`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const predicates = filter?.predicates ?? {};
|
|
228
|
+
if (predicates.assignee?.length) {
|
|
229
|
+
clauses.push(`assignee in (${predicates.assignee.map(jqlQuote).join(', ')})`);
|
|
230
|
+
}
|
|
231
|
+
if (predicates.statusCategory?.length) {
|
|
232
|
+
const jiraCategories = predicates.statusCategory.map((c) => JIRA_STATUS_CATEGORY[c]).filter(Boolean);
|
|
233
|
+
if (jiraCategories.length) clauses.push(`statusCategory in (${jiraCategories.map(jqlQuote).join(', ')})`);
|
|
234
|
+
}
|
|
235
|
+
if (predicates.priority?.length) {
|
|
236
|
+
clauses.push(`priority in (${predicates.priority.map(jqlQuote).join(', ')})`);
|
|
237
|
+
}
|
|
238
|
+
if (predicates.label?.length) {
|
|
239
|
+
clauses.push(`labels in (${predicates.label.map(jqlQuote).join(', ')})`);
|
|
240
|
+
}
|
|
241
|
+
if (predicates.updatedSince) {
|
|
242
|
+
clauses.push(`updated >= ${jqlUpdatedSince(predicates.updatedSince)}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!clauses.length) clauses.push('statusCategory != Done');
|
|
246
|
+
|
|
247
|
+
let jql = clauses.join(' AND ');
|
|
248
|
+
if (filter?.nativeQuery) jql = `(${jql}) AND (${filter.nativeQuery})`;
|
|
249
|
+
return `${jql} ORDER BY updated DESC`;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function jqlUpdatedSince(value) {
|
|
253
|
+
const isoDuration = /^P(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?)?$/.exec(value);
|
|
254
|
+
if (isoDuration) {
|
|
255
|
+
const days = Number(isoDuration[1] ?? 0);
|
|
256
|
+
if (days > 0) return `"-${days}d"`;
|
|
257
|
+
const hours = Number(isoDuration[2] ?? 0);
|
|
258
|
+
const minutes = Number(isoDuration[3] ?? 0);
|
|
259
|
+
if (hours > 0) return `"-${hours}h"`;
|
|
260
|
+
if (minutes > 0) return `"-${minutes}m"`;
|
|
261
|
+
}
|
|
262
|
+
return jqlQuote(value);
|
|
263
|
+
}
|