@geraldmaron/construct 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -18
- package/bin/construct +392 -91
- package/bin/construct-postinstall.mjs +30 -2
- package/examples/distribution/README.md +42 -0
- package/examples/distribution/manifest.json +53 -0
- package/examples/distribution/sources/adr.md +84 -0
- package/examples/distribution/sources/deck-one-pager.md +65 -0
- package/examples/distribution/sources/prd-platform.md +161 -0
- package/examples/distribution/sources/research-brief.md +80 -0
- package/examples/distribution/sources/rfc-platform.md +82 -0
- package/examples/distribution/sources/runbook.md +103 -0
- package/examples/distribution/sources/strategy.md +88 -0
- package/lib/adapters-sync.mjs +67 -0
- package/lib/artifact-gate-notice.mjs +38 -0
- package/lib/artifact-manifest.mjs +112 -0
- package/lib/artifact-release-gate.mjs +171 -0
- package/lib/artifact-reviewers.mjs +68 -0
- package/lib/artifact-type-from-path.mjs +79 -0
- package/lib/audit-skills.mjs +4 -0
- package/lib/audit-specialists.mjs +285 -0
- package/lib/audit-trail.mjs +77 -10
- package/lib/auto-docs.mjs +87 -18
- package/lib/beads/drift.mjs +27 -6
- package/lib/beads-client.mjs +49 -121
- package/lib/beads-optimistic.mjs +5 -12
- package/lib/brand-fonts.mjs +93 -0
- package/lib/brand-prose.mjs +214 -0
- package/lib/brand-tokens.mjs +92 -0
- package/lib/bridges/copilot-proxy.mjs +13 -26
- package/lib/capability-ledger.mjs +156 -0
- package/lib/certification/artifact-fixtures.mjs +132 -0
- package/lib/certification/artifact-gates.mjs +63 -0
- package/lib/certification/artifact-provenance.mjs +97 -0
- package/lib/certification/canonical-scenarios.mjs +78 -0
- package/lib/certification/cli.mjs +191 -0
- package/lib/certification/dashboard-api.mjs +71 -0
- package/lib/certification/demo-parity.mjs +116 -0
- package/lib/certification/document-io-fixtures.mjs +246 -0
- package/lib/certification/document-workflow.mjs +97 -0
- package/lib/certification/eval-bridge.mjs +77 -0
- package/lib/certification/model-routing.mjs +149 -0
- package/lib/certification/prompt-budget.mjs +119 -0
- package/lib/certification/rc-gate.mjs +206 -0
- package/lib/certification/real-llm-scenarios.mjs +303 -0
- package/lib/certification/role-cards.mjs +113 -0
- package/lib/certification/role-overlays.mjs +117 -0
- package/lib/certification/run.mjs +122 -0
- package/lib/certification/runner.mjs +323 -0
- package/lib/certification/scenarios.mjs +51 -0
- package/lib/certification/skill-inventory.mjs +289 -0
- package/lib/certification/skill-scenarios.mjs +147 -0
- package/lib/certification/specialist-contracts.mjs +85 -0
- package/lib/certification/specialist-scenarios.mjs +175 -0
- package/lib/certification/stale-impact.mjs +146 -0
- package/lib/certification/status.mjs +252 -0
- package/lib/certification/store.mjs +77 -0
- package/lib/chat/cli.mjs +333 -0
- package/lib/chat/command-suggest.mjs +161 -0
- package/lib/chat/commands.mjs +215 -0
- package/lib/chat/config.mjs +142 -0
- package/lib/chat/context-compactor.mjs +250 -0
- package/lib/chat/context-continuation.mjs +253 -0
- package/lib/chat/continuation-source.mjs +58 -0
- package/lib/chat/demo-guide.mjs +61 -0
- package/lib/chat/design-tokens.mjs +91 -0
- package/lib/chat/desktop-binary.mjs +79 -0
- package/lib/chat/desktop-build.mjs +130 -0
- package/lib/chat/desktop-launcher.mjs +133 -0
- package/lib/chat/evidence.mjs +145 -0
- package/lib/chat/export.mjs +74 -0
- package/lib/chat/harness/driver.mjs +91 -0
- package/lib/chat/list-picker.mjs +112 -0
- package/lib/chat/model-picker.mjs +356 -0
- package/lib/chat/openrouter-fallback.mjs +151 -0
- package/lib/chat/permission-prompt.mjs +33 -0
- package/lib/chat/picker-catalog.mjs +45 -0
- package/lib/chat/policy-telemetry.mjs +34 -0
- package/lib/chat/present.mjs +245 -0
- package/lib/chat/session-context.mjs +39 -0
- package/lib/chat/session-persist.mjs +73 -0
- package/lib/chat/session-restore.mjs +71 -0
- package/lib/chat/session-settings.mjs +53 -0
- package/lib/chat/system-prompt.mjs +52 -0
- package/lib/chat/transparency.mjs +93 -0
- package/lib/chat/tui/color-scheme.mjs +42 -0
- package/lib/chat/tui/markdown.mjs +123 -0
- package/lib/chat/tui/presentation.mjs +100 -0
- package/lib/chat/tui/render.mjs +500 -0
- package/lib/chat/tui/turn-block.mjs +284 -0
- package/lib/chat/tui/turn-present.mjs +18 -0
- package/lib/chat/tui/turn-state.mjs +88 -0
- package/lib/chat/tui/usage.mjs +122 -0
- package/lib/chat/web-commands.mjs +146 -0
- package/lib/chat/web-launcher.mjs +63 -0
- package/lib/chat/web-picker-keys.mjs +46 -0
- package/lib/chat/web-session.mjs +159 -0
- package/lib/cli-commands.mjs +232 -11
- package/lib/comment-lint.mjs +6 -4
- package/lib/config/schema.mjs +36 -5
- package/lib/contract-schemas/decision.json +50 -0
- package/lib/contract-schemas/implementation.json +51 -0
- package/lib/contract-schemas/review-report.json +32 -0
- package/lib/contract-schemas/test-report.json +43 -0
- package/lib/contracts/construct-handoff.mjs +60 -0
- package/lib/contracts/validate.mjs +32 -3
- package/lib/contracts/violation-log.mjs +58 -5
- package/lib/dashboard-demo.mjs +71 -0
- package/lib/dashboard-static.mjs +19 -5
- package/lib/decisions/registry.mjs +5 -3
- package/lib/deck-export-pptx.mjs +1152 -0
- package/lib/demo-recording.mjs +142 -0
- package/lib/demo-script.mjs +114 -0
- package/lib/demo-surface.mjs +249 -0
- package/lib/demo.mjs +703 -0
- package/lib/diagram-export.mjs +192 -0
- package/lib/diagram.mjs +302 -0
- package/lib/docs-verify.mjs +25 -7
- package/lib/doctor/index.mjs +4 -2
- package/lib/doctor/project-adapters.mjs +44 -0
- package/lib/doctor/watchers/cx-budget.mjs +98 -0
- package/lib/doctor/watchers/graph-staleness.mjs +52 -0
- package/lib/doctor/watchers/process-pressure.mjs +4 -3
- package/lib/doctor/watchers/service-health.mjs +1 -1
- package/lib/document-export.mjs +360 -36
- package/lib/document-extract.mjs +88 -0
- package/lib/document-ingest.mjs +32 -18
- package/lib/embed/cli.mjs +2 -2
- package/lib/embed/daemon.mjs +18 -46
- package/lib/embed/semantic.mjs +5 -2
- package/lib/embed/worker.mjs +2 -2
- package/lib/embedded-contract/model-resolve.mjs +10 -9
- package/lib/env-config.mjs +26 -0
- package/lib/evals/dataset.mjs +137 -0
- package/lib/evals/gates.mjs +175 -0
- package/lib/graph/build-co-change.mjs +54 -0
- package/lib/graph/build-from-registry.mjs +136 -0
- package/lib/graph/build-import-graph.mjs +153 -0
- package/lib/graph/cli.mjs +110 -0
- package/lib/graph/impact-cli.mjs +89 -0
- package/lib/graph/impact.mjs +108 -0
- package/lib/graph/staleness.mjs +44 -0
- package/lib/graph/store.mjs +172 -0
- package/lib/handoffs/cleanup.mjs +1 -1
- package/lib/health-check.mjs +90 -76
- package/lib/hooks/artifact-release-gate.mjs +43 -0
- package/lib/hooks/audit-trail.mjs +14 -28
- package/lib/hooks/brand-prose-lint.mjs +38 -0
- package/lib/hooks/graph-impact-advisory.mjs +62 -0
- package/lib/hooks/session-optimize.mjs +84 -207
- package/lib/hooks/session-start.mjs +4 -5
- package/lib/host-disposition.mjs +7 -0
- package/lib/improvement/cli.mjs +189 -0
- package/lib/improvement/controller.mjs +137 -0
- package/lib/improvement/proposal.mjs +120 -0
- package/lib/improvement/specialist-loop.mjs +192 -0
- package/lib/improvement/store.mjs +89 -0
- package/lib/improvement/surface.mjs +219 -0
- package/lib/ingest-tooling.mjs +97 -0
- package/lib/init/doc-lanes.mjs +165 -0
- package/lib/init-docs.mjs +31 -178
- package/lib/init-unified.mjs +27 -97
- package/lib/init-update-guide.mjs +102 -0
- package/lib/init-update.mjs +32 -1
- package/lib/init.mjs +8 -0
- package/lib/install/desktop-binary-download.mjs +85 -0
- package/lib/install/legacy-global-cleanup.mjs +189 -0
- package/lib/intake/daemon.mjs +2 -0
- package/lib/intake/git-queue.mjs +9 -8
- package/lib/intake/queue.mjs +2 -1
- package/lib/intake/session-prelude.mjs +90 -1
- package/lib/libreoffice-export.mjs +97 -0
- package/lib/logging/rotate.mjs +9 -0
- package/lib/maintenance/docker-reclaim.mjs +206 -0
- package/lib/mcp/external-schema-cost.mjs +74 -0
- package/lib/mcp/server.mjs +84 -9
- package/lib/mcp/stdio-mcp-probe.mjs +188 -0
- package/lib/mcp/tool-budget.mjs +55 -4
- package/lib/mcp/tools/document.mjs +18 -4
- package/lib/mcp/tools/skills.mjs +32 -3
- package/lib/mcp/tools/workflow.mjs +13 -1
- package/lib/model-free-selector.mjs +18 -0
- package/lib/model-registry.mjs +13 -229
- package/lib/model-router.mjs +425 -93
- package/lib/models/behavior-matrix.mjs +289 -0
- package/lib/models/catalog.mjs +209 -0
- package/lib/models/execution-capability-profile.mjs +196 -0
- package/lib/models/execution-policy.mjs +307 -0
- package/lib/models/provider-poll.mjs +383 -0
- package/lib/npm-spawn-env.mjs +17 -0
- package/lib/ollama/installed-models.mjs +129 -0
- package/lib/oracle/actions.mjs +187 -0
- package/lib/oracle/artifact-gate.mjs +99 -0
- package/lib/oracle/cli.mjs +204 -0
- package/lib/oracle/daemon-entry.mjs +14 -0
- package/lib/oracle/dispatch.mjs +81 -0
- package/lib/oracle/execute.mjs +143 -0
- package/lib/oracle/gaps.mjs +76 -0
- package/lib/oracle/index.mjs +84 -0
- package/lib/oracle/issues.mjs +164 -0
- package/lib/oracle/org-graph.mjs +170 -0
- package/lib/oracle/policy.mjs +80 -0
- package/lib/oracle/read-model.mjs +398 -0
- package/lib/oracle/reconcile.mjs +191 -0
- package/lib/oracle/routing.mjs +89 -0
- package/lib/oracle/synthesize.mjs +384 -0
- package/lib/oracle/verdicts.mjs +51 -0
- package/lib/orchestration/worker.mjs +88 -27
- package/lib/orchestration-policy.mjs +50 -0
- package/lib/parity.mjs +96 -2
- package/lib/persona-sections.mjs +66 -0
- package/lib/playwright-demo.mjs +252 -0
- package/lib/project-init-shared.mjs +4 -1
- package/lib/prompt-composer.js +9 -4
- package/lib/prompt-validation-contract.mjs +24 -0
- package/lib/provider-capabilities.js +57 -11
- package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
- package/lib/providers/contract/adapters/git/index.mjs +115 -0
- package/lib/providers/contract/adapters/github/index.mjs +166 -0
- package/lib/providers/contract/adapters/jira/index.mjs +187 -0
- package/lib/providers/contract/adapters/slack/index.mjs +175 -0
- package/lib/providers/contract/contract-tests.mjs +57 -0
- package/lib/providers/contract/errors.mjs +48 -0
- package/lib/providers/contract/interface.mjs +50 -0
- package/lib/providers/contract/registry.mjs +102 -0
- package/lib/providers/copilot-auth.mjs +297 -0
- package/lib/providers/credential-bootstrap.mjs +178 -0
- package/lib/providers/credential-catalog.mjs +46 -0
- package/lib/providers/credential-sources.mjs +63 -0
- package/lib/providers/creds.mjs +5 -2
- package/lib/providers/op-run.mjs +59 -0
- package/lib/providers/secret-resolver.mjs +159 -0
- package/lib/publish-template.mjs +163 -0
- package/lib/publish-tooling.mjs +119 -0
- package/lib/publish.mjs +305 -0
- package/lib/registry/cli.mjs +82 -0
- package/lib/registry/consolidation.mjs +147 -0
- package/lib/registry/generate-docs.mjs +75 -0
- package/lib/registry/skill-verification.mjs +57 -0
- package/lib/registry/surface-map.mjs +76 -0
- package/lib/registry/validate.mjs +135 -0
- package/lib/resources/budget.mjs +82 -0
- package/lib/resources/process-budget.mjs +45 -0
- package/lib/rules-delivery.mjs +9 -2
- package/lib/rules-read.mjs +26 -0
- package/lib/runtime-env.mjs +23 -0
- package/lib/runtime-pressure.mjs +51 -7
- package/lib/schema-infer.mjs +13 -2
- package/lib/server/chat-loop.mjs +622 -0
- package/lib/server/demo-preview.mjs +63 -0
- package/lib/server/index.mjs +229 -8
- package/lib/server/langfuse-login.mjs +3 -3
- package/lib/service-manager.mjs +61 -9
- package/lib/session-store.mjs +1 -1
- package/lib/setup.mjs +102 -2
- package/lib/specialists/prompt-schema.mjs +19 -10
- package/lib/specialists/roster.mjs +43 -0
- package/lib/specialists/scaffold.mjs +56 -0
- package/lib/storage/backend.mjs +6 -6
- package/lib/storage/embeddings-local.mjs +6 -3
- package/lib/storage/file-lock.mjs +18 -11
- package/lib/storage/hybrid-query.mjs +7 -4
- package/lib/storage/state-source.mjs +5 -5
- package/lib/storage/sync.mjs +2 -3
- package/lib/telemetry/rule-calls.mjs +52 -0
- package/lib/template-registry.mjs +2 -2
- package/lib/templates/visual-requirements.mjs +27 -51
- package/lib/test-corpus-inventory.mjs +313 -0
- package/lib/uninstall/uninstall.mjs +19 -7
- package/lib/update.mjs +12 -0
- package/lib/upgrade.mjs +14 -0
- package/lib/wireframe.mjs +20 -14
- package/lib/worker/run.mjs +17 -4
- package/lib/worker/trace.mjs +11 -3
- package/package.json +29 -13
- package/personas/construct.md +13 -13
- package/platforms/claude/settings.template.json +36 -0
- package/rules/common/patterns.md +1 -1
- package/rules/common/release-gates.md +4 -3
- package/scripts/sync-specialists.mjs +187 -60
- package/skills/devops/data-engineering.md +1 -1
- package/skills/docs/adr-workflow.md +1 -0
- package/skills/docs/backlog-proposal-workflow.md +1 -0
- package/skills/docs/codebase-research-workflow.md +40 -0
- package/skills/docs/customer-profile-workflow.md +1 -0
- package/skills/docs/document-ingest-workflow.md +1 -0
- package/skills/docs/evidence-ingest-workflow.md +1 -0
- package/skills/docs/init-docs.md +2 -2
- package/skills/docs/init-project.md +8 -3
- package/skills/docs/prd-workflow.md +24 -1
- package/skills/docs/prfaq-workflow.md +1 -0
- package/skills/docs/product-intelligence-workflow.md +1 -0
- package/skills/docs/product-signal-workflow.md +1 -0
- package/skills/docs/research-workflow.md +54 -37
- package/skills/docs/runbook-workflow.md +1 -0
- package/skills/docs/strategy-workflow.md +1 -0
- package/skills/docs/user-research-workflow.md +40 -0
- package/skills/operating/orchestration-reference.md +1 -1
- package/skills/roles/architect.md +5 -0
- package/skills/roles/operator.docs.md +4 -0
- package/skills/routing.md +4 -2
- package/specialists/artifact-manifest.json +480 -0
- package/specialists/artifact-manifest.schema.json +53 -0
- package/specialists/audit-enrichments.json +454 -0
- package/specialists/contracts.json +37 -25
- package/specialists/prompts/_shared/validation-contract.md +26 -0
- package/specialists/prompts/cx-accessibility.md +21 -2
- package/specialists/prompts/cx-ai-engineer.md +24 -1
- package/specialists/prompts/cx-architect.md +4 -1
- package/specialists/prompts/cx-business-strategist.md +23 -2
- package/specialists/prompts/cx-data-analyst.md +22 -1
- package/specialists/prompts/cx-data-engineer.md +23 -2
- package/specialists/prompts/cx-debugger.md +21 -2
- package/specialists/prompts/cx-designer.md +26 -3
- package/specialists/prompts/cx-devil-advocate.md +19 -2
- package/specialists/prompts/cx-docs-keeper.md +28 -1
- package/specialists/prompts/cx-engineer.md +22 -1
- package/specialists/prompts/cx-evaluator.md +18 -1
- package/specialists/prompts/cx-explorer.md +21 -2
- package/specialists/prompts/cx-legal-compliance.md +21 -2
- package/specialists/prompts/cx-operations.md +21 -2
- package/specialists/prompts/cx-oracle.md +94 -0
- package/specialists/prompts/cx-orchestrator.md +20 -1
- package/specialists/prompts/cx-platform-engineer.md +25 -2
- package/specialists/prompts/cx-product-manager.md +32 -1
- package/specialists/prompts/cx-qa.md +24 -2
- package/specialists/prompts/cx-rd-lead.md +23 -2
- package/specialists/prompts/cx-release-manager.md +23 -2
- package/specialists/prompts/cx-researcher.md +22 -2
- package/specialists/prompts/cx-reviewer.md +18 -1
- package/specialists/prompts/cx-security.md +22 -2
- package/specialists/prompts/cx-sre.md +30 -3
- package/specialists/prompts/cx-test-automation.md +7 -1
- package/specialists/prompts/cx-trace-reviewer.md +22 -3
- package/specialists/prompts/cx-ux-researcher.md +21 -2
- package/specialists/registry.json +52 -173
- package/specialists/tone-profiles.json +42 -0
- package/templates/demos/playwright/demo-recording.config.mjs +47 -0
- package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
- package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
- package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
- package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
- package/templates/demos/tapes/resource-guard-rails.tape +49 -0
- package/templates/demos/vhs/construct-cockpit.json +24 -0
- package/templates/distribution/construct-brand.typ +446 -0
- package/templates/distribution/construct-decision.typ +38 -0
- package/templates/distribution/construct-deck.html +95 -0
- package/templates/distribution/construct-pdf.typ +38 -0
- package/templates/distribution/construct-prd.typ +38 -0
- package/templates/distribution/construct-research.typ +38 -0
- package/templates/distribution/construct-web.html +92 -0
- package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
- package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
- package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
- package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/README.md +36 -0
- package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
- package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
- package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
- package/templates/distribution/icons/activity.svg +15 -0
- package/templates/distribution/icons/alert-triangle.svg +17 -0
- package/templates/distribution/icons/book-open.svg +16 -0
- package/templates/distribution/icons/bot.svg +20 -0
- package/templates/distribution/icons/brain.svg +22 -0
- package/templates/distribution/icons/circle-check.svg +16 -0
- package/templates/distribution/icons/clipboard-check.svg +17 -0
- package/templates/distribution/icons/cpu.svg +28 -0
- package/templates/distribution/icons/database.svg +17 -0
- package/templates/distribution/icons/eye.svg +16 -0
- package/templates/distribution/icons/file-text.svg +19 -0
- package/templates/distribution/icons/gauge.svg +16 -0
- package/templates/distribution/icons/git-branch.svg +17 -0
- package/templates/distribution/icons/key.svg +17 -0
- package/templates/distribution/icons/layers.svg +17 -0
- package/templates/distribution/icons/list-checks.svg +19 -0
- package/templates/distribution/icons/lock.svg +16 -0
- package/templates/distribution/icons/message-square.svg +15 -0
- package/templates/distribution/icons/network.svg +19 -0
- package/templates/distribution/icons/route.svg +17 -0
- package/templates/distribution/icons/scale.svg +19 -0
- package/templates/distribution/icons/search.svg +16 -0
- package/templates/distribution/icons/send.svg +16 -0
- package/templates/distribution/icons/server.svg +18 -0
- package/templates/distribution/icons/shield-check.svg +16 -0
- package/templates/distribution/icons/users.svg +18 -0
- package/templates/distribution/icons/webhook.svg +17 -0
- package/templates/distribution/icons/workflow.svg +17 -0
- package/templates/distribution/icons/wrench.svg +15 -0
- package/templates/distribution/run.mjs +18 -1
- package/templates/docs/adr.md +7 -0
- package/templates/docs/construct_guide.md +10 -10
- package/templates/docs/customer-profile.md +4 -0
- package/templates/docs/one-pager.md +4 -0
- package/templates/docs/postmortem.md +31 -0
- package/templates/docs/prd-platform.md +19 -0
- package/templates/docs/prd.md +15 -0
- package/templates/docs/prfaq.md +7 -0
- package/templates/docs/qa-strategy.md +103 -0
- package/templates/docs/research-brief.md +8 -0
- package/templates/docs/rfc-platform.md +12 -0
- package/templates/docs/test-plan.md +7 -0
- package/lib/ingest/chunker.mjs +0 -94
- package/lib/ingest/pipeline.mjs +0 -53
- package/lib/ingest/store.mjs +0 -82
- package/lib/mode-commands.mjs +0 -122
- package/lib/policy/unified-gates.mjs +0 -96
- package/lib/profiles/validate-custom.mjs +0 -114
- package/lib/services/telemetry-backend.mjs +0 -177
- package/lib/storage/fusion.mjs +0 -95
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Distribution example — operational runbook with diagnostic flow and severity table.
|
|
3
|
+
artifactType: runbook
|
|
4
|
+
subtitle: Publish pipeline degraded — Pandoc or figure tooling missing
|
|
5
|
+
version: "1.1"
|
|
6
|
+
doc_id: RUNBOOK-PUBLISH-001
|
|
7
|
+
classification: internal
|
|
8
|
+
status: accepted
|
|
9
|
+
owner: cx-sre
|
|
10
|
+
last_verified_at: 2026-06-22
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Runbook: construct publish / export failures
|
|
14
|
+
|
|
15
|
+
- **Service**: document-export (`construct publish`, `construct export`)
|
|
16
|
+
- **Owner**: platform on-call
|
|
17
|
+
- **Last tested**: 2026-06-22
|
|
18
|
+
- **Severity**: SEV-2 when exports block release; SEV-3 when examples only
|
|
19
|
+
|
|
20
|
+
## Alert trigger
|
|
21
|
+
|
|
22
|
+
`construct publish` exits 2 with `missing: pandoc` or `Figure tooling` in `construct tools detect --figures` output. CI job `release:check` fails on export certification scenarios.
|
|
23
|
+
|
|
24
|
+
## Symptoms
|
|
25
|
+
|
|
26
|
+
- PDF export fails before Typst invocation
|
|
27
|
+
- `--figures` renders empty diagram placeholders
|
|
28
|
+
- `npm run examples:distribution` reports partial failures in summary
|
|
29
|
+
|
|
30
|
+
## Impact
|
|
31
|
+
|
|
32
|
+
| Audience | Impact |
|
|
33
|
+
|---|---|
|
|
34
|
+
| Release managers | Cannot ship branded PRDs/ADRs |
|
|
35
|
+
| Developers | Local previews blocked |
|
|
36
|
+
| Agents | `publish_run` MCP tool returns remediation hints |
|
|
37
|
+
|
|
38
|
+
## Severity and response
|
|
39
|
+
|
|
40
|
+
| Severity | Trigger condition | Page within | Comms | Error budget |
|
|
41
|
+
|----------|-------------------|-------------|-------|--------------|
|
|
42
|
+
| SEV-1 | All publish paths down in CI | 5 min | #incident + status | breach → freeze |
|
|
43
|
+
| SEV-2 | PDF down, HTML OK | 15 min | #platform | partial spend |
|
|
44
|
+
| SEV-3 | Examples script only | business hours | team channel | none |
|
|
45
|
+
|
|
46
|
+
## Diagnostic steps
|
|
47
|
+
|
|
48
|
+
1. Run `construct tools detect --figures --json` and read `steps.export` + `steps.figures`.
|
|
49
|
+
2. Confirm `pandoc` and `typst` on PATH; confirm `d2` and `mmdc` when `--figures` is set.
|
|
50
|
+
3. Re-run a single fixture: `node bin/construct export examples/distribution/sources/adr.md --to=pdf --figures`.
|
|
51
|
+
|
|
52
|
+
```mermaid
|
|
53
|
+
flowchart TD
|
|
54
|
+
A[Alert fires] --> B{export.present?}
|
|
55
|
+
B -->|No| C[Install pandoc + typst]
|
|
56
|
+
B -->|Yes| D{figures.present?}
|
|
57
|
+
D -->|No| E[brew install d2; npm i -g @mermaid-js/mermaid-cli]
|
|
58
|
+
D -->|Yes| F[Re-run examples:distribution]
|
|
59
|
+
C --> F
|
|
60
|
+
E --> F
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```d2
|
|
64
|
+
direction: down
|
|
65
|
+
|
|
66
|
+
detect: tools detect
|
|
67
|
+
pandoc: pandoc + typst
|
|
68
|
+
figures: d2 + mmdc
|
|
69
|
+
ok: Green export
|
|
70
|
+
|
|
71
|
+
detect -> pandoc: if missing
|
|
72
|
+
detect -> figures: if --figures
|
|
73
|
+
pandoc -> ok
|
|
74
|
+
figures -> ok
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Remediation
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
brew install pandoc typst d2 graphviz
|
|
81
|
+
npm install -g @mermaid-js/mermaid-cli
|
|
82
|
+
npm run examples:distribution
|
|
83
|
+
open .tmp/distribution-examples/index.html
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Expected: index lists PDF and HTML links for each artifact type; no `FAILED` lines in script output.
|
|
87
|
+
|
|
88
|
+
## Rollback
|
|
89
|
+
|
|
90
|
+
Not applicable — this runbook restores tooling, not application state. If a bad template change caused failures, revert the Typst commit and re-run exports.
|
|
91
|
+
|
|
92
|
+
## Escalation
|
|
93
|
+
|
|
94
|
+
After 30 minutes without restore: page cx-platform-engineer. After 60 minutes with release blocked: page cx-release-manager.
|
|
95
|
+
|
|
96
|
+
## Post-incident
|
|
97
|
+
|
|
98
|
+
Preserve `construct tools detect --json` output and the failing pandoc/typst stderr in the incident ticket.
|
|
99
|
+
|
|
100
|
+
## References
|
|
101
|
+
|
|
102
|
+
- `docs/reference/document-io.md`
|
|
103
|
+
- `docs/cookbook/diagram-and-demo.md`
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Distribution example — strategy memo with goals, metrics, and landscape diagram.
|
|
3
|
+
artifactType: strategy
|
|
4
|
+
subtitle: Make Construct the default governed layer for agentic IDE workflows
|
|
5
|
+
version: "1.0"
|
|
6
|
+
doc_id: STRATEGY-2026-H1
|
|
7
|
+
classification: internal
|
|
8
|
+
status: draft
|
|
9
|
+
owner: cx-product-manager
|
|
10
|
+
last_verified_at: 2026-06-22
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Strategy: Governed agentic workflows for IDE-native teams
|
|
14
|
+
|
|
15
|
+
- **Date**: 2026-06-22
|
|
16
|
+
- **Owner**: cx-product-manager
|
|
17
|
+
- **Status**: draft
|
|
18
|
+
- **Horizon**: H1 2026 – H1 2027
|
|
19
|
+
|
|
20
|
+
> Platform and product teams want specialist routing without losing citation discipline, branded exports, or profile-aware terminology. Construct wins when evaluators can open one gallery of PDFs and see the same ink, typography, and diagram styling across document types.
|
|
21
|
+
|
|
22
|
+
## Problem
|
|
23
|
+
|
|
24
|
+
Host-native agents optimize for single-session chat. Enterprise platform teams need durable invoke plans, manifest-enforced artifacts, and distribution that looks intentional — not markdown attachments with host fonts.
|
|
25
|
+
|
|
26
|
+
## Goals
|
|
27
|
+
|
|
28
|
+
1. **Prove value in five minutes** — `npm run examples:distribution` produces a browsable gallery with figures.
|
|
29
|
+
2. **One brand contract** — `docs/reference/branding.md` + `03d-brand` audit ratchet.
|
|
30
|
+
3. **Profile-aware language** — intake queue labels follow active profile rebrand in CLI, session prelude, and dashboard API.
|
|
31
|
+
|
|
32
|
+
## Non-goals
|
|
33
|
+
|
|
34
|
+
Replacing host LLM execution. Mandating cloud render farms. Re-linting every historical doc for marketing voice.
|
|
35
|
+
|
|
36
|
+
## Landscape
|
|
37
|
+
|
|
38
|
+
```mermaid
|
|
39
|
+
flowchart TB
|
|
40
|
+
subgraph hosts [IDE hosts]
|
|
41
|
+
CC[Claude Code]
|
|
42
|
+
OC[OpenCode]
|
|
43
|
+
CX[Cursor]
|
|
44
|
+
end
|
|
45
|
+
subgraph construct [Construct]
|
|
46
|
+
R[Router]
|
|
47
|
+
V[Validate]
|
|
48
|
+
P[Publish]
|
|
49
|
+
end
|
|
50
|
+
hosts --> R --> V --> P
|
|
51
|
+
P --> PDF[Branded PDF]
|
|
52
|
+
P --> HTML[HTML / deck]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```d2
|
|
56
|
+
direction: down
|
|
57
|
+
|
|
58
|
+
construct: Construct {
|
|
59
|
+
style.fill: "#f3f4f6"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
hosts: IDE hosts
|
|
63
|
+
validate: Release gate
|
|
64
|
+
export: Branded export
|
|
65
|
+
|
|
66
|
+
hosts -> construct -> validate -> export
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Success metrics
|
|
70
|
+
|
|
71
|
+
| Metric | Baseline | Target |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| Brand audit findings | unknown | 0 in `03d-brand` |
|
|
74
|
+
| Example types with figures | 1 (PRD) | ≥6 layouts |
|
|
75
|
+
| Dashboard rebrand coverage | partial | intake API + pages |
|
|
76
|
+
|
|
77
|
+
## Risks and mitigations
|
|
78
|
+
|
|
79
|
+
| Risk | Mitigation |
|
|
80
|
+
|---|---|
|
|
81
|
+
| Toolchain missing on contributor laptops | `construct tools detect --figures` in runbook |
|
|
82
|
+
| Template drift | Functional tests on `construct-brand.typ` + HTML decks |
|
|
83
|
+
| Profile label regressions | `intake-rebrand.functional.test.mjs` |
|
|
84
|
+
|
|
85
|
+
## References
|
|
86
|
+
|
|
87
|
+
- `examples/distribution/manifest.json`
|
|
88
|
+
- https://langchain-ai.github.io/langgraph/ (accessed 2026-06-22)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/adapters-sync.mjs — project adapter sync for the Construct tool repo and
|
|
3
|
+
* `npm run adapters`. Detects installed hosts, runs sync-specialists --project,
|
|
4
|
+
* and is invoked from postinstall when developing inside the package itself.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import { existsSync } from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
|
|
12
|
+
import { detectHostCapabilities } from './host-capabilities.mjs';
|
|
13
|
+
import { isConstructPackageRepo } from './host-disposition.mjs';
|
|
14
|
+
import { stageProjectAdapters } from './install/stage-project.mjs';
|
|
15
|
+
|
|
16
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const PKG_ROOT = path.resolve(MODULE_DIR, '..');
|
|
18
|
+
|
|
19
|
+
const HOST_ID_MAP = {
|
|
20
|
+
'Claude Code': 'claude',
|
|
21
|
+
OpenCode: 'opencode',
|
|
22
|
+
Codex: 'codex',
|
|
23
|
+
'VS Code': 'vscode',
|
|
24
|
+
Cursor: 'cursor',
|
|
25
|
+
Copilot: 'copilot',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function resolveAdapterHosts({ forceAll = false, extra = [] } = {}) {
|
|
29
|
+
if (forceAll) return ['claude', 'opencode', 'codex', 'vscode', 'cursor'];
|
|
30
|
+
const hosts = new Set(extra);
|
|
31
|
+
for (const entry of detectHostCapabilities()) {
|
|
32
|
+
if (entry.availability !== 'installed') continue;
|
|
33
|
+
const id = HOST_ID_MAP[entry.host];
|
|
34
|
+
if (id) hosts.add(id);
|
|
35
|
+
}
|
|
36
|
+
if (hosts.size === 0) hosts.add('claude');
|
|
37
|
+
return [...hosts];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function syncProjectAdapters({
|
|
41
|
+
projectRoot = process.cwd(),
|
|
42
|
+
packageRoot = PKG_ROOT,
|
|
43
|
+
hosts = null,
|
|
44
|
+
log = () => {},
|
|
45
|
+
} = {}) {
|
|
46
|
+
const resolvedHosts = hosts ?? resolveAdapterHosts({ forceAll: isConstructPackageRepo(projectRoot) });
|
|
47
|
+
return stageProjectAdapters({
|
|
48
|
+
projectRoot,
|
|
49
|
+
packageRoot,
|
|
50
|
+
pkgVersion: null,
|
|
51
|
+
log,
|
|
52
|
+
hosts: resolvedHosts,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function runAdaptersScript({ cwd = process.cwd(), hosts = null } = {}) {
|
|
57
|
+
const result = syncProjectAdapters({ projectRoot: cwd, hosts, log: (m) => process.stdout.write(`[adapters] ${m}\n`) });
|
|
58
|
+
return result.synced ? 0 : 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
62
|
+
const args = new Set(process.argv.slice(2));
|
|
63
|
+
const forceAll = args.has('--all-hosts');
|
|
64
|
+
const hosts = forceAll ? resolveAdapterHosts({ forceAll: true }) : null;
|
|
65
|
+
const code = runAdaptersScript({ cwd: process.cwd(), hosts });
|
|
66
|
+
process.exit(code);
|
|
67
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/artifact-gate-notice.mjs — Shared PostToolUse artifact structure/visual check.
|
|
3
|
+
*
|
|
4
|
+
* Returns violations for typed docs under docs/** and .cx/research/** without
|
|
5
|
+
* applying TTY/CI suppression (callers decide whether to emit).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { inferArtifactTypeFromPath, isArtifactGatePath } from './artifact-type-from-path.mjs';
|
|
10
|
+
import { lintDocStructure, lintDocVisuals } from './templates/visual-requirements.mjs';
|
|
11
|
+
|
|
12
|
+
export function checkArtifactGateNotice(filePath, { cwd = process.cwd() } = {}) {
|
|
13
|
+
const rel = filePath.startsWith(cwd)
|
|
14
|
+
? filePath.slice(cwd.length).replace(/^\/+/, '')
|
|
15
|
+
: filePath;
|
|
16
|
+
if (!isArtifactGatePath(rel)) return null;
|
|
17
|
+
|
|
18
|
+
const type = inferArtifactTypeFromPath(filePath, { rootDir: cwd });
|
|
19
|
+
if (!type) return null;
|
|
20
|
+
|
|
21
|
+
const errors = [
|
|
22
|
+
...lintDocStructure(filePath, type),
|
|
23
|
+
...lintDocVisuals(filePath, type),
|
|
24
|
+
];
|
|
25
|
+
if (errors.length === 0) return null;
|
|
26
|
+
|
|
27
|
+
return { type, rel, errors };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function formatArtifactGateNotice({ type, rel, errors }) {
|
|
31
|
+
const lines = [
|
|
32
|
+
`\nartifact-release-gate (${type}): ${errors.length} structure/visual issue(s). `
|
|
33
|
+
+ 'Fix before calling the artifact done; full gate: '
|
|
34
|
+
+ `\`construct artifact validate ${rel} --type=${type}\`\n`,
|
|
35
|
+
];
|
|
36
|
+
for (const e of errors) lines.push(` - ${e}\n`);
|
|
37
|
+
return lines.join('');
|
|
38
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/artifact-manifest.mjs — Load and resolve the artifact capability manifest.
|
|
3
|
+
*
|
|
4
|
+
* specialists/artifact-manifest.json is the single source of truth for document
|
|
5
|
+
* type metadata: templates, tone, structure, visuals, and release gates.
|
|
6
|
+
* Consumer projects without a local copy resolve the shipped manifest from the
|
|
7
|
+
* Construct package root.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
|
|
14
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
15
|
+
const EMPTY_MANIFEST = { version: 1, artifacts: {} };
|
|
16
|
+
|
|
17
|
+
let cached = null;
|
|
18
|
+
let cachedRoot = null;
|
|
19
|
+
|
|
20
|
+
function manifestPathForRoot(root) {
|
|
21
|
+
return path.join(root, 'specialists', 'artifact-manifest.json');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function findConstructRoot(startPath = process.cwd()) {
|
|
25
|
+
let current = path.resolve(startPath);
|
|
26
|
+
while (true) {
|
|
27
|
+
const manifest = manifestPathForRoot(current);
|
|
28
|
+
if (fs.existsSync(manifest)) return current;
|
|
29
|
+
const parent = path.dirname(current);
|
|
30
|
+
if (parent === current) break;
|
|
31
|
+
current = parent;
|
|
32
|
+
}
|
|
33
|
+
if (fs.existsSync(manifestPathForRoot(PACKAGE_ROOT))) return PACKAGE_ROOT;
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function loadArtifactManifest({ rootDir, force = false, cwd = process.cwd() } = {}) {
|
|
38
|
+
const resolvedRoot = rootDir ?? findConstructRoot(cwd) ?? PACKAGE_ROOT;
|
|
39
|
+
if (cached && !force && cachedRoot === resolvedRoot) return cached;
|
|
40
|
+
|
|
41
|
+
const p = manifestPathForRoot(resolvedRoot);
|
|
42
|
+
if (!fs.existsSync(p)) {
|
|
43
|
+
cached = EMPTY_MANIFEST;
|
|
44
|
+
cachedRoot = resolvedRoot;
|
|
45
|
+
return cached;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
cached = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
49
|
+
cachedRoot = resolvedRoot;
|
|
50
|
+
return cached;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function getArtifactEntry(type, opts = {}) {
|
|
54
|
+
const manifest = loadArtifactManifest(opts);
|
|
55
|
+
return manifest.artifacts?.[type] ?? null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function artifactTypes(opts = {}) {
|
|
59
|
+
const manifest = loadArtifactManifest(opts);
|
|
60
|
+
return Object.keys(manifest.artifacts ?? {});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function structureRequirementsFromManifest(opts = {}) {
|
|
64
|
+
const manifest = loadArtifactManifest(opts);
|
|
65
|
+
const out = {};
|
|
66
|
+
for (const [type, entry] of Object.entries(manifest.artifacts ?? {})) {
|
|
67
|
+
if (entry.structureRequirements?.length) out[type] = entry.structureRequirements;
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function visualRequirementsFromManifest(opts = {}) {
|
|
73
|
+
const manifest = loadArtifactManifest(opts);
|
|
74
|
+
const out = {};
|
|
75
|
+
for (const [type, entry] of Object.entries(manifest.artifacts ?? {})) {
|
|
76
|
+
if (entry.visualRequirements?.length) out[type] = entry.visualRequirements;
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function loadBrandVoice(cwd = process.cwd()) {
|
|
82
|
+
const p = path.join(cwd, '.cx', 'brand-voice.json');
|
|
83
|
+
if (!fs.existsSync(p)) return null;
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function resolveToneForArtifact(type, { cwd = process.cwd(), rootDir } = {}) {
|
|
92
|
+
const entry = getArtifactEntry(type, { rootDir, cwd });
|
|
93
|
+
const brand = loadBrandVoice(cwd);
|
|
94
|
+
const override = brand?.toneOverride?.[type];
|
|
95
|
+
if (override) return override;
|
|
96
|
+
return entry?.toneDefault ?? 'direct';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function templateMetadata(type, { cwd = process.cwd(), rootDir } = {}) {
|
|
100
|
+
const entry = getArtifactEntry(type, { rootDir, cwd });
|
|
101
|
+
if (!entry) return null;
|
|
102
|
+
return {
|
|
103
|
+
type,
|
|
104
|
+
tone: resolveToneForArtifact(type, { cwd, rootDir }),
|
|
105
|
+
toneAllowed: entry.toneAllowed ?? [],
|
|
106
|
+
structureRequirements: entry.structureRequirements ?? [],
|
|
107
|
+
visualRequirements: entry.visualRequirements ?? [],
|
|
108
|
+
primaryOwners: entry.primaryOwners ?? [],
|
|
109
|
+
workflowSkill: entry.workflowSkill ?? null,
|
|
110
|
+
releaseGate: entry.releaseGate ?? null,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/artifact-release-gate.mjs — Real-org release validation for typed artifacts.
|
|
3
|
+
*
|
|
4
|
+
* Composes structure/visual lint, citation discipline, prose heuristics, and
|
|
5
|
+
* optional reviewer sign-off from the artifact manifest releaseGate block.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { lintDocStructure, lintDocVisuals } from './templates/visual-requirements.mjs';
|
|
11
|
+
import { getArtifactEntry, resolveToneForArtifact } from './artifact-manifest.mjs';
|
|
12
|
+
import { lintFile } from './comment-lint.mjs';
|
|
13
|
+
import {
|
|
14
|
+
missingRequiredReviewers,
|
|
15
|
+
parseReleaseGateFrontmatter,
|
|
16
|
+
} from './artifact-reviewers.mjs';
|
|
17
|
+
import { inferArtifactTypeFromPath } from './artifact-type-from-path.mjs';
|
|
18
|
+
|
|
19
|
+
function countProseParagraphs(body) {
|
|
20
|
+
const blocks = body.split(/\n\s*\n/).map((b) => b.trim()).filter(Boolean);
|
|
21
|
+
let prose = 0;
|
|
22
|
+
for (const b of blocks) {
|
|
23
|
+
const first = b.split('\n')[0].trimStart();
|
|
24
|
+
const isHeading = first.startsWith('#');
|
|
25
|
+
const isList = /^([-*+]\s|\d+[.)]\s|>\s)/.test(first);
|
|
26
|
+
const isTable = first.startsWith('|');
|
|
27
|
+
const isFence = first.startsWith('```');
|
|
28
|
+
if (isHeading || isList || isTable || isFence) continue;
|
|
29
|
+
const sentences = (b.match(/[.!?](\s|$)/g) || []).length;
|
|
30
|
+
if (sentences >= 2 || b.length >= 200) prose++;
|
|
31
|
+
}
|
|
32
|
+
return prose;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function countCitations(body) {
|
|
36
|
+
const patterns = [
|
|
37
|
+
/\bhttps?:\/\/[^\s)]+/g,
|
|
38
|
+
/\barxiv:\s*\d{4}\.\d{4,5}/gi,
|
|
39
|
+
/\[source:[^\]]+\]/gi,
|
|
40
|
+
/\(accessed\s+\d{4}-\d{2}-\d{2}\)/gi,
|
|
41
|
+
];
|
|
42
|
+
const hits = new Set();
|
|
43
|
+
for (const re of patterns) for (const m of body.match(re) || []) hits.add(m.trim().toLowerCase());
|
|
44
|
+
return hits.size;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function validateArtifactRelease({
|
|
48
|
+
filePath,
|
|
49
|
+
type,
|
|
50
|
+
cwd = process.cwd(),
|
|
51
|
+
rootDir,
|
|
52
|
+
reviewersSeen,
|
|
53
|
+
} = {}) {
|
|
54
|
+
const resolvedType = type || inferArtifactTypeFromPath(filePath, { rootDir: cwd });
|
|
55
|
+
const entry = getArtifactEntry(resolvedType, { rootDir });
|
|
56
|
+
if (!entry) {
|
|
57
|
+
return { ok: false, errors: [`Unknown artifact type: ${resolvedType}`], warnings: [], tone: null };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { bypass, reason } = parseReleaseGateFrontmatter(filePath);
|
|
61
|
+
if (bypass) {
|
|
62
|
+
if (!reason) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
errors: ['cx_release_gate: bypass requires cx_release_gate_reason in frontmatter'],
|
|
66
|
+
warnings: [],
|
|
67
|
+
tone: resolveToneForArtifact(resolvedType, { cwd, rootDir }),
|
|
68
|
+
type: resolvedType,
|
|
69
|
+
filePath,
|
|
70
|
+
bypassed: true,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
ok: true,
|
|
75
|
+
errors: [],
|
|
76
|
+
warnings: [`release gate bypassed: ${reason}`],
|
|
77
|
+
tone: resolveToneForArtifact(resolvedType, { cwd, rootDir }),
|
|
78
|
+
type: resolvedType,
|
|
79
|
+
filePath,
|
|
80
|
+
bypassed: true,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const gate = entry.releaseGate ?? {};
|
|
85
|
+
const errors = [];
|
|
86
|
+
const warnings = [];
|
|
87
|
+
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
|
+
|
|
95
|
+
if (gate.citationLint) {
|
|
96
|
+
const isShippedTemplate = filePath.includes(`${path.sep}templates${path.sep}docs${path.sep}`);
|
|
97
|
+
if (!isShippedTemplate) {
|
|
98
|
+
const lint = lintFile(filePath);
|
|
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,
|
|
121
|
+
cwd,
|
|
122
|
+
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
|
+
filePath,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function runArtifactValidateCli(args = []) {
|
|
141
|
+
const positional = args.filter((a) => !a.startsWith('--'));
|
|
142
|
+
const filePath = positional[0];
|
|
143
|
+
const typeArg = args.find((a) => a.startsWith('--type='));
|
|
144
|
+
const json = args.includes('--json');
|
|
145
|
+
|
|
146
|
+
if (!filePath || !typeArg) {
|
|
147
|
+
process.stderr.write('Usage: construct artifact validate <path> --type=<doc-type> [--json]\n');
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const type = typeArg.split('=')[1];
|
|
152
|
+
const result = validateArtifactRelease({ filePath, type });
|
|
153
|
+
|
|
154
|
+
if (json) {
|
|
155
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
156
|
+
} else {
|
|
157
|
+
process.stdout.write(`Artifact release gate: ${type}\n`);
|
|
158
|
+
process.stdout.write(` Tone: ${result.tone}\n`);
|
|
159
|
+
if (result.errors.length) {
|
|
160
|
+
process.stdout.write(` Errors (${result.errors.length}):\n`);
|
|
161
|
+
for (const e of result.errors) process.stdout.write(` - ${e}\n`);
|
|
162
|
+
}
|
|
163
|
+
if (result.warnings.length) {
|
|
164
|
+
process.stdout.write(` Warnings (${result.warnings.length}):\n`);
|
|
165
|
+
for (const w of result.warnings) process.stdout.write(` - ${w}\n`);
|
|
166
|
+
}
|
|
167
|
+
process.stdout.write(result.ok ? ' Result: PASS\n' : ' Result: FAIL\n');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!result.ok) process.exit(1);
|
|
171
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/artifact-reviewers.mjs — Required reviewer resolution from manifest + agent log.
|
|
3
|
+
*
|
|
4
|
+
* Shared by artifact release gate, contract postconditions, and Oracle read-model.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { getArtifactEntry } from './artifact-manifest.mjs';
|
|
10
|
+
import { inferArtifactTypeFromPath } from './artifact-type-from-path.mjs';
|
|
11
|
+
|
|
12
|
+
const RELEASE_GATE_BYPASS_FIELD = 'cx_release_gate';
|
|
13
|
+
const RELEASE_GATE_REASON_FIELD = 'cx_release_gate_reason';
|
|
14
|
+
|
|
15
|
+
export function readAgentLogReviewers(projectRoot = process.cwd()) {
|
|
16
|
+
const logPath = path.join(projectRoot, '.cx', 'agent-log.jsonl');
|
|
17
|
+
if (!fs.existsSync(logPath)) return new Set();
|
|
18
|
+
const reviewers = new Set();
|
|
19
|
+
for (const line of fs.readFileSync(logPath, 'utf8').split('\n')) {
|
|
20
|
+
if (!line.trim()) continue;
|
|
21
|
+
try {
|
|
22
|
+
const row = JSON.parse(line);
|
|
23
|
+
const id = row.agent || row.specialist;
|
|
24
|
+
if (id) reviewers.add(String(id));
|
|
25
|
+
} catch { /* skip */ }
|
|
26
|
+
}
|
|
27
|
+
return reviewers;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function parseReleaseGateFrontmatter(filePath) {
|
|
31
|
+
try {
|
|
32
|
+
const head = fs.readFileSync(filePath, 'utf8').slice(0, 4096);
|
|
33
|
+
const match = head.match(/^---\n([\s\S]*?)\n---/);
|
|
34
|
+
if (!match) return { bypass: false, reason: null };
|
|
35
|
+
const fm = {};
|
|
36
|
+
for (const line of match[1].split('\n')) {
|
|
37
|
+
const m = line.match(/^([\w-]+)\s*:\s*(.+)$/);
|
|
38
|
+
if (m) fm[m[1]] = m[2].trim().replace(/^['"]|['"]$/g, '');
|
|
39
|
+
}
|
|
40
|
+
const bypass = fm[RELEASE_GATE_BYPASS_FIELD] === 'bypass';
|
|
41
|
+
return { bypass, reason: fm[RELEASE_GATE_REASON_FIELD] ?? null };
|
|
42
|
+
} catch {
|
|
43
|
+
return { bypass: false, reason: null };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function resolveRequiredReviewers({ docType, filePath, rootDir, cwd = process.cwd() }) {
|
|
48
|
+
let type = docType;
|
|
49
|
+
if (!type && filePath) {
|
|
50
|
+
type = inferArtifactTypeFromPath(filePath, { rootDir: cwd });
|
|
51
|
+
}
|
|
52
|
+
if (!type) return [];
|
|
53
|
+
const entry = getArtifactEntry(type, { rootDir });
|
|
54
|
+
return entry?.releaseGate?.requiredReviewers ?? [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function missingRequiredReviewers({
|
|
58
|
+
docType,
|
|
59
|
+
filePath,
|
|
60
|
+
rootDir,
|
|
61
|
+
cwd = process.cwd(),
|
|
62
|
+
reviewersSeen,
|
|
63
|
+
} = {}) {
|
|
64
|
+
const required = resolveRequiredReviewers({ docType, filePath, rootDir, cwd });
|
|
65
|
+
if (required.length === 0) return [];
|
|
66
|
+
const seen = reviewersSeen ?? readAgentLogReviewers(cwd);
|
|
67
|
+
return required.filter((r) => !seen.has(r) && !seen.has(r.replace(/^cx-/, 'cx-')));
|
|
68
|
+
}
|