@geraldmaron/construct 1.1.1 → 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 +29 -19
- package/bin/construct +311 -81
- 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 +1 -1
- 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 +129 -15
- 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 +598 -140
- package/lib/diagram-export.mjs +192 -0
- package/lib/diagram.mjs +13 -11
- package/lib/docs-verify.mjs +25 -7
- package/lib/doctor/index.mjs +3 -1
- package/lib/doctor/project-adapters.mjs +44 -0
- package/lib/doctor/watchers/cx-budget.mjs +98 -0
- package/lib/doctor/watchers/graph-staleness.mjs +52 -0
- package/lib/doctor/watchers/process-pressure.mjs +4 -3
- package/lib/document-export.mjs +360 -36
- package/lib/document-extract.mjs +44 -4
- package/lib/document-ingest.mjs +29 -20
- package/lib/embed/cli.mjs +2 -2
- package/lib/embed/daemon.mjs +18 -46
- 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/health-check.mjs +90 -76
- package/lib/hooks/artifact-release-gate.mjs +43 -0
- package/lib/hooks/brand-prose-lint.mjs +38 -0
- package/lib/hooks/graph-impact-advisory.mjs +62 -0
- package/lib/hooks/session-optimize.mjs +84 -207
- package/lib/hooks/session-start.mjs +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 +373 -92
- package/lib/models/behavior-matrix.mjs +289 -0
- package/lib/models/catalog.mjs +209 -0
- package/lib/models/execution-capability-profile.mjs +196 -0
- package/lib/models/execution-policy.mjs +307 -0
- package/lib/models/provider-poll.mjs +383 -0
- package/lib/npm-spawn-env.mjs +17 -0
- package/lib/ollama/installed-models.mjs +129 -0
- package/lib/oracle/actions.mjs +187 -0
- package/lib/oracle/artifact-gate.mjs +99 -0
- package/lib/oracle/cli.mjs +204 -0
- package/lib/oracle/daemon-entry.mjs +14 -0
- package/lib/oracle/dispatch.mjs +81 -0
- package/lib/oracle/execute.mjs +143 -0
- package/lib/oracle/gaps.mjs +76 -0
- package/lib/oracle/index.mjs +84 -0
- package/lib/oracle/issues.mjs +164 -0
- package/lib/oracle/org-graph.mjs +170 -0
- package/lib/oracle/policy.mjs +80 -0
- package/lib/oracle/read-model.mjs +398 -0
- package/lib/oracle/reconcile.mjs +191 -0
- package/lib/oracle/routing.mjs +89 -0
- package/lib/oracle/synthesize.mjs +384 -0
- package/lib/oracle/verdicts.mjs +51 -0
- package/lib/orchestration/worker.mjs +88 -27
- package/lib/orchestration-policy.mjs +50 -0
- package/lib/parity.mjs +96 -2
- package/lib/playwright-demo.mjs +252 -0
- package/lib/project-init-shared.mjs +4 -1
- package/lib/prompt-composer.js +7 -3
- package/lib/prompt-validation-contract.mjs +24 -0
- package/lib/provider-capabilities.js +57 -11
- package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
- package/lib/providers/contract/adapters/git/index.mjs +115 -0
- package/lib/providers/contract/adapters/github/index.mjs +166 -0
- package/lib/providers/contract/adapters/jira/index.mjs +187 -0
- package/lib/providers/contract/adapters/slack/index.mjs +175 -0
- package/lib/providers/contract/contract-tests.mjs +57 -0
- package/lib/providers/contract/errors.mjs +48 -0
- package/lib/providers/contract/interface.mjs +50 -0
- package/lib/providers/contract/registry.mjs +102 -0
- package/lib/providers/copilot-auth.mjs +297 -0
- package/lib/providers/credential-bootstrap.mjs +178 -0
- package/lib/providers/credential-catalog.mjs +46 -0
- package/lib/providers/credential-sources.mjs +63 -0
- package/lib/providers/creds.mjs +5 -2
- package/lib/providers/op-run.mjs +59 -0
- package/lib/providers/secret-resolver.mjs +159 -0
- package/lib/publish-template.mjs +163 -0
- package/lib/publish-tooling.mjs +119 -0
- package/lib/publish.mjs +305 -0
- package/lib/registry/cli.mjs +82 -0
- package/lib/registry/consolidation.mjs +147 -0
- package/lib/registry/generate-docs.mjs +75 -0
- package/lib/registry/skill-verification.mjs +57 -0
- package/lib/registry/surface-map.mjs +76 -0
- package/lib/registry/validate.mjs +135 -0
- package/lib/resources/budget.mjs +82 -0
- package/lib/resources/process-budget.mjs +45 -0
- package/lib/rules-delivery.mjs +9 -2
- package/lib/rules-read.mjs +26 -0
- package/lib/runtime-env.mjs +23 -0
- package/lib/runtime-pressure.mjs +51 -7
- package/lib/schema-infer.mjs +13 -2
- package/lib/server/chat-loop.mjs +622 -0
- package/lib/server/demo-preview.mjs +63 -0
- package/lib/server/index.mjs +228 -7
- package/lib/service-manager.mjs +60 -8
- package/lib/setup.mjs +70 -7
- package/lib/specialists/prompt-schema.mjs +19 -10
- package/lib/specialists/roster.mjs +43 -0
- package/lib/specialists/scaffold.mjs +56 -0
- package/lib/storage/backend.mjs +6 -6
- package/lib/storage/hybrid-query.mjs +7 -4
- package/lib/storage/state-source.mjs +5 -5
- package/lib/storage/sync.mjs +2 -3
- package/lib/telemetry/rule-calls.mjs +52 -0
- package/lib/template-registry.mjs +2 -2
- package/lib/templates/visual-requirements.mjs +27 -51
- package/lib/test-corpus-inventory.mjs +313 -0
- package/lib/uninstall/uninstall.mjs +19 -7
- package/lib/update.mjs +12 -0
- package/lib/upgrade.mjs +14 -0
- package/lib/wireframe.mjs +20 -14
- package/lib/worker/run.mjs +17 -4
- package/lib/worker/trace.mjs +11 -3
- package/package.json +28 -14
- package/personas/construct.md +2 -2
- package/platforms/claude/settings.template.json +36 -0
- package/rules/common/patterns.md +1 -1
- package/rules/common/release-gates.md +4 -3
- package/scripts/sync-specialists.mjs +41 -28
- package/skills/devops/data-engineering.md +1 -1
- package/skills/docs/adr-workflow.md +1 -0
- package/skills/docs/backlog-proposal-workflow.md +1 -0
- package/skills/docs/codebase-research-workflow.md +40 -0
- package/skills/docs/customer-profile-workflow.md +1 -0
- package/skills/docs/document-ingest-workflow.md +1 -0
- package/skills/docs/evidence-ingest-workflow.md +1 -0
- package/skills/docs/init-docs.md +2 -2
- package/skills/docs/init-project.md +7 -2
- package/skills/docs/prd-workflow.md +24 -1
- package/skills/docs/prfaq-workflow.md +1 -0
- package/skills/docs/product-intelligence-workflow.md +1 -0
- package/skills/docs/product-signal-workflow.md +1 -0
- package/skills/docs/research-workflow.md +54 -37
- package/skills/docs/runbook-workflow.md +1 -0
- package/skills/docs/strategy-workflow.md +1 -0
- package/skills/docs/user-research-workflow.md +40 -0
- package/skills/operating/orchestration-reference.md +1 -1
- package/skills/roles/architect.md +5 -0
- package/skills/roles/operator.docs.md +4 -0
- package/skills/routing.md +4 -2
- package/specialists/artifact-manifest.json +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 +8 -8
- package/templates/docs/customer-profile.md +4 -0
- package/templates/docs/one-pager.md +4 -0
- package/templates/docs/postmortem.md +31 -0
- package/templates/docs/prd-platform.md +19 -0
- package/templates/docs/prd.md +15 -0
- package/templates/docs/prfaq.md +7 -0
- package/templates/docs/qa-strategy.md +103 -0
- package/templates/docs/research-brief.md +8 -0
- package/templates/docs/rfc-platform.md +12 -0
- package/templates/docs/test-plan.md +7 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/diagram-export.mjs — distribution diagram styling for publish exports.
|
|
3
|
+
*
|
|
4
|
+
* Publish path renders hand-drawn D2 (--sketch geometry) and Mermaid (handDrawn
|
|
5
|
+
* look, Caveat handwriting) in a monochrome ink palette — black/grey strokes,
|
|
6
|
+
* light surfaces, near-black text — for an Excalidraw-adjacent human aesthetic
|
|
7
|
+
* that stays consistent with the document brand. Color is reserved for explicit
|
|
8
|
+
* per-node emphasis in the source, not the default theme.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
|
|
15
|
+
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
16
|
+
const ICON_DIR = path.join(REPO_ROOT, 'templates', 'distribution', 'icons');
|
|
17
|
+
|
|
18
|
+
// Monochrome diagram palette, mirroring the ink ramp in construct-brand.typ so
|
|
19
|
+
// embedded diagrams and surrounding body share one design language.
|
|
20
|
+
|
|
21
|
+
const MONO = {
|
|
22
|
+
ink: '#0a0c10',
|
|
23
|
+
inkBody: '#23272e',
|
|
24
|
+
muted: '#565c66',
|
|
25
|
+
faint: '#9499a2',
|
|
26
|
+
hairline: '#e3e4e8',
|
|
27
|
+
hairlineStrong: '#cdd0d6',
|
|
28
|
+
surface: '#fafafa',
|
|
29
|
+
surfaceAlt: '#f3f4f6',
|
|
30
|
+
paper: '#ffffff',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const DISTRIBUTION_D2_THEME = '0';
|
|
34
|
+
export const DISTRIBUTION_D2_PAD = '16';
|
|
35
|
+
export const DISTRIBUTION_D2_SCALE = '0.72';
|
|
36
|
+
export const DISTRIBUTION_D2_SKETCH = '1';
|
|
37
|
+
export const DISTRIBUTION_D2_FONT_SIZE = '14';
|
|
38
|
+
export const DISTRIBUTION_MERMAID_MIME = 'image/png';
|
|
39
|
+
export const DISTRIBUTION_MERMAID_WIDTH = '480';
|
|
40
|
+
export const DISTRIBUTION_MERMAID_SCALE = '1';
|
|
41
|
+
export const DISTRIBUTION_FIGURE_MAX_WIDTH = '58%';
|
|
42
|
+
|
|
43
|
+
// Handwritten font for Mermaid labels so its hand-drawn look matches D2 sketch
|
|
44
|
+
// mode. Caveat ships bundled (templates/distribution/fonts/handwritten) and is
|
|
45
|
+
// installed for the headless Chrome that mmdc drives; the fallbacks stay in the
|
|
46
|
+
// handwriting register so a missing face never drops to a serif.
|
|
47
|
+
|
|
48
|
+
const MERMAID_FONT = 'Caveat, Comic Sans MS, cursive';
|
|
49
|
+
|
|
50
|
+
const MERMAID_INIT = `%%{init: {
|
|
51
|
+
'theme': 'base',
|
|
52
|
+
'look': 'handDrawn',
|
|
53
|
+
'fontFamily': '${MERMAID_FONT}',
|
|
54
|
+
'themeVariables': {
|
|
55
|
+
'fontFamily': '${MERMAID_FONT}',
|
|
56
|
+
'fontSize': '19px',
|
|
57
|
+
'primaryColor': '${MONO.surfaceAlt}',
|
|
58
|
+
'primaryTextColor': '${MONO.ink}',
|
|
59
|
+
'primaryBorderColor': '${MONO.ink}',
|
|
60
|
+
'lineColor': '${MONO.muted}',
|
|
61
|
+
'secondaryColor': '${MONO.paper}',
|
|
62
|
+
'secondaryTextColor': '${MONO.ink}',
|
|
63
|
+
'secondaryBorderColor': '${MONO.ink}',
|
|
64
|
+
'tertiaryColor': '${MONO.surface}',
|
|
65
|
+
'tertiaryTextColor': '${MONO.ink}',
|
|
66
|
+
'tertiaryBorderColor': '${MONO.hairlineStrong}',
|
|
67
|
+
'clusterBkg': '${MONO.surface}',
|
|
68
|
+
'clusterBorder': '${MONO.hairlineStrong}',
|
|
69
|
+
'edgeLabelBackground': '${MONO.paper}',
|
|
70
|
+
'titleColor': '${MONO.ink}',
|
|
71
|
+
'noteBkgColor': '${MONO.surface}',
|
|
72
|
+
'noteTextColor': '${MONO.inkBody}',
|
|
73
|
+
'noteBorderColor': '${MONO.hairlineStrong}',
|
|
74
|
+
'actorBkg': '${MONO.surfaceAlt}',
|
|
75
|
+
'actorBorder': '${MONO.ink}',
|
|
76
|
+
'actorTextColor': '${MONO.ink}',
|
|
77
|
+
'actorLineColor': '${MONO.hairlineStrong}',
|
|
78
|
+
'signalColor': '${MONO.inkBody}',
|
|
79
|
+
'signalTextColor': '${MONO.inkBody}',
|
|
80
|
+
'labelBoxBkgColor': '${MONO.surface}',
|
|
81
|
+
'labelBoxBorderColor': '${MONO.hairlineStrong}',
|
|
82
|
+
'labelTextColor': '${MONO.ink}',
|
|
83
|
+
'loopTextColor': '${MONO.inkBody}',
|
|
84
|
+
'activationBkgColor': '${MONO.hairline}',
|
|
85
|
+
'activationBorderColor': '${MONO.muted}'
|
|
86
|
+
}
|
|
87
|
+
}}%%`;
|
|
88
|
+
|
|
89
|
+
const CHROME_CANDIDATES = [
|
|
90
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
91
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
92
|
+
'/usr/bin/google-chrome',
|
|
93
|
+
'/usr/bin/chromium',
|
|
94
|
+
'/usr/bin/chromium-browser',
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
export function resolvePuppeteerExecutable(env = process.env) {
|
|
98
|
+
if (env.PUPPETEER_EXECUTABLE_PATH) return env.PUPPETEER_EXECUTABLE_PATH;
|
|
99
|
+
for (const candidate of CHROME_CANDIDATES) {
|
|
100
|
+
try {
|
|
101
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
102
|
+
} catch {
|
|
103
|
+
/* skip */
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function injectMermaidBrandTheme(code) {
|
|
110
|
+
const body = String(code || '').trim();
|
|
111
|
+
if (!body) return body;
|
|
112
|
+
if (/%%\{init:/.test(body)) return body;
|
|
113
|
+
return `${MERMAID_INIT}\n${body}\n`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// `icon: @name` in a d2 block resolves to the bundled monochrome Lucide icon at
|
|
117
|
+
// templates/distribution/icons/name.svg, keeping source markdown portable while
|
|
118
|
+
// d2 (which runs in a temp dir) still receives an absolute path it can read.
|
|
119
|
+
|
|
120
|
+
export function resolveIconTokens(code) {
|
|
121
|
+
return String(code || '').replace(
|
|
122
|
+
/icon:\s*@([a-z0-9-]+)/gi,
|
|
123
|
+
(match, name) => {
|
|
124
|
+
const file = path.join(ICON_DIR, `${name}.svg`);
|
|
125
|
+
return fs.existsSync(file) ? `icon: ${file}` : match;
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function injectD2DistributionDefaults(code) {
|
|
131
|
+
const body = resolveIconTokens(String(code || '').trim());
|
|
132
|
+
if (!body) return body;
|
|
133
|
+
if (/CONSTRUCT_D2_DEFAULTS/.test(body)) return body;
|
|
134
|
+
return `**: {
|
|
135
|
+
style.font-size: ${DISTRIBUTION_D2_FONT_SIZE}
|
|
136
|
+
style.font-color: "${MONO.ink}"
|
|
137
|
+
style.stroke: "${MONO.ink}"
|
|
138
|
+
style.fill: "${MONO.surfaceAlt}"
|
|
139
|
+
}
|
|
140
|
+
(** -> **)[*]: {
|
|
141
|
+
style.stroke: "${MONO.muted}"
|
|
142
|
+
style.font-color: "${MONO.inkBody}"
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
${body}
|
|
146
|
+
`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function preprocessMarkdownDiagrams(content) {
|
|
150
|
+
let out = String(content);
|
|
151
|
+
out = out.replace(/```mermaid\n([\s\S]*?)```/g, (_match, inner) => `\`\`\`mermaid\n${injectMermaidBrandTheme(inner)}\`\`\``);
|
|
152
|
+
out = out.replace(/```d2\n([\s\S]*?)```/g, (_match, inner) => `\`\`\`d2\n${injectD2DistributionDefaults(inner)}\`\`\``);
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function countDiagramFences(content) {
|
|
157
|
+
const matches = String(content || '').match(/```(?:d2|mermaid)\n/g);
|
|
158
|
+
return matches ? matches.length : 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function buildDistributionDiagramEnv(baseEnv = process.env) {
|
|
162
|
+
const env = {
|
|
163
|
+
...baseEnv,
|
|
164
|
+
CONSTRUCT_D2_THEME: DISTRIBUTION_D2_THEME,
|
|
165
|
+
CONSTRUCT_D2_PAD: DISTRIBUTION_D2_PAD,
|
|
166
|
+
CONSTRUCT_D2_SCALE: DISTRIBUTION_D2_SCALE,
|
|
167
|
+
CONSTRUCT_D2_SKETCH: DISTRIBUTION_D2_SKETCH,
|
|
168
|
+
CONSTRUCT_MERMAID_THEME: 'construct',
|
|
169
|
+
CONSTRUCT_MERMAID_MIME: DISTRIBUTION_MERMAID_MIME,
|
|
170
|
+
CONSTRUCT_MERMAID_WIDTH: DISTRIBUTION_MERMAID_WIDTH,
|
|
171
|
+
CONSTRUCT_MERMAID_SCALE: DISTRIBUTION_MERMAID_SCALE,
|
|
172
|
+
};
|
|
173
|
+
const chrome = resolvePuppeteerExecutable(baseEnv);
|
|
174
|
+
if (chrome) env.PUPPETEER_EXECUTABLE_PATH = chrome;
|
|
175
|
+
return env;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function distributionDiagramDefaults() {
|
|
179
|
+
return {
|
|
180
|
+
d2Theme: 'neutral',
|
|
181
|
+
d2ThemeId: DISTRIBUTION_D2_THEME,
|
|
182
|
+
d2Sketch: true,
|
|
183
|
+
d2Scale: Number(DISTRIBUTION_D2_SCALE),
|
|
184
|
+
d2FontSize: Number(DISTRIBUTION_D2_FONT_SIZE),
|
|
185
|
+
figureMaxWidth: DISTRIBUTION_FIGURE_MAX_WIDTH,
|
|
186
|
+
mermaidLook: 'handDrawn',
|
|
187
|
+
mermaidTheme: 'construct',
|
|
188
|
+
mermaidWidth: Number(DISTRIBUTION_MERMAID_WIDTH),
|
|
189
|
+
mermaidScale: Number(DISTRIBUTION_MERMAID_SCALE),
|
|
190
|
+
accent: MONO.ink,
|
|
191
|
+
};
|
|
192
|
+
}
|
package/lib/diagram.mjs
CHANGED
|
@@ -6,10 +6,9 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Tool evaluation (2026-06), against: visual distinctiveness, reproducible
|
|
8
8
|
* from code, headless Node/CI SVG+PNG render, theming, license:
|
|
9
|
-
* - D2 (terrastruct, MPL-2.0): single self-contained Go binary,
|
|
10
|
-
*
|
|
11
|
-
* deterministic from a text file. Chosen as PRIMARY
|
|
12
|
-
* distinctiveness and one-binary install.
|
|
9
|
+
* - D2 (terrastruct, MPL-2.0): single self-contained Go binary, clean neutral
|
|
10
|
+
* theme plus `--sketch` hand-drawn mode (publish distribution path), headless
|
|
11
|
+
* `d2 in.d2 out.svg|png`, deterministic from a text file. Chosen as PRIMARY.
|
|
13
12
|
* - Graphviz `dot` (EPL-1.0): ubiquitous, almost always already present on
|
|
14
13
|
* CI images and dev machines, headless `dot -Tsvg|-Tpng`. Chosen as
|
|
15
14
|
* FALLBACK for reach when D2 is absent.
|
|
@@ -157,8 +156,11 @@ export function generateDiagram({ description = '', type = null, theme = null }
|
|
|
157
156
|
|
|
158
157
|
function renderWithD2(binary, sourcePath, outPath, { format, theme }) {
|
|
159
158
|
const args = [];
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
const themeName = theme?.toLowerCase();
|
|
160
|
+
if (themeName === 'sketch') {
|
|
161
|
+
args.push('--sketch');
|
|
162
|
+
} else if (themeName) {
|
|
163
|
+
const themeId = D2_THEMES[themeName];
|
|
162
164
|
if (themeId != null) args.push('--theme', String(themeId));
|
|
163
165
|
}
|
|
164
166
|
args.push(sourcePath, outPath);
|
|
@@ -176,7 +178,6 @@ const D2_THEMES = {
|
|
|
176
178
|
'colorblind-clear': 8,
|
|
177
179
|
'vanilla-nitro-cola': 100,
|
|
178
180
|
'orange-creamsicle': 101,
|
|
179
|
-
sketch: 200,
|
|
180
181
|
};
|
|
181
182
|
|
|
182
183
|
function renderWithDot(binary, sourcePath, outPath, { format }) {
|
|
@@ -193,7 +194,7 @@ installed, writes the diagram SOURCE plus an install hint and exits 0.
|
|
|
193
194
|
Options:
|
|
194
195
|
--type <t> architecture (default) | flow | sequence | state | er | class
|
|
195
196
|
--format <f> svg (default) | png
|
|
196
|
-
--theme <name> D2 theme name (
|
|
197
|
+
--theme <name> D2 theme name (default: neutral; sketch enables --sketch hand-drawn)
|
|
197
198
|
--out <path> Output path (default: .cx/diagrams/<slug>-<ts>.<ext>)
|
|
198
199
|
--source-only Always write the source file; skip rendering
|
|
199
200
|
-h, --help Show this message
|
|
@@ -205,7 +206,7 @@ Renderers (detected at runtime, never bundled):
|
|
|
205
206
|
Examples:
|
|
206
207
|
construct diagram "web app: client -> api -> db"
|
|
207
208
|
construct diagram "auth flow" --type flow
|
|
208
|
-
construct diagram "client -> api -> db" --format png --theme
|
|
209
|
+
construct diagram "client -> api -> db" --format png --theme neutral
|
|
209
210
|
`);
|
|
210
211
|
}
|
|
211
212
|
|
|
@@ -245,7 +246,8 @@ export async function runDiagramCli(argv = [], { cwd = process.cwd() } = {}) {
|
|
|
245
246
|
process.exit(1);
|
|
246
247
|
}
|
|
247
248
|
|
|
248
|
-
const
|
|
249
|
+
const effectiveTheme = options.theme || 'neutral';
|
|
250
|
+
const result = generateDiagram({ description: options.description, type: options.type, theme: effectiveTheme });
|
|
249
251
|
const renderer = options.sourceOnly ? null : locateRenderer();
|
|
250
252
|
|
|
251
253
|
const slug = slugify(options.description);
|
|
@@ -283,7 +285,7 @@ export async function runDiagramCli(argv = [], { cwd = process.cwd() } = {}) {
|
|
|
283
285
|
|
|
284
286
|
const outPath = `${baseName}.${options.format}`;
|
|
285
287
|
const render = renderer.engine === 'd2'
|
|
286
|
-
? renderWithD2(renderer.binary, sourcePath, outPath, { format: options.format, theme:
|
|
288
|
+
? renderWithD2(renderer.binary, sourcePath, outPath, { format: options.format, theme: effectiveTheme })
|
|
287
289
|
: renderWithDot(renderer.binary, sourcePath, outPath, { format: options.format });
|
|
288
290
|
|
|
289
291
|
if (render.status === 0 && existsSync(outPath)) {
|
package/lib/docs-verify.mjs
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import fs from "node:fs";
|
|
21
21
|
import path from "node:path";
|
|
22
22
|
import { statSync, readFileSync, existsSync } from "node:fs";
|
|
23
|
+
import { spawnSync } from "node:child_process";
|
|
23
24
|
import { hasIntakeReference } from "./intake/traceability.mjs";
|
|
24
25
|
import { ensureCxDir } from "./project-init-shared.mjs";
|
|
25
26
|
|
|
@@ -27,6 +28,7 @@ const args = process.argv.slice(2);
|
|
|
27
28
|
const quickMode = args.includes("--quick");
|
|
28
29
|
const fixMode = args.includes("--fix");
|
|
29
30
|
const stagedMode = args.includes("--staged");
|
|
31
|
+
const strictMode = args.includes("--strict") || process.env.CI === 'true';
|
|
30
32
|
const cwdArg = args.find(arg => arg.startsWith("--cwd="));
|
|
31
33
|
const cwd = cwdArg ? path.resolve(cwdArg.split("=")[1]) : process.cwd();
|
|
32
34
|
|
|
@@ -195,6 +197,17 @@ function checkContextHasProgress(contextPath) {
|
|
|
195
197
|
// not every artifact necessarily comes from an intake packet — but the absence
|
|
196
198
|
// is a flag for the operator to either stamp the provenance or declare it
|
|
197
199
|
// intake-independent.
|
|
200
|
+
function gitTrackedMdFiles(cwd, dir) {
|
|
201
|
+
if (!fs.existsSync(dir)) return [];
|
|
202
|
+
const rel = path.relative(cwd, dir).split(path.sep).join('/');
|
|
203
|
+
const result = spawnSync('git', ['ls-files', '--', `${rel}/`], { cwd, encoding: 'utf8' });
|
|
204
|
+
if (result.status !== 0 || !result.stdout) return [];
|
|
205
|
+
return result.stdout
|
|
206
|
+
.split('\n')
|
|
207
|
+
.filter((line) => line.endsWith('.md') && !line.endsWith('/README.md'))
|
|
208
|
+
.map((line) => path.join(cwd, line));
|
|
209
|
+
}
|
|
210
|
+
|
|
198
211
|
function checkIntakeTraceability(cwd) {
|
|
199
212
|
const intakeFedDirs = [
|
|
200
213
|
path.join(cwd, 'docs', 'prd'),
|
|
@@ -203,13 +216,10 @@ function checkIntakeTraceability(cwd) {
|
|
|
203
216
|
];
|
|
204
217
|
let inspected = 0;
|
|
205
218
|
for (const dir of intakeFedDirs) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
|
|
211
|
-
if (entry.name.startsWith('_') || entry.name.toLowerCase() === 'readme.md') continue;
|
|
212
|
-
const filePath = path.join(dir, entry.name);
|
|
219
|
+
const files = gitTrackedMdFiles(cwd, dir);
|
|
220
|
+
for (const filePath of files) {
|
|
221
|
+
const base = path.basename(filePath);
|
|
222
|
+
if (base.startsWith('_')) continue;
|
|
213
223
|
inspected += 1;
|
|
214
224
|
if (!hasIntakeReference(filePath)) {
|
|
215
225
|
warnings.push(
|
|
@@ -438,6 +448,14 @@ function main() {
|
|
|
438
448
|
console.log("💡 Run `construct init:update` to update project to current standards");
|
|
439
449
|
process.exit(1);
|
|
440
450
|
}
|
|
451
|
+
|
|
452
|
+
if (strictMode && warnings.length > 0) {
|
|
453
|
+
console.log("❌ Strict mode: warnings are blocking:");
|
|
454
|
+
for (const warning of warnings) {
|
|
455
|
+
console.log(` • ${warning}`);
|
|
456
|
+
}
|
|
457
|
+
process.exit(1);
|
|
458
|
+
}
|
|
441
459
|
|
|
442
460
|
console.log("✅ Documentation validation passed with warnings");
|
|
443
461
|
process.exit(0);
|
package/lib/doctor/index.mjs
CHANGED
|
@@ -24,9 +24,11 @@ import * as bdWatch from './watchers/bd-watch.mjs';
|
|
|
24
24
|
import * as handoffs from './watchers/handoffs.mjs';
|
|
25
25
|
import * as consistency from './watchers/consistency.mjs';
|
|
26
26
|
import * as mcpProtocol from './watchers/mcp-protocol.mjs';
|
|
27
|
+
import * as cxBudget from './watchers/cx-budget.mjs';
|
|
28
|
+
import * as graphStaleness from './watchers/graph-staleness.mjs';
|
|
27
29
|
|
|
28
30
|
const STATE_PATH = join(homedir(), '.construct', 'doctor.json');
|
|
29
|
-
const WATCHERS = [disk, cost, processPressure, serviceHealth, bdWatch, handoffs, consistency, mcpProtocol];
|
|
31
|
+
const WATCHERS = [disk, cost, processPressure, serviceHealth, bdWatch, handoffs, consistency, mcpProtocol, cxBudget, graphStaleness];
|
|
30
32
|
|
|
31
33
|
let running = false;
|
|
32
34
|
let timers = [];
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/doctor/project-adapters.mjs — doctor checks for project-scoped host adapters.
|
|
3
|
+
*
|
|
4
|
+
* When inside a Construct-managed project, verifies gitignored adapter artifacts
|
|
5
|
+
* exist for installed hosts (.cursor/mcp.json, .vscode/mcp.json, construct.mdc).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { checkProjectParity } from '../parity.mjs';
|
|
9
|
+
import { detectHostCapabilities } from '../host-capabilities.mjs';
|
|
10
|
+
|
|
11
|
+
const HOST_REQUIRES = {
|
|
12
|
+
cursor: ['cursor'],
|
|
13
|
+
vscode: ['vscode'],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function checkProjectAdaptersForDoctor({ rootDir, projectDir = rootDir }) {
|
|
17
|
+
const parity = checkProjectParity({ rootDir, projectDir });
|
|
18
|
+
if (parity.skipped) return { ok: true, label: 'Project adapters (not a Construct project)', warning: false };
|
|
19
|
+
|
|
20
|
+
const installed = new Set(
|
|
21
|
+
detectHostCapabilities()
|
|
22
|
+
.filter((h) => h.availability === 'installed')
|
|
23
|
+
.map((h) => h.host.toLowerCase().replace(/\s+/g, '')),
|
|
24
|
+
);
|
|
25
|
+
const installedIds = new Set();
|
|
26
|
+
if (installed.has('claudecode') || installed.has('claude')) installedIds.add('claude');
|
|
27
|
+
if (installed.has('vscode')) installedIds.add('vscode');
|
|
28
|
+
if (installed.has('cursor')) installedIds.add('cursor');
|
|
29
|
+
|
|
30
|
+
const failures = [];
|
|
31
|
+
for (const surface of parity.surfaces) {
|
|
32
|
+
if (surface.status === 'absent' && (surface.surface === 'cursor' || surface.surface === 'vscode')) {
|
|
33
|
+
if (installedIds.has(surface.surface)) failures.push(surface);
|
|
34
|
+
}
|
|
35
|
+
if (surface.status === 'drift') failures.push(surface);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ok = failures.length === 0;
|
|
39
|
+
const label = ok
|
|
40
|
+
? `Project adapters (${parity.summary.filter((s) => s.includes(': ok')).length}/${parity.surfaces.length} ok)`
|
|
41
|
+
: `Project adapters missing or stale — run \`npm run adapters\` (${failures.map((f) => f.surface).join(', ')})`;
|
|
42
|
+
|
|
43
|
+
return { ok, label, warning: !ok, parity };
|
|
44
|
+
}
|
|
@@ -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
|
*/
|