@geraldmaron/construct 1.2.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -10
- package/bin/construct +235 -209
- package/bin/construct-postinstall.mjs +0 -21
- package/commands/work/optimize-prompts.md +1 -1
- package/examples/distribution/sources/adr.md +2 -2
- package/examples/seed-observations/decisions.md +1 -1
- package/lib/artifact-loop-core.mjs +412 -0
- package/lib/artifact-manifest.mjs +6 -0
- package/lib/artifact-release-gate.mjs +115 -49
- package/lib/audit-rules.mjs +2 -4
- package/lib/audit-skills.mjs +32 -20
- package/lib/audit-specialists.mjs +44 -26
- package/lib/auto-docs.mjs +6 -5
- package/lib/brand-prose.mjs +3 -3
- package/lib/brand-tokens.mjs +2 -2
- package/lib/certification/artifact-fixtures.mjs +4 -1
- package/lib/certification/demo-parity.mjs +6 -32
- package/lib/certification/real-llm-scenarios.mjs +58 -13
- package/lib/certification/role-cards.mjs +7 -6
- package/lib/certification/role-overlays.mjs +4 -4
- package/lib/certification/runner.mjs +9 -2
- package/lib/certification/skill-inventory.mjs +34 -22
- package/lib/certification/skill-scenarios.mjs +21 -8
- package/lib/certification/specialist-contracts.mjs +4 -3
- package/lib/certification/specialist-scenarios.mjs +7 -6
- package/lib/certification/status.mjs +5 -4
- package/lib/cli-commands.mjs +55 -54
- package/lib/comment-lint.mjs +1 -5
- package/lib/completions.mjs +20 -2
- package/lib/config/legacy-config-migration.mjs +90 -24
- package/lib/config/schema.mjs +5 -5
- package/lib/config/source-targets.mjs +80 -0
- package/lib/contracts/validate.mjs +119 -51
- package/lib/decisions/golden.mjs +3 -2
- package/lib/decisions/registry.mjs +11 -6
- package/lib/demo-project.mjs +188 -0
- package/lib/demo-recording.mjs +9 -34
- package/lib/demo-script.mjs +2 -2
- package/lib/demo-surface.mjs +13 -131
- package/lib/demo-tour-renderer.mjs +92 -0
- package/lib/demo.mjs +69 -54
- package/lib/diagram-export.mjs +63 -14
- package/lib/doctor/source-checkout.mjs +3 -4
- package/lib/doctor/watchers/consistency.mjs +90 -22
- package/lib/doctor/watchers/credential-parity.mjs +97 -0
- package/lib/doctor/watchers/mcp-protocol.mjs +1 -1
- package/lib/doctor/watchers/service-health.mjs +4 -32
- package/lib/document-export.mjs +73 -9
- package/lib/document-extract.mjs +10 -198
- package/lib/embed/artifact.mjs +2 -3
- package/lib/embed/cli.mjs +1 -1
- package/lib/embed/config.mjs +1 -2
- package/lib/embed/customer-profiles.mjs +1 -1
- package/lib/embed/daemon.mjs +5 -6
- package/lib/embed/demand-fetch.mjs +114 -2
- package/lib/embed/docs-lifecycle.mjs +9 -5
- package/lib/embed/intake-metrics.mjs +2 -2
- package/lib/embed/notifications.mjs +2 -3
- package/lib/embed/recommendation-store.mjs +25 -22
- package/lib/embed/role-framing.mjs +4 -9
- package/lib/embedded-contract/capability.mjs +4 -2
- package/lib/embedded-contract/contract-version.mjs +1 -1
- package/lib/env-config.mjs +2 -2
- package/lib/flavors/loader.mjs +21 -19
- package/lib/graph/build-from-registry.mjs +31 -7
- package/lib/graph/staleness.mjs +1 -1
- package/lib/headhunt.mjs +82 -35
- package/lib/hooks/agent-tracker.mjs +2 -2
- package/lib/hooks/config-protection.mjs +1 -1
- package/lib/hooks/registry-sync.mjs +3 -3
- package/lib/host-capabilities.mjs +5 -5
- package/lib/improvement/controller.mjs +2 -2
- package/lib/init-unified.mjs +19 -21
- package/lib/intake/classify.mjs +26 -0
- package/lib/intake/daemon.mjs +2 -3
- package/lib/intake/prepare.mjs +10 -3
- package/lib/intake/session-prelude.mjs +1 -1
- package/lib/intake/tables/creative.mjs +9 -9
- package/lib/intake/tables/operations.mjs +4 -4
- package/lib/intake/tables/research.mjs +2 -2
- package/lib/integrations/intake-integrations.mjs +9 -10
- package/lib/knowledge/research-store.mjs +2 -2
- package/lib/mcp/server.mjs +104 -61
- package/lib/mcp/tool-budget.mjs +1 -46
- package/lib/mcp/tool-recovery.mjs +44 -0
- package/lib/mcp/tools/artifact-author.mjs +36 -0
- package/lib/mcp/tools/find-tool.mjs +86 -0
- package/lib/mcp/tools/orchestration-run.mjs +107 -69
- package/lib/mcp/tools/project.mjs +4 -3
- package/lib/mcp/tools/{profile.mjs → scope.mjs} +54 -52
- package/lib/mcp/tools/skills.mjs +39 -22
- package/lib/mcp/tools/telemetry.mjs +2 -1
- package/lib/mcp/tools/workflow.mjs +1 -1
- package/lib/mcp-platform-config.mjs +7 -5
- package/lib/migrations/index.mjs +4 -2
- package/lib/migrations/v2-unified-registry.mjs +35 -0
- package/lib/model-router.mjs +203 -36
- package/lib/models/catalog.mjs +25 -9
- package/lib/models/execution-capability-profile.mjs +3 -3
- package/lib/models/execution-policy.mjs +7 -6
- package/lib/models/provider-poll.mjs +18 -4
- package/lib/opencode-config.mjs +56 -1
- package/lib/opencode-runtime-plugin.mjs +13 -10
- package/lib/oracle/artifact-gate.mjs +14 -4
- package/lib/oracle/cli.mjs +2 -0
- package/lib/oracle/dispatch.mjs +18 -1
- package/lib/oracle/execute.mjs +39 -7
- package/lib/oracle/index.mjs +1 -1
- package/lib/oracle/org-graph.mjs +10 -5
- package/lib/oracle/policy.mjs +6 -0
- package/lib/oracle/read-model.mjs +103 -6
- package/lib/oracle/reconcile.mjs +31 -4
- package/lib/oracle/remediation-dispatch.mjs +53 -0
- package/lib/oracle/routing.mjs +5 -0
- package/lib/oracle/synthesize.mjs +80 -3
- package/lib/orchestration/routing-tables.mjs +44 -10
- package/lib/orchestration/runtime.mjs +2 -0
- package/lib/orchestration/worker.mjs +1 -1
- package/lib/orchestration-policy.mjs +351 -62
- package/lib/overrides/resolver.mjs +1 -12
- package/lib/parity.mjs +37 -11
- package/lib/policy/engine.mjs +80 -15
- package/lib/prompt-composer.js +51 -15
- package/lib/prompt-metadata.mjs +3 -2
- package/lib/providers/connection-probe.mjs +58 -0
- package/lib/providers/copilot-auth.mjs +1 -1
- package/lib/providers/credential-bootstrap.mjs +1 -1
- package/lib/providers/op-run.mjs +3 -4
- package/lib/providers/secret-resolver.mjs +31 -0
- package/lib/publish-tooling.mjs +3 -11
- package/lib/publish.mjs +12 -29
- package/lib/reconcile/mcp-entry-reconcile.mjs +14 -9
- package/lib/reflect.mjs +2 -2
- package/lib/registry/agent-manifest.mjs +190 -0
- package/lib/registry/assemble.mjs +133 -0
- package/lib/registry/catalog.mjs +171 -0
- package/lib/registry/cli.mjs +590 -6
- package/lib/registry/consolidation.mjs +5 -4
- package/lib/registry/docs-sync.mjs +67 -0
- package/lib/registry/loader.mjs +147 -0
- package/lib/registry/org-io.mjs +76 -0
- package/lib/registry/retired-paths.mjs +71 -0
- package/lib/registry/surface-map.mjs +5 -7
- package/lib/registry/validator.mjs +438 -0
- package/lib/research-execution-policy.mjs +173 -0
- package/lib/roles/catalog.mjs +4 -9
- package/lib/roles/fence.mjs +107 -1
- package/lib/roles/flavor-bindings.mjs +68 -0
- package/lib/roles/gateway.mjs +140 -29
- package/lib/roles/manifest.mjs +22 -13
- package/lib/roles/router.mjs +1 -1
- package/lib/scopes/enrich.mjs +67 -0
- package/lib/scopes/hooks.mjs +12 -0
- package/lib/{profiles → scopes}/lifecycle.mjs +70 -70
- package/lib/scopes/loader.mjs +104 -0
- package/lib/scopes/rebrand.mjs +32 -0
- package/lib/scopes/research-profile.mjs +16 -0
- package/lib/scopes/teams.mjs +98 -0
- package/lib/service-manager.mjs +1 -117
- package/lib/setup-prompts.mjs +1 -1
- package/lib/setup.mjs +6 -26
- package/lib/skills/composition-graph.mjs +98 -0
- package/lib/skills/router.mjs +80 -0
- package/lib/specialist-contracts.mjs +17 -18
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/specialists/prompt-schema.mjs +6 -5
- package/lib/specialists/roster.mjs +7 -12
- package/lib/specialists/schema.mjs +9 -6
- package/lib/status.mjs +25 -90
- package/lib/storage/file-lock.mjs +6 -3
- package/lib/telemetry/skill-calls.mjs +1 -1
- package/lib/templates/doc-presentation.mjs +63 -0
- package/lib/templates/visual-requirements.mjs +35 -2
- package/lib/term-format.mjs +107 -2
- package/lib/test-env-setup.mjs +21 -0
- package/lib/ui/components.mjs +42 -0
- package/lib/ui/glyphs.mjs +58 -0
- package/lib/ui/links.mjs +115 -0
- package/lib/ui/theme.mjs +108 -0
- package/lib/uninstall/uninstall.mjs +2 -1
- package/lib/validator.mjs +45 -8
- package/lib/validators/skill-effectiveness.mjs +174 -0
- package/lib/validators/skills.mjs +1 -1
- package/package.json +12 -8
- package/personas/construct.md +12 -3
- package/platforms/claude/CLAUDE.md +1 -1
- package/rules/common/beads-hygiene.md +52 -0
- package/rules/common/neurodivergent-output.md +4 -7
- package/rules/common/research.md +2 -0
- package/scripts/sync-specialists.mjs +260 -49
- package/skills/ai/prompt-optimizer.md +3 -3
- package/skills/brand/output-vibe.md +48 -0
- package/skills/docs/adr-workflow.md +3 -0
- package/skills/docs/backlog-proposal-workflow.md +3 -0
- package/skills/docs/codebase-research-workflow.md +5 -2
- package/skills/docs/customer-profile-workflow.md +3 -0
- package/skills/docs/document-ingest-workflow.md +3 -0
- package/skills/docs/evidence-ingest-workflow.md +4 -1
- package/skills/docs/init-project.md +1 -1
- package/skills/docs/prd-workflow.md +1 -1
- package/skills/docs/prfaq-workflow.md +3 -0
- package/skills/docs/product-intelligence-workflow.md +4 -1
- package/skills/docs/product-signal-workflow.md +3 -0
- package/skills/docs/research-workflow.md +13 -6
- package/skills/docs/runbook-workflow.md +3 -0
- package/skills/docs/strategy-workflow.md +3 -0
- package/skills/docs/user-research-workflow.md +6 -3
- package/skills/operating/orchestration-reference.md +1 -1
- package/skills/roles/{engineer.ai.md → ai-engineer.md} +2 -2
- package/skills/roles/architect.ai-systems.md +1 -1
- package/skills/roles/architect.data.md +1 -1
- package/skills/roles/architect.enterprise.md +1 -1
- package/skills/roles/architect.integration.md +1 -1
- package/skills/roles/architect.md +1 -1
- package/skills/roles/architect.platform.md +1 -1
- package/skills/roles/{product-manager.business-strategy.md → business-strategist.md} +3 -3
- package/skills/roles/data-analyst.experiment.md +1 -1
- package/skills/roles/data-analyst.md +1 -1
- package/skills/roles/data-analyst.product-intelligence.md +1 -1
- package/skills/roles/data-analyst.product.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/{engineer.data.md → data-engineer.md} +2 -2
- package/skills/roles/data-engineer.pipeline.md +2 -2
- package/skills/roles/data-engineer.vector-retrieval.md +2 -2
- package/skills/roles/data-engineer.warehouse.md +2 -2
- package/skills/roles/debugger.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +1 -1
- package/skills/roles/{reviewer.devil-advocate.md → devil-advocate.md} +2 -2
- package/skills/roles/{operator.docs.md → docs-keeper.md} +3 -3
- package/skills/roles/engineer.md +3 -7
- package/skills/roles/{reviewer.evaluator.md → evaluator.md} +2 -2
- package/skills/roles/{researcher.explorer.md → explorer.md} +2 -2
- package/skills/roles/{operator.md → operations.md} +2 -5
- package/skills/roles/orchestrator.md +1 -1
- package/skills/roles/{engineer.platform.md → platform-engineer.md} +2 -2
- package/skills/roles/product-manager.ai-product.md +1 -1
- package/skills/roles/product-manager.enterprise.md +1 -1
- package/skills/roles/product-manager.growth.md +1 -2
- package/skills/roles/product-manager.md +1 -2
- package/skills/roles/product-manager.platform.md +1 -1
- package/skills/roles/product-manager.product.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -1
- package/skills/roles/qa.api-contract.md +1 -1
- package/skills/roles/qa.data-pipeline.md +1 -1
- package/skills/roles/qa.md +1 -1
- package/skills/roles/qa.web-ui.md +1 -1
- package/skills/roles/{operator.release.md → release-manager.md} +3 -3
- package/skills/roles/researcher.md +1 -1
- package/skills/roles/reviewer.md +1 -1
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.appsec.md +1 -1
- package/skills/roles/security.cloud.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +1 -1
- package/skills/roles/security.privacy.md +1 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/{operator.sre.md → sre.md} +3 -3
- package/skills/roles/{qa.test-automation.md → test-automation.md} +2 -2
- package/skills/roles/{reviewer.trace.md → trace-reviewer.md} +2 -2
- package/skills/roles/{researcher.ux.md → ux-researcher.md} +2 -2
- package/skills/routing.json +18 -0
- package/skills/routing.md +1 -1
- package/specialists/artifact-manifest.json +2 -1
- package/specialists/audit-enrichments.json +14 -14
- package/specialists/org/contracts/accessibility-to-qa.json +37 -0
- package/specialists/org/contracts/any-to-business-strategist.json +57 -0
- package/specialists/org/contracts/any-to-debugger.json +38 -0
- package/specialists/org/contracts/any-to-designer.json +33 -0
- package/specialists/org/contracts/any-to-docs-keeper.json +34 -0
- package/specialists/org/contracts/any-to-explorer.json +39 -0
- package/specialists/org/contracts/any-to-sre-incident.json +33 -0
- package/specialists/org/contracts/any-to-trace-reviewer.json +32 -0
- package/specialists/org/contracts/architect-to-ai-engineer.json +32 -0
- package/specialists/org/contracts/architect-to-data-engineer.json +52 -0
- package/specialists/org/contracts/architect-to-devil-advocate.json +38 -0
- package/specialists/org/contracts/architect-to-engineer.json +34 -0
- package/specialists/org/contracts/architect-to-evaluator.json +59 -0
- package/specialists/org/contracts/architect-to-legal-compliance.json +55 -0
- package/specialists/org/contracts/architect-to-operations.json +45 -0
- package/specialists/org/contracts/architect-to-platform-engineer.json +42 -0
- package/specialists/org/contracts/business-strategist-to-product-manager.json +39 -0
- package/specialists/org/contracts/construct-to-orchestrator.json +36 -0
- package/specialists/org/contracts/construct-to-rd-lead.json +53 -0
- package/specialists/org/contracts/data-analyst-to-product-manager.json +43 -0
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +36 -0
- package/specialists/org/contracts/designer-to-accessibility.json +36 -0
- package/specialists/org/contracts/engineer-to-qa.json +34 -0
- package/specialists/org/contracts/engineer-to-reviewer.json +52 -0
- package/specialists/org/contracts/explorer-to-engineer.json +38 -0
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +43 -0
- package/specialists/org/contracts/platform-engineer-to-engineer.json +31 -0
- package/specialists/org/contracts/product-manager-to-architect.json +71 -0
- package/specialists/org/contracts/product-manager-to-data-analyst.json +49 -0
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +51 -0
- package/specialists/org/contracts/qa-to-release-manager.json +42 -0
- package/specialists/org/contracts/qa-to-test-automation.json +42 -0
- package/specialists/org/contracts/rd-lead-to-architect.json +38 -0
- package/specialists/org/contracts/researcher-to-architect.json +40 -0
- package/specialists/org/contracts/researcher-to-product-manager.json +57 -0
- package/specialists/org/contracts/reviewer-to-security.json +41 -0
- package/specialists/org/contracts/sre-to-release-manager.json +36 -0
- package/specialists/org/contracts/test-automation-to-engineer.json +34 -0
- package/specialists/org/contracts/trace-reviewer-to-sre.json +41 -0
- package/specialists/org/contracts/user-to-construct.json +30 -0
- package/specialists/org/groups/engineering-group.json +43 -0
- package/specialists/org/groups/governance-group.json +38 -0
- package/specialists/org/groups/operations-group.json +41 -0
- package/specialists/org/groups/product-group.json +46 -0
- package/specialists/org/groups/quality-group.json +42 -0
- package/specialists/org/groups/strategy-group.json +41 -0
- package/specialists/org/policies/action-approval.json +13 -0
- package/specialists/org/policies/agents-routing.json +13 -0
- package/specialists/org/policies/architecture.json +20 -0
- package/specialists/org/policies/bash-safety.json +13 -0
- package/specialists/org/policies/beads-hygiene.json +13 -0
- package/specialists/org/policies/bootstrap-state.json +13 -0
- package/specialists/org/policies/code-review.json +13 -0
- package/specialists/org/policies/coding-style.json +13 -0
- package/specialists/org/policies/comments.json +13 -0
- package/specialists/org/policies/commit-approval.json +13 -0
- package/specialists/org/policies/contract-preconditions.json +13 -0
- package/specialists/org/policies/deployment.json +20 -0
- package/specialists/org/policies/description.json +14 -0
- package/specialists/org/policies/design-approval.json +19 -0
- package/specialists/org/policies/doc-ownership.json +13 -0
- package/specialists/org/policies/file-path-fence.json +13 -0
- package/specialists/org/policies/framing.json +13 -0
- package/specialists/org/policies/git-workflow.json +13 -0
- package/specialists/org/policies/incident-response.json +15 -0
- package/specialists/org/policies/intake-triage.json +15 -0
- package/specialists/org/policies/neurodivergent-output.json +13 -0
- package/specialists/org/policies/no-fabrication.json +13 -0
- package/specialists/org/policies/patterns.json +13 -0
- package/specialists/org/policies/quality-gate-approval.json +17 -0
- package/specialists/org/policies/release-gates.json +13 -0
- package/specialists/org/policies/research-evidence.json +13 -0
- package/specialists/org/policies/review-before-change.json +13 -0
- package/specialists/org/policies/rollback.json +15 -0
- package/specialists/org/policies/scope-change.json +20 -0
- package/specialists/org/policies/secret-scan.json +13 -0
- package/specialists/org/policies/security-approval.json +17 -0
- package/specialists/org/policies/security.json +13 -0
- package/specialists/org/policies/session-efficiency.json +13 -0
- package/specialists/org/policies/skill-routing.json +13 -0
- package/specialists/org/policies/strategic-prioritization.json +17 -0
- package/specialists/org/policies/testing.json +13 -0
- package/specialists/org/policies/tool-invisibility.json +14 -0
- package/specialists/org/scopes/creative.json +54 -0
- package/specialists/org/scopes/operations.json +51 -0
- package/specialists/org/scopes/research.json +60 -0
- package/specialists/org/scopes/rnd.json +81 -0
- package/specialists/org/specialists/cx-accessibility.json +69 -0
- package/specialists/org/specialists/cx-ai-engineer.json +75 -0
- package/specialists/org/specialists/cx-architect.json +89 -0
- package/specialists/org/specialists/cx-business-strategist.json +72 -0
- package/specialists/org/specialists/cx-data-analyst.json +68 -0
- package/specialists/org/specialists/cx-data-engineer.json +71 -0
- package/specialists/org/specialists/cx-debugger.json +75 -0
- package/specialists/org/specialists/cx-designer.json +85 -0
- package/specialists/org/specialists/cx-devil-advocate.json +71 -0
- package/specialists/org/specialists/cx-docs-keeper.json +82 -0
- package/specialists/org/specialists/cx-engineer.json +106 -0
- package/specialists/org/specialists/cx-evaluator.json +69 -0
- package/specialists/org/specialists/cx-explorer.json +69 -0
- package/specialists/org/specialists/cx-legal-compliance.json +74 -0
- package/specialists/org/specialists/cx-operations.json +72 -0
- package/specialists/org/specialists/cx-oracle.json +46 -0
- package/specialists/org/specialists/cx-orchestrator.json +81 -0
- package/specialists/org/specialists/cx-platform-engineer.json +80 -0
- package/specialists/org/specialists/cx-product-manager.json +102 -0
- package/specialists/org/specialists/cx-qa.json +79 -0
- package/specialists/org/specialists/cx-rd-lead.json +73 -0
- package/specialists/org/specialists/cx-release-manager.json +77 -0
- package/specialists/org/specialists/cx-researcher.json +82 -0
- package/specialists/org/specialists/cx-reviewer.json +71 -0
- package/specialists/org/specialists/cx-security.json +91 -0
- package/specialists/org/specialists/cx-sre.json +94 -0
- package/specialists/org/specialists/cx-test-automation.json +69 -0
- package/specialists/org/specialists/cx-trace-reviewer.json +69 -0
- package/specialists/org/specialists/cx-ux-researcher.json +68 -0
- package/specialists/org/teams/accessibility-team.json +39 -0
- package/specialists/org/teams/design-team.json +40 -0
- package/specialists/org/teams/engineering-team.json +50 -0
- package/specialists/org/teams/governance-team.json +41 -0
- package/specialists/org/teams/operations-team.json +46 -0
- package/specialists/org/teams/product-management-team.json +45 -0
- package/specialists/org/teams/quality-team.json +49 -0
- package/specialists/org/teams/research-team.json +39 -0
- package/specialists/org/teams/strategy-team.json +48 -0
- package/specialists/org/teams/ux-research-team.json +39 -0
- package/specialists/prompts/_shared/validation-contract.md +1 -1
- package/specialists/prompts/_team-template.md +10 -0
- package/specialists/prompts/cx-accessibility.md +1 -0
- package/specialists/prompts/cx-ai-engineer.md +1 -1
- package/specialists/prompts/cx-business-strategist.md +1 -1
- package/specialists/prompts/cx-data-engineer.md +1 -1
- package/specialists/prompts/cx-designer.md +1 -0
- package/specialists/prompts/cx-devil-advocate.md +1 -1
- package/specialists/prompts/cx-docs-keeper.md +1 -1
- package/specialists/prompts/cx-evaluator.md +1 -1
- package/specialists/prompts/cx-explorer.md +1 -1
- package/specialists/prompts/cx-operations.md +1 -1
- package/specialists/prompts/cx-oracle.md +7 -3
- package/specialists/prompts/cx-orchestrator.md +17 -1
- package/specialists/prompts/cx-platform-engineer.md +1 -1
- package/specialists/prompts/cx-product-manager.md +2 -0
- package/specialists/prompts/cx-release-manager.md +1 -1
- package/specialists/prompts/cx-researcher.md +7 -4
- package/specialists/prompts/cx-sre.md +1 -1
- package/specialists/prompts/cx-test-automation.md +2 -2
- package/specialists/prompts/cx-trace-reviewer.md +3 -3
- package/specialists/prompts/cx-ux-researcher.md +2 -1
- package/templates/demos/scripts/architecture-review-adr.json +30 -0
- package/templates/demos/scripts/capability-contract.json +25 -0
- package/templates/demos/scripts/intake-triage.json +25 -0
- package/templates/demos/scripts/profile-doctor-health.json +25 -0
- package/templates/demos/tapes/agentic-platforms-prd.tape +2 -2
- package/templates/demos/tapes/architecture-review-adr.tape +44 -0
- package/templates/demos/tapes/capability-contract.tape +39 -0
- package/templates/demos/tapes/intake-triage.tape +39 -0
- package/templates/demos/tapes/profile-doctor-health.tape +39 -0
- package/templates/distribution/construct-brand.typ +23 -18
- package/templates/distribution/construct-decision.typ +1 -1
- package/templates/distribution/construct-pdf.typ +1 -1
- package/templates/distribution/construct-prd.typ +1 -1
- package/templates/distribution/construct-research.typ +1 -1
- package/templates/distribution/mermaid-puppeteer.json +8 -0
- package/templates/docs/persona-artifact.md +1 -1
- package/templates/docs/prd.md +6 -0
- package/apps/chat/engine/ai-sdk-agent.mjs +0 -183
- package/apps/chat/engine/loop-driver.mjs +0 -211
- package/apps/chat/engine/models.mjs +0 -122
- package/apps/chat/engine/provider-adapters.mjs +0 -171
- package/apps/chat/engine/tools/permission.mjs +0 -54
- package/apps/chat/engine/tools/primitives.mjs +0 -180
- package/apps/chat/engine/tools/registry.mjs +0 -122
- package/apps/chat/engine/turn-controls.mjs +0 -70
- package/lib/boundary.mjs +0 -127
- package/lib/certification/dashboard-api.mjs +0 -71
- package/lib/chat/cli.mjs +0 -333
- package/lib/chat/command-suggest.mjs +0 -161
- package/lib/chat/commands.mjs +0 -215
- package/lib/chat/config.mjs +0 -142
- package/lib/chat/context-compactor.mjs +0 -250
- package/lib/chat/context-continuation.mjs +0 -253
- package/lib/chat/continuation-source.mjs +0 -58
- package/lib/chat/demo-guide.mjs +0 -61
- package/lib/chat/design-tokens.mjs +0 -91
- package/lib/chat/desktop-binary.mjs +0 -81
- package/lib/chat/desktop-build.mjs +0 -130
- package/lib/chat/desktop-launcher.mjs +0 -133
- package/lib/chat/evidence.mjs +0 -145
- package/lib/chat/export.mjs +0 -74
- package/lib/chat/harness/driver.mjs +0 -91
- package/lib/chat/list-picker.mjs +0 -112
- package/lib/chat/model-picker.mjs +0 -356
- package/lib/chat/openrouter-fallback.mjs +0 -151
- package/lib/chat/permission-prompt.mjs +0 -33
- package/lib/chat/picker-catalog.mjs +0 -45
- package/lib/chat/policy-telemetry.mjs +0 -34
- package/lib/chat/present.mjs +0 -246
- package/lib/chat/session-context.mjs +0 -39
- package/lib/chat/session-persist.mjs +0 -73
- package/lib/chat/session-restore.mjs +0 -71
- package/lib/chat/session-settings.mjs +0 -53
- package/lib/chat/system-prompt.mjs +0 -52
- package/lib/chat/transparency.mjs +0 -93
- package/lib/chat/tui/color-scheme.mjs +0 -42
- package/lib/chat/tui/markdown.mjs +0 -123
- package/lib/chat/tui/presentation.mjs +0 -100
- package/lib/chat/tui/render.mjs +0 -500
- package/lib/chat/tui/turn-block.mjs +0 -284
- package/lib/chat/tui/turn-present.mjs +0 -18
- package/lib/chat/tui/turn-state.mjs +0 -88
- package/lib/chat/tui/usage.mjs +0 -122
- package/lib/chat/web-commands.mjs +0 -146
- package/lib/chat/web-launcher.mjs +0 -63
- package/lib/chat/web-picker-keys.mjs +0 -46
- package/lib/chat/web-session.mjs +0 -159
- package/lib/config/alias.mjs +0 -57
- package/lib/dashboard-demo.mjs +0 -71
- package/lib/dashboard-static.mjs +0 -175
- package/lib/install/desktop-binary-download.mjs +0 -88
- package/lib/profiles/loader.mjs +0 -123
- package/lib/profiles/rebrand.mjs +0 -46
- package/lib/server/auth.mjs +0 -169
- package/lib/server/chat-loop.mjs +0 -622
- package/lib/server/chat.mjs +0 -336
- package/lib/server/cors.mjs +0 -77
- package/lib/server/csrf.mjs +0 -103
- package/lib/server/demo-preview.mjs +0 -63
- package/lib/server/index.mjs +0 -2641
- package/lib/server/insights.mjs +0 -780
- package/lib/server/langfuse-login.mjs +0 -58
- package/lib/server/rate-limit.mjs +0 -93
- package/lib/server/telemetry-login.mjs +0 -100
- package/lib/server/webhook.mjs +0 -512
- package/specialists/contracts.json +0 -1032
- package/specialists/contracts.schema.json +0 -83
- package/specialists/policy-inventory.json +0 -188
- package/specialists/registry.json +0 -1412
- package/specialists/role-manifests.json +0 -217
- package/specialists/teams.json +0 -94
package/lib/audit-rules.mjs
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* concern; here they mark the rule as intentionally scoped, not an orphan.
|
|
12
12
|
* 2. REFERENCED — the canonical `rules/<dir>/<name>` path token (with or without
|
|
13
13
|
* the .md) appears somewhere in the active surface (personas, CLAUDE.md,
|
|
14
|
-
* skills, all of lib
|
|
14
|
+
* skills, all of lib/, registry/
|
|
15
15
|
* contracts, other rules, scripts, the CLI).
|
|
16
16
|
* A rule that is neither glob-activated nor referenced is a true orphan — a
|
|
17
17
|
* pruning candidate. A rule's own file never counts as referencing itself.
|
|
@@ -30,9 +30,7 @@ const SURFACE_DIRS = ['personas', 'skills', 'rules', 'lib', 'specialists', 'scri
|
|
|
30
30
|
const SURFACE_FILES = ['CLAUDE.md', 'AGENTS.md', 'bin/construct', 'claude/settings.template.json'];
|
|
31
31
|
const TEXT_EXT = new Set(['.md', '.mjs', '.js', '.json', '.txt', '.mdx']);
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
// markdown lexer's own grammar) that would read as false references.
|
|
35
|
-
const SURFACE_SKIP = ['lib/server/static'];
|
|
33
|
+
const SURFACE_SKIP = [];
|
|
36
34
|
|
|
37
35
|
function isSkipped(full, root) {
|
|
38
36
|
const rel = path.relative(root, full);
|
package/lib/audit-skills.mjs
CHANGED
|
@@ -6,18 +6,19 @@
|
|
|
6
6
|
* Called by 'construct audit skills' and incorporated into 'construct doctor'.
|
|
7
7
|
*
|
|
8
8
|
* A `roles/<base>[.<flavor>]` skill is owned when `<base>` is a specialist OR a role
|
|
9
|
-
* named in any
|
|
9
|
+
* named in any curated scope (specialists/org/scopes) — a role skill loads when its specialist or
|
|
10
10
|
* profile role runs, even without a direct registry `skills:` binding. Counting only
|
|
11
11
|
* registry bindings over-reported orphans (it missed profile roles like `operator`
|
|
12
12
|
* from operations.json and conditional specialist flavors); see bead construct-ksfa.
|
|
13
13
|
*/
|
|
14
14
|
import fs from 'node:fs';
|
|
15
|
+
import { loadRegistry } from './registry/loader.mjs';
|
|
15
16
|
import path from 'node:path';
|
|
16
17
|
|
|
17
18
|
function findConstructRoot(startPath = process.cwd()) {
|
|
18
19
|
let current = path.resolve(startPath);
|
|
19
20
|
while (true) {
|
|
20
|
-
if (fs.existsSync(path.join(current, 'specialists', '
|
|
21
|
+
if (fs.existsSync(path.join(current, 'specialists', 'org'))) return current;
|
|
21
22
|
const parent = path.dirname(current);
|
|
22
23
|
if (parent === current) return path.resolve(startPath);
|
|
23
24
|
current = parent;
|
|
@@ -40,23 +41,18 @@ function collectSkillFiles(skillsDir) {
|
|
|
40
41
|
return results;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// base even if no specialist declares the skill path directly.
|
|
44
|
+
import { collectScopeRoleIds } from './scopes/enrich.mjs';
|
|
45
|
+
|
|
46
46
|
function collectProfileRoles(root) {
|
|
47
|
-
const dir = path.join(root, '
|
|
47
|
+
const dir = path.join(root, 'specialists', 'org', 'scopes');
|
|
48
48
|
const roles = new Set();
|
|
49
|
-
const scan = (node) => {
|
|
50
|
-
if (Array.isArray(node)) { node.forEach(scan); return; }
|
|
51
|
-
if (node && typeof node === 'object') {
|
|
52
|
-
if (Array.isArray(node.roles)) for (const r of node.roles) if (typeof r === 'string') roles.add(r);
|
|
53
|
-
for (const v of Object.values(node)) scan(v);
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
49
|
try {
|
|
57
50
|
for (const f of fs.readdirSync(dir)) {
|
|
58
51
|
if (!f.endsWith('.json')) continue;
|
|
59
|
-
try {
|
|
52
|
+
try {
|
|
53
|
+
const raw = JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8'));
|
|
54
|
+
for (const r of collectScopeRoleIds(raw, { rootDir: root })) roles.add(r);
|
|
55
|
+
} catch { /* skip malformed */ }
|
|
60
56
|
}
|
|
61
57
|
} catch { /* no profiles dir */ }
|
|
62
58
|
return roles;
|
|
@@ -70,17 +66,16 @@ function roleBase(skill) {
|
|
|
70
66
|
|
|
71
67
|
export function auditSkills({ rootDir, silent = false } = {}) {
|
|
72
68
|
const root = rootDir ?? findConstructRoot();
|
|
73
|
-
const registryPath = path.join(root, 'specialists', 'registry.json');
|
|
74
69
|
const skillsDir = path.join(root, 'skills');
|
|
75
70
|
|
|
76
|
-
const registry =
|
|
71
|
+
const registry = loadRegistry({ rootDir: root });
|
|
77
72
|
const allSkillFiles = collectSkillFiles(skillsDir);
|
|
78
73
|
|
|
79
74
|
const declaredSkills = new Set();
|
|
80
75
|
const agentsWithNoSkills = [];
|
|
81
76
|
const missingSkillFiles = [];
|
|
82
77
|
|
|
83
|
-
for (const agent of registry.specialists ??
|
|
78
|
+
for (const agent of Object.values(registry.specialists ?? {})) {
|
|
84
79
|
const skills = agent.skills ?? [];
|
|
85
80
|
if (skills.length === 0) {
|
|
86
81
|
agentsWithNoSkills.push(agent.name);
|
|
@@ -93,12 +88,17 @@ export function auditSkills({ rootDir, silent = false } = {}) {
|
|
|
93
88
|
}
|
|
94
89
|
}
|
|
95
90
|
|
|
96
|
-
// Owning role bases = specialist names
|
|
97
|
-
// skill is owned when its base is in this set, even without a direct binding.
|
|
91
|
+
// Owning role bases = specialist names, scope/org roles, and role-skill bases declared on specialists.
|
|
98
92
|
const ownerBases = new Set([
|
|
99
|
-
...(registry.specialists ??
|
|
93
|
+
...Object.values(registry.specialists ?? {}).map((a) => a.name.replace(/^cx-/, '')),
|
|
100
94
|
...collectProfileRoles(root),
|
|
101
95
|
]);
|
|
96
|
+
for (const agent of Object.values(registry.specialists ?? {})) {
|
|
97
|
+
for (const skill of agent.skills ?? []) {
|
|
98
|
+
const base = roleBase(skill);
|
|
99
|
+
if (base) ownerBases.add(base);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
102
|
const isOwned = (s) => declaredSkills.has(s) || (ownerBases.has(roleBase(s)));
|
|
103
103
|
|
|
104
104
|
const orphanSkills = [...allSkillFiles].filter((s) => !isOwned(s));
|
|
@@ -159,6 +159,18 @@ export async function runAuditSkillsCli(args = []) {
|
|
|
159
159
|
const { runSkillInventoryAuditCli } = await import('./certification/skill-inventory.mjs');
|
|
160
160
|
return runSkillInventoryAuditCli(args.filter((a) => a !== '--inventory'), { rootDir });
|
|
161
161
|
}
|
|
162
|
+
if (args.includes('--composition')) {
|
|
163
|
+
const { auditSkillComposition } = await import('./skills/composition-graph.mjs');
|
|
164
|
+
const result = auditSkillComposition({ rootDir });
|
|
165
|
+
if (args.includes('--json')) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
166
|
+
else {
|
|
167
|
+
process.stdout.write(`Skill composition: ${result.stats.onDisk} on disk, ${result.blocking.length} blocking\n`);
|
|
168
|
+
for (const b of result.blocking) process.stderr.write(` ✗ ${b}\n`);
|
|
169
|
+
process.stdout.write(result.pass ? ' Result: PASS\n' : ' Result: FAIL\n');
|
|
170
|
+
}
|
|
171
|
+
if (!result.pass) process.exit(1);
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
162
174
|
const result = auditSkills({ rootDir });
|
|
163
175
|
if (!result.pass) process.exit(1);
|
|
164
176
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import fs from 'node:fs';
|
|
10
|
+
import { loadRegistry } from './registry/loader.mjs';
|
|
10
11
|
import path from 'node:path';
|
|
11
12
|
import { auditSkills } from './audit-skills.mjs';
|
|
12
13
|
import { loadArtifactManifest } from './artifact-manifest.mjs';
|
|
@@ -17,14 +18,14 @@ import { validateAllRoleOverlays } from './certification/role-overlays.mjs';
|
|
|
17
18
|
import { ROLE_DIRECTIVE_RE } from './role-preload.mjs';
|
|
18
19
|
|
|
19
20
|
function findConstructRoot(startPath = process.cwd()) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
21
|
+
let current = path.resolve(startPath);
|
|
22
|
+
while (true) {
|
|
23
|
+
if (fs.existsSync(path.join(current, 'specialists', 'org'))) return current;
|
|
24
|
+
const parent = path.dirname(current);
|
|
25
|
+
if (parent === current) return path.resolve(startPath);
|
|
26
|
+
current = parent;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
28
29
|
|
|
29
30
|
function readJson(p) {
|
|
30
31
|
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
@@ -85,7 +86,7 @@ function crossChecks({ registry, manifest, skillAudit, allSkills, contractDocTyp
|
|
|
85
86
|
const issues = [];
|
|
86
87
|
const manifestTypes = new Set(Object.keys(manifest.artifacts ?? {}));
|
|
87
88
|
|
|
88
|
-
for (const agent of registry.specialists ??
|
|
89
|
+
for (const agent of Object.values(registry.specialists ?? {})) {
|
|
89
90
|
for (const skill of agent.skills ?? []) {
|
|
90
91
|
if (!allSkills.includes(skill)) {
|
|
91
92
|
issues.push({ kind: 'missing-skill-file', agent: agent.name, skill });
|
|
@@ -114,24 +115,41 @@ function crossChecks({ registry, manifest, skillAudit, allSkills, contractDocTyp
|
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
export function auditSpecialists({ rootDir, silent = false } = {}) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
118
|
+
const root = rootDir ?? findConstructRoot();
|
|
119
|
+
|
|
120
|
+
const unifiedRegistry = loadRegistry({ rootDir: root });
|
|
121
|
+
|
|
122
|
+
// Convert unified registry to legacy registry shape for compatibility
|
|
123
|
+
const registry = {
|
|
124
|
+
specialists: Object.entries(unifiedRegistry.specialists || {}).map(([id, spec]) => ({
|
|
125
|
+
name: spec.name,
|
|
126
|
+
displayName: spec.displayName,
|
|
127
|
+
description: spec.description,
|
|
128
|
+
team: spec.team,
|
|
129
|
+
role: spec.role,
|
|
130
|
+
skills: spec.skills,
|
|
131
|
+
promptFile: spec.promptFile,
|
|
132
|
+
docArtifacts: spec.docArtifacts,
|
|
133
|
+
...spec
|
|
134
|
+
}))
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const contractsDoc = { contracts: Object.values(unifiedRegistry.contracts || {}) };
|
|
138
|
+
const enrichments = readJson(path.join(root, 'specialists', 'audit-enrichments.json'));
|
|
139
|
+
const manifest = loadArtifactManifest({ rootDir: root });
|
|
140
|
+
const skillAudit = auditSkills({ rootDir: root, silent: true });
|
|
141
|
+
const allSkills = collectSkillFiles(path.join(root, 'skills'));
|
|
142
|
+
const contractDocTypes = docTypesFromContracts(contractsDoc.contracts ?? []);
|
|
143
|
+
const crossCheckIssues = crossChecks({ registry, manifest, skillAudit, allSkills, contractDocTypes });
|
|
126
144
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
145
|
+
const specialists = Object.values(registry.specialists ?? {}).map((agent) => {
|
|
146
|
+
const promptPath = agent.promptFile
|
|
147
|
+
? path.join(root, agent.promptFile)
|
|
148
|
+
: null;
|
|
149
|
+
const meta = enrichments.specialists?.[agent.name] ?? {};
|
|
150
|
+
const perspective = resolvePerspectiveFromPrompt(agent.name, { rootDir: root, registry });
|
|
151
|
+
return {
|
|
152
|
+
name: agent.name,
|
|
135
153
|
id: `cx-${agent.name}`,
|
|
136
154
|
humanEquivalent: meta.humanEquivalent ?? 'unknown',
|
|
137
155
|
primaryOutputs: agent.docArtifacts ?? [],
|
package/lib/auto-docs.mjs
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import fs from 'node:fs';
|
|
18
|
+
import { loadRegistry } from './registry/loader.mjs';
|
|
18
19
|
import path from 'node:path';
|
|
19
20
|
import { execSync } from 'node:child_process';
|
|
20
21
|
|
|
@@ -90,7 +91,7 @@ function buildCoreDocsContract() {
|
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
const DIR_DESCRIPTIONS = {
|
|
93
|
-
apps: 'User-facing apps shipped from this repo (
|
|
94
|
+
apps: 'User-facing apps shipped from this repo (dashboard, docs)',
|
|
94
95
|
bin: 'CLI entrypoint (`construct`)',
|
|
95
96
|
commands: 'Command prompt assets',
|
|
96
97
|
config: 'Repo-wide controlled vocabulary (tag-vocabulary.json)',
|
|
@@ -165,13 +166,13 @@ function buildHooksTable(rootDir) {
|
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
function buildAgentsTable(rootDir) {
|
|
168
|
-
const registryPath = path.join(rootDir, 'specialists', '
|
|
169
|
+
const registryPath = path.join(rootDir, 'specialists', 'org');
|
|
169
170
|
let registry;
|
|
170
171
|
try { registry = JSON.parse(fs.readFileSync(registryPath, 'utf8')); } catch { return ''; }
|
|
171
172
|
|
|
172
173
|
const agents = [
|
|
173
174
|
...(registry.orchestrator ? [{ ...registry.orchestrator, name: registry.orchestrator.name }] : []),
|
|
174
|
-
...(registry.specialists ??
|
|
175
|
+
...Object.values(registry.specialists ?? {}),
|
|
175
176
|
];
|
|
176
177
|
if (!agents.length) return '';
|
|
177
178
|
|
|
@@ -410,10 +411,10 @@ function renderAgentsPage(rootDir) {
|
|
|
410
411
|
return [
|
|
411
412
|
'---',
|
|
412
413
|
'title: Specialists',
|
|
413
|
-
'description: The 28 specialists behind the construct persona. Generated from specialists/
|
|
414
|
+
'description: The 28 specialists behind the construct persona. Generated from specialists/org.',
|
|
414
415
|
'---',
|
|
415
416
|
'',
|
|
416
|
-
'> Generated from `specialists/
|
|
417
|
+
'> Generated from `specialists/org`. Re-run `construct docs:site` to refresh.',
|
|
417
418
|
'',
|
|
418
419
|
'Construct ships one persona (`construct`) and 28 specialists behind it. You address `@construct` for all everyday work; it routes to specialists internally. Each specialist has a role, model tier, and prompt file that defines its decision authority.',
|
|
419
420
|
'',
|
package/lib/brand-prose.mjs
CHANGED
|
@@ -15,10 +15,10 @@ export const RETIRED_FONT_RE =
|
|
|
15
15
|
/Plus\s+Jakarta|PlusJakarta|Geist(?:Mono)?|IBM\s+Plex|Libertinus|font-family:\s*["']?Inter["']?/i;
|
|
16
16
|
|
|
17
17
|
const CLI_SUBCOMMANDS =
|
|
18
|
-
'doctor|init|sync|dev|stop|status|intake|
|
|
18
|
+
'doctor|init|sync|dev|stop|status|intake|install|drop|search|prune|upgrade|config|list|graph|beads|export|publish|artifact|demo|oracle|ci|acp|profile|models|uninstall|reconcile|embed|ingest|classify|workflow|capability';
|
|
19
19
|
|
|
20
20
|
const PRODUCT_NOUN_PHRASES =
|
|
21
|
-
/\bConstruct (?:install|MCP server|MCP tools?|workflow|
|
|
21
|
+
/\bConstruct (?:install|MCP server|MCP tools?|workflow|package|repo|surfaces?|integration|orchestrator|daemon|specialists?|persona|brand|tooling|standards|config|adapters?|platform|version|release|hooks?|memory|loop|Dashboard|capability)\b/i;
|
|
22
22
|
|
|
23
23
|
const UNBACKTICKED_CLI_RE = new RegExp(
|
|
24
24
|
`(?<![\`~/])construct\\s+(${CLI_SUBCOMMANDS})(?:\\s+[\\w:-]+)*\\b`,
|
|
@@ -40,7 +40,7 @@ const DOC_EXEMPT =
|
|
|
40
40
|
/(?:^|\/)(docs\/STYLE\.md|docs\/guides\/reference\/branding\.md|templates\/distribution\/fonts\/README\.md)$/;
|
|
41
41
|
|
|
42
42
|
const HOOK_SCOPED =
|
|
43
|
-
/^(docs\/|skills\/|specialists\/|personas\/|templates\/|rules\/|apps\/(dashboard|docs
|
|
43
|
+
/^(docs\/|skills\/|specialists\/|personas\/|templates\/|rules\/|apps\/(dashboard|docs)\/)/;
|
|
44
44
|
|
|
45
45
|
export function isBrandHookPath(relPath) {
|
|
46
46
|
if (!relPath || EXCLUDE_PATH.test(relPath) || DOC_EXEMPT.test(relPath)) return false;
|
package/lib/brand-tokens.mjs
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* lib/brand-tokens.mjs — Construct distribution brand primitives.
|
|
3
3
|
*
|
|
4
4
|
* Monochrome ink ramp from templates/distribution/construct-brand.typ plus Space
|
|
5
|
-
* Grotesk / JetBrains Mono typography.
|
|
6
|
-
*
|
|
5
|
+
* Grotesk / JetBrains Mono typography. BRAND_TOKENS feed publish exports,
|
|
6
|
+
* deck HTML, and PPTX.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
export const INK = Object.freeze({
|
|
@@ -73,7 +73,10 @@ export function buildGoldenFixtureMarkdown(type, { rootDir } = {}) {
|
|
|
73
73
|
|
|
74
74
|
for (const section of entry.structureRequirements ?? []) {
|
|
75
75
|
lines.push(`## ${section}`, '');
|
|
76
|
-
if (
|
|
76
|
+
if (section === 'Phases' && type === 'prd') {
|
|
77
|
+
lines.push('### Phase 1: Fixture delivery', '', '- **Goal**: Ship an isolated billing ledger per tenant.', '- **Status**: not started', '', '**Functional**', '', '- **FR-1.1**: Each tenant invoice derives only from that tenant ledger events.', ' - *Acceptance*: Reconciliation test passes without cross-tenant reads.', '');
|
|
78
|
+
proseAdded += 1;
|
|
79
|
+
} else if (proseAdded < proseMinimum) {
|
|
77
80
|
lines.push(proseBlock(section));
|
|
78
81
|
proseAdded += 1;
|
|
79
82
|
} else {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/certification/demo-parity.mjs —
|
|
2
|
+
* lib/certification/demo-parity.mjs — demo terminal-harness report generator.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* demo
|
|
6
|
-
* platform constraints with evidence paths.
|
|
4
|
+
* Verifies each canonical demo ships a VHS terminal tape. Terminal is the sole
|
|
5
|
+
* guided demo surface after web-deprecation retired the desktop window and web cockpit;
|
|
6
|
+
* acceptable divergences cite platform constraints with evidence paths.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import fs from 'node:fs';
|
|
@@ -12,26 +12,6 @@ import path from 'node:path';
|
|
|
12
12
|
import { loadCanonicalScenarios } from './canonical-scenarios.mjs';
|
|
13
13
|
|
|
14
14
|
const SURFACE_MARKERS = Object.freeze({
|
|
15
|
-
tauri: {
|
|
16
|
-
label: 'Tauri desktop',
|
|
17
|
-
probe: (root, demo) => {
|
|
18
|
-
const launcher = path.join(root, 'tests/functional/chat-desktop-launcher.functional.test.mjs');
|
|
19
|
-
if (!fs.existsSync(launcher)) return { ok: false, detail: 'missing desktop launcher test' };
|
|
20
|
-
const src = fs.readFileSync(launcher, 'utf8');
|
|
21
|
-
return { ok: /surface=desktop/.test(src), detail: launcher };
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
web: {
|
|
25
|
-
label: 'Web cockpit',
|
|
26
|
-
probe: (root, demo) => {
|
|
27
|
-
const routeStrip = path.join(root, 'apps/chat/web/components/route-strip.tsx');
|
|
28
|
-
const tokens = path.join(root, 'apps/chat/web/theme/tokens.css');
|
|
29
|
-
if (!fs.existsSync(routeStrip) || !fs.existsSync(tokens)) {
|
|
30
|
-
return { ok: false, detail: 'missing web cockpit markers' };
|
|
31
|
-
}
|
|
32
|
-
return { ok: true, detail: routeStrip };
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
15
|
terminal: {
|
|
36
16
|
label: 'VHS terminal',
|
|
37
17
|
probe: (root, demo) => {
|
|
@@ -45,15 +25,9 @@ const SURFACE_MARKERS = Object.freeze({
|
|
|
45
25
|
|
|
46
26
|
const ACCEPTABLE_DIVERGENCES = Object.freeze([
|
|
47
27
|
{
|
|
48
|
-
id: '
|
|
49
|
-
surface: 'tauri',
|
|
50
|
-
reason: 'Desktop shell uses native window chrome; web uses browser viewport.',
|
|
51
|
-
evidence: 'apps/chat/desktop/',
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
id: 'vhs-terminal-only',
|
|
28
|
+
id: 'vhs-tape-direct',
|
|
55
29
|
surface: 'terminal',
|
|
56
|
-
reason: 'VHS tapes assert
|
|
30
|
+
reason: 'VHS tapes assert the scripted terminal recording directly.',
|
|
57
31
|
evidence: 'templates/demos/tapes/',
|
|
58
32
|
},
|
|
59
33
|
]);
|
|
@@ -34,9 +34,10 @@ export function realLlmOptInEnabled(env = process.env) {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
export function realLlmCredentialsPresent(env = process.env) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|| hasSecret('
|
|
37
|
+
const allowAmbient = env === process.env;
|
|
38
|
+
return hasSecret('OPENROUTER_API_KEY', { env, allowAmbient })
|
|
39
|
+
|| hasSecret('ANTHROPIC_API_KEY', { env, allowAmbient })
|
|
40
|
+
|| hasSecret('OPENAI_API_KEY', { env, allowAmbient })
|
|
40
41
|
|| hasStoredCredential();
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -51,25 +52,26 @@ export function realLlmSkipReason(env = process.env) {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
export function resolveRealLlmProvider(env = process.env) {
|
|
55
|
+
const allowAmbient = env === process.env;
|
|
54
56
|
const requested = (env[REAL_LLM_PROVIDER_ENV] || DEFAULT_REAL_LLM_PROVIDER).trim().toLowerCase();
|
|
55
57
|
const modelOverride = env[REAL_LLM_MODEL_ENV]?.trim() || null;
|
|
56
58
|
|
|
57
59
|
if (requested === 'openrouter') {
|
|
58
|
-
if (!hasSecret('OPENROUTER_API_KEY', { env })) {
|
|
60
|
+
if (!hasSecret('OPENROUTER_API_KEY', { env, allowAmbient })) {
|
|
59
61
|
return { skip: 'OPENROUTER_API_KEY required for OpenRouter real-LLM scenarios' };
|
|
60
62
|
}
|
|
61
63
|
return { provider: 'openrouter', model: modelOverride || DEFAULT_REAL_LLM_MODEL };
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
if (requested === 'anthropic') {
|
|
65
|
-
if (!hasSecret('ANTHROPIC_API_KEY', { env })) {
|
|
67
|
+
if (!hasSecret('ANTHROPIC_API_KEY', { env, allowAmbient })) {
|
|
66
68
|
return { skip: 'ANTHROPIC_API_KEY required for Anthropic real-LLM scenarios' };
|
|
67
69
|
}
|
|
68
70
|
return { provider: 'anthropic', model: modelOverride || 'claude-sonnet-4-20250514' };
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
if (requested === 'openai') {
|
|
72
|
-
if (!hasSecret('OPENAI_API_KEY', { env })) {
|
|
74
|
+
if (!hasSecret('OPENAI_API_KEY', { env, allowAmbient })) {
|
|
73
75
|
return { skip: 'OPENAI_API_KEY required for OpenAI real-LLM scenarios' };
|
|
74
76
|
}
|
|
75
77
|
return { provider: 'openai', model: modelOverride || 'gpt-4o-mini' };
|
|
@@ -125,7 +127,9 @@ export function buildPrdPolishMessages({ requestSummary = S3_REQUEST, specialist
|
|
|
125
127
|
const system = [
|
|
126
128
|
'You are cx-product-manager synthesizing a multi-specialist orchestration run into one release-quality PRD markdown file.',
|
|
127
129
|
'Output markdown only — no preamble.',
|
|
128
|
-
'Required ## sections (exact headings): Problem, Goals, Success metrics, Risks and mitigations.',
|
|
130
|
+
'Required ## sections (exact headings): Problem, Goals, Success metrics, Phases, Risks and mitigations.',
|
|
131
|
+
'Under Phases include at least one phase with **FR-<phase>.<n>:** functional requirements and *Acceptance:* criteria per requirement.',
|
|
132
|
+
'You may instead use ## Requirements (or Functional requirements) and ## Acceptance criteria when requirements are not phased inline.',
|
|
129
133
|
'Under Problem and Goals write multi-sentence paragraphs (not bullet-only outlines).',
|
|
130
134
|
'Include a ```mermaid flowchart``` diagram for the user flow.',
|
|
131
135
|
'Include a markdown table with columns: Metric | Baseline | Target (header row required).',
|
|
@@ -140,6 +144,50 @@ function resolvePolishModel(env = process.env) {
|
|
|
140
144
|
return env[REAL_LLM_POLISH_MODEL_ENV]?.trim() || DEFAULT_REAL_LLM_POLISH_MODEL;
|
|
141
145
|
}
|
|
142
146
|
|
|
147
|
+
export const S3_QUALITY_THRESHOLDS = Object.freeze({ minProse: 2, minCitations: 1 });
|
|
148
|
+
|
|
149
|
+
export function formatArtifactQualityVerdictDetail(
|
|
150
|
+
verdict,
|
|
151
|
+
{ minProse = S3_QUALITY_THRESHOLDS.minProse, minCitations = S3_QUALITY_THRESHOLDS.minCitations } = {},
|
|
152
|
+
) {
|
|
153
|
+
if (!verdict || typeof verdict !== 'object') {
|
|
154
|
+
return 'quality gate not met after polish';
|
|
155
|
+
}
|
|
156
|
+
const structurePart = verdict.structure?.ok
|
|
157
|
+
? 'structure: ok'
|
|
158
|
+
: `structure: fail (${(verdict.structure?.errors ?? []).join('; ') || 'unknown'})`;
|
|
159
|
+
const prosePart = `prose: ${verdict.prose?.paragraphs ?? 0}/${minProse} (${verdict.prose?.ok ? 'ok' : 'fail'})`;
|
|
160
|
+
const researchPart = `research: citations=${verdict.research?.citations ?? 0}/${minCitations} unverified=${Boolean(verdict.research?.unverifiedDiscipline)} (${verdict.research?.ok ? 'ok' : 'fail'})`;
|
|
161
|
+
return `quality gate not met after polish; ${structurePart}; ${prosePart}; ${researchPart}`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function buildS3QualityGateFailure({
|
|
165
|
+
verdict,
|
|
166
|
+
polishModel = null,
|
|
167
|
+
rawOutputLength = null,
|
|
168
|
+
thresholds = S3_QUALITY_THRESHOLDS,
|
|
169
|
+
} = {}) {
|
|
170
|
+
return {
|
|
171
|
+
scenarioId: 'real-llm.s3',
|
|
172
|
+
status: 'inconclusive',
|
|
173
|
+
detail: formatArtifactQualityVerdictDetail(verdict, thresholds),
|
|
174
|
+
verdict,
|
|
175
|
+
qualityThresholds: thresholds,
|
|
176
|
+
polishModel,
|
|
177
|
+
rawOutputLength,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function redactRealLlmGateEvidence(result) {
|
|
182
|
+
if (!result?.verdict) return null;
|
|
183
|
+
return JSON.stringify({
|
|
184
|
+
artifactQuality: result.verdict,
|
|
185
|
+
qualityThresholds: result.qualityThresholds ?? S3_QUALITY_THRESHOLDS,
|
|
186
|
+
polishModel: result.polishModel ?? null,
|
|
187
|
+
rawOutputLength: result.rawOutputLength ?? null,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
143
191
|
async function polishSpecialistOutputsToPrd({ specialistOutputs, requestSummary, env, fetchImpl = globalThis.fetch }) {
|
|
144
192
|
if (!hasSecret('OPENROUTER_API_KEY', { env })) {
|
|
145
193
|
return { skip: 'OPENROUTER_API_KEY required for PRD polish synthesis' };
|
|
@@ -230,16 +278,13 @@ export async function runRealLlmS3({ env = process.env, cleanup = true } = {}) {
|
|
|
230
278
|
|
|
231
279
|
const prdPath = join(cwd, 'prd-output.md');
|
|
232
280
|
writeFileSync(prdPath, polished.text);
|
|
233
|
-
const verdict = assessArtifactQuality(prdPath, 'prd',
|
|
281
|
+
const verdict = assessArtifactQuality(prdPath, 'prd', S3_QUALITY_THRESHOLDS);
|
|
234
282
|
if (!verdict.ok) {
|
|
235
|
-
return {
|
|
236
|
-
scenarioId: 'real-llm.s3',
|
|
237
|
-
status: 'inconclusive',
|
|
238
|
-
detail: 'quality gate not met after polish',
|
|
283
|
+
return buildS3QualityGateFailure({
|
|
239
284
|
verdict,
|
|
240
285
|
polishModel: polished.polishModel,
|
|
241
286
|
rawOutputLength: rawLongest.length,
|
|
242
|
-
};
|
|
287
|
+
});
|
|
243
288
|
}
|
|
244
289
|
return {
|
|
245
290
|
scenarioId: 'real-llm.s3',
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import fs from 'node:fs';
|
|
10
|
+
import { loadRegistry } from '../registry/loader.mjs';
|
|
10
11
|
import path from 'node:path';
|
|
11
12
|
|
|
12
13
|
import { splitFrontmatter } from '../specialists/prompt-schema.mjs';
|
|
@@ -14,7 +15,7 @@ import { splitFrontmatter } from '../specialists/prompt-schema.mjs';
|
|
|
14
15
|
function findConstructRoot(startPath = process.cwd()) {
|
|
15
16
|
let current = path.resolve(startPath);
|
|
16
17
|
while (true) {
|
|
17
|
-
if (fs.existsSync(path.join(current, 'specialists', '
|
|
18
|
+
if (fs.existsSync(path.join(current, 'specialists', 'org'))) return current;
|
|
18
19
|
const parent = path.dirname(current);
|
|
19
20
|
if (parent === current) return path.resolve(startPath);
|
|
20
21
|
current = parent;
|
|
@@ -60,7 +61,7 @@ export function buildRoleCard(agent, { rootDir } = {}) {
|
|
|
60
61
|
refusalBoundaries,
|
|
61
62
|
modelTier: agent.modelTier ?? null,
|
|
62
63
|
sources: {
|
|
63
|
-
registry: 'specialists/
|
|
64
|
+
registry: 'specialists/org',
|
|
64
65
|
promptFile: agent.promptFile ?? null,
|
|
65
66
|
},
|
|
66
67
|
};
|
|
@@ -68,11 +69,11 @@ export function buildRoleCard(agent, { rootDir } = {}) {
|
|
|
68
69
|
|
|
69
70
|
export function writeRoleCards({ rootDir } = {}) {
|
|
70
71
|
const root = findConstructRoot(rootDir);
|
|
71
|
-
const registry =
|
|
72
|
+
const registry = loadRegistry({ rootDir: root });
|
|
72
73
|
const dir = roleCardsDir(root);
|
|
73
74
|
fs.mkdirSync(dir, { recursive: true });
|
|
74
75
|
const written = [];
|
|
75
|
-
for (const agent of registry.specialists ??
|
|
76
|
+
for (const agent of Object.values(registry.specialists ?? {})) {
|
|
76
77
|
const card = buildRoleCard(agent, { rootDir: root });
|
|
77
78
|
const file = path.join(dir, `${card.specialistId}.role-card.json`);
|
|
78
79
|
fs.writeFileSync(file, `${JSON.stringify(card, null, 2)}\n`);
|
|
@@ -83,10 +84,10 @@ export function writeRoleCards({ rootDir } = {}) {
|
|
|
83
84
|
|
|
84
85
|
export function validateRoleCards({ rootDir } = {}) {
|
|
85
86
|
const root = findConstructRoot(rootDir);
|
|
86
|
-
const registry =
|
|
87
|
+
const registry = loadRegistry({ rootDir: root });
|
|
87
88
|
const errors = [];
|
|
88
89
|
const cards = [];
|
|
89
|
-
for (const agent of registry.specialists ??
|
|
90
|
+
for (const agent of Object.values(registry.specialists ?? {})) {
|
|
90
91
|
const specialistId = `cx-${agent.name}`;
|
|
91
92
|
const file = path.join(roleCardsDir(root), `${specialistId}.role-card.json`);
|
|
92
93
|
if (!fs.existsSync(file)) {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import fs from 'node:fs';
|
|
9
|
+
import { loadRegistry } from '../registry/loader.mjs';
|
|
9
10
|
import path from 'node:path';
|
|
10
11
|
|
|
11
12
|
import { splitFrontmatter } from '../specialists/prompt-schema.mjs';
|
|
@@ -21,7 +22,7 @@ const OVERLAY_CLASSES = Object.freeze({
|
|
|
21
22
|
function findConstructRoot(startPath = process.cwd()) {
|
|
22
23
|
let current = path.resolve(startPath);
|
|
23
24
|
while (true) {
|
|
24
|
-
if (fs.existsSync(path.join(current, 'specialists', '
|
|
25
|
+
if (fs.existsSync(path.join(current, 'specialists', 'org'))) return current;
|
|
25
26
|
const parent = path.dirname(current);
|
|
26
27
|
if (parent === current) return path.resolve(startPath);
|
|
27
28
|
current = parent;
|
|
@@ -29,12 +30,11 @@ function findConstructRoot(startPath = process.cwd()) {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
function readRegistry(rootDir) {
|
|
32
|
-
|
|
33
|
-
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
33
|
+
return loadRegistry({ rootDir: findConstructRoot(rootDir) });
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function specialistIds(registry) {
|
|
37
|
-
return new Set((registry.specialists ??
|
|
37
|
+
return new Set(Object.values(registry.specialists ?? {}).map((s) => `cx-${s.name}`));
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
function countAntiPatterns(body) {
|
|
@@ -22,7 +22,7 @@ import { measurePromptBudgetChains } from './prompt-budget.mjs';
|
|
|
22
22
|
import { validateAllArtifactProvenance } from './artifact-provenance.mjs';
|
|
23
23
|
import { validateDocumentWorkflowCertification } from './document-workflow.mjs';
|
|
24
24
|
import { buildDemoParityReport } from './demo-parity.mjs';
|
|
25
|
-
import { runRealLlmS3, runRealLlmS8 } from './real-llm-scenarios.mjs';
|
|
25
|
+
import { redactRealLlmGateEvidence, runRealLlmS3, runRealLlmS8 } from './real-llm-scenarios.mjs';
|
|
26
26
|
import { decidePromotion } from '../evals/gates.mjs';
|
|
27
27
|
import { buildScenarioEvaluationReport, runScenarioEvalGates } from './eval-bridge.mjs';
|
|
28
28
|
import {
|
|
@@ -106,7 +106,14 @@ async function runGate(gate, { root, scenario }) {
|
|
|
106
106
|
const runner = gate.scenario === 's8' ? runRealLlmS8 : runRealLlmS3;
|
|
107
107
|
const result = await runner({ env: process.env });
|
|
108
108
|
if (result.status === 'inconclusive') {
|
|
109
|
-
|
|
109
|
+
const evidence = redactRealLlmGateEvidence(result);
|
|
110
|
+
return {
|
|
111
|
+
id: gate.id,
|
|
112
|
+
pass: false,
|
|
113
|
+
detail: result.skip ?? result.detail ?? 'inconclusive',
|
|
114
|
+
...(evidence ? { evidence } : {}),
|
|
115
|
+
inconclusive: true,
|
|
116
|
+
};
|
|
110
117
|
}
|
|
111
118
|
return { id: gate.id, pass: result.status === 'pass', detail: result.detail ?? result.status };
|
|
112
119
|
}
|