@geraldmaron/construct 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -18
- package/bin/construct +392 -91
- package/bin/construct-postinstall.mjs +30 -2
- package/examples/distribution/README.md +42 -0
- package/examples/distribution/manifest.json +53 -0
- package/examples/distribution/sources/adr.md +84 -0
- package/examples/distribution/sources/deck-one-pager.md +65 -0
- package/examples/distribution/sources/prd-platform.md +161 -0
- package/examples/distribution/sources/research-brief.md +80 -0
- package/examples/distribution/sources/rfc-platform.md +82 -0
- package/examples/distribution/sources/runbook.md +103 -0
- package/examples/distribution/sources/strategy.md +88 -0
- package/lib/adapters-sync.mjs +67 -0
- package/lib/artifact-gate-notice.mjs +38 -0
- package/lib/artifact-manifest.mjs +112 -0
- package/lib/artifact-release-gate.mjs +171 -0
- package/lib/artifact-reviewers.mjs +68 -0
- package/lib/artifact-type-from-path.mjs +79 -0
- package/lib/audit-skills.mjs +4 -0
- package/lib/audit-specialists.mjs +285 -0
- package/lib/audit-trail.mjs +77 -10
- package/lib/auto-docs.mjs +87 -18
- package/lib/beads/drift.mjs +27 -6
- package/lib/beads-client.mjs +49 -121
- package/lib/beads-optimistic.mjs +5 -12
- package/lib/brand-fonts.mjs +93 -0
- package/lib/brand-prose.mjs +214 -0
- package/lib/brand-tokens.mjs +92 -0
- package/lib/bridges/copilot-proxy.mjs +13 -26
- package/lib/capability-ledger.mjs +156 -0
- package/lib/certification/artifact-fixtures.mjs +132 -0
- package/lib/certification/artifact-gates.mjs +63 -0
- package/lib/certification/artifact-provenance.mjs +97 -0
- package/lib/certification/canonical-scenarios.mjs +78 -0
- package/lib/certification/cli.mjs +191 -0
- package/lib/certification/dashboard-api.mjs +71 -0
- package/lib/certification/demo-parity.mjs +116 -0
- package/lib/certification/document-io-fixtures.mjs +246 -0
- package/lib/certification/document-workflow.mjs +97 -0
- package/lib/certification/eval-bridge.mjs +77 -0
- package/lib/certification/model-routing.mjs +149 -0
- package/lib/certification/prompt-budget.mjs +119 -0
- package/lib/certification/rc-gate.mjs +206 -0
- package/lib/certification/real-llm-scenarios.mjs +303 -0
- package/lib/certification/role-cards.mjs +113 -0
- package/lib/certification/role-overlays.mjs +117 -0
- package/lib/certification/run.mjs +122 -0
- package/lib/certification/runner.mjs +323 -0
- package/lib/certification/scenarios.mjs +51 -0
- package/lib/certification/skill-inventory.mjs +289 -0
- package/lib/certification/skill-scenarios.mjs +147 -0
- package/lib/certification/specialist-contracts.mjs +85 -0
- package/lib/certification/specialist-scenarios.mjs +175 -0
- package/lib/certification/stale-impact.mjs +146 -0
- package/lib/certification/status.mjs +252 -0
- package/lib/certification/store.mjs +77 -0
- package/lib/chat/cli.mjs +333 -0
- package/lib/chat/command-suggest.mjs +161 -0
- package/lib/chat/commands.mjs +215 -0
- package/lib/chat/config.mjs +142 -0
- package/lib/chat/context-compactor.mjs +250 -0
- package/lib/chat/context-continuation.mjs +253 -0
- package/lib/chat/continuation-source.mjs +58 -0
- package/lib/chat/demo-guide.mjs +61 -0
- package/lib/chat/design-tokens.mjs +91 -0
- package/lib/chat/desktop-binary.mjs +79 -0
- package/lib/chat/desktop-build.mjs +130 -0
- package/lib/chat/desktop-launcher.mjs +133 -0
- package/lib/chat/evidence.mjs +145 -0
- package/lib/chat/export.mjs +74 -0
- package/lib/chat/harness/driver.mjs +91 -0
- package/lib/chat/list-picker.mjs +112 -0
- package/lib/chat/model-picker.mjs +356 -0
- package/lib/chat/openrouter-fallback.mjs +151 -0
- package/lib/chat/permission-prompt.mjs +33 -0
- package/lib/chat/picker-catalog.mjs +45 -0
- package/lib/chat/policy-telemetry.mjs +34 -0
- package/lib/chat/present.mjs +245 -0
- package/lib/chat/session-context.mjs +39 -0
- package/lib/chat/session-persist.mjs +73 -0
- package/lib/chat/session-restore.mjs +71 -0
- package/lib/chat/session-settings.mjs +53 -0
- package/lib/chat/system-prompt.mjs +52 -0
- package/lib/chat/transparency.mjs +93 -0
- package/lib/chat/tui/color-scheme.mjs +42 -0
- package/lib/chat/tui/markdown.mjs +123 -0
- package/lib/chat/tui/presentation.mjs +100 -0
- package/lib/chat/tui/render.mjs +500 -0
- package/lib/chat/tui/turn-block.mjs +284 -0
- package/lib/chat/tui/turn-present.mjs +18 -0
- package/lib/chat/tui/turn-state.mjs +88 -0
- package/lib/chat/tui/usage.mjs +122 -0
- package/lib/chat/web-commands.mjs +146 -0
- package/lib/chat/web-launcher.mjs +63 -0
- package/lib/chat/web-picker-keys.mjs +46 -0
- package/lib/chat/web-session.mjs +159 -0
- package/lib/cli-commands.mjs +232 -11
- package/lib/comment-lint.mjs +6 -4
- package/lib/config/schema.mjs +36 -5
- package/lib/contract-schemas/decision.json +50 -0
- package/lib/contract-schemas/implementation.json +51 -0
- package/lib/contract-schemas/review-report.json +32 -0
- package/lib/contract-schemas/test-report.json +43 -0
- package/lib/contracts/construct-handoff.mjs +60 -0
- package/lib/contracts/validate.mjs +32 -3
- package/lib/contracts/violation-log.mjs +58 -5
- package/lib/dashboard-demo.mjs +71 -0
- package/lib/dashboard-static.mjs +19 -5
- package/lib/decisions/registry.mjs +5 -3
- package/lib/deck-export-pptx.mjs +1152 -0
- package/lib/demo-recording.mjs +142 -0
- package/lib/demo-script.mjs +114 -0
- package/lib/demo-surface.mjs +249 -0
- package/lib/demo.mjs +703 -0
- package/lib/diagram-export.mjs +192 -0
- package/lib/diagram.mjs +302 -0
- package/lib/docs-verify.mjs +25 -7
- package/lib/doctor/index.mjs +4 -2
- package/lib/doctor/project-adapters.mjs +44 -0
- package/lib/doctor/watchers/cx-budget.mjs +98 -0
- package/lib/doctor/watchers/graph-staleness.mjs +52 -0
- package/lib/doctor/watchers/process-pressure.mjs +4 -3
- package/lib/doctor/watchers/service-health.mjs +1 -1
- package/lib/document-export.mjs +360 -36
- package/lib/document-extract.mjs +88 -0
- package/lib/document-ingest.mjs +32 -18
- package/lib/embed/cli.mjs +2 -2
- package/lib/embed/daemon.mjs +18 -46
- package/lib/embed/semantic.mjs +5 -2
- package/lib/embed/worker.mjs +2 -2
- package/lib/embedded-contract/model-resolve.mjs +10 -9
- package/lib/env-config.mjs +26 -0
- package/lib/evals/dataset.mjs +137 -0
- package/lib/evals/gates.mjs +175 -0
- package/lib/graph/build-co-change.mjs +54 -0
- package/lib/graph/build-from-registry.mjs +136 -0
- package/lib/graph/build-import-graph.mjs +153 -0
- package/lib/graph/cli.mjs +110 -0
- package/lib/graph/impact-cli.mjs +89 -0
- package/lib/graph/impact.mjs +108 -0
- package/lib/graph/staleness.mjs +44 -0
- package/lib/graph/store.mjs +172 -0
- package/lib/handoffs/cleanup.mjs +1 -1
- package/lib/health-check.mjs +90 -76
- package/lib/hooks/artifact-release-gate.mjs +43 -0
- package/lib/hooks/audit-trail.mjs +14 -28
- package/lib/hooks/brand-prose-lint.mjs +38 -0
- package/lib/hooks/graph-impact-advisory.mjs +62 -0
- package/lib/hooks/session-optimize.mjs +84 -207
- package/lib/hooks/session-start.mjs +4 -5
- package/lib/host-disposition.mjs +7 -0
- package/lib/improvement/cli.mjs +189 -0
- package/lib/improvement/controller.mjs +137 -0
- package/lib/improvement/proposal.mjs +120 -0
- package/lib/improvement/specialist-loop.mjs +192 -0
- package/lib/improvement/store.mjs +89 -0
- package/lib/improvement/surface.mjs +219 -0
- package/lib/ingest-tooling.mjs +97 -0
- package/lib/init/doc-lanes.mjs +165 -0
- package/lib/init-docs.mjs +31 -178
- package/lib/init-unified.mjs +27 -97
- package/lib/init-update-guide.mjs +102 -0
- package/lib/init-update.mjs +32 -1
- package/lib/init.mjs +8 -0
- package/lib/install/desktop-binary-download.mjs +85 -0
- package/lib/install/legacy-global-cleanup.mjs +189 -0
- package/lib/intake/daemon.mjs +2 -0
- package/lib/intake/git-queue.mjs +9 -8
- package/lib/intake/queue.mjs +2 -1
- package/lib/intake/session-prelude.mjs +90 -1
- package/lib/libreoffice-export.mjs +97 -0
- package/lib/logging/rotate.mjs +9 -0
- package/lib/maintenance/docker-reclaim.mjs +206 -0
- package/lib/mcp/external-schema-cost.mjs +74 -0
- package/lib/mcp/server.mjs +84 -9
- package/lib/mcp/stdio-mcp-probe.mjs +188 -0
- package/lib/mcp/tool-budget.mjs +55 -4
- package/lib/mcp/tools/document.mjs +18 -4
- package/lib/mcp/tools/skills.mjs +32 -3
- package/lib/mcp/tools/workflow.mjs +13 -1
- package/lib/model-free-selector.mjs +18 -0
- package/lib/model-registry.mjs +13 -229
- package/lib/model-router.mjs +425 -93
- package/lib/models/behavior-matrix.mjs +289 -0
- package/lib/models/catalog.mjs +209 -0
- package/lib/models/execution-capability-profile.mjs +196 -0
- package/lib/models/execution-policy.mjs +307 -0
- package/lib/models/provider-poll.mjs +383 -0
- package/lib/npm-spawn-env.mjs +17 -0
- package/lib/ollama/installed-models.mjs +129 -0
- package/lib/oracle/actions.mjs +187 -0
- package/lib/oracle/artifact-gate.mjs +99 -0
- package/lib/oracle/cli.mjs +204 -0
- package/lib/oracle/daemon-entry.mjs +14 -0
- package/lib/oracle/dispatch.mjs +81 -0
- package/lib/oracle/execute.mjs +143 -0
- package/lib/oracle/gaps.mjs +76 -0
- package/lib/oracle/index.mjs +84 -0
- package/lib/oracle/issues.mjs +164 -0
- package/lib/oracle/org-graph.mjs +170 -0
- package/lib/oracle/policy.mjs +80 -0
- package/lib/oracle/read-model.mjs +398 -0
- package/lib/oracle/reconcile.mjs +191 -0
- package/lib/oracle/routing.mjs +89 -0
- package/lib/oracle/synthesize.mjs +384 -0
- package/lib/oracle/verdicts.mjs +51 -0
- package/lib/orchestration/worker.mjs +88 -27
- package/lib/orchestration-policy.mjs +50 -0
- package/lib/parity.mjs +96 -2
- package/lib/persona-sections.mjs +66 -0
- package/lib/playwright-demo.mjs +252 -0
- package/lib/project-init-shared.mjs +4 -1
- package/lib/prompt-composer.js +9 -4
- package/lib/prompt-validation-contract.mjs +24 -0
- package/lib/provider-capabilities.js +57 -11
- package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
- package/lib/providers/contract/adapters/git/index.mjs +115 -0
- package/lib/providers/contract/adapters/github/index.mjs +166 -0
- package/lib/providers/contract/adapters/jira/index.mjs +187 -0
- package/lib/providers/contract/adapters/slack/index.mjs +175 -0
- package/lib/providers/contract/contract-tests.mjs +57 -0
- package/lib/providers/contract/errors.mjs +48 -0
- package/lib/providers/contract/interface.mjs +50 -0
- package/lib/providers/contract/registry.mjs +102 -0
- package/lib/providers/copilot-auth.mjs +297 -0
- package/lib/providers/credential-bootstrap.mjs +178 -0
- package/lib/providers/credential-catalog.mjs +46 -0
- package/lib/providers/credential-sources.mjs +63 -0
- package/lib/providers/creds.mjs +5 -2
- package/lib/providers/op-run.mjs +59 -0
- package/lib/providers/secret-resolver.mjs +159 -0
- package/lib/publish-template.mjs +163 -0
- package/lib/publish-tooling.mjs +119 -0
- package/lib/publish.mjs +305 -0
- package/lib/registry/cli.mjs +82 -0
- package/lib/registry/consolidation.mjs +147 -0
- package/lib/registry/generate-docs.mjs +75 -0
- package/lib/registry/skill-verification.mjs +57 -0
- package/lib/registry/surface-map.mjs +76 -0
- package/lib/registry/validate.mjs +135 -0
- package/lib/resources/budget.mjs +82 -0
- package/lib/resources/process-budget.mjs +45 -0
- package/lib/rules-delivery.mjs +9 -2
- package/lib/rules-read.mjs +26 -0
- package/lib/runtime-env.mjs +23 -0
- package/lib/runtime-pressure.mjs +51 -7
- package/lib/schema-infer.mjs +13 -2
- package/lib/server/chat-loop.mjs +622 -0
- package/lib/server/demo-preview.mjs +63 -0
- package/lib/server/index.mjs +229 -8
- package/lib/server/langfuse-login.mjs +3 -3
- package/lib/service-manager.mjs +61 -9
- package/lib/session-store.mjs +1 -1
- package/lib/setup.mjs +102 -2
- package/lib/specialists/prompt-schema.mjs +19 -10
- package/lib/specialists/roster.mjs +43 -0
- package/lib/specialists/scaffold.mjs +56 -0
- package/lib/storage/backend.mjs +6 -6
- package/lib/storage/embeddings-local.mjs +6 -3
- package/lib/storage/file-lock.mjs +18 -11
- package/lib/storage/hybrid-query.mjs +7 -4
- package/lib/storage/state-source.mjs +5 -5
- package/lib/storage/sync.mjs +2 -3
- package/lib/telemetry/rule-calls.mjs +52 -0
- package/lib/template-registry.mjs +2 -2
- package/lib/templates/visual-requirements.mjs +27 -51
- package/lib/test-corpus-inventory.mjs +313 -0
- package/lib/uninstall/uninstall.mjs +19 -7
- package/lib/update.mjs +12 -0
- package/lib/upgrade.mjs +14 -0
- package/lib/wireframe.mjs +20 -14
- package/lib/worker/run.mjs +17 -4
- package/lib/worker/trace.mjs +11 -3
- package/package.json +29 -13
- package/personas/construct.md +13 -13
- package/platforms/claude/settings.template.json +36 -0
- package/rules/common/patterns.md +1 -1
- package/rules/common/release-gates.md +4 -3
- package/scripts/sync-specialists.mjs +187 -60
- package/skills/devops/data-engineering.md +1 -1
- package/skills/docs/adr-workflow.md +1 -0
- package/skills/docs/backlog-proposal-workflow.md +1 -0
- package/skills/docs/codebase-research-workflow.md +40 -0
- package/skills/docs/customer-profile-workflow.md +1 -0
- package/skills/docs/document-ingest-workflow.md +1 -0
- package/skills/docs/evidence-ingest-workflow.md +1 -0
- package/skills/docs/init-docs.md +2 -2
- package/skills/docs/init-project.md +8 -3
- package/skills/docs/prd-workflow.md +24 -1
- package/skills/docs/prfaq-workflow.md +1 -0
- package/skills/docs/product-intelligence-workflow.md +1 -0
- package/skills/docs/product-signal-workflow.md +1 -0
- package/skills/docs/research-workflow.md +54 -37
- package/skills/docs/runbook-workflow.md +1 -0
- package/skills/docs/strategy-workflow.md +1 -0
- package/skills/docs/user-research-workflow.md +40 -0
- package/skills/operating/orchestration-reference.md +1 -1
- package/skills/roles/architect.md +5 -0
- package/skills/roles/operator.docs.md +4 -0
- package/skills/routing.md +4 -2
- package/specialists/artifact-manifest.json +480 -0
- package/specialists/artifact-manifest.schema.json +53 -0
- package/specialists/audit-enrichments.json +454 -0
- package/specialists/contracts.json +37 -25
- package/specialists/prompts/_shared/validation-contract.md +26 -0
- package/specialists/prompts/cx-accessibility.md +21 -2
- package/specialists/prompts/cx-ai-engineer.md +24 -1
- package/specialists/prompts/cx-architect.md +4 -1
- package/specialists/prompts/cx-business-strategist.md +23 -2
- package/specialists/prompts/cx-data-analyst.md +22 -1
- package/specialists/prompts/cx-data-engineer.md +23 -2
- package/specialists/prompts/cx-debugger.md +21 -2
- package/specialists/prompts/cx-designer.md +26 -3
- package/specialists/prompts/cx-devil-advocate.md +19 -2
- package/specialists/prompts/cx-docs-keeper.md +28 -1
- package/specialists/prompts/cx-engineer.md +22 -1
- package/specialists/prompts/cx-evaluator.md +18 -1
- package/specialists/prompts/cx-explorer.md +21 -2
- package/specialists/prompts/cx-legal-compliance.md +21 -2
- package/specialists/prompts/cx-operations.md +21 -2
- package/specialists/prompts/cx-oracle.md +94 -0
- package/specialists/prompts/cx-orchestrator.md +20 -1
- package/specialists/prompts/cx-platform-engineer.md +25 -2
- package/specialists/prompts/cx-product-manager.md +32 -1
- package/specialists/prompts/cx-qa.md +24 -2
- package/specialists/prompts/cx-rd-lead.md +23 -2
- package/specialists/prompts/cx-release-manager.md +23 -2
- package/specialists/prompts/cx-researcher.md +22 -2
- package/specialists/prompts/cx-reviewer.md +18 -1
- package/specialists/prompts/cx-security.md +22 -2
- package/specialists/prompts/cx-sre.md +30 -3
- package/specialists/prompts/cx-test-automation.md +7 -1
- package/specialists/prompts/cx-trace-reviewer.md +22 -3
- package/specialists/prompts/cx-ux-researcher.md +21 -2
- package/specialists/registry.json +52 -173
- package/specialists/tone-profiles.json +42 -0
- package/templates/demos/playwright/demo-recording.config.mjs +47 -0
- package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
- package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
- package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
- package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
- package/templates/demos/tapes/resource-guard-rails.tape +49 -0
- package/templates/demos/vhs/construct-cockpit.json +24 -0
- package/templates/distribution/construct-brand.typ +446 -0
- package/templates/distribution/construct-decision.typ +38 -0
- package/templates/distribution/construct-deck.html +95 -0
- package/templates/distribution/construct-pdf.typ +38 -0
- package/templates/distribution/construct-prd.typ +38 -0
- package/templates/distribution/construct-research.typ +38 -0
- package/templates/distribution/construct-web.html +92 -0
- package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
- package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
- package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
- package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/README.md +36 -0
- package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
- package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
- package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
- package/templates/distribution/icons/activity.svg +15 -0
- package/templates/distribution/icons/alert-triangle.svg +17 -0
- package/templates/distribution/icons/book-open.svg +16 -0
- package/templates/distribution/icons/bot.svg +20 -0
- package/templates/distribution/icons/brain.svg +22 -0
- package/templates/distribution/icons/circle-check.svg +16 -0
- package/templates/distribution/icons/clipboard-check.svg +17 -0
- package/templates/distribution/icons/cpu.svg +28 -0
- package/templates/distribution/icons/database.svg +17 -0
- package/templates/distribution/icons/eye.svg +16 -0
- package/templates/distribution/icons/file-text.svg +19 -0
- package/templates/distribution/icons/gauge.svg +16 -0
- package/templates/distribution/icons/git-branch.svg +17 -0
- package/templates/distribution/icons/key.svg +17 -0
- package/templates/distribution/icons/layers.svg +17 -0
- package/templates/distribution/icons/list-checks.svg +19 -0
- package/templates/distribution/icons/lock.svg +16 -0
- package/templates/distribution/icons/message-square.svg +15 -0
- package/templates/distribution/icons/network.svg +19 -0
- package/templates/distribution/icons/route.svg +17 -0
- package/templates/distribution/icons/scale.svg +19 -0
- package/templates/distribution/icons/search.svg +16 -0
- package/templates/distribution/icons/send.svg +16 -0
- package/templates/distribution/icons/server.svg +18 -0
- package/templates/distribution/icons/shield-check.svg +16 -0
- package/templates/distribution/icons/users.svg +18 -0
- package/templates/distribution/icons/webhook.svg +17 -0
- package/templates/distribution/icons/workflow.svg +17 -0
- package/templates/distribution/icons/wrench.svg +15 -0
- package/templates/distribution/run.mjs +18 -1
- package/templates/docs/adr.md +7 -0
- package/templates/docs/construct_guide.md +10 -10
- package/templates/docs/customer-profile.md +4 -0
- package/templates/docs/one-pager.md +4 -0
- package/templates/docs/postmortem.md +31 -0
- package/templates/docs/prd-platform.md +19 -0
- package/templates/docs/prd.md +15 -0
- package/templates/docs/prfaq.md +7 -0
- package/templates/docs/qa-strategy.md +103 -0
- package/templates/docs/research-brief.md +8 -0
- package/templates/docs/rfc-platform.md +12 -0
- package/templates/docs/test-plan.md +7 -0
- package/lib/ingest/chunker.mjs +0 -94
- package/lib/ingest/pipeline.mjs +0 -53
- package/lib/ingest/store.mjs +0 -82
- package/lib/mode-commands.mjs +0 -122
- package/lib/policy/unified-gates.mjs +0 -96
- package/lib/profiles/validate-custom.mjs +0 -114
- package/lib/services/telemetry-backend.mjs +0 -177
- package/lib/storage/fusion.mjs +0 -95
package/lib/setup.mjs
CHANGED
|
@@ -32,6 +32,9 @@ import { getCanonicalOpenCodeConfigPath, readOpenCodeConfig, writeOpenCodeConfig
|
|
|
32
32
|
import { getEmbeddingModelInfo, warmupEmbeddingModel } from './storage/embeddings-engine.mjs';
|
|
33
33
|
import { buildPressureGuardValues, installPressureGuardLaunchAgent, loadPressureGuardLaunchAgent } from './runtime-pressure.mjs';
|
|
34
34
|
import { consentToInstall } from './setup-prompts.mjs';
|
|
35
|
+
import { cleanupLegacyGlobalConstruct } from './install/legacy-global-cleanup.mjs';
|
|
36
|
+
import { downloadDesktopBinary } from './install/desktop-binary-download.mjs';
|
|
37
|
+
import { buildDesktopBinary, hasDesktopSource } from './chat/desktop-build.mjs';
|
|
35
38
|
|
|
36
39
|
const ROOT_DIR = path.resolve(import.meta.dirname, '..');
|
|
37
40
|
const HOME = os.homedir();
|
|
@@ -40,7 +43,7 @@ function printHelp() {
|
|
|
40
43
|
console.log(`Construct install — machine setup (once per machine)
|
|
41
44
|
|
|
42
45
|
Usage:
|
|
43
|
-
construct install [--scope=project|user|both] [--yes] [--no-launch-agent] [--reconfigure] [--with-docling]
|
|
46
|
+
construct install [--scope=project|user|both] [--yes] [--dry-run] [--no-launch-agent] [--reconfigure] [--with-docling] [--cleanup-legacy-global]
|
|
44
47
|
|
|
45
48
|
Flags:
|
|
46
49
|
--scope=<s> project | user | both (default: project — see ADR-0029)
|
|
@@ -50,11 +53,15 @@ Flags:
|
|
|
50
53
|
~/.claude/* via consent-gated sync).
|
|
51
54
|
both: print project guidance, then run user-scope install.
|
|
52
55
|
--yes accept detected defaults without prompting
|
|
56
|
+
--dry-run print the install plan and exit without writing anything
|
|
53
57
|
--no-launch-agent skip macOS LaunchAgent background service registration
|
|
54
58
|
--reconfigure re-prompt for service consent, ignoring cached answers
|
|
55
59
|
--with-docling eagerly provision the docling document-extraction venv now
|
|
56
60
|
(heavy, ~10 min via uv; otherwise provisioned lazily on
|
|
57
61
|
first document ingest)
|
|
62
|
+
--cleanup-legacy-global
|
|
63
|
+
remove legacy user-global Construct adapters/MCP/history
|
|
64
|
+
before setup; does not uninstall a global construct CLI
|
|
58
65
|
|
|
59
66
|
What --scope=user does:
|
|
60
67
|
- creates ~/.construct/config.env
|
|
@@ -306,17 +313,71 @@ function printProjectScopeGuidance() {
|
|
|
306
313
|
console.log(' construct install --scope=both');
|
|
307
314
|
}
|
|
308
315
|
|
|
316
|
+
function runLegacyGlobalCleanup({ homeDir, dryRun }) {
|
|
317
|
+
const result = cleanupLegacyGlobalConstruct({ homeDir, dryRun });
|
|
318
|
+
const action = dryRun ? 'would remove' : 'removed';
|
|
319
|
+
console.log(`Legacy global cleanup: ${result.removed.length ? `${action} ${result.removed.length} item(s)` : 'nothing detected'}`);
|
|
320
|
+
for (const item of result.removed.slice(0, 20)) {
|
|
321
|
+
console.log(` - ${item}`);
|
|
322
|
+
}
|
|
323
|
+
if (result.removed.length > 20) {
|
|
324
|
+
console.log(` - ...and ${result.removed.length - 20} more`);
|
|
325
|
+
}
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Dry-run renders the user-scope plan as intent only — every line names a write,
|
|
330
|
+
// network call, or service registration that the real run would perform — and
|
|
331
|
+
// returns before touching disk so the preview is provably side-effect-free.
|
|
332
|
+
|
|
333
|
+
function printInstallPlan({ scope, homeDir, withDocling, noLaunchAgent, cleanupLegacyGlobal }) {
|
|
334
|
+
console.log('Construct install — dry-run (no changes written)');
|
|
335
|
+
console.log('────────────────────────────────────────────────');
|
|
336
|
+
console.log(`Scope: ${scope}`);
|
|
337
|
+
if (cleanupLegacyGlobal) {
|
|
338
|
+
console.log('\nLegacy global cleanup:');
|
|
339
|
+
runLegacyGlobalCleanup({ homeDir, dryRun: true });
|
|
340
|
+
}
|
|
341
|
+
if (scope === 'project') {
|
|
342
|
+
console.log('\nWould not run user-scope setup. Re-run without --dry-run to apply cleanup only.');
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (scope === 'both') {
|
|
346
|
+
console.log(' · would print project-scope guidance, then run the user-scope plan below');
|
|
347
|
+
}
|
|
348
|
+
console.log('\nWould write:');
|
|
349
|
+
console.log(` · ${getUserEnvPath(homeDir)} (user config, managed defaults)`);
|
|
350
|
+
console.log(` · ${getCanonicalOpenCodeConfigPath(homeDir)} (OpenCode config)`);
|
|
351
|
+
console.log(` · ${path.join(homeDir, '.construct', 'lib')} → ${path.join(ROOT_DIR, 'lib')} (hook lib symlink)`);
|
|
352
|
+
console.log(` · ${path.join(homeDir, '.construct', 'workspace')} (workspace docs scaffold)`);
|
|
353
|
+
console.log(` · ${path.dirname(defaultVectorIndexPath(homeDir))} (LanceDB vector store dir)`);
|
|
354
|
+
console.log('\nWould check / install runtime tooling:');
|
|
355
|
+
console.log(' · cm (memory CLI) and cass (session search) when available on PATH');
|
|
356
|
+
console.log(' · construct-chat desktop binary (from GitHub releases → ~/.construct/bin/)');
|
|
357
|
+
console.log('\nWould reach the network / external services:');
|
|
358
|
+
console.log(' · embedding model warmup (loads from local cache; offline-safe, no download)');
|
|
359
|
+
if (withDocling) console.log(' · docling Python venv provisioning via uv (~10 min)');
|
|
360
|
+
console.log(' · construct sync --quiet, construct sync --global, construct doctor');
|
|
361
|
+
if (process.platform === 'darwin' && !noLaunchAgent) {
|
|
362
|
+
console.log('\nWould register macOS LaunchAgent:');
|
|
363
|
+
console.log(` · ${path.join(homeDir, 'Library', 'LaunchAgents', 'dev.construct.pressure-release.plist')} (consent-gated)`);
|
|
364
|
+
}
|
|
365
|
+
console.log('\nNo files were written. Re-run without --dry-run to apply.');
|
|
366
|
+
}
|
|
367
|
+
|
|
309
368
|
export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME } = {}) {
|
|
310
369
|
const argSet = new Set(args);
|
|
311
370
|
const isYes = argSet.has('--yes');
|
|
312
371
|
const reconfigure = argSet.has('--reconfigure');
|
|
372
|
+
const cleanupLegacyGlobal = argSet.has('--cleanup-legacy-global');
|
|
313
373
|
|
|
314
374
|
if (argSet.has('--help') || argSet.has('-h')) {
|
|
315
375
|
printHelp();
|
|
316
376
|
return;
|
|
317
377
|
}
|
|
318
378
|
|
|
319
|
-
const KNOWN_FLAGS = new Set(['--yes', '--no-launch-agent', '--reconfigure', '--with-docling', '--help', '-h']);
|
|
379
|
+
const KNOWN_FLAGS = new Set(['--yes', '--dry-run', '--no-launch-agent', '--reconfigure', '--with-docling', '--cleanup-legacy-global', '--help', '-h']);
|
|
380
|
+
const dryRun = argSet.has('--dry-run');
|
|
320
381
|
const withDocling = argSet.has('--with-docling');
|
|
321
382
|
const unknownFlags = args.filter((a) => {
|
|
322
383
|
if (!a.startsWith('-')) return false;
|
|
@@ -337,6 +398,23 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
|
|
|
337
398
|
}
|
|
338
399
|
const scope = scopeResult.scope;
|
|
339
400
|
|
|
401
|
+
if (dryRun) {
|
|
402
|
+
if (scope === 'project' && !cleanupLegacyGlobal) {
|
|
403
|
+
printProjectScopeGuidance();
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
printInstallPlan({ scope, homeDir, withDocling, noLaunchAgent: argSet.has('--no-launch-agent'), cleanupLegacyGlobal });
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (cleanupLegacyGlobal) {
|
|
411
|
+
runLegacyGlobalCleanup({ homeDir, dryRun: false });
|
|
412
|
+
if (scope === 'project') {
|
|
413
|
+
printProjectScopeGuidance();
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
340
418
|
if (scope === 'project') {
|
|
341
419
|
printProjectScopeGuidance();
|
|
342
420
|
return;
|
|
@@ -378,6 +456,28 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
|
|
|
378
456
|
if (cassInstall.installCommand) console.log(` Install with: ${cassInstall.installCommand}`);
|
|
379
457
|
}
|
|
380
458
|
|
|
459
|
+
try {
|
|
460
|
+
if (hasDesktopSource(ROOT_DIR)) {
|
|
461
|
+
const built = buildDesktopBinary({ repoRoot: ROOT_DIR, env: process.env });
|
|
462
|
+
if (built.ok) {
|
|
463
|
+
console.log(`Desktop binary: built from source → ${built.binaryPath}`);
|
|
464
|
+
} else {
|
|
465
|
+
console.log(`Desktop binary: build skipped (${built.reason}) — runs on first \`construct chat\``);
|
|
466
|
+
}
|
|
467
|
+
} else {
|
|
468
|
+
const desktop = await downloadDesktopBinary({ homeDir, env: process.env });
|
|
469
|
+
if (desktop.status === 'downloaded') {
|
|
470
|
+
console.log(`Desktop binary: ${desktop.version} → ${desktop.dest}`);
|
|
471
|
+
} else if (desktop.status === 'failed') {
|
|
472
|
+
console.log(`Desktop binary: download failed (${desktop.reason}) — run \`construct install --scope=user\` again to retry`);
|
|
473
|
+
} else {
|
|
474
|
+
console.log(`Desktop binary: ${desktop.reason}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
} catch (err) {
|
|
478
|
+
console.log(`Desktop binary: skipped (${err?.message || 'unknown error'})`);
|
|
479
|
+
}
|
|
480
|
+
|
|
381
481
|
const telemetryResult = process.env.CONSTRUCT_TELEMETRY_URL
|
|
382
482
|
? { status: 'configured', note: `remote export configured (${process.env.CONSTRUCT_TELEMETRY_URL})` }
|
|
383
483
|
: { status: 'local', note: 'local JSONL traces in .cx/traces; remote export optional' };
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* shape: YAML frontmatter (structured metadata) + markdown body (the authored
|
|
6
6
|
* prose), mirroring skills/roles/*.md. This validates the frontmatter and the
|
|
7
7
|
* canonical-section contract, and gates the highest-value invariant — the
|
|
8
|
-
* frontmatter `perspective{}`
|
|
9
|
-
*
|
|
8
|
+
* frontmatter `perspective{}` is the sole source of truth (ADR-0037); registry.json
|
|
9
|
+
* carries routing metadata only.
|
|
10
10
|
*
|
|
11
11
|
* A file with no frontmatter is treated as not-yet-converted: reported, never an
|
|
12
12
|
* error, so the linter can run across a half-migrated corpus. Frontmatter is the
|
|
@@ -55,11 +55,6 @@ function hasHeading(body, title) {
|
|
|
55
55
|
return false;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function samePerspective(a, b) {
|
|
59
|
-
if (!a || !b) return false;
|
|
60
|
-
return PERSPECTIVE_FIELDS.every((f) => String(a[f] ?? '').trim() === String(b[f] ?? '').trim());
|
|
61
|
-
}
|
|
62
|
-
|
|
63
58
|
/**
|
|
64
59
|
* Validate one prompt file's content against the hybrid format.
|
|
65
60
|
*
|
|
@@ -103,9 +98,6 @@ export function validatePromptContent({ content, id = '(prompt)', registryEntry
|
|
|
103
98
|
for (const f of PERSPECTIVE_FIELDS) {
|
|
104
99
|
if (!persp[f] || !String(persp[f]).trim()) errors.push(`${id}: perspective.${f} is required and must be non-empty`);
|
|
105
100
|
}
|
|
106
|
-
if (registryEntry?.perspective && !samePerspective(persp, registryEntry.perspective)) {
|
|
107
|
-
errors.push(`${id}: perspective{} drifts from registry.json — they must match while both exist (frontmatter is the source of truth; update the registry)`);
|
|
108
|
-
}
|
|
109
101
|
} else if (frontmatter.perspective != null) {
|
|
110
102
|
errors.push(`${id}: perspective must be an object with ${PERSPECTIVE_FIELDS.join(', ')}`);
|
|
111
103
|
}
|
|
@@ -160,3 +152,20 @@ function loadRegistry(rootDir) {
|
|
|
160
152
|
return { specialists: [] };
|
|
161
153
|
}
|
|
162
154
|
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Resolve a specialist's perspective from its prompt frontmatter.
|
|
158
|
+
* @param {string} agentName - registry name or cx-prefixed id
|
|
159
|
+
* @returns {object|null}
|
|
160
|
+
*/
|
|
161
|
+
export function resolvePerspectiveFromPrompt(agentName, { rootDir = process.cwd(), registry } = {}) {
|
|
162
|
+
const reg = registry ?? loadRegistry(rootDir);
|
|
163
|
+
const normalized = String(agentName ?? '').trim().replace(/^cx-/, '');
|
|
164
|
+
const entry = (reg.specialists ?? []).find((s) => s.name === normalized);
|
|
165
|
+
if (!entry?.promptFile) return null;
|
|
166
|
+
const filePath = path.join(rootDir, entry.promptFile);
|
|
167
|
+
if (!fs.existsSync(filePath)) return null;
|
|
168
|
+
const { frontmatter } = splitFrontmatter(fs.readFileSync(filePath, 'utf8'));
|
|
169
|
+
const persp = frontmatter?.perspective;
|
|
170
|
+
return persp && typeof persp === 'object' ? persp : null;
|
|
171
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/specialists/roster.mjs — lazy specialist catalog for runtime resolution
|
|
3
|
+
* (construct-ymp5).
|
|
4
|
+
*
|
|
5
|
+
* The specialist catalog is resolved at runtime through orchestration_policy rather
|
|
6
|
+
* than inlined into synced orchestrator prompts. Each catalog entry carries id and
|
|
7
|
+
* whenToUse for routing discovery without an always-on prompt block.
|
|
8
|
+
*/
|
|
9
|
+
import { readFileSync } from 'node:fs';
|
|
10
|
+
import { dirname, join } from 'node:path';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
|
|
13
|
+
const REGISTRY_PATH = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'specialists', 'registry.json');
|
|
14
|
+
|
|
15
|
+
function loadRegistry(rootDir = null) {
|
|
16
|
+
const path = rootDir
|
|
17
|
+
? join(rootDir, 'specialists', 'registry.json')
|
|
18
|
+
: REGISTRY_PATH;
|
|
19
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Compact specialist catalog for orchestration_policy responses. Each entry names
|
|
24
|
+
* the cx-prefixed id and the routing hint (when_to_use when present, else description).
|
|
25
|
+
*/
|
|
26
|
+
export function buildSpecialistCatalog({ rootDir = null, includeInternal = true } = {}) {
|
|
27
|
+
const registry = loadRegistry(rootDir);
|
|
28
|
+
const prefix = registry.prefix || 'cx';
|
|
29
|
+
const specialists = Array.isArray(registry.specialists) ? registry.specialists : [];
|
|
30
|
+
return specialists
|
|
31
|
+
.filter((s) => includeInternal || !s.internal)
|
|
32
|
+
.map((s) => ({
|
|
33
|
+
id: `${prefix}-${s.name}`,
|
|
34
|
+
whenToUse: s.when_to_use || s.description || '',
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Legacy text roster shape retained for tests and migration comparisons.
|
|
40
|
+
*/
|
|
41
|
+
export function formatSpecialistRosterText(catalog) {
|
|
42
|
+
return catalog.map((row) => `- ${row.id}: ${row.whenToUse}`).join('\n');
|
|
43
|
+
}
|
|
@@ -51,6 +51,62 @@ export function renderSkeleton({ role, perspective = {}, roleGuidance } = {}) {
|
|
|
51
51
|
return `---\n${yaml.dump(fm).trimEnd()}\n---\n\n${body}`;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function buildFrontmatterFromRegistry(role, { perspective, roleGuidance, roleOverlays, docArtifacts } = {}) {
|
|
55
|
+
if (!perspective || typeof perspective !== 'object') {
|
|
56
|
+
throw new Error(`perspective is required to wrap legacy prompt cx-${role} (frontmatter is the source of truth)`);
|
|
57
|
+
}
|
|
58
|
+
const fm = {
|
|
59
|
+
name: `cx-${role}`,
|
|
60
|
+
role,
|
|
61
|
+
version: 1,
|
|
62
|
+
perspective,
|
|
63
|
+
};
|
|
64
|
+
if (roleGuidance) fm.roleGuidance = roleGuidance;
|
|
65
|
+
if (Array.isArray(roleOverlays) && roleOverlays.length) fm.roleOverlays = roleOverlays;
|
|
66
|
+
if (Array.isArray(docArtifacts) && docArtifacts.length) fm.templates = docArtifacts;
|
|
67
|
+
return fm;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Wrap a legacy prompt body with hybrid frontmatter from the registry entry.
|
|
72
|
+
* The body bytes are preserved (emit-neutral) aside from a trailing newline.
|
|
73
|
+
*/
|
|
74
|
+
export function wrapLegacyPromptWithFrontmatter({ role, perspective, registryEntry = {}, body }) {
|
|
75
|
+
const fm = buildFrontmatterFromRegistry(role, {
|
|
76
|
+
perspective,
|
|
77
|
+
roleGuidance: registryEntry.roleGuidance,
|
|
78
|
+
roleOverlays: registryEntry.roleOverlays,
|
|
79
|
+
docArtifacts: registryEntry.docArtifacts,
|
|
80
|
+
});
|
|
81
|
+
const normalizedBody = String(body ?? '').replace(/^\n+/, '');
|
|
82
|
+
const withNl = normalizedBody.endsWith('\n') ? normalizedBody : `${normalizedBody}\n`;
|
|
83
|
+
return `---\n${yaml.dump(fm).trimEnd()}\n---\n\n${withNl}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Convert one on-disk legacy prompt to hybrid format in place.
|
|
88
|
+
* @returns {{ path: string, relPath: string, converted: boolean }}
|
|
89
|
+
*/
|
|
90
|
+
export function convertLegacyPromptFile({ rootDir = process.cwd(), role, perspective, registryEntry } = {}) {
|
|
91
|
+
const relPath = promptRelPath(role);
|
|
92
|
+
const filePath = path.join(rootDir, relPath);
|
|
93
|
+
if (!fs.existsSync(filePath)) throw new Error(`${relPath} does not exist`);
|
|
94
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
95
|
+
const { frontmatter, body } = splitFrontmatter(raw);
|
|
96
|
+
if (frontmatter) return { path: filePath, relPath, converted: false };
|
|
97
|
+
|
|
98
|
+
const persp = perspective ?? registryEntry?.perspective;
|
|
99
|
+
if (!persp) {
|
|
100
|
+
throw new Error(`perspective required to convert ${relPath} — pass explicitly or supply registryEntry.perspective`);
|
|
101
|
+
}
|
|
102
|
+
const content = wrapLegacyPromptWithFrontmatter({ role, perspective: persp, registryEntry, body });
|
|
103
|
+
const { errors } = validatePromptContent({ content, id: `cx-${role}`, registryEntry });
|
|
104
|
+
if (errors.length) throw new Error(`convert ${relPath} failed:\n ${errors.join('\n ')}`);
|
|
105
|
+
|
|
106
|
+
fs.writeFileSync(filePath, content);
|
|
107
|
+
return { path: filePath, relPath, converted: true };
|
|
108
|
+
}
|
|
109
|
+
|
|
54
110
|
/**
|
|
55
111
|
* Create a specialist prompt draft on disk. Refuses to overwrite. Validates
|
|
56
112
|
* the rendered skeleton before writing.
|
package/lib/storage/backend.mjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/storage/backend.mjs — shared storage backend helpers.
|
|
3
3
|
*
|
|
4
|
-
* Construct
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Postgres orchestration.
|
|
4
|
+
* Construct uses embedded LanceDB and Git-backed state. createSqlClient and
|
|
5
|
+
* related helpers are stubs that keep existing imports working without
|
|
6
|
+
* orchestrating local Postgres.
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
9
|
export function createSqlClient(env = process.env) {
|
|
11
|
-
//
|
|
12
|
-
//
|
|
10
|
+
// Stub: local-first operations skip Postgres, so external DATABASE_URL has
|
|
11
|
+
// no effect here.
|
|
12
|
+
|
|
13
13
|
return null;
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/storage/embeddings-local.mjs — Local ONNX embedding via @huggingface/transformers.
|
|
3
3
|
*
|
|
4
|
-
* Lazy-loads the model
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Lazy-loads the model from local cache only and runs inference offline. Falls
|
|
5
|
+
* back to hashing-bow-v1 when the model is absent or fails to load. Remote model
|
|
6
|
+
* fetching is disabled (allowRemoteModels=false): the runtime path never reaches
|
|
7
|
+
* the network, so a first run with no cached weights degrades to the hashing
|
|
8
|
+
* backend rather than downloading from the HuggingFace hub.
|
|
7
9
|
*
|
|
8
10
|
* Model: Xenova/all-MiniLM-L6-v2 (384 dimensions, quantized)
|
|
9
11
|
*/
|
|
@@ -29,6 +31,7 @@ async function getExtractor(cacheDir) {
|
|
|
29
31
|
modelPromise = (async () => {
|
|
30
32
|
const { pipeline, env: hfEnv } = await import('@huggingface/transformers');
|
|
31
33
|
hfEnv.allowLocalModels = true;
|
|
34
|
+
hfEnv.allowRemoteModels = false;
|
|
32
35
|
hfEnv.useBrowserCache = false;
|
|
33
36
|
try {
|
|
34
37
|
const ex = await pipeline('feature-extraction', MODEL_ID, {
|
|
@@ -50,17 +50,24 @@ function tryAcquire(lockPath) {
|
|
|
50
50
|
return true;
|
|
51
51
|
} catch (err) {
|
|
52
52
|
if (err.code !== 'EEXIST') throw err;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return false;
|
|
53
|
+
|
|
54
|
+
// Steal only from a holder we can positively read as dead. An empty or
|
|
55
|
+
// unreadable pidfile is the mid-create window of a live holder, not a
|
|
56
|
+
// crash; stealing it would admit two writers and break an append chain.
|
|
57
|
+
// The acquire timeout still bounds the wait if the holder truly crashed.
|
|
58
|
+
|
|
59
|
+
let raw = '';
|
|
60
|
+
try { raw = readFileSync(lockPath, 'utf8').trim(); } catch { return false; }
|
|
61
|
+
if (raw === '') return false;
|
|
62
|
+
const holder = Number(raw);
|
|
63
|
+
if (!Number.isFinite(holder) || isHolderAlive(holder)) return false;
|
|
64
|
+
|
|
65
|
+
try { unlinkSync(lockPath); } catch { /* another stealer won */ }
|
|
66
|
+
try {
|
|
67
|
+
writeFileSync(lockPath, String(process.pid), { flag: 'wx' });
|
|
68
|
+
heldLocks.add(lockPath);
|
|
69
|
+
return true;
|
|
70
|
+
} catch { return false; }
|
|
64
71
|
}
|
|
65
72
|
}
|
|
66
73
|
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/storage/hybrid-query.mjs — unified search across knowledge and facts.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Stub for the LanceDB-backed search.
|
|
5
5
|
*/
|
|
6
6
|
import { VectorClient } from './vector-client.mjs';
|
|
7
7
|
|
|
8
8
|
export async function buildHybridSearchResultsAsync(rootDir, query, { limit = 10, env = process.env } = {}) {
|
|
9
|
-
//
|
|
9
|
+
// Embed the query
|
|
10
|
+
|
|
10
11
|
const { embedText } = await import('./embeddings-engine.mjs');
|
|
11
12
|
const { embedding } = await embedText(query, { env });
|
|
12
13
|
|
|
13
|
-
//
|
|
14
|
+
// Search LanceDB documents and observations
|
|
15
|
+
|
|
14
16
|
const client = new VectorClient({ env });
|
|
15
17
|
const docs = await client.searchDocuments({ project: 'construct', queryEmbedding: embedding, limit });
|
|
16
18
|
const obs = await client.searchObservations({ project: 'construct', queryEmbedding: embedding, limit });
|
|
17
19
|
|
|
18
|
-
//
|
|
20
|
+
// Merge both result sets and rank by similarity
|
|
21
|
+
|
|
19
22
|
const all = [...docs.map(d => ({ ...d, type: 'document' })), ...obs.map(o => ({ ...o, type: 'observation' }))];
|
|
20
23
|
return all.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
|
|
21
24
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { existsSync, readdirSync, statSync, readFileSync } from 'node:fs';
|
|
6
6
|
import { basename, extname, join, relative } from 'node:path';
|
|
7
|
-
import { EXTRACTABLE_DOCUMENT_EXTS,
|
|
7
|
+
import { EXTRACTABLE_DOCUMENT_EXTS, extractDocumentTextNodeNative } from '../document-extract.mjs';
|
|
8
8
|
|
|
9
9
|
function readJsonIfExists(filePath) {
|
|
10
10
|
if (!existsSync(filePath)) return null;
|
|
@@ -28,7 +28,7 @@ function titleFromPath(filePath) {
|
|
|
28
28
|
return basename(filePath, extname(filePath)).replace(/[-_]+/g, ' ');
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function collectTextFiles(rootDir, dirs, { extensions = EXTRACTABLE_DOCUMENT_EXTS } = {}) {
|
|
31
|
+
async function collectTextFiles(rootDir, dirs, { extensions = EXTRACTABLE_DOCUMENT_EXTS } = {}) {
|
|
32
32
|
const files = [];
|
|
33
33
|
for (const dir of dirs) {
|
|
34
34
|
const abs = join(rootDir, dir);
|
|
@@ -47,7 +47,7 @@ function collectTextFiles(rootDir, dirs, { extensions = EXTRACTABLE_DOCUMENT_EXT
|
|
|
47
47
|
try {
|
|
48
48
|
const stat = statSync(full);
|
|
49
49
|
if (stat.size > 5_000_000) continue;
|
|
50
|
-
const body =
|
|
50
|
+
const body = (await extractDocumentTextNodeNative(full, { maxChars: 200_000 })).text;
|
|
51
51
|
files.push({
|
|
52
52
|
path: relative(rootDir, full),
|
|
53
53
|
title: titleFromPath(full),
|
|
@@ -62,12 +62,12 @@ function collectTextFiles(rootDir, dirs, { extensions = EXTRACTABLE_DOCUMENT_EXT
|
|
|
62
62
|
return files.sort((a, b) => a.path.localeCompare(b.path));
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
export function loadStateSnapshot(rootDir) {
|
|
65
|
+
export async function loadStateSnapshot(rootDir) {
|
|
66
66
|
const contextJsonPath = join(rootDir, '.cx', 'context.json');
|
|
67
67
|
const planPath = join(rootDir, 'plan.md');
|
|
68
68
|
const architecturePath = join(rootDir, 'docs', 'architecture.md');
|
|
69
69
|
const docsReadmePath = join(rootDir, 'docs', 'README.md');
|
|
70
|
-
const productIntelDocs = collectTextFiles(rootDir, [
|
|
70
|
+
const productIntelDocs = await collectTextFiles(rootDir, [
|
|
71
71
|
'.cx/knowledge',
|
|
72
72
|
'docs/prd',
|
|
73
73
|
'docs/meta-prd',
|
package/lib/storage/sync.mjs
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* lib/storage/sync.mjs — sync file-state artifacts into local LanceDB indices.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* one, matching the new local-first, Git-backed architecture.
|
|
5
|
+
* Syncs into LanceDB indices, matching the local-first, Git-backed architecture.
|
|
7
6
|
*/
|
|
8
7
|
import crypto from 'node:crypto';
|
|
9
8
|
import { loadStateSnapshot } from './state-source.mjs';
|
|
@@ -87,7 +86,7 @@ function toDocumentRows(rootDir, snapshot, project = 'construct') {
|
|
|
87
86
|
}
|
|
88
87
|
|
|
89
88
|
export async function syncFileStateToSql(rootDir, { env = process.env, project = 'construct' } = {}) {
|
|
90
|
-
const snapshot = loadStateSnapshot(rootDir);
|
|
89
|
+
const snapshot = await loadStateSnapshot(rootDir);
|
|
91
90
|
const rows = toDocumentRows(rootDir, snapshot, project);
|
|
92
91
|
|
|
93
92
|
const modelInfo = await getEmbeddingModelInfo({ env });
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/telemetry/rule-calls.mjs — record when a rule path is referenced at runtime.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors hook-calls telemetry: append-only JSONL at ~/.cx/rule-calls.jsonl.
|
|
5
|
+
* Consumed by `construct rules usage` for consolidation decisions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import os from 'node:os';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { appendBounded } from '../logging/rotate.mjs';
|
|
12
|
+
|
|
13
|
+
export const DEFAULT_LOG_PATH = path.join(os.homedir(), '.cx', 'rule-calls.jsonl');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {object} event
|
|
17
|
+
* @param {string} event.rulePath — e.g. rules/common/no-fabrication.md
|
|
18
|
+
* @param {string} event.source — mcp | hook | prompt-composer | validation | other
|
|
19
|
+
* @param {string} [event.callerContext]
|
|
20
|
+
*/
|
|
21
|
+
export function logRuleCall(event, { logPath = DEFAULT_LOG_PATH, env = process.env } = {}) {
|
|
22
|
+
if (env.CONSTRUCT_RULE_TELEMETRY === 'off') return;
|
|
23
|
+
const entry = {
|
|
24
|
+
ts: new Date().toISOString(),
|
|
25
|
+
rulePath: event.rulePath,
|
|
26
|
+
source: event.source || 'other',
|
|
27
|
+
callerContext: event.callerContext || null,
|
|
28
|
+
};
|
|
29
|
+
try {
|
|
30
|
+
appendBounded('rule-calls', logPath, `${JSON.stringify(entry)}\n`);
|
|
31
|
+
} catch {
|
|
32
|
+
/* non-fatal */
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function summarizeRuleCalls({ logPath = DEFAULT_LOG_PATH, since = '30d' } = {}) {
|
|
37
|
+
if (!fs.existsSync(logPath)) return { rules: {}, total: 0 };
|
|
38
|
+
const ms = since.endsWith('d') ? parseInt(since, 10) * 86400000 : 30 * 86400000;
|
|
39
|
+
const cutoff = Date.now() - ms;
|
|
40
|
+
const rules = {};
|
|
41
|
+
let total = 0;
|
|
42
|
+
for (const line of fs.readFileSync(logPath, 'utf8').split('\n')) {
|
|
43
|
+
if (!line.trim()) continue;
|
|
44
|
+
try {
|
|
45
|
+
const row = JSON.parse(line);
|
|
46
|
+
if (Date.parse(row.ts) < cutoff) continue;
|
|
47
|
+
total += 1;
|
|
48
|
+
rules[row.rulePath] = (rules[row.rulePath] || 0) + 1;
|
|
49
|
+
} catch { /* skip */ }
|
|
50
|
+
}
|
|
51
|
+
return { rules, total, since };
|
|
52
|
+
}
|
|
@@ -20,7 +20,7 @@ export const SPECIALIST_TEMPLATES = {
|
|
|
20
20
|
// Tranche 1 — five new templates authored alongside this map.
|
|
21
21
|
'cx-reviewer': ['code-review-report'],
|
|
22
22
|
'cx-security': ['security-audit-report'],
|
|
23
|
-
'cx-qa': ['qa-report', 'test-plan'],
|
|
23
|
+
'cx-qa': ['qa-report', 'test-plan', 'qa-strategy'],
|
|
24
24
|
'cx-debugger': ['debug-investigation'],
|
|
25
25
|
'cx-devil-advocate': ['verdict'],
|
|
26
26
|
'cx-evaluator': ['verdict'],
|
|
@@ -31,7 +31,7 @@ export const SPECIALIST_TEMPLATES = {
|
|
|
31
31
|
// Already-wired specialists pinned here so the gate guards them against drift.
|
|
32
32
|
'cx-architect': ['adr', 'rfc', 'architecture-review'],
|
|
33
33
|
'cx-researcher': ['research-brief', 'evidence-brief'],
|
|
34
|
-
'cx-sre': ['runbook', 'incident-report'],
|
|
34
|
+
'cx-sre': ['runbook', 'incident-report', 'postmortem'],
|
|
35
35
|
'cx-product-manager': ['prd', 'meta-prd'],
|
|
36
36
|
|
|
37
37
|
// Tranche 2 — three lower-frequency artifacts.
|