@geraldmaron/construct 1.2.2 → 1.3.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/README.md +7 -10
- package/bin/construct +286 -220
- package/bin/construct-postinstall.mjs +0 -21
- package/commands/work/optimize-prompts.md +1 -1
- package/examples/distribution/sources/adr.md +2 -2
- package/examples/seed-observations/decisions.md +1 -1
- package/lib/artifact-loop-core.mjs +412 -0
- package/lib/artifact-manifest.mjs +6 -0
- package/lib/artifact-release-gate.mjs +115 -49
- package/lib/audit-rules.mjs +2 -4
- package/lib/audit-skills.mjs +32 -20
- package/lib/audit-specialists.mjs +44 -26
- package/lib/auto-docs.mjs +6 -5
- package/lib/brand-prose.mjs +3 -3
- package/lib/brand-tokens.mjs +2 -2
- package/lib/certification/artifact-fixtures.mjs +4 -1
- package/lib/certification/demo-parity.mjs +6 -32
- package/lib/certification/real-llm-scenarios.mjs +58 -13
- package/lib/certification/role-cards.mjs +7 -6
- package/lib/certification/role-overlays.mjs +4 -4
- package/lib/certification/runner.mjs +9 -2
- package/lib/certification/skill-inventory.mjs +34 -22
- package/lib/certification/skill-scenarios.mjs +21 -8
- package/lib/certification/specialist-contracts.mjs +4 -3
- package/lib/certification/specialist-scenarios.mjs +7 -6
- package/lib/certification/status.mjs +5 -4
- package/lib/cli-commands.mjs +56 -57
- package/lib/comment-lint.mjs +1 -5
- package/lib/completions.mjs +20 -2
- package/lib/config/legacy-config-migration.mjs +125 -0
- package/lib/config/schema.mjs +5 -5
- package/lib/config/source-targets.mjs +80 -0
- package/lib/contracts/validate.mjs +119 -51
- package/lib/decisions/golden.mjs +3 -2
- package/lib/decisions/registry.mjs +11 -6
- package/lib/demo-project.mjs +188 -0
- package/lib/demo-recording.mjs +9 -34
- package/lib/demo-script.mjs +2 -2
- package/lib/demo-surface.mjs +13 -131
- package/lib/demo-tour-renderer.mjs +92 -0
- package/lib/demo.mjs +69 -54
- package/lib/diagram-export.mjs +63 -14
- package/lib/doctor/source-checkout.mjs +3 -4
- package/lib/doctor/watchers/consistency.mjs +90 -22
- package/lib/doctor/watchers/credential-parity.mjs +97 -0
- package/lib/doctor/watchers/mcp-protocol.mjs +1 -1
- package/lib/doctor/watchers/service-health.mjs +4 -32
- package/lib/document-export.mjs +73 -9
- package/lib/document-extract.mjs +10 -198
- package/lib/embed/artifact.mjs +2 -3
- package/lib/embed/cli.mjs +1 -1
- package/lib/embed/config.mjs +1 -2
- package/lib/embed/customer-profiles.mjs +1 -1
- package/lib/embed/daemon.mjs +5 -6
- package/lib/embed/demand-fetch.mjs +114 -2
- package/lib/embed/docs-lifecycle.mjs +9 -5
- package/lib/embed/intake-metrics.mjs +2 -2
- package/lib/embed/notifications.mjs +2 -3
- package/lib/embed/recommendation-store.mjs +25 -22
- package/lib/embed/role-framing.mjs +4 -9
- package/lib/embedded-contract/capability.mjs +4 -2
- package/lib/embedded-contract/contract-version.mjs +1 -1
- package/lib/env-config.mjs +2 -2
- package/lib/flavors/loader.mjs +21 -19
- package/lib/graph/build-from-registry.mjs +31 -7
- package/lib/graph/staleness.mjs +1 -1
- package/lib/headhunt.mjs +82 -35
- package/lib/hooks/agent-tracker.mjs +2 -2
- package/lib/hooks/config-protection.mjs +1 -1
- package/lib/hooks/registry-sync.mjs +3 -3
- package/lib/host-capabilities.mjs +5 -5
- package/lib/improvement/controller.mjs +2 -2
- package/lib/init-unified.mjs +19 -21
- package/lib/intake/classify.mjs +26 -0
- package/lib/intake/daemon.mjs +2 -3
- package/lib/intake/prepare.mjs +10 -3
- package/lib/intake/session-prelude.mjs +1 -1
- package/lib/intake/tables/creative.mjs +9 -9
- package/lib/intake/tables/operations.mjs +4 -4
- package/lib/intake/tables/research.mjs +2 -2
- package/lib/integrations/intake-integrations.mjs +9 -10
- package/lib/knowledge/research-store.mjs +2 -2
- package/lib/mcp/server.mjs +104 -61
- package/lib/mcp/tool-budget.mjs +1 -46
- package/lib/mcp/tool-recovery.mjs +44 -0
- package/lib/mcp/tools/artifact-author.mjs +36 -0
- package/lib/mcp/tools/find-tool.mjs +86 -0
- package/lib/mcp/tools/orchestration-run.mjs +107 -69
- package/lib/mcp/tools/project.mjs +4 -3
- package/lib/mcp/tools/{profile.mjs → scope.mjs} +54 -52
- package/lib/mcp/tools/skills.mjs +39 -22
- package/lib/mcp/tools/telemetry.mjs +2 -1
- package/lib/mcp/tools/workflow.mjs +1 -1
- package/lib/mcp-platform-config.mjs +7 -5
- package/lib/migrations/index.mjs +4 -2
- package/lib/migrations/v2-unified-registry.mjs +35 -0
- package/lib/model-router.mjs +203 -36
- package/lib/models/catalog.mjs +25 -9
- package/lib/models/execution-capability-profile.mjs +3 -3
- package/lib/models/execution-policy.mjs +7 -6
- package/lib/models/provider-poll.mjs +18 -4
- package/lib/opencode-config.mjs +56 -1
- package/lib/opencode-runtime-plugin.mjs +13 -10
- package/lib/oracle/artifact-gate.mjs +14 -4
- package/lib/oracle/cli.mjs +2 -0
- package/lib/oracle/dispatch.mjs +18 -1
- package/lib/oracle/execute.mjs +39 -7
- package/lib/oracle/index.mjs +1 -1
- package/lib/oracle/org-graph.mjs +10 -5
- package/lib/oracle/policy.mjs +6 -0
- package/lib/oracle/read-model.mjs +103 -6
- package/lib/oracle/reconcile.mjs +31 -4
- package/lib/oracle/remediation-dispatch.mjs +53 -0
- package/lib/oracle/routing.mjs +5 -0
- package/lib/oracle/synthesize.mjs +80 -3
- package/lib/orchestration/routing-tables.mjs +44 -10
- package/lib/orchestration/runtime.mjs +2 -0
- package/lib/orchestration/worker.mjs +1 -1
- package/lib/orchestration-policy.mjs +351 -62
- package/lib/overrides/resolver.mjs +1 -12
- package/lib/parity.mjs +37 -11
- package/lib/policy/engine.mjs +80 -15
- package/lib/prompt-composer.js +51 -15
- package/lib/prompt-metadata.mjs +3 -2
- package/lib/providers/connection-probe.mjs +58 -0
- package/lib/providers/copilot-auth.mjs +1 -1
- package/lib/providers/credential-bootstrap.mjs +1 -1
- package/lib/providers/op-run.mjs +3 -4
- package/lib/providers/secret-resolver.mjs +31 -0
- package/lib/publish-tooling.mjs +3 -11
- package/lib/publish.mjs +12 -29
- package/lib/reconcile/mcp-entry-reconcile.mjs +14 -9
- package/lib/reflect.mjs +2 -2
- package/lib/registry/agent-manifest.mjs +190 -0
- package/lib/registry/assemble.mjs +133 -0
- package/lib/registry/catalog.mjs +171 -0
- package/lib/registry/cli.mjs +590 -6
- package/lib/registry/consolidation.mjs +5 -4
- package/lib/registry/docs-sync.mjs +67 -0
- package/lib/registry/loader.mjs +147 -0
- package/lib/registry/org-io.mjs +76 -0
- package/lib/registry/retired-paths.mjs +71 -0
- package/lib/registry/surface-map.mjs +5 -7
- package/lib/registry/validator.mjs +438 -0
- package/lib/research-execution-policy.mjs +173 -0
- package/lib/roles/catalog.mjs +4 -9
- package/lib/roles/fence.mjs +107 -1
- package/lib/roles/flavor-bindings.mjs +68 -0
- package/lib/roles/gateway.mjs +140 -29
- package/lib/roles/manifest.mjs +22 -13
- package/lib/roles/router.mjs +1 -1
- package/lib/runtime-env.mjs +1 -1
- package/lib/scopes/enrich.mjs +67 -0
- package/lib/scopes/hooks.mjs +12 -0
- package/lib/{profiles → scopes}/lifecycle.mjs +70 -70
- package/lib/scopes/loader.mjs +104 -0
- package/lib/scopes/rebrand.mjs +32 -0
- package/lib/scopes/research-profile.mjs +16 -0
- package/lib/scopes/teams.mjs +98 -0
- package/lib/service-manager.mjs +1 -117
- package/lib/setup-prompts.mjs +1 -1
- package/lib/setup.mjs +17 -28
- package/lib/skills/composition-graph.mjs +98 -0
- package/lib/skills/router.mjs +80 -0
- package/lib/specialist-contracts.mjs +17 -18
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/specialists/prompt-schema.mjs +6 -5
- package/lib/specialists/roster.mjs +7 -12
- package/lib/specialists/schema.mjs +9 -6
- package/lib/status.mjs +25 -90
- package/lib/storage/file-lock.mjs +6 -3
- package/lib/telemetry/skill-calls.mjs +1 -1
- package/lib/templates/doc-presentation.mjs +63 -0
- package/lib/templates/visual-requirements.mjs +35 -2
- package/lib/term-format.mjs +107 -2
- package/lib/test-env-setup.mjs +21 -0
- package/lib/ui/components.mjs +42 -0
- package/lib/ui/glyphs.mjs +58 -0
- package/lib/ui/links.mjs +115 -0
- package/lib/ui/theme.mjs +108 -0
- package/lib/uninstall/uninstall.mjs +2 -1
- package/lib/validator.mjs +45 -8
- package/lib/validators/skill-effectiveness.mjs +174 -0
- package/lib/validators/skills.mjs +1 -1
- package/package.json +12 -7
- package/personas/construct.md +12 -3
- package/platforms/claude/CLAUDE.md +1 -1
- package/rules/common/beads-hygiene.md +52 -0
- package/rules/common/neurodivergent-output.md +4 -7
- package/rules/common/research.md +2 -0
- package/scripts/sync-specialists.mjs +260 -49
- package/skills/ai/prompt-optimizer.md +3 -3
- package/skills/brand/output-vibe.md +48 -0
- package/skills/docs/adr-workflow.md +3 -0
- package/skills/docs/backlog-proposal-workflow.md +3 -0
- package/skills/docs/codebase-research-workflow.md +5 -2
- package/skills/docs/customer-profile-workflow.md +3 -0
- package/skills/docs/document-ingest-workflow.md +3 -0
- package/skills/docs/evidence-ingest-workflow.md +4 -1
- package/skills/docs/init-project.md +1 -1
- package/skills/docs/prd-workflow.md +1 -1
- package/skills/docs/prfaq-workflow.md +3 -0
- package/skills/docs/product-intelligence-workflow.md +4 -1
- package/skills/docs/product-signal-workflow.md +3 -0
- package/skills/docs/research-workflow.md +13 -6
- package/skills/docs/runbook-workflow.md +3 -0
- package/skills/docs/strategy-workflow.md +3 -0
- package/skills/docs/user-research-workflow.md +6 -3
- package/skills/operating/orchestration-reference.md +1 -1
- package/skills/roles/{engineer.ai.md → ai-engineer.md} +2 -2
- package/skills/roles/architect.ai-systems.md +1 -1
- package/skills/roles/architect.data.md +1 -1
- package/skills/roles/architect.enterprise.md +1 -1
- package/skills/roles/architect.integration.md +1 -1
- package/skills/roles/architect.md +1 -1
- package/skills/roles/architect.platform.md +1 -1
- package/skills/roles/{product-manager.business-strategy.md → business-strategist.md} +3 -3
- package/skills/roles/data-analyst.experiment.md +1 -1
- package/skills/roles/data-analyst.md +1 -1
- package/skills/roles/data-analyst.product-intelligence.md +1 -1
- package/skills/roles/data-analyst.product.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/{engineer.data.md → data-engineer.md} +2 -2
- package/skills/roles/data-engineer.pipeline.md +2 -2
- package/skills/roles/data-engineer.vector-retrieval.md +2 -2
- package/skills/roles/data-engineer.warehouse.md +2 -2
- package/skills/roles/debugger.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +1 -1
- package/skills/roles/{reviewer.devil-advocate.md → devil-advocate.md} +2 -2
- package/skills/roles/{operator.docs.md → docs-keeper.md} +3 -3
- package/skills/roles/engineer.md +3 -7
- package/skills/roles/{reviewer.evaluator.md → evaluator.md} +2 -2
- package/skills/roles/{researcher.explorer.md → explorer.md} +2 -2
- package/skills/roles/{operator.md → operations.md} +2 -5
- package/skills/roles/orchestrator.md +1 -1
- package/skills/roles/{engineer.platform.md → platform-engineer.md} +2 -2
- package/skills/roles/product-manager.ai-product.md +1 -1
- package/skills/roles/product-manager.enterprise.md +1 -1
- package/skills/roles/product-manager.growth.md +1 -2
- package/skills/roles/product-manager.md +1 -2
- package/skills/roles/product-manager.platform.md +1 -1
- package/skills/roles/product-manager.product.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -1
- package/skills/roles/qa.api-contract.md +1 -1
- package/skills/roles/qa.data-pipeline.md +1 -1
- package/skills/roles/qa.md +1 -1
- package/skills/roles/qa.web-ui.md +1 -1
- package/skills/roles/{operator.release.md → release-manager.md} +3 -3
- package/skills/roles/researcher.md +1 -1
- package/skills/roles/reviewer.md +1 -1
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.appsec.md +1 -1
- package/skills/roles/security.cloud.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +1 -1
- package/skills/roles/security.privacy.md +1 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/{operator.sre.md → sre.md} +3 -3
- package/skills/roles/{qa.test-automation.md → test-automation.md} +2 -2
- package/skills/roles/{reviewer.trace.md → trace-reviewer.md} +2 -2
- package/skills/roles/{researcher.ux.md → ux-researcher.md} +2 -2
- package/skills/routing.json +18 -0
- package/skills/routing.md +1 -1
- package/specialists/artifact-manifest.json +2 -1
- package/specialists/audit-enrichments.json +14 -14
- package/specialists/org/contracts/accessibility-to-qa.json +37 -0
- package/specialists/org/contracts/any-to-business-strategist.json +57 -0
- package/specialists/org/contracts/any-to-debugger.json +38 -0
- package/specialists/org/contracts/any-to-designer.json +33 -0
- package/specialists/org/contracts/any-to-docs-keeper.json +34 -0
- package/specialists/org/contracts/any-to-explorer.json +39 -0
- package/specialists/org/contracts/any-to-sre-incident.json +33 -0
- package/specialists/org/contracts/any-to-trace-reviewer.json +32 -0
- package/specialists/org/contracts/architect-to-ai-engineer.json +32 -0
- package/specialists/org/contracts/architect-to-data-engineer.json +52 -0
- package/specialists/org/contracts/architect-to-devil-advocate.json +38 -0
- package/specialists/org/contracts/architect-to-engineer.json +34 -0
- package/specialists/org/contracts/architect-to-evaluator.json +59 -0
- package/specialists/org/contracts/architect-to-legal-compliance.json +55 -0
- package/specialists/org/contracts/architect-to-operations.json +45 -0
- package/specialists/org/contracts/architect-to-platform-engineer.json +42 -0
- package/specialists/org/contracts/business-strategist-to-product-manager.json +39 -0
- package/specialists/org/contracts/construct-to-orchestrator.json +36 -0
- package/specialists/org/contracts/construct-to-rd-lead.json +53 -0
- package/specialists/org/contracts/data-analyst-to-product-manager.json +43 -0
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +36 -0
- package/specialists/org/contracts/designer-to-accessibility.json +36 -0
- package/specialists/org/contracts/engineer-to-qa.json +34 -0
- package/specialists/org/contracts/engineer-to-reviewer.json +52 -0
- package/specialists/org/contracts/explorer-to-engineer.json +38 -0
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +43 -0
- package/specialists/org/contracts/platform-engineer-to-engineer.json +31 -0
- package/specialists/org/contracts/product-manager-to-architect.json +71 -0
- package/specialists/org/contracts/product-manager-to-data-analyst.json +49 -0
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +51 -0
- package/specialists/org/contracts/qa-to-release-manager.json +42 -0
- package/specialists/org/contracts/qa-to-test-automation.json +42 -0
- package/specialists/org/contracts/rd-lead-to-architect.json +38 -0
- package/specialists/org/contracts/researcher-to-architect.json +40 -0
- package/specialists/org/contracts/researcher-to-product-manager.json +57 -0
- package/specialists/org/contracts/reviewer-to-security.json +41 -0
- package/specialists/org/contracts/sre-to-release-manager.json +36 -0
- package/specialists/org/contracts/test-automation-to-engineer.json +34 -0
- package/specialists/org/contracts/trace-reviewer-to-sre.json +41 -0
- package/specialists/org/contracts/user-to-construct.json +30 -0
- package/specialists/org/groups/engineering-group.json +43 -0
- package/specialists/org/groups/governance-group.json +38 -0
- package/specialists/org/groups/operations-group.json +41 -0
- package/specialists/org/groups/product-group.json +46 -0
- package/specialists/org/groups/quality-group.json +42 -0
- package/specialists/org/groups/strategy-group.json +41 -0
- package/specialists/org/policies/action-approval.json +13 -0
- package/specialists/org/policies/agents-routing.json +13 -0
- package/specialists/org/policies/architecture.json +20 -0
- package/specialists/org/policies/bash-safety.json +13 -0
- package/specialists/org/policies/beads-hygiene.json +13 -0
- package/specialists/org/policies/bootstrap-state.json +13 -0
- package/specialists/org/policies/code-review.json +13 -0
- package/specialists/org/policies/coding-style.json +13 -0
- package/specialists/org/policies/comments.json +13 -0
- package/specialists/org/policies/commit-approval.json +13 -0
- package/specialists/org/policies/contract-preconditions.json +13 -0
- package/specialists/org/policies/deployment.json +20 -0
- package/specialists/org/policies/description.json +14 -0
- package/specialists/org/policies/design-approval.json +19 -0
- package/specialists/org/policies/doc-ownership.json +13 -0
- package/specialists/org/policies/file-path-fence.json +13 -0
- package/specialists/org/policies/framing.json +13 -0
- package/specialists/org/policies/git-workflow.json +13 -0
- package/specialists/org/policies/incident-response.json +15 -0
- package/specialists/org/policies/intake-triage.json +15 -0
- package/specialists/org/policies/neurodivergent-output.json +13 -0
- package/specialists/org/policies/no-fabrication.json +13 -0
- package/specialists/org/policies/patterns.json +13 -0
- package/specialists/org/policies/quality-gate-approval.json +17 -0
- package/specialists/org/policies/release-gates.json +13 -0
- package/specialists/org/policies/research-evidence.json +13 -0
- package/specialists/org/policies/review-before-change.json +13 -0
- package/specialists/org/policies/rollback.json +15 -0
- package/specialists/org/policies/scope-change.json +20 -0
- package/specialists/org/policies/secret-scan.json +13 -0
- package/specialists/org/policies/security-approval.json +17 -0
- package/specialists/org/policies/security.json +13 -0
- package/specialists/org/policies/session-efficiency.json +13 -0
- package/specialists/org/policies/skill-routing.json +13 -0
- package/specialists/org/policies/strategic-prioritization.json +17 -0
- package/specialists/org/policies/testing.json +13 -0
- package/specialists/org/policies/tool-invisibility.json +14 -0
- package/specialists/org/scopes/creative.json +54 -0
- package/specialists/org/scopes/operations.json +51 -0
- package/specialists/org/scopes/research.json +60 -0
- package/specialists/org/scopes/rnd.json +81 -0
- package/specialists/org/specialists/cx-accessibility.json +69 -0
- package/specialists/org/specialists/cx-ai-engineer.json +75 -0
- package/specialists/org/specialists/cx-architect.json +89 -0
- package/specialists/org/specialists/cx-business-strategist.json +72 -0
- package/specialists/org/specialists/cx-data-analyst.json +68 -0
- package/specialists/org/specialists/cx-data-engineer.json +71 -0
- package/specialists/org/specialists/cx-debugger.json +75 -0
- package/specialists/org/specialists/cx-designer.json +85 -0
- package/specialists/org/specialists/cx-devil-advocate.json +71 -0
- package/specialists/org/specialists/cx-docs-keeper.json +82 -0
- package/specialists/org/specialists/cx-engineer.json +106 -0
- package/specialists/org/specialists/cx-evaluator.json +69 -0
- package/specialists/org/specialists/cx-explorer.json +69 -0
- package/specialists/org/specialists/cx-legal-compliance.json +74 -0
- package/specialists/org/specialists/cx-operations.json +72 -0
- package/specialists/org/specialists/cx-oracle.json +46 -0
- package/specialists/org/specialists/cx-orchestrator.json +81 -0
- package/specialists/org/specialists/cx-platform-engineer.json +80 -0
- package/specialists/org/specialists/cx-product-manager.json +102 -0
- package/specialists/org/specialists/cx-qa.json +79 -0
- package/specialists/org/specialists/cx-rd-lead.json +73 -0
- package/specialists/org/specialists/cx-release-manager.json +77 -0
- package/specialists/org/specialists/cx-researcher.json +82 -0
- package/specialists/org/specialists/cx-reviewer.json +71 -0
- package/specialists/org/specialists/cx-security.json +91 -0
- package/specialists/org/specialists/cx-sre.json +94 -0
- package/specialists/org/specialists/cx-test-automation.json +69 -0
- package/specialists/org/specialists/cx-trace-reviewer.json +69 -0
- package/specialists/org/specialists/cx-ux-researcher.json +68 -0
- package/specialists/org/teams/accessibility-team.json +39 -0
- package/specialists/org/teams/design-team.json +40 -0
- package/specialists/org/teams/engineering-team.json +50 -0
- package/specialists/org/teams/governance-team.json +41 -0
- package/specialists/org/teams/operations-team.json +46 -0
- package/specialists/org/teams/product-management-team.json +45 -0
- package/specialists/org/teams/quality-team.json +49 -0
- package/specialists/org/teams/research-team.json +39 -0
- package/specialists/org/teams/strategy-team.json +48 -0
- package/specialists/org/teams/ux-research-team.json +39 -0
- package/specialists/prompts/_shared/validation-contract.md +1 -1
- package/specialists/prompts/_team-template.md +10 -0
- package/specialists/prompts/cx-accessibility.md +1 -0
- package/specialists/prompts/cx-ai-engineer.md +1 -1
- package/specialists/prompts/cx-business-strategist.md +1 -1
- package/specialists/prompts/cx-data-engineer.md +1 -1
- package/specialists/prompts/cx-designer.md +1 -0
- package/specialists/prompts/cx-devil-advocate.md +1 -1
- package/specialists/prompts/cx-docs-keeper.md +1 -1
- package/specialists/prompts/cx-evaluator.md +1 -1
- package/specialists/prompts/cx-explorer.md +1 -1
- package/specialists/prompts/cx-operations.md +1 -1
- package/specialists/prompts/cx-oracle.md +7 -3
- package/specialists/prompts/cx-orchestrator.md +17 -1
- package/specialists/prompts/cx-platform-engineer.md +1 -1
- package/specialists/prompts/cx-product-manager.md +2 -0
- package/specialists/prompts/cx-release-manager.md +1 -1
- package/specialists/prompts/cx-researcher.md +7 -4
- package/specialists/prompts/cx-sre.md +1 -1
- package/specialists/prompts/cx-test-automation.md +2 -2
- package/specialists/prompts/cx-trace-reviewer.md +3 -3
- package/specialists/prompts/cx-ux-researcher.md +2 -1
- package/templates/demos/scripts/architecture-review-adr.json +30 -0
- package/templates/demos/scripts/capability-contract.json +25 -0
- package/templates/demos/scripts/intake-triage.json +25 -0
- package/templates/demos/scripts/profile-doctor-health.json +25 -0
- package/templates/demos/tapes/agentic-platforms-prd.tape +2 -2
- package/templates/demos/tapes/architecture-review-adr.tape +44 -0
- package/templates/demos/tapes/capability-contract.tape +39 -0
- package/templates/demos/tapes/intake-triage.tape +39 -0
- package/templates/demos/tapes/profile-doctor-health.tape +39 -0
- package/templates/distribution/construct-brand.typ +23 -18
- package/templates/distribution/construct-decision.typ +1 -1
- package/templates/distribution/construct-pdf.typ +1 -1
- package/templates/distribution/construct-prd.typ +1 -1
- package/templates/distribution/construct-research.typ +1 -1
- package/templates/distribution/mermaid-puppeteer.json +8 -0
- package/templates/docs/persona-artifact.md +1 -1
- package/templates/docs/prd.md +6 -0
- package/lib/boundary.mjs +0 -127
- package/lib/certification/dashboard-api.mjs +0 -71
- package/lib/chat/cli.mjs +0 -333
- package/lib/chat/command-suggest.mjs +0 -161
- package/lib/chat/commands.mjs +0 -215
- package/lib/chat/config.mjs +0 -142
- package/lib/chat/context-compactor.mjs +0 -250
- package/lib/chat/context-continuation.mjs +0 -253
- package/lib/chat/continuation-source.mjs +0 -58
- package/lib/chat/demo-guide.mjs +0 -61
- package/lib/chat/design-tokens.mjs +0 -91
- package/lib/chat/desktop-binary.mjs +0 -81
- package/lib/chat/desktop-build.mjs +0 -130
- package/lib/chat/desktop-launcher.mjs +0 -133
- package/lib/chat/evidence.mjs +0 -145
- package/lib/chat/export.mjs +0 -74
- package/lib/chat/harness/driver.mjs +0 -91
- package/lib/chat/list-picker.mjs +0 -112
- package/lib/chat/model-picker.mjs +0 -356
- package/lib/chat/openrouter-fallback.mjs +0 -151
- package/lib/chat/permission-prompt.mjs +0 -33
- package/lib/chat/picker-catalog.mjs +0 -45
- package/lib/chat/policy-telemetry.mjs +0 -34
- package/lib/chat/present.mjs +0 -246
- package/lib/chat/session-context.mjs +0 -39
- package/lib/chat/session-persist.mjs +0 -73
- package/lib/chat/session-restore.mjs +0 -71
- package/lib/chat/session-settings.mjs +0 -53
- package/lib/chat/system-prompt.mjs +0 -52
- package/lib/chat/transparency.mjs +0 -93
- package/lib/chat/tui/color-scheme.mjs +0 -42
- package/lib/chat/tui/markdown.mjs +0 -123
- package/lib/chat/tui/presentation.mjs +0 -100
- package/lib/chat/tui/render.mjs +0 -500
- package/lib/chat/tui/turn-block.mjs +0 -284
- package/lib/chat/tui/turn-present.mjs +0 -18
- package/lib/chat/tui/turn-state.mjs +0 -88
- package/lib/chat/tui/usage.mjs +0 -122
- package/lib/chat/web-commands.mjs +0 -146
- package/lib/chat/web-launcher.mjs +0 -63
- package/lib/chat/web-picker-keys.mjs +0 -46
- package/lib/chat/web-session.mjs +0 -159
- package/lib/config/alias.mjs +0 -57
- package/lib/dashboard-demo.mjs +0 -71
- package/lib/dashboard-static.mjs +0 -175
- package/lib/install/desktop-binary-download.mjs +0 -88
- package/lib/profiles/loader.mjs +0 -123
- package/lib/profiles/rebrand.mjs +0 -46
- package/lib/server/auth.mjs +0 -169
- package/lib/server/chat-loop.mjs +0 -622
- package/lib/server/chat.mjs +0 -336
- package/lib/server/cors.mjs +0 -77
- package/lib/server/csrf.mjs +0 -103
- package/lib/server/demo-preview.mjs +0 -63
- package/lib/server/index.mjs +0 -2641
- package/lib/server/insights.mjs +0 -780
- package/lib/server/langfuse-login.mjs +0 -58
- package/lib/server/rate-limit.mjs +0 -93
- package/lib/server/telemetry-login.mjs +0 -100
- package/lib/server/webhook.mjs +0 -512
- package/specialists/contracts.json +0 -1032
- package/specialists/contracts.schema.json +0 -83
- package/specialists/policy-inventory.json +0 -188
- package/specialists/registry.json +0 -1412
- package/specialists/role-manifests.json +0 -217
- package/specialists/teams.json +0 -94
package/lib/server/webhook.mjs
DELETED
|
@@ -1,512 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/server/webhook.mjs — Webhook ingestion + Slack slash command handler.
|
|
3
|
-
*
|
|
4
|
-
* Receives inbound webhook payloads from external providers (GitHub, Jira,
|
|
5
|
-
* Slack, Confluence, etc.), normalises them through the provider's
|
|
6
|
-
* `normalizeWebhook` capability, and routes the resulting event to:
|
|
7
|
-
*
|
|
8
|
-
* 1. The approval queue — if the event matches a configured approval rule.
|
|
9
|
-
* 2. A snapshot trigger — if the event is tagged as a significant change.
|
|
10
|
-
* 3. The SSE notification stream — so the dashboard updates in real-time.
|
|
11
|
-
*
|
|
12
|
-
* Route: POST /api/webhooks/:provider
|
|
13
|
-
*
|
|
14
|
-
* Security:
|
|
15
|
-
* Each provider validates its own signature. The handler calls
|
|
16
|
-
* provider.verifyWebhookSignature(payload, headers) before processing.
|
|
17
|
-
* Requests that fail signature verification are rejected with 401.
|
|
18
|
-
*
|
|
19
|
-
* Provider registration:
|
|
20
|
-
* Providers are loaded lazily from providers/<name>/index.mjs. A provider
|
|
21
|
-
* is eligible for webhook ingestion if it declares 'webhook' in its
|
|
22
|
-
* capabilities array and exports normalizeWebhook.
|
|
23
|
-
*
|
|
24
|
-
* Slack slash commands:
|
|
25
|
-
* Route: POST /api/slack/commands
|
|
26
|
-
* Requires SLACK_SIGNING_SECRET in config.env.
|
|
27
|
-
* Set the Slack app's slash command Request URL to:
|
|
28
|
-
* https://<your-domain>/api/slack/commands
|
|
29
|
-
*
|
|
30
|
-
* Supported commands:
|
|
31
|
-
* /construct snapshot — run a live embed snapshot and post summary
|
|
32
|
-
* /construct risks — surface escalating risks from the knowledge base
|
|
33
|
-
* /construct plan — post the current plan.md workflow summary
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
import { createHmac, timingSafeEqual } from 'crypto';
|
|
37
|
-
import { existsSync, readFileSync } from 'fs';
|
|
38
|
-
import { join } from 'path';
|
|
39
|
-
import { fileURLToPath } from 'url';
|
|
40
|
-
import { homedir as osHomedir } from 'os';
|
|
41
|
-
import { configDir } from '../config/xdg.mjs';
|
|
42
|
-
|
|
43
|
-
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
44
|
-
const ROOT_DIR = join(__dirname, '..', '..');
|
|
45
|
-
const HOME = osHomedir();
|
|
46
|
-
|
|
47
|
-
// ── Provider loader ────────────────────────────────────────────────────────
|
|
48
|
-
|
|
49
|
-
const _providerCache = new Map();
|
|
50
|
-
|
|
51
|
-
async function loadProvider(name) {
|
|
52
|
-
if (_providerCache.has(name)) return _providerCache.get(name);
|
|
53
|
-
const providerPath = join(ROOT_DIR, 'providers', name, 'index.mjs');
|
|
54
|
-
if (!existsSync(providerPath)) return null;
|
|
55
|
-
try {
|
|
56
|
-
const mod = await import(providerPath);
|
|
57
|
-
_providerCache.set(name, mod);
|
|
58
|
-
return mod;
|
|
59
|
-
} catch {
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ── Signature verification helpers ────────────────────────────────────────
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* GitHub: X-Hub-Signature-256: sha256=<hex>
|
|
68
|
-
*/
|
|
69
|
-
function verifyGitHubSignature(body, headers, secret) {
|
|
70
|
-
const sig = headers['x-hub-signature-256'];
|
|
71
|
-
if (!sig || !secret) return !secret; // open if no secret configured
|
|
72
|
-
const expected = 'sha256=' + createHmac('sha256', secret).update(body).digest('hex');
|
|
73
|
-
try {
|
|
74
|
-
return timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
|
|
75
|
-
} catch {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Jira: no standard HMAC — verify via shared token in X-Atlassian-Token header.
|
|
82
|
-
*/
|
|
83
|
-
function verifyJiraSignature(body, headers, secret) {
|
|
84
|
-
if (!secret) return true;
|
|
85
|
-
const token = headers['x-atlassian-token'] || headers['x-webhook-token'];
|
|
86
|
-
if (!token) return false;
|
|
87
|
-
try {
|
|
88
|
-
return timingSafeEqual(Buffer.from(token), Buffer.from(secret));
|
|
89
|
-
} catch {
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Slack: X-Slack-Signature: v0=<hex> with X-Slack-Request-Timestamp
|
|
96
|
-
*/
|
|
97
|
-
function verifySlackSignature(body, headers, secret) {
|
|
98
|
-
const sig = headers['x-slack-signature'];
|
|
99
|
-
const ts = headers['x-slack-request-timestamp'];
|
|
100
|
-
if (!sig || !ts || !secret) return !secret;
|
|
101
|
-
// Reject requests older than 5 minutes
|
|
102
|
-
if (Math.abs(Date.now() / 1000 - parseInt(ts, 10)) > 300) return false;
|
|
103
|
-
const baseString = `v0:${ts}:${body}`;
|
|
104
|
-
const expected = 'v0=' + createHmac('sha256', secret).update(baseString).digest('hex');
|
|
105
|
-
try {
|
|
106
|
-
return timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
|
|
107
|
-
} catch {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const SIGNATURE_VERIFIERS = {
|
|
113
|
-
github: verifyGitHubSignature,
|
|
114
|
-
jira: verifyJiraSignature,
|
|
115
|
-
slack: verifySlackSignature,
|
|
116
|
-
confluence: verifyJiraSignature, // same Atlassian token scheme
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
// ── Event classification ────────────────────────────────────────────────────
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Returns { significant: boolean, reason: string } for a normalised event.
|
|
123
|
-
* Significant events trigger an immediate snapshot; others are logged only.
|
|
124
|
-
*/
|
|
125
|
-
function classifyEvent(providerName, event) {
|
|
126
|
-
const type = (event?.type || '').toLowerCase();
|
|
127
|
-
const SIGNIFICANT = [
|
|
128
|
-
'pr.merged', 'pr.closed',
|
|
129
|
-
'issue.created', 'issue.closed', 'issue.transitioned',
|
|
130
|
-
'push',
|
|
131
|
-
'message.posted',
|
|
132
|
-
'page.created', 'page.updated',
|
|
133
|
-
];
|
|
134
|
-
const significant = SIGNIFICANT.some(t => type === t || type.startsWith(t));
|
|
135
|
-
return { significant, reason: significant ? `${providerName}:${type} is a tracked event` : 'informational' };
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ── Main handler ────────────────────────────────────────────────────────────
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* @param {object} opts
|
|
142
|
-
* @param {ApprovalQueue} opts.approvalQueue
|
|
143
|
-
* @param {function} opts.notifyClients — triggers SSE broadcast
|
|
144
|
-
* @param {object} opts.webhookSecrets — { github: '...', slack: '...', ... }
|
|
145
|
-
*/
|
|
146
|
-
export function createWebhookHandler({ approvalQueue, notifyClients, webhookSecrets = {} }) {
|
|
147
|
-
return async function handleWebhook(req, res) {
|
|
148
|
-
// Extract provider name from path: /api/webhooks/:provider
|
|
149
|
-
const match = req.url.match(/\/api\/webhooks\/([a-z0-9_-]+)/i);
|
|
150
|
-
if (!match) {
|
|
151
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
152
|
-
res.end(JSON.stringify({ error: 'Missing provider name in path' }));
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const providerName = match[1].toLowerCase();
|
|
157
|
-
|
|
158
|
-
// Collect body
|
|
159
|
-
const chunks = [];
|
|
160
|
-
await new Promise((resolve, reject) => {
|
|
161
|
-
req.on('data', c => chunks.push(c));
|
|
162
|
-
req.on('end', resolve);
|
|
163
|
-
req.on('error', reject);
|
|
164
|
-
});
|
|
165
|
-
const rawBody = Buffer.concat(chunks).toString('utf8');
|
|
166
|
-
|
|
167
|
-
// Signature verification
|
|
168
|
-
const verifier = SIGNATURE_VERIFIERS[providerName];
|
|
169
|
-
const secret = webhookSecrets[providerName] || process.env[`WEBHOOK_SECRET_${providerName.toUpperCase()}`];
|
|
170
|
-
if (verifier && !verifier(rawBody, req.headers, secret)) {
|
|
171
|
-
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
172
|
-
res.end(JSON.stringify({ error: 'Webhook signature verification failed' }));
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Parse payload
|
|
177
|
-
let payload;
|
|
178
|
-
try {
|
|
179
|
-
payload = JSON.parse(rawBody || '{}');
|
|
180
|
-
} catch {
|
|
181
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
182
|
-
res.end(JSON.stringify({ error: 'Invalid JSON payload' }));
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Normalise via provider if available
|
|
187
|
-
let event = { type: 'unknown', raw: payload, provider: providerName, receivedAt: new Date().toISOString() };
|
|
188
|
-
const provider = await loadProvider(providerName);
|
|
189
|
-
if (provider?.normalizeWebhook) {
|
|
190
|
-
try {
|
|
191
|
-
event = { ...event, ...provider.normalizeWebhook(payload, req.headers) };
|
|
192
|
-
} catch {
|
|
193
|
-
// Normalisation failure is non-fatal — log and continue with raw event
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Classify and route
|
|
198
|
-
const { significant } = classifyEvent(providerName, event);
|
|
199
|
-
|
|
200
|
-
if (significant && approvalQueue) {
|
|
201
|
-
// Queue for embed awareness (not necessarily requiring human approval)
|
|
202
|
-
approvalQueue.enqueue({
|
|
203
|
-
action: `webhook.${providerName}.${event.type || 'event'}`,
|
|
204
|
-
payload: event,
|
|
205
|
-
source: `webhook:${providerName}`,
|
|
206
|
-
autoApprove: true, // informational enqueue; hybrid model decides actual approval need
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Notify dashboard clients
|
|
211
|
-
if (notifyClients) notifyClients();
|
|
212
|
-
|
|
213
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
214
|
-
res.end(JSON.stringify({ ok: true, provider: providerName, type: event.type, significant }));
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// ── Slack slash command handler ────────────────────────────────────────────
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Parse application/x-www-form-urlencoded body into an object.
|
|
222
|
-
*/
|
|
223
|
-
function parseFormBody(raw) {
|
|
224
|
-
const out = {};
|
|
225
|
-
for (const pair of raw.split('&')) {
|
|
226
|
-
const eq = pair.indexOf('=');
|
|
227
|
-
if (eq === -1) continue;
|
|
228
|
-
const k = decodeURIComponent(pair.slice(0, eq).replace(/\+/g, ' '));
|
|
229
|
-
const v = decodeURIComponent(pair.slice(eq + 1).replace(/\+/g, ' '));
|
|
230
|
-
out[k] = v;
|
|
231
|
-
}
|
|
232
|
-
return out;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Post a message back to Slack using response_url (no bot token required for
|
|
237
|
-
* slash command responses). Falls back to bot token + chat.postMessage if the
|
|
238
|
-
* response_url is absent (e.g. in tests).
|
|
239
|
-
*/
|
|
240
|
-
async function postSlackMessage(responseUrl, text, botToken) {
|
|
241
|
-
const body = JSON.stringify({ response_type: 'in_channel', text });
|
|
242
|
-
|
|
243
|
-
if (responseUrl) {
|
|
244
|
-
const { fetch: nodeFetch } = await import('node:http');
|
|
245
|
-
// Use built-in fetch (Node 18+) or a simple https post
|
|
246
|
-
const url = new URL(responseUrl);
|
|
247
|
-
const isHttps = url.protocol === 'https:';
|
|
248
|
-
const mod = isHttps ? await import('node:https') : await import('node:http');
|
|
249
|
-
return new Promise((resolve, reject) => {
|
|
250
|
-
const req = mod.request(responseUrl, {
|
|
251
|
-
method: 'POST',
|
|
252
|
-
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
|
|
253
|
-
}, (res) => {
|
|
254
|
-
res.resume();
|
|
255
|
-
res.on('end', resolve);
|
|
256
|
-
});
|
|
257
|
-
req.on('error', reject);
|
|
258
|
-
req.write(body);
|
|
259
|
-
req.end();
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (botToken) {
|
|
264
|
-
const https = await import('node:https');
|
|
265
|
-
const postBody = JSON.stringify({ text });
|
|
266
|
-
return new Promise((resolve, reject) => {
|
|
267
|
-
const req = https.request('https://slack.com/api/chat.postMessage', {
|
|
268
|
-
method: 'POST',
|
|
269
|
-
headers: {
|
|
270
|
-
'Content-Type': 'application/json',
|
|
271
|
-
'Authorization': `Bearer ${botToken}`,
|
|
272
|
-
'Content-Length': Buffer.byteLength(postBody),
|
|
273
|
-
},
|
|
274
|
-
}, (res) => { res.resume(); res.on('end', resolve); });
|
|
275
|
-
req.on('error', reject);
|
|
276
|
-
req.write(postBody);
|
|
277
|
-
req.end();
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Load config.env values as an object.
|
|
284
|
-
*/
|
|
285
|
-
function loadConfigEnv() {
|
|
286
|
-
const envFile = join(configDir(HOME), 'config.env');
|
|
287
|
-
if (!existsSync(envFile)) return {};
|
|
288
|
-
const lines = readFileSync(envFile, 'utf8').split('\n');
|
|
289
|
-
const out = {};
|
|
290
|
-
for (const line of lines) {
|
|
291
|
-
const trimmed = line.trim();
|
|
292
|
-
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
293
|
-
const eq = trimmed.indexOf('=');
|
|
294
|
-
if (eq === -1) continue;
|
|
295
|
-
out[trimmed.slice(0, eq).trim()] = trimmed.slice(eq + 1).trim().replace(/^['"]|['"]$/g, '');
|
|
296
|
-
}
|
|
297
|
-
return out;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Run an embed snapshot and return a formatted Slack message string.
|
|
302
|
-
*/
|
|
303
|
-
async function runSnapshotForSlack() {
|
|
304
|
-
try {
|
|
305
|
-
const env = { ...process.env, ...loadConfigEnv() };
|
|
306
|
-
const { ProviderRegistry } = await import('../embed/providers/registry.mjs');
|
|
307
|
-
const { SnapshotEngine, renderMarkdown } = await import('../embed/snapshot.mjs');
|
|
308
|
-
const { EMPTY_CONFIG } = await import('../embed/config.mjs');
|
|
309
|
-
|
|
310
|
-
const registry = await ProviderRegistry.fromEnv(env);
|
|
311
|
-
const { resolveAutoEmbedSources } = await import('../embed/auto-sources.mjs');
|
|
312
|
-
const sources = resolveAutoEmbedSources({ cwd: process.cwd(), env, registry });
|
|
313
|
-
if (!sources.length) return '⚠️ No embed sources configured. Add credentials to config.env.';
|
|
314
|
-
|
|
315
|
-
const config = { ...EMPTY_CONFIG, sources, snapshot: { maxItems: 10 } };
|
|
316
|
-
const engine = new SnapshotEngine(registry, config);
|
|
317
|
-
const snapshot = await engine.generate();
|
|
318
|
-
|
|
319
|
-
const totalItems = snapshot.sections.reduce((n, s) => n + s.items.length, 0);
|
|
320
|
-
const errorCount = snapshot.errors.length;
|
|
321
|
-
|
|
322
|
-
const lines = [`*Construct Snapshot* — ${new Date(snapshot.generatedAt).toLocaleString()}`];
|
|
323
|
-
lines.push(`${totalItems} items across ${snapshot.sections.length} sources${errorCount ? ` (${errorCount} errors)` : ''}`);
|
|
324
|
-
lines.push('');
|
|
325
|
-
|
|
326
|
-
for (const section of snapshot.sections) {
|
|
327
|
-
if (!section.items.length) continue;
|
|
328
|
-
lines.push(`*${section.provider}* (${section.items.length})`);
|
|
329
|
-
for (const item of section.items.slice(0, 5)) {
|
|
330
|
-
const title = item.title || item.summary || item.text || '(untitled)';
|
|
331
|
-
const url = item.url || item.html_url || '';
|
|
332
|
-
lines.push(url ? `• <${url}|${title}>` : `• ${title}`);
|
|
333
|
-
}
|
|
334
|
-
if (section.items.length > 5) lines.push(` _…and ${section.items.length - 5} more_`);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
if (errorCount) {
|
|
338
|
-
lines.push('');
|
|
339
|
-
lines.push(`⚠️ Errors: ${snapshot.errors.map(e => `${e.source}: ${e.error}`).join(', ')}`);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
return lines.join('\n');
|
|
343
|
-
} catch (err) {
|
|
344
|
-
return `❌ Snapshot failed: ${err.message}`;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Surface escalating risks from the knowledge base.
|
|
350
|
-
*/
|
|
351
|
-
async function runRisksForSlack() {
|
|
352
|
-
try {
|
|
353
|
-
const { buildTrendReport } = await import('../knowledge/trends.mjs');
|
|
354
|
-
const report = buildTrendReport(ROOT_DIR);
|
|
355
|
-
|
|
356
|
-
if (!report.escalatingRisks?.length && !report.decisionDrift?.length) {
|
|
357
|
-
return '✅ No escalating risks detected in the current knowledge base.';
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const lines = ['*Construct Risks*', ''];
|
|
361
|
-
|
|
362
|
-
if (report.escalatingRisks?.length) {
|
|
363
|
-
lines.push('*Escalating Risks*');
|
|
364
|
-
for (const r of report.escalatingRisks) {
|
|
365
|
-
lines.push(`• ${r.summary} _(↑${r.escalationScore}× — ${r.recentCount} recent)_`);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
if (report.decisionDrift?.length) {
|
|
370
|
-
lines.push('');
|
|
371
|
-
lines.push('*Decision Drift*');
|
|
372
|
-
for (const d of report.decisionDrift) {
|
|
373
|
-
lines.push(`• ${d.decision.summary} _(drift score ${d.driftScore})_`);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
return lines.join('\n');
|
|
378
|
-
} catch (err) {
|
|
379
|
-
return `❌ Risk analysis failed: ${err.message}`;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* Summarise plan.md as a Slack message.
|
|
385
|
-
*/
|
|
386
|
-
function runPlanForSlack() {
|
|
387
|
-
try {
|
|
388
|
-
const planPath = join(ROOT_DIR, 'plan.md');
|
|
389
|
-
if (!existsSync(planPath)) return '⚠️ No plan.md found in the project root.';
|
|
390
|
-
|
|
391
|
-
const content = readFileSync(planPath, 'utf8');
|
|
392
|
-
const lines = content.split('\n');
|
|
393
|
-
|
|
394
|
-
// Extract phase headers and their done/in-progress items
|
|
395
|
-
const output = ['*Construct Plan*', ''];
|
|
396
|
-
let currentPhase = null;
|
|
397
|
-
let phaseItems = [];
|
|
398
|
-
let inTable = false;
|
|
399
|
-
|
|
400
|
-
for (const line of lines) {
|
|
401
|
-
if (line.startsWith('## Phase') || line.startsWith('## Goal') || line.startsWith('## Next')) {
|
|
402
|
-
if (currentPhase && phaseItems.length) {
|
|
403
|
-
output.push(`*${currentPhase}*`);
|
|
404
|
-
output.push(...phaseItems.slice(0, 6));
|
|
405
|
-
if (phaseItems.length > 6) output.push(` _…and ${phaseItems.length - 6} more_`);
|
|
406
|
-
output.push('');
|
|
407
|
-
}
|
|
408
|
-
currentPhase = line.replace(/^#+\s*/, '').trim();
|
|
409
|
-
phaseItems = [];
|
|
410
|
-
inTable = false;
|
|
411
|
-
continue;
|
|
412
|
-
}
|
|
413
|
-
// Table rows with status
|
|
414
|
-
if (line.includes('| done |') || line.includes('| in-progress |') || line.includes('| blocked |')) {
|
|
415
|
-
const match = line.match(/\|\s*[\d.]+\s*\|\s*(.+?)\s*\|\s*(done|in-progress|blocked)\s*\|/);
|
|
416
|
-
if (match) {
|
|
417
|
-
const emoji = match[2] === 'done' ? '✅' : match[2] === 'in-progress' ? '🔄' : '🚫';
|
|
418
|
-
phaseItems.push(`${emoji} ${match[1].trim()}`);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// Flush last phase
|
|
424
|
-
if (currentPhase && phaseItems.length) {
|
|
425
|
-
output.push(`*${currentPhase}*`);
|
|
426
|
-
output.push(...phaseItems.slice(0, 6));
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// Extract next steps if present
|
|
430
|
-
const nextIdx = lines.findIndex(l => l.startsWith('## Next Steps') || l.startsWith('## Next'));
|
|
431
|
-
if (nextIdx !== -1) {
|
|
432
|
-
const nextLines = [];
|
|
433
|
-
for (let i = nextIdx + 1; i < Math.min(nextIdx + 10, lines.length); i++) {
|
|
434
|
-
if (lines[i].startsWith('#')) break;
|
|
435
|
-
const item = lines[i].trim();
|
|
436
|
-
if (item.startsWith('1.') || item.startsWith('2.') || item.startsWith('3.') || item.startsWith('-')) {
|
|
437
|
-
nextLines.push(item);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
if (nextLines.length) {
|
|
441
|
-
output.push('');
|
|
442
|
-
output.push('*Next Steps*');
|
|
443
|
-
output.push(...nextLines);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
return output.join('\n') || '_(Plan is empty)_';
|
|
448
|
-
} catch (err) {
|
|
449
|
-
return `❌ Could not read plan: ${err.message}`;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* @param {object} opts
|
|
455
|
-
* @param {object} opts.webhookSecrets
|
|
456
|
-
*/
|
|
457
|
-
export function createSlackCommandHandler({ webhookSecrets = {} } = {}) {
|
|
458
|
-
return async function handleSlackCommand(req, res) {
|
|
459
|
-
// Collect body
|
|
460
|
-
const chunks = [];
|
|
461
|
-
await new Promise((resolve, reject) => {
|
|
462
|
-
req.on('data', c => chunks.push(c));
|
|
463
|
-
req.on('end', resolve);
|
|
464
|
-
req.on('error', reject);
|
|
465
|
-
});
|
|
466
|
-
const rawBody = Buffer.concat(chunks).toString('utf8');
|
|
467
|
-
|
|
468
|
-
// Verify Slack signature
|
|
469
|
-
const env = loadConfigEnv();
|
|
470
|
-
const signingSecret = webhookSecrets.slack
|
|
471
|
-
|| env.SLACK_SIGNING_SECRET
|
|
472
|
-
|| process.env.SLACK_SIGNING_SECRET;
|
|
473
|
-
|
|
474
|
-
if (signingSecret && !verifySlackSignature(rawBody, req.headers, signingSecret)) {
|
|
475
|
-
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
476
|
-
res.end(JSON.stringify({ error: 'Invalid Slack signature' }));
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
const params = parseFormBody(rawBody);
|
|
481
|
-
const text = (params.text || '').trim().toLowerCase();
|
|
482
|
-
const responseUrl = params.response_url || '';
|
|
483
|
-
const botToken = env.SLACK_BOT_TOKEN || process.env.SLACK_BOT_TOKEN;
|
|
484
|
-
|
|
485
|
-
// Acknowledge immediately — Slack requires a 200 within 3 seconds
|
|
486
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
487
|
-
|
|
488
|
-
let ackText;
|
|
489
|
-
if (text === 'snapshot') ackText = '⏳ Running snapshot…';
|
|
490
|
-
else if (text === 'risks') ackText = '⏳ Analysing risks…';
|
|
491
|
-
else if (text === 'plan') ackText = '⏳ Loading plan…';
|
|
492
|
-
else {
|
|
493
|
-
res.end(JSON.stringify({
|
|
494
|
-
response_type: 'ephemeral',
|
|
495
|
-
text: 'Unknown command. Try: `/construct snapshot`, `/construct risks`, `/construct plan`',
|
|
496
|
-
}));
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
res.end(JSON.stringify({ response_type: 'in_channel', text: ackText }));
|
|
501
|
-
|
|
502
|
-
// Do the work after acknowledging
|
|
503
|
-
setImmediate(async () => {
|
|
504
|
-
let message;
|
|
505
|
-
if (text === 'snapshot') message = await runSnapshotForSlack();
|
|
506
|
-
else if (text === 'risks') message = await runRisksForSlack();
|
|
507
|
-
else message = runPlanForSlack();
|
|
508
|
-
|
|
509
|
-
await postSlackMessage(responseUrl, message, botToken).catch(() => {});
|
|
510
|
-
});
|
|
511
|
-
};
|
|
512
|
-
}
|