@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
|
@@ -26,7 +26,6 @@ import { fileURLToPath } from 'node:url';
|
|
|
26
26
|
import { stageProjectAdapters } from '../lib/install/stage-project.mjs';
|
|
27
27
|
import { syncProjectAdapters } from '../lib/adapters-sync.mjs';
|
|
28
28
|
import { missingIgnorePatterns, isConstructPackageRepo } from '../lib/host-disposition.mjs';
|
|
29
|
-
import { sanitizeNpmSpawnEnv } from '../lib/npm-spawn-env.mjs';
|
|
30
29
|
|
|
31
30
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
32
31
|
const PKG_ROOT = path.resolve(HERE, '..');
|
|
@@ -74,27 +73,7 @@ try {
|
|
|
74
73
|
} catch (err) {
|
|
75
74
|
fail(`Tool-repo adapter sync failed: ${err.message}`, 'Run `npm run adapters` manually.');
|
|
76
75
|
}
|
|
77
|
-
const chatBuild = spawnSync('npm', ['run', 'build:chat'], {
|
|
78
|
-
cwd: PKG_ROOT,
|
|
79
|
-
stdio: 'inherit',
|
|
80
|
-
env: sanitizeNpmSpawnEnv(process.env),
|
|
81
|
-
});
|
|
82
|
-
if (chatBuild.status !== 0) {
|
|
83
|
-
fail('Ink TUI build failed', 'Run `npm run build:chat` manually.');
|
|
84
|
-
} else {
|
|
85
|
-
log('built apps/chat/dist/tui.mjs');
|
|
86
|
-
}
|
|
87
76
|
|
|
88
|
-
const dashBuild = spawnSync('npm', ['run', 'build:dashboard'], {
|
|
89
|
-
cwd: PKG_ROOT,
|
|
90
|
-
stdio: 'inherit',
|
|
91
|
-
env: sanitizeNpmSpawnEnv(process.env),
|
|
92
|
-
});
|
|
93
|
-
if (dashBuild.status !== 0) {
|
|
94
|
-
fail('Dashboard build failed', 'Run `npm run build:dashboard` manually.');
|
|
95
|
-
} else {
|
|
96
|
-
log('built lib/server/static/ (dashboard)');
|
|
97
|
-
}
|
|
98
77
|
process.exit(0);
|
|
99
78
|
}
|
|
100
79
|
} catch { /* fall through */ }
|
|
@@ -12,7 +12,7 @@ Optimize prompt fragments and overlays, not the runtime orchestration policy. If
|
|
|
12
12
|
|
|
13
13
|
## Required steps
|
|
14
14
|
|
|
15
|
-
1. **Read current prompt**: read the agent's prompt from `specialists/
|
|
15
|
+
1. **Read current prompt**: read the agent's prompt from `specialists/org` (or its `promptFile`)
|
|
16
16
|
2. **Fetch recent scores**: GET `{CONSTRUCT_TELEMETRY_URL}/api/public/scores?name=quality&limit=200`
|
|
17
17
|
3. **Skip agents with fewer than 20 scored traces**: insufficient signal; note them but do not optimize
|
|
18
18
|
4. **Diagnose failure patterns**: analyze low-scoring traces, identify top 3 recurring patterns
|
|
@@ -19,11 +19,11 @@ last_verified_at: 2026-06-22
|
|
|
19
19
|
|
|
20
20
|
## Problem
|
|
21
21
|
|
|
22
|
-
Distribution exports (PDF, HTML, deck, PPTX) and
|
|
22
|
+
Distribution exports (PDF, HTML, deck, PPTX) and interactive host surfaces drifted when typography moved from retired font families to Space Grotesk and JetBrains Mono. Docs and templates cited stale names; some apps still declared unused npm font packages. Operators could not tell which file was canonical for brand behavior.
|
|
23
23
|
|
|
24
24
|
## Context
|
|
25
25
|
|
|
26
|
-
Construct ships a monochrome ink ramp and bundled OFL fonts under `templates/distribution/fonts/`.
|
|
26
|
+
Construct ships a monochrome ink ramp and bundled OFL fonts under `templates/distribution/fonts/`. Published artifacts and interactive hosts consume the same tokens via `lib/brand-tokens.mjs`. Profile-specific intake terminology flows through `lib/scopes/rebrand.mjs`.
|
|
27
27
|
|
|
28
28
|
```mermaid
|
|
29
29
|
flowchart LR
|
|
@@ -33,4 +33,4 @@ Hook guards for workflow, drive, bootstrap, and task behavior are expressed as Y
|
|
|
33
33
|
|
|
34
34
|
## Registry as Single Source of Truth
|
|
35
35
|
|
|
36
|
-
`specialists/
|
|
36
|
+
`specialists/org` is the canonical definition for all agents on all platforms (Claude Code, OpenCode, Codex, Copilot). `construct sync` regenerates all platform-specific files from the registry. Never edit generated files directly: edits are overwritten on the next sync.
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/artifact-loop-core.mjs — Surface-neutral Construct artifact loop core.
|
|
3
|
+
*
|
|
4
|
+
* Intent resolution, typed-artifact drafting helpers, workflow-plan invocation,
|
|
5
|
+
* on-disk materialization, and the release gate — with zero host UI imports so
|
|
6
|
+
* the MCP server and supported hosts share one author→materialize→validate path.
|
|
7
|
+
*/
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
import { findConstructRoot, getArtifactEntry } from './artifact-manifest.mjs';
|
|
12
|
+
import { validateArtifactRelease } from './artifact-release-gate.mjs';
|
|
13
|
+
import { detectDocAuthoringIntent, resolveDocTypeMention } from './orchestration-policy.mjs';
|
|
14
|
+
import { invokeWorkflow } from './embedded-contract/workflow-invoke.mjs';
|
|
15
|
+
import { getTemplate } from './mcp/tools/skills.mjs';
|
|
16
|
+
|
|
17
|
+
const LOOP_INTENT_RE = [
|
|
18
|
+
/\brun (?:it )?through (?:the )?(?:actual )?construct (?:artifact )?loop\b/i,
|
|
19
|
+
/\brun (?:this|that|it) through construct\b/i,
|
|
20
|
+
/\b(execute|run) (?:the )?(?:artifact|prd) (?:pipeline|workflow|gate)\b/i,
|
|
21
|
+
/\bconstruct(?:\s+artifact)?\s+loop\b/i,
|
|
22
|
+
/\bthrough the construct (?:artifact )?loop\b/i,
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const LOOP_DOC_GATE_RE = /\b(?:loop|release gate|artifact gate|artifact validate)\b/i;
|
|
26
|
+
const AUTHOR_VERBS_RE = /\b(write|writing|draft|drafting|create|creating|author|authoring|produce|producing|compose|composing|prepare|preparing|generate|generating)\b/i;
|
|
27
|
+
const VALIDATE_FOLLOWUP_RE = /\b(run|execute|push|send|put)\b.*\b(?:it|this|that|the draft)\b.*\b(?:through|via)\b/i;
|
|
28
|
+
const QUESTION_PREFIX_RE = /^\s*(what|how|why|when|where|who|which|is|are|does|do|can|could|should|would|tell me about|explain)\b/i;
|
|
29
|
+
|
|
30
|
+
const OUTPUT_DIR_BY_TYPE = {
|
|
31
|
+
prd: 'docs/specs/prd',
|
|
32
|
+
'prd-platform': 'docs/prd-platform',
|
|
33
|
+
'prd-business': 'docs/prd-business',
|
|
34
|
+
'meta-prd': 'docs/meta-prd',
|
|
35
|
+
adr: 'docs/decisions/adr',
|
|
36
|
+
rfc: 'docs/decisions/rfc',
|
|
37
|
+
'research-brief': '.cx/research',
|
|
38
|
+
'evidence-brief': '.cx/knowledge/internal/evidence-briefs',
|
|
39
|
+
runbook: 'docs/runbooks',
|
|
40
|
+
strategy: '.cx',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const WORKFLOW_BY_TYPE = {
|
|
44
|
+
prd: 'prd-draft',
|
|
45
|
+
'prd-platform': 'prd-draft',
|
|
46
|
+
'prd-business': 'prd-draft',
|
|
47
|
+
'meta-prd': 'prd-draft',
|
|
48
|
+
adr: 'architecture-review',
|
|
49
|
+
rfc: 'architecture-review',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export function lastAssistantBody(turnBlocks = []) {
|
|
53
|
+
for (let i = turnBlocks.length - 1; i >= 0; i--) {
|
|
54
|
+
const item = turnBlocks[i];
|
|
55
|
+
if (item?.kind === 'turn' && item.block?.assistant?.trim()) {
|
|
56
|
+
return item.block.assistant.trim();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function lastUserBody(turnBlocks = []) {
|
|
63
|
+
for (let i = turnBlocks.length - 1; i >= 0; i--) {
|
|
64
|
+
const item = turnBlocks[i];
|
|
65
|
+
if (item?.kind === 'turn' && item.block?.userText?.trim()) {
|
|
66
|
+
return item.block.userText.trim();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function combinedContext(text, turnBlocks) {
|
|
73
|
+
const parts = [String(text || ''), lastUserBody(turnBlocks), lastAssistantBody(turnBlocks)].filter(Boolean);
|
|
74
|
+
return parts.join('\n');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function mentionsConstructLoop(text) {
|
|
78
|
+
return LOOP_INTENT_RE.some((re) => re.test(text));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isQuestion(text) {
|
|
82
|
+
const t = String(text || '').trim();
|
|
83
|
+
return QUESTION_PREFIX_RE.test(t) || /\?\s*$/.test(t);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isValidateFollowUp(text) {
|
|
87
|
+
return VALIDATE_FOLLOWUP_RE.test(text)
|
|
88
|
+
|| /\b(?:run|execute)\b.*\b(?:artifact|release)\s+gate\b/i.test(text)
|
|
89
|
+
|| /\bvalidate (?:the )?(?:draft|artifact|prd)\b/i.test(text);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function hasSubstantialDraft(turnBlocks = []) {
|
|
93
|
+
const draft = lastAssistantBody(turnBlocks);
|
|
94
|
+
return Boolean(draft && normalizeDraftMarkdown(draft).length >= 400 && /^#\s+/m.test(normalizeDraftMarkdown(draft)));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Resolve whether the user wants to author+loop or validate an existing draft.
|
|
99
|
+
* Returns null when the message should follow the normal host path.
|
|
100
|
+
*/
|
|
101
|
+
export function resolveArtifactLoopRequest(text, { turnBlocks = [], explicit = false } = {}) {
|
|
102
|
+
const t = String(text || '').trim();
|
|
103
|
+
if (!t) return null;
|
|
104
|
+
|
|
105
|
+
const loopMention = explicit || mentionsConstructLoop(t);
|
|
106
|
+
const docIntent = detectDocAuthoringIntent(combinedContext(t, turnBlocks));
|
|
107
|
+
const hybridLoopDoc = !loopMention && Boolean(docIntent) && LOOP_DOC_GATE_RE.test(t);
|
|
108
|
+
|
|
109
|
+
if (!loopMention && !hybridLoopDoc) return null;
|
|
110
|
+
if (!explicit && isQuestion(t)) return null;
|
|
111
|
+
|
|
112
|
+
const artifactType = resolveLoopArtifactType(t, turnBlocks);
|
|
113
|
+
const hasDraft = hasSubstantialDraft(turnBlocks);
|
|
114
|
+
const wantsAuthor = Boolean(
|
|
115
|
+
explicit && t.replace(/^\/loop\s*/i, '').trim()
|
|
116
|
+
|| detectDocAuthoringIntent(t)
|
|
117
|
+
|| docIntent
|
|
118
|
+
|| AUTHOR_VERBS_RE.test(t),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (hasDraft && (isValidateFollowUp(t) || (!wantsAuthor && loopMention))) {
|
|
122
|
+
return { mode: 'validate', artifactType };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (wantsAuthor) {
|
|
126
|
+
return { mode: 'author', artifactType };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (hasDraft) {
|
|
130
|
+
return { mode: 'validate', artifactType };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function detectConstructLoopIntent(text, { turnBlocks = [], explicit = false } = {}) {
|
|
137
|
+
return resolveArtifactLoopRequest(text, { turnBlocks, explicit }) !== null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function resolveLoopArtifactType(text, turnBlocks) {
|
|
141
|
+
const blob = combinedContext(text, turnBlocks);
|
|
142
|
+
let type = detectDocAuthoringIntent(blob)?.docType || resolveDocTypeMention(blob) || 'prd';
|
|
143
|
+
const platformSignals = /\bplatform prd\b|\bteam mode\b|\bteam deployments\b|\benterprise\b|\boidc\b|\bsso\b|\bapi contract\b/i.test(blob);
|
|
144
|
+
if (platformSignals && (type === 'prd' || !type)) type = 'prd-platform';
|
|
145
|
+
if (/\bbusiness prd\b|\bmarket\b|\bpricing strategy\b/i.test(blob)) type = 'prd-business';
|
|
146
|
+
if (/\bmeta[\s-]prd\b/i.test(blob)) type = 'meta-prd';
|
|
147
|
+
return type;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function extractLoopSubject(userText) {
|
|
151
|
+
return String(userText || '')
|
|
152
|
+
.replace(/^\/loop\s*/i, '')
|
|
153
|
+
.replace(/\b(?:use |the )?construct(?:\s+artifact)?\s+loop\b/gi, '')
|
|
154
|
+
.replace(/\b(?:to|and)\s+(?:write|draft|create|generate|author|compose|produce|prepare)\b/gi, '')
|
|
155
|
+
.replace(/\b(?:write|draft|create|generate|author|compose|produce|prepare)\b/gi, '')
|
|
156
|
+
.replace(/\b(?:a|an|the)\s+/gi, ' ')
|
|
157
|
+
.replace(/\s{2,}/g, ' ')
|
|
158
|
+
.trim();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function buildArtifactAuthoringPrompt({
|
|
162
|
+
userText,
|
|
163
|
+
artifactType,
|
|
164
|
+
rootDir,
|
|
165
|
+
gateErrors = [],
|
|
166
|
+
} = {}) {
|
|
167
|
+
const root = rootDir || findConstructRoot();
|
|
168
|
+
const entry = getArtifactEntry(artifactType, { rootDir: root });
|
|
169
|
+
const subject = extractLoopSubject(userText) || 'the requested topic';
|
|
170
|
+
const sectionHeadings = (entry?.structureRequirements || [])
|
|
171
|
+
.map((section) => `## ${section}`)
|
|
172
|
+
.join('\n');
|
|
173
|
+
const visualLines = (entry?.visualRequirements || []).map((req) => {
|
|
174
|
+
if (req.check === 'artifact-has-mermaid') {
|
|
175
|
+
return `- Include a fenced \`\`\`mermaid\`\`\` block with a ${req.diagram || 'flowchart'} diagram`;
|
|
176
|
+
}
|
|
177
|
+
if (req.check === 'artifact-table-has-columns' && Array.isArray(req.columns)) {
|
|
178
|
+
return `- Include a markdown table with columns: ${req.columns.join(' | ')} (with at least one data row)`;
|
|
179
|
+
}
|
|
180
|
+
return `- Satisfy visual requirement: ${req.id}`;
|
|
181
|
+
});
|
|
182
|
+
let templateExcerpt = '';
|
|
183
|
+
try {
|
|
184
|
+
const tmpl = getTemplate({ name: artifactType }, { ROOT_DIR: root });
|
|
185
|
+
if (tmpl?.content) {
|
|
186
|
+
templateExcerpt = tmpl.content.split('\n').slice(0, 40).join('\n');
|
|
187
|
+
}
|
|
188
|
+
} catch { /* template excerpt is best-effort */ }
|
|
189
|
+
|
|
190
|
+
const gateBlock = gateErrors.length
|
|
191
|
+
? `\nPrevious draft failed the release gate. Fix every item:\n${gateErrors.map((e) => `- ${e}`).join('\n')}\n`
|
|
192
|
+
: '';
|
|
193
|
+
|
|
194
|
+
return `${String(userText || '').trim()}
|
|
195
|
+
|
|
196
|
+
Produce a complete ${artifactType} markdown document for: ${subject}.
|
|
197
|
+
${gateBlock}
|
|
198
|
+
Required structure (use these ## headings):
|
|
199
|
+
${sectionHeadings || '## Problem\n## Goals and non-goals\n## Success metrics'}
|
|
200
|
+
|
|
201
|
+
Visual requirements:
|
|
202
|
+
${visualLines.length ? visualLines.join('\n') : '- Include a mermaid flowchart and a metrics table when applicable'}
|
|
203
|
+
|
|
204
|
+
Content rules:
|
|
205
|
+
- Start with a single # title line
|
|
206
|
+
- Write at least three prose paragraphs (not lists/tables alone) across the document
|
|
207
|
+
- Use repo tools to find relevant paths before claiming facts; cite file paths or URLs
|
|
208
|
+
- Mark unsupported claims [unverified]
|
|
209
|
+
- Output only the document markdown (no preamble)
|
|
210
|
+
|
|
211
|
+
Template reference (shape only):
|
|
212
|
+
${templateExcerpt || '(no template excerpt)'}`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function slugify(title) {
|
|
216
|
+
return String(title)
|
|
217
|
+
.toLowerCase()
|
|
218
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
219
|
+
.replace(/^-|-$/g, '')
|
|
220
|
+
.slice(0, 56) || 'draft';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function extractTitle(draft, fallback = 'draft-artifact') {
|
|
224
|
+
if (!draft) return fallback;
|
|
225
|
+
const normalized = normalizeDraftMarkdown(draft);
|
|
226
|
+
const prd = normalized.match(/^#\s+(?:PRD:\s*)?(.+)$/m);
|
|
227
|
+
if (prd?.[1]) return prd[1].trim();
|
|
228
|
+
const h1 = normalized.match(/^#\s+(.+)$/m);
|
|
229
|
+
if (h1?.[1]) return h1[1].trim();
|
|
230
|
+
return fallback;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function todayStamp() {
|
|
234
|
+
return new Date().toISOString().slice(0, 10);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function outputPathForType(artifactType, slug, cwd) {
|
|
238
|
+
const relDir = OUTPUT_DIR_BY_TYPE[artifactType] || 'docs/specs/prd';
|
|
239
|
+
const fileName = artifactType === 'adr'
|
|
240
|
+
? `ADR-draft-${slug}.md`
|
|
241
|
+
: `${todayStamp()}-${slug}.md`;
|
|
242
|
+
return path.join(cwd, relDir, fileName);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function fillTemplate(template, { title, date }) {
|
|
246
|
+
return template
|
|
247
|
+
.replace(/\{title\}/g, title)
|
|
248
|
+
.replace(/\{YYYY-MM-DD\}/g, date)
|
|
249
|
+
.replace(/\{name\}/g, 'cx-product-manager');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function ensureFrontmatter(body, artifactType) {
|
|
253
|
+
if (/^---\r?\n[\s\S]*?\r?\n---/.test(body)) return body;
|
|
254
|
+
return `---
|
|
255
|
+
artifactType: ${artifactType}
|
|
256
|
+
status: draft
|
|
257
|
+
owner: cx-product-manager
|
|
258
|
+
last_verified_at: ${todayStamp()}
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
${body.trim()}\n`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function normalizeDraftMarkdown(draft) {
|
|
265
|
+
let text = String(draft || '').trim();
|
|
266
|
+
const fenced = text.match(/^```(?:markdown|md)?\s*\n([\s\S]*?)\n```\s*$/i);
|
|
267
|
+
if (fenced) text = fenced[1].trim();
|
|
268
|
+
return text;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function extractArtifactMarkdown(text) {
|
|
272
|
+
const normalized = normalizeDraftMarkdown(text);
|
|
273
|
+
if (!normalized) return null;
|
|
274
|
+
const idx = normalized.search(/^#\s+/m);
|
|
275
|
+
if (idx < 0) return null;
|
|
276
|
+
const doc = normalized.slice(idx).trim();
|
|
277
|
+
if (doc.length < 200) return null;
|
|
278
|
+
return doc;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function buildDraftBody({ artifactType, title, draftFromHost, draftMarkdown, rootDir, allowScaffold = true }) {
|
|
282
|
+
const source = draftMarkdown || (draftFromHost ? normalizeDraftMarkdown(draftFromHost) : null);
|
|
283
|
+
if (source && source.length >= 200 && /^#\s+/m.test(source)) {
|
|
284
|
+
return ensureFrontmatter(source, artifactType);
|
|
285
|
+
}
|
|
286
|
+
if (!allowScaffold) return null;
|
|
287
|
+
const tmpl = getTemplate({ name: artifactType }, { ROOT_DIR: rootDir });
|
|
288
|
+
if (tmpl?.content) {
|
|
289
|
+
return fillTemplate(tmpl.content, { title, date: todayStamp() });
|
|
290
|
+
}
|
|
291
|
+
return ensureFrontmatter(`# ${title}\n\n## Summary\n\nDraft scaffold — expand from workflow plan and evidence.\n`, artifactType);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export async function runConstructArtifactLoop({
|
|
295
|
+
text = '',
|
|
296
|
+
turnBlocks = [],
|
|
297
|
+
cwd = process.cwd(),
|
|
298
|
+
rootDir,
|
|
299
|
+
explicit = false,
|
|
300
|
+
artifactType: artifactTypeOverride,
|
|
301
|
+
draftMarkdown,
|
|
302
|
+
allowScaffold = true,
|
|
303
|
+
} = {}) {
|
|
304
|
+
const root = rootDir || findConstructRoot(cwd);
|
|
305
|
+
const draftFromHost = draftMarkdown ? null : lastAssistantBody(turnBlocks);
|
|
306
|
+
const resolvedDraft = draftMarkdown || extractArtifactMarkdown(draftFromHost);
|
|
307
|
+
const artifactType = artifactTypeOverride || resolveLoopArtifactType(text, turnBlocks);
|
|
308
|
+
const title = extractTitle(
|
|
309
|
+
resolvedDraft || draftFromHost,
|
|
310
|
+
explicit ? (extractLoopSubject(text) || 'draft-artifact') : 'draft-artifact',
|
|
311
|
+
);
|
|
312
|
+
const slug = slugify(title);
|
|
313
|
+
const outPath = outputPathForType(artifactType, slug, cwd);
|
|
314
|
+
const workflowType = WORKFLOW_BY_TYPE[artifactType] || 'prd-draft';
|
|
315
|
+
const input = [
|
|
316
|
+
`Artifact loop: ${artifactType}`,
|
|
317
|
+
`Title: ${title}`,
|
|
318
|
+
resolvedDraft || draftFromHost
|
|
319
|
+
? `Draft excerpt:\n${(resolvedDraft || draftFromHost).slice(0, 4000)}`
|
|
320
|
+
: String(text),
|
|
321
|
+
].join('\n\n');
|
|
322
|
+
|
|
323
|
+
const invokePlan = await invokeWorkflow({
|
|
324
|
+
workflowType,
|
|
325
|
+
input,
|
|
326
|
+
approvalMode: 'proposal-only',
|
|
327
|
+
trace: true,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
331
|
+
const body = buildDraftBody({
|
|
332
|
+
artifactType,
|
|
333
|
+
title,
|
|
334
|
+
draftFromHost,
|
|
335
|
+
draftMarkdown: resolvedDraft,
|
|
336
|
+
rootDir: root,
|
|
337
|
+
allowScaffold,
|
|
338
|
+
});
|
|
339
|
+
if (!body) {
|
|
340
|
+
const relPath = path.relative(cwd, outPath);
|
|
341
|
+
return {
|
|
342
|
+
ok: false,
|
|
343
|
+
artifactType,
|
|
344
|
+
path: null,
|
|
345
|
+
relPath,
|
|
346
|
+
title,
|
|
347
|
+
slug,
|
|
348
|
+
workflowType,
|
|
349
|
+
invokePlan,
|
|
350
|
+
validation: { ok: false, errors: ['No draft content to materialize'], warnings: [] },
|
|
351
|
+
summary: 'Release gate: not run — no draft content to materialize.',
|
|
352
|
+
draftMissing: true,
|
|
353
|
+
overlay: {
|
|
354
|
+
intent: 'doc-authoring',
|
|
355
|
+
workCategory: 'artifact-loop',
|
|
356
|
+
track: 'orchestrated',
|
|
357
|
+
specialists: (invokePlan?.selectedRoles || ['product-manager', 'architect']).map((r) => `cx-${r}`),
|
|
358
|
+
dispatchSummary: `artifact loop: ${artifactType} → validate`,
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
fs.writeFileSync(outPath, body.endsWith('\n') ? body : `${body}\n`);
|
|
363
|
+
|
|
364
|
+
const validation = validateArtifactRelease({
|
|
365
|
+
filePath: outPath,
|
|
366
|
+
type: artifactType,
|
|
367
|
+
cwd,
|
|
368
|
+
rootDir: root,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
const relPath = path.relative(cwd, outPath);
|
|
372
|
+
const gateStatus = validation.ok
|
|
373
|
+
? 'PASS'
|
|
374
|
+
: `FAIL (${validation.errors.length} error${validation.errors.length === 1 ? '' : 's'})`;
|
|
375
|
+
const summary = validation.ok
|
|
376
|
+
? [
|
|
377
|
+
`Wrote ${relPath} (${artifactType}).`,
|
|
378
|
+
`Release gate: ${gateStatus}`,
|
|
379
|
+
invokePlan?.selectedRoles?.length
|
|
380
|
+
? `Workflow plan (${workflowType}): ${invokePlan.selectedRoles.map((r) => `cx-${r}`).join(' → ')}`
|
|
381
|
+
: `Workflow plan (${workflowType}): ${invokePlan?.status || 'proposed'}`,
|
|
382
|
+
'Next: address gate findings, run specialists per plan, then `construct publish` if distributing.',
|
|
383
|
+
].join('\n')
|
|
384
|
+
: [
|
|
385
|
+
`Draft failed release gate for ${relPath} (${artifactType}).`,
|
|
386
|
+
`Release gate: ${gateStatus}`,
|
|
387
|
+
invokePlan?.selectedRoles?.length
|
|
388
|
+
? `Workflow plan (${workflowType}): ${invokePlan.selectedRoles.map((r) => `cx-${r}`).join(' → ')}`
|
|
389
|
+
: `Workflow plan (${workflowType}): ${invokePlan?.status || 'proposed'}`,
|
|
390
|
+
'Next: fix gate errors below, re-run `/loop` or `construct artifact validate`.',
|
|
391
|
+
].join('\n');
|
|
392
|
+
|
|
393
|
+
return {
|
|
394
|
+
ok: validation.ok,
|
|
395
|
+
artifactType,
|
|
396
|
+
path: outPath,
|
|
397
|
+
relPath,
|
|
398
|
+
title,
|
|
399
|
+
slug,
|
|
400
|
+
workflowType,
|
|
401
|
+
invokePlan,
|
|
402
|
+
validation,
|
|
403
|
+
summary,
|
|
404
|
+
overlay: {
|
|
405
|
+
intent: 'doc-authoring',
|
|
406
|
+
workCategory: 'artifact-loop',
|
|
407
|
+
track: 'orchestrated',
|
|
408
|
+
specialists: (invokePlan?.selectedRoles || ['product-manager', 'architect']).map((r) => `cx-${r}`),
|
|
409
|
+
dispatchSummary: `artifact loop: ${artifactType} → validate`,
|
|
410
|
+
},
|
|
411
|
+
};
|
|
412
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import fs from 'node:fs';
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { resolveActiveScope } from './scopes/loader.mjs';
|
|
13
14
|
|
|
14
15
|
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
15
16
|
const EMPTY_MANIFEST = { version: 1, artifacts: {} };
|
|
@@ -233,6 +234,11 @@ export function resolveToneForArtifact(type, { cwd = process.cwd(), rootDir } =
|
|
|
233
234
|
const brand = loadBrandVoice(cwd);
|
|
234
235
|
const override = brand?.toneOverride?.[type];
|
|
235
236
|
if (override) return override;
|
|
237
|
+
try {
|
|
238
|
+
const scope = resolveActiveScope(cwd);
|
|
239
|
+
const scopeTone = scope?.toneDefaults?.[type];
|
|
240
|
+
if (scopeTone) return scopeTone;
|
|
241
|
+
} catch { /* scope optional */ }
|
|
236
242
|
return entry?.toneDefault ?? 'direct';
|
|
237
243
|
}
|
|
238
244
|
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
import fs from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
|
+
import os from 'node:os';
|
|
10
11
|
import { lintDocStructure, lintDocVisuals } from './templates/visual-requirements.mjs';
|
|
12
|
+
import { lintDocPresentationFile } from './templates/doc-presentation.mjs';
|
|
11
13
|
import { getArtifactEntry, resolveToneForArtifact } from './artifact-manifest.mjs';
|
|
12
14
|
import { lintFile } from './comment-lint.mjs';
|
|
13
15
|
import {
|
|
@@ -44,6 +46,112 @@ function countCitations(body) {
|
|
|
44
46
|
return hits.size;
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
export function dedupeGateErrors(errors = []) {
|
|
50
|
+
return [...new Set((errors || []).filter(Boolean))];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function validateArtifactBodyCore({
|
|
54
|
+
body,
|
|
55
|
+
resolvedType,
|
|
56
|
+
entry,
|
|
57
|
+
cwd,
|
|
58
|
+
rootDir,
|
|
59
|
+
reviewersSeen,
|
|
60
|
+
filePath = null,
|
|
61
|
+
}) {
|
|
62
|
+
const gate = entry.releaseGate ?? {};
|
|
63
|
+
const errors = [];
|
|
64
|
+
const warnings = [];
|
|
65
|
+
const rawBody = String(body || '');
|
|
66
|
+
const stripped = rawBody.replace(/^---\n[\s\S]*?\n---\n/, '');
|
|
67
|
+
const bodyWithoutComments = stripped.replace(/<!--[\s\S]*?-->/g, '');
|
|
68
|
+
|
|
69
|
+
if (gate.structuralLint !== false && filePath) {
|
|
70
|
+
errors.push(...lintDocStructure(filePath, resolvedType));
|
|
71
|
+
errors.push(...lintDocVisuals(filePath, resolvedType));
|
|
72
|
+
const presentation = lintDocPresentationFile(filePath, resolvedType);
|
|
73
|
+
errors.push(...presentation.errors);
|
|
74
|
+
warnings.push(...presentation.warnings);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (gate.citationLint) {
|
|
78
|
+
const isShippedTemplate = filePath?.includes(`${path.sep}templates${path.sep}docs${path.sep}`);
|
|
79
|
+
if (!isShippedTemplate) {
|
|
80
|
+
if (filePath) {
|
|
81
|
+
const lint = lintFile(filePath);
|
|
82
|
+
const artifactIssues = [...(lint.errors ?? []), ...(lint.warnings ?? [])].filter((w) => w.kind === 'artifact');
|
|
83
|
+
for (const issue of artifactIssues) errors.push(issue.message || String(issue));
|
|
84
|
+
}
|
|
85
|
+
const citations = countCitations(bodyWithoutComments);
|
|
86
|
+
const unverified = /\[unverified\]/i.test(bodyWithoutComments);
|
|
87
|
+
if (citations < 1 && !unverified) {
|
|
88
|
+
errors.push('citationLint: no verifiable sources or [unverified] markers');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const minProse = gate.proseMinimum ?? 0;
|
|
94
|
+
if (minProse > 0) {
|
|
95
|
+
const prose = countProseParagraphs(bodyWithoutComments);
|
|
96
|
+
if (prose < minProse) {
|
|
97
|
+
errors.push(`proseMinimum: ${prose} paragraphs (need ${minProse})`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (filePath) {
|
|
102
|
+
const missing = missingRequiredReviewers({
|
|
103
|
+
docType: resolvedType,
|
|
104
|
+
filePath,
|
|
105
|
+
rootDir,
|
|
106
|
+
cwd,
|
|
107
|
+
reviewersSeen,
|
|
108
|
+
});
|
|
109
|
+
if (missing.length > 0) {
|
|
110
|
+
warnings.push(`requiredReviewers not seen in agent log: ${missing.join(', ')}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
ok: dedupeGateErrors(errors).length === 0,
|
|
116
|
+
errors: dedupeGateErrors(errors),
|
|
117
|
+
warnings,
|
|
118
|
+
tone: resolveToneForArtifact(resolvedType, { cwd, rootDir }),
|
|
119
|
+
type: resolvedType,
|
|
120
|
+
filePath,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function validateArtifactBody({
|
|
125
|
+
body,
|
|
126
|
+
type,
|
|
127
|
+
cwd = process.cwd(),
|
|
128
|
+
rootDir,
|
|
129
|
+
reviewersSeen,
|
|
130
|
+
} = {}) {
|
|
131
|
+
const resolvedType = type;
|
|
132
|
+
const entry = getArtifactEntry(resolvedType, { rootDir });
|
|
133
|
+
if (!entry) {
|
|
134
|
+
return { ok: false, errors: [`Unknown artifact type: ${resolvedType}`], warnings: [], tone: null };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cx-artifact-gate-'));
|
|
138
|
+
const filePath = path.join(tmpDir, 'draft.md');
|
|
139
|
+
try {
|
|
140
|
+
fs.writeFileSync(filePath, body.endsWith('\n') ? body : `${body}\n`);
|
|
141
|
+
return validateArtifactBodyCore({
|
|
142
|
+
body,
|
|
143
|
+
resolvedType,
|
|
144
|
+
entry,
|
|
145
|
+
cwd,
|
|
146
|
+
rootDir,
|
|
147
|
+
reviewersSeen,
|
|
148
|
+
filePath,
|
|
149
|
+
});
|
|
150
|
+
} finally {
|
|
151
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
47
155
|
export function validateArtifactRelease({
|
|
48
156
|
filePath,
|
|
49
157
|
type,
|
|
@@ -81,60 +189,18 @@ export function validateArtifactRelease({
|
|
|
81
189
|
};
|
|
82
190
|
}
|
|
83
191
|
|
|
84
|
-
const gate = entry.releaseGate ?? {};
|
|
85
|
-
const errors = [];
|
|
86
|
-
const warnings = [];
|
|
87
192
|
const raw = fs.readFileSync(filePath, 'utf8');
|
|
88
|
-
const body = raw.replace(/^---\n[\s\S]*?\n---\n/, '');
|
|
89
|
-
|
|
90
|
-
if (gate.structuralLint !== false) {
|
|
91
|
-
errors.push(...lintDocStructure(filePath, resolvedType));
|
|
92
|
-
errors.push(...lintDocVisuals(filePath, resolvedType));
|
|
93
|
-
}
|
|
94
193
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const artifactIssues = [...(lint.errors ?? []), ...(lint.warnings ?? [])].filter((w) => w.kind === 'artifact');
|
|
100
|
-
for (const issue of artifactIssues) errors.push(issue.message || String(issue));
|
|
101
|
-
const citations = countCitations(body);
|
|
102
|
-
const unverified = /\[unverified\]/i.test(body);
|
|
103
|
-
if (citations < 1 && !unverified) {
|
|
104
|
-
errors.push('citationLint: no verifiable sources or [unverified] markers');
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const minProse = gate.proseMinimum ?? 0;
|
|
110
|
-
if (minProse > 0) {
|
|
111
|
-
const prose = countProseParagraphs(body);
|
|
112
|
-
if (prose < minProse) {
|
|
113
|
-
errors.push(`proseMinimum: ${prose} paragraphs (need ${minProse})`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const missing = missingRequiredReviewers({
|
|
118
|
-
docType: resolvedType,
|
|
119
|
-
filePath,
|
|
120
|
-
rootDir,
|
|
194
|
+
const result = validateArtifactBodyCore({
|
|
195
|
+
body: raw,
|
|
196
|
+
resolvedType,
|
|
197
|
+
entry,
|
|
121
198
|
cwd,
|
|
199
|
+
rootDir,
|
|
122
200
|
reviewersSeen,
|
|
123
|
-
});
|
|
124
|
-
if (missing.length > 0) {
|
|
125
|
-
warnings.push(`requiredReviewers not seen in agent log: ${missing.join(', ')}`);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const tone = resolveToneForArtifact(resolvedType, { cwd, rootDir });
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
ok: errors.length === 0,
|
|
132
|
-
errors,
|
|
133
|
-
warnings,
|
|
134
|
-
tone,
|
|
135
|
-
type: resolvedType,
|
|
136
201
|
filePath,
|
|
137
|
-
};
|
|
202
|
+
});
|
|
203
|
+
return { ...result, filePath };
|
|
138
204
|
}
|
|
139
205
|
|
|
140
206
|
export async function runArtifactValidateCli(args = []) {
|