@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,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/doctor/watchers/cx-budget.mjs — .cx/ disk budget maintenance.
|
|
3
|
+
*
|
|
4
|
+
* When project .cx/ usage crosses the configured cap, runs age/count prune.
|
|
5
|
+
* When still over cap after retention prune, emergency-reclaims oldest
|
|
6
|
+
* traces and worker logs (hard-reject categories only).
|
|
7
|
+
*
|
|
8
|
+
* Tick: 15 min.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync } from 'node:fs';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
|
|
14
|
+
import { record } from '../audit.mjs';
|
|
15
|
+
import { escalate } from '../escalate.mjs';
|
|
16
|
+
import {
|
|
17
|
+
measureUsage,
|
|
18
|
+
planPrune,
|
|
19
|
+
executePrune,
|
|
20
|
+
planEmergencyReclaim,
|
|
21
|
+
} from '../../resources/budget.mjs';
|
|
22
|
+
|
|
23
|
+
export const name = 'cx-budget';
|
|
24
|
+
export const intervalMs = 15 * 60 * 1000;
|
|
25
|
+
|
|
26
|
+
function projectRoot() {
|
|
27
|
+
return process.env.CONSTRUCT_PROJECT_ROOT || process.cwd();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function tick() {
|
|
31
|
+
const actions = [];
|
|
32
|
+
const escalations = [];
|
|
33
|
+
const root = projectRoot();
|
|
34
|
+
const cxDir = join(root, '.cx');
|
|
35
|
+
if (!existsSync(cxDir)) {
|
|
36
|
+
return { actions, escalations, notes: [{ skipped: 'no .cx directory' }] };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const before = measureUsage(root, process.env);
|
|
40
|
+
if (before.totalCxUsageRatio <= 0.8) {
|
|
41
|
+
return { actions, escalations, notes: [{ usageRatio: before.totalCxUsageRatio }] };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const planned = planPrune(root, process.env);
|
|
45
|
+
let reclaimed = { removed: [], bytesFreed: 0 };
|
|
46
|
+
if (planned.length) {
|
|
47
|
+
reclaimed = executePrune(planned);
|
|
48
|
+
record({
|
|
49
|
+
kind: 'action',
|
|
50
|
+
watcher: name,
|
|
51
|
+
action: 'prune',
|
|
52
|
+
target: '.cx/',
|
|
53
|
+
summary: `retention prune removed ${reclaimed.removed.length} file(s), freed ${reclaimed.bytesFreed} bytes`,
|
|
54
|
+
context: { planned: planned.length, removed: reclaimed.removed.length },
|
|
55
|
+
});
|
|
56
|
+
actions.push({ type: 'prune', target: '.cx/', removed: reclaimed.removed.length });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const afterPrune = measureUsage(root, process.env);
|
|
60
|
+
if (afterPrune.totalCxUsageRatio > 1) {
|
|
61
|
+
const emergency = planEmergencyReclaim(root, 0, process.env);
|
|
62
|
+
if (emergency.length) {
|
|
63
|
+
const emergencyResult = executePrune(emergency);
|
|
64
|
+
record({
|
|
65
|
+
kind: 'action',
|
|
66
|
+
watcher: name,
|
|
67
|
+
action: 'emergency-reclaim',
|
|
68
|
+
target: '.cx/traces|.cx/runtime/worker',
|
|
69
|
+
summary: `emergency reclaim removed ${emergencyResult.removed.length} file(s), freed ${emergencyResult.bytesFreed} bytes`,
|
|
70
|
+
context: emergencyResult,
|
|
71
|
+
});
|
|
72
|
+
actions.push({ type: 'emergency-reclaim', removed: emergencyResult.removed.length });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const after = measureUsage(root, process.env);
|
|
77
|
+
if (after.totalCxUsageRatio > 1) {
|
|
78
|
+
const usedMb = Math.round(after.totalCxBytes / 1024 / 1024);
|
|
79
|
+
const capMb = Math.round(after.totalCxCap / 1024 / 1024);
|
|
80
|
+
const r = await escalate({
|
|
81
|
+
watcher: name,
|
|
82
|
+
eventType: 'service.down',
|
|
83
|
+
summary: `.cx/ still over cap after prune (${usedMb}MB / ${capMb}MB) — raise resources.disk.totalCxMaxMb or delete load-bearing state manually`,
|
|
84
|
+
context: { usageRatio: after.totalCxUsageRatio, usedMb, capMb },
|
|
85
|
+
});
|
|
86
|
+
escalations.push({ eventType: 'service.down', result: r });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
actions,
|
|
91
|
+
escalations,
|
|
92
|
+
notes: [{
|
|
93
|
+
usageBefore: before.totalCxUsageRatio,
|
|
94
|
+
usageAfter: after.totalCxUsageRatio,
|
|
95
|
+
bytesFreed: reclaimed.bytesFreed,
|
|
96
|
+
}],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/doctor/watchers/graph-staleness.mjs — dependency graph freshness watcher.
|
|
3
|
+
*
|
|
4
|
+
* Ticks every 30 minutes. When registry/contracts/workflow seeds changed since
|
|
5
|
+
* the last `construct graph build`, records an audit entry so doctor surfaces
|
|
6
|
+
* stale-graph drift without waiting for the next Oracle tick.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { existsSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
|
|
12
|
+
import { record } from '../audit.mjs';
|
|
13
|
+
import { checkGraphStaleness } from '../../graph/staleness.mjs';
|
|
14
|
+
import { isConstructPackageRepo } from '../../host-disposition.mjs';
|
|
15
|
+
|
|
16
|
+
export const name = 'graph-staleness';
|
|
17
|
+
export const intervalMs = 30 * 60 * 1000;
|
|
18
|
+
|
|
19
|
+
function projectRoot() {
|
|
20
|
+
return process.env.CONSTRUCT_PROJECT_ROOT || process.cwd();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function tick() {
|
|
24
|
+
const actions = [];
|
|
25
|
+
const escalations = [];
|
|
26
|
+
const root = projectRoot();
|
|
27
|
+
const graphDir = join(root, '.cx', 'graph');
|
|
28
|
+
|
|
29
|
+
if (!existsSync(graphDir) && !isConstructPackageRepo(root)) {
|
|
30
|
+
return { actions, escalations, notes: [{ skipped: 'no graph and not construct package repo' }] };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const state = checkGraphStaleness(root);
|
|
34
|
+
if (!state.present) {
|
|
35
|
+
return { actions, escalations, notes: [{ present: false }] };
|
|
36
|
+
}
|
|
37
|
+
if (!state.stale) {
|
|
38
|
+
return { actions, escalations, notes: [{ present: true, stale: false }] };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
record({
|
|
42
|
+
kind: 'action',
|
|
43
|
+
watcher: name,
|
|
44
|
+
action: 'graph-stale',
|
|
45
|
+
target: '.cx/graph/',
|
|
46
|
+
summary: state.staleReason,
|
|
47
|
+
context: { storedHash: state.storedHash, currentHash: state.currentHash },
|
|
48
|
+
});
|
|
49
|
+
actions.push({ type: 'graph-stale', target: '.cx/graph/' });
|
|
50
|
+
|
|
51
|
+
return { actions, escalations, notes: [state] };
|
|
52
|
+
}
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* lib/doctor/watchers/process-pressure.mjs — continuous process pressure guard.
|
|
3
3
|
*
|
|
4
4
|
* Runs the runtime-pressure cleanup on a recurring tick so stale Construct
|
|
5
|
-
* helpers don't accumulate between
|
|
6
|
-
* process is recorded; if the same
|
|
7
|
-
*
|
|
5
|
+
* helpers and leaked VHS demo-recorder processes don't accumulate between
|
|
6
|
+
* `construct dev` invocations. Any killed process is recorded; if the same
|
|
7
|
+
* service-class has been killed >=2 times in the last hour, escalation lets
|
|
8
|
+
* cx-sre investigate the underlying churn.
|
|
8
9
|
*
|
|
9
10
|
* Tick: 60s.
|
|
10
11
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/doctor/watchers/service-health.mjs — continuous service probes.
|
|
3
3
|
*
|
|
4
|
-
* Probes the services `construct
|
|
4
|
+
* Probes the services `construct dev` starts (postgres, dashboard,
|
|
5
5
|
* cm, opencode). On 2 consecutive probe failures, attempts a restart of
|
|
6
6
|
* docker-backed services (postgres). On 2 consecutive failed
|
|
7
7
|
* restarts, escalates `service.down` for cx-sre. Non-docker services log +
|
package/lib/document-export.mjs
CHANGED
|
@@ -1,32 +1,135 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/document-export.mjs —
|
|
3
|
-
* binaries (Pandoc + Typst
|
|
2
|
+
* lib/document-export.mjs — artifact export to PDF / DOCX / DOC / deck / PPTX / HTML /
|
|
3
|
+
* RTF / ODT / EPUB / LaTeX / TXT / MD / MDX via external binaries (Pandoc + Typst,
|
|
4
|
+
* LibreOffice for legacy .doc, pptxgenjs for PPTX), per ADR-0024.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* the process boundary; Typst is the Apache-2.0 PDF engine driven through
|
|
9
|
-
* Pandoc's `--pdf-engine=typst` flag, so a single Pandoc invocation produces
|
|
10
|
-
* every target format the contract supports.
|
|
11
|
-
*
|
|
12
|
-
* detect(format) reports availability without spawning the engine for real.
|
|
13
|
-
* exportMarkdown({ inputPath, outputPath, format, env }) spawns the engine and
|
|
14
|
-
* returns a structured result. Both surface actionable "install X" guidance
|
|
15
|
-
* when a required binary is absent; neither throws on missing tooling.
|
|
6
|
+
* Export **source** is Construct-authored markdown (typed artifacts). **Output**
|
|
7
|
+
* spans many distributable formats. Ingest (separate pipeline) accepts PDF, Office,
|
|
8
|
+
* email, AV, and plain text — see docs/reference/document-io.md.
|
|
16
9
|
*/
|
|
17
10
|
|
|
18
11
|
import fs from 'node:fs';
|
|
19
12
|
import path from 'node:path';
|
|
20
13
|
import { spawnSync } from 'node:child_process';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import {
|
|
16
|
+
resolvePdfTemplatePath,
|
|
17
|
+
parseArtifactMetadata,
|
|
18
|
+
pandocMetadataArgs,
|
|
19
|
+
preprocessMarkdownForPdfExport,
|
|
20
|
+
distributionFontsDir,
|
|
21
|
+
} from './publish-template.mjs';
|
|
22
|
+
import {
|
|
23
|
+
preprocessMarkdownDiagrams,
|
|
24
|
+
buildDistributionDiagramEnv,
|
|
25
|
+
countDiagramFences,
|
|
26
|
+
} from './diagram-export.mjs';
|
|
27
|
+
import { exportDeckPptx, pptxgenPresent } from './deck-export-pptx.mjs';
|
|
28
|
+
import { pdfUsesBundledBrandSans } from './brand-fonts.mjs';
|
|
29
|
+
import { convertDocxToDoc, libreOfficeInstallHint, libreOfficePresent, resolveLibreOfficeBin } from './libreoffice-export.mjs';
|
|
30
|
+
|
|
31
|
+
export const EXPORT_FORMATS = ['pdf', 'docx', 'doc', 'deck', 'pptx', 'html', 'rtf', 'odt', 'epub', 'tex', 'txt', 'md', 'mdx'];
|
|
32
|
+
|
|
33
|
+
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
21
34
|
|
|
22
|
-
|
|
35
|
+
// Pandoc drives typeset formats; pptxgenjs builds slide decks; legacy `.doc` has no
|
|
36
|
+
// pandoc writer — export DOCX first, then LibreOffice headless down-converts.
|
|
23
37
|
|
|
24
38
|
const FORMAT_ENGINES = {
|
|
25
|
-
pdf: { engine: 'pandoc', pdfEngine: 'typst', extraBinaries: ['typst'] },
|
|
26
|
-
docx: { engine: 'pandoc', pdfEngine: null, extraBinaries: [] },
|
|
27
|
-
|
|
39
|
+
pdf: { engine: 'pandoc', writer: null, pdfEngine: 'typst', extraBinaries: ['typst'] },
|
|
40
|
+
docx: { engine: 'pandoc', writer: null, pdfEngine: null, extraBinaries: [] },
|
|
41
|
+
doc: { engine: 'pandoc', writer: null, pdfEngine: null, extraBinaries: [], via: 'libreoffice', intermediate: 'docx' },
|
|
42
|
+
deck: { engine: 'pandoc', writer: null, pdfEngine: null, extraBinaries: [] },
|
|
43
|
+
pptx: { engine: 'pptxgenjs', writer: null, pdfEngine: null, extraBinaries: [] },
|
|
44
|
+
html: { engine: 'pandoc', writer: null, pdfEngine: null, extraBinaries: [] },
|
|
45
|
+
rtf: { engine: 'pandoc', writer: 'rtf', pdfEngine: null, extraBinaries: [] },
|
|
46
|
+
odt: { engine: 'pandoc', writer: 'odt', pdfEngine: null, extraBinaries: [] },
|
|
47
|
+
epub: { engine: 'pandoc', writer: 'epub', pdfEngine: null, extraBinaries: [] },
|
|
48
|
+
tex: { engine: 'pandoc', writer: 'latex', pdfEngine: null, extraBinaries: [] },
|
|
49
|
+
txt: { engine: 'pandoc', writer: 'plain', pdfEngine: null, extraBinaries: [] },
|
|
50
|
+
md: { engine: 'copy', writer: null, pdfEngine: null, extraBinaries: [] },
|
|
51
|
+
mdx: { engine: 'copy', writer: null, pdfEngine: null, extraBinaries: [] },
|
|
28
52
|
};
|
|
29
53
|
|
|
54
|
+
export function htmlTemplatePath(repoRoot = REPO_ROOT) {
|
|
55
|
+
const p = path.join(repoRoot, 'templates', 'distribution', 'construct-web.html');
|
|
56
|
+
return fs.existsSync(p) ? p : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function deckTemplatePath(repoRoot = REPO_ROOT) {
|
|
60
|
+
const p = path.join(repoRoot, 'templates', 'distribution', 'construct-deck.html');
|
|
61
|
+
return fs.existsSync(p) ? p : null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function docxReferencePath(repoRoot = REPO_ROOT) {
|
|
65
|
+
const p = path.join(repoRoot, 'templates', 'distribution', 'construct-reference.docx');
|
|
66
|
+
return fs.existsSync(p) ? p : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { resolvePdfTemplatePath } from './publish-template.mjs';
|
|
70
|
+
|
|
71
|
+
export function pdfEngineFontOpts(repoRoot = REPO_ROOT) {
|
|
72
|
+
const fontPath = distributionFontsDir(repoRoot);
|
|
73
|
+
return [
|
|
74
|
+
'--pdf-engine-opt', `--font-path=${fontPath}`,
|
|
75
|
+
'--pdf-engine-opt', '--ignore-system-fonts',
|
|
76
|
+
'--pdf-engine-opt', '--ignore-embedded-fonts',
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function countPdfEmbeddedImages(pdfPath) {
|
|
81
|
+
try {
|
|
82
|
+
const data = fs.readFileSync(pdfPath);
|
|
83
|
+
const text = data.toString('latin1');
|
|
84
|
+
const raster = (text.match(/\/Subtype\s*\/Image/g) || []).length;
|
|
85
|
+
const vector = (text.match(/\/Subtype\s*\/Form/g) || []).length;
|
|
86
|
+
return raster + vector;
|
|
87
|
+
} catch {
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function pdfRenderedDiagrams(pdfPath, sourceContent) {
|
|
93
|
+
const expected = countDiagramFences(sourceContent);
|
|
94
|
+
if (expected === 0) return true;
|
|
95
|
+
const embedded = countPdfEmbeddedImages(pdfPath);
|
|
96
|
+
if (embedded >= expected) return true;
|
|
97
|
+
try {
|
|
98
|
+
const text = fs.readFileSync(pdfPath).toString('latin1');
|
|
99
|
+
const rawD2 = /direction:\s*(right|down|left|up)/.test(text);
|
|
100
|
+
const rawMermaid = /flowchart\s+(TD|LR|BT|RL)/.test(text);
|
|
101
|
+
return !rawD2 && !rawMermaid && embedded > 0;
|
|
102
|
+
} catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export { pdfUsesBundledBrandSans } from './brand-fonts.mjs';
|
|
108
|
+
|
|
109
|
+
/** @deprecated use pdfUsesBundledBrandSans */
|
|
110
|
+
export function pdfUsesBundledInter(pdfPath) {
|
|
111
|
+
return pdfUsesBundledBrandSans(pdfPath);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function diagramFilterPath() {
|
|
115
|
+
return path.join(REPO_ROOT, 'vendor', 'pandoc-ext', 'diagram.lua');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function detectPdfTemplate({
|
|
119
|
+
artifactType = null,
|
|
120
|
+
cwd = process.cwd(),
|
|
121
|
+
repoRoot = REPO_ROOT,
|
|
122
|
+
} = {}) {
|
|
123
|
+
const templatePath = resolvePdfTemplatePath({ artifactType, cwd, repoRoot });
|
|
124
|
+
return {
|
|
125
|
+
present: Boolean(templatePath),
|
|
126
|
+
path: templatePath,
|
|
127
|
+
message: templatePath
|
|
128
|
+
? `PDF template: ${path.relative(repoRoot, templatePath)}`
|
|
129
|
+
: 'PDF template missing: templates/distribution/construct-pdf.typ',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
30
133
|
function whichBin(name, env = process.env) {
|
|
31
134
|
const cmd = process.platform === 'win32' ? 'where' : 'which';
|
|
32
135
|
const result = spawnSync(cmd, [name], { encoding: 'utf8', env });
|
|
@@ -45,28 +148,77 @@ function binVersion(name, args = ['--version'], env = process.env) {
|
|
|
45
148
|
function installHint(name) {
|
|
46
149
|
if (name === 'pandoc') return 'Install pandoc to enable document export (e.g. `brew install pandoc` on macOS, `apt install pandoc` on Debian/Ubuntu, or https://pandoc.org/installing.html).';
|
|
47
150
|
if (name === 'typst') return 'Install typst to enable PDF export via Pandoc (`brew install typst` on macOS, https://github.com/typst/typst/releases for binaries).';
|
|
151
|
+
if (name === 'construct-pdf.typ') return 'Bundled PDF template missing from templates/distribution/construct-pdf.typ (Construct install integrity issue).';
|
|
152
|
+
if (name === 'd2') return 'Install d2 for diagram blocks (`brew install d2`).';
|
|
153
|
+
if (name === 'mmdc') return 'Install mermaid-cli for mermaid blocks (`npm install -g @mermaid-js/mermaid-cli`).';
|
|
154
|
+
if (name === 'pptxgenjs') return 'Install pptxgenjs to enable PPTX export (`npm install pptxgenjs` in the Construct package).';
|
|
155
|
+
if (name === 'construct-deck.html') return 'Bundled deck template missing from templates/distribution/construct-deck.html (Construct install integrity issue).';
|
|
156
|
+
if (name === 'libreoffice') return libreOfficeInstallHint();
|
|
48
157
|
return `Install ${name} to enable this export format.`;
|
|
49
158
|
}
|
|
50
159
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
160
|
+
export function detect(format, env = process.env, {
|
|
161
|
+
figures = false,
|
|
162
|
+
artifactType = null,
|
|
163
|
+
cwd = process.cwd(),
|
|
164
|
+
repoRoot = REPO_ROOT,
|
|
165
|
+
} = {}) {
|
|
56
166
|
const config = FORMAT_ENGINES[format];
|
|
57
167
|
if (!config) return { ok: false, format, present: false, missing: [], message: `Unsupported format: ${format}. Supported: ${EXPORT_FORMATS.join(', ')}.` };
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
168
|
+
if (config.engine === 'copy') {
|
|
169
|
+
return { ok: true, format, figures: false, present: true, binaries: [], missing: [], message: `Ready: source emit (${format})` };
|
|
170
|
+
}
|
|
171
|
+
const required = [];
|
|
172
|
+
if (config.engine === 'pptxgenjs') {
|
|
173
|
+
required.push('pptxgenjs');
|
|
174
|
+
} else {
|
|
175
|
+
required.push(config.engine, ...(config.extraBinaries || []));
|
|
176
|
+
}
|
|
177
|
+
if (format === 'pdf') {
|
|
178
|
+
const template = detectPdfTemplate({ artifactType, cwd, repoRoot });
|
|
179
|
+
if (!template.present) required.push('construct-pdf.typ');
|
|
180
|
+
}
|
|
181
|
+
if (format === 'deck' && !deckTemplatePath(repoRoot)) {
|
|
182
|
+
required.push('construct-deck.html');
|
|
183
|
+
}
|
|
184
|
+
if (format === 'doc' && !libreOfficePresent(env)) {
|
|
185
|
+
required.push('libreoffice');
|
|
186
|
+
}
|
|
187
|
+
if (figures) {
|
|
188
|
+
if (!fs.existsSync(diagramFilterPath())) required.push('diagram.lua');
|
|
189
|
+
if (!whichBin('d2', env) && !whichBin('dot', env)) required.push('d2');
|
|
190
|
+
if (!whichBin('mmdc', env)) required.push('mmdc');
|
|
191
|
+
}
|
|
192
|
+
const status = required.map((name) => {
|
|
193
|
+
if (name.endsWith('.lua')) {
|
|
194
|
+
return { name, path: fs.existsSync(diagramFilterPath()) ? diagramFilterPath() : null, version: null };
|
|
195
|
+
}
|
|
196
|
+
if (name.endsWith('.html')) {
|
|
197
|
+
return { name, path: deckTemplatePath(repoRoot), version: null };
|
|
198
|
+
}
|
|
199
|
+
if (name === 'pptxgenjs') {
|
|
200
|
+
return { name, path: pptxgenPresent() ? 'bundled' : null, version: null };
|
|
201
|
+
}
|
|
202
|
+
if (name === 'libreoffice') {
|
|
203
|
+
return { name, path: libreOfficePresent(env) ? resolveLibreOfficeBin(env) : null, version: null };
|
|
204
|
+
}
|
|
205
|
+
return { name, path: whichBin(name, env), version: null };
|
|
206
|
+
});
|
|
207
|
+
for (const s of status) {
|
|
208
|
+
if (s.path && !s.name.endsWith('.lua') && s.name !== 'pptxgenjs' && s.name !== 'libreoffice') {
|
|
209
|
+
s.version = binVersion(s.name, ['--version'], env);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
61
212
|
const missing = status.filter((s) => !s.path).map((s) => s.name);
|
|
62
213
|
return {
|
|
63
214
|
ok: true,
|
|
64
215
|
format,
|
|
216
|
+
figures,
|
|
65
217
|
present: missing.length === 0,
|
|
66
218
|
binaries: status,
|
|
67
219
|
missing,
|
|
68
220
|
message: missing.length === 0
|
|
69
|
-
? `Ready: ${status.map((s) => `${s.name} (${s.version?.split(' ').slice(0, 2).join(' ') ?? '
|
|
221
|
+
? `Ready: ${status.map((s) => `${s.name} (${s.version?.split(' ').slice(0, 2).join(' ') ?? 'bundled'})`).join(', ')}`
|
|
70
222
|
: missing.map(installHint).join(' '),
|
|
71
223
|
};
|
|
72
224
|
}
|
|
@@ -76,31 +228,187 @@ function defaultOutputPath(inputPath, format) {
|
|
|
76
228
|
return path.join(parsed.dir, `${parsed.name}.${format}`);
|
|
77
229
|
}
|
|
78
230
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
231
|
+
export function parsePublishFrontmatter(inputPath) {
|
|
232
|
+
try {
|
|
233
|
+
const content = fs.readFileSync(inputPath, 'utf8');
|
|
234
|
+
if (!content.startsWith('---')) return null;
|
|
235
|
+
const end = content.indexOf('\n---', 3);
|
|
236
|
+
if (end === -1) return null;
|
|
237
|
+
const block = content.slice(3, end);
|
|
238
|
+
const publish = {};
|
|
239
|
+
let inPublish = false;
|
|
240
|
+
for (const line of block.split('\n')) {
|
|
241
|
+
if (/^publish:\s*$/.test(line)) { inPublish = true; continue; }
|
|
242
|
+
if (inPublish && /^\S/.test(line) && !line.startsWith(' ')) inPublish = false;
|
|
243
|
+
if (!inPublish) continue;
|
|
244
|
+
const m = line.match(/^\s+(\w+):\s*(.+)$/);
|
|
245
|
+
if (m) publish[m[1]] = m[2].replace(/^["']|["']$/g, '').trim();
|
|
246
|
+
}
|
|
247
|
+
return Object.keys(publish).length ? publish : null;
|
|
248
|
+
} catch {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
84
252
|
|
|
85
|
-
|
|
253
|
+
function prepareExportInput(inputPath, { format = 'html', figures = false, metadata = null } = {}) {
|
|
254
|
+
let raw = fs.readFileSync(inputPath, 'utf8');
|
|
255
|
+
let processed = raw;
|
|
256
|
+
if ((format === 'pdf' || format === 'html') && metadata?.title) {
|
|
257
|
+
processed = preprocessMarkdownForPdfExport(processed, metadata);
|
|
258
|
+
}
|
|
259
|
+
if (figures) {
|
|
260
|
+
processed = preprocessMarkdownDiagrams(processed);
|
|
261
|
+
}
|
|
262
|
+
if (processed === raw) return { inputPath, cleanup: null };
|
|
263
|
+
const tmp = path.join(path.dirname(inputPath), `.construct-export-${path.basename(inputPath)}`);
|
|
264
|
+
fs.writeFileSync(tmp, processed, 'utf8');
|
|
265
|
+
return {
|
|
266
|
+
inputPath: tmp,
|
|
267
|
+
cleanup: () => { try { fs.unlinkSync(tmp); } catch { /* skip */ } },
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// md and mdx are emitted straight from the authored source: the artifact already
|
|
272
|
+
// is Markdown, and Construct-authored Markdown is MDX-compatible (fenced diagrams,
|
|
273
|
+
// YAML frontmatter), so no transform is needed beyond writing the target file.
|
|
274
|
+
|
|
275
|
+
function exportSourceCopy({ inputPath, target, format }) {
|
|
276
|
+
const content = fs.readFileSync(inputPath, 'utf8');
|
|
277
|
+
fs.writeFileSync(target, content, 'utf8');
|
|
278
|
+
return {
|
|
279
|
+
ok: true,
|
|
280
|
+
format,
|
|
281
|
+
inputPath,
|
|
282
|
+
outputPath: target,
|
|
283
|
+
engine: 'copy',
|
|
284
|
+
message: `Wrote ${path.relative(process.cwd(), target)}`,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function exportMarkdown({
|
|
289
|
+
inputPath,
|
|
290
|
+
outputPath,
|
|
291
|
+
format,
|
|
292
|
+
figures = false,
|
|
293
|
+
artifactType = null,
|
|
294
|
+
env = process.env,
|
|
295
|
+
spawnFn = spawnSync,
|
|
296
|
+
cwd = process.cwd(),
|
|
297
|
+
repoRoot = REPO_ROOT,
|
|
298
|
+
} = {}) {
|
|
86
299
|
if (!inputPath) return { ok: false, format, message: 'exportMarkdown: inputPath is required.' };
|
|
87
300
|
if (!EXPORT_FORMATS.includes(format)) return { ok: false, format, inputPath, message: `Unsupported format: ${format}. Supported: ${EXPORT_FORMATS.join(', ')}.` };
|
|
88
301
|
if (!fs.existsSync(inputPath)) return { ok: false, format, inputPath, message: `exportMarkdown: input does not exist: ${inputPath}` };
|
|
89
302
|
|
|
90
|
-
const
|
|
303
|
+
const metadata = parseArtifactMetadata(inputPath);
|
|
304
|
+
const resolvedType = artifactType || metadata.artifactType || null;
|
|
305
|
+
const effectiveEnv = figures ? buildDistributionDiagramEnv(env) : env;
|
|
306
|
+
|
|
307
|
+
const detection = detect(format, effectiveEnv, { figures, artifactType: resolvedType, cwd, repoRoot });
|
|
91
308
|
if (!detection.present) {
|
|
92
309
|
return { ok: false, format, inputPath, missing: detection.missing, message: detection.message };
|
|
93
310
|
}
|
|
94
311
|
|
|
95
312
|
const config = FORMAT_ENGINES[format];
|
|
96
|
-
const target = outputPath || defaultOutputPath(inputPath, format);
|
|
313
|
+
const target = path.resolve(outputPath || defaultOutputPath(inputPath, format));
|
|
97
314
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
98
315
|
|
|
316
|
+
if (config.engine === 'copy') {
|
|
317
|
+
return exportSourceCopy({ inputPath, target, format });
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (format === 'pptx') {
|
|
321
|
+
return exportDeckPptx({ inputPath, outputPath: target, metadata, repoRoot });
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (format === 'doc') {
|
|
325
|
+
const intermediate = path.join(path.dirname(target), `.construct-export-${path.basename(inputPath, path.extname(inputPath))}.docx`);
|
|
326
|
+
const docxResult = exportMarkdown({
|
|
327
|
+
inputPath,
|
|
328
|
+
outputPath: intermediate,
|
|
329
|
+
format: 'docx',
|
|
330
|
+
figures,
|
|
331
|
+
artifactType: resolvedType,
|
|
332
|
+
env: effectiveEnv,
|
|
333
|
+
spawnFn,
|
|
334
|
+
cwd,
|
|
335
|
+
repoRoot,
|
|
336
|
+
});
|
|
337
|
+
if (!docxResult.ok) return { ...docxResult, format: 'doc' };
|
|
338
|
+
const loResult = convertDocxToDoc({ docxPath: intermediate, outputPath: target, env: effectiveEnv, spawnFn });
|
|
339
|
+
try { fs.unlinkSync(intermediate); } catch { /* skip */ }
|
|
340
|
+
if (!loResult.ok) {
|
|
341
|
+
return {
|
|
342
|
+
ok: false,
|
|
343
|
+
format: 'doc',
|
|
344
|
+
inputPath,
|
|
345
|
+
outputPath: target,
|
|
346
|
+
missing: loResult.missing,
|
|
347
|
+
message: loResult.message,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
ok: true,
|
|
352
|
+
format: 'doc',
|
|
353
|
+
inputPath,
|
|
354
|
+
outputPath: target,
|
|
355
|
+
engine: 'libreoffice',
|
|
356
|
+
pdfEngine: null,
|
|
357
|
+
figures,
|
|
358
|
+
template: null,
|
|
359
|
+
message: loResult.message,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const prepared = prepareExportInput(inputPath, { format, figures, metadata });
|
|
364
|
+
const templatePath = format === 'pdf'
|
|
365
|
+
? resolvePdfTemplatePath({ artifactType: resolvedType, cwd, repoRoot })
|
|
366
|
+
: null;
|
|
367
|
+
|
|
368
|
+
const metadataArgs = pandocMetadataArgs({
|
|
369
|
+
title: metadata.title,
|
|
370
|
+
subtitle: metadata.subtitle,
|
|
371
|
+
date: metadata.date,
|
|
372
|
+
status: metadata.status,
|
|
373
|
+
owner: metadata.owner,
|
|
374
|
+
artifactType: resolvedType || metadata.artifactType,
|
|
375
|
+
version: metadata.version,
|
|
376
|
+
docId: metadata.docId,
|
|
377
|
+
classification: metadata.classification,
|
|
378
|
+
});
|
|
379
|
+
|
|
99
380
|
const args = ['-f', 'markdown', '-o', target, '--standalone'];
|
|
100
381
|
if (config.pdfEngine) args.push(`--pdf-engine=${config.pdfEngine}`);
|
|
101
|
-
|
|
382
|
+
if (format === 'pdf') {
|
|
383
|
+
if (templatePath) args.push('--template', templatePath);
|
|
384
|
+
args.push(...pdfEngineFontOpts(repoRoot));
|
|
385
|
+
args.push(...metadataArgs);
|
|
386
|
+
} else if (format === 'html') {
|
|
387
|
+
const htmlTpl = htmlTemplatePath(repoRoot);
|
|
388
|
+
if (htmlTpl) args.push('--template', htmlTpl);
|
|
389
|
+
args.push('--embed-resources');
|
|
390
|
+
args.push(...metadataArgs);
|
|
391
|
+
} else if (format === 'deck') {
|
|
392
|
+
const deckTpl = deckTemplatePath(repoRoot);
|
|
393
|
+
if (deckTpl) args.push('--template', deckTpl);
|
|
394
|
+
args.push('--section-divs');
|
|
395
|
+
args.push('--embed-resources');
|
|
396
|
+
args.push(...metadataArgs);
|
|
397
|
+
} else if (format === 'docx') {
|
|
398
|
+
const ref = docxReferencePath(repoRoot);
|
|
399
|
+
if (ref) args.push('--reference-doc', ref);
|
|
400
|
+
args.push(...metadataArgs);
|
|
401
|
+
}
|
|
402
|
+
if (figures && fs.existsSync(diagramFilterPath())) {
|
|
403
|
+
args.push('--lua-filter', diagramFilterPath());
|
|
404
|
+
}
|
|
405
|
+
args.push(path.resolve(prepared.inputPath));
|
|
406
|
+
|
|
407
|
+
const spawnCwd = (format === 'pdf' && templatePath) ? path.dirname(templatePath) : path.dirname(prepared.inputPath);
|
|
408
|
+
|
|
409
|
+
const result = spawnFn(config.engine, args, { encoding: 'utf8', env: effectiveEnv, cwd: spawnCwd });
|
|
410
|
+
if (prepared.cleanup) prepared.cleanup();
|
|
102
411
|
|
|
103
|
-
const result = spawnFn(config.engine, args, { encoding: 'utf8', env });
|
|
104
412
|
if (result.status !== 0) {
|
|
105
413
|
return {
|
|
106
414
|
ok: false,
|
|
@@ -112,6 +420,20 @@ export function exportMarkdown({ inputPath, outputPath, format, env = process.en
|
|
|
112
420
|
};
|
|
113
421
|
}
|
|
114
422
|
|
|
423
|
+
if (format === 'pdf' && figures) {
|
|
424
|
+
const source = fs.readFileSync(inputPath, 'utf8');
|
|
425
|
+
const expectedDiagrams = countDiagramFences(source);
|
|
426
|
+
if (expectedDiagrams > 0 && !pdfRenderedDiagrams(target, source)) {
|
|
427
|
+
return {
|
|
428
|
+
ok: false,
|
|
429
|
+
format,
|
|
430
|
+
inputPath,
|
|
431
|
+
outputPath: target,
|
|
432
|
+
message: `Export wrote PDF but rendered 0/${expectedDiagrams} diagram(s). Ensure d2 and mmdc are installed; mmdc needs Chrome (set PUPPETEER_EXECUTABLE_PATH or install Google Chrome). Pandoc stderr: ${(result.stderr || '').trim().slice(0, 300)}`,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
115
437
|
return {
|
|
116
438
|
ok: true,
|
|
117
439
|
format,
|
|
@@ -119,6 +441,8 @@ export function exportMarkdown({ inputPath, outputPath, format, env = process.en
|
|
|
119
441
|
outputPath: target,
|
|
120
442
|
engine: config.engine,
|
|
121
443
|
pdfEngine: config.pdfEngine,
|
|
444
|
+
figures,
|
|
445
|
+
template: templatePath,
|
|
122
446
|
message: `Wrote ${path.relative(process.cwd(), target)}`,
|
|
123
447
|
};
|
|
124
448
|
}
|