@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
package/lib/beads-client.mjs
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/beads-client.mjs — Wrapper for bd commands with lock‑management and queueing.
|
|
3
3
|
*
|
|
4
|
-
* VERSION 2.0:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Changes:
|
|
9
|
-
* - Reads are now lock-free (concurrent safe)
|
|
10
|
-
* - Writes use optimistic locking with automatic retry
|
|
11
|
-
* - Legacy file-based lock is fallback only
|
|
12
|
-
* - Better error messages and conflict resolution
|
|
4
|
+
* VERSION 2.0: optimistic locking by default; the exclusive file-lock fallback
|
|
5
|
+
* was retired after zero telemetry firings (construct-nhn5). Writes retry via
|
|
6
|
+
* Dolt versioning; reads are lock-free.
|
|
13
7
|
*/
|
|
14
8
|
|
|
15
9
|
import { spawnSync } from 'node:child_process';
|
|
@@ -18,14 +12,10 @@ import path from 'node:path';
|
|
|
18
12
|
import { fileURLToPath } from 'node:url';
|
|
19
13
|
|
|
20
14
|
import {
|
|
21
|
-
acquireLock,
|
|
22
|
-
releaseLock,
|
|
23
15
|
getLockStatus,
|
|
24
16
|
cleanupStaleLock,
|
|
25
|
-
enqueueRequest,
|
|
26
|
-
removeFromQueue,
|
|
27
|
-
readQueue,
|
|
28
17
|
cleanupStaleQueue,
|
|
18
|
+
readQueue,
|
|
29
19
|
} from './beads-lock.mjs';
|
|
30
20
|
|
|
31
21
|
// Anchored bead-ID format. IDs reaching the bd shell boundary must match this;
|
|
@@ -67,7 +57,7 @@ const DEFAULT_OPTIONS = {
|
|
|
67
57
|
queuePollMs: 100, // How often to check for lock release when queued
|
|
68
58
|
maxQueueWaitSeconds: 300, // Max total time allowed in queue
|
|
69
59
|
useOptimisticLocking: true, // NEW: Use optimistic locking by default
|
|
70
|
-
fallbackToLegacy:
|
|
60
|
+
fallbackToLegacy: false,
|
|
71
61
|
maxRetries: 5, // NEW: Max retries for optimistic locking
|
|
72
62
|
};
|
|
73
63
|
|
|
@@ -237,122 +227,50 @@ export async function runBd(args, options = {}) {
|
|
|
237
227
|
|
|
238
228
|
if (!opts.silent) {
|
|
239
229
|
console.error(`[beads] ⚠ Optimistic locking failed: ${result.error}`);
|
|
240
|
-
console.error(`[beads] ↘ Falling back to legacy lock...`);
|
|
241
230
|
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// ==========================================================================
|
|
245
|
-
// PATH 3: Legacy file-based locking (fallback, most conservative)
|
|
246
|
-
// ==========================================================================
|
|
247
|
-
return runWithLegacyLock(args, opts, cwd, commandDesc);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Legacy locking implementation (original behavior).
|
|
252
|
-
* Used as fallback when optimistic locking fails.
|
|
253
|
-
*/
|
|
254
|
-
async function runWithLegacyLock(args, opts, cwd, commandDesc) {
|
|
255
|
-
// Telemetry: every legacy-fallback firing is recorded so the lock+queue removal
|
|
256
|
-
// decision rests on observed data — if this never fires over a representative
|
|
257
|
-
// window, the whole exclusive-lock path is removable; if it fires, the entries
|
|
258
|
-
// name the bd errors that actually need handling (bead construct-nhn5).
|
|
259
|
-
try {
|
|
260
|
-
const { logBeadsFallback } = await import('./telemetry/beads-fallback.mjs');
|
|
261
|
-
logBeadsFallback({ command: commandDesc || args[0] || 'unknown' });
|
|
262
|
-
} catch { /* telemetry is best-effort */ }
|
|
263
|
-
|
|
264
|
-
// Clean up any stale locks before trying
|
|
265
|
-
cleanupStaleLock({ cwd });
|
|
266
|
-
cleanupStaleQueue({ cwd });
|
|
267
|
-
|
|
268
|
-
// Try to acquire the lock
|
|
269
|
-
const startWait = Date.now();
|
|
270
|
-
const lock = await acquireLock({
|
|
271
|
-
actor: opts.actor,
|
|
272
|
-
command: commandDesc,
|
|
273
|
-
timeoutSeconds: opts.timeoutSeconds,
|
|
274
|
-
cwd,
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
if (!lock) {
|
|
278
|
-
// Failed to get lock; add to queue if allowed
|
|
279
|
-
const maxQueueWaitMs = opts.maxQueueWaitSeconds * 1000;
|
|
280
|
-
if (Date.now() - startWait > maxQueueWaitMs) {
|
|
281
|
-
return {
|
|
282
|
-
success: false,
|
|
283
|
-
error: `Timeout waiting for lock after ${opts.timeoutSeconds}s`,
|
|
284
|
-
queued: false,
|
|
285
|
-
method: 'legacy-lock',
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const queueEntry = enqueueRequest({
|
|
290
|
-
actor: opts.actor,
|
|
291
|
-
command: commandDesc,
|
|
292
|
-
args,
|
|
293
|
-
cwd,
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
if (!opts.silent) {
|
|
297
|
-
console.error(`[beads] ↘ Added to queue (position ${readQueue({ cwd }).length})`);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Wait in queue, polling periodically
|
|
301
|
-
while (Date.now() - startWait < opts.maxQueueWaitSeconds * 1000) {
|
|
302
|
-
await new Promise((resolve) => setTimeout(resolve, opts.queuePollMs));
|
|
303
|
-
|
|
304
|
-
cleanupStaleLock({ cwd });
|
|
305
|
-
const status = getLockStatus({ cwd });
|
|
306
|
-
if (!status.locked) {
|
|
307
|
-
// Lock free, remove from queue and try again
|
|
308
|
-
removeFromQueue(queueEntry.timestamp || queueEntry.pid, { cwd });
|
|
309
|
-
return await runBd(args, { ...opts, silent: true });
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Queue timeout
|
|
314
|
-
removeFromQueue(queueEntry.timestamp || queueEntry.pid, { cwd });
|
|
315
231
|
return {
|
|
316
232
|
success: false,
|
|
317
|
-
error:
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
method: 'legacy-lock',
|
|
233
|
+
error: result.error,
|
|
234
|
+
attempts: result.attempts,
|
|
235
|
+
method: 'optimistic-locking',
|
|
321
236
|
};
|
|
322
237
|
}
|
|
323
238
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
239
|
+
// ==========================================================================
|
|
240
|
+
// PATH 3: Legacy exclusive lock (retired — bead construct-nhn5 stage 2)
|
|
241
|
+
// ==========================================================================
|
|
242
|
+
if (!isRead && !shouldUseOptimisticLocking()) {
|
|
243
|
+
const startTime = Date.now();
|
|
244
|
+
const result = spawnSync('bd', args, {
|
|
328
245
|
cwd,
|
|
329
246
|
encoding: 'utf8',
|
|
330
247
|
env: { ...process.env, BEADS_ACTOR: opts.actor },
|
|
331
|
-
killSignal: 'SIGTERM',
|
|
332
248
|
timeout: Math.max(1, Math.round(opts.commandTimeoutSeconds * 1000)),
|
|
333
249
|
});
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
250
|
+
const execTime = Date.now() - startTime;
|
|
251
|
+
if (result.error?.code === 'ETIMEDOUT') {
|
|
252
|
+
return {
|
|
253
|
+
success: false,
|
|
254
|
+
error: `bd ${commandDesc} timed out after ${opts.commandTimeoutSeconds}s`,
|
|
255
|
+
exitCode: null,
|
|
256
|
+
execTimeMs: execTime,
|
|
257
|
+
method: 'direct-write',
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
success: result.status === 0 && !result.error,
|
|
262
|
+
output: result.stdout,
|
|
263
|
+
error: result.stderr || result.error?.message,
|
|
264
|
+
exitCode: result.status,
|
|
265
|
+
execTimeMs: execTime,
|
|
266
|
+
method: 'direct-write',
|
|
267
|
+
};
|
|
343
268
|
}
|
|
344
269
|
|
|
345
270
|
return {
|
|
346
|
-
success:
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
? `bd ${commandDesc} timed out after ${opts.commandTimeoutSeconds}s`
|
|
350
|
-
: (result.stderr || result.error?.message),
|
|
351
|
-
exitCode: result.status,
|
|
352
|
-
waitTimeMs: execStart - startWait,
|
|
353
|
-
execTimeMs: execTime,
|
|
354
|
-
totalTimeMs: totalTime,
|
|
355
|
-
method: 'legacy-lock',
|
|
271
|
+
success: false,
|
|
272
|
+
error: 'Unexpected beads routing state',
|
|
273
|
+
method: 'unknown',
|
|
356
274
|
};
|
|
357
275
|
}
|
|
358
276
|
|
|
@@ -405,9 +323,8 @@ export async function claimIssue(id, options = {}) {
|
|
|
405
323
|
if (result.success) {
|
|
406
324
|
return { success: true, output: JSON.stringify(result.result) };
|
|
407
325
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
return runBd(['update', id, '--claim'], { ...options, useOptimisticLocking: false });
|
|
326
|
+
|
|
327
|
+
return runBd(['update', id, '--claim'], { ...options, useOptimisticLocking: true });
|
|
411
328
|
}
|
|
412
329
|
|
|
413
330
|
/**
|
|
@@ -498,7 +415,18 @@ export async function getHumanStatus(cwd = process.cwd()) {
|
|
|
498
415
|
|
|
499
416
|
// Optimistic locking status
|
|
500
417
|
out += 'Locking Strategy:\n';
|
|
501
|
-
out += ` Mode: ${shouldUseOptimisticLocking() ? 'Optimistic (concurrent reads + versioned writes)' : '
|
|
418
|
+
out += ` Mode: ${shouldUseOptimisticLocking() ? 'Optimistic (concurrent reads + versioned writes)' : 'Direct writes (optimistic disabled via CONSTRUCT_BEADS_LEGACY_LOCK=1)'}\n`;
|
|
419
|
+
out += ' Exclusive file-lock fallback: retired (construct-nhn5)\n';
|
|
420
|
+
|
|
421
|
+
try {
|
|
422
|
+
const { readBeadsFallbacks } = await import('./telemetry/beads-fallback.mjs');
|
|
423
|
+
const history = readBeadsFallbacks();
|
|
424
|
+
if (history.length > 0) {
|
|
425
|
+
out += ` Historical fallback log entries: ${history.length} (~/.cx/beads-fallback.jsonl)\n`;
|
|
426
|
+
}
|
|
427
|
+
} catch {
|
|
428
|
+
/* optional archaeology */
|
|
429
|
+
}
|
|
502
430
|
|
|
503
431
|
// Try to get stats if available
|
|
504
432
|
try {
|
package/lib/beads-optimistic.mjs
CHANGED
|
@@ -280,7 +280,7 @@ export async function runBeadsOperation(operation, options = {}) {
|
|
|
280
280
|
type = 'read', // 'read' | 'write' | 'claim' | 'update'
|
|
281
281
|
beadId,
|
|
282
282
|
execute,
|
|
283
|
-
fallbackToLegacy =
|
|
283
|
+
fallbackToLegacy = false,
|
|
284
284
|
cwd = process.cwd(),
|
|
285
285
|
} = options;
|
|
286
286
|
|
|
@@ -302,16 +302,9 @@ export async function runBeadsOperation(operation, options = {}) {
|
|
|
302
302
|
if (result.success || !fallbackToLegacy) {
|
|
303
303
|
return result;
|
|
304
304
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
console.error(`[beads] Optimistic locking failed, falling back to legacy lock: ${result.error}`);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Legacy path: use the file-based lock
|
|
311
|
-
if (fallbackToLegacy) {
|
|
312
|
-
const { runBd } = await import('./beads-client.mjs');
|
|
313
|
-
return runBd([operation], { cwd });
|
|
305
|
+
|
|
306
|
+
return { success: false, error: result.error || 'Optimistic write failed' };
|
|
314
307
|
}
|
|
315
|
-
|
|
316
|
-
return { success: false, error: 'Operation failed
|
|
308
|
+
|
|
309
|
+
return { success: false, error: 'Operation failed' };
|
|
317
310
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/brand-fonts.mjs — bundled Space Grotesk / JetBrains Mono paths and PPTX font embedding.
|
|
3
|
+
*
|
|
4
|
+
* PDF/Typst reads TTF from templates/distribution/fonts/. HTML/deck templates load
|
|
5
|
+
* the same families from Google Fonts. PPTX uses pptx-embed-fonts when available so
|
|
6
|
+
* PowerPoint renders the bundled faces without a local install. Space Grotesk ships
|
|
7
|
+
* as a single weight-axis variable file; JetBrains Mono as discrete cuts.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { createRequire } from 'node:module';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { BRAND_TOKENS } from './brand-tokens.mjs';
|
|
15
|
+
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
18
|
+
|
|
19
|
+
export const BRAND_SANS_FAMILY = BRAND_TOKENS.typography.fontSans;
|
|
20
|
+
export const BRAND_MONO_FAMILY = BRAND_TOKENS.typography.fontMono;
|
|
21
|
+
|
|
22
|
+
export const BUNDLED_SANS_FILES = Object.freeze([
|
|
23
|
+
'SpaceGrotesk-Variable.ttf',
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
export const BUNDLED_MONO_FILES = Object.freeze([
|
|
27
|
+
'JetBrainsMono-Regular.ttf',
|
|
28
|
+
'JetBrainsMono-Medium.ttf',
|
|
29
|
+
'JetBrainsMono-SemiBold.ttf',
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
export function distributionFontsDir(repoRoot = REPO_ROOT) {
|
|
33
|
+
return path.join(repoRoot, 'templates', 'distribution', 'fonts');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function bundledSansFontPaths(repoRoot = REPO_ROOT) {
|
|
37
|
+
const dir = distributionFontsDir(repoRoot);
|
|
38
|
+
return BUNDLED_SANS_FILES.map((file) => path.join(dir, file)).filter((p) => fs.existsSync(p));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function bundledMonoFontPaths(repoRoot = REPO_ROOT) {
|
|
42
|
+
const dir = distributionFontsDir(repoRoot);
|
|
43
|
+
return BUNDLED_MONO_FILES.map((file) => path.join(dir, file)).filter((p) => fs.existsSync(p));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createPptxGenerator() {
|
|
47
|
+
const PptxGenJS = require('pptxgenjs');
|
|
48
|
+
try {
|
|
49
|
+
const { withPPTXEmbedFonts } = require('pptx-embed-fonts/pptxgenjs');
|
|
50
|
+
return { PptxClass: withPPTXEmbedFonts(PptxGenJS), embedFonts: true };
|
|
51
|
+
} catch {
|
|
52
|
+
return { PptxClass: PptxGenJS, embedFonts: false };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function embedFamilyInPptx(pptx, family, paths) {
|
|
57
|
+
let count = 0;
|
|
58
|
+
for (const fontPath of paths) {
|
|
59
|
+
const buf = fs.readFileSync(fontPath);
|
|
60
|
+
await pptx.addFont({
|
|
61
|
+
fontFace: family,
|
|
62
|
+
fontFile: buf,
|
|
63
|
+
fontType: 'ttf',
|
|
64
|
+
});
|
|
65
|
+
count += 1;
|
|
66
|
+
}
|
|
67
|
+
return count;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function embedBundledSansInPptx(pptx, repoRoot = REPO_ROOT) {
|
|
71
|
+
if (typeof pptx?.addFont !== 'function') return { embedded: false, count: 0 };
|
|
72
|
+
const count = await embedFamilyInPptx(pptx, BRAND_SANS_FAMILY, bundledSansFontPaths(repoRoot));
|
|
73
|
+
return { embedded: count > 0, count };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function embedBundledMonoInPptx(pptx, repoRoot = REPO_ROOT) {
|
|
77
|
+
if (typeof pptx?.addFont !== 'function') return { embedded: false, count: 0 };
|
|
78
|
+
const count = await embedFamilyInPptx(pptx, BRAND_MONO_FAMILY, bundledMonoFontPaths(repoRoot));
|
|
79
|
+
return { embedded: count > 0, count };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function googleFontsSansHref() {
|
|
83
|
+
return 'https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Space+Grotesk:wght@400;500;600;700&display=swap';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function pdfUsesBundledBrandSans(pdfPath) {
|
|
87
|
+
try {
|
|
88
|
+
const text = fs.readFileSync(pdfPath).toString('latin1');
|
|
89
|
+
return /SpaceGrotesk|Space Grotesk|JetBrainsMono|JetBrains Mono/.test(text);
|
|
90
|
+
} catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/brand-prose.mjs — marketing voice, retired typography, and Construct naming lint.
|
|
3
|
+
*
|
|
4
|
+
* Shared by scripts/audit/03d-brand.mjs, lib/hooks/brand-prose-lint.mjs, and
|
|
5
|
+
* `construct lint:brand`. Scans markdown prose only (skips fenced code and inline code).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
export const MARKETING_VOICE_RE =
|
|
12
|
+
/\b(robust|enterprise-grade|best-in-class|world-class|cutting-edge|powerful|battle-tested|seamless)\b/i;
|
|
13
|
+
|
|
14
|
+
export const RETIRED_FONT_RE =
|
|
15
|
+
/Plus\s+Jakarta|PlusJakarta|Geist(?:Mono)?|IBM\s+Plex|Libertinus|font-family:\s*["']?Inter["']?/i;
|
|
16
|
+
|
|
17
|
+
const CLI_SUBCOMMANDS =
|
|
18
|
+
'doctor|init|sync|dev|stop|status|intake|chat|install|drop|search|prune|upgrade|config|list|graph|beads|export|publish|artifact|demo|oracle|ci|acp|profile|models|uninstall|reconcile|embed|ingest|classify|workflow|capability';
|
|
19
|
+
|
|
20
|
+
const PRODUCT_NOUN_PHRASES =
|
|
21
|
+
/\bConstruct (?:install|MCP server|MCP tools?|workflow|chat|package|repo|surfaces?|integration|orchestrator|daemon|specialists?|persona|brand|tooling|standards|config|adapters?|platform|version|release|hooks?|memory|loop|Dashboard|Chat|capability)\b/i;
|
|
22
|
+
|
|
23
|
+
const UNBACKTICKED_CLI_RE = new RegExp(
|
|
24
|
+
`(?<![\`~/])construct\\s+(${CLI_SUBCOMMANDS})(?:\\s+[\\w:-]+)*\\b`,
|
|
25
|
+
'i',
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const MISCAPITALIZED_CLI_RE = new RegExp(
|
|
29
|
+
`\\bConstruct\\s+(${CLI_SUBCOMMANDS})(?:\\s+[\\w:-]+)*\\b`,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const MARKETING_SCAN_ROOTS = ['docs', 'skills', 'specialists', 'personas', 'templates', 'rules'];
|
|
33
|
+
const MARKETING_SCAN_EXTS = ['.md', '.mdx'];
|
|
34
|
+
const FONT_SCAN_EXTS = ['.mjs', '.js', '.md', '.mdx', '.json', '.html', '.css', '.tsx', '.ts', '.jsx', '.typ'];
|
|
35
|
+
|
|
36
|
+
const EXCLUDE_PATH =
|
|
37
|
+
/(node_modules|\.git|audit-artifacts|scripts\/audit|CHANGELOG\.md|fonts\/legacy\/|\.next\/|\/out\/|tests\/e2e\/reports\/|package-lock\.json)/;
|
|
38
|
+
|
|
39
|
+
const DOC_EXEMPT =
|
|
40
|
+
/(?:^|\/)(docs\/STYLE\.md|docs\/reference\/branding\.md|templates\/distribution\/fonts\/README\.md)$/;
|
|
41
|
+
|
|
42
|
+
const HOOK_SCOPED =
|
|
43
|
+
/^(docs\/|skills\/|specialists\/|personas\/|templates\/|rules\/|apps\/(dashboard|docs|chat)\/)/;
|
|
44
|
+
|
|
45
|
+
export function isBrandHookPath(relPath) {
|
|
46
|
+
if (!relPath || EXCLUDE_PATH.test(relPath) || DOC_EXEMPT.test(relPath)) return false;
|
|
47
|
+
return HOOK_SCOPED.test(relPath.replace(/\\/g, '/'));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function splitProseLines(content) {
|
|
51
|
+
const prose = [];
|
|
52
|
+
let inFence = false;
|
|
53
|
+
const lines = content.split('\n');
|
|
54
|
+
for (let i = 0; i < lines.length; i++) {
|
|
55
|
+
const line = lines[i];
|
|
56
|
+
const trimmed = line.trim();
|
|
57
|
+
if (trimmed.startsWith('```')) {
|
|
58
|
+
inFence = !inFence;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (inFence) continue;
|
|
62
|
+
prose.push({ line: i + 1, text: line });
|
|
63
|
+
}
|
|
64
|
+
return prose;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function stripInlineCode(text) {
|
|
68
|
+
return text.replace(/`[^`]*`/g, '');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isMarketingAllowlisted(relPath, lineText) {
|
|
72
|
+
if (/skills\/roles\//.test(relPath) && /["']robust["']|POUR|Symptom/i.test(lineText)) return true;
|
|
73
|
+
if (/<!--/.test(lineText)) return true;
|
|
74
|
+
if (/\b(WCAG|Perceivable, Operable)/i.test(lineText) && /\bRobust\b/.test(lineText)) return true;
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isNamingAllowlisted(relPath, lineText) {
|
|
79
|
+
const trimmed = lineText.trim();
|
|
80
|
+
if (/^#+\s+construct\s+/i.test(trimmed)) return true;
|
|
81
|
+
if (/^title:\s/i.test(trimmed)) return true;
|
|
82
|
+
if (/^description:\s/i.test(trimmed)) return true;
|
|
83
|
+
if (/node\s+bin\/construct/.test(lineText)) return true;
|
|
84
|
+
if (/CONSTRUCT_[A-Z_]+=/.test(lineText) && /construct\s+sync/.test(lineText)) return true;
|
|
85
|
+
if ((lineText.match(/\|/g) || []).length >= 2) return true;
|
|
86
|
+
if (/cli:construct/.test(lineText)) return true;
|
|
87
|
+
if (/mcp:.*construct/.test(lineText)) return true;
|
|
88
|
+
if (/\{\s*label:\s*['`]construct/.test(lineText)) return true;
|
|
89
|
+
if (PRODUCT_NOUN_PHRASES.test(lineText)) return true;
|
|
90
|
+
if (/verified_by:\s*construct\b/.test(lineText)) return true;
|
|
91
|
+
if (/construct_schema_migrations/.test(lineText)) return true;
|
|
92
|
+
if (/non-Construct/.test(lineText)) return true;
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function lintMarketingVoiceLine(relPath, lineNumber, lineText) {
|
|
97
|
+
const prose = stripInlineCode(lineText);
|
|
98
|
+
if (!MARKETING_VOICE_RE.test(prose)) return null;
|
|
99
|
+
if (isMarketingAllowlisted(relPath, lineText)) return null;
|
|
100
|
+
const match = prose.match(MARKETING_VOICE_RE);
|
|
101
|
+
return {
|
|
102
|
+
kind: 'marketing-voice',
|
|
103
|
+
line: lineNumber,
|
|
104
|
+
token: match?.[0] ?? 'marketing',
|
|
105
|
+
text: lineText.trim().slice(0, 140),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function lintRetiredFontLine(relPath, lineNumber, lineText) {
|
|
110
|
+
if (!RETIRED_FONT_RE.test(lineText)) return null;
|
|
111
|
+
return {
|
|
112
|
+
kind: 'retired-font',
|
|
113
|
+
line: lineNumber,
|
|
114
|
+
text: lineText.trim().slice(0, 140),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function lintConstructNamingLine(relPath, lineNumber, lineText) {
|
|
119
|
+
if (!/\.mdx?$/.test(relPath)) return null;
|
|
120
|
+
if (isNamingAllowlisted(relPath, lineText)) return null;
|
|
121
|
+
const prose = stripInlineCode(lineText);
|
|
122
|
+
const miscap = prose.match(MISCAPITALIZED_CLI_RE);
|
|
123
|
+
if (miscap) {
|
|
124
|
+
return {
|
|
125
|
+
kind: 'construct-naming',
|
|
126
|
+
line: lineNumber,
|
|
127
|
+
text: lineText.trim().slice(0, 140),
|
|
128
|
+
detail: `Use lowercase CLI form: \`construct ${miscap[1]}\``,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (/^\s*\d+\.\s/.test(lineText) || /^\s*-\s/.test(lineText)) {
|
|
132
|
+
const unback = prose.match(UNBACKTICKED_CLI_RE);
|
|
133
|
+
if (unback) {
|
|
134
|
+
return {
|
|
135
|
+
kind: 'construct-naming',
|
|
136
|
+
line: lineNumber,
|
|
137
|
+
text: lineText.trim().slice(0, 140),
|
|
138
|
+
detail: `Backtick CLI invocations: \`construct ${unback[1]}...\``,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function lintMarkdownBrand(content, { relPath = '', checks = ['marketing', 'naming', 'font'] } = {}) {
|
|
146
|
+
const violations = [];
|
|
147
|
+
for (const { line, text } of splitProseLines(content)) {
|
|
148
|
+
if (checks.includes('marketing')) {
|
|
149
|
+
const hit = lintMarketingVoiceLine(relPath, line, text);
|
|
150
|
+
if (hit) violations.push(hit);
|
|
151
|
+
}
|
|
152
|
+
if (checks.includes('naming')) {
|
|
153
|
+
const hit = lintConstructNamingLine(relPath, line, text);
|
|
154
|
+
if (hit) violations.push(hit);
|
|
155
|
+
}
|
|
156
|
+
if (checks.includes('font')) {
|
|
157
|
+
const hit = lintRetiredFontLine(relPath, line, text);
|
|
158
|
+
if (hit) violations.push(hit);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return violations;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function walk(dir, exts, out = []) {
|
|
165
|
+
if (!fs.existsSync(dir)) return out;
|
|
166
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
167
|
+
const full = path.join(dir, e.name);
|
|
168
|
+
if (EXCLUDE_PATH.test(full)) continue;
|
|
169
|
+
if (e.isDirectory()) walk(full, exts, out);
|
|
170
|
+
else if (exts.some((x) => e.name.endsWith(x))) out.push(full);
|
|
171
|
+
}
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function scanRepoBrandProse(rootDir, { checks = ['marketing', 'naming', 'font'] } = {}) {
|
|
176
|
+
const hits = [];
|
|
177
|
+
for (const base of MARKETING_SCAN_ROOTS) {
|
|
178
|
+
const abs = path.join(rootDir, base);
|
|
179
|
+
for (const file of walk(abs, [...MARKETING_SCAN_EXTS, ...(checks.includes('font') ? FONT_SCAN_EXTS : [])])) {
|
|
180
|
+
const rel = path.relative(rootDir, file).replace(/\\/g, '/');
|
|
181
|
+
if (DOC_EXEMPT.test(rel)) continue;
|
|
182
|
+
const ext = path.extname(file);
|
|
183
|
+
const fileChecks = checks.filter((c) => c !== 'font' || FONT_SCAN_EXTS.includes(ext));
|
|
184
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
185
|
+
for (const v of lintMarkdownBrand(content, { relPath: rel, checks: fileChecks })) {
|
|
186
|
+
hits.push({ file: rel, ...v });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return hits;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function lintFile(absPath, { rootDir = process.cwd() } = {}) {
|
|
194
|
+
const rel = path.relative(rootDir, absPath).replace(/\\/g, '/');
|
|
195
|
+
if (!isBrandHookPath(rel)) return { rel, violations: [] };
|
|
196
|
+
const ext = path.extname(absPath).toLowerCase();
|
|
197
|
+
if (!['.md', '.mdx', '.html', '.css', '.tsx', '.ts', '.jsx', '.typ'].includes(ext)) {
|
|
198
|
+
return { rel, violations: [] };
|
|
199
|
+
}
|
|
200
|
+
const content = fs.readFileSync(absPath, 'utf8');
|
|
201
|
+
const checks = ['.md', '.mdx'].includes(ext) ? ['marketing', 'naming', 'font'] : ['font'];
|
|
202
|
+
return { rel, violations: lintMarkdownBrand(content, { relPath: rel, checks }) };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function formatBrandViolations(results) {
|
|
206
|
+
const lines = [];
|
|
207
|
+
for (const { rel, violations } of results) {
|
|
208
|
+
for (const v of violations) {
|
|
209
|
+
const detail = v.detail ? ` — ${v.detail}` : '';
|
|
210
|
+
lines.push(`${rel}:${v.line} [${v.kind}] ${v.text}${detail}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return lines.join('\n');
|
|
214
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/brand-tokens.mjs — Construct distribution brand primitives.
|
|
3
|
+
*
|
|
4
|
+
* Monochrome ink ramp from templates/distribution/construct-brand.typ plus Space
|
|
5
|
+
* Grotesk / JetBrains Mono typography. INK/FONTS/STATUS feed chat surfaces;
|
|
6
|
+
* BRAND_TOKENS feed publish exports, deck HTML, and PPTX.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const INK = Object.freeze({
|
|
10
|
+
ink: '#0a0c10',
|
|
11
|
+
inkStrong: '#16191f',
|
|
12
|
+
inkBody: '#23272e',
|
|
13
|
+
muted: '#565c66',
|
|
14
|
+
faint: '#9499a2',
|
|
15
|
+
hairline: '#e3e4e8',
|
|
16
|
+
hairlineStrong: '#cdd0d6',
|
|
17
|
+
surface: '#fafafa',
|
|
18
|
+
surfaceAlt: '#f3f4f6',
|
|
19
|
+
paper: '#ffffff',
|
|
20
|
+
navy: '#0c1018',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const FONTS = Object.freeze({
|
|
24
|
+
sans: "'Space Grotesk', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
|
25
|
+
mono: "'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const STATUS = Object.freeze({
|
|
29
|
+
ok: '#98c379',
|
|
30
|
+
warn: '#e5c07b',
|
|
31
|
+
danger: '#e06c75',
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const BRAND_TOKENS = Object.freeze({
|
|
35
|
+
ink: Object.freeze({
|
|
36
|
+
default: INK.ink,
|
|
37
|
+
strong: INK.inkStrong,
|
|
38
|
+
body: INK.inkBody,
|
|
39
|
+
muted: INK.muted,
|
|
40
|
+
faint: INK.faint,
|
|
41
|
+
}),
|
|
42
|
+
line: Object.freeze({
|
|
43
|
+
hairline: INK.hairline,
|
|
44
|
+
hairlineStrong: INK.hairlineStrong,
|
|
45
|
+
}),
|
|
46
|
+
surface: Object.freeze({
|
|
47
|
+
default: INK.surface,
|
|
48
|
+
alt: INK.surfaceAlt,
|
|
49
|
+
paper: INK.paper,
|
|
50
|
+
}),
|
|
51
|
+
navy: INK.navy,
|
|
52
|
+
typography: Object.freeze({
|
|
53
|
+
fontSans: 'Space Grotesk',
|
|
54
|
+
fontDisplay: 'Space Grotesk',
|
|
55
|
+
fontMono: 'JetBrains Mono',
|
|
56
|
+
fontStack: FONTS.sans,
|
|
57
|
+
fontStackMono: FONTS.mono,
|
|
58
|
+
weight: Object.freeze({
|
|
59
|
+
regular: 400,
|
|
60
|
+
medium: 500,
|
|
61
|
+
semibold: 600,
|
|
62
|
+
bold: 700,
|
|
63
|
+
}),
|
|
64
|
+
size: Object.freeze({
|
|
65
|
+
micro: '8pt',
|
|
66
|
+
small: '8.5pt',
|
|
67
|
+
meta: '9pt',
|
|
68
|
+
body: '10pt',
|
|
69
|
+
h4: '8.5pt',
|
|
70
|
+
h3: '11pt',
|
|
71
|
+
h2: '13pt',
|
|
72
|
+
h1: '17pt',
|
|
73
|
+
subtitle: '11.5pt',
|
|
74
|
+
title: '24pt',
|
|
75
|
+
}),
|
|
76
|
+
}),
|
|
77
|
+
layout: Object.freeze({
|
|
78
|
+
figureMaxWidth: '58%',
|
|
79
|
+
slideAspect: '16 / 9',
|
|
80
|
+
}),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
export const BRAND = Object.freeze({
|
|
84
|
+
accent: INK.ink,
|
|
85
|
+
accentWarm: INK.ink,
|
|
86
|
+
navy: INK.navy,
|
|
87
|
+
ink: INK.inkStrong,
|
|
88
|
+
muted: INK.muted,
|
|
89
|
+
surface: INK.surface,
|
|
90
|
+
surfaceAlt: INK.surfaceAlt,
|
|
91
|
+
tableHeader: INK.surfaceAlt,
|
|
92
|
+
});
|