@geraldmaron/construct 1.1.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -19
- package/bin/construct +487 -87
- package/bin/construct-postinstall.mjs +30 -2
- package/examples/distribution/README.md +42 -0
- package/examples/distribution/manifest.json +53 -0
- package/examples/distribution/sources/adr.md +84 -0
- package/examples/distribution/sources/deck-one-pager.md +65 -0
- package/examples/distribution/sources/prd-platform.md +161 -0
- package/examples/distribution/sources/research-brief.md +80 -0
- package/examples/distribution/sources/rfc-platform.md +82 -0
- package/examples/distribution/sources/runbook.md +103 -0
- package/examples/distribution/sources/strategy.md +88 -0
- package/lib/adapters-sync.mjs +67 -0
- package/lib/artifact-gate-notice.mjs +38 -0
- package/lib/artifact-manifest.mjs +258 -0
- package/lib/artifact-release-gate.mjs +171 -0
- package/lib/artifact-reviewers.mjs +68 -0
- package/lib/artifact-type-from-path.mjs +79 -0
- package/lib/artifact-workflow.mjs +252 -0
- package/lib/audit-skills.mjs +4 -0
- package/lib/audit-specialists.mjs +285 -0
- package/lib/audit-trail.mjs +1 -1
- package/lib/auto-docs.mjs +90 -19
- package/lib/beads/drift.mjs +27 -6
- package/lib/beads-client.mjs +49 -121
- package/lib/beads-optimistic.mjs +5 -12
- package/lib/brand-fonts.mjs +93 -0
- package/lib/brand-prose.mjs +214 -0
- package/lib/brand-tokens.mjs +92 -0
- package/lib/bridges/copilot-proxy.mjs +13 -26
- package/lib/capability-ledger.mjs +156 -0
- package/lib/certification/artifact-fixtures.mjs +132 -0
- package/lib/certification/artifact-gates.mjs +63 -0
- package/lib/certification/artifact-provenance.mjs +97 -0
- package/lib/certification/canonical-scenarios.mjs +78 -0
- package/lib/certification/cli.mjs +191 -0
- package/lib/certification/dashboard-api.mjs +71 -0
- package/lib/certification/demo-parity.mjs +116 -0
- package/lib/certification/document-io-fixtures.mjs +246 -0
- package/lib/certification/document-workflow.mjs +97 -0
- package/lib/certification/eval-bridge.mjs +77 -0
- package/lib/certification/model-routing.mjs +149 -0
- package/lib/certification/prompt-budget.mjs +119 -0
- package/lib/certification/rc-gate.mjs +206 -0
- package/lib/certification/real-llm-scenarios.mjs +303 -0
- package/lib/certification/role-cards.mjs +113 -0
- package/lib/certification/role-overlays.mjs +117 -0
- package/lib/certification/run.mjs +122 -0
- package/lib/certification/runner.mjs +323 -0
- package/lib/certification/scenarios.mjs +51 -0
- package/lib/certification/skill-inventory.mjs +289 -0
- package/lib/certification/skill-scenarios.mjs +147 -0
- package/lib/certification/specialist-contracts.mjs +85 -0
- package/lib/certification/specialist-scenarios.mjs +175 -0
- package/lib/certification/stale-impact.mjs +146 -0
- package/lib/certification/status.mjs +252 -0
- package/lib/certification/store.mjs +77 -0
- package/lib/chat/cli.mjs +333 -0
- package/lib/chat/command-suggest.mjs +161 -0
- package/lib/chat/commands.mjs +215 -0
- package/lib/chat/config.mjs +142 -0
- package/lib/chat/context-compactor.mjs +250 -0
- package/lib/chat/context-continuation.mjs +253 -0
- package/lib/chat/continuation-source.mjs +58 -0
- package/lib/chat/demo-guide.mjs +61 -0
- package/lib/chat/design-tokens.mjs +91 -0
- package/lib/chat/desktop-binary.mjs +79 -0
- package/lib/chat/desktop-build.mjs +130 -0
- package/lib/chat/desktop-launcher.mjs +133 -0
- package/lib/chat/evidence.mjs +145 -0
- package/lib/chat/export.mjs +74 -0
- package/lib/chat/harness/driver.mjs +91 -0
- package/lib/chat/list-picker.mjs +112 -0
- package/lib/chat/model-picker.mjs +356 -0
- package/lib/chat/openrouter-fallback.mjs +151 -0
- package/lib/chat/permission-prompt.mjs +33 -0
- package/lib/chat/picker-catalog.mjs +45 -0
- package/lib/chat/policy-telemetry.mjs +34 -0
- package/lib/chat/present.mjs +246 -0
- package/lib/chat/session-context.mjs +39 -0
- package/lib/chat/session-persist.mjs +73 -0
- package/lib/chat/session-restore.mjs +71 -0
- package/lib/chat/session-settings.mjs +53 -0
- package/lib/chat/system-prompt.mjs +52 -0
- package/lib/chat/transparency.mjs +93 -0
- package/lib/chat/tui/color-scheme.mjs +42 -0
- package/lib/chat/tui/markdown.mjs +123 -0
- package/lib/chat/tui/presentation.mjs +100 -0
- package/lib/chat/tui/render.mjs +500 -0
- package/lib/chat/tui/turn-block.mjs +284 -0
- package/lib/chat/tui/turn-present.mjs +18 -0
- package/lib/chat/tui/turn-state.mjs +88 -0
- package/lib/chat/tui/usage.mjs +122 -0
- package/lib/chat/web-commands.mjs +146 -0
- package/lib/chat/web-launcher.mjs +63 -0
- package/lib/chat/web-picker-keys.mjs +46 -0
- package/lib/chat/web-session.mjs +159 -0
- package/lib/cli-commands.mjs +146 -15
- package/lib/cli-service-inventory.mjs +42 -0
- package/lib/comment-lint.mjs +6 -4
- package/lib/config/intake-policy.mjs +209 -0
- package/lib/config/project-config.mjs +11 -2
- package/lib/config/schema.mjs +86 -6
- package/lib/config/source-targets.mjs +311 -0
- package/lib/contract-schemas/decision.json +50 -0
- package/lib/contract-schemas/implementation.json +51 -0
- package/lib/contract-schemas/review-report.json +32 -0
- package/lib/contract-schemas/test-report.json +43 -0
- package/lib/contracts/construct-handoff.mjs +60 -0
- package/lib/contracts/validate.mjs +32 -3
- package/lib/contracts/violation-log.mjs +58 -5
- package/lib/dashboard-demo.mjs +71 -0
- package/lib/dashboard-static.mjs +19 -5
- package/lib/decisions/registry.mjs +5 -3
- package/lib/deck-export-pptx.mjs +1152 -0
- package/lib/demo-recording.mjs +142 -0
- package/lib/demo-script.mjs +114 -0
- package/lib/demo-surface.mjs +249 -0
- package/lib/demo.mjs +598 -140
- package/lib/diagram-export.mjs +192 -0
- package/lib/diagram.mjs +13 -11
- package/lib/docs-verify.mjs +25 -7
- package/lib/doctor/index.mjs +3 -1
- package/lib/doctor/project-adapters.mjs +44 -0
- package/lib/doctor/source-checkout.mjs +16 -0
- package/lib/doctor/watchers/cx-budget.mjs +98 -0
- package/lib/doctor/watchers/graph-staleness.mjs +52 -0
- package/lib/doctor/watchers/process-pressure.mjs +4 -3
- package/lib/document-export.mjs +384 -38
- package/lib/document-extract.mjs +44 -4
- package/lib/document-ingest.mjs +29 -20
- package/lib/embed/auto-sources.mjs +44 -0
- package/lib/embed/cli.mjs +4 -3
- package/lib/embed/daemon.mjs +18 -46
- package/lib/embed/demand-fetch.mjs +50 -19
- package/lib/embed/inbox.mjs +6 -10
- package/lib/embed/worker.mjs +4 -3
- package/lib/embedded-contract/index.mjs +11 -0
- package/lib/embedded-contract/model-resolve.mjs +10 -9
- package/lib/env-config.mjs +26 -0
- package/lib/evals/dataset.mjs +137 -0
- package/lib/evals/gates.mjs +175 -0
- package/lib/export-branding.mjs +32 -0
- package/lib/graph/build-co-change.mjs +54 -0
- package/lib/graph/build-from-registry.mjs +136 -0
- package/lib/graph/build-import-graph.mjs +153 -0
- package/lib/graph/cli.mjs +110 -0
- package/lib/graph/impact-cli.mjs +89 -0
- package/lib/graph/impact.mjs +108 -0
- package/lib/graph/staleness.mjs +44 -0
- package/lib/graph/store.mjs +172 -0
- package/lib/health-check.mjs +90 -76
- package/lib/hooks/artifact-release-gate.mjs +43 -0
- package/lib/hooks/brand-prose-lint.mjs +38 -0
- package/lib/hooks/graph-impact-advisory.mjs +62 -0
- package/lib/hooks/session-optimize.mjs +84 -207
- package/lib/hooks/session-start.mjs +18 -16
- package/lib/host-disposition.mjs +7 -0
- package/lib/improvement/cli.mjs +189 -0
- package/lib/improvement/controller.mjs +137 -0
- package/lib/improvement/proposal.mjs +120 -0
- package/lib/improvement/specialist-loop.mjs +192 -0
- package/lib/improvement/store.mjs +89 -0
- package/lib/improvement/surface.mjs +219 -0
- package/lib/ingest-tooling.mjs +97 -0
- package/lib/init/doc-lanes.mjs +165 -0
- package/lib/init-docs.mjs +31 -178
- package/lib/init-unified.mjs +50 -119
- package/lib/init-update-guide.mjs +102 -0
- package/lib/init-update.mjs +32 -1
- package/lib/init.mjs +8 -0
- package/lib/install/desktop-binary-download.mjs +85 -0
- package/lib/install/legacy-global-cleanup.mjs +189 -0
- package/lib/intake/constants.mjs +29 -0
- package/lib/intake/daemon.mjs +2 -0
- package/lib/intake/git-queue.mjs +9 -8
- package/lib/intake/intake-config.mjs +52 -105
- package/lib/intake/legacy-paths.mjs +5 -0
- package/lib/intake/queue.mjs +2 -1
- package/lib/intake/session-prelude.mjs +90 -1
- package/lib/libreoffice-export.mjs +97 -0
- package/lib/logging/rotate.mjs +9 -0
- package/lib/maintenance/docker-reclaim.mjs +206 -0
- package/lib/mcp/external-schema-cost.mjs +74 -0
- package/lib/mcp/server.mjs +103 -10
- package/lib/mcp/stdio-mcp-probe.mjs +188 -0
- package/lib/mcp/tool-budget.mjs +55 -4
- package/lib/mcp/tools/document.mjs +18 -4
- package/lib/mcp/tools/embedded-contract.mjs +14 -0
- package/lib/mcp/tools/skills.mjs +32 -3
- package/lib/mcp/tools/workflow.mjs +13 -1
- package/lib/model-free-selector.mjs +18 -0
- package/lib/model-registry.mjs +13 -229
- package/lib/model-router.mjs +373 -92
- package/lib/models/behavior-matrix.mjs +289 -0
- package/lib/models/catalog.mjs +209 -0
- package/lib/models/execution-capability-profile.mjs +196 -0
- package/lib/models/execution-policy.mjs +307 -0
- package/lib/models/provider-poll.mjs +383 -0
- package/lib/npm-spawn-env.mjs +17 -0
- package/lib/ollama/installed-models.mjs +129 -0
- package/lib/oracle/actions.mjs +187 -0
- package/lib/oracle/artifact-gate.mjs +99 -0
- package/lib/oracle/cli.mjs +204 -0
- package/lib/oracle/daemon-entry.mjs +14 -0
- package/lib/oracle/dispatch.mjs +81 -0
- package/lib/oracle/execute.mjs +143 -0
- package/lib/oracle/gaps.mjs +76 -0
- package/lib/oracle/index.mjs +84 -0
- package/lib/oracle/issues.mjs +164 -0
- package/lib/oracle/org-graph.mjs +170 -0
- package/lib/oracle/policy.mjs +80 -0
- package/lib/oracle/read-model.mjs +398 -0
- package/lib/oracle/reconcile.mjs +191 -0
- package/lib/oracle/routing.mjs +89 -0
- package/lib/oracle/synthesize.mjs +384 -0
- package/lib/oracle/verdicts.mjs +51 -0
- package/lib/orchestration/worker.mjs +88 -27
- package/lib/orchestration-policy.mjs +50 -0
- package/lib/parity.mjs +96 -2
- package/lib/playwright-demo.mjs +254 -0
- package/lib/project-init-shared.mjs +4 -1
- package/lib/prompt-composer.js +7 -3
- package/lib/prompt-validation-contract.mjs +24 -0
- package/lib/provider-capabilities.js +57 -11
- package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
- package/lib/providers/contract/adapters/git/index.mjs +115 -0
- package/lib/providers/contract/adapters/github/index.mjs +166 -0
- package/lib/providers/contract/adapters/jira/index.mjs +187 -0
- package/lib/providers/contract/adapters/slack/index.mjs +175 -0
- package/lib/providers/contract/contract-tests.mjs +57 -0
- package/lib/providers/contract/errors.mjs +48 -0
- package/lib/providers/contract/interface.mjs +50 -0
- package/lib/providers/contract/registry.mjs +102 -0
- package/lib/providers/copilot-auth.mjs +297 -0
- package/lib/providers/credential-bootstrap.mjs +178 -0
- package/lib/providers/credential-catalog.mjs +46 -0
- package/lib/providers/credential-sources.mjs +63 -0
- package/lib/providers/creds.mjs +5 -2
- package/lib/providers/op-run.mjs +59 -0
- package/lib/providers/secret-resolver.mjs +159 -0
- package/lib/publish-template.mjs +163 -0
- package/lib/publish-tooling.mjs +119 -0
- package/lib/publish.mjs +305 -0
- package/lib/registry/cli.mjs +82 -0
- package/lib/registry/consolidation.mjs +147 -0
- package/lib/registry/generate-docs.mjs +75 -0
- package/lib/registry/skill-verification.mjs +57 -0
- package/lib/registry/surface-map.mjs +76 -0
- package/lib/registry/validate.mjs +135 -0
- package/lib/resources/budget.mjs +82 -0
- package/lib/resources/process-budget.mjs +45 -0
- package/lib/rules-delivery.mjs +9 -2
- package/lib/rules-read.mjs +26 -0
- package/lib/runtime-env.mjs +23 -0
- package/lib/runtime-pressure.mjs +51 -7
- package/lib/schema-infer.mjs +13 -2
- package/lib/server/chat-loop.mjs +622 -0
- package/lib/server/demo-preview.mjs +63 -0
- package/lib/server/index.mjs +259 -9
- package/lib/server/rate-limit.mjs +5 -3
- package/lib/server/webhook.mjs +3 -2
- package/lib/service-manager.mjs +60 -8
- package/lib/setup.mjs +70 -7
- package/lib/specialists/prompt-schema.mjs +19 -10
- package/lib/specialists/roster.mjs +43 -0
- package/lib/specialists/scaffold.mjs +56 -0
- package/lib/storage/backend.mjs +6 -6
- package/lib/storage/hybrid-query.mjs +7 -4
- package/lib/storage/state-source.mjs +5 -5
- package/lib/storage/sync.mjs +2 -3
- package/lib/telemetry/rule-calls.mjs +52 -0
- package/lib/template-registry.mjs +2 -2
- package/lib/templates/visual-requirements.mjs +27 -51
- package/lib/test-corpus-inventory.mjs +313 -0
- package/lib/uninstall/uninstall.mjs +19 -7
- package/lib/update.mjs +12 -0
- package/lib/upgrade.mjs +14 -0
- package/lib/wireframe.mjs +20 -14
- package/lib/worker/run.mjs +17 -4
- package/lib/worker/trace.mjs +11 -3
- package/package.json +28 -14
- package/personas/construct.md +2 -2
- package/platforms/claude/settings.template.json +36 -0
- package/rules/common/patterns.md +1 -1
- package/rules/common/release-gates.md +4 -3
- package/scripts/sync-specialists.mjs +41 -28
- package/skills/devops/data-engineering.md +1 -1
- package/skills/docs/adr-workflow.md +1 -0
- package/skills/docs/backlog-proposal-workflow.md +1 -0
- package/skills/docs/codebase-research-workflow.md +40 -0
- package/skills/docs/customer-profile-workflow.md +1 -0
- package/skills/docs/document-ingest-workflow.md +1 -0
- package/skills/docs/evidence-ingest-workflow.md +1 -0
- package/skills/docs/init-docs.md +2 -2
- package/skills/docs/init-project.md +7 -2
- package/skills/docs/prd-workflow.md +24 -1
- package/skills/docs/prfaq-workflow.md +1 -0
- package/skills/docs/product-intelligence-workflow.md +1 -0
- package/skills/docs/product-signal-workflow.md +1 -0
- package/skills/docs/research-workflow.md +54 -37
- package/skills/docs/runbook-workflow.md +1 -0
- package/skills/docs/strategy-workflow.md +1 -0
- package/skills/docs/user-research-workflow.md +40 -0
- package/skills/operating/orchestration-reference.md +1 -1
- package/skills/roles/architect.md +5 -0
- package/skills/roles/operator.docs.md +4 -0
- package/skills/routing.md +4 -2
- package/specialists/artifact-manifest.json +489 -0
- package/specialists/artifact-manifest.schema.json +83 -0
- package/specialists/audit-enrichments.json +454 -0
- package/specialists/contracts.json +37 -25
- package/specialists/prompts/_shared/validation-contract.md +26 -0
- package/specialists/prompts/cx-accessibility.md +21 -2
- package/specialists/prompts/cx-ai-engineer.md +24 -1
- package/specialists/prompts/cx-architect.md +4 -1
- package/specialists/prompts/cx-business-strategist.md +23 -2
- package/specialists/prompts/cx-data-analyst.md +22 -1
- package/specialists/prompts/cx-data-engineer.md +23 -2
- package/specialists/prompts/cx-debugger.md +21 -2
- package/specialists/prompts/cx-designer.md +26 -3
- package/specialists/prompts/cx-devil-advocate.md +19 -2
- package/specialists/prompts/cx-docs-keeper.md +28 -1
- package/specialists/prompts/cx-engineer.md +22 -1
- package/specialists/prompts/cx-evaluator.md +18 -1
- package/specialists/prompts/cx-explorer.md +21 -2
- package/specialists/prompts/cx-legal-compliance.md +21 -2
- package/specialists/prompts/cx-operations.md +21 -2
- package/specialists/prompts/cx-oracle.md +94 -0
- package/specialists/prompts/cx-orchestrator.md +20 -1
- package/specialists/prompts/cx-platform-engineer.md +25 -2
- package/specialists/prompts/cx-product-manager.md +32 -1
- package/specialists/prompts/cx-qa.md +24 -2
- package/specialists/prompts/cx-rd-lead.md +23 -2
- package/specialists/prompts/cx-release-manager.md +23 -2
- package/specialists/prompts/cx-researcher.md +22 -2
- package/specialists/prompts/cx-reviewer.md +18 -1
- package/specialists/prompts/cx-security.md +22 -2
- package/specialists/prompts/cx-sre.md +30 -3
- package/specialists/prompts/cx-test-automation.md +7 -1
- package/specialists/prompts/cx-trace-reviewer.md +22 -3
- package/specialists/prompts/cx-ux-researcher.md +21 -2
- package/specialists/registry.json +52 -173
- package/specialists/tone-profiles.json +42 -0
- package/templates/demos/playwright/demo-recording.config.mjs +47 -0
- package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
- package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
- package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
- package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
- package/templates/demos/tapes/resource-guard-rails.tape +49 -0
- package/templates/demos/vhs/construct-cockpit.json +24 -0
- package/templates/distribution/construct-brand.typ +446 -0
- package/templates/distribution/construct-decision.typ +38 -0
- package/templates/distribution/construct-deck.html +95 -0
- package/templates/distribution/construct-pdf.typ +38 -0
- package/templates/distribution/construct-prd.typ +38 -0
- package/templates/distribution/construct-reference.docx +0 -0
- package/templates/distribution/construct-research.typ +38 -0
- package/templates/distribution/construct-web.html +92 -0
- package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
- package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
- package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
- package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/README.md +36 -0
- package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
- package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
- package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
- package/templates/distribution/icons/activity.svg +15 -0
- package/templates/distribution/icons/alert-triangle.svg +17 -0
- package/templates/distribution/icons/book-open.svg +16 -0
- package/templates/distribution/icons/bot.svg +20 -0
- package/templates/distribution/icons/brain.svg +22 -0
- package/templates/distribution/icons/circle-check.svg +16 -0
- package/templates/distribution/icons/clipboard-check.svg +17 -0
- package/templates/distribution/icons/cpu.svg +28 -0
- package/templates/distribution/icons/database.svg +17 -0
- package/templates/distribution/icons/eye.svg +16 -0
- package/templates/distribution/icons/file-text.svg +19 -0
- package/templates/distribution/icons/gauge.svg +16 -0
- package/templates/distribution/icons/git-branch.svg +17 -0
- package/templates/distribution/icons/key.svg +17 -0
- package/templates/distribution/icons/layers.svg +17 -0
- package/templates/distribution/icons/list-checks.svg +19 -0
- package/templates/distribution/icons/lock.svg +16 -0
- package/templates/distribution/icons/message-square.svg +15 -0
- package/templates/distribution/icons/network.svg +19 -0
- package/templates/distribution/icons/route.svg +17 -0
- package/templates/distribution/icons/scale.svg +19 -0
- package/templates/distribution/icons/search.svg +16 -0
- package/templates/distribution/icons/send.svg +16 -0
- package/templates/distribution/icons/server.svg +18 -0
- package/templates/distribution/icons/shield-check.svg +16 -0
- package/templates/distribution/icons/users.svg +18 -0
- package/templates/distribution/icons/webhook.svg +17 -0
- package/templates/distribution/icons/workflow.svg +17 -0
- package/templates/distribution/icons/wrench.svg +15 -0
- package/templates/distribution/run.mjs +18 -1
- package/templates/docs/adr.md +7 -0
- package/templates/docs/construct_guide.md +8 -8
- package/templates/docs/customer-profile.md +4 -0
- package/templates/docs/one-pager.md +4 -0
- package/templates/docs/postmortem.md +31 -0
- package/templates/docs/prd-platform.md +19 -0
- package/templates/docs/prd.md +15 -0
- package/templates/docs/prfaq.md +7 -0
- package/templates/docs/qa-strategy.md +103 -0
- package/templates/docs/research-brief.md +8 -0
- package/templates/docs/rfc-platform.md +12 -0
- package/templates/docs/test-plan.md +7 -0
package/lib/mcp/server.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import { resolve, dirname } from 'node:path';
|
|
|
13
13
|
import { fileURLToPath } from 'node:url';
|
|
14
14
|
import { realpathSync } from 'node:fs';
|
|
15
15
|
import { loadToolkitEnv } from '../toolkit-env.mjs';
|
|
16
|
-
import { loadConstructEnv } from '../env
|
|
16
|
+
import { loadConstructEnv, prepareConstructEnv } from '../runtime-env.mjs';
|
|
17
17
|
import { getInstalledVersion } from '../version.mjs';
|
|
18
18
|
import { getDeploymentMode } from '../deployment-mode.mjs';
|
|
19
19
|
import { withGenAiSpan, GenAiAttrs, extractTraceContext, injectTraceContext } from '../telemetry/otel-tracer.mjs';
|
|
@@ -21,7 +21,7 @@ import { withGenAiSpan, GenAiAttrs, extractTraceContext, injectTraceContext } fr
|
|
|
21
21
|
// Apply config.env values to process.env, letting config.env win over shell env
|
|
22
22
|
// so telemetry/OpenRouter credentials are always correct regardless of host env.
|
|
23
23
|
{
|
|
24
|
-
const confEnv =
|
|
24
|
+
const confEnv = prepareConstructEnv({ warn: false });
|
|
25
25
|
for (const [k, v] of Object.entries(confEnv)) {
|
|
26
26
|
process.env[k] = v;
|
|
27
27
|
}
|
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
profileCreate, profileArchive, sandboxList, learningStatus,
|
|
59
59
|
knowledgeGraphAsk,
|
|
60
60
|
} from './tools/profile.mjs';
|
|
61
|
-
import { modelResolve, triageRecommend, workflowInvoke, capabilityDescribe, executionResolve } from './tools/embedded-contract.mjs';
|
|
61
|
+
import { modelResolve, triageRecommend, workflowInvoke, capabilityDescribe, executionResolve, artifactWorkflow } from './tools/embedded-contract.mjs';
|
|
62
62
|
|
|
63
63
|
const DEFAULT_ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
64
64
|
const ROOT_DIR = resolve(process.env.CX_TOOLKIT_DIR || DEFAULT_ROOT_DIR);
|
|
@@ -170,7 +170,7 @@ const ALL_TOOL_DEFS = [
|
|
|
170
170
|
},
|
|
171
171
|
{
|
|
172
172
|
name: 'extract_document_text',
|
|
173
|
-
description: 'Extracts readable text from a local document path.
|
|
173
|
+
description: 'Extracts readable text from a local document path. Uses node-native extractors (unpdf/mammoth) first; escalates to the docling Python sidecar or whisper.cpp when needed (same pipeline as `construct ingest`). Supports PDF, DOCX, XLSX, PPTX, HTML, plain text, email, and transcripts.',
|
|
174
174
|
inputSchema: {
|
|
175
175
|
type: 'object',
|
|
176
176
|
properties: {
|
|
@@ -375,7 +375,7 @@ const ALL_TOOL_DEFS = [
|
|
|
375
375
|
},
|
|
376
376
|
{
|
|
377
377
|
name: 'get_skill',
|
|
378
|
-
description: 'Reads a specific skill playbook from the Construct knowledge base (e.g. "
|
|
378
|
+
description: 'Reads a specific skill playbook from the Construct knowledge base (e.g. "docs/adr-workflow", "roles/engineer", "architecture/security-arch").',
|
|
379
379
|
inputSchema: {
|
|
380
380
|
type: 'object',
|
|
381
381
|
properties: {
|
|
@@ -932,15 +932,50 @@ const ALL_TOOL_DEFS = [
|
|
|
932
932
|
},
|
|
933
933
|
{
|
|
934
934
|
name: 'document_export',
|
|
935
|
-
description: 'Convert a markdown file into a distributable PDF, DOCX,
|
|
935
|
+
description: 'Convert a markdown file into a distributable document — PDF, DOCX, legacy DOC (LibreOffice), deck HTML, PPTX, HTML, RTF, ODT, EPUB, LaTeX, plain text, or Markdown — via Pandoc and Typst (PDF engine) plus optional pptxgenjs/LibreOffice, per ADR-0024. Engines are optional dependencies discovered at runtime — the tool returns a structured "install <binary>" error when tooling is absent, never crashes. Use `detect_only=true` to check availability without running an export.',
|
|
936
936
|
inputSchema: {
|
|
937
937
|
type: 'object',
|
|
938
938
|
required: ['format'],
|
|
939
939
|
properties: {
|
|
940
940
|
input_path: { type: 'string', description: 'Absolute path to the markdown source file (required unless detect_only=true).' },
|
|
941
941
|
output_path: { type: 'string', description: 'Optional absolute output path; defaults to <input>.<format> next to the source.' },
|
|
942
|
-
format: { type: 'string', enum: ['pdf', 'docx', 'html'], description: 'Target format.' },
|
|
942
|
+
format: { type: 'string', enum: ['pdf', 'docx', 'doc', 'deck', 'pptx', 'html', 'rtf', 'odt', 'epub', 'tex', 'txt', 'md', 'mdx'], description: 'Target format.' },
|
|
943
943
|
detect_only: { type: 'boolean', description: 'When true, report binary availability without exporting. Default false.' },
|
|
944
|
+
figures: { type: 'boolean', description: 'When true, render d2/mermaid via pandoc-ext/diagram filter. Default false.' },
|
|
945
|
+
},
|
|
946
|
+
},
|
|
947
|
+
},
|
|
948
|
+
{
|
|
949
|
+
name: 'publish_detect',
|
|
950
|
+
description: 'Detect availability of publish pipeline tooling: Pandoc/Typst export, pandoc-ext/diagram figures, VHS terminal demos, and Playwright dashboard demos.',
|
|
951
|
+
inputSchema: {
|
|
952
|
+
type: 'object',
|
|
953
|
+
properties: {
|
|
954
|
+
format: { type: 'string', enum: ['pdf', 'docx', 'doc', 'deck', 'pptx', 'html', 'rtf', 'odt', 'epub', 'tex', 'txt', 'md', 'mdx'], description: 'Export format to probe (default pdf).' },
|
|
955
|
+
figures: { type: 'boolean', description: 'Include figure binaries (default true).' },
|
|
956
|
+
demo: { type: 'string', description: 'When set, require VHS/asciinema for terminal demo.' },
|
|
957
|
+
dashboard_demo: { type: 'string', description: 'When set, require Playwright workspace for dashboard demo.' },
|
|
958
|
+
},
|
|
959
|
+
},
|
|
960
|
+
},
|
|
961
|
+
{
|
|
962
|
+
name: 'publish_run',
|
|
963
|
+
description: 'Run the publish pipeline: export markdown with optional figure filter and optional demo recordings. Use dry_run=true to probe tooling only.',
|
|
964
|
+
inputSchema: {
|
|
965
|
+
type: 'object',
|
|
966
|
+
required: ['input_path'],
|
|
967
|
+
properties: {
|
|
968
|
+
input_path: { type: 'string', description: 'Absolute path to markdown research brief.' },
|
|
969
|
+
output_path: { type: 'string', description: 'Optional output path for export.' },
|
|
970
|
+
format: { type: 'string', enum: ['pdf', 'docx', 'doc', 'deck', 'pptx', 'html', 'rtf', 'odt', 'epub', 'tex', 'txt', 'md', 'mdx'], description: 'Target format (default pdf).' },
|
|
971
|
+
demo: { type: 'string', description: 'Terminal tape name to record.' },
|
|
972
|
+
dashboard_demo: { type: 'string', description: 'Dashboard Playwright demo spec basename.' },
|
|
973
|
+
figures: { type: 'boolean', description: 'Render diagrams (default true).' },
|
|
974
|
+
strict: { type: 'boolean', description: 'Fail when toolchain or release gate fails (default true).' },
|
|
975
|
+
source_only: { type: 'boolean', description: 'Write sources only (default false).' },
|
|
976
|
+
no_gate: { type: 'boolean', description: 'Skip artifact release gate (default false).' },
|
|
977
|
+
artifact_type: { type: 'string', description: 'Manifest doc type for release gate when path inference is ambiguous.' },
|
|
978
|
+
dry_run: { type: 'boolean', description: 'Detect tooling only (default false).' },
|
|
944
979
|
},
|
|
945
980
|
},
|
|
946
981
|
},
|
|
@@ -1099,6 +1134,23 @@ const ALL_TOOL_DEFS = [
|
|
|
1099
1134
|
},
|
|
1100
1135
|
},
|
|
1101
1136
|
},
|
|
1137
|
+
{
|
|
1138
|
+
name: 'artifact_workflow',
|
|
1139
|
+
description: 'Plan a manifest-backed document artifact workflow and return a truthful provenance report. It separates planned steps from locally executed validation/export and never claims a host-planned specialist review or rewrite was completed. Durable local export requires approval_mode=allow-durable-write.',
|
|
1140
|
+
inputSchema: {
|
|
1141
|
+
type: 'object',
|
|
1142
|
+
properties: {
|
|
1143
|
+
input: { type: 'string', description: 'Natural-language artifact request.' },
|
|
1144
|
+
artifact_type: { type: 'string', description: 'Registered manifest document class.' },
|
|
1145
|
+
file_path: { type: 'string', description: 'Optional existing source document for local validation/export.' },
|
|
1146
|
+
format: { type: 'string', description: 'Distribution format such as pdf, docx, html, deck, or pptx.' },
|
|
1147
|
+
output_path: { type: 'string', description: 'Optional output destination when locally exporting.' },
|
|
1148
|
+
branding: { type: 'string', enum: ['construct', 'plain'], description: 'Construct is default; plain is an explicit opt-out.' },
|
|
1149
|
+
overrides: { type: 'object', description: 'Per-invocation manifest workflow overrides.' },
|
|
1150
|
+
approval_mode: { type: 'string', enum: ['proposal-only', 'requires-human-approval', 'allow-durable-write'], description: 'Only allow-durable-write performs local validation/export.' },
|
|
1151
|
+
},
|
|
1152
|
+
},
|
|
1153
|
+
},
|
|
1102
1154
|
{
|
|
1103
1155
|
name: 'model_resolve',
|
|
1104
1156
|
description: 'Resolve which model an embedded Construct workflow should use given the host/IDE provider context. Precedence: host model → same-provider-family fallback → Construct tier default → structured config error. Never reads or returns credential values (requiresCredential is a boolean) and never claims unverified provider health. Read-only; performs no writes.',
|
|
@@ -1270,7 +1322,7 @@ export async function dispatchToolByName(name, args = {}) {
|
|
|
1270
1322
|
if (name === 'agent_health') result = agentHealth(args);
|
|
1271
1323
|
else if (name === 'summarize_diff') result = summarizeDiff(args);
|
|
1272
1324
|
else if (name === 'scan_file') result = scanFile(args);
|
|
1273
|
-
else if (name === 'extract_document_text') result = extractDocumentText(args);
|
|
1325
|
+
else if (name === 'extract_document_text') result = await extractDocumentText(args);
|
|
1274
1326
|
else if (name === 'ingest_document') result = await ingestDocument(args);
|
|
1275
1327
|
else if (name === 'infer_document_schema') result = await inferDocumentSchemaTool(args);
|
|
1276
1328
|
else if (name === 'list_schema_artifacts') result = listSchemaArtifactsTool(args);
|
|
@@ -1324,9 +1376,49 @@ export async function dispatchToolByName(name, args = {}) {
|
|
|
1324
1376
|
else if (name === 'document_export') {
|
|
1325
1377
|
const { detect, exportMarkdown } = await import('../document-export.mjs');
|
|
1326
1378
|
if (args.detect_only) {
|
|
1327
|
-
result = detect(args.format);
|
|
1379
|
+
result = detect(args.format, process.env, { figures: Boolean(args.figures) });
|
|
1380
|
+
} else {
|
|
1381
|
+
result = exportMarkdown({
|
|
1382
|
+
inputPath: args.input_path,
|
|
1383
|
+
outputPath: args.output_path,
|
|
1384
|
+
format: args.format,
|
|
1385
|
+
figures: Boolean(args.figures),
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
else if (name === 'publish_detect') {
|
|
1390
|
+
const { detectPublishPipeline } = await import('../publish-tooling.mjs');
|
|
1391
|
+
result = detectPublishPipeline({
|
|
1392
|
+
format: args.format || 'pdf',
|
|
1393
|
+
includeFigures: args.figures !== false,
|
|
1394
|
+
includeTerminalDemo: Boolean(args.demo),
|
|
1395
|
+
includeDashboardDemo: Boolean(args.dashboard_demo),
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
else if (name === 'publish_run') {
|
|
1399
|
+
const { runPublish } = await import('../publish.mjs');
|
|
1400
|
+
if (args.dry_run) {
|
|
1401
|
+
const { detectPublishPipeline } = await import('../publish-tooling.mjs');
|
|
1402
|
+
result = detectPublishPipeline({
|
|
1403
|
+
format: args.format || 'pdf',
|
|
1404
|
+
includeFigures: args.figures !== false,
|
|
1405
|
+
includeTerminalDemo: Boolean(args.demo),
|
|
1406
|
+
includeDashboardDemo: Boolean(args.dashboard_demo),
|
|
1407
|
+
});
|
|
1328
1408
|
} else {
|
|
1329
|
-
result =
|
|
1409
|
+
result = runPublish({
|
|
1410
|
+
inputPath: args.input_path,
|
|
1411
|
+
format: args.format || 'pdf',
|
|
1412
|
+
outputPath: args.output_path,
|
|
1413
|
+
demos: args.demo ? [args.demo] : [],
|
|
1414
|
+
dashboardDemos: args.dashboard_demo ? [args.dashboard_demo] : [],
|
|
1415
|
+
figures: args.figures !== false,
|
|
1416
|
+
strict: args.strict !== false,
|
|
1417
|
+
gate: args.no_gate !== true,
|
|
1418
|
+
artifactType: args.artifact_type || args.type || null,
|
|
1419
|
+
sourceOnly: Boolean(args.source_only),
|
|
1420
|
+
repoRoot: process.cwd(),
|
|
1421
|
+
});
|
|
1330
1422
|
}
|
|
1331
1423
|
}
|
|
1332
1424
|
else if (name === 'profile_show') result = profileShow(args);
|
|
@@ -1346,6 +1438,7 @@ export async function dispatchToolByName(name, args = {}) {
|
|
|
1346
1438
|
else if (name === 'workflow_invoke') result = await workflowInvoke(args);
|
|
1347
1439
|
else if (name === 'capability_describe') result = capabilityDescribe(args);
|
|
1348
1440
|
else if (name === 'construct_execution_resolve') result = executionResolve(args);
|
|
1441
|
+
else if (name === 'artifact_workflow') result = artifactWorkflow(args);
|
|
1349
1442
|
else if (name === 'orchestration_run') { const m = await import('./tools/orchestration-run.mjs'); result = await m.orchestrationRun(args); }
|
|
1350
1443
|
else if (name === 'orchestration_status') { const m = await import('./tools/orchestration-run.mjs'); result = await m.orchestrationStatus(args); }
|
|
1351
1444
|
else if (name === 'construct_call') {
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/mcp/stdio-mcp-probe.mjs — newline-delimited JSON-RPC probe for stdio MCP servers.
|
|
3
|
+
*
|
|
4
|
+
* Spawns a child MCP process, completes initialize + notifications/initialized, then
|
|
5
|
+
* issues tools/list to capture serialized tool schemas for token-budget fixtures.
|
|
6
|
+
* Stdout is the only protocol channel; stderr is ignored.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { spawn } from 'node:child_process';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_TIMEOUT_MS = 25_000;
|
|
12
|
+
|
|
13
|
+
function parseFrames(chunk, state) {
|
|
14
|
+
state.buffer += chunk;
|
|
15
|
+
let idx;
|
|
16
|
+
while ((idx = state.buffer.indexOf('\n')) >= 0) {
|
|
17
|
+
const raw = state.buffer.slice(0, idx).trim();
|
|
18
|
+
state.buffer = state.buffer.slice(idx + 1);
|
|
19
|
+
if (!raw) continue;
|
|
20
|
+
try {
|
|
21
|
+
state.frames.push(JSON.parse(raw));
|
|
22
|
+
} catch {
|
|
23
|
+
/* non-JSON noise */
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function send(child, frame) {
|
|
29
|
+
child.stdin.write(`${JSON.stringify(frame)}\n`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function waitForFrame(frames, predicate, timeoutMs) {
|
|
33
|
+
const deadline = Date.now() + timeoutMs;
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const tick = () => {
|
|
36
|
+
const hit = frames.find(predicate);
|
|
37
|
+
if (hit) {
|
|
38
|
+
resolve(hit);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (Date.now() >= deadline) {
|
|
42
|
+
reject(new Error(`Timed out after ${timeoutMs}ms; frames=${frames.length}`));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
setTimeout(tick, 50);
|
|
46
|
+
};
|
|
47
|
+
tick();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Probe a stdio MCP server and return the tools/list result array.
|
|
52
|
+
|
|
53
|
+
export async function probeStdioMcpTools(command, args, {
|
|
54
|
+
env = {},
|
|
55
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
56
|
+
} = {}) {
|
|
57
|
+
const child = spawn(command, args, {
|
|
58
|
+
env: { ...process.env, ...env },
|
|
59
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
60
|
+
});
|
|
61
|
+
const state = { buffer: '', frames: [] };
|
|
62
|
+
child.stdout.setEncoding('utf8');
|
|
63
|
+
child.stdout.on('data', (chunk) => parseFrames(chunk, state));
|
|
64
|
+
|
|
65
|
+
const cleanup = () => {
|
|
66
|
+
try {
|
|
67
|
+
child.kill('SIGTERM');
|
|
68
|
+
} catch {
|
|
69
|
+
/* already exited */
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
send(child, {
|
|
75
|
+
jsonrpc: '2.0',
|
|
76
|
+
id: 1,
|
|
77
|
+
method: 'initialize',
|
|
78
|
+
params: {
|
|
79
|
+
protocolVersion: '2024-11-05',
|
|
80
|
+
capabilities: {},
|
|
81
|
+
clientInfo: { name: 'construct-mcp-probe', version: '1' },
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
await waitForFrame(state.frames, (f) => f.id === 1, timeoutMs);
|
|
85
|
+
send(child, { jsonrpc: '2.0', method: 'notifications/initialized' });
|
|
86
|
+
send(child, { jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} });
|
|
87
|
+
const listFrame = await waitForFrame(state.frames, (f) => f.id === 2, timeoutMs);
|
|
88
|
+
if (listFrame.error) {
|
|
89
|
+
throw new Error(listFrame.error.message || JSON.stringify(listFrame.error));
|
|
90
|
+
}
|
|
91
|
+
return listFrame.result?.tools || [];
|
|
92
|
+
} finally {
|
|
93
|
+
cleanup();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Parse JSON or MCP-over-SSE (event: message / data: {...}) bodies from HTTP MCP hosts.
|
|
98
|
+
|
|
99
|
+
function parseMcpHttpFrame(text) {
|
|
100
|
+
try {
|
|
101
|
+
return JSON.parse(text);
|
|
102
|
+
} catch {
|
|
103
|
+
for (const line of text.split('\n')) {
|
|
104
|
+
const trimmed = line.trim();
|
|
105
|
+
if (!trimmed.startsWith('data:')) continue;
|
|
106
|
+
const payload = trimmed.slice(5).trim();
|
|
107
|
+
if (!payload) continue;
|
|
108
|
+
try {
|
|
109
|
+
return JSON.parse(payload);
|
|
110
|
+
} catch {
|
|
111
|
+
/* try next data line */
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
throw new Error('response was not JSON or SSE data');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Probe an HTTP MCP endpoint (GitHub Copilot MCP) with bearer auth.
|
|
119
|
+
|
|
120
|
+
export async function probeHttpMcpTools(url, headers = {}, { timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
|
|
121
|
+
const initBody = {
|
|
122
|
+
jsonrpc: '2.0',
|
|
123
|
+
id: 1,
|
|
124
|
+
method: 'initialize',
|
|
125
|
+
params: {
|
|
126
|
+
protocolVersion: '2024-11-05',
|
|
127
|
+
capabilities: {},
|
|
128
|
+
clientInfo: { name: 'construct-mcp-probe', version: '1' },
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
const listBody = { jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} };
|
|
132
|
+
const commonHeaders = {
|
|
133
|
+
'Content-Type': 'application/json',
|
|
134
|
+
Accept: 'application/json, text/event-stream',
|
|
135
|
+
...headers,
|
|
136
|
+
};
|
|
137
|
+
const controller = new AbortController();
|
|
138
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
139
|
+
|
|
140
|
+
async function postRpc(body, extraHeaders = {}) {
|
|
141
|
+
const res = await fetch(url, {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
headers: { ...commonHeaders, ...extraHeaders },
|
|
144
|
+
body: JSON.stringify(body),
|
|
145
|
+
signal: controller.signal,
|
|
146
|
+
});
|
|
147
|
+
const text = await res.text();
|
|
148
|
+
return { res, text };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const { res: initRes, text: initText } = await postRpc(initBody);
|
|
153
|
+
if (!initRes.ok) {
|
|
154
|
+
throw new Error(`initialize HTTP ${initRes.status}: ${initText.slice(0, 160)}`);
|
|
155
|
+
}
|
|
156
|
+
parseMcpHttpFrame(initText);
|
|
157
|
+
|
|
158
|
+
const sessionId = initRes.headers.get('mcp-session-id')
|
|
159
|
+
|| initRes.headers.get('Mcp-Session-Id')
|
|
160
|
+
|| null;
|
|
161
|
+
const sessionHeaders = sessionId ? { 'mcp-session-id': sessionId } : {};
|
|
162
|
+
|
|
163
|
+
const { res: listRes, text: listText } = await postRpc(listBody, sessionHeaders);
|
|
164
|
+
if (!listRes.ok) {
|
|
165
|
+
throw new Error(`tools/list HTTP ${listRes.status}: ${listText.slice(0, 160)}`);
|
|
166
|
+
}
|
|
167
|
+
const frame = parseMcpHttpFrame(listText);
|
|
168
|
+
if (frame.error) {
|
|
169
|
+
throw new Error(frame.error.message || JSON.stringify(frame.error));
|
|
170
|
+
}
|
|
171
|
+
return frame.result?.tools || [];
|
|
172
|
+
} finally {
|
|
173
|
+
clearTimeout(timer);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Probe the Construct memory stdio bridge, which forwards tools/list to cm.
|
|
178
|
+
|
|
179
|
+
export async function probeMemoryBridgeTools({
|
|
180
|
+
bridgePath,
|
|
181
|
+
backendUrl = 'http://127.0.0.1:8765/',
|
|
182
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
183
|
+
} = {}) {
|
|
184
|
+
return probeStdioMcpTools(process.execPath, [bridgePath], {
|
|
185
|
+
env: { CONSTRUCT_MEMORY_BRIDGE_URL: backendUrl },
|
|
186
|
+
timeoutMs,
|
|
187
|
+
});
|
|
188
|
+
}
|
package/lib/mcp/tool-budget.mjs
CHANGED
|
@@ -8,15 +8,20 @@
|
|
|
8
8
|
* 2. which external MCP servers are `enabled` in opencode.json — sync disables
|
|
9
9
|
* the heavy ones for local-capable setups (they cannot be trimmed at runtime).
|
|
10
10
|
* Holds the shared token-sizing helper and the external-server id list both
|
|
11
|
-
* levers reason about
|
|
11
|
+
* levers reason about, plus the owned-loop tool-budget filter that trims the
|
|
12
|
+
* Vercel AI SDK tool set to a compiled execution policy's allowed groups and
|
|
13
|
+
* schema cap (lib/models/execution-policy.mjs).
|
|
12
14
|
*/
|
|
13
15
|
|
|
16
|
+
import { getMeasuredHeavyExternalMcpTokenCost } from './external-schema-cost.mjs';
|
|
17
|
+
|
|
14
18
|
const TOKENS_PER_CHAR = 0.25;
|
|
15
19
|
|
|
16
20
|
// Heavy external MCP servers Construct registers globally. On a local-capable
|
|
17
|
-
// setup these
|
|
18
|
-
//
|
|
19
|
-
//
|
|
21
|
+
// setup these serialize a measured ~37k tokens of tool schema into every agent's
|
|
22
|
+
// window (github alone ~30k as of 2026-06-22; see tests/fixtures/mcp-tool-schemas/
|
|
23
|
+
// and scripts/measure-external-mcp-schemas.mjs) and cannot be filtered per request,
|
|
24
|
+
// so sync disables them in opencode.json. construct-mcp's own knowledge_search /
|
|
20
25
|
// memory_search cover the search/memory cases.
|
|
21
26
|
|
|
22
27
|
export const HEAVY_EXTERNAL_MCP_IDS = ['context7', 'github', 'memory', 'sequential-thinking', 'playwright'];
|
|
@@ -29,6 +34,7 @@ export const LOCAL_SURFACE_MODES = ['auto', 'on', 'off'];
|
|
|
29
34
|
|
|
30
35
|
export function isLocalModel(model) {
|
|
31
36
|
const m = (model || "").toLowerCase();
|
|
37
|
+
if (m.startsWith("local/")) return true;
|
|
32
38
|
return m.includes("ollama") || m.includes("localhost") || m.includes("127.0.0.1");
|
|
33
39
|
}
|
|
34
40
|
|
|
@@ -51,3 +57,48 @@ export function estimateToolTokens(tools) {
|
|
|
51
57
|
const arr = Array.isArray(tools) ? tools : Object.values(tools);
|
|
52
58
|
return Math.round(JSON.stringify(arr).length * TOKENS_PER_CHAR);
|
|
53
59
|
}
|
|
60
|
+
|
|
61
|
+
export { getMeasuredHeavyExternalMcpTokenCost };
|
|
62
|
+
|
|
63
|
+
// Maps the owned loop's agent tool names (apps/chat/engine/tools/registry.mjs)
|
|
64
|
+
// onto the execution-policy tool groups (lib/models/execution-policy.mjs) so a
|
|
65
|
+
// compiled policy can trim the serialized tool surface for a capability-
|
|
66
|
+
// constrained model. construct_tool is the single MCP gateway, so it stands in
|
|
67
|
+
// for the whole 'construct' group. An unrecognized name falls back to
|
|
68
|
+
// 'construct' — a group present in every tier — so an unclassified tool is never
|
|
69
|
+
// silently dropped from a constrained surface.
|
|
70
|
+
|
|
71
|
+
const AGENT_TOOL_GROUPS = Object.freeze({
|
|
72
|
+
read: 'read',
|
|
73
|
+
glob: 'search',
|
|
74
|
+
grep: 'search',
|
|
75
|
+
write: 'edit',
|
|
76
|
+
edit: 'edit',
|
|
77
|
+
shell: 'shell',
|
|
78
|
+
construct_tool: 'construct',
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
export function toolGroupForName(name) {
|
|
82
|
+
return AGENT_TOOL_GROUPS[name] || 'construct';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Trim a tool set to the compiled policy's envelope: drop any tool whose group is
|
|
86
|
+
// outside allowedToolGroups, then cap the survivors at maxToolSchemas in stable
|
|
87
|
+
// insertion order. A null allowedToolGroups means "no group filter" and an
|
|
88
|
+
// unbounded cap means "no count limit", so the rich/hosted-direct envelope (all
|
|
89
|
+
// groups, cap 32) returns the input set unchanged.
|
|
90
|
+
|
|
91
|
+
export function applyToolBudget(tools, { allowedToolGroups = null, maxToolSchemas = Infinity } = {}) {
|
|
92
|
+
if (!tools || typeof tools !== 'object') return tools;
|
|
93
|
+
const allow = Array.isArray(allowedToolGroups) ? new Set(allowedToolGroups) : null;
|
|
94
|
+
const cap = Number.isFinite(maxToolSchemas) ? Math.max(0, Math.floor(maxToolSchemas)) : Infinity;
|
|
95
|
+
const out = {};
|
|
96
|
+
let kept = 0;
|
|
97
|
+
for (const [name, def] of Object.entries(tools)) {
|
|
98
|
+
if (allow && !allow.has(toolGroupForName(name))) continue;
|
|
99
|
+
if (kept >= cap) break;
|
|
100
|
+
out[name] = def;
|
|
101
|
+
kept += 1;
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/mcp/tools/document.mjs — Document-handling MCP tools: extract text, ingest, infer schema, list schema artifacts.
|
|
3
3
|
*
|
|
4
|
-
* All functions are async-safe;
|
|
4
|
+
* All functions are async-safe; listSchemaArtifactsTool is synchronous.
|
|
5
5
|
* Depends on lib/document-extract.mjs, lib/document-ingest.mjs, lib/schema-infer.mjs, lib/schema-artifact.mjs.
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync } from 'node:fs';
|
|
8
8
|
import { resolve } from 'node:path';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
extractDocumentTextAsync,
|
|
11
|
+
extractDocumentTextNodeNative,
|
|
12
|
+
} from '../../document-extract.mjs';
|
|
10
13
|
import { ingestDocuments } from '../../document-ingest.mjs';
|
|
11
14
|
import { inferDocumentSchema, inferUnifiedSchema } from '../../schema-infer.mjs';
|
|
12
15
|
import { writeSchemaArtifact, listSchemaArtifacts, SCHEMA_DIR } from '../../schema-artifact.mjs';
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
async function extractViaIngestPipeline(filePath, { maxChars }) {
|
|
18
|
+
try {
|
|
19
|
+
return await extractDocumentTextNodeNative(filePath, { maxChars });
|
|
20
|
+
} catch (err) {
|
|
21
|
+
if (err.code === 'OFFICE_REQUIRES_DOCLING' || err.code === 'PDF_REQUIRES_DOCLING') {
|
|
22
|
+
return extractDocumentTextAsync(filePath, { maxChars });
|
|
23
|
+
}
|
|
24
|
+
throw err;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function extractDocumentText(args) {
|
|
15
29
|
const filePath = resolve(String(args.file_path || ''));
|
|
16
30
|
const maxChars = Number.isFinite(Number(args.max_chars)) && Number(args.max_chars) > 0
|
|
17
31
|
? Math.min(Number(args.max_chars), 200_000)
|
|
@@ -22,7 +36,7 @@ export function extractDocumentText(args) {
|
|
|
22
36
|
}
|
|
23
37
|
|
|
24
38
|
try {
|
|
25
|
-
const extracted =
|
|
39
|
+
const extracted = await extractViaIngestPipeline(filePath, { maxChars });
|
|
26
40
|
return {
|
|
27
41
|
file_path: extracted.filePath,
|
|
28
42
|
extension: extracted.extension,
|
|
@@ -14,6 +14,7 @@ import { invokeWorkflow as invokeWorkflowCore } from '../../embedded-contract/wo
|
|
|
14
14
|
import { buildCapabilityContract } from '../../embedded-contract/capability.mjs';
|
|
15
15
|
import { resolveExecution as resolveExecutionCore } from '../../embedded-contract/execution.mjs';
|
|
16
16
|
import { resolveInput } from '../../embedded-contract/ingest.mjs';
|
|
17
|
+
import { runArtifactWorkflow as runArtifactWorkflowCore } from '../../artifact-workflow.mjs';
|
|
17
18
|
|
|
18
19
|
async function ingestArgs(args) {
|
|
19
20
|
if (args.input || !args.file_path) return { input: args.input, ingestion: undefined };
|
|
@@ -91,3 +92,16 @@ export function executionResolve(args = {}) {
|
|
|
91
92
|
};
|
|
92
93
|
return wrapContractResult(resolveExecutionCore(request), { surface: 'mcp' });
|
|
93
94
|
}
|
|
95
|
+
|
|
96
|
+
export function artifactWorkflow(args = {}) {
|
|
97
|
+
return wrapContractResult(runArtifactWorkflowCore({
|
|
98
|
+
input: args.input || args.request,
|
|
99
|
+
artifactType: args.artifact_type,
|
|
100
|
+
filePath: args.file_path,
|
|
101
|
+
format: args.format,
|
|
102
|
+
outputPath: args.output_path,
|
|
103
|
+
branding: args.branding,
|
|
104
|
+
overrides: args.overrides,
|
|
105
|
+
approvalMode: args.approval_mode,
|
|
106
|
+
}), { surface: 'mcp' });
|
|
107
|
+
}
|
package/lib/mcp/tools/skills.mjs
CHANGED
|
@@ -8,9 +8,17 @@
|
|
|
8
8
|
import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
10
|
import { execFileSync } from 'node:child_process';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
buildConstructToOrchestratorPacket,
|
|
13
|
+
routeRequest,
|
|
14
|
+
routeRequestVerified,
|
|
15
|
+
requiresExecutiveApproval,
|
|
16
|
+
TERMINAL_STATES,
|
|
17
|
+
} from '../../orchestration-policy.mjs';
|
|
12
18
|
import { buildTaskPacketFromIntent } from '../../workflow-state.mjs';
|
|
13
19
|
import { logSkillCall } from '../../telemetry/skill-calls.mjs';
|
|
20
|
+
import { templateMetadata } from '../../artifact-manifest.mjs';
|
|
21
|
+
import { buildSpecialistCatalog } from '../../specialists/roster.mjs';
|
|
14
22
|
|
|
15
23
|
export function listSkills({ ROOT_DIR }) {
|
|
16
24
|
const skillsDir = join(ROOT_DIR, 'skills');
|
|
@@ -120,14 +128,21 @@ export function getTemplate(args, { ROOT_DIR }) {
|
|
|
120
128
|
];
|
|
121
129
|
for (const { source, path: p } of candidates) {
|
|
122
130
|
if (existsSync(p)) {
|
|
123
|
-
|
|
131
|
+
const meta = templateMetadata(safe, { cwd, rootDir: ROOT_DIR });
|
|
132
|
+
return { source, path: p, content: readFileSync(p, 'utf8'), metadata: meta };
|
|
124
133
|
}
|
|
125
134
|
}
|
|
126
135
|
return { error: `Template not found: ${safe}`, available: listTemplatesRaw({ ROOT_DIR }) };
|
|
127
136
|
}
|
|
128
137
|
|
|
129
138
|
export function listTemplates({ ROOT_DIR }) {
|
|
130
|
-
|
|
139
|
+
const raw = listTemplatesRaw({ ROOT_DIR });
|
|
140
|
+
const cwd = process.cwd();
|
|
141
|
+
const metadata = {};
|
|
142
|
+
for (const name of [...new Set([...raw.shipped, ...raw.overridden])]) {
|
|
143
|
+
metadata[name] = templateMetadata(name, { cwd, rootDir: ROOT_DIR });
|
|
144
|
+
}
|
|
145
|
+
return { ...raw, metadata };
|
|
131
146
|
}
|
|
132
147
|
|
|
133
148
|
export async function agentContract(args = {}) {
|
|
@@ -171,6 +186,18 @@ export async function orchestrationPolicy(args) {
|
|
|
171
186
|
const draftTask = route.track !== 'immediate'
|
|
172
187
|
? buildTaskPacketFromIntent(args?.request || '', { fileCount: args?.fileCount, moduleCount: args?.moduleCount })
|
|
173
188
|
: null;
|
|
189
|
+
const handoffPacket = route.track !== 'immediate'
|
|
190
|
+
? buildConstructToOrchestratorPacket({
|
|
191
|
+
request: args?.request || '',
|
|
192
|
+
route,
|
|
193
|
+
goal: args?.goal,
|
|
194
|
+
acceptanceCriteria: args?.acceptanceCriteria,
|
|
195
|
+
fileCount: args?.fileCount,
|
|
196
|
+
moduleCount: args?.moduleCount,
|
|
197
|
+
introducesContract: args?.introducesContract,
|
|
198
|
+
explicitDrive: args?.explicitDrive,
|
|
199
|
+
})
|
|
200
|
+
: null;
|
|
174
201
|
|
|
175
202
|
// Per-specialist context packets: when the caller supplied a candidate
|
|
176
203
|
// artifact list, build the role-filtered bundle each specialist should
|
|
@@ -249,9 +276,11 @@ export async function orchestrationPolicy(args) {
|
|
|
249
276
|
|
|
250
277
|
return {
|
|
251
278
|
...route,
|
|
279
|
+
specialistCatalog: buildSpecialistCatalog(),
|
|
252
280
|
approvalRequired,
|
|
253
281
|
terminalStates: TERMINAL_STATES,
|
|
254
282
|
draftTask,
|
|
283
|
+
handoffPacket,
|
|
255
284
|
...(contextPackets ? { contextPackets } : {}),
|
|
256
285
|
...(taskGraph ? { taskGraph } : {}),
|
|
257
286
|
...(intakeError ? { intakeError } : {}),
|
|
@@ -115,11 +115,23 @@ export function workflowValidate(args) {
|
|
|
115
115
|
*/
|
|
116
116
|
export async function workflowContractValidate(args) {
|
|
117
117
|
const { validateHandoff } = await import('../../contracts/validate.mjs');
|
|
118
|
+
const { enrichConstructOrchestratorHandoff } = await import('../../contracts/construct-handoff.mjs');
|
|
119
|
+
const contractId = args.id
|
|
120
|
+
?? (args.producer === 'construct' && args.consumer === 'cx-orchestrator' ? 'construct-to-orchestrator' : null);
|
|
121
|
+
let artifact = args.artifact;
|
|
122
|
+
if (contractId === 'construct-to-orchestrator' && artifact && typeof artifact === 'object') {
|
|
123
|
+
artifact = enrichConstructOrchestratorHandoff(artifact, {
|
|
124
|
+
request: args.request,
|
|
125
|
+
fileCount: args.fileCount,
|
|
126
|
+
moduleCount: args.moduleCount,
|
|
127
|
+
introducesContract: args.introducesContract,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
118
130
|
return validateHandoff({
|
|
119
131
|
producer: args.producer,
|
|
120
132
|
consumer: args.consumer,
|
|
121
133
|
id: args.id,
|
|
122
|
-
artifact
|
|
134
|
+
artifact,
|
|
123
135
|
packet: args.packet,
|
|
124
136
|
enforcement: args.enforcement || 'block',
|
|
125
137
|
});
|
|
@@ -62,6 +62,7 @@ export async function pollFreeModels(apiKey) {
|
|
|
62
62
|
(m) =>
|
|
63
63
|
m?.pricing?.prompt === "0" && m?.pricing?.completion === "0"
|
|
64
64
|
)
|
|
65
|
+
.filter(isTextOutputModel)
|
|
65
66
|
.map((m) => ({
|
|
66
67
|
id: `openrouter/${m.id}`,
|
|
67
68
|
name: m.name ?? m.id,
|
|
@@ -72,6 +73,23 @@ export async function pollFreeModels(apiKey) {
|
|
|
72
73
|
return models.sort((a, b) => score(b, "standard") - score(a, "standard"));
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
// A coding/chat tier must emit text; image-generation models (output modality
|
|
77
|
+
// "image") are never valid tier defaults. OpenRouter reports modalities under
|
|
78
|
+
// `architecture`; absent metadata is treated as text so unknown models are not
|
|
79
|
+
// dropped. Vision models (image INPUT, text OUTPUT) remain eligible.
|
|
80
|
+
|
|
81
|
+
export function isTextOutputModel(model = {}) {
|
|
82
|
+
const arch = model.architecture || {};
|
|
83
|
+
let output = arch.output_modalities;
|
|
84
|
+
if (!Array.isArray(output) && typeof arch.modality === "string" && arch.modality.includes("->")) {
|
|
85
|
+
output = arch.modality.split("->")[1].split("+").map((s) => s.trim());
|
|
86
|
+
}
|
|
87
|
+
if (Array.isArray(output) && output.length) {
|
|
88
|
+
return output.includes("text") && !output.includes("image");
|
|
89
|
+
}
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
75
93
|
/**
|
|
76
94
|
* Score a model for a given tier (higher is better).
|
|
77
95
|
* @param {object} model – Model object as returned by `pollFreeModels`.
|