@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
|
@@ -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,254 @@
|
|
|
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
|
+
'-crf', '18',
|
|
84
|
+
'-preset', 'medium',
|
|
85
|
+
'-pix_fmt', 'yuv420p',
|
|
86
|
+
'-movflags', '+faststart',
|
|
87
|
+
dest,
|
|
88
|
+
], { encoding: 'utf8', timeout: 180_000 });
|
|
89
|
+
if (result.status !== 0) {
|
|
90
|
+
return {
|
|
91
|
+
ok: false,
|
|
92
|
+
message: `ffmpeg failed (exit ${result.status}): ${(result.stderr || '').trim().slice(0, 300)}`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return { ok: true, outputPath: dest };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function finalizeDemoVideo({ sourcePath, outputPath, format = 'mp4' }) {
|
|
99
|
+
if (!sourcePath || !fs.existsSync(sourcePath)) {
|
|
100
|
+
return { ok: false, message: 'No video artifact produced' };
|
|
101
|
+
}
|
|
102
|
+
const target = outputPath || sourcePath;
|
|
103
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
104
|
+
|
|
105
|
+
if (format === 'mp4' && sourcePath.endsWith('.webm')) {
|
|
106
|
+
const transcode = transcodeWebmToMp4(sourcePath, target);
|
|
107
|
+
if (!transcode.ok) return transcode;
|
|
108
|
+
return { ok: true, outputPath: target, format: 'mp4', engine: 'playwright+ffmpeg' };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (path.resolve(sourcePath) !== path.resolve(target)) {
|
|
112
|
+
fs.copyFileSync(sourcePath, target);
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
ok: true,
|
|
116
|
+
outputPath: target,
|
|
117
|
+
format: path.extname(target).slice(1) || format,
|
|
118
|
+
engine: 'playwright',
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function buildArtifactRevealEnv(recording, {
|
|
123
|
+
repoRoot,
|
|
124
|
+
cwd = repoRoot,
|
|
125
|
+
artifactDir = null,
|
|
126
|
+
artifactFile = null,
|
|
127
|
+
env = process.env,
|
|
128
|
+
} = {}) {
|
|
129
|
+
const reveal = recording?.artifactReveal;
|
|
130
|
+
const out = { ...env };
|
|
131
|
+
if (!reveal && !artifactDir && !artifactFile) return out;
|
|
132
|
+
|
|
133
|
+
const file = artifactFile || reveal?.file || reveal?.path || '';
|
|
134
|
+
const staticDir = artifactDir
|
|
135
|
+
|| (reveal?.staticDir ? path.resolve(repoRoot || cwd, reveal.staticDir) : '');
|
|
136
|
+
|
|
137
|
+
if (staticDir) out.CONSTRUCT_DEMO_ARTIFACT_DIR = staticDir;
|
|
138
|
+
if (file) out.DEMO_ARTIFACT_FILE = file;
|
|
139
|
+
if (reveal?.mode) out.DEMO_ARTIFACT_REVEAL_MODE = reveal.mode;
|
|
140
|
+
if (recording?.baseUrl) out.BASE_URL = recording.baseUrl;
|
|
141
|
+
if (recording?.webServer && !recording.skipWebServer) {
|
|
142
|
+
out.DEMO_WEB_SERVER_COMMAND = recording.webServer.command;
|
|
143
|
+
out.DEMO_WEB_SERVER_URL = recording.webServer.url;
|
|
144
|
+
out.DEMO_WEB_SERVER_CWD = path.resolve(repoRoot || cwd, recording.webServer.cwd || recording.workspace || '.');
|
|
145
|
+
out.DEMO_WEB_SERVER_TIMEOUT = String(recording.webServer.timeout || 120_000);
|
|
146
|
+
}
|
|
147
|
+
if (recording?.skipWebServer) out.SKIP_WEBSERVER = '1';
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function resolveRecordingPaths(recording, { repoRoot, cwd = repoRoot } = {}) {
|
|
152
|
+
const root = repoRoot || cwd;
|
|
153
|
+
const workspace = path.resolve(root, recording.workspace || '.');
|
|
154
|
+
const spec = path.isAbsolute(recording.spec)
|
|
155
|
+
? recording.spec
|
|
156
|
+
: path.resolve(root, recording.spec);
|
|
157
|
+
const config = recording.playwrightConfig
|
|
158
|
+
? (path.isAbsolute(recording.playwrightConfig)
|
|
159
|
+
? recording.playwrightConfig
|
|
160
|
+
: path.resolve(root, recording.playwrightConfig))
|
|
161
|
+
: resolvePlaywrightConfig(workspace);
|
|
162
|
+
const outputDir = recording.output?.path
|
|
163
|
+
? path.dirname(path.resolve(root, recording.output.path))
|
|
164
|
+
: path.join(cwd, '.cx', 'demos');
|
|
165
|
+
return { workspace, spec, config, outputDir };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function recordPlaywrightDemo(recording, {
|
|
169
|
+
cwd = process.cwd(),
|
|
170
|
+
repoRoot,
|
|
171
|
+
outputDir = null,
|
|
172
|
+
outputPath = null,
|
|
173
|
+
format = null,
|
|
174
|
+
artifactDir = null,
|
|
175
|
+
artifactFile = null,
|
|
176
|
+
env = process.env,
|
|
177
|
+
timeout = null,
|
|
178
|
+
} = {}) {
|
|
179
|
+
const root = repoRoot || cwd;
|
|
180
|
+
const name = recording.name || path.basename(recording.spec || '', '.spec.ts');
|
|
181
|
+
const { workspace, spec, config, outputDir: defaultOutDir } = resolveRecordingPaths(recording, { repoRoot: root, cwd });
|
|
182
|
+
const detection = detectPlaywrightDemo({ workspace, repoRoot: root, cwd });
|
|
183
|
+
if (!detection.present) {
|
|
184
|
+
return { ok: false, name, missing: detection.missing, message: detection.message };
|
|
185
|
+
}
|
|
186
|
+
if (!config || !fs.existsSync(config)) {
|
|
187
|
+
return { ok: false, name, message: `Playwright config not found for workspace ${recording.workspace || '.'}` };
|
|
188
|
+
}
|
|
189
|
+
if (!fs.existsSync(spec)) {
|
|
190
|
+
return { ok: false, name, message: `Demo spec not found: ${spec}` };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const outDir = outputDir || defaultOutDir;
|
|
194
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
195
|
+
|
|
196
|
+
const outFormat = format || recording.output?.format || 'mp4';
|
|
197
|
+
const destPath = outputPath
|
|
198
|
+
|| (recording.output?.path ? path.resolve(root, recording.output.path) : path.join(outDir, `${name}.${outFormat}`));
|
|
199
|
+
|
|
200
|
+
const spawnEnv = buildArtifactRevealEnv(recording, {
|
|
201
|
+
repoRoot: root,
|
|
202
|
+
cwd,
|
|
203
|
+
artifactDir,
|
|
204
|
+
artifactFile,
|
|
205
|
+
env: {
|
|
206
|
+
...env,
|
|
207
|
+
DEMO_OUTPUT_DIR: outDir,
|
|
208
|
+
CONSTRUCT_DEMO: name,
|
|
209
|
+
CI: env.CI || '',
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const args = ['playwright', 'test', '--config', config, spec];
|
|
214
|
+
if (recording.project) args.push(`--project=${recording.project}`);
|
|
215
|
+
|
|
216
|
+
const spawnTimeout = timeout || recording.timeout || 600_000;
|
|
217
|
+
const result = spawnSync('npx', args, {
|
|
218
|
+
cwd: workspace,
|
|
219
|
+
encoding: 'utf8',
|
|
220
|
+
env: sanitizeNpmSpawnEnv(spawnEnv),
|
|
221
|
+
timeout: spawnTimeout,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
if (result.status !== 0) {
|
|
225
|
+
const detail = (result.stderr || result.stdout || result.error?.message || '').trim().slice(0, 500);
|
|
226
|
+
const exitLabel = result.signal ? `signal ${result.signal}` : `exit ${result.status}`;
|
|
227
|
+
return {
|
|
228
|
+
ok: false,
|
|
229
|
+
name,
|
|
230
|
+
message: `Playwright demo failed (${exitLabel})${detail ? `: ${detail}` : ''}`,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const videos = collectVideoFiles([
|
|
235
|
+
path.join(workspace, 'test-results'),
|
|
236
|
+
outDir,
|
|
237
|
+
]);
|
|
238
|
+
const newest = newestVideo(videos);
|
|
239
|
+
const finalized = finalizeDemoVideo({ sourcePath: newest, outputPath: destPath, format: outFormat });
|
|
240
|
+
if (!finalized.ok) {
|
|
241
|
+
return { ok: false, name, message: finalized.message };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
ok: true,
|
|
246
|
+
name,
|
|
247
|
+
outputDir: outDir,
|
|
248
|
+
outputPath: finalized.outputPath,
|
|
249
|
+
format: finalized.format,
|
|
250
|
+
engine: finalized.engine,
|
|
251
|
+
videos: videos.map((p) => path.basename(p)),
|
|
252
|
+
message: `Recorded Playwright demo to ${finalized.outputPath}`,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
@@ -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,9 +24,12 @@ 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';
|
|
@@ -208,10 +211,11 @@ export function composePrompt(agentName, {
|
|
|
208
211
|
|
|
209
212
|
const metadata = resolvePromptMetadata(agentName, { rootDir, registry });
|
|
210
213
|
const fragments = [];
|
|
211
|
-
const
|
|
214
|
+
const executionProfile = resolveExecutionCapabilityProfile({
|
|
215
|
+
model: executionContractModel?.selectedModel ?? modelId,
|
|
212
216
|
envValues: executionContractModel?.profile ? { CONSTRUCT_MODEL_PROFILE: executionContractModel.profile.id } : {},
|
|
213
|
-
selectedModel: executionContractModel?.selectedModel ?? modelId,
|
|
214
217
|
});
|
|
218
|
+
const selectedProfile = MODEL_OPERATING_PROFILES[operatingProfileIdFromProfile(executionProfile)];
|
|
215
219
|
|
|
216
220
|
fragments.push({ type: 'core', priority: PRIORITY['core'], label: entry.name, content: stripSectionMarkers(readPromptBody(entry.promptFile, rootDir)), tokenBudget: null });
|
|
217
221
|
|
|
@@ -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
|
/**
|