@geraldmaron/construct 1.0.0 → 1.0.2
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 +9 -6
- package/LICENSE +201 -21
- package/README.md +132 -257
- package/agents/contracts.json +387 -0
- package/agents/prompts/cx-accessibility.md +8 -0
- package/agents/prompts/cx-ai-engineer.md +92 -0
- package/agents/prompts/cx-architect.md +10 -2
- package/agents/prompts/cx-business-strategist.md +13 -0
- package/agents/prompts/cx-data-analyst.md +80 -0
- package/agents/prompts/cx-data-engineer.md +4 -0
- package/agents/prompts/cx-debugger.md +8 -0
- package/agents/prompts/cx-designer.md +19 -0
- package/agents/prompts/cx-devil-advocate.md +4 -0
- package/agents/prompts/cx-docs-keeper.md +128 -0
- package/agents/prompts/cx-engineer.md +13 -0
- package/agents/prompts/cx-evaluator.md +4 -0
- package/agents/prompts/cx-explorer.md +12 -0
- package/agents/prompts/cx-legal-compliance.md +13 -0
- package/agents/prompts/cx-operations.md +13 -0
- package/agents/prompts/cx-orchestrator.md +107 -4
- package/agents/prompts/cx-platform-engineer.md +75 -0
- package/agents/prompts/cx-product-manager.md +8 -0
- package/agents/prompts/cx-qa.md +100 -0
- package/agents/prompts/cx-rd-lead.md +13 -0
- package/agents/prompts/cx-release-manager.md +8 -0
- package/agents/prompts/cx-researcher.md +21 -1
- package/agents/prompts/cx-reviewer.md +8 -0
- package/agents/prompts/cx-security.md +104 -0
- package/agents/prompts/cx-sre.md +104 -0
- package/agents/prompts/cx-test-automation.md +4 -0
- package/agents/prompts/cx-trace-reviewer.md +7 -3
- package/agents/prompts/cx-ux-researcher.md +4 -0
- package/agents/registry.json +365 -90
- package/agents/role-manifests.json +217 -0
- package/bin/construct +3345 -141
- package/bin/construct-postinstall.mjs +87 -0
- package/commands/build/feature.md +6 -6
- package/commands/plan/feature.md +6 -5
- package/commands/plan/requirements.md +1 -1
- package/commands/ship/status.md +1 -1
- package/commands/work/drive.md +3 -3
- package/commands/work/optimize-prompts.md +3 -3
- package/db/{migrations → schema}/001_init.sql +2 -0
- package/db/schema/002_pgvector.sql +182 -0
- package/db/schema/003_intake.sql +47 -0
- package/examples/README.md +85 -0
- package/examples/internal/roles/architect/bad/clever-plan-without-contracts.md +30 -0
- package/examples/internal/roles/architect/golden/explicit-tradeoff-before-plan.md +23 -0
- package/examples/internal/roles/engineer/bad/speculative-abstraction.md +30 -0
- package/examples/internal/roles/engineer/golden/read-before-write.md +28 -0
- package/examples/internal/roles/orchestrator/bad/everything-becomes-multi-agent.md +29 -0
- package/examples/internal/roles/orchestrator/golden/minimal-dispatch.md +22 -0
- package/examples/internal/roles/qa/bad/coverage-theater.md +30 -0
- package/examples/internal/roles/qa/golden/regression-gate.md +23 -0
- package/examples/internal/roles/reviewer/bad/lgtm-without-verification.md +29 -0
- package/examples/internal/roles/reviewer/golden/find-structural-risk-first.md +28 -0
- package/examples/personas/construct/adversarial/ignore-instruction-to-skip-approval.md +22 -0
- package/examples/personas/construct/bad/commit-without-approval.md +30 -0
- package/examples/personas/construct/boundary/blocked-needs-main-input.md +29 -0
- package/examples/personas/construct/golden/branch-approval-before-mutation.md +36 -0
- package/examples/personas/construct/golden/focused-direct-answer.md +28 -0
- package/examples/provider-plugin/README.md +34 -0
- package/examples/provider-plugin/index.mjs +74 -0
- package/examples/provider-plugin/package.json +15 -0
- package/examples/seed-observations/README.md +38 -0
- package/examples/seed-observations/anti-patterns.md +44 -0
- package/examples/seed-observations/decisions.md +36 -0
- package/examples/seed-observations/patterns.md +42 -0
- package/lib/agent-contracts-enforce.mjs +158 -0
- package/lib/agent-contracts.mjs +231 -0
- package/lib/agents/postconditions.mjs +126 -0
- package/lib/agents/schema.mjs +124 -0
- package/lib/artifact-capture.mjs +183 -0
- package/lib/audit-trail.mjs +149 -0
- package/lib/auto-docs.mjs +245 -65
- package/lib/beads/auto-close.mjs +126 -0
- package/lib/beads/drift.mjs +171 -0
- package/lib/beads-automation.mjs +542 -0
- package/lib/beads-client.mjs +518 -0
- package/lib/beads-lock.mjs +377 -0
- package/lib/beads-optimistic.mjs +365 -0
- package/lib/bootstrap/built-ins.mjs +136 -0
- package/lib/bootstrap/lazy-install.mjs +161 -0
- package/lib/bootstrap/resources.mjs +120 -0
- package/lib/bootstrap.mjs +105 -0
- package/lib/cache-governor.js +213 -0
- package/lib/cache-strategy-anthropic.js +62 -0
- package/lib/cache-strategy-google.js +79 -0
- package/lib/cache-strategy-none.js +30 -0
- package/lib/cache-strategy-openai.js +48 -0
- package/lib/cache-strategy.js +91 -0
- package/lib/claude-allow.mjs +149 -0
- package/lib/cli-commands.mjs +413 -258
- package/lib/codex-config.mjs +3 -1
- package/lib/comment-lint.mjs +55 -6
- package/lib/completions.mjs +21 -6
- package/lib/config/alias.mjs +56 -0
- package/lib/config/project-config.mjs +335 -0
- package/lib/config/schema.mjs +159 -0
- package/lib/context-router.mjs +308 -0
- package/lib/cost-ledger.mjs +177 -0
- package/lib/cost.mjs +171 -10
- package/lib/dashboard-static.mjs +158 -0
- package/lib/deployment-mode.mjs +86 -0
- package/lib/deprecate.mjs +49 -0
- package/lib/dispatch-batch.js +183 -0
- package/lib/distill.mjs +21 -8
- package/lib/doc-stamp.mjs +164 -0
- package/lib/doc-verify.mjs +119 -0
- package/lib/docs-routing.mjs +89 -0
- package/lib/docs-verify.mjs +417 -0
- package/lib/doctor/audit.mjs +71 -0
- package/lib/doctor/cli.mjs +99 -0
- package/lib/doctor/escalate.mjs +29 -0
- package/lib/doctor/index.mjs +140 -0
- package/lib/doctor/report.mjs +170 -0
- package/lib/doctor/watchers/bd-watch.mjs +117 -0
- package/lib/doctor/watchers/cost.mjs +130 -0
- package/lib/doctor/watchers/disk.mjs +122 -0
- package/lib/doctor/watchers/handoffs.mjs +33 -0
- package/lib/doctor/watchers/process-pressure.mjs +60 -0
- package/lib/doctor/watchers/service-health.mjs +188 -0
- package/lib/document-extract.mjs +288 -0
- package/lib/document-ingest.mjs +230 -0
- package/lib/drop.mjs +282 -0
- package/lib/embed/approval-queue.mjs +176 -0
- package/lib/embed/artifact.mjs +349 -0
- package/lib/embed/authority-guard.mjs +155 -0
- package/lib/embed/cli.mjs +408 -0
- package/lib/embed/config.mjs +355 -0
- package/lib/embed/conflict-detection.mjs +264 -0
- package/lib/embed/customer-profiles.mjs +480 -0
- package/lib/embed/daemon.mjs +1309 -0
- package/lib/embed/demand-fetch.mjs +449 -0
- package/lib/embed/docs-lifecycle.mjs +349 -0
- package/lib/embed/inbox-live-watcher.mjs +119 -0
- package/lib/embed/inbox.mjs +343 -0
- package/lib/embed/intake-metrics.mjs +190 -0
- package/lib/embed/jobs/vector-sync.mjs +198 -0
- package/lib/embed/notifications.mjs +75 -0
- package/lib/embed/output.mjs +79 -0
- package/lib/embed/providers/github.mjs +295 -0
- package/lib/embed/providers/jira.mjs +192 -0
- package/lib/embed/providers/linear.mjs +186 -0
- package/lib/embed/providers/registry.mjs +115 -0
- package/lib/embed/providers/slack.mjs +203 -0
- package/lib/embed/recommendation-store.mjs +378 -0
- package/lib/embed/roadmap.mjs +374 -0
- package/lib/embed/role-framing.mjs +110 -0
- package/lib/embed/scheduler.mjs +99 -0
- package/lib/embed/semantic.mjs +325 -0
- package/lib/embed/snapshot.mjs +191 -0
- package/lib/embed/supervision.mjs +235 -0
- package/lib/embed/target-resolver.mjs +186 -0
- package/lib/embed/worker.mjs +62 -0
- package/lib/embed/workspaces.mjs +297 -0
- package/lib/engine/chunker-headings.mjs +110 -0
- package/lib/engine/compressor-heuristic.mjs +100 -0
- package/lib/engine/consolidate.mjs +287 -0
- package/lib/engine/contracts.mjs +126 -0
- package/lib/engine/defaults.mjs +129 -0
- package/lib/engine/eval-retrieval.mjs +148 -0
- package/lib/engine/fuser-rrf.mjs +62 -0
- package/lib/engine/index.mjs +37 -0
- package/lib/engine/registry.mjs +146 -0
- package/lib/engine/reranker-mmr.mjs +90 -0
- package/lib/engine/tokens.mjs +77 -0
- package/lib/entity-store.mjs +280 -0
- package/lib/env-config.mjs +69 -4
- package/lib/evals/retrieval-bench.mjs +159 -0
- package/lib/evaluator-optimizer.mjs +317 -0
- package/lib/features.mjs +159 -29
- package/lib/gates-audit.mjs +236 -0
- package/lib/git-hooks/prepare-commit-msg +58 -0
- package/lib/handoffs/cleanup.mjs +159 -0
- package/lib/handoffs/contract.mjs +162 -0
- package/lib/handoffs/inventory.mjs +120 -0
- package/lib/headhunt.mjs +28 -47
- package/lib/health-check.mjs +399 -0
- package/lib/hook-health.mjs +442 -0
- package/lib/hooks/_lib/log.mjs +82 -0
- package/lib/hooks/adaptive-lint.mjs +26 -2
- package/lib/hooks/agent-tracker.mjs +171 -14
- package/lib/hooks/audit-reads.mjs +109 -0
- package/lib/hooks/audit-trail.mjs +153 -0
- package/lib/hooks/bash-output-logger.mjs +71 -0
- package/lib/hooks/block-no-verify.mjs +41 -0
- package/lib/hooks/ci-status-check.mjs +82 -0
- package/lib/hooks/comment-lint.mjs +29 -7
- package/lib/hooks/config-protection.mjs +39 -18
- package/lib/hooks/context-watch.mjs +137 -0
- package/lib/hooks/context-window-recovery.mjs +4 -20
- package/lib/hooks/dep-audit.mjs +16 -0
- package/lib/hooks/doc-coupling-check.mjs +80 -0
- package/lib/hooks/edit-accumulator.mjs +3 -0
- package/lib/hooks/edit-error-recovery.mjs +3 -0
- package/lib/hooks/edit-guard.mjs +45 -4
- package/lib/hooks/env-check.mjs +3 -0
- package/lib/hooks/guard-bash.mjs +58 -1
- package/lib/hooks/mcp-audit.mjs +18 -20
- package/lib/hooks/mcp-health-check.mjs +36 -0
- package/lib/hooks/model-fallback.mjs +42 -56
- package/lib/hooks/policy-engine.mjs +209 -0
- package/lib/hooks/post-merge-docs-check.mjs +63 -0
- package/lib/hooks/pre-compact.mjs +4 -24
- package/lib/hooks/pre-push-gate.mjs +200 -37
- package/lib/hooks/proactive-activation.mjs +284 -0
- package/lib/hooks/read-tracker.mjs +27 -4
- package/lib/hooks/readme-age-check.mjs +77 -0
- package/lib/hooks/registry-sync.mjs +12 -5
- package/lib/hooks/scan-secrets.mjs +50 -7
- package/lib/hooks/session-optimize.mjs +311 -0
- package/lib/hooks/session-start.mjs +287 -28
- package/lib/hooks/stop-notify.mjs +236 -96
- package/lib/hooks/stop-typecheck.mjs +13 -1
- package/lib/hooks/test-watch.mjs +68 -0
- package/lib/host-capabilities.mjs +31 -10
- package/lib/init-docs.mjs +731 -291
- package/lib/init-unified.mjs +1000 -0
- package/lib/init-update.mjs +168 -0
- package/lib/init.mjs +107 -0
- package/lib/install/first-invocation.mjs +119 -0
- package/lib/install/stage-project.mjs +69 -0
- package/lib/intake/classify.mjs +278 -0
- package/lib/intake/feedback.mjs +273 -0
- package/lib/intake/filesystem-queue.mjs +158 -0
- package/lib/intake/intake-config.mjs +132 -0
- package/lib/intake/postgres-queue.mjs +198 -0
- package/lib/intake/prepare.mjs +139 -0
- package/lib/intake/queue.mjs +83 -0
- package/lib/intake/session-prelude.mjs +50 -0
- package/lib/integrations/intake-integrations.mjs +740 -0
- package/lib/intent-classifier.mjs +253 -0
- package/lib/knowledge/layout.mjs +72 -0
- package/lib/knowledge/rag.mjs +331 -0
- package/lib/knowledge/search.mjs +315 -0
- package/lib/knowledge/trends.mjs +261 -0
- package/lib/logger.mjs +85 -0
- package/lib/mcp/broker.mjs +124 -0
- package/lib/mcp/server.mjs +554 -854
- package/lib/mcp/tools/document.mjs +132 -0
- package/lib/mcp/tools/memory.mjs +209 -0
- package/lib/mcp/tools/project.mjs +349 -0
- package/lib/mcp/tools/skills.mjs +409 -0
- package/lib/mcp/tools/storage.mjs +60 -0
- package/lib/mcp/tools/telemetry.mjs +315 -0
- package/lib/mcp/tools/workflow.mjs +112 -0
- package/lib/mcp-catalog.json +54 -3
- package/lib/mcp-manager.mjs +96 -48
- package/lib/mcp-platform-config.mjs +22 -22
- package/lib/memory-stats.mjs +121 -0
- package/lib/mode-commands.mjs +124 -0
- package/lib/model-free-selector.mjs +184 -0
- package/lib/model-pricing.mjs +152 -0
- package/lib/model-registry.mjs +226 -0
- package/lib/model-router.mjs +362 -386
- package/lib/observation-store.mjs +391 -0
- package/lib/ollama-manager.mjs +428 -0
- package/lib/opencode-config.mjs +13 -3
- package/lib/opencode-runtime-plugin.mjs +70 -38
- package/lib/opencode-telemetry.mjs +64 -6
- package/lib/orchestration-policy.mjs +509 -6
- package/lib/overrides/resolver.mjs +207 -0
- package/lib/parity.mjs +147 -0
- package/lib/paths.mjs +33 -0
- package/lib/performance/generate.mjs +212 -0
- package/lib/plugin-registry.mjs +268 -0
- package/lib/policy/engine.mjs +130 -0
- package/lib/policy/unified-gates.mjs +96 -0
- package/lib/project-detection.mjs +129 -0
- package/lib/project-init-shared.mjs +272 -0
- package/lib/project-profile.mjs +447 -0
- package/lib/prompt-composer.js +434 -0
- package/lib/prompt-metadata.mjs +1 -1
- package/lib/provider-capabilities-anthropic.js +44 -0
- package/lib/provider-capabilities-deepseek.js +37 -0
- package/lib/provider-capabilities-generic.js +26 -0
- package/lib/provider-capabilities-google.js +47 -0
- package/lib/provider-capabilities-openai.js +45 -0
- package/lib/provider-capabilities.js +142 -0
- package/lib/providers/atlassian-confluence/index.mjs +103 -0
- package/lib/providers/atlassian-jira/index.mjs +100 -0
- package/lib/providers/auth-manager.mjs +126 -0
- package/lib/providers/circuit-breaker.mjs +124 -0
- package/lib/providers/contract.mjs +93 -0
- package/lib/providers/github/index.mjs +126 -0
- package/lib/providers/registry.mjs +184 -0
- package/lib/providers/salesforce/index.mjs +100 -0
- package/lib/providers/slack/index.mjs +80 -0
- package/lib/reflect.mjs +137 -0
- package/lib/research-lint.mjs +164 -0
- package/lib/resources/budget.mjs +259 -0
- package/lib/role-preload.mjs +21 -6
- package/lib/roles/approval-surface.mjs +54 -0
- package/lib/roles/cli.mjs +118 -0
- package/lib/roles/event-bus.mjs +79 -0
- package/lib/roles/fence.mjs +84 -0
- package/lib/roles/gateway.mjs +260 -0
- package/lib/roles/hook-emit.mjs +37 -0
- package/lib/roles/manifest.mjs +48 -0
- package/lib/roles/router.mjs +27 -0
- package/lib/runtime-pressure.mjs +360 -0
- package/lib/schema-artifact.mjs +134 -0
- package/lib/schema-infer.mjs +551 -0
- package/lib/server/auth.mjs +168 -0
- package/lib/server/chat.mjs +336 -0
- package/lib/server/cors.mjs +77 -0
- package/lib/server/csrf.mjs +91 -0
- package/lib/server/index.mjs +1927 -78
- package/lib/server/insights.mjs +765 -0
- package/lib/server/rate-limit.mjs +91 -0
- package/lib/server/static/assets/index-ab25c707.js +70 -0
- package/lib/server/static/assets/index-f0c80a2b.css +1 -0
- package/lib/server/static/index.html +12 -817
- package/lib/server/telemetry-login.mjs +108 -0
- package/lib/server/webhook.mjs +510 -0
- package/lib/service-manager.mjs +522 -58
- package/lib/services/pattern-promotion-service.mjs +167 -0
- package/lib/services/telemetry-backend.mjs +178 -0
- package/lib/session-store.mjs +374 -0
- package/lib/setup-prompts.mjs +96 -0
- package/lib/setup.mjs +523 -36
- package/lib/skills-apply.mjs +280 -0
- package/lib/skills-scope.mjs +118 -0
- package/lib/status.mjs +261 -70
- package/lib/storage/admin.mjs +355 -0
- package/lib/storage/backend.mjs +2 -1
- package/lib/storage/backup.mjs +347 -0
- package/lib/storage/embeddings-engine.mjs +133 -0
- package/lib/storage/embeddings-legacy.mjs +85 -0
- package/lib/storage/embeddings-local.mjs +108 -0
- package/lib/storage/embeddings-ollama.mjs +78 -0
- package/lib/storage/embeddings-openai.mjs +85 -0
- package/lib/storage/embeddings.mjs +92 -33
- package/lib/storage/file-lock.mjs +130 -0
- package/lib/storage/fusion.mjs +95 -0
- package/lib/storage/hybrid-query.mjs +34 -27
- package/lib/storage/migrations.mjs +187 -0
- package/lib/storage/postgres-backup.mjs +124 -0
- package/lib/storage/sql-store.mjs +5 -15
- package/lib/storage/state-source.mjs +12 -13
- package/lib/storage/store-version.mjs +115 -0
- package/lib/storage/sync.mjs +144 -35
- package/lib/storage/unified-storage.mjs +550 -0
- package/lib/storage/vector-client.mjs +286 -0
- package/lib/storage/vector-store.mjs +71 -30
- package/lib/task-graph/generate.mjs +135 -0
- package/lib/task-graph/schema.mjs +81 -0
- package/lib/task-graph/store.mjs +71 -0
- package/lib/telemetry/backends/local.mjs +62 -0
- package/lib/telemetry/backends/{langfuse.mjs → remote.mjs} +27 -14
- package/lib/telemetry/backfill.mjs +180 -0
- package/lib/telemetry/eval-datasets.mjs +203 -0
- package/lib/telemetry/{langfuse-ingest.mjs → ingest.mjs} +26 -20
- package/lib/telemetry/intent-verifications.mjs +86 -0
- package/lib/telemetry/llm-judge.mjs +350 -0
- package/lib/telemetry/model-pricing-catalog.mjs +557 -0
- package/lib/telemetry/setup.mjs +151 -0
- package/lib/telemetry/skill-calls.mjs +78 -0
- package/lib/telemetry/team-rollup.mjs +4 -4
- package/lib/token-engine.js +117 -0
- package/lib/token-estimator-anthropic.js +15 -0
- package/lib/token-estimator-deepseek.js +13 -0
- package/lib/token-estimator-default.js +13 -0
- package/lib/token-estimator-google.js +13 -0
- package/lib/token-estimator-openai.js +13 -0
- package/lib/toolkit-env.mjs +1 -1
- package/lib/tty-prompts.mjs +211 -0
- package/lib/uninstall/uninstall.mjs +423 -0
- package/lib/update.mjs +115 -0
- package/lib/upgrade.mjs +141 -0
- package/lib/validator.mjs +51 -2
- package/lib/validators/skills.mjs +142 -0
- package/lib/wireframe.mjs +422 -0
- package/lib/worker/entrypoint.mjs +241 -0
- package/lib/worker/evidence.mjs +107 -0
- package/lib/worker/run.mjs +154 -0
- package/lib/worker/trace.mjs +182 -0
- package/lib/workflow-state.mjs +14 -18
- package/package.json +21 -8
- package/personas/construct.md +53 -51
- package/platforms/claude/CLAUDE.md +1 -1
- package/platforms/claude/settings.template.json +115 -68
- package/platforms/opencode/config.template.json +3 -7
- package/rules/common/agents.md +11 -38
- package/rules/common/beads-hygiene.md +75 -0
- package/rules/common/code-review.md +11 -100
- package/rules/common/coding-style.md +5 -3
- package/rules/common/comments.md +30 -111
- package/rules/common/commit-approval.md +53 -0
- package/rules/common/cx-agent-routing.md +1 -0
- package/rules/common/development-workflow.md +23 -41
- package/rules/common/doc-ownership.md +54 -0
- package/rules/common/efficiency.md +57 -0
- package/rules/common/framing.md +76 -0
- package/rules/common/git-workflow.md +2 -4
- package/rules/common/patterns.md +3 -7
- package/rules/common/performance.md +15 -31
- package/rules/common/release-gates.md +69 -0
- package/rules/common/research.md +107 -0
- package/rules/common/security.md +6 -6
- package/rules/common/skill-composition.md +67 -0
- package/rules/common/testing.md +15 -27
- package/rules/golang/hooks.md +0 -4
- package/rules/policy/bootstrap.yaml +11 -0
- package/rules/policy/drive.yaml +8 -0
- package/rules/policy/task.yaml +9 -0
- package/rules/policy/workflow.yaml +9 -0
- package/rules/python/hooks.md +0 -4
- package/rules/swift/hooks.md +0 -4
- package/rules/typescript/hooks.md +0 -4
- package/rules/web/hooks.md +0 -2
- package/{sync-agents.mjs → scripts/sync-agents.mjs} +306 -61
- package/skills/ai/prompt-optimizer.md +7 -7
- package/skills/compliance/ai-disclosure.md +58 -0
- package/skills/compliance/data-privacy.md +46 -0
- package/skills/compliance/license-audit.md +40 -0
- package/skills/compliance/regulatory-review.md +61 -0
- package/skills/docs/document-ingest-workflow.md +52 -0
- package/skills/docs/evidence-ingest-workflow.md +9 -0
- package/skills/docs/init-docs.md +51 -18
- package/skills/docs/product-intelligence-workflow.md +12 -0
- package/skills/docs/product-signal-workflow.md +4 -0
- package/skills/docs/research-workflow.md +23 -8
- package/skills/docs/runbook-workflow.md +1 -1
- package/skills/operating/orchestration-reference.md +151 -0
- package/skills/roles/architect.md +5 -0
- package/skills/roles/engineer.md +32 -0
- package/skills/roles/operator.md +12 -0
- package/skills/roles/reviewer.md +23 -0
- package/skills/routing.md +1 -1
- package/templates/devcontainer/Dockerfile.devcontainer +38 -0
- package/templates/devcontainer/devcontainer.json +31 -0
- package/templates/distribution/bootstrap.ps1 +142 -0
- package/templates/distribution/bootstrap.sh +196 -0
- package/templates/distribution/run.mjs +187 -0
- package/templates/docs/adr.md +44 -8
- package/templates/docs/changelog-entry.md +43 -0
- package/templates/docs/construct_guide.md +149 -0
- package/templates/docs/evidence-brief.md +2 -2
- package/templates/docs/meta-prd.md +140 -19
- package/templates/docs/onboarding.md +57 -0
- package/templates/docs/prd.md +159 -15
- package/templates/docs/research-brief.md +4 -4
- package/templates/homebrew/construct.rb +67 -0
- package/langfuse/docker-compose.yml +0 -82
- package/lib/eval-harness.mjs +0 -59
- package/lib/hooks/bootstrap-guard.mjs +0 -90
- package/lib/hooks/console-warn.mjs +0 -43
- package/lib/hooks/continuation-enforcer.mjs +0 -72
- package/lib/hooks/drive-guard.mjs +0 -89
- package/lib/hooks/mcp-task-scope.mjs +0 -47
- package/lib/hooks/task-completed-guard.mjs +0 -43
- package/lib/hooks/teammate-idle-guard.mjs +0 -54
- package/lib/hooks/workflow-guard.mjs +0 -62
- package/lib/prompt-composer.mjs +0 -196
- package/lib/review.mjs +0 -429
- package/lib/server/static/app.js +0 -841
- package/lib/telemetry/langfuse-model-sync.mjs +0 -270
- package/rules/common/hooks.md +0 -35
- package/rules/zh/README.md +0 -113
- package/rules/zh/agents.md +0 -55
- package/rules/zh/code-review.md +0 -129
- package/rules/zh/coding-style.md +0 -53
- package/rules/zh/development-workflow.md +0 -49
- package/rules/zh/git-workflow.md +0 -29
- package/rules/zh/hooks.md +0 -35
- package/rules/zh/patterns.md +0 -36
- package/rules/zh/performance.md +0 -60
- package/rules/zh/security.md +0 -34
- package/rules/zh/testing.md +0 -34
package/lib/mcp/server.mjs
CHANGED
|
@@ -1,774 +1,60 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* lib/mcp/server.mjs — Construct MCP server
|
|
3
|
+
* lib/mcp/server.mjs — Construct MCP server: tool registry and request dispatcher.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Thin dispatcher only — all tool implementations live in lib/mcp/tools/*.mjs.
|
|
6
|
+
* Registers 40 tools across 7 modules: project, document, storage, skills, workflow, telemetry, memory.
|
|
6
7
|
* Consumed by Claude Code, OpenCode, and any MCP-compatible host.
|
|
7
8
|
*/
|
|
8
9
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
9
10
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
10
11
|
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
11
|
-
import {
|
|
12
|
-
import { join, resolve, dirname } from 'node:path';
|
|
12
|
+
import { resolve, dirname } from 'node:path';
|
|
13
13
|
import { fileURLToPath } from 'node:url';
|
|
14
|
-
import {
|
|
15
|
-
import { execSync } from 'node:child_process';
|
|
16
|
-
import * as langfuse from '../telemetry/backends/langfuse.mjs';
|
|
17
|
-
import { summarizePromptComposition } from '../prompt-composer.mjs';
|
|
18
|
-
import { enrichMetadataWithPrompt } from '../prompt-metadata.mjs';
|
|
19
|
-
import { readCurrentModels, resolveExecutionContractModelMetadata, selectModelTierForWorkCategory } from '../model-router.mjs';
|
|
14
|
+
import { realpathSync } from 'node:fs';
|
|
20
15
|
import { loadToolkitEnv } from '../toolkit-env.mjs';
|
|
21
16
|
import { loadConstructEnv } from '../env-config.mjs';
|
|
22
|
-
import { inspectContextState } from '../context-state.mjs';
|
|
23
17
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
addTasksFromPlan,
|
|
31
|
-
alignmentFindings,
|
|
32
|
-
createNeedsMainInputPacket,
|
|
33
|
-
initWorkflow,
|
|
34
|
-
inspectWorkflowHealth,
|
|
35
|
-
loadWorkflow,
|
|
36
|
-
summarizeWorkflow,
|
|
37
|
-
updateTask,
|
|
38
|
-
validateWorkflowState,
|
|
39
|
-
} from '../workflow-state.mjs';
|
|
40
|
-
import { buildPublicHealthSurface, buildStatus } from '../status.mjs';
|
|
41
|
-
import { routeRequest, requiresExecutiveApproval, TERMINAL_STATES } from '../orchestration-policy.mjs';
|
|
42
|
-
|
|
43
|
-
const SECRET_PATTERNS = [
|
|
44
|
-
{ name: 'Anthropic API key', pattern: /ANTHROPIC_API_KEY\s*=\s*(sk-ant-[a-zA-Z0-9\-_]{20,})/i },
|
|
45
|
-
{ name: 'OpenAI API key', pattern: /OPENAI_API_KEY\s*=\s*(sk-[a-zA-Z0-9]{40,})/i },
|
|
46
|
-
{ name: 'OpenRouter key', pattern: /(sk-or-v1-[a-zA-Z0-9]{40,})/ },
|
|
47
|
-
{ name: 'AWS access key', pattern: /(AKIA[0-9A-Z]{16})/ },
|
|
48
|
-
{ name: 'Private key (PEM)', pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/ },
|
|
49
|
-
{ name: 'GitHub personal access token', pattern: /(ghp_[a-zA-Z0-9]{36})/ },
|
|
50
|
-
{ name: 'GitHub Actions token', pattern: /(ghs_[a-zA-Z0-9]{36})/ },
|
|
51
|
-
{ name: 'Database URL with credentials', pattern: /DATABASE_URL\s*=\s*(postgresql:\/\/[^@]+:[^@]+@)/i },
|
|
52
|
-
];
|
|
53
|
-
|
|
54
|
-
const PLACEHOLDER_PATTERNS = [
|
|
55
|
-
/\.\.\./,
|
|
56
|
-
/YOUR_KEY/i,
|
|
57
|
-
/<[^>]+>/,
|
|
58
|
-
/^sk-\.\.\./,
|
|
59
|
-
/^pk-lf-\.\.\./,
|
|
60
|
-
/__[A-Z_]+__/,
|
|
61
|
-
];
|
|
62
|
-
|
|
63
|
-
function isPlaceholder(value) {
|
|
64
|
-
return PLACEHOLDER_PATTERNS.some((p) => p.test(value));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function isBinary(buffer) {
|
|
68
|
-
const len = Math.min(buffer.length, 512);
|
|
69
|
-
for (let i = 0; i < len; i++) {
|
|
70
|
-
if (buffer[i] === 0) return true;
|
|
71
|
-
}
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function exec(cmd, cwd) {
|
|
76
|
-
return execSync(cmd, { stdio: 'pipe', timeout: 10000, cwd }).toString().trim();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function readJSON(filePath) {
|
|
80
|
-
if (!existsSync(filePath)) return null;
|
|
81
|
-
try {
|
|
82
|
-
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
83
|
-
} catch {
|
|
84
|
-
return null;
|
|
18
|
+
// Apply config.env values to process.env, letting config.env win over shell env
|
|
19
|
+
// so telemetry/OpenRouter credentials are always correct regardless of host env.
|
|
20
|
+
{
|
|
21
|
+
const confEnv = loadConstructEnv({ warn: false });
|
|
22
|
+
for (const [k, v] of Object.entries(confEnv)) {
|
|
23
|
+
process.env[k] = v;
|
|
85
24
|
}
|
|
86
25
|
}
|
|
87
26
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const candidates = processed.length > 0 ? processed : raw;
|
|
114
|
-
|
|
115
|
-
if (candidates.length === 0) {
|
|
116
|
-
return { error: "No performance reviews found. Run 'construct review' to generate one." };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
let data;
|
|
120
|
-
try {
|
|
121
|
-
const content = readFileSync(join(reviewDir, candidates[0]), 'utf8');
|
|
122
|
-
data = JSON.parse(content);
|
|
123
|
-
} catch {
|
|
124
|
-
return { error: "Failed to parse performance review file." };
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const reviewDate = data.reviewDate ?? data.generatedAt ?? candidates[0].replace(/\.json$/, '');
|
|
128
|
-
const period = data.period ?? data.windowDays ?? null;
|
|
129
|
-
|
|
130
|
-
let agents = [];
|
|
131
|
-
|
|
132
|
-
if (Array.isArray(data.agents)) {
|
|
133
|
-
agents = data.agents;
|
|
134
|
-
} else if (data.agentReviews) {
|
|
135
|
-
agents = Object.entries(data.agentReviews).map(([name, review]) => ({
|
|
136
|
-
name,
|
|
137
|
-
status: review.status ?? review.health ?? 'unknown',
|
|
138
|
-
avgScore: review.avgScore ?? review.averageScore ?? null,
|
|
139
|
-
trend: review.trend ?? null,
|
|
140
|
-
failureRate: review.failureRate ?? null,
|
|
141
|
-
invocations: review.invocations ?? review.totalCalls ?? null,
|
|
142
|
-
}));
|
|
143
|
-
} else if (data.summary && Array.isArray(data.summary.agents)) {
|
|
144
|
-
agents = data.summary.agents;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (args.agent_name) {
|
|
148
|
-
const target = args.agent_name.toLowerCase();
|
|
149
|
-
agents = agents.filter((a) => (a.name ?? '').toLowerCase().includes(target));
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const normalized = agents.map((a) => ({
|
|
153
|
-
name: a.name ?? 'unknown',
|
|
154
|
-
status: a.status ?? 'unknown',
|
|
155
|
-
avgScore: a.avgScore ?? a.averageScore ?? null,
|
|
156
|
-
trend: a.trend ?? null,
|
|
157
|
-
failureRate: a.failureRate ?? null,
|
|
158
|
-
invocations: a.invocations ?? a.totalCalls ?? null,
|
|
159
|
-
}));
|
|
160
|
-
|
|
161
|
-
return { reviewDate, period, agents: normalized };
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Tool: summarize_diff
|
|
165
|
-
function summarizeDiff(args) {
|
|
166
|
-
const baseRef = args.base_ref ?? 'HEAD~1';
|
|
167
|
-
const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
|
|
168
|
-
|
|
169
|
-
let stat, nameStatus;
|
|
170
|
-
try {
|
|
171
|
-
stat = exec(`git diff --stat ${baseRef}`, cwd);
|
|
172
|
-
nameStatus = exec(`git diff --name-status ${baseRef}`, cwd);
|
|
173
|
-
} catch {
|
|
174
|
-
return { error: 'Not a git repository or git not available' };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const changes = nameStatus
|
|
178
|
-
.split('\n')
|
|
179
|
-
.filter(Boolean)
|
|
180
|
-
.map((line) => {
|
|
181
|
-
const [status, ...parts] = line.split('\t');
|
|
182
|
-
return { status: status.trim(), file: parts.join('\t') };
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
// Parse insertions/deletions from stat summary line
|
|
186
|
-
const statLines = stat.split('\n');
|
|
187
|
-
const summaryLine = statLines[statLines.length - 1] ?? '';
|
|
188
|
-
const filesMatch = summaryLine.match(/(\d+) files? changed/);
|
|
189
|
-
const insertionsMatch = summaryLine.match(/(\d+) insertions?\(\+\)/);
|
|
190
|
-
const deletionsMatch = summaryLine.match(/(\d+) deletions?\(-\)/);
|
|
191
|
-
|
|
192
|
-
const filesChanged = filesMatch ? parseInt(filesMatch[1], 10) : changes.length;
|
|
193
|
-
const insertions = insertionsMatch ? parseInt(insertionsMatch[1], 10) : 0;
|
|
194
|
-
const deletions = deletionsMatch ? parseInt(deletionsMatch[1], 10) : 0;
|
|
195
|
-
|
|
196
|
-
const added = changes.filter((c) => c.status === 'A').length;
|
|
197
|
-
const modified = changes.filter((c) => c.status === 'M').length;
|
|
198
|
-
const deleted = changes.filter((c) => c.status === 'D').length;
|
|
199
|
-
|
|
200
|
-
const parts = [];
|
|
201
|
-
if (modified > 0) parts.push(`${modified} file${modified > 1 ? 's' : ''} modified`);
|
|
202
|
-
if (added > 0) parts.push(`${added} file${added > 1 ? 's' : ''} added`);
|
|
203
|
-
if (deleted > 0) parts.push(`${deleted} file${deleted > 1 ? 's' : ''} deleted`);
|
|
204
|
-
|
|
205
|
-
const summary =
|
|
206
|
-
parts.length > 0
|
|
207
|
-
? `${parts.join(', ')} (+${insertions}/-${deletions} lines) vs ${baseRef}.`
|
|
208
|
-
: `No changes vs ${baseRef}.`;
|
|
209
|
-
|
|
210
|
-
return { base_ref: baseRef, files_changed: filesChanged, insertions, deletions, changes, summary };
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Tool: scan_file
|
|
214
|
-
function scanFile(args) {
|
|
215
|
-
const filePath = resolve(args.file_path);
|
|
216
|
-
|
|
217
|
-
let rawBuffer;
|
|
218
|
-
try {
|
|
219
|
-
rawBuffer = readFileSync(filePath);
|
|
220
|
-
} catch (err) {
|
|
221
|
-
return { error: `Cannot read file: ${err.message}` };
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (isBinary(rawBuffer)) {
|
|
225
|
-
return { file_path: filePath, secrets: [], quality_issues: [], clean: true };
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const content = rawBuffer.toString('utf8');
|
|
229
|
-
const lines = content.split('\n');
|
|
230
|
-
|
|
231
|
-
// Secret scanning
|
|
232
|
-
const secrets = [];
|
|
233
|
-
for (let i = 0; i < lines.length; i++) {
|
|
234
|
-
const line = lines[i];
|
|
235
|
-
for (const { name, pattern } of SECRET_PATTERNS) {
|
|
236
|
-
const match = pattern.exec(line);
|
|
237
|
-
if (!match) continue;
|
|
238
|
-
const captured = match[1] ?? match[0];
|
|
239
|
-
if (isPlaceholder(captured)) continue;
|
|
240
|
-
secrets.push({
|
|
241
|
-
pattern: name,
|
|
242
|
-
line: i + 1,
|
|
243
|
-
masked_value: captured.slice(0, 20) + '...',
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Quality scanning
|
|
249
|
-
const quality_issues = [];
|
|
250
|
-
|
|
251
|
-
if (lines.length > 800) {
|
|
252
|
-
quality_issues.push({ type: 'file_too_long', detail: `File has ${lines.length} lines (limit: 800)` });
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Detect oversized functions via simple heuristic
|
|
256
|
-
const funcKeywords = /^\s*(function\s+\w+|async\s+function\s+\w+|\w+\s*=\s*(?:async\s+)?\(|def\s+\w+|func\s+\w+)/;
|
|
257
|
-
let funcStart = null;
|
|
258
|
-
let braceDepth = 0;
|
|
259
|
-
let inBlock = false;
|
|
260
|
-
|
|
261
|
-
for (let i = 0; i < lines.length; i++) {
|
|
262
|
-
const line = lines[i];
|
|
263
|
-
|
|
264
|
-
if (funcKeywords.test(line)) {
|
|
265
|
-
funcStart = i + 1;
|
|
266
|
-
braceDepth = 0;
|
|
267
|
-
inBlock = false;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (funcStart !== null) {
|
|
271
|
-
for (const ch of line) {
|
|
272
|
-
if (ch === '{') { braceDepth++; inBlock = true; }
|
|
273
|
-
else if (ch === '}') { braceDepth--; }
|
|
274
|
-
}
|
|
275
|
-
if (inBlock && braceDepth <= 0) {
|
|
276
|
-
const funcLen = i + 1 - funcStart + 1;
|
|
277
|
-
if (funcLen > 50) {
|
|
278
|
-
quality_issues.push({
|
|
279
|
-
type: 'function_too_long',
|
|
280
|
-
detail: `Function starting at line ${funcStart} is ${funcLen} lines (limit: 50)`,
|
|
281
|
-
line: funcStart,
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
funcStart = null;
|
|
285
|
-
inBlock = false;
|
|
286
|
-
braceDepth = 0;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// TODO/FIXME/HACK comments
|
|
291
|
-
const todoMatch = /\/\/\s*(TODO|FIXME|HACK)\b|#\s*(TODO|FIXME|HACK)\b/i.exec(line);
|
|
292
|
-
if (todoMatch) {
|
|
293
|
-
const tag = (todoMatch[1] ?? todoMatch[2]).toUpperCase();
|
|
294
|
-
quality_issues.push({
|
|
295
|
-
type: 'comment_marker',
|
|
296
|
-
detail: `${tag} comment`,
|
|
297
|
-
line: i + 1,
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
return {
|
|
303
|
-
file_path: filePath,
|
|
304
|
-
secrets,
|
|
305
|
-
quality_issues,
|
|
306
|
-
clean: secrets.length === 0 && quality_issues.length === 0,
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Tool: project_context
|
|
311
|
-
function projectContext(args) {
|
|
312
|
-
const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
|
|
313
|
-
const contextInspection = inspectContextState(cwd);
|
|
314
|
-
const context_state = contextInspection.state;
|
|
315
|
-
const context = context_state?.markdown || null;
|
|
316
|
-
const has_context_file = contextInspection.hasFile;
|
|
317
|
-
const workflow = loadWorkflow(cwd);
|
|
318
|
-
const mergedEnv = loadConstructEnv({ rootDir: ROOT_DIR, homeDir: homedir(), env: process.env });
|
|
319
|
-
const registry = readJSON(join(ROOT_DIR, 'agents', 'registry.json')) ?? {};
|
|
320
|
-
const executionContractModel = resolveExecutionContractModelMetadata({
|
|
321
|
-
envValues: mergedEnv,
|
|
322
|
-
registryModels: registry.models ?? {},
|
|
323
|
-
requestedTier: selectModelTierForWorkCategory(workflow?.tasks?.find((task) => task.key === workflow?.currentTaskKey)?.workCategory),
|
|
324
|
-
workCategory: null,
|
|
325
|
-
});
|
|
326
|
-
const publicHealth = buildPublicHealthSurface({
|
|
327
|
-
cwd,
|
|
328
|
-
contextInspection,
|
|
329
|
-
workflow,
|
|
330
|
-
executionContractModel,
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
let recent_commits = [];
|
|
334
|
-
let working_tree_status = 'clean';
|
|
335
|
-
let changed_files = [];
|
|
336
|
-
|
|
337
|
-
try {
|
|
338
|
-
const log = exec('git log --oneline -10', cwd);
|
|
339
|
-
recent_commits = log.split('\n').filter(Boolean);
|
|
340
|
-
} catch {
|
|
341
|
-
// not a git repo or no commits
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
try {
|
|
345
|
-
const status = exec('git status --short', cwd);
|
|
346
|
-
const statusLines = status.split('\n').filter(Boolean);
|
|
347
|
-
changed_files = statusLines.map((l) => l.trim());
|
|
348
|
-
working_tree_status = statusLines.length > 0 ? 'dirty' : 'clean';
|
|
349
|
-
} catch {
|
|
350
|
-
// not a git repo
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
return { cwd, has_context_file, context, context_state, recent_commits, working_tree_status, changed_files, publicHealth };
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function orchestrationPolicy(args) {
|
|
357
|
-
const route = routeRequest(args || {});
|
|
358
|
-
const approvalRequired = requiresExecutiveApproval(args?.approval || {});
|
|
359
|
-
return {
|
|
360
|
-
...route,
|
|
361
|
-
approvalRequired,
|
|
362
|
-
terminalStates: TERMINAL_STATES,
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// Tool: list_skills
|
|
367
|
-
function listSkills() {
|
|
368
|
-
const skillsDir = join(ROOT_DIR, 'skills');
|
|
369
|
-
if (!existsSync(skillsDir)) return { error: 'Skills directory not found.' };
|
|
370
|
-
|
|
371
|
-
const listDirRecursive = (dir, prefix = '') => {
|
|
372
|
-
let results = [];
|
|
373
|
-
const entries = readdirSync(dir, { withFileTypes: true });
|
|
374
|
-
for (const entry of entries) {
|
|
375
|
-
if (entry.isDirectory()) {
|
|
376
|
-
results = results.concat(listDirRecursive(join(dir, entry.name), `${prefix}${entry.name}/`));
|
|
377
|
-
} else if (entry.name.endsWith('.md') && entry.name !== 'SKILL.md') {
|
|
378
|
-
results.push(`${prefix}${entry.name.replace('.md', '')}`);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
return results;
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
const skills = listDirRecursive(skillsDir).sort();
|
|
385
|
-
return { skills };
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Tool: get_skill
|
|
389
|
-
function getSkill(args) {
|
|
390
|
-
const { path: skillPath } = args;
|
|
391
|
-
if (!skillPath) return { error: 'Missing path argument' };
|
|
392
|
-
|
|
393
|
-
const fullPath = join(ROOT_DIR, 'skills', `${skillPath}.md`);
|
|
394
|
-
|
|
395
|
-
if (!existsSync(fullPath)) {
|
|
396
|
-
return { error: `Skill not found: ${skillPath}` };
|
|
397
|
-
}
|
|
398
|
-
const content = readFileSync(fullPath, 'utf8');
|
|
399
|
-
return { content };
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// Tool: get_template
|
|
403
|
-
function getTemplate(args) {
|
|
404
|
-
const { name } = args;
|
|
405
|
-
if (!name) return { error: 'Missing name argument' };
|
|
406
|
-
const safe = String(name).replace(/[^a-zA-Z0-9._-]/g, '');
|
|
407
|
-
if (!safe) return { error: 'Invalid template name' };
|
|
408
|
-
|
|
409
|
-
const cwd = process.cwd();
|
|
410
|
-
const candidates = [
|
|
411
|
-
{ source: 'project-override', path: join(cwd, '.cx', 'templates', 'docs', `${safe}.md`) },
|
|
412
|
-
{ source: 'shipped-default', path: join(ROOT_DIR, 'templates', 'docs', `${safe}.md`) },
|
|
413
|
-
];
|
|
414
|
-
for (const { source, path: p } of candidates) {
|
|
415
|
-
if (existsSync(p)) {
|
|
416
|
-
return { source, path: p, content: readFileSync(p, 'utf8') };
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
return { error: `Template not found: ${safe}`, available: listTemplatesRaw() };
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
function listTemplatesRaw() {
|
|
423
|
-
const shipped = [];
|
|
424
|
-
const override = [];
|
|
425
|
-
const shippedDir = join(ROOT_DIR, 'templates', 'docs');
|
|
426
|
-
if (existsSync(shippedDir)) {
|
|
427
|
-
for (const f of readdirSync(shippedDir)) {
|
|
428
|
-
if (f.endsWith('.md')) shipped.push(f.replace(/\.md$/, ''));
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
const overrideDir = join(process.cwd(), '.cx', 'templates', 'docs');
|
|
432
|
-
if (existsSync(overrideDir)) {
|
|
433
|
-
for (const f of readdirSync(overrideDir)) {
|
|
434
|
-
if (f.endsWith('.md')) override.push(f.replace(/\.md$/, ''));
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
return { shipped: shipped.sort(), overridden: override.sort() };
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// Tool: list_templates
|
|
441
|
-
function listTemplates() {
|
|
442
|
-
return listTemplatesRaw();
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// Tool: search_skills
|
|
446
|
-
function searchSkills(args) {
|
|
447
|
-
const { pattern } = args;
|
|
448
|
-
if (!pattern) return { error: 'Missing pattern argument' };
|
|
449
|
-
const skillsDir = join(ROOT_DIR, 'skills');
|
|
450
|
-
|
|
451
|
-
try {
|
|
452
|
-
// Try ripgrep first
|
|
453
|
-
const output = execSync(`rg -i "${pattern.replace(/"/g, '\\"')}" "${skillsDir}"`, { encoding: 'utf8' });
|
|
454
|
-
return { results: output.split('\n').filter(Boolean) };
|
|
455
|
-
} catch (err) {
|
|
456
|
-
// Fallback to basic grep
|
|
457
|
-
try {
|
|
458
|
-
const output = execSync(`grep -ri "${pattern.replace(/"/g, '\\"')}" "${skillsDir}"`, { encoding: 'utf8' });
|
|
459
|
-
return { results: output.split('\n').filter(Boolean) };
|
|
460
|
-
} catch (err2) {
|
|
461
|
-
return { results: [], note: 'No matches found or grep error' };
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
function workflowStatus(args) {
|
|
467
|
-
const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
|
|
468
|
-
const workflow = loadWorkflow(cwd);
|
|
469
|
-
const workflowHealth = inspectWorkflowHealth(workflow, { cwd });
|
|
470
|
-
const mergedEnv = loadConstructEnv({ rootDir: ROOT_DIR, homeDir: homedir(), env: process.env });
|
|
471
|
-
const registry = readJSON(join(ROOT_DIR, 'agents', 'registry.json')) ?? {};
|
|
472
|
-
const executionContractModel = resolveExecutionContractModelMetadata({
|
|
473
|
-
envValues: mergedEnv,
|
|
474
|
-
registryModels: registry.models ?? {},
|
|
475
|
-
requestedTier: selectModelTierForWorkCategory(workflow?.tasks?.find((task) => task.key === workflow?.currentTaskKey)?.workCategory),
|
|
476
|
-
workCategory: null,
|
|
477
|
-
});
|
|
478
|
-
const publicHealth = buildPublicHealthSurface({
|
|
479
|
-
cwd,
|
|
480
|
-
workflow,
|
|
481
|
-
executionContractModel,
|
|
482
|
-
});
|
|
483
|
-
return {
|
|
484
|
-
cwd,
|
|
485
|
-
exists: Boolean(workflow),
|
|
486
|
-
summary: summarizeWorkflow(workflow),
|
|
487
|
-
workflow,
|
|
488
|
-
alignment: {
|
|
489
|
-
status: workflowHealth.alignment.status,
|
|
490
|
-
findings: workflowHealth.alignment.findings,
|
|
491
|
-
},
|
|
492
|
-
publicHealth,
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
function workflowInit(args) {
|
|
497
|
-
const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
|
|
498
|
-
const title = args.title || 'Untitled workflow';
|
|
499
|
-
const { workflow, created } = initWorkflow(cwd, title, args.spec_ref ?? null);
|
|
500
|
-
return { cwd, created, workflow, summary: summarizeWorkflow(workflow) };
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// Tool: list_teams
|
|
504
|
-
function listTeams() {
|
|
505
|
-
const teamsPath = join(ROOT_DIR, 'agents', 'teams.json');
|
|
506
|
-
if (!existsSync(teamsPath)) return { error: 'agents/teams.json not found' };
|
|
507
|
-
const { templates } = JSON.parse(readFileSync(teamsPath, 'utf8'));
|
|
508
|
-
return {
|
|
509
|
-
teams: Object.entries(templates).map(([name, t]) => ({
|
|
510
|
-
name,
|
|
511
|
-
description: t.description,
|
|
512
|
-
members: t.members,
|
|
513
|
-
focus: t.focus,
|
|
514
|
-
skills: t.skills,
|
|
515
|
-
promotionGates: t.promotionGates,
|
|
516
|
-
})),
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// Tool: get_team
|
|
521
|
-
function getTeam(args) {
|
|
522
|
-
const { name } = args;
|
|
523
|
-
if (!name) return { error: 'Missing name argument' };
|
|
524
|
-
const teamsPath = join(ROOT_DIR, 'agents', 'teams.json');
|
|
525
|
-
if (!existsSync(teamsPath)) return { error: 'agents/teams.json not found' };
|
|
526
|
-
const { templates } = JSON.parse(readFileSync(teamsPath, 'utf8'));
|
|
527
|
-
const team = templates[name];
|
|
528
|
-
if (!team) return { error: `Team not found: ${name}`, available: Object.keys(templates) };
|
|
529
|
-
return { name, ...team };
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
function workflowAddTask(args) {
|
|
533
|
-
const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
|
|
534
|
-
if (args.request) {
|
|
535
|
-
const workflow = addTaskFromIntent(cwd, args.request, {
|
|
536
|
-
key: args.key,
|
|
537
|
-
title: args.title,
|
|
538
|
-
phase: args.phase,
|
|
539
|
-
owner: args.owner,
|
|
540
|
-
files: args.files,
|
|
541
|
-
readFirst: args.readFirst,
|
|
542
|
-
doNotChange: args.doNotChange,
|
|
543
|
-
acceptanceCriteria: args.acceptanceCriteria,
|
|
544
|
-
verification: args.verification,
|
|
545
|
-
overlays: args.overlays,
|
|
546
|
-
challengeRequired: args.challengeRequired,
|
|
547
|
-
challengeStatus: args.challengeStatus,
|
|
548
|
-
tokenBudget: args.tokenBudget,
|
|
549
|
-
status: args.status,
|
|
550
|
-
});
|
|
551
|
-
if (!workflow) return { ok: true, skipped: true, reason: 'immediate-track' };
|
|
552
|
-
return { cwd, workflow, summary: summarizeWorkflow(workflow), source: 'intent' };
|
|
553
|
-
}
|
|
554
|
-
const workflow = addTask(cwd, {
|
|
555
|
-
key: args.key,
|
|
556
|
-
title: args.title,
|
|
557
|
-
phase: args.phase,
|
|
558
|
-
owner: args.owner,
|
|
559
|
-
files: args.files,
|
|
560
|
-
readFirst: args.readFirst,
|
|
561
|
-
doNotChange: args.doNotChange,
|
|
562
|
-
acceptanceCriteria: args.acceptanceCriteria,
|
|
563
|
-
verification: args.verification,
|
|
564
|
-
dependsOn: args.dependsOn,
|
|
565
|
-
overlays: args.overlays,
|
|
566
|
-
challengeRequired: args.challengeRequired,
|
|
567
|
-
challengeStatus: args.challengeStatus,
|
|
568
|
-
});
|
|
569
|
-
return { cwd, workflow, summary: summarizeWorkflow(workflow), source: 'manual' };
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
function workflowUpdateTask(args) {
|
|
573
|
-
const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
|
|
574
|
-
const workflow = updateTask(cwd, args.key, {
|
|
575
|
-
status: args.status,
|
|
576
|
-
owner: args.owner,
|
|
577
|
-
phase: args.phase,
|
|
578
|
-
note: args.note,
|
|
579
|
-
verification: args.verification,
|
|
580
|
-
overlays: args.overlays,
|
|
581
|
-
challengeRequired: args.challengeRequired,
|
|
582
|
-
challengeStatus: args.challengeStatus,
|
|
583
|
-
});
|
|
584
|
-
return { cwd, workflow, summary: summarizeWorkflow(workflow) };
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
function workflowNeedsMainInput(args) {
|
|
588
|
-
const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
|
|
589
|
-
const packet = createNeedsMainInputPacket(args);
|
|
590
|
-
const workflow = updateTask(cwd, args.taskKey, {
|
|
591
|
-
status: 'blocked_needs_user',
|
|
592
|
-
note: `${packet.worker}: ${packet.blocker} | question: ${packet.question}`,
|
|
593
|
-
});
|
|
594
|
-
return { cwd, packet, workflow, summary: summarizeWorkflow(workflow) };
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
function workflowValidate(args) {
|
|
598
|
-
const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
|
|
599
|
-
const workflow = loadWorkflow(cwd);
|
|
600
|
-
const result = validateWorkflowState(workflow);
|
|
601
|
-
return { cwd, ...result };
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
function workflowImportPlan(args) {
|
|
605
|
-
const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
|
|
606
|
-
const markdown = args.markdown ?? '';
|
|
607
|
-
const { workflow, count } = addTasksFromPlan(cwd, markdown, {
|
|
608
|
-
phase: args.phase,
|
|
609
|
-
owner: args.owner,
|
|
610
|
-
readFirst: Array.isArray(args.readFirst) ? args.readFirst : undefined,
|
|
611
|
-
doNotChange: Array.isArray(args.doNotChange) ? args.doNotChange : undefined,
|
|
612
|
-
acceptanceCriteria: Array.isArray(args.acceptanceCriteria) ? args.acceptanceCriteria : undefined,
|
|
613
|
-
workflowTitle: args.title,
|
|
614
|
-
specRef: args.spec_ref,
|
|
615
|
-
});
|
|
616
|
-
return { cwd, count, workflow, summary: summarizeWorkflow(workflow) };
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
function resolveReleaseTag(cwd) {
|
|
620
|
-
try {
|
|
621
|
-
return execSync('git rev-parse --short HEAD', { stdio: 'pipe', cwd, timeout: 2000 }).toString().trim() || undefined;
|
|
622
|
-
} catch {
|
|
623
|
-
return undefined;
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
function resolveSessionContext() {
|
|
628
|
-
const cwd = process.cwd();
|
|
629
|
-
let workflowId;
|
|
630
|
-
let workflowPhase;
|
|
631
|
-
let workflowOwner;
|
|
632
|
-
try {
|
|
633
|
-
const wf = loadWorkflow(cwd);
|
|
634
|
-
if (wf) {
|
|
635
|
-
workflowId = wf.id;
|
|
636
|
-
workflowPhase = wf.phase;
|
|
637
|
-
const active = (wf.tasks || []).find((t) => t.status === 'in-progress' || t.status === 'in_progress');
|
|
638
|
-
if (active) workflowOwner = active.owner;
|
|
639
|
-
}
|
|
640
|
-
} catch { /* best effort */ }
|
|
641
|
-
const sessionId = process.env.CLAUDE_SESSION_ID
|
|
642
|
-
|| process.env.CX_SESSION_ID
|
|
643
|
-
|| process.env.OPENCODE_SESSION_ID
|
|
644
|
-
|| workflowId;
|
|
645
|
-
const userId = process.env.USER || process.env.USERNAME || process.env.LOGNAME;
|
|
646
|
-
return { cwd, sessionId, userId, release: resolveReleaseTag(cwd), workflowPhase, workflowOwner, workflowId };
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
function langfuseHeaders() {
|
|
650
|
-
const key = process.env.LANGFUSE_PUBLIC_KEY;
|
|
651
|
-
const secret = process.env.LANGFUSE_SECRET_KEY;
|
|
652
|
-
if (!key || !secret) throw new Error('LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY must be set.');
|
|
653
|
-
return {
|
|
654
|
-
Authorization: `Basic ${Buffer.from(`${key}:${secret}`).toString('base64')}`,
|
|
655
|
-
'Content-Type': 'application/json',
|
|
656
|
-
};
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
function langfuseBaseUrl() {
|
|
660
|
-
return (process.env.LANGFUSE_BASEURL ?? 'https://cloud.langfuse.com').replace(/\/$/, '');
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
async function cxTrace(args) {
|
|
664
|
-
const ctx = resolveSessionContext();
|
|
665
|
-
const registry = readJSON(join(ROOT_DIR, 'agents', 'registry.json')) ?? {};
|
|
666
|
-
const registryModels = registry.models ?? {};
|
|
667
|
-
const currentModels = readCurrentModels(join(ROOT_DIR, '.env'), registryModels, process.env);
|
|
668
|
-
const route = typeof args.input === 'string' ? routeRequest({ request: args.input }) : null;
|
|
669
|
-
const executionContractModel = resolveExecutionContractModelMetadata({
|
|
670
|
-
envValues: currentModels,
|
|
671
|
-
registryModels,
|
|
672
|
-
requestedTier: selectModelTierForWorkCategory(route?.workCategory),
|
|
673
|
-
workCategory: route?.workCategory || null,
|
|
674
|
-
});
|
|
675
|
-
const runtimePromptMetadata = summarizePromptComposition(args.name, {
|
|
676
|
-
rootDir: ROOT_DIR,
|
|
677
|
-
request: typeof args.input === 'string' ? args.input : '',
|
|
678
|
-
route,
|
|
679
|
-
registryModels,
|
|
680
|
-
envValues: currentModels,
|
|
681
|
-
executionContractModel,
|
|
682
|
-
hostConstraints: {
|
|
683
|
-
runtime: 'mcp',
|
|
684
|
-
providerAgnostic: true,
|
|
685
|
-
telemetryBackend: 'langfuse',
|
|
686
|
-
},
|
|
687
|
-
});
|
|
688
|
-
const metadata = enrichMetadataWithPrompt(args.name, {
|
|
689
|
-
...(args.metadata && typeof args.metadata === 'object' ? args.metadata : {}),
|
|
690
|
-
...runtimePromptMetadata,
|
|
691
|
-
workflowId: ctx.workflowId,
|
|
692
|
-
workflowPhase: ctx.workflowPhase,
|
|
693
|
-
workflowOwner: ctx.workflowOwner,
|
|
694
|
-
release: ctx.release,
|
|
695
|
-
}, { rootDir: ROOT_DIR });
|
|
696
|
-
const traceId = args.id ?? crypto.randomUUID();
|
|
697
|
-
try {
|
|
698
|
-
const available = await langfuse.isAvailable();
|
|
699
|
-
if (!available) return { ok: false, error: 'Langfuse credentials not configured', id: traceId };
|
|
700
|
-
const teamId = args.metadata?.teamId ?? metadata.teamId;
|
|
701
|
-
const body = {
|
|
702
|
-
id: traceId,
|
|
703
|
-
name: args.name,
|
|
704
|
-
metadata: {
|
|
705
|
-
...metadata,
|
|
706
|
-
agentName: args.name,
|
|
707
|
-
goal: typeof args.input === 'string' ? args.input : JSON.stringify(args.input ?? ''),
|
|
708
|
-
teamId,
|
|
709
|
-
},
|
|
710
|
-
tags: [args.name, teamId].filter(Boolean),
|
|
711
|
-
userId: ctx.userId,
|
|
712
|
-
sessionId: args.session_id || ctx.sessionId,
|
|
713
|
-
input: args.input,
|
|
714
|
-
output: args.output,
|
|
715
|
-
timestamp: args.timestamp ?? new Date().toISOString(),
|
|
716
|
-
release: ctx.release,
|
|
717
|
-
};
|
|
718
|
-
const res = await fetch(`${langfuseBaseUrl()}/api/public/traces`, {
|
|
719
|
-
method: 'POST',
|
|
720
|
-
headers: langfuseHeaders(),
|
|
721
|
-
body: JSON.stringify(body),
|
|
722
|
-
});
|
|
723
|
-
if (!res.ok) {
|
|
724
|
-
const text = await res.text().catch(() => '');
|
|
725
|
-
return { ok: false, error: `Langfuse API error ${res.status}: ${text}`, id: traceId };
|
|
726
|
-
}
|
|
727
|
-
return { ok: true, id: traceId };
|
|
728
|
-
} catch (err) {
|
|
729
|
-
return { ok: false, error: err.message, id: traceId };
|
|
730
|
-
}
|
|
731
|
-
}
|
|
27
|
+
import {
|
|
28
|
+
agentHealth, summarizeDiff, scanFile, projectContext, workflowStatus,
|
|
29
|
+
} from './tools/project.mjs';
|
|
30
|
+
import {
|
|
31
|
+
extractDocumentText, ingestDocument, inferDocumentSchemaTool, listSchemaArtifactsTool,
|
|
32
|
+
} from './tools/document.mjs';
|
|
33
|
+
import {
|
|
34
|
+
storageStatus, storageSync, storageReset, deleteIngestedArtifactsTool,
|
|
35
|
+
} from './tools/storage.mjs';
|
|
36
|
+
import {
|
|
37
|
+
listSkills, getSkill, searchSkills, getTemplate, listTemplates,
|
|
38
|
+
agentContract, brokerCheck, orchestrationPolicy, workerRun, listTeams, getTeam,
|
|
39
|
+
} from './tools/skills.mjs';
|
|
40
|
+
import {
|
|
41
|
+
workflowInit, workflowAddTask, workflowUpdateTask,
|
|
42
|
+
workflowNeedsMainInput, workflowValidate, workflowImportPlan,
|
|
43
|
+
} from './tools/workflow.mjs';
|
|
44
|
+
import {
|
|
45
|
+
cxTrace, cxTraceUpdate, cxScore, sessionUsage, efficiencySnapshot,
|
|
46
|
+
} from './tools/telemetry.mjs';
|
|
47
|
+
import {
|
|
48
|
+
memorySearch, memoryAddObservations, memoryCreateEntities, memoryRecent,
|
|
49
|
+
sessionList, sessionLoad, sessionSearch, sessionSave, rovoSearch,
|
|
50
|
+
} from './tools/memory.mjs';
|
|
732
51
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
const available = await langfuse.isAvailable();
|
|
737
|
-
if (!available) return { ok: false, error: 'Langfuse credentials not configured' };
|
|
738
|
-
const body = {
|
|
739
|
-
id: crypto.randomUUID(),
|
|
740
|
-
traceId,
|
|
741
|
-
name: args.name ?? 'quality',
|
|
742
|
-
value: args.value,
|
|
743
|
-
dataType: 'NUMERIC',
|
|
744
|
-
comment: args.comment,
|
|
745
|
-
};
|
|
746
|
-
const res = await fetch(`${langfuseBaseUrl()}/api/public/scores`, {
|
|
747
|
-
method: 'POST',
|
|
748
|
-
headers: langfuseHeaders(),
|
|
749
|
-
body: JSON.stringify(body),
|
|
750
|
-
});
|
|
751
|
-
if (!res.ok) {
|
|
752
|
-
const text = await res.text().catch(() => '');
|
|
753
|
-
return { ok: false, error: `Langfuse API error ${res.status}: ${text}` };
|
|
754
|
-
}
|
|
755
|
-
return { ok: true, traceId };
|
|
756
|
-
} catch (err) {
|
|
757
|
-
return { ok: false, error: err.message };
|
|
758
|
-
}
|
|
759
|
-
}
|
|
52
|
+
const DEFAULT_ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
53
|
+
const ROOT_DIR = resolve(process.env.CX_TOOLKIT_DIR || DEFAULT_ROOT_DIR);
|
|
54
|
+
loadToolkitEnv(ROOT_DIR);
|
|
760
55
|
|
|
761
|
-
|
|
762
|
-
const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
|
|
763
|
-
const homeDir = args.home_dir ? resolve(args.home_dir) : homedir();
|
|
764
|
-
const status = await buildStatus({ rootDir: ROOT_DIR, cwd, homeDir, env: process.env });
|
|
765
|
-
return {
|
|
766
|
-
cwd,
|
|
767
|
-
sessionUsage: status.sessionUsage,
|
|
768
|
-
};
|
|
769
|
-
}
|
|
56
|
+
const opts = { ROOT_DIR };
|
|
770
57
|
|
|
771
|
-
// Server setup
|
|
772
58
|
const server = new Server(
|
|
773
59
|
{ name: 'construct-mcp', version: '1.0.0' },
|
|
774
60
|
{ capabilities: { tools: {} } }
|
|
@@ -820,6 +106,198 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
820
106
|
required: ['file_path'],
|
|
821
107
|
},
|
|
822
108
|
},
|
|
109
|
+
{
|
|
110
|
+
name: 'extract_document_text',
|
|
111
|
+
description: 'Extracts readable text from a local document path. Supports PDF on macOS plus common text and office document formats.',
|
|
112
|
+
inputSchema: {
|
|
113
|
+
type: 'object',
|
|
114
|
+
properties: {
|
|
115
|
+
file_path: {
|
|
116
|
+
type: 'string',
|
|
117
|
+
description: 'Absolute or relative path to the document file.',
|
|
118
|
+
},
|
|
119
|
+
max_chars: {
|
|
120
|
+
type: 'number',
|
|
121
|
+
description: 'Maximum characters to return (default 20000, hard cap 200000).',
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
required: ['file_path'],
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'ingest_document',
|
|
129
|
+
description: 'Converts a local document into a normalized markdown file, placing it into an indexed project path by default.',
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: 'object',
|
|
132
|
+
properties: {
|
|
133
|
+
file_path: {
|
|
134
|
+
type: 'string',
|
|
135
|
+
description: 'Absolute or relative path to the source document.',
|
|
136
|
+
},
|
|
137
|
+
out_path: {
|
|
138
|
+
type: 'string',
|
|
139
|
+
description: 'Optional explicit markdown output path.',
|
|
140
|
+
},
|
|
141
|
+
out_dir: {
|
|
142
|
+
type: 'string',
|
|
143
|
+
description: 'Optional directory for generated markdown output files.',
|
|
144
|
+
},
|
|
145
|
+
target: {
|
|
146
|
+
type: 'string',
|
|
147
|
+
description: 'Output mode: knowledge/internal, knowledge/external, knowledge/decisions, knowledge/how-tos, knowledge/reference, or sibling. Defaults to knowledge/internal.',
|
|
148
|
+
},
|
|
149
|
+
cwd: {
|
|
150
|
+
type: 'string',
|
|
151
|
+
description: 'Project root used to resolve default output paths and storage sync.',
|
|
152
|
+
},
|
|
153
|
+
sync: {
|
|
154
|
+
type: 'boolean',
|
|
155
|
+
description: 'When true, sync file-state into configured SQL/vector storage after writing output.',
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
required: ['file_path'],
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: 'infer_document_schema',
|
|
163
|
+
description: 'Infers a structured field schema from a local document using AI. Returns field names, types, formats, examples, and confidence. Supports all document types handled by extract_document_text. Pass multiple file_paths to get a reconciled unified schema across documents.',
|
|
164
|
+
inputSchema: {
|
|
165
|
+
type: 'object',
|
|
166
|
+
properties: {
|
|
167
|
+
file_path: {
|
|
168
|
+
type: 'string',
|
|
169
|
+
description: 'Absolute or relative path to the document file. For unified inference across multiple documents, use file_paths instead.',
|
|
170
|
+
},
|
|
171
|
+
file_paths: {
|
|
172
|
+
type: 'array',
|
|
173
|
+
items: { type: 'string' },
|
|
174
|
+
description: 'Multiple document paths for unified schema inference. Reconciles fields across all documents.',
|
|
175
|
+
},
|
|
176
|
+
max_chars: {
|
|
177
|
+
type: 'number',
|
|
178
|
+
description: 'Maximum characters of document text to send to the model (default 40000, hard cap 200000).',
|
|
179
|
+
},
|
|
180
|
+
save: {
|
|
181
|
+
type: 'boolean',
|
|
182
|
+
description: 'When true, write the schema result as a .schema.json artifact under .cx/knowledge/reference/schemas/.',
|
|
183
|
+
},
|
|
184
|
+
cwd: {
|
|
185
|
+
type: 'string',
|
|
186
|
+
description: 'Project root used to resolve output paths when save is true.',
|
|
187
|
+
},
|
|
188
|
+
sample_size: {
|
|
189
|
+
type: 'number',
|
|
190
|
+
description: 'For unified inference: max number of documents to sample (default 10).',
|
|
191
|
+
},
|
|
192
|
+
threshold: {
|
|
193
|
+
type: 'number',
|
|
194
|
+
description: 'For unified inference: minimum fraction of documents a field must appear in to be included (default 0.5).',
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: 'list_schema_artifacts',
|
|
201
|
+
description: 'Lists all inferred schema artifacts (.schema.json files) in the project.',
|
|
202
|
+
inputSchema: {
|
|
203
|
+
type: 'object',
|
|
204
|
+
properties: {
|
|
205
|
+
cwd: {
|
|
206
|
+
type: 'string',
|
|
207
|
+
description: 'Project directory to search (default: process.cwd()).',
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: 'storage_status',
|
|
214
|
+
description: 'Returns SQL, local vector index, and ingested-artifact status for the current project.',
|
|
215
|
+
inputSchema: {
|
|
216
|
+
type: 'object',
|
|
217
|
+
properties: {
|
|
218
|
+
cwd: {
|
|
219
|
+
type: 'string',
|
|
220
|
+
description: 'Project directory to inspect.',
|
|
221
|
+
},
|
|
222
|
+
project: {
|
|
223
|
+
type: 'string',
|
|
224
|
+
description: 'Optional explicit project key for SQL document counts.',
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: 'storage_sync',
|
|
231
|
+
description: 'Syncs file-state documents into the local vector index and configured SQL storage.',
|
|
232
|
+
inputSchema: {
|
|
233
|
+
type: 'object',
|
|
234
|
+
properties: {
|
|
235
|
+
cwd: {
|
|
236
|
+
type: 'string',
|
|
237
|
+
description: 'Project directory to sync.',
|
|
238
|
+
},
|
|
239
|
+
project: {
|
|
240
|
+
type: 'string',
|
|
241
|
+
description: 'Optional explicit project key.',
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: 'storage_reset',
|
|
248
|
+
description: 'Resets SQL/vector storage state for a project. Requires explicit confirm=true.',
|
|
249
|
+
inputSchema: {
|
|
250
|
+
type: 'object',
|
|
251
|
+
properties: {
|
|
252
|
+
cwd: {
|
|
253
|
+
type: 'string',
|
|
254
|
+
description: 'Project directory whose storage should be reset.',
|
|
255
|
+
},
|
|
256
|
+
project: {
|
|
257
|
+
type: 'string',
|
|
258
|
+
description: 'Optional explicit project key.',
|
|
259
|
+
},
|
|
260
|
+
reset_sql: {
|
|
261
|
+
type: 'boolean',
|
|
262
|
+
description: 'Set false to keep SQL state intact.',
|
|
263
|
+
},
|
|
264
|
+
reset_vector: {
|
|
265
|
+
type: 'boolean',
|
|
266
|
+
description: 'Set false to keep the local vector index intact.',
|
|
267
|
+
},
|
|
268
|
+
reset_ingested: {
|
|
269
|
+
type: 'boolean',
|
|
270
|
+
description: 'Set true to also delete ingested markdown artifacts under .cx/knowledge/.',
|
|
271
|
+
},
|
|
272
|
+
confirm: {
|
|
273
|
+
type: 'boolean',
|
|
274
|
+
description: 'Must be true or the reset is rejected.',
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
name: 'delete_ingested_artifacts',
|
|
281
|
+
description: 'Deletes ingested markdown artifacts. Requires explicit confirm=true and only allows files under the ingested artifact directory.',
|
|
282
|
+
inputSchema: {
|
|
283
|
+
type: 'object',
|
|
284
|
+
properties: {
|
|
285
|
+
cwd: {
|
|
286
|
+
type: 'string',
|
|
287
|
+
description: 'Project directory whose ingested artifacts should be deleted.',
|
|
288
|
+
},
|
|
289
|
+
files: {
|
|
290
|
+
type: 'array',
|
|
291
|
+
description: 'Optional relative file paths under .cx/knowledge/. Omit to delete all ingested markdown artifacts.',
|
|
292
|
+
items: { type: 'string' },
|
|
293
|
+
},
|
|
294
|
+
confirm: {
|
|
295
|
+
type: 'boolean',
|
|
296
|
+
description: 'Must be true or deletion is rejected.',
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
},
|
|
823
301
|
{
|
|
824
302
|
name: 'project_context',
|
|
825
303
|
description: 'Returns project context: .cx/context.md content, recent commits, and working tree status.',
|
|
@@ -846,7 +324,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
846
324
|
},
|
|
847
325
|
{
|
|
848
326
|
name: 'orchestration_policy',
|
|
849
|
-
description: 'Classifies a request into intent, execution track, specialists, and
|
|
327
|
+
description: 'Classifies a request into intent, execution track, specialists, approval boundaries, and the contract chain that applies. The contractChain field names the typed producer→consumer handoffs expected for this dispatch plan. When `candidates` is supplied, also returns `contextPackets` keyed by specialist with the role-filtered artifact bundle each specialist should receive (omitted artifacts include the reason they were dropped).',
|
|
850
328
|
inputSchema: {
|
|
851
329
|
type: 'object',
|
|
852
330
|
properties: {
|
|
@@ -866,6 +344,32 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
866
344
|
blockedDependency: { type: 'boolean' },
|
|
867
345
|
},
|
|
868
346
|
},
|
|
347
|
+
candidates: {
|
|
348
|
+
type: 'array',
|
|
349
|
+
description: 'Pre-retrieved artifacts to consider for each specialist. Each item: {id, path, title, kind, summary, score}. When supplied, the response includes `contextPackets` keyed by specialist.',
|
|
350
|
+
items: { type: 'object' },
|
|
351
|
+
},
|
|
352
|
+
budget: {
|
|
353
|
+
type: 'object',
|
|
354
|
+
description: 'Token budget for the per-specialist context packet (default: 6000).',
|
|
355
|
+
properties: { maxTokens: { type: 'number' } },
|
|
356
|
+
},
|
|
357
|
+
triage: {
|
|
358
|
+
type: 'object',
|
|
359
|
+
description: 'Optional R&D triage block from a related intake packet — surfaced in each specialist\'s taskSummary.',
|
|
360
|
+
},
|
|
361
|
+
intakeId: {
|
|
362
|
+
type: 'string',
|
|
363
|
+
description: 'Optional id of a pending intake packet. When supplied, the tool generates a task graph from the packet\'s triage, persists it to .cx/task-graphs/, emits a task_graph.created trace event correlated by traceId, and returns the graph in `taskGraph`.',
|
|
364
|
+
},
|
|
365
|
+
project: {
|
|
366
|
+
type: 'string',
|
|
367
|
+
description: 'Project scope for the generated task graph (defaults to basename(cwd)).',
|
|
368
|
+
},
|
|
369
|
+
traceId: {
|
|
370
|
+
type: 'string',
|
|
371
|
+
description: 'Optional traceId to link task_graph.created with the originating intake.received event.',
|
|
372
|
+
},
|
|
869
373
|
},
|
|
870
374
|
},
|
|
871
375
|
},
|
|
@@ -877,6 +381,59 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
877
381
|
properties: {},
|
|
878
382
|
},
|
|
879
383
|
},
|
|
384
|
+
{
|
|
385
|
+
name: 'worker_run',
|
|
386
|
+
description: 'Runs a bounded shell command via the worker plane: timeout, path-policy denial, restricted env, stdout/stderr artifacts under `.cx/runtime/worker/<jobId>.{stdout,stderr}.log`. When `graphId` + `nodeId` are supplied, an evidence record is appended to that task graph node so it can transition to `done`. Emits `worker.started` / `worker.completed` / `evidence.recorded` trace events correlated by `traceId`.',
|
|
387
|
+
inputSchema: {
|
|
388
|
+
type: 'object',
|
|
389
|
+
properties: {
|
|
390
|
+
command: { type: 'string', description: 'Shell command to run (e.g. `npm test`).' },
|
|
391
|
+
args: { type: 'array', items: { type: 'string' }, description: 'Argv to pass alongside the command (when not embedded in the command string).' },
|
|
392
|
+
workspaceRef: { type: 'string', description: 'Absolute path the job runs in. Must be inside `allowedPaths` (default: cwd).' },
|
|
393
|
+
allowedPaths: { type: 'array', items: { type: 'string' }, description: 'Absolute path allowlist for the workspace. Default: [cwd] so the job can\'t reach outside the project.' },
|
|
394
|
+
timeoutSeconds: { type: 'number', description: 'Hard timeout. Default: 300.' },
|
|
395
|
+
envPolicy: { type: 'string', description: 'restricted (default — only PATH/HOME/USER/TZ/LANG/LC_ALL/TMPDIR plus allowedEnvKeys) | inherit.' },
|
|
396
|
+
allowedEnvKeys: { type: 'array', items: { type: 'string' }, description: 'Additional env keys allowed through under restricted policy.' },
|
|
397
|
+
graphId: { type: 'string', description: 'Optional task graph id — when present with `nodeId`, evidence is recorded on that node.' },
|
|
398
|
+
nodeId: { type: 'string', description: 'Optional task graph node id — when present with `graphId`, evidence is recorded.' },
|
|
399
|
+
evidenceType: { type: 'string', description: 'Evidence type (e.g. `test-result`, `lint-result`, `build-result`). Default: test-result.' },
|
|
400
|
+
evidenceSummary: { type: 'string', description: 'Optional override for the evidence summary string.' },
|
|
401
|
+
traceId: { type: 'string', description: 'Optional traceId to correlate with the rest of the agent\'s trace.' },
|
|
402
|
+
taskId: { type: 'string', description: 'Optional task id (used when graphId/nodeId aren\'t supplied — purely for trace context).' },
|
|
403
|
+
project: { type: 'string', description: 'Optional project scope.' },
|
|
404
|
+
jobId: { type: 'string', description: 'Optional job id. Default: worker-<ts>-<rand>.' },
|
|
405
|
+
},
|
|
406
|
+
required: ['command'],
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
name: 'broker_check',
|
|
411
|
+
description: 'Queries the MCP broker policy gate for a pending action without executing it. Agents call this BEFORE attempting a high-risk action so the response (allowed / approvalRequired / reason / source) can be surfaced to the user in the agent\'s voice rather than triggering a denial after the fact. In solo mode the broker is off by default — returns `brokerActive: false` with `allowed: true` so the call is cheap and agents don\'t waste tokens on an inactive gate. Always emits a `tool.called` trace event for audit-trail parity. Reads agents/role-manifests.json fence rules in team / enterprise mode.',
|
|
412
|
+
inputSchema: {
|
|
413
|
+
type: 'object',
|
|
414
|
+
properties: {
|
|
415
|
+
role: { type: 'string', description: 'Persona name (e.g. `engineer`, `security`). Must match a key in agents/role-manifests.json for team / enterprise mode.' },
|
|
416
|
+
project: { type: 'string', description: 'Optional project scope for the decision.' },
|
|
417
|
+
tool: { type: 'string', description: 'The tool the agent wants to invoke (e.g. `github`, `fs`).' },
|
|
418
|
+
action: { type: 'string', description: 'The action on that tool (e.g. `create_pr`, `edit:lib/foo.mjs`).' },
|
|
419
|
+
risk: { type: 'string', description: 'low | medium | high. high actions need approval for non-autonomous roles.' },
|
|
420
|
+
traceId: { type: 'string', description: 'Optional traceId to correlate this check with the rest of the agent\'s trace.' },
|
|
421
|
+
},
|
|
422
|
+
required: ['role', 'tool', 'action'],
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
name: 'agent_contract',
|
|
427
|
+
description: 'Looks up explicit agent-to-agent service contracts (from agents/contracts.json). Specialists should call this at the start of a handoff to see the expected input shape, preconditions, and what postconditions they must satisfy. Use without args to get all contracts; pass producer/consumer to narrow; pass id for a specific contract.',
|
|
428
|
+
inputSchema: {
|
|
429
|
+
type: 'object',
|
|
430
|
+
properties: {
|
|
431
|
+
id: { type: 'string', description: 'Exact contract id (e.g. "architect-to-engineer")' },
|
|
432
|
+
producer: { type: 'string', description: 'Producer agent name (e.g. "cx-architect"). Returns outgoing contracts.' },
|
|
433
|
+
consumer: { type: 'string', description: 'Consumer agent name (e.g. "cx-engineer"). Returns incoming contracts.' },
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
},
|
|
880
437
|
{
|
|
881
438
|
name: 'get_template',
|
|
882
439
|
description: 'Reads a doc template by name (e.g. "prd", "meta-prd", "prfaq", "evidence-brief", "adr", "runbook"). Resolves .cx/templates/docs/{name}.md first, then templates/docs/{name}.md.',
|
|
@@ -905,162 +462,230 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
905
462
|
},
|
|
906
463
|
},
|
|
907
464
|
{
|
|
908
|
-
name: '
|
|
909
|
-
description: '
|
|
465
|
+
name: 'list_teams',
|
|
466
|
+
description: 'Lists all available Construct team templates with members, focus, and promotion gates.',
|
|
467
|
+
inputSchema: { type: 'object', properties: {} },
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
name: 'get_team',
|
|
471
|
+
description: 'Returns the full definition of a named team template including members, skills, and promotion gates.',
|
|
472
|
+
inputSchema: {
|
|
473
|
+
type: 'object',
|
|
474
|
+
properties: {
|
|
475
|
+
name: { type: 'string', description: 'Team template name (e.g. feature, incident, architecture).' },
|
|
476
|
+
},
|
|
477
|
+
required: ['name'],
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
name: 'cx_trace',
|
|
482
|
+
description: 'Records an agent trace for observability. Call at the start of every significant task with your agent name and the user goal.',
|
|
483
|
+
inputSchema: {
|
|
484
|
+
type: 'object',
|
|
485
|
+
properties: {
|
|
486
|
+
name: { type: 'string', description: 'Agent name (e.g. cx-engineer)' },
|
|
487
|
+
id: { type: 'string', description: 'Optional trace UUID — auto-generated if omitted' },
|
|
488
|
+
session_id: { type: 'string', description: 'Session ID to group related spans' },
|
|
489
|
+
metadata: { type: 'object', description: 'Extra metadata (teamId, workflowId, etc.)' },
|
|
490
|
+
input: { type: ['string', 'object'], description: 'Agent goal or user request' },
|
|
491
|
+
output: { type: ['string', 'object'], description: 'Agent deliverable or response' },
|
|
492
|
+
timestamp: { type: 'string', description: 'ISO start time (default: now)' },
|
|
493
|
+
},
|
|
494
|
+
required: ['name'],
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
name: 'cx_score',
|
|
499
|
+
description: 'Attaches a quality score to a trace. Call after producing a significant deliverable.',
|
|
500
|
+
inputSchema: {
|
|
501
|
+
type: 'object',
|
|
502
|
+
properties: {
|
|
503
|
+
trace_id: { type: 'string', description: 'The trace ID returned by cx_trace' },
|
|
504
|
+
name: { type: 'string', description: 'Score name — use "quality"' },
|
|
505
|
+
value: { type: 'number', description: 'Score from 0.0 (poor) to 1.0 (excellent)' },
|
|
506
|
+
comment: { type: 'string', description: 'Brief explanation of the score' },
|
|
507
|
+
},
|
|
508
|
+
required: ['trace_id', 'name', 'value'],
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
name: 'cx_trace_update',
|
|
513
|
+
description: 'Updates an existing trace with output and metadata. Use when a trace was created early but the result becomes available later.',
|
|
514
|
+
inputSchema: {
|
|
515
|
+
type: 'object',
|
|
516
|
+
properties: {
|
|
517
|
+
trace_id: { type: 'string', description: 'The trace ID returned by cx_trace' },
|
|
518
|
+
output: { type: ['string', 'object'], description: 'Final output / deliverable content' },
|
|
519
|
+
metadata: { type: 'object', description: 'Additional metadata to merge into the trace' },
|
|
520
|
+
},
|
|
521
|
+
required: ['trace_id'],
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
name: 'session_list',
|
|
526
|
+
description: 'List construct sessions for the current project. Returns distilled session index entries with id, project, status, and summary.',
|
|
910
527
|
inputSchema: {
|
|
911
528
|
type: 'object',
|
|
912
529
|
properties: {
|
|
913
530
|
cwd: { type: 'string', description: 'Project directory (default: process.cwd()).' },
|
|
531
|
+
status: { type: 'string', description: 'Filter by status: active, completed, closed.' },
|
|
532
|
+
limit: { type: 'number', description: 'Max results (default: 20).' },
|
|
914
533
|
},
|
|
915
534
|
},
|
|
916
535
|
},
|
|
917
536
|
{
|
|
918
|
-
name: '
|
|
919
|
-
description: '
|
|
537
|
+
name: 'session_load',
|
|
538
|
+
description: 'Load a full distilled session record by ID. Returns summary, decisions, files changed, open questions, and task snapshot.',
|
|
920
539
|
inputSchema: {
|
|
921
540
|
type: 'object',
|
|
922
541
|
properties: {
|
|
923
542
|
cwd: { type: 'string', description: 'Project directory (default: process.cwd()).' },
|
|
924
|
-
|
|
925
|
-
spec_ref: { type: 'string', description: 'Path to the governing spec/plan document (e.g. .cx/plans/feature-x.md).' },
|
|
543
|
+
session_id: { type: 'string', description: 'The session ID to load.' },
|
|
926
544
|
},
|
|
545
|
+
required: ['session_id'],
|
|
927
546
|
},
|
|
928
547
|
},
|
|
929
548
|
{
|
|
930
|
-
name: '
|
|
931
|
-
description: '
|
|
549
|
+
name: 'session_search',
|
|
550
|
+
description: 'Search sessions by keyword in summary or project name.',
|
|
932
551
|
inputSchema: {
|
|
933
552
|
type: 'object',
|
|
934
553
|
properties: {
|
|
935
|
-
cwd: { type: 'string' },
|
|
936
|
-
|
|
937
|
-
title: { type: 'string' },
|
|
938
|
-
phase: { type: 'string' },
|
|
939
|
-
owner: { type: 'string' },
|
|
940
|
-
files: { type: 'array', items: { type: 'string' } },
|
|
941
|
-
readFirst: { type: 'array', items: { type: 'string' } },
|
|
942
|
-
doNotChange: { type: 'array', items: { type: 'string' } },
|
|
943
|
-
acceptanceCriteria: { type: 'array', items: { type: 'string' } },
|
|
944
|
-
verification: { type: 'array', items: { type: 'string' } },
|
|
945
|
-
dependsOn: { type: 'array', items: { type: 'string' } },
|
|
946
|
-
overlays: { type: 'array', items: { type: 'string' } },
|
|
947
|
-
challengeRequired: { type: 'boolean' },
|
|
948
|
-
challengeStatus: { type: 'string' },
|
|
554
|
+
cwd: { type: 'string', description: 'Project directory (default: process.cwd()).' },
|
|
555
|
+
query: { type: 'string', description: 'Search keyword.' },
|
|
949
556
|
},
|
|
950
|
-
required: ['
|
|
557
|
+
required: ['query'],
|
|
951
558
|
},
|
|
952
559
|
},
|
|
953
560
|
{
|
|
954
|
-
name: '
|
|
955
|
-
description: '
|
|
561
|
+
name: 'session_save',
|
|
562
|
+
description: 'Update the active session with distilled context: summary, decisions, files changed, open questions, task snapshot.',
|
|
956
563
|
inputSchema: {
|
|
957
564
|
type: 'object',
|
|
958
565
|
properties: {
|
|
959
|
-
cwd: { type: 'string' },
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
challengeRequired: { type: 'boolean' },
|
|
968
|
-
challengeStatus: { type: 'string' },
|
|
566
|
+
cwd: { type: 'string', description: 'Project directory (default: process.cwd()).' },
|
|
567
|
+
session_id: { type: 'string', description: 'The session ID to update.' },
|
|
568
|
+
summary: { type: 'string', description: 'Brief summary of what happened (2-3 sentences).' },
|
|
569
|
+
decisions: { type: 'array', items: { type: 'string' }, description: 'Key decisions made during the session.' },
|
|
570
|
+
files_changed: { type: 'array', items: { type: 'object', properties: { path: { type: 'string' }, reason: { type: 'string' } } }, description: 'Files modified with reasons.' },
|
|
571
|
+
open_questions: { type: 'array', items: { type: 'string' }, description: 'Unresolved questions or blockers.' },
|
|
572
|
+
task_snapshot: { type: 'array', items: { type: 'object', properties: { id: { type: 'string' }, subject: { type: 'string' }, status: { type: 'string' } } }, description: 'Current task state.' },
|
|
573
|
+
status: { type: 'string', description: 'Session status: active, completed, closed.' },
|
|
969
574
|
},
|
|
970
|
-
required: ['
|
|
575
|
+
required: ['session_id'],
|
|
971
576
|
},
|
|
972
577
|
},
|
|
973
578
|
{
|
|
974
|
-
name: '
|
|
975
|
-
description: '
|
|
579
|
+
name: 'memory_search',
|
|
580
|
+
description: 'Search the observation store for patterns, decisions, and insights learned by specialists across sessions. Returns semantically matched observations scoped by role, category, or project.',
|
|
976
581
|
inputSchema: {
|
|
977
582
|
type: 'object',
|
|
978
583
|
properties: {
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
safeDefault: { type: 'string' },
|
|
985
|
-
context: { type: 'array', items: { type: 'string' } },
|
|
584
|
+
query: { type: 'string', description: 'Semantic search query (e.g., project name, pattern, component).' },
|
|
585
|
+
role: { type: 'string', description: 'Filter by specialist role (e.g., cx-engineer, cx-architect).' },
|
|
586
|
+
category: { type: 'string', description: 'Filter by category: pattern, anti-pattern, dependency, decision, insight, session-summary.' },
|
|
587
|
+
project: { type: 'string', description: 'Filter by project name.' },
|
|
588
|
+
limit: { type: 'number', description: 'Max results (default: 10).' },
|
|
986
589
|
},
|
|
987
|
-
required: ['
|
|
590
|
+
required: ['query'],
|
|
988
591
|
},
|
|
989
592
|
},
|
|
990
593
|
{
|
|
991
|
-
name: '
|
|
992
|
-
description: '
|
|
594
|
+
name: 'memory_add_observations',
|
|
595
|
+
description: 'Record observations (patterns, insights, decisions, anti-patterns) that specialists discover during work. These are indexed for semantic search and surface in future sessions.',
|
|
993
596
|
inputSchema: {
|
|
994
597
|
type: 'object',
|
|
995
598
|
properties: {
|
|
996
|
-
|
|
599
|
+
observations: {
|
|
600
|
+
type: 'array',
|
|
601
|
+
items: {
|
|
602
|
+
type: 'object',
|
|
603
|
+
properties: {
|
|
604
|
+
role: { type: 'string', description: 'Specialist role (e.g., cx-engineer).' },
|
|
605
|
+
category: { type: 'string', description: 'Category: pattern, anti-pattern, dependency, decision, insight.' },
|
|
606
|
+
summary: { type: 'string', description: 'Brief summary (max 500 chars).' },
|
|
607
|
+
content: { type: 'string', description: 'Detailed observation (max 2000 chars).' },
|
|
608
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Tags for filtering.' },
|
|
609
|
+
confidence: { type: 'number', description: 'Confidence 0.0-1.0 (default: 0.8).' },
|
|
610
|
+
},
|
|
611
|
+
required: ['summary'],
|
|
612
|
+
},
|
|
613
|
+
description: 'Observations to record (max 10 per call).',
|
|
614
|
+
},
|
|
997
615
|
},
|
|
616
|
+
required: ['observations'],
|
|
998
617
|
},
|
|
999
618
|
},
|
|
1000
619
|
{
|
|
1001
|
-
name: '
|
|
1002
|
-
description: '
|
|
620
|
+
name: 'memory_create_entities',
|
|
621
|
+
description: 'Track recurring entities (components, services, APIs, dependencies) that specialists encounter. Enables "what do we know about X?" queries.',
|
|
1003
622
|
inputSchema: {
|
|
1004
623
|
type: 'object',
|
|
1005
624
|
properties: {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
625
|
+
entities: {
|
|
626
|
+
type: 'array',
|
|
627
|
+
items: {
|
|
628
|
+
type: 'object',
|
|
629
|
+
properties: {
|
|
630
|
+
name: { type: 'string', description: 'Entity name (normalized to lowercase).' },
|
|
631
|
+
type: { type: 'string', description: 'Type: component, service, dependency, api, concept, file-group.' },
|
|
632
|
+
summary: { type: 'string', description: 'Brief description (max 500 chars).' },
|
|
633
|
+
observation_ids: { type: 'array', items: { type: 'string' }, description: 'Link to observation IDs.' },
|
|
634
|
+
},
|
|
635
|
+
required: ['name'],
|
|
636
|
+
},
|
|
637
|
+
description: 'Entities to create or update (max 10 per call).',
|
|
638
|
+
},
|
|
1015
639
|
},
|
|
1016
|
-
required: ['
|
|
640
|
+
required: ['entities'],
|
|
1017
641
|
},
|
|
1018
642
|
},
|
|
1019
643
|
{
|
|
1020
|
-
name: '
|
|
1021
|
-
description: '
|
|
1022
|
-
inputSchema: {
|
|
644
|
+
name: 'memory_recent',
|
|
645
|
+
description: 'Returns the most recent observations for the current project, deduplicated by (role, summary). Use this when the session-start hint indicates prior observations are available — fetch them on demand instead of paying for them every session.',
|
|
646
|
+
inputSchema: {
|
|
647
|
+
type: 'object',
|
|
648
|
+
properties: {
|
|
649
|
+
cwd: { type: 'string', description: 'Project directory (default: process.cwd()).' },
|
|
650
|
+
project: { type: 'string', description: 'Filter by project name (default: inferred from cwd).' },
|
|
651
|
+
limit: { type: 'number', description: 'Max distinct observations (default: 10, max: 50).' },
|
|
652
|
+
},
|
|
653
|
+
},
|
|
1023
654
|
},
|
|
1024
655
|
{
|
|
1025
|
-
name: '
|
|
1026
|
-
description: 'Returns the
|
|
656
|
+
name: 'rovo_search',
|
|
657
|
+
description: 'Cross-system semantic search via Atlassian Rovo. Searches Jira, Confluence, and other accessible sources. Returns excerpts with source attribution. Does NOT store results in the observation store. Use for broad queries across all sources, not just focal projects.',
|
|
1027
658
|
inputSchema: {
|
|
1028
659
|
type: 'object',
|
|
1029
660
|
properties: {
|
|
1030
|
-
|
|
661
|
+
query: { type: 'string', description: 'Search query (e.g., "Iverson reliability", "DR failover").' },
|
|
662
|
+
top_k: { type: 'number', description: 'Max results (default: 10, max: 50).' },
|
|
663
|
+
sources: { type: 'string', description: 'Comma-separated source filter (e.g., "jira,confluence").' },
|
|
1031
664
|
},
|
|
1032
|
-
required: ['
|
|
665
|
+
required: ['query'],
|
|
1033
666
|
},
|
|
1034
667
|
},
|
|
1035
668
|
{
|
|
1036
|
-
name: '
|
|
1037
|
-
description: '
|
|
669
|
+
name: 'rovo_search',
|
|
670
|
+
description: 'Cross-system semantic search via Atlassian Rovo. Searches Jira, Confluence, and other accessible sources. Returns excerpts with source attribution. Does NOT store results in the observation store. Use for broad queries across all sources, not just focal projects.',
|
|
1038
671
|
inputSchema: {
|
|
1039
672
|
type: 'object',
|
|
1040
673
|
properties: {
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
metadata: { type: 'object', description: 'Extra metadata (teamId, workflowId, etc.)' },
|
|
1045
|
-
input: { type: ['string', 'object'], description: 'Agent goal or user request' },
|
|
1046
|
-
output: { type: ['string', 'object'], description: 'Agent deliverable or response' },
|
|
1047
|
-
timestamp: { type: 'string', description: 'ISO start time (default: now)' },
|
|
674
|
+
query: { type: 'string', description: 'Search query (e.g., "Iverson reliability", "DR failover").' },
|
|
675
|
+
top_k: { type: 'number', description: 'Max results (default: 10, max: 50).' },
|
|
676
|
+
sources: { type: 'string', description: 'Comma-separated source filter (e.g., "jira,confluence").' },
|
|
1048
677
|
},
|
|
1049
|
-
required: ['
|
|
678
|
+
required: ['query'],
|
|
1050
679
|
},
|
|
1051
680
|
},
|
|
1052
681
|
{
|
|
1053
|
-
name: '
|
|
1054
|
-
description: '
|
|
682
|
+
name: 'efficiency_snapshot',
|
|
683
|
+
description: 'Returns the read-efficiency snapshot for the current session — repeated reads, large reads, hot-spot files, and recommendations. Use this when investigating why a session feels slow or to surface optimization opportunities.',
|
|
1055
684
|
inputSchema: {
|
|
1056
685
|
type: 'object',
|
|
1057
686
|
properties: {
|
|
1058
|
-
|
|
1059
|
-
name: { type: 'string', description: 'Score name — use "quality"' },
|
|
1060
|
-
value: { type: 'number', description: 'Score from 0.0 (poor) to 1.0 (excellent)' },
|
|
1061
|
-
comment: { type: 'string', description: 'Brief explanation of the score' },
|
|
687
|
+
home_dir: { type: 'string', description: 'Home directory override (default: os.homedir()).' },
|
|
1062
688
|
},
|
|
1063
|
-
required: ['trace_id', 'name', 'value'],
|
|
1064
689
|
},
|
|
1065
690
|
},
|
|
1066
691
|
{
|
|
@@ -1074,6 +699,32 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
1074
699
|
},
|
|
1075
700
|
},
|
|
1076
701
|
},
|
|
702
|
+
{
|
|
703
|
+
name: 'provider_fetch',
|
|
704
|
+
description: 'Look up current data for a configured repo, project, or team. This is an internal lookup against sources the operator has already authorized (set in config.env). Call this immediately — no user approval needed — whenever the user asks about a specific repo, project, or team name (e.g. "what is project iverson", "cloud-reliability status", "PLAT issues"). Pass the user\'s query and the tool resolves the right source automatically. Returns repo metadata, README, docs, open PRs, issues, and recent commits, then stores them as observations.',
|
|
705
|
+
inputSchema: {
|
|
706
|
+
type: 'object',
|
|
707
|
+
required: ['query'],
|
|
708
|
+
properties: {
|
|
709
|
+
query: { type: 'string', description: 'The user\'s question or the project/repo name (e.g. "project iverson", "hashicorp/project-iverson", "PLAT"). The tool matches this against configured GITHUB_REPOS, JIRA_PROJECTS, LINEAR_TEAMS.' },
|
|
710
|
+
root_dir: { type: 'string', description: 'Data root dir override (default: homedir()). Use CX_DATA_DIR value if set.' },
|
|
711
|
+
},
|
|
712
|
+
},
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
name: 'knowledge_search',
|
|
716
|
+
description: 'Search Construct\'s own documentation, knowledge base, and distilled embed observations. Call this immediately — no approval needed — when the user asks what Construct is, how a feature works, what commands exist, or anything about Construct\'s architecture or configuration. Also searches embed observations from GitHub, Jira, and other configured sources. Returns relevant excerpts with source file and heading.',
|
|
717
|
+
inputSchema: {
|
|
718
|
+
type: 'object',
|
|
719
|
+
required: ['query'],
|
|
720
|
+
properties: {
|
|
721
|
+
query: { type: 'string', description: 'Natural-language question or keyword (e.g. "what is construct", "how does embed mode work", "provider authority guard", "slack configuration", "open Jira issues").' },
|
|
722
|
+
top_k: { type: 'number', description: 'Max excerpts to return (default: 5).' },
|
|
723
|
+
repo_root: { type: 'string', description: 'Repo root override (default: auto-detected from server location).' },
|
|
724
|
+
root_dir: { type: 'string', description: 'Data directory where .cx/observations/ lives (default: home directory). Pass this to search embed observations from a custom data dir.' },
|
|
725
|
+
},
|
|
726
|
+
},
|
|
727
|
+
},
|
|
1077
728
|
],
|
|
1078
729
|
}));
|
|
1079
730
|
|
|
@@ -1085,25 +736,56 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1085
736
|
if (name === 'agent_health') result = agentHealth(args);
|
|
1086
737
|
else if (name === 'summarize_diff') result = summarizeDiff(args);
|
|
1087
738
|
else if (name === 'scan_file') result = scanFile(args);
|
|
1088
|
-
else if (name === '
|
|
1089
|
-
else if (name === '
|
|
1090
|
-
else if (name === '
|
|
1091
|
-
else if (name === '
|
|
1092
|
-
else if (name === '
|
|
1093
|
-
else if (name === '
|
|
1094
|
-
else if (name === '
|
|
1095
|
-
else if (name === '
|
|
739
|
+
else if (name === 'extract_document_text') result = extractDocumentText(args);
|
|
740
|
+
else if (name === 'ingest_document') result = await ingestDocument(args);
|
|
741
|
+
else if (name === 'infer_document_schema') result = await inferDocumentSchemaTool(args);
|
|
742
|
+
else if (name === 'list_schema_artifacts') result = listSchemaArtifactsTool(args);
|
|
743
|
+
else if (name === 'storage_status') result = await storageStatus(args);
|
|
744
|
+
else if (name === 'storage_sync') result = await storageSync(args);
|
|
745
|
+
else if (name === 'storage_reset') result = await storageReset(args);
|
|
746
|
+
else if (name === 'delete_ingested_artifacts') result = deleteIngestedArtifactsTool(args);
|
|
747
|
+
else if (name === 'project_context') result = projectContext(args, opts);
|
|
748
|
+
else if (name === 'orchestration_policy') result = await orchestrationPolicy(args);
|
|
749
|
+
else if (name === 'list_skills') result = listSkills(opts);
|
|
750
|
+
else if (name === 'get_skill') result = getSkill(args, opts);
|
|
751
|
+
else if (name === 'search_skills') result = searchSkills(args, opts);
|
|
752
|
+
else if (name === 'get_template') result = getTemplate(args, opts);
|
|
753
|
+
else if (name === 'list_templates') result = listTemplates(opts);
|
|
754
|
+
else if (name === 'agent_contract') result = await agentContract(args);
|
|
755
|
+
else if (name === 'broker_check') result = await brokerCheck(args);
|
|
756
|
+
else if (name === 'worker_run') result = await workerRun(args);
|
|
757
|
+
else if (name === 'workflow_status') result = workflowStatus(args, opts);
|
|
1096
758
|
else if (name === 'workflow_init') result = workflowInit(args);
|
|
1097
759
|
else if (name === 'workflow_add_task') result = workflowAddTask(args);
|
|
1098
760
|
else if (name === 'workflow_update_task') result = workflowUpdateTask(args);
|
|
1099
761
|
else if (name === 'workflow_needs_main_input') result = workflowNeedsMainInput(args);
|
|
1100
762
|
else if (name === 'workflow_validate') result = workflowValidate(args);
|
|
1101
763
|
else if (name === 'workflow_import_plan') result = workflowImportPlan(args);
|
|
1102
|
-
else if (name === 'list_teams') result = listTeams();
|
|
1103
|
-
else if (name === 'get_team') result = getTeam(args);
|
|
1104
|
-
else if (name === '
|
|
764
|
+
else if (name === 'list_teams') result = listTeams(opts);
|
|
765
|
+
else if (name === 'get_team') result = getTeam(args, opts);
|
|
766
|
+
else if (name === 'cx_trace_telemetry') { const m = await import('./tools/telemetry.mjs'); result = await m.cxTrace(args, opts); }
|
|
767
|
+
else if (name === 'cx_trace') result = await cxTrace(args, opts);
|
|
1105
768
|
else if (name === 'cx_score') result = await cxScore(args);
|
|
1106
|
-
else if (name === '
|
|
769
|
+
else if (name === 'cx_trace_update') result = await cxTraceUpdate(args);
|
|
770
|
+
else if (name === 'session_list') result = sessionList(args);
|
|
771
|
+
else if (name === 'session_load') result = sessionLoad(args);
|
|
772
|
+
else if (name === 'session_search') result = sessionSearch(args);
|
|
773
|
+
else if (name === 'session_save') result = sessionSave(args);
|
|
774
|
+
else if (name === 'memory_search') result = await memorySearch(args);
|
|
775
|
+
else if (name === 'memory_add_observations') result = memoryAddObservations(args);
|
|
776
|
+
else if (name === 'memory_create_entities') result = memoryCreateEntities(args);
|
|
777
|
+
else if (name === 'memory_recent') result = memoryRecent(args);
|
|
778
|
+
else if (name === 'rovo_search') result = await rovoSearch(args);
|
|
779
|
+
else if (name === 'efficiency_snapshot') result = efficiencySnapshot(args);
|
|
780
|
+
else if (name === 'session_usage') result = await sessionUsage(args, opts);
|
|
781
|
+
else if (name === 'provider_fetch') {
|
|
782
|
+
const { demandFetch } = await import('../embed/demand-fetch.mjs');
|
|
783
|
+
result = await demandFetch({ query: args.query, rootDir: args.root_dir });
|
|
784
|
+
}
|
|
785
|
+
else if (name === 'knowledge_search') {
|
|
786
|
+
const { knowledgeSearch } = await import('../knowledge/search.mjs');
|
|
787
|
+
result = knowledgeSearch({ query: args.query, topK: args.top_k, repoRoot: args.repo_root, rootDir: args.root_dir });
|
|
788
|
+
}
|
|
1107
789
|
else result = { error: `Unknown tool: ${name}` };
|
|
1108
790
|
} catch (err) {
|
|
1109
791
|
result = { error: err.message ?? String(err) };
|
|
@@ -1114,9 +796,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1114
796
|
};
|
|
1115
797
|
});
|
|
1116
798
|
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
799
|
+
const cxTraceBound = (args) => cxTrace(args, opts);
|
|
800
|
+
const projectContextBound = (args) => projectContext(args, opts);
|
|
801
|
+
const workflowStatusBound = (args) => workflowStatus(args, opts);
|
|
802
|
+
|
|
803
|
+
export {
|
|
804
|
+
cxTraceBound as cxTrace,
|
|
805
|
+
projectContextBound as projectContext,
|
|
806
|
+
workflowStatusBound as workflowStatus,
|
|
807
|
+
extractDocumentText,
|
|
808
|
+
ingestDocument,
|
|
809
|
+
inferDocumentSchemaTool,
|
|
810
|
+
listSchemaArtifactsTool,
|
|
811
|
+
storageStatus,
|
|
812
|
+
storageSync,
|
|
813
|
+
storageReset,
|
|
814
|
+
deleteIngestedArtifactsTool,
|
|
815
|
+
agentContract,
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
const argv1Real = (() => { try { return realpathSync(process.argv[1]); } catch { return process.argv[1]; } })();
|
|
819
|
+
if (fileURLToPath(import.meta.url) === argv1Real) {
|
|
1120
820
|
console.error('[construct-mcp] server started');
|
|
1121
821
|
const transport = new StdioServerTransport();
|
|
1122
822
|
await server.connect(transport);
|