@geraldmaron/construct 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -18
- package/bin/construct +392 -91
- 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 +112 -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/audit-skills.mjs +4 -0
- package/lib/audit-specialists.mjs +285 -0
- package/lib/audit-trail.mjs +77 -10
- package/lib/auto-docs.mjs +87 -18
- 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 +245 -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 +232 -11
- package/lib/comment-lint.mjs +6 -4
- package/lib/config/schema.mjs +36 -5
- 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 +703 -0
- package/lib/diagram-export.mjs +192 -0
- package/lib/diagram.mjs +302 -0
- package/lib/docs-verify.mjs +25 -7
- package/lib/doctor/index.mjs +4 -2
- package/lib/doctor/project-adapters.mjs +44 -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/doctor/watchers/service-health.mjs +1 -1
- package/lib/document-export.mjs +360 -36
- package/lib/document-extract.mjs +88 -0
- package/lib/document-ingest.mjs +32 -18
- package/lib/embed/cli.mjs +2 -2
- package/lib/embed/daemon.mjs +18 -46
- package/lib/embed/semantic.mjs +5 -2
- package/lib/embed/worker.mjs +2 -2
- 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/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/handoffs/cleanup.mjs +1 -1
- package/lib/health-check.mjs +90 -76
- package/lib/hooks/artifact-release-gate.mjs +43 -0
- package/lib/hooks/audit-trail.mjs +14 -28
- 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 +4 -5
- 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 +27 -97
- 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/daemon.mjs +2 -0
- package/lib/intake/git-queue.mjs +9 -8
- 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 +84 -9
- 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/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 +425 -93
- 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/persona-sections.mjs +66 -0
- package/lib/playwright-demo.mjs +252 -0
- package/lib/project-init-shared.mjs +4 -1
- package/lib/prompt-composer.js +9 -4
- 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 +229 -8
- package/lib/server/langfuse-login.mjs +3 -3
- package/lib/service-manager.mjs +61 -9
- package/lib/session-store.mjs +1 -1
- package/lib/setup.mjs +102 -2
- 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/embeddings-local.mjs +6 -3
- package/lib/storage/file-lock.mjs +18 -11
- 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 +29 -13
- package/personas/construct.md +13 -13
- 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 +187 -60
- 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 +8 -3
- 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 +480 -0
- package/specialists/artifact-manifest.schema.json +53 -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-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 +10 -10
- 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/ingest/chunker.mjs +0 -94
- package/lib/ingest/pipeline.mjs +0 -53
- package/lib/ingest/store.mjs +0 -82
- package/lib/mode-commands.mjs +0 -122
- package/lib/policy/unified-gates.mjs +0 -96
- package/lib/profiles/validate-custom.mjs +0 -114
- package/lib/services/telemetry-backend.mjs +0 -177
- package/lib/storage/fusion.mjs +0 -95
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { resolveContractChain } from './specialist-contracts.mjs';
|
|
19
19
|
import { verifyRoute } from './intent-classifier.mjs';
|
|
20
20
|
import { ownerForEvent, ownerForDoc, evaluateWatchConditions, knownDocTypes } from './orchestration/routing-tables.mjs';
|
|
21
|
+
import { getArtifactEntry } from './artifact-manifest.mjs';
|
|
21
22
|
|
|
22
23
|
export const EXECUTION_TRACKS = {
|
|
23
24
|
immediate: 'immediate',
|
|
@@ -653,6 +654,19 @@ export function formatOverlayTrace(roleFlavors = {}, request = '') {
|
|
|
653
654
|
return `Overlays: ${parts.join(', ')}`;
|
|
654
655
|
}
|
|
655
656
|
|
|
657
|
+
function resolveArtifactReviewRequirements(docAuthoring) {
|
|
658
|
+
if (!docAuthoring?.docType) {
|
|
659
|
+
return { requiredReviewers: [], optionalReviewers: [], releaseGate: null };
|
|
660
|
+
}
|
|
661
|
+
const entry = getArtifactEntry(docAuthoring.docType);
|
|
662
|
+
const gate = entry?.releaseGate ?? {};
|
|
663
|
+
return {
|
|
664
|
+
requiredReviewers: gate.requiredReviewers ?? [],
|
|
665
|
+
optionalReviewers: gate.optionalReviewers ?? [],
|
|
666
|
+
releaseGate: gate,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
|
|
656
670
|
export function routeRequest(options = {}) {
|
|
657
671
|
const intent = classifyIntent(options.request);
|
|
658
672
|
const riskFlags = detectRiskFlags(options.request);
|
|
@@ -679,6 +693,11 @@ export function routeRequest(options = {}) {
|
|
|
679
693
|
if (framingChallenge.required && !specialists.includes('cx-devil-advocate')) {
|
|
680
694
|
specialists = ['cx-devil-advocate', ...specialists];
|
|
681
695
|
}
|
|
696
|
+
|
|
697
|
+
const artifactReview = resolveArtifactReviewRequirements(docAuthoring);
|
|
698
|
+
for (const reviewer of artifactReview.requiredReviewers) {
|
|
699
|
+
if (!specialists.includes(reviewer)) specialists = [...specialists, reviewer];
|
|
700
|
+
}
|
|
682
701
|
// Pre-dispatch specialists that surface concerns before architecture locks in.
|
|
683
702
|
// Order matters: business framing → R&D validation → legal/compliance review,
|
|
684
703
|
// each prepended so it runs earliest in the chain.
|
|
@@ -715,6 +734,7 @@ export function routeRequest(options = {}) {
|
|
|
715
734
|
framingChallenge,
|
|
716
735
|
externalResearch,
|
|
717
736
|
docAuthoring,
|
|
737
|
+
artifactReview,
|
|
718
738
|
});
|
|
719
739
|
|
|
720
740
|
const route = {
|
|
@@ -731,6 +751,7 @@ export function routeRequest(options = {}) {
|
|
|
731
751
|
docAuthoring,
|
|
732
752
|
externalResearch,
|
|
733
753
|
framingChallenge,
|
|
754
|
+
artifactReview,
|
|
734
755
|
contractChain,
|
|
735
756
|
dispatchPlan: buildDispatchPlan({ track, intent, specialists }),
|
|
736
757
|
dispatchSummary: buildDispatchSummary({ specialists, reasons }),
|
|
@@ -754,3 +775,32 @@ export function routeRequestVerified(options = {}) {
|
|
|
754
775
|
const route = routeRequest(options);
|
|
755
776
|
return verifyRoute(route, { request: options.request, modelCaller: options.modelCaller });
|
|
756
777
|
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Build the construct→orchestrator contract packet (specialists/contracts.json).
|
|
781
|
+
* Callers must pass this object to agent_contract — not a bare goal string.
|
|
782
|
+
*/
|
|
783
|
+
export function buildConstructToOrchestratorPacket(options = {}) {
|
|
784
|
+
const request = String(options.request || '');
|
|
785
|
+
const route = options.route || routeRequest({
|
|
786
|
+
request,
|
|
787
|
+
fileCount: options.fileCount ?? 0,
|
|
788
|
+
moduleCount: options.moduleCount ?? 0,
|
|
789
|
+
introducesContract: options.introducesContract ?? false,
|
|
790
|
+
explicitDrive: options.explicitDrive ?? false,
|
|
791
|
+
});
|
|
792
|
+
if (route.track === EXECUTION_TRACKS.immediate) return null;
|
|
793
|
+
|
|
794
|
+
const goal = String(options.goal || request || '').trim() || 'User-requested work';
|
|
795
|
+
const acceptanceCriteria = Array.isArray(options.acceptanceCriteria) && options.acceptanceCriteria.length
|
|
796
|
+
? options.acceptanceCriteria
|
|
797
|
+
: ['Dispatch plan emitted with specialists in sequence', 'Acceptance criteria verified before close'];
|
|
798
|
+
|
|
799
|
+
return {
|
|
800
|
+
goal,
|
|
801
|
+
intent: route.intent,
|
|
802
|
+
workCategory: route.workCategory,
|
|
803
|
+
riskFlags: route.riskFlags || {},
|
|
804
|
+
acceptanceCriteria,
|
|
805
|
+
};
|
|
806
|
+
}
|
package/lib/parity.mjs
CHANGED
|
@@ -104,6 +104,16 @@ function diffSets(expected, actual) {
|
|
|
104
104
|
return { missing, extra };
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// Construct ensures its own MCP servers are present; it does not own the user's
|
|
108
|
+
// full editor MCP list. A non-registry server the user added (e.g. playwright) is
|
|
109
|
+
// not drift — only a MISSING registry server is, which is a real sync regression.
|
|
110
|
+
// A rename still surfaces, since the new name registers as missing. Agent surfaces
|
|
111
|
+
// keep extra-detection (legacy cx-* cleanup), so this applies to MCP kinds only.
|
|
112
|
+
|
|
113
|
+
function mcpStatus(missing) {
|
|
114
|
+
return missing.length === 0 ? 'ok' : 'drift';
|
|
115
|
+
}
|
|
116
|
+
|
|
107
117
|
function checkFileSurface({ surface, kind, dir, extension, expected }) {
|
|
108
118
|
if (!fs.existsSync(dir)) return { surface, kind, status: 'absent', dir };
|
|
109
119
|
const actual = fs.readdirSync(dir)
|
|
@@ -245,7 +255,7 @@ function checkVSCode(registry, { homeDir = os.homedir() } = {}) {
|
|
|
245
255
|
return {
|
|
246
256
|
surface: 'vscode',
|
|
247
257
|
kind: 'mcps',
|
|
248
|
-
status: missing
|
|
258
|
+
status: mcpStatus(missing),
|
|
249
259
|
paths,
|
|
250
260
|
expectedCount: expected.length,
|
|
251
261
|
actualCount: actual.length,
|
|
@@ -269,7 +279,7 @@ function checkCursor(registry, { homeDir = os.homedir() } = {}) {
|
|
|
269
279
|
return {
|
|
270
280
|
surface: 'cursor',
|
|
271
281
|
kind: 'mcps',
|
|
272
|
-
status: missing
|
|
282
|
+
status: mcpStatus(missing),
|
|
273
283
|
file,
|
|
274
284
|
expectedCount: expected.length,
|
|
275
285
|
actualCount: actual.length,
|
|
@@ -278,6 +288,90 @@ function checkCursor(registry, { homeDir = os.homedir() } = {}) {
|
|
|
278
288
|
};
|
|
279
289
|
}
|
|
280
290
|
|
|
291
|
+
function checkProjectMcp(registry, { projectDir, surface, relPath, configKey }) {
|
|
292
|
+
const file = path.join(projectDir, relPath);
|
|
293
|
+
if (!fs.existsSync(file)) {
|
|
294
|
+
return { surface, scope: 'project', kind: 'mcps', status: 'absent', file };
|
|
295
|
+
}
|
|
296
|
+
let config;
|
|
297
|
+
try {
|
|
298
|
+
config = readJsoncFile(file);
|
|
299
|
+
} catch (err) {
|
|
300
|
+
return { surface, scope: 'project', kind: 'mcps', status: 'unreadable', file, error: err.message };
|
|
301
|
+
}
|
|
302
|
+
const servers = config[configKey] ?? {};
|
|
303
|
+
const expected = Object.keys(registry.mcpServers ?? {}).filter((id) => id !== 'memory');
|
|
304
|
+
const actual = Object.keys(servers).filter((id) => id !== 'memory');
|
|
305
|
+
const { missing, extra } = diffSets(expected, actual);
|
|
306
|
+
return {
|
|
307
|
+
surface,
|
|
308
|
+
scope: 'project',
|
|
309
|
+
kind: 'mcps',
|
|
310
|
+
status: mcpStatus(missing),
|
|
311
|
+
file,
|
|
312
|
+
expectedCount: expected.length,
|
|
313
|
+
actualCount: actual.length,
|
|
314
|
+
missing,
|
|
315
|
+
extra,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function checkProjectCursorRules({ projectDir }) {
|
|
320
|
+
const pointer = path.join(projectDir, '.cursor', 'rules', 'construct.mdc');
|
|
321
|
+
if (!fs.existsSync(pointer)) {
|
|
322
|
+
return { surface: 'cursor', scope: 'project', kind: 'rules', status: 'absent', file: pointer };
|
|
323
|
+
}
|
|
324
|
+
const text = fs.readFileSync(pointer, 'utf8');
|
|
325
|
+
const stale = !text.includes('Generated by construct sync');
|
|
326
|
+
return {
|
|
327
|
+
surface: 'cursor',
|
|
328
|
+
scope: 'project',
|
|
329
|
+
kind: 'rules',
|
|
330
|
+
status: stale ? 'drift' : 'ok',
|
|
331
|
+
file: pointer,
|
|
332
|
+
stale,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function checkProjectClaudeAgent({ projectDir, registry }) {
|
|
337
|
+
const expected = registry.orchestrator?.name || 'construct';
|
|
338
|
+
const file = path.join(projectDir, '.claude', 'agents', `${expected}.md`);
|
|
339
|
+
if (!fs.existsSync(file)) {
|
|
340
|
+
return { surface: 'claude', scope: 'project', kind: 'agents', status: 'absent', file, expected: [expected] };
|
|
341
|
+
}
|
|
342
|
+
return { surface: 'claude', scope: 'project', kind: 'agents', status: 'ok', file, expected: [expected], actual: [expected] };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Project-scoped adapter parity for Construct-managed repos (.cx/ or .construct/).
|
|
347
|
+
* Checks `.cursor/mcp.json`, `.vscode/mcp.json`, front-door rule, and construct agent.
|
|
348
|
+
*/
|
|
349
|
+
export function checkProjectParity({ rootDir = ROOT_DIR, projectDir = rootDir } = {}) {
|
|
350
|
+
const cxPresent = fs.existsSync(path.join(projectDir, '.cx')) || fs.existsSync(path.join(projectDir, '.construct'));
|
|
351
|
+
if (!cxPresent) {
|
|
352
|
+
return { ok: true, skipped: true, surfaces: [], summary: ['project: not a Construct project'] };
|
|
353
|
+
}
|
|
354
|
+
const registry = loadRegistry(rootDir);
|
|
355
|
+
const surfaces = [
|
|
356
|
+
checkProjectClaudeAgent({ projectDir, registry }),
|
|
357
|
+
checkProjectMcp(registry, { projectDir, surface: 'cursor', relPath: '.cursor/mcp.json', configKey: 'mcpServers' }),
|
|
358
|
+
checkProjectMcp(registry, { projectDir, surface: 'vscode', relPath: '.vscode/mcp.json', configKey: 'servers' }),
|
|
359
|
+
checkProjectCursorRules({ projectDir }),
|
|
360
|
+
];
|
|
361
|
+
const ok = surfaces.every((s) => s.status === 'ok' || s.status === 'absent');
|
|
362
|
+
const summary = surfaces.map((s) => {
|
|
363
|
+
if (s.status === 'absent') return `${s.surface} (project): missing ${s.kind}`;
|
|
364
|
+
if (s.status === 'unreadable') return `${s.surface} (project): unreadable (${s.error})`;
|
|
365
|
+
if (s.status === 'ok') return `${s.surface} (project): ok`;
|
|
366
|
+
if (s.stale) return `${s.surface} (project): stale construct.mdc — run \`npm run adapters\``;
|
|
367
|
+
const parts = [];
|
|
368
|
+
if (s.missing?.length) parts.push(`missing: ${s.missing.join(', ')}`);
|
|
369
|
+
if (s.extra?.length) parts.push(`extra: ${s.extra.join(', ')}`);
|
|
370
|
+
return `${s.surface} (project): drift — ${parts.join(' · ')}`;
|
|
371
|
+
});
|
|
372
|
+
return { ok, skipped: false, surfaces, summary };
|
|
373
|
+
}
|
|
374
|
+
|
|
281
375
|
// Names a v1.0.10 install would have populated at user scope (claude, codex,
|
|
282
376
|
// copilot) before the two-tier sync contract moved specialists to project
|
|
283
377
|
// scope. Pulled from the live registry so the roster auto-updates as new
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/persona-sections.mjs — Capability-tiered persona section rendering.
|
|
3
|
+
*
|
|
4
|
+
* A persona is authored once. Each `## ` section carries an inline `<!-- cx:prio=N -->`
|
|
5
|
+
* marker on its heading; the preamble before the first heading is always prio 1
|
|
6
|
+
* (identity + anti-fabrication). Small local models follow large multi-instruction
|
|
7
|
+
* prompts poorly — instruction-following degrades well before the context window fills —
|
|
8
|
+
* so a weak model receives only the sections it can actually comply with. Tiers:
|
|
9
|
+
* floor (prio 1, must-keep), mid (prio <= 2), full (all). The marker is stripped from the
|
|
10
|
+
* emitted text. Untagged `## ` sections default to prio 2 so authoring omissions degrade
|
|
11
|
+
* gracefully rather than vanishing or forcing a floor model to read everything.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const TIER_MAX_PRIO = Object.freeze({ floor: 1, mid: 2, full: 3 });
|
|
15
|
+
const PRIO_MARKER = /<!--\s*cx:prio=(\d+)\s*-->/;
|
|
16
|
+
const PRIO_MARKER_GLOBAL = /[ \t]*<!--\s*cx:prio=\d+\s*-->/g;
|
|
17
|
+
|
|
18
|
+
// The markers are authoring metadata; no emitted prompt (full or tiered) may carry them.
|
|
19
|
+
// Tiered render strips them via section parsing; full render (composePrompt) calls this.
|
|
20
|
+
|
|
21
|
+
export function stripSectionMarkers(text) {
|
|
22
|
+
return String(text || "").replace(PRIO_MARKER_GLOBAL, "");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// `## ` (exactly two hashes) is the only section boundary; `### ` subheadings stay with
|
|
26
|
+
// their parent section. The preamble (before the first `## `) is implicit prio 1.
|
|
27
|
+
|
|
28
|
+
export function parsePersonaSections(body) {
|
|
29
|
+
const lines = String(body || "").split("\n");
|
|
30
|
+
const sections = [];
|
|
31
|
+
let current = { heading: null, prio: 1, lines: [] };
|
|
32
|
+
|
|
33
|
+
const flush = () => {
|
|
34
|
+
const content = current.lines.join("\n").trim();
|
|
35
|
+
if (content || current.heading) {
|
|
36
|
+
sections.push({ heading: current.heading, prio: current.prio, content });
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
for (const line of lines) {
|
|
41
|
+
if (line.startsWith("## ")) {
|
|
42
|
+
flush();
|
|
43
|
+
const marker = line.match(PRIO_MARKER);
|
|
44
|
+
const prio = marker ? Number(marker[1]) : 2;
|
|
45
|
+
const heading = line.replace(PRIO_MARKER, "").trimEnd();
|
|
46
|
+
current = { heading, prio, lines: [heading] };
|
|
47
|
+
} else {
|
|
48
|
+
current.lines.push(line);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
flush();
|
|
52
|
+
return sections;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function renderPersonaForTier(body, tier = "full") {
|
|
56
|
+
const maxPrio = TIER_MAX_PRIO[tier] ?? TIER_MAX_PRIO.full;
|
|
57
|
+
return parsePersonaSections(body)
|
|
58
|
+
.filter((section) => section.prio <= maxPrio)
|
|
59
|
+
.map((section) => stripSectionMarkers(section.content))
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.join("\n\n");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function personaTierMaxPrio(tier) {
|
|
65
|
+
return TIER_MAX_PRIO[tier] ?? TIER_MAX_PRIO.full;
|
|
66
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/playwright-demo.mjs — workspace-agnostic Playwright demo recording.
|
|
3
|
+
*
|
|
4
|
+
* Spawns `npx playwright test --config <config> <spec>` with env passthrough for
|
|
5
|
+
* artifact reveal, collects video output, and transcodes webm→mp4 when ffmpeg exists.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { spawnSync } from 'node:child_process';
|
|
11
|
+
import { sanitizeNpmSpawnEnv } from './npm-spawn-env.mjs';
|
|
12
|
+
|
|
13
|
+
export function resolvePlaywrightPackage(workspace, repoRoot) {
|
|
14
|
+
const candidates = [
|
|
15
|
+
path.join(workspace, 'node_modules', '@playwright', 'test'),
|
|
16
|
+
path.join(repoRoot, 'node_modules', '@playwright', 'test'),
|
|
17
|
+
];
|
|
18
|
+
return candidates.find((p) => fs.existsSync(p)) || null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function resolvePlaywrightConfig(workspace) {
|
|
22
|
+
const candidates = ['playwright.config.mjs', 'playwright.config.ts'].map((f) => path.join(workspace, f));
|
|
23
|
+
return candidates.find((p) => fs.existsSync(p)) || null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function detectPlaywrightDemo({ workspace, cwd = process.cwd(), repoRoot = cwd } = {}) {
|
|
27
|
+
const root = repoRoot || cwd;
|
|
28
|
+
const resolvedWorkspace = path.resolve(root, workspace || '.');
|
|
29
|
+
const missing = [];
|
|
30
|
+
if (!fs.existsSync(path.join(resolvedWorkspace, 'package.json'))) {
|
|
31
|
+
missing.push(`package.json in ${workspace || '.'}`);
|
|
32
|
+
}
|
|
33
|
+
const config = resolvePlaywrightConfig(resolvedWorkspace);
|
|
34
|
+
if (!config) missing.push(`playwright.config.mjs in ${workspace || '.'}`);
|
|
35
|
+
if (!resolvePlaywrightPackage(resolvedWorkspace, root)) {
|
|
36
|
+
missing.push('@playwright/test (npm install in workspace)');
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
ok: true,
|
|
40
|
+
present: missing.length === 0,
|
|
41
|
+
workspace: resolvedWorkspace,
|
|
42
|
+
config,
|
|
43
|
+
missing,
|
|
44
|
+
message: missing.length === 0
|
|
45
|
+
? 'Playwright demo tooling ready'
|
|
46
|
+
: `Install Playwright demo deps: ${missing.join('; ')}`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function locateFfmpeg() {
|
|
51
|
+
const result = spawnSync(process.platform === 'win32' ? 'where' : 'which', ['ffmpeg'], { encoding: 'utf8' });
|
|
52
|
+
if (result.status !== 0) return null;
|
|
53
|
+
return result.stdout.trim().split('\n')[0] || null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function collectVideoFiles(dirs) {
|
|
57
|
+
const collected = [];
|
|
58
|
+
function walk(dir) {
|
|
59
|
+
if (!dir || !fs.existsSync(dir)) return;
|
|
60
|
+
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
61
|
+
const p = path.join(dir, ent.name);
|
|
62
|
+
if (ent.isDirectory()) walk(p);
|
|
63
|
+
else if (/\.(webm|mp4)$/.test(ent.name)) collected.push(p);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
for (const dir of dirs) walk(dir);
|
|
67
|
+
return collected;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function newestVideo(paths) {
|
|
71
|
+
if (!paths.length) return null;
|
|
72
|
+
return paths
|
|
73
|
+
.map((p) => ({ p, mtime: fs.statSync(p).mtimeMs }))
|
|
74
|
+
.sort((a, b) => b.mtime - a.mtime)[0].p;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function transcodeWebmToMp4(src, dest) {
|
|
78
|
+
const ffmpeg = locateFfmpeg();
|
|
79
|
+
if (!ffmpeg) return { ok: false, message: 'ffmpeg not on PATH' };
|
|
80
|
+
const result = spawnSync(ffmpeg, [
|
|
81
|
+
'-y', '-i', src,
|
|
82
|
+
'-c:v', 'libx264',
|
|
83
|
+
'-pix_fmt', 'yuv420p',
|
|
84
|
+
'-movflags', '+faststart',
|
|
85
|
+
dest,
|
|
86
|
+
], { encoding: 'utf8', timeout: 180_000 });
|
|
87
|
+
if (result.status !== 0) {
|
|
88
|
+
return {
|
|
89
|
+
ok: false,
|
|
90
|
+
message: `ffmpeg failed (exit ${result.status}): ${(result.stderr || '').trim().slice(0, 300)}`,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return { ok: true, outputPath: dest };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function finalizeDemoVideo({ sourcePath, outputPath, format = 'mp4' }) {
|
|
97
|
+
if (!sourcePath || !fs.existsSync(sourcePath)) {
|
|
98
|
+
return { ok: false, message: 'No video artifact produced' };
|
|
99
|
+
}
|
|
100
|
+
const target = outputPath || sourcePath;
|
|
101
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
102
|
+
|
|
103
|
+
if (format === 'mp4' && sourcePath.endsWith('.webm')) {
|
|
104
|
+
const transcode = transcodeWebmToMp4(sourcePath, target);
|
|
105
|
+
if (!transcode.ok) return transcode;
|
|
106
|
+
return { ok: true, outputPath: target, format: 'mp4', engine: 'playwright+ffmpeg' };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (path.resolve(sourcePath) !== path.resolve(target)) {
|
|
110
|
+
fs.copyFileSync(sourcePath, target);
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
ok: true,
|
|
114
|
+
outputPath: target,
|
|
115
|
+
format: path.extname(target).slice(1) || format,
|
|
116
|
+
engine: 'playwright',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function buildArtifactRevealEnv(recording, {
|
|
121
|
+
repoRoot,
|
|
122
|
+
cwd = repoRoot,
|
|
123
|
+
artifactDir = null,
|
|
124
|
+
artifactFile = null,
|
|
125
|
+
env = process.env,
|
|
126
|
+
} = {}) {
|
|
127
|
+
const reveal = recording?.artifactReveal;
|
|
128
|
+
const out = { ...env };
|
|
129
|
+
if (!reveal && !artifactDir && !artifactFile) return out;
|
|
130
|
+
|
|
131
|
+
const file = artifactFile || reveal?.file || reveal?.path || '';
|
|
132
|
+
const staticDir = artifactDir
|
|
133
|
+
|| (reveal?.staticDir ? path.resolve(repoRoot || cwd, reveal.staticDir) : '');
|
|
134
|
+
|
|
135
|
+
if (staticDir) out.CONSTRUCT_DEMO_ARTIFACT_DIR = staticDir;
|
|
136
|
+
if (file) out.DEMO_ARTIFACT_FILE = file;
|
|
137
|
+
if (reveal?.mode) out.DEMO_ARTIFACT_REVEAL_MODE = reveal.mode;
|
|
138
|
+
if (recording?.baseUrl) out.BASE_URL = recording.baseUrl;
|
|
139
|
+
if (recording?.webServer && !recording.skipWebServer) {
|
|
140
|
+
out.DEMO_WEB_SERVER_COMMAND = recording.webServer.command;
|
|
141
|
+
out.DEMO_WEB_SERVER_URL = recording.webServer.url;
|
|
142
|
+
out.DEMO_WEB_SERVER_CWD = path.resolve(repoRoot || cwd, recording.webServer.cwd || recording.workspace || '.');
|
|
143
|
+
out.DEMO_WEB_SERVER_TIMEOUT = String(recording.webServer.timeout || 120_000);
|
|
144
|
+
}
|
|
145
|
+
if (recording?.skipWebServer) out.SKIP_WEBSERVER = '1';
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function resolveRecordingPaths(recording, { repoRoot, cwd = repoRoot } = {}) {
|
|
150
|
+
const root = repoRoot || cwd;
|
|
151
|
+
const workspace = path.resolve(root, recording.workspace || '.');
|
|
152
|
+
const spec = path.isAbsolute(recording.spec)
|
|
153
|
+
? recording.spec
|
|
154
|
+
: path.resolve(root, recording.spec);
|
|
155
|
+
const config = recording.playwrightConfig
|
|
156
|
+
? (path.isAbsolute(recording.playwrightConfig)
|
|
157
|
+
? recording.playwrightConfig
|
|
158
|
+
: path.resolve(root, recording.playwrightConfig))
|
|
159
|
+
: resolvePlaywrightConfig(workspace);
|
|
160
|
+
const outputDir = recording.output?.path
|
|
161
|
+
? path.dirname(path.resolve(root, recording.output.path))
|
|
162
|
+
: path.join(cwd, '.cx', 'demos');
|
|
163
|
+
return { workspace, spec, config, outputDir };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function recordPlaywrightDemo(recording, {
|
|
167
|
+
cwd = process.cwd(),
|
|
168
|
+
repoRoot,
|
|
169
|
+
outputDir = null,
|
|
170
|
+
outputPath = null,
|
|
171
|
+
format = null,
|
|
172
|
+
artifactDir = null,
|
|
173
|
+
artifactFile = null,
|
|
174
|
+
env = process.env,
|
|
175
|
+
timeout = null,
|
|
176
|
+
} = {}) {
|
|
177
|
+
const root = repoRoot || cwd;
|
|
178
|
+
const name = recording.name || path.basename(recording.spec || '', '.spec.ts');
|
|
179
|
+
const { workspace, spec, config, outputDir: defaultOutDir } = resolveRecordingPaths(recording, { repoRoot: root, cwd });
|
|
180
|
+
const detection = detectPlaywrightDemo({ workspace, repoRoot: root, cwd });
|
|
181
|
+
if (!detection.present) {
|
|
182
|
+
return { ok: false, name, missing: detection.missing, message: detection.message };
|
|
183
|
+
}
|
|
184
|
+
if (!config || !fs.existsSync(config)) {
|
|
185
|
+
return { ok: false, name, message: `Playwright config not found for workspace ${recording.workspace || '.'}` };
|
|
186
|
+
}
|
|
187
|
+
if (!fs.existsSync(spec)) {
|
|
188
|
+
return { ok: false, name, message: `Demo spec not found: ${spec}` };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const outDir = outputDir || defaultOutDir;
|
|
192
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
193
|
+
|
|
194
|
+
const outFormat = format || recording.output?.format || 'mp4';
|
|
195
|
+
const destPath = outputPath
|
|
196
|
+
|| (recording.output?.path ? path.resolve(root, recording.output.path) : path.join(outDir, `${name}.${outFormat}`));
|
|
197
|
+
|
|
198
|
+
const spawnEnv = buildArtifactRevealEnv(recording, {
|
|
199
|
+
repoRoot: root,
|
|
200
|
+
cwd,
|
|
201
|
+
artifactDir,
|
|
202
|
+
artifactFile,
|
|
203
|
+
env: {
|
|
204
|
+
...env,
|
|
205
|
+
DEMO_OUTPUT_DIR: outDir,
|
|
206
|
+
CONSTRUCT_DEMO: name,
|
|
207
|
+
CI: env.CI || '',
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const args = ['playwright', 'test', '--config', config, spec];
|
|
212
|
+
if (recording.project) args.push(`--project=${recording.project}`);
|
|
213
|
+
|
|
214
|
+
const spawnTimeout = timeout || recording.timeout || 600_000;
|
|
215
|
+
const result = spawnSync('npx', args, {
|
|
216
|
+
cwd: workspace,
|
|
217
|
+
encoding: 'utf8',
|
|
218
|
+
env: sanitizeNpmSpawnEnv(spawnEnv),
|
|
219
|
+
timeout: spawnTimeout,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
if (result.status !== 0) {
|
|
223
|
+
const detail = (result.stderr || result.stdout || result.error?.message || '').trim().slice(0, 500);
|
|
224
|
+
const exitLabel = result.signal ? `signal ${result.signal}` : `exit ${result.status}`;
|
|
225
|
+
return {
|
|
226
|
+
ok: false,
|
|
227
|
+
name,
|
|
228
|
+
message: `Playwright demo failed (${exitLabel})${detail ? `: ${detail}` : ''}`,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const videos = collectVideoFiles([
|
|
233
|
+
path.join(workspace, 'test-results'),
|
|
234
|
+
outDir,
|
|
235
|
+
]);
|
|
236
|
+
const newest = newestVideo(videos);
|
|
237
|
+
const finalized = finalizeDemoVideo({ sourcePath: newest, outputPath: destPath, format: outFormat });
|
|
238
|
+
if (!finalized.ok) {
|
|
239
|
+
return { ok: false, name, message: finalized.message };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
ok: true,
|
|
244
|
+
name,
|
|
245
|
+
outputDir: outDir,
|
|
246
|
+
outputPath: finalized.outputPath,
|
|
247
|
+
format: finalized.format,
|
|
248
|
+
engine: finalized.engine,
|
|
249
|
+
videos: videos.map((p) => path.basename(p)),
|
|
250
|
+
message: `Recorded Playwright demo to ${finalized.outputPath}`,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
@@ -87,7 +87,10 @@ instead of letting it turn into a historical log. Durable task status belongs in
|
|
|
87
87
|
|
|
88
88
|
> Required project state. Keep this file current enough that a new session can resume work quickly.
|
|
89
89
|
|
|
90
|
-
##
|
|
90
|
+
## What was in progress
|
|
91
|
+
|
|
92
|
+
## Open issues
|
|
93
|
+
None
|
|
91
94
|
|
|
92
95
|
## Recent Decisions
|
|
93
96
|
|
package/lib/prompt-composer.js
CHANGED
|
@@ -24,12 +24,16 @@ import { buildContextDigest, readContextState } from './context-state.mjs';
|
|
|
24
24
|
import {
|
|
25
25
|
MODEL_OPERATING_PROFILES,
|
|
26
26
|
resolveExecutionContractModelMetadata,
|
|
27
|
-
resolveModelOperatingProfile,
|
|
28
27
|
selectModelTierForWorkCategory,
|
|
29
28
|
} from './model-router.mjs';
|
|
29
|
+
import {
|
|
30
|
+
resolveExecutionCapabilityProfile,
|
|
31
|
+
operatingProfileIdFromProfile,
|
|
32
|
+
} from './models/execution-capability-profile.mjs';
|
|
30
33
|
import { listObservations } from './observation-store.mjs';
|
|
31
34
|
import { routeRequest } from './orchestration-policy.mjs';
|
|
32
35
|
import { resolvePromptEntry, resolvePromptMetadata } from './prompt-metadata.mjs';
|
|
36
|
+
import { stripSectionMarkers } from './persona-sections.mjs';
|
|
33
37
|
import { readRoleFile } from './role-preload.mjs';
|
|
34
38
|
import { estimateTokens, estimatePromptTokens, estimateTokensSync } from './token-engine.js';
|
|
35
39
|
import { cxDir } from './paths.mjs';
|
|
@@ -207,12 +211,13 @@ export function composePrompt(agentName, {
|
|
|
207
211
|
|
|
208
212
|
const metadata = resolvePromptMetadata(agentName, { rootDir, registry });
|
|
209
213
|
const fragments = [];
|
|
210
|
-
const
|
|
214
|
+
const executionProfile = resolveExecutionCapabilityProfile({
|
|
215
|
+
model: executionContractModel?.selectedModel ?? modelId,
|
|
211
216
|
envValues: executionContractModel?.profile ? { CONSTRUCT_MODEL_PROFILE: executionContractModel.profile.id } : {},
|
|
212
|
-
selectedModel: executionContractModel?.selectedModel ?? modelId,
|
|
213
217
|
});
|
|
218
|
+
const selectedProfile = MODEL_OPERATING_PROFILES[operatingProfileIdFromProfile(executionProfile)];
|
|
214
219
|
|
|
215
|
-
fragments.push({ type: 'core', priority: PRIORITY['core'], label: entry.name, content: readPromptBody(entry.promptFile, rootDir), tokenBudget: null });
|
|
220
|
+
fragments.push({ type: 'core', priority: PRIORITY['core'], label: entry.name, content: stripSectionMarkers(readPromptBody(entry.promptFile, rootDir)), tokenBudget: null });
|
|
216
221
|
|
|
217
222
|
const shortName = String(agentName).replace(/^cx-/, '');
|
|
218
223
|
const flavorMapping = AGENT_FLAVOR_MAP[shortName];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/prompt-validation-contract.mjs — Inline shared validation contract at sync time.
|
|
3
|
+
*
|
|
4
|
+
* Prepends specialists/prompts/_shared/validation-contract.md to every cx-* prompt
|
|
5
|
+
* so assume-nothing discipline is present at runtime without 29 copy-paste edits.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
const MARKER = '<!-- cx:validation-contract -->';
|
|
12
|
+
|
|
13
|
+
export function readValidationContract(root) {
|
|
14
|
+
const p = path.join(root, 'specialists', 'prompts', '_shared', 'validation-contract.md');
|
|
15
|
+
if (!fs.existsSync(p)) return null;
|
|
16
|
+
return fs.readFileSync(p, 'utf8').trim();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function inlineValidationContract(prompt, root, agentName = '(unknown)') {
|
|
20
|
+
if (!prompt || prompt.includes(MARKER)) return prompt;
|
|
21
|
+
const block = readValidationContract(root);
|
|
22
|
+
if (!block) return prompt;
|
|
23
|
+
return `${MARKER}\n\n${block}\n\n${prompt}`;
|
|
24
|
+
}
|
|
@@ -41,6 +41,62 @@ function resolveAdapterKey(modelId) {
|
|
|
41
41
|
return 'generic';
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// Declared adapter defaults for sync hot paths when the 24h probe cache is cold.
|
|
45
|
+
// Matches the static `capabilities()` exports in provider-capabilities-*.js.
|
|
46
|
+
|
|
47
|
+
const SYNC_ADAPTER_CAPS = {
|
|
48
|
+
anthropic: {
|
|
49
|
+
cacheControl: true,
|
|
50
|
+
cacheMechanism: 'annotation',
|
|
51
|
+
cacheTTL: { '5m': 300_000, '1h': 1_200_000 },
|
|
52
|
+
structuredOutput: true,
|
|
53
|
+
maxContextWindow: 200_000,
|
|
54
|
+
tokenRatio: 3.5,
|
|
55
|
+
annotationFormat: 'anthropic',
|
|
56
|
+
annotationHeaders: { 'anthropic-version': '2024-10-22' },
|
|
57
|
+
},
|
|
58
|
+
google: {
|
|
59
|
+
cacheControl: true,
|
|
60
|
+
cacheMechanism: 'resource',
|
|
61
|
+
cacheTTL: { '5m': null, '1h': 1_200_000 },
|
|
62
|
+
structuredOutput: true,
|
|
63
|
+
maxContextWindow: 1_000_000,
|
|
64
|
+
tokenRatio: 4,
|
|
65
|
+
annotationFormat: 'google',
|
|
66
|
+
annotationHeaders: null,
|
|
67
|
+
},
|
|
68
|
+
openai: {
|
|
69
|
+
cacheControl: false,
|
|
70
|
+
cacheMechanism: 'automatic',
|
|
71
|
+
cacheTTL: null,
|
|
72
|
+
structuredOutput: true,
|
|
73
|
+
maxContextWindow: 128_000,
|
|
74
|
+
tokenRatio: 4,
|
|
75
|
+
annotationFormat: 'openai',
|
|
76
|
+
annotationHeaders: null,
|
|
77
|
+
},
|
|
78
|
+
deepseek: {
|
|
79
|
+
cacheControl: false,
|
|
80
|
+
cacheMechanism: 'none',
|
|
81
|
+
cacheTTL: null,
|
|
82
|
+
structuredOutput: true,
|
|
83
|
+
maxContextWindow: 128_000,
|
|
84
|
+
tokenRatio: 4,
|
|
85
|
+
annotationFormat: 'none',
|
|
86
|
+
annotationHeaders: null,
|
|
87
|
+
},
|
|
88
|
+
generic: {
|
|
89
|
+
cacheControl: false,
|
|
90
|
+
cacheMechanism: 'none',
|
|
91
|
+
cacheTTL: null,
|
|
92
|
+
structuredOutput: false,
|
|
93
|
+
maxContextWindow: 200_000,
|
|
94
|
+
tokenRatio: 4,
|
|
95
|
+
annotationFormat: 'none',
|
|
96
|
+
annotationHeaders: null,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
44
100
|
function readCapabilityCache() {
|
|
45
101
|
try {
|
|
46
102
|
if (!existsSync(CAPABILITY_CACHE_PATH)) return {};
|
|
@@ -103,17 +159,7 @@ export function resolveProviderCapabilitiesSync(modelId) {
|
|
|
103
159
|
// Check cache first (keyed by adapter type, not full model ID)
|
|
104
160
|
if (cache[adapterKey]) return cache[adapterKey];
|
|
105
161
|
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
cacheControl: false,
|
|
109
|
-
cacheMechanism: 'none',
|
|
110
|
-
cacheTTL: null,
|
|
111
|
-
structuredOutput: false,
|
|
112
|
-
maxContextWindow: 200000,
|
|
113
|
-
tokenRatio: 4,
|
|
114
|
-
annotationFormat: 'none',
|
|
115
|
-
annotationHeaders: null,
|
|
116
|
-
};
|
|
162
|
+
return SYNC_ADAPTER_CAPS[adapterKey] || SYNC_ADAPTER_CAPS.generic;
|
|
117
163
|
}
|
|
118
164
|
|
|
119
165
|
/**
|