@geraldmaron/construct 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -19
- package/bin/construct +311 -81
- package/bin/construct-postinstall.mjs +30 -2
- package/examples/distribution/README.md +42 -0
- package/examples/distribution/manifest.json +53 -0
- package/examples/distribution/sources/adr.md +84 -0
- package/examples/distribution/sources/deck-one-pager.md +65 -0
- package/examples/distribution/sources/prd-platform.md +161 -0
- package/examples/distribution/sources/research-brief.md +80 -0
- package/examples/distribution/sources/rfc-platform.md +82 -0
- package/examples/distribution/sources/runbook.md +103 -0
- package/examples/distribution/sources/strategy.md +88 -0
- package/lib/adapters-sync.mjs +67 -0
- package/lib/artifact-gate-notice.mjs +38 -0
- package/lib/artifact-manifest.mjs +112 -0
- package/lib/artifact-release-gate.mjs +171 -0
- package/lib/artifact-reviewers.mjs +68 -0
- package/lib/artifact-type-from-path.mjs +79 -0
- package/lib/audit-skills.mjs +4 -0
- package/lib/audit-specialists.mjs +285 -0
- package/lib/audit-trail.mjs +1 -1
- package/lib/auto-docs.mjs +87 -18
- package/lib/beads/drift.mjs +27 -6
- package/lib/beads-client.mjs +49 -121
- package/lib/beads-optimistic.mjs +5 -12
- package/lib/brand-fonts.mjs +93 -0
- package/lib/brand-prose.mjs +214 -0
- package/lib/brand-tokens.mjs +92 -0
- package/lib/bridges/copilot-proxy.mjs +13 -26
- package/lib/capability-ledger.mjs +156 -0
- package/lib/certification/artifact-fixtures.mjs +132 -0
- package/lib/certification/artifact-gates.mjs +63 -0
- package/lib/certification/artifact-provenance.mjs +97 -0
- package/lib/certification/canonical-scenarios.mjs +78 -0
- package/lib/certification/cli.mjs +191 -0
- package/lib/certification/dashboard-api.mjs +71 -0
- package/lib/certification/demo-parity.mjs +116 -0
- package/lib/certification/document-io-fixtures.mjs +246 -0
- package/lib/certification/document-workflow.mjs +97 -0
- package/lib/certification/eval-bridge.mjs +77 -0
- package/lib/certification/model-routing.mjs +149 -0
- package/lib/certification/prompt-budget.mjs +119 -0
- package/lib/certification/rc-gate.mjs +206 -0
- package/lib/certification/real-llm-scenarios.mjs +303 -0
- package/lib/certification/role-cards.mjs +113 -0
- package/lib/certification/role-overlays.mjs +117 -0
- package/lib/certification/run.mjs +122 -0
- package/lib/certification/runner.mjs +323 -0
- package/lib/certification/scenarios.mjs +51 -0
- package/lib/certification/skill-inventory.mjs +289 -0
- package/lib/certification/skill-scenarios.mjs +147 -0
- package/lib/certification/specialist-contracts.mjs +85 -0
- package/lib/certification/specialist-scenarios.mjs +175 -0
- package/lib/certification/stale-impact.mjs +146 -0
- package/lib/certification/status.mjs +252 -0
- package/lib/certification/store.mjs +77 -0
- package/lib/chat/cli.mjs +333 -0
- package/lib/chat/command-suggest.mjs +161 -0
- package/lib/chat/commands.mjs +215 -0
- package/lib/chat/config.mjs +142 -0
- package/lib/chat/context-compactor.mjs +250 -0
- package/lib/chat/context-continuation.mjs +253 -0
- package/lib/chat/continuation-source.mjs +58 -0
- package/lib/chat/demo-guide.mjs +61 -0
- package/lib/chat/design-tokens.mjs +91 -0
- package/lib/chat/desktop-binary.mjs +79 -0
- package/lib/chat/desktop-build.mjs +130 -0
- package/lib/chat/desktop-launcher.mjs +133 -0
- package/lib/chat/evidence.mjs +145 -0
- package/lib/chat/export.mjs +74 -0
- package/lib/chat/harness/driver.mjs +91 -0
- package/lib/chat/list-picker.mjs +112 -0
- package/lib/chat/model-picker.mjs +356 -0
- package/lib/chat/openrouter-fallback.mjs +151 -0
- package/lib/chat/permission-prompt.mjs +33 -0
- package/lib/chat/picker-catalog.mjs +45 -0
- package/lib/chat/policy-telemetry.mjs +34 -0
- package/lib/chat/present.mjs +245 -0
- package/lib/chat/session-context.mjs +39 -0
- package/lib/chat/session-persist.mjs +73 -0
- package/lib/chat/session-restore.mjs +71 -0
- package/lib/chat/session-settings.mjs +53 -0
- package/lib/chat/system-prompt.mjs +52 -0
- package/lib/chat/transparency.mjs +93 -0
- package/lib/chat/tui/color-scheme.mjs +42 -0
- package/lib/chat/tui/markdown.mjs +123 -0
- package/lib/chat/tui/presentation.mjs +100 -0
- package/lib/chat/tui/render.mjs +500 -0
- package/lib/chat/tui/turn-block.mjs +284 -0
- package/lib/chat/tui/turn-present.mjs +18 -0
- package/lib/chat/tui/turn-state.mjs +88 -0
- package/lib/chat/tui/usage.mjs +122 -0
- package/lib/chat/web-commands.mjs +146 -0
- package/lib/chat/web-launcher.mjs +63 -0
- package/lib/chat/web-picker-keys.mjs +46 -0
- package/lib/chat/web-session.mjs +159 -0
- package/lib/cli-commands.mjs +129 -15
- package/lib/comment-lint.mjs +6 -4
- package/lib/config/schema.mjs +36 -5
- package/lib/contract-schemas/decision.json +50 -0
- package/lib/contract-schemas/implementation.json +51 -0
- package/lib/contract-schemas/review-report.json +32 -0
- package/lib/contract-schemas/test-report.json +43 -0
- package/lib/contracts/construct-handoff.mjs +60 -0
- package/lib/contracts/validate.mjs +32 -3
- package/lib/contracts/violation-log.mjs +58 -5
- package/lib/dashboard-demo.mjs +71 -0
- package/lib/dashboard-static.mjs +19 -5
- package/lib/decisions/registry.mjs +5 -3
- package/lib/deck-export-pptx.mjs +1152 -0
- package/lib/demo-recording.mjs +142 -0
- package/lib/demo-script.mjs +114 -0
- package/lib/demo-surface.mjs +249 -0
- package/lib/demo.mjs +598 -140
- package/lib/diagram-export.mjs +192 -0
- package/lib/diagram.mjs +13 -11
- package/lib/docs-verify.mjs +25 -7
- package/lib/doctor/index.mjs +3 -1
- package/lib/doctor/project-adapters.mjs +44 -0
- package/lib/doctor/watchers/cx-budget.mjs +98 -0
- package/lib/doctor/watchers/graph-staleness.mjs +52 -0
- package/lib/doctor/watchers/process-pressure.mjs +4 -3
- package/lib/document-export.mjs +360 -36
- package/lib/document-extract.mjs +44 -4
- package/lib/document-ingest.mjs +29 -20
- package/lib/embed/cli.mjs +2 -2
- package/lib/embed/daemon.mjs +18 -46
- package/lib/embed/worker.mjs +2 -2
- package/lib/embedded-contract/model-resolve.mjs +10 -9
- package/lib/env-config.mjs +26 -0
- package/lib/evals/dataset.mjs +137 -0
- package/lib/evals/gates.mjs +175 -0
- package/lib/graph/build-co-change.mjs +54 -0
- package/lib/graph/build-from-registry.mjs +136 -0
- package/lib/graph/build-import-graph.mjs +153 -0
- package/lib/graph/cli.mjs +110 -0
- package/lib/graph/impact-cli.mjs +89 -0
- package/lib/graph/impact.mjs +108 -0
- package/lib/graph/staleness.mjs +44 -0
- package/lib/graph/store.mjs +172 -0
- package/lib/health-check.mjs +90 -76
- package/lib/hooks/artifact-release-gate.mjs +43 -0
- package/lib/hooks/brand-prose-lint.mjs +38 -0
- package/lib/hooks/graph-impact-advisory.mjs +62 -0
- package/lib/hooks/session-optimize.mjs +84 -207
- package/lib/hooks/session-start.mjs +4 -5
- package/lib/host-disposition.mjs +7 -0
- package/lib/improvement/cli.mjs +189 -0
- package/lib/improvement/controller.mjs +137 -0
- package/lib/improvement/proposal.mjs +120 -0
- package/lib/improvement/specialist-loop.mjs +192 -0
- package/lib/improvement/store.mjs +89 -0
- package/lib/improvement/surface.mjs +219 -0
- package/lib/ingest-tooling.mjs +97 -0
- package/lib/init/doc-lanes.mjs +165 -0
- package/lib/init-docs.mjs +31 -178
- package/lib/init-unified.mjs +27 -97
- package/lib/init-update-guide.mjs +102 -0
- package/lib/init-update.mjs +32 -1
- package/lib/init.mjs +8 -0
- package/lib/install/desktop-binary-download.mjs +85 -0
- package/lib/install/legacy-global-cleanup.mjs +189 -0
- package/lib/intake/daemon.mjs +2 -0
- package/lib/intake/git-queue.mjs +9 -8
- package/lib/intake/queue.mjs +2 -1
- package/lib/intake/session-prelude.mjs +90 -1
- package/lib/libreoffice-export.mjs +97 -0
- package/lib/logging/rotate.mjs +9 -0
- package/lib/maintenance/docker-reclaim.mjs +206 -0
- package/lib/mcp/external-schema-cost.mjs +74 -0
- package/lib/mcp/server.mjs +84 -9
- package/lib/mcp/stdio-mcp-probe.mjs +188 -0
- package/lib/mcp/tool-budget.mjs +55 -4
- package/lib/mcp/tools/document.mjs +18 -4
- package/lib/mcp/tools/skills.mjs +32 -3
- package/lib/mcp/tools/workflow.mjs +13 -1
- package/lib/model-free-selector.mjs +18 -0
- package/lib/model-registry.mjs +13 -229
- package/lib/model-router.mjs +373 -92
- package/lib/models/behavior-matrix.mjs +289 -0
- package/lib/models/catalog.mjs +209 -0
- package/lib/models/execution-capability-profile.mjs +196 -0
- package/lib/models/execution-policy.mjs +307 -0
- package/lib/models/provider-poll.mjs +383 -0
- package/lib/npm-spawn-env.mjs +17 -0
- package/lib/ollama/installed-models.mjs +129 -0
- package/lib/oracle/actions.mjs +187 -0
- package/lib/oracle/artifact-gate.mjs +99 -0
- package/lib/oracle/cli.mjs +204 -0
- package/lib/oracle/daemon-entry.mjs +14 -0
- package/lib/oracle/dispatch.mjs +81 -0
- package/lib/oracle/execute.mjs +143 -0
- package/lib/oracle/gaps.mjs +76 -0
- package/lib/oracle/index.mjs +84 -0
- package/lib/oracle/issues.mjs +164 -0
- package/lib/oracle/org-graph.mjs +170 -0
- package/lib/oracle/policy.mjs +80 -0
- package/lib/oracle/read-model.mjs +398 -0
- package/lib/oracle/reconcile.mjs +191 -0
- package/lib/oracle/routing.mjs +89 -0
- package/lib/oracle/synthesize.mjs +384 -0
- package/lib/oracle/verdicts.mjs +51 -0
- package/lib/orchestration/worker.mjs +88 -27
- package/lib/orchestration-policy.mjs +50 -0
- package/lib/parity.mjs +96 -2
- package/lib/playwright-demo.mjs +252 -0
- package/lib/project-init-shared.mjs +4 -1
- package/lib/prompt-composer.js +7 -3
- package/lib/prompt-validation-contract.mjs +24 -0
- package/lib/provider-capabilities.js +57 -11
- package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
- package/lib/providers/contract/adapters/git/index.mjs +115 -0
- package/lib/providers/contract/adapters/github/index.mjs +166 -0
- package/lib/providers/contract/adapters/jira/index.mjs +187 -0
- package/lib/providers/contract/adapters/slack/index.mjs +175 -0
- package/lib/providers/contract/contract-tests.mjs +57 -0
- package/lib/providers/contract/errors.mjs +48 -0
- package/lib/providers/contract/interface.mjs +50 -0
- package/lib/providers/contract/registry.mjs +102 -0
- package/lib/providers/copilot-auth.mjs +297 -0
- package/lib/providers/credential-bootstrap.mjs +178 -0
- package/lib/providers/credential-catalog.mjs +46 -0
- package/lib/providers/credential-sources.mjs +63 -0
- package/lib/providers/creds.mjs +5 -2
- package/lib/providers/op-run.mjs +59 -0
- package/lib/providers/secret-resolver.mjs +159 -0
- package/lib/publish-template.mjs +163 -0
- package/lib/publish-tooling.mjs +119 -0
- package/lib/publish.mjs +305 -0
- package/lib/registry/cli.mjs +82 -0
- package/lib/registry/consolidation.mjs +147 -0
- package/lib/registry/generate-docs.mjs +75 -0
- package/lib/registry/skill-verification.mjs +57 -0
- package/lib/registry/surface-map.mjs +76 -0
- package/lib/registry/validate.mjs +135 -0
- package/lib/resources/budget.mjs +82 -0
- package/lib/resources/process-budget.mjs +45 -0
- package/lib/rules-delivery.mjs +9 -2
- package/lib/rules-read.mjs +26 -0
- package/lib/runtime-env.mjs +23 -0
- package/lib/runtime-pressure.mjs +51 -7
- package/lib/schema-infer.mjs +13 -2
- package/lib/server/chat-loop.mjs +622 -0
- package/lib/server/demo-preview.mjs +63 -0
- package/lib/server/index.mjs +228 -7
- package/lib/service-manager.mjs +60 -8
- package/lib/setup.mjs +70 -7
- package/lib/specialists/prompt-schema.mjs +19 -10
- package/lib/specialists/roster.mjs +43 -0
- package/lib/specialists/scaffold.mjs +56 -0
- package/lib/storage/backend.mjs +6 -6
- package/lib/storage/hybrid-query.mjs +7 -4
- package/lib/storage/state-source.mjs +5 -5
- package/lib/storage/sync.mjs +2 -3
- package/lib/telemetry/rule-calls.mjs +52 -0
- package/lib/template-registry.mjs +2 -2
- package/lib/templates/visual-requirements.mjs +27 -51
- package/lib/test-corpus-inventory.mjs +313 -0
- package/lib/uninstall/uninstall.mjs +19 -7
- package/lib/update.mjs +12 -0
- package/lib/upgrade.mjs +14 -0
- package/lib/wireframe.mjs +20 -14
- package/lib/worker/run.mjs +17 -4
- package/lib/worker/trace.mjs +11 -3
- package/package.json +28 -14
- package/personas/construct.md +2 -2
- package/platforms/claude/settings.template.json +36 -0
- package/rules/common/patterns.md +1 -1
- package/rules/common/release-gates.md +4 -3
- package/scripts/sync-specialists.mjs +41 -28
- package/skills/devops/data-engineering.md +1 -1
- package/skills/docs/adr-workflow.md +1 -0
- package/skills/docs/backlog-proposal-workflow.md +1 -0
- package/skills/docs/codebase-research-workflow.md +40 -0
- package/skills/docs/customer-profile-workflow.md +1 -0
- package/skills/docs/document-ingest-workflow.md +1 -0
- package/skills/docs/evidence-ingest-workflow.md +1 -0
- package/skills/docs/init-docs.md +2 -2
- package/skills/docs/init-project.md +7 -2
- package/skills/docs/prd-workflow.md +24 -1
- package/skills/docs/prfaq-workflow.md +1 -0
- package/skills/docs/product-intelligence-workflow.md +1 -0
- package/skills/docs/product-signal-workflow.md +1 -0
- package/skills/docs/research-workflow.md +54 -37
- package/skills/docs/runbook-workflow.md +1 -0
- package/skills/docs/strategy-workflow.md +1 -0
- package/skills/docs/user-research-workflow.md +40 -0
- package/skills/operating/orchestration-reference.md +1 -1
- package/skills/roles/architect.md +5 -0
- package/skills/roles/operator.docs.md +4 -0
- package/skills/routing.md +4 -2
- package/specialists/artifact-manifest.json +480 -0
- package/specialists/artifact-manifest.schema.json +53 -0
- package/specialists/audit-enrichments.json +454 -0
- package/specialists/contracts.json +37 -25
- package/specialists/prompts/_shared/validation-contract.md +26 -0
- package/specialists/prompts/cx-accessibility.md +21 -2
- package/specialists/prompts/cx-ai-engineer.md +24 -1
- package/specialists/prompts/cx-architect.md +4 -1
- package/specialists/prompts/cx-business-strategist.md +23 -2
- package/specialists/prompts/cx-data-analyst.md +22 -1
- package/specialists/prompts/cx-data-engineer.md +23 -2
- package/specialists/prompts/cx-debugger.md +21 -2
- package/specialists/prompts/cx-designer.md +26 -3
- package/specialists/prompts/cx-devil-advocate.md +19 -2
- package/specialists/prompts/cx-docs-keeper.md +28 -1
- package/specialists/prompts/cx-engineer.md +22 -1
- package/specialists/prompts/cx-evaluator.md +18 -1
- package/specialists/prompts/cx-explorer.md +21 -2
- package/specialists/prompts/cx-legal-compliance.md +21 -2
- package/specialists/prompts/cx-operations.md +21 -2
- package/specialists/prompts/cx-oracle.md +94 -0
- package/specialists/prompts/cx-orchestrator.md +20 -1
- package/specialists/prompts/cx-platform-engineer.md +25 -2
- package/specialists/prompts/cx-product-manager.md +32 -1
- package/specialists/prompts/cx-qa.md +24 -2
- package/specialists/prompts/cx-rd-lead.md +23 -2
- package/specialists/prompts/cx-release-manager.md +23 -2
- package/specialists/prompts/cx-researcher.md +22 -2
- package/specialists/prompts/cx-reviewer.md +18 -1
- package/specialists/prompts/cx-security.md +22 -2
- package/specialists/prompts/cx-sre.md +30 -3
- package/specialists/prompts/cx-test-automation.md +7 -1
- package/specialists/prompts/cx-trace-reviewer.md +22 -3
- package/specialists/prompts/cx-ux-researcher.md +21 -2
- package/specialists/registry.json +52 -173
- package/specialists/tone-profiles.json +42 -0
- package/templates/demos/playwright/demo-recording.config.mjs +47 -0
- package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
- package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
- package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
- package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
- package/templates/demos/tapes/resource-guard-rails.tape +49 -0
- package/templates/demos/vhs/construct-cockpit.json +24 -0
- package/templates/distribution/construct-brand.typ +446 -0
- package/templates/distribution/construct-decision.typ +38 -0
- package/templates/distribution/construct-deck.html +95 -0
- package/templates/distribution/construct-pdf.typ +38 -0
- package/templates/distribution/construct-prd.typ +38 -0
- package/templates/distribution/construct-research.typ +38 -0
- package/templates/distribution/construct-web.html +92 -0
- package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
- package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
- package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
- package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/README.md +36 -0
- package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
- package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
- package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
- package/templates/distribution/icons/activity.svg +15 -0
- package/templates/distribution/icons/alert-triangle.svg +17 -0
- package/templates/distribution/icons/book-open.svg +16 -0
- package/templates/distribution/icons/bot.svg +20 -0
- package/templates/distribution/icons/brain.svg +22 -0
- package/templates/distribution/icons/circle-check.svg +16 -0
- package/templates/distribution/icons/clipboard-check.svg +17 -0
- package/templates/distribution/icons/cpu.svg +28 -0
- package/templates/distribution/icons/database.svg +17 -0
- package/templates/distribution/icons/eye.svg +16 -0
- package/templates/distribution/icons/file-text.svg +19 -0
- package/templates/distribution/icons/gauge.svg +16 -0
- package/templates/distribution/icons/git-branch.svg +17 -0
- package/templates/distribution/icons/key.svg +17 -0
- package/templates/distribution/icons/layers.svg +17 -0
- package/templates/distribution/icons/list-checks.svg +19 -0
- package/templates/distribution/icons/lock.svg +16 -0
- package/templates/distribution/icons/message-square.svg +15 -0
- package/templates/distribution/icons/network.svg +19 -0
- package/templates/distribution/icons/route.svg +17 -0
- package/templates/distribution/icons/scale.svg +19 -0
- package/templates/distribution/icons/search.svg +16 -0
- package/templates/distribution/icons/send.svg +16 -0
- package/templates/distribution/icons/server.svg +18 -0
- package/templates/distribution/icons/shield-check.svg +16 -0
- package/templates/distribution/icons/users.svg +18 -0
- package/templates/distribution/icons/webhook.svg +17 -0
- package/templates/distribution/icons/workflow.svg +17 -0
- package/templates/distribution/icons/wrench.svg +15 -0
- package/templates/distribution/run.mjs +18 -1
- package/templates/docs/adr.md +7 -0
- package/templates/docs/construct_guide.md +8 -8
- package/templates/docs/customer-profile.md +4 -0
- package/templates/docs/one-pager.md +4 -0
- package/templates/docs/postmortem.md +31 -0
- package/templates/docs/prd-platform.md +19 -0
- package/templates/docs/prd.md +15 -0
- package/templates/docs/prfaq.md +7 -0
- package/templates/docs/qa-strategy.md +103 -0
- package/templates/docs/research-brief.md +8 -0
- package/templates/docs/rfc-platform.md +12 -0
- package/templates/docs/test-plan.md +7 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/test-corpus-inventory.mjs — classify the Construct test corpus for certification traceability.
|
|
3
|
+
*
|
|
4
|
+
* Walks all test files under tests/, assigns layer and signal category per file, extracts
|
|
5
|
+
* @capability markers, and compares shipped capability-matrix rows against the behavior
|
|
6
|
+
* ledger to surface release-critical gaps. Output feeds tests/capabilities/corpus-inventory.json.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { loadCapabilityLedger } from './capability-ledger.mjs';
|
|
12
|
+
|
|
13
|
+
export const CORPUS_CATEGORIES = Object.freeze([
|
|
14
|
+
'structural-guard',
|
|
15
|
+
'unit',
|
|
16
|
+
'integration',
|
|
17
|
+
'hook',
|
|
18
|
+
'functional',
|
|
19
|
+
'live-provider',
|
|
20
|
+
'visual',
|
|
21
|
+
'contract-subsystem',
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
const STRUCTURAL_GUARD_NAMES = [
|
|
25
|
+
'agent-prompts',
|
|
26
|
+
'release-workflow',
|
|
27
|
+
'parity',
|
|
28
|
+
'comment-lint',
|
|
29
|
+
'agents-registry',
|
|
30
|
+
'cli-catalog-accuracy',
|
|
31
|
+
'auto-docs',
|
|
32
|
+
'docs-verify',
|
|
33
|
+
'sync-contract',
|
|
34
|
+
'release-gates',
|
|
35
|
+
'template-policy',
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const SUBSYSTEM_DIRS = [
|
|
39
|
+
'profile',
|
|
40
|
+
'profiles',
|
|
41
|
+
'outcomes',
|
|
42
|
+
'flavors',
|
|
43
|
+
'knowledge',
|
|
44
|
+
'intake',
|
|
45
|
+
'roles',
|
|
46
|
+
'embed',
|
|
47
|
+
'doctor',
|
|
48
|
+
'integrations',
|
|
49
|
+
'graph',
|
|
50
|
+
'evals',
|
|
51
|
+
'reflect',
|
|
52
|
+
'hooks',
|
|
53
|
+
'functional',
|
|
54
|
+
'capabilities',
|
|
55
|
+
'fixtures',
|
|
56
|
+
'e2e',
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const SHIPPED_MATRIX_CAPABILITIES = Object.freeze([
|
|
60
|
+
{ id: 'document', ledgerHints: ['artifact', 'docs'] },
|
|
61
|
+
{ id: 'research', ledgerHints: ['research', 'knowledge', 'ask'] },
|
|
62
|
+
{ id: 'ingest', ledgerHints: ['document.ingest', 'ingest'] },
|
|
63
|
+
{ id: 'diagram', ledgerHints: ['diagram', 'wireframe'] },
|
|
64
|
+
{ id: 'demo', ledgerHints: ['demo'] },
|
|
65
|
+
{ id: 'publish', ledgerHints: ['publish', 'artifact', 'export'] },
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
function findConstructRoot(startPath = process.cwd()) {
|
|
69
|
+
let current = path.resolve(startPath);
|
|
70
|
+
while (true) {
|
|
71
|
+
if (fs.existsSync(path.join(current, 'package.json')) && fs.existsSync(path.join(current, 'tests'))) return current;
|
|
72
|
+
const parent = path.dirname(current);
|
|
73
|
+
if (parent === current) return path.resolve(startPath);
|
|
74
|
+
current = parent;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function defaultCorpusInventoryPath(rootDir = process.cwd()) {
|
|
79
|
+
return path.join(findConstructRoot(rootDir), 'tests', 'capabilities', 'corpus-inventory.json');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function listTestFiles(testsDir) {
|
|
83
|
+
const files = [];
|
|
84
|
+
const walk = (dir) => {
|
|
85
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
86
|
+
const absolute = path.join(dir, entry.name);
|
|
87
|
+
if (entry.isDirectory()) {
|
|
88
|
+
if (entry.name === 'node_modules' || entry.name === '.tmp') continue;
|
|
89
|
+
walk(absolute);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (/\.test\.(mjs|js)$/.test(entry.name)) files.push(absolute);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
walk(testsDir);
|
|
96
|
+
return files.sort();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function readHeaderNote(content) {
|
|
100
|
+
const block = content.match(/^\/\*\*([\s\S]*?)\*\//)?.[1] ?? content.match(/^\/\/\/([\s\S]*?)(?:\n\n|\nimport )/)?.[1];
|
|
101
|
+
if (!block) return '';
|
|
102
|
+
const line = block
|
|
103
|
+
.split('\n')
|
|
104
|
+
.map((row) => row.replace(/^\s*\*\s?/, '').trim())
|
|
105
|
+
.find((row) => row.length > 0 && !row.startsWith('@'));
|
|
106
|
+
return line?.slice(0, 160) ?? '';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function classifyLayer(relPath, content) {
|
|
110
|
+
const base = path.basename(relPath);
|
|
111
|
+
if (relPath.includes('tests/functional/') || base.includes('.functional.test.')) return 'functional';
|
|
112
|
+
if (base.includes('real-llm') || content.includes('CONSTRUCT_E2E_REAL_LLM')) return 'live-provider';
|
|
113
|
+
if (/visual/i.test(base) || /playwright/i.test(content)) return 'visual';
|
|
114
|
+
if (
|
|
115
|
+
/store/i.test(base)
|
|
116
|
+
|| /postgres/i.test(base)
|
|
117
|
+
|| /hybrid/i.test(base)
|
|
118
|
+
|| /vector-client/i.test(base)
|
|
119
|
+
|| relPath.includes('/hooks/')
|
|
120
|
+
|| /hook/i.test(base)
|
|
121
|
+
) return 'integration';
|
|
122
|
+
return 'unit';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function classifyCategory(relPath, layer, content) {
|
|
126
|
+
const base = path.basename(relPath);
|
|
127
|
+
const dir = path.dirname(relPath);
|
|
128
|
+
if (layer === 'functional') return 'functional';
|
|
129
|
+
if (layer === 'live-provider') return 'live-provider';
|
|
130
|
+
if (layer === 'visual') return 'visual';
|
|
131
|
+
if (dir.includes('/hooks/') || /\bhook/i.test(base)) return 'hook';
|
|
132
|
+
if (STRUCTURAL_GUARD_NAMES.some((name) => base.includes(name))) return 'structural-guard';
|
|
133
|
+
if (layer === 'integration') return 'integration';
|
|
134
|
+
if (SUBSYSTEM_DIRS.some((segment) => dir.split(path.sep).includes(segment))) return 'contract-subsystem';
|
|
135
|
+
if (/@capability\s+/.test(content) && layer === 'unit') return 'unit';
|
|
136
|
+
return 'unit';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function extractCapabilities(content) {
|
|
140
|
+
return [...content.matchAll(/@capability\s+([a-z][a-z0-9]*(?:[.-][a-z0-9]+)*)/g)].map((match) => match[1]);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function detectSkipped(content) {
|
|
144
|
+
return /\btest\.skip\s*\(|\bit\.skip\s*\(|\bdescribe\.skip\s*\(/.test(content);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function classifyTestFile(relPath, content) {
|
|
148
|
+
const layer = classifyLayer(relPath, content);
|
|
149
|
+
return {
|
|
150
|
+
path: relPath.replace(/\\/g, '/'),
|
|
151
|
+
layer,
|
|
152
|
+
category: classifyCategory(relPath, layer, content),
|
|
153
|
+
capabilities: extractCapabilities(content),
|
|
154
|
+
skipped: detectSkipped(content),
|
|
155
|
+
maintainerNotes: readHeaderNote(content),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function countBy(items, key) {
|
|
160
|
+
const totals = {};
|
|
161
|
+
for (const item of items) totals[item[key]] = (totals[item[key]] ?? 0) + 1;
|
|
162
|
+
return totals;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function ledgerCoversHint(ledgerIds, hints) {
|
|
166
|
+
return hints.some((hint) => ledgerIds.some((id) => id.includes(hint)));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function computeReleaseCriticalGaps(ledger) {
|
|
170
|
+
const allLedgerIds = (ledger?.capabilities ?? []).map((entry) => entry.id);
|
|
171
|
+
const gaps = [];
|
|
172
|
+
|
|
173
|
+
for (const row of SHIPPED_MATRIX_CAPABILITIES) {
|
|
174
|
+
if (ledgerCoversHint(allLedgerIds, row.ledgerHints)) continue;
|
|
175
|
+
gaps.push({
|
|
176
|
+
source: 'docs/audit/capability-matrix.md',
|
|
177
|
+
capability: row.id,
|
|
178
|
+
status: 'shipped',
|
|
179
|
+
reason: 'no ledger entry matches shipped capability hints',
|
|
180
|
+
suggestedLedgerHints: row.ledgerHints,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return gaps;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function buildTestCorpusInventory({ rootDir } = {}) {
|
|
188
|
+
const root = findConstructRoot(rootDir);
|
|
189
|
+
const testsDir = path.join(root, 'tests');
|
|
190
|
+
const files = listTestFiles(testsDir);
|
|
191
|
+
const entries = files.map((absolute) => {
|
|
192
|
+
const rel = path.relative(root, absolute);
|
|
193
|
+
const content = fs.readFileSync(absolute, 'utf8');
|
|
194
|
+
return classifyTestFile(rel, content);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const { ledger } = loadCapabilityLedger({ rootDir: root });
|
|
198
|
+
const topLevel = entries.filter((entry) => entry.path.split('/').length === 2).length;
|
|
199
|
+
const nested = entries.length - topLevel;
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
version: 1,
|
|
203
|
+
generatedAt: new Date().toISOString(),
|
|
204
|
+
generator: 'node scripts/generate-test-corpus-inventory.mjs',
|
|
205
|
+
summary: {
|
|
206
|
+
testFiles: entries.length,
|
|
207
|
+
topLevelTestFiles: topLevel,
|
|
208
|
+
nestedTestFiles: nested,
|
|
209
|
+
skippedFiles: entries.filter((entry) => entry.skipped).length,
|
|
210
|
+
capabilityMarkedFiles: entries.filter((entry) => entry.capabilities.length > 0).length,
|
|
211
|
+
byLayer: countBy(entries, 'layer'),
|
|
212
|
+
byCategory: countBy(entries, 'category'),
|
|
213
|
+
},
|
|
214
|
+
releaseCriticalGaps: computeReleaseCriticalGaps(ledger),
|
|
215
|
+
files: entries,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function formatAuditAtAGlance(inventory) {
|
|
220
|
+
const { summary } = inventory;
|
|
221
|
+
const functional = summary.byCategory.functional ?? 0;
|
|
222
|
+
const subsystem = summary.byCategory['contract-subsystem'] ?? 0;
|
|
223
|
+
const hook = summary.byCategory.hook ?? 0;
|
|
224
|
+
return [
|
|
225
|
+
`- **${summary.testFiles} test files** total: ${summary.topLevelTestFiles} at \`tests/\` top level + ${summary.nestedTestFiles} in subdirectories.`,
|
|
226
|
+
`- **Layers:** ${Object.entries(summary.byLayer).map(([k, v]) => `${k} ${v}`).join(', ')}.`,
|
|
227
|
+
`- **Functional layer:** ${functional} file(s) under \`tests/functional/\` or \`*.functional.test.mjs\`. End-to-end checks spawn the real binary or import real modules in an isolated tmpdir.`,
|
|
228
|
+
`- **Contract subsystems:** ${subsystem} file(s) under profile/outcomes/hooks/knowledge/intake/graph/evals and related subdirs.`,
|
|
229
|
+
`- **Hook tests:** ${hook} file(s) (including \`tests/hooks/\`).`,
|
|
230
|
+
`- **Capability-marked:** ${summary.capabilityMarkedFiles} file(s) declare \`@capability\` markers.`,
|
|
231
|
+
`- **Skipped markers:** ${summary.skippedFiles} file(s) contain \`test.skip\` / \`describe.skip\` (see inventory for paths).`,
|
|
232
|
+
`- **Regenerate inventory:** \`node scripts/generate-test-corpus-inventory.mjs\`.`,
|
|
233
|
+
].join('\n');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function refreshAuditMarkdown(inventory, { rootDir } = {}) {
|
|
237
|
+
const root = findConstructRoot(rootDir);
|
|
238
|
+
const auditPath = path.join(root, 'tests', 'AUDIT.md');
|
|
239
|
+
const markdown = fs.readFileSync(auditPath, 'utf8');
|
|
240
|
+
const start = '## At a glance';
|
|
241
|
+
const end = '## Categories';
|
|
242
|
+
const startIdx = markdown.indexOf(start);
|
|
243
|
+
const endIdx = markdown.indexOf(end);
|
|
244
|
+
if (startIdx === -1 || endIdx === -1) throw new Error('tests/AUDIT.md missing At a glance section');
|
|
245
|
+
const replacement = `${start}\n\n${formatAuditAtAGlance(inventory)}\n\n`;
|
|
246
|
+
return `${markdown.slice(0, startIdx)}${replacement}${markdown.slice(endIdx)}`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function validateCorpusInventory({ rootDir, inventory: supplied } = {}) {
|
|
250
|
+
const root = findConstructRoot(rootDir);
|
|
251
|
+
const inventory = supplied ?? JSON.parse(fs.readFileSync(defaultCorpusInventoryPath(root), 'utf8'));
|
|
252
|
+
const errors = [];
|
|
253
|
+
const warnings = [];
|
|
254
|
+
const onDisk = listTestFiles(path.join(root, 'tests')).map((absolute) => path.relative(root, absolute).replace(/\\/g, '/'));
|
|
255
|
+
const indexed = new Set((inventory.files ?? []).map((entry) => entry.path));
|
|
256
|
+
|
|
257
|
+
if (inventory.version !== 1) errors.push('corpus-inventory.version must equal 1');
|
|
258
|
+
if (!Array.isArray(inventory.files) || inventory.files.length === 0) errors.push('corpus-inventory.files must be non-empty');
|
|
259
|
+
|
|
260
|
+
for (const rel of onDisk) {
|
|
261
|
+
if (!indexed.has(rel)) errors.push(`missing inventory entry: ${rel}`);
|
|
262
|
+
}
|
|
263
|
+
for (const entry of inventory.files ?? []) {
|
|
264
|
+
if (!onDisk.includes(entry.path)) errors.push(`stale inventory entry: ${entry.path}`);
|
|
265
|
+
if (!CORPUS_CATEGORIES.includes(entry.category)) errors.push(`${entry.path}: invalid category ${entry.category}`);
|
|
266
|
+
if (!entry.path.endsWith('.test.mjs') && !entry.path.endsWith('.test.js')) errors.push(`${entry.path}: not a test file path`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if ((inventory.summary?.testFiles ?? 0) !== (inventory.files?.length ?? 0)) {
|
|
270
|
+
errors.push('summary.testFiles must equal files.length');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (!Array.isArray(inventory.releaseCriticalGaps) || inventory.releaseCriticalGaps.length === 0) {
|
|
274
|
+
warnings.push('releaseCriticalGaps is empty');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
filePath: defaultCorpusInventoryPath(root),
|
|
279
|
+
fileCount: inventory.files?.length ?? 0,
|
|
280
|
+
errors,
|
|
281
|
+
warnings,
|
|
282
|
+
pass: errors.length === 0,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function writeCorpusInventoryArtifacts({ rootDir } = {}) {
|
|
287
|
+
const root = findConstructRoot(rootDir);
|
|
288
|
+
const inventory = buildTestCorpusInventory({ rootDir: root });
|
|
289
|
+
const inventoryPath = defaultCorpusInventoryPath(root);
|
|
290
|
+
fs.mkdirSync(path.dirname(inventoryPath), { recursive: true });
|
|
291
|
+
fs.writeFileSync(inventoryPath, `${JSON.stringify(inventory, null, 2)}\n`);
|
|
292
|
+
fs.writeFileSync(path.join(root, 'tests', 'AUDIT.md'), refreshAuditMarkdown(inventory, { rootDir: root }));
|
|
293
|
+
return { inventoryPath, inventory };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function runCorpusInventoryAuditCli(args = [], { rootDir } = {}) {
|
|
297
|
+
if (args.includes('--write')) {
|
|
298
|
+
const { inventoryPath, inventory } = writeCorpusInventoryArtifacts({ rootDir });
|
|
299
|
+
process.stdout.write(`Wrote ${inventoryPath} (${inventory.files.length} files)\n`);
|
|
300
|
+
process.stdout.write('Updated tests/AUDIT.md At a glance section\n');
|
|
301
|
+
return validateCorpusInventory({ rootDir, inventory });
|
|
302
|
+
}
|
|
303
|
+
const result = validateCorpusInventory({ rootDir });
|
|
304
|
+
if (args.includes('--json')) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
305
|
+
else {
|
|
306
|
+
process.stdout.write(`Corpus inventory: ${result.fileCount} files\n`);
|
|
307
|
+
if (result.errors.length) result.errors.forEach((error) => process.stderr.write(` ✗ ${error}\n`));
|
|
308
|
+
if (result.warnings.length) result.warnings.forEach((warning) => process.stderr.write(` ⚠ ${warning}\n`));
|
|
309
|
+
process.stdout.write(result.pass ? ' Result: PASS\n' : ' Result: FAIL\n');
|
|
310
|
+
}
|
|
311
|
+
if (!result.pass) process.exitCode = 1;
|
|
312
|
+
return result;
|
|
313
|
+
}
|
|
@@ -38,11 +38,10 @@ import { fileURLToPath } from 'node:url';
|
|
|
38
38
|
import { multiSelect } from '../tty-prompts.mjs';
|
|
39
39
|
import { getUserConfigDir, getUserEnvPath } from '../env-config.mjs';
|
|
40
40
|
import { removeTomlTables, tomlString } from '../codex-config.mjs';
|
|
41
|
+
import { postgresContainerName, LEGACY_PG_CONTAINER } from '../home-namespace.mjs';
|
|
41
42
|
|
|
42
43
|
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
43
44
|
const PKG_ROOT = path.resolve(__dirname, '..', '..');
|
|
44
|
-
|
|
45
|
-
const POSTGRES_CONTAINER = 'construct-postgres';
|
|
46
45
|
const PGVECTOR_IMAGE = 'pgvector/pgvector:pg16';
|
|
47
46
|
const PRESSURE_GUARD_LABEL = 'dev.construct.pressure-release';
|
|
48
47
|
const MEMORY_MCP_KEYS = ['memory', 'cass'];
|
|
@@ -75,6 +74,7 @@ export function parseArgs(argv) {
|
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
function buildCategories({ cwd, home, withImages = false }) {
|
|
77
|
+
const pgContainerName = postgresContainerName(process.env, home);
|
|
78
78
|
const dotConstruct = path.join(cwd, '.construct');
|
|
79
79
|
const dotClaudeAgents = path.join(cwd, '.claude', 'agents');
|
|
80
80
|
const dotClaudeCommands = path.join(cwd, '.claude', 'commands');
|
|
@@ -213,10 +213,19 @@ function buildCategories({ cwd, home, withImages = false }) {
|
|
|
213
213
|
id: 'machine-postgres-container',
|
|
214
214
|
scope: 'machine',
|
|
215
215
|
risk: 'ask',
|
|
216
|
-
label: `Docker container "${
|
|
217
|
-
detect: () => dockerContainerExists(
|
|
218
|
-
describe: () => 'Stops and removes the
|
|
219
|
-
execute: () =>
|
|
216
|
+
label: `Docker container "${pgContainerName}" (stop + remove, including data)`,
|
|
217
|
+
detect: () => dockerContainerExists(pgContainerName) || dockerContainerExists(LEGACY_PG_CONTAINER),
|
|
218
|
+
describe: () => 'Stops and removes the Postgres container and its named data volume. The pgvector image stays cached. This destroys indexed observations — skip if you might restore.',
|
|
219
|
+
execute: () => {
|
|
220
|
+
const results = [];
|
|
221
|
+
if (dockerContainerExists(pgContainerName)) {
|
|
222
|
+
results.push(removeDockerContainer(pgContainerName, `postgres_${pgContainerName}-data`));
|
|
223
|
+
}
|
|
224
|
+
if (dockerContainerExists(LEGACY_PG_CONTAINER)) {
|
|
225
|
+
results.push(removeDockerContainer(LEGACY_PG_CONTAINER));
|
|
226
|
+
}
|
|
227
|
+
return results.join(', ') || 'nothing to remove';
|
|
228
|
+
},
|
|
220
229
|
},
|
|
221
230
|
{
|
|
222
231
|
id: 'machine-postgres-compose',
|
|
@@ -468,12 +477,15 @@ function dockerContainerExists(name) {
|
|
|
468
477
|
return probe.stdout.trim() === name;
|
|
469
478
|
}
|
|
470
479
|
|
|
471
|
-
function removeDockerContainer(name) {
|
|
480
|
+
function removeDockerContainer(name, namedVolume = null) {
|
|
472
481
|
const stop = spawnSync('docker', ['stop', name], { encoding: 'utf8' });
|
|
473
482
|
const rm = spawnSync('docker', ['rm', '-v', name], { encoding: 'utf8' });
|
|
474
483
|
if (rm.status !== 0) {
|
|
475
484
|
throw new Error((rm.stderr || '').trim() || 'docker rm failed');
|
|
476
485
|
}
|
|
486
|
+
if (namedVolume) {
|
|
487
|
+
spawnSync('docker', ['volume', 'rm', namedVolume], { encoding: 'utf8' });
|
|
488
|
+
}
|
|
477
489
|
return stop.status === 0 ? 'stopped and removed (with volume)' : 'removed (with volume)';
|
|
478
490
|
}
|
|
479
491
|
|
package/lib/update.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import path from 'node:path';
|
|
|
11
11
|
import { spawnSync } from 'node:child_process';
|
|
12
12
|
|
|
13
13
|
import { writeVersionStamp } from './maintenance/cleanup.mjs';
|
|
14
|
+
import { reclaimOrphanedDockerResources, formatReclaim } from './maintenance/docker-reclaim.mjs';
|
|
14
15
|
|
|
15
16
|
const PACKAGE_NAME = '@geraldmaron/construct';
|
|
16
17
|
|
|
@@ -131,6 +132,17 @@ export function runUpdate({ cwd, env = process.env, spawn = spawnSync, stdout =
|
|
|
131
132
|
runStep(step, { spawn, env, stdout });
|
|
132
133
|
}
|
|
133
134
|
|
|
135
|
+
// Reclaim Postgres containers and volumes left behind by deleted home
|
|
136
|
+
// namespaces. An upgrade is the once-per-version moment to sweep machine
|
|
137
|
+
// state without taxing ordinary commands. The disable switch opts a machine
|
|
138
|
+
// out of every auto-maintenance pass, not this sweep alone.
|
|
139
|
+
if (env.CONSTRUCT_DISABLE_AUTO_CLEANUP !== '1') {
|
|
140
|
+
try {
|
|
141
|
+
const line = formatReclaim(reclaimOrphanedDockerResources({ env }));
|
|
142
|
+
if (line) stdout.write(`${line}\n`);
|
|
143
|
+
} catch { /* reclaim is best-effort */ }
|
|
144
|
+
}
|
|
145
|
+
|
|
134
146
|
// Advance the cleanup stamp to the checkout version so the next CLI
|
|
135
147
|
// invocation doesn't run a full cleanup pass on the assumption that
|
|
136
148
|
// an upgrade just happened.
|
package/lib/upgrade.mjs
CHANGED
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
import fs from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { spawnSync } from 'node:child_process';
|
|
11
|
+
import { sanitizeNpmSpawnEnv } from './npm-spawn-env.mjs';
|
|
11
12
|
import { fileURLToPath } from 'node:url';
|
|
12
13
|
|
|
13
14
|
import { writeVersionStamp } from './maintenance/cleanup.mjs';
|
|
15
|
+
import { reclaimOrphanedDockerResources, formatReclaim } from './maintenance/docker-reclaim.mjs';
|
|
14
16
|
|
|
15
17
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
18
|
const ROOT_DIR = path.resolve(__dirname, '..');
|
|
@@ -31,6 +33,7 @@ function getLatestVersion() {
|
|
|
31
33
|
const result = spawnSync('npm', ['view', PACKAGE_NAME, 'version'], {
|
|
32
34
|
encoding: 'utf8',
|
|
33
35
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
36
|
+
env: sanitizeNpmSpawnEnv(process.env),
|
|
34
37
|
});
|
|
35
38
|
if (result.status !== 0) return null;
|
|
36
39
|
return result.stdout.trim();
|
|
@@ -154,6 +157,17 @@ export function runUpgrade({ cwd = process.cwd(), env = process.env, stdout = pr
|
|
|
154
157
|
}
|
|
155
158
|
}
|
|
156
159
|
|
|
160
|
+
// Reclaim Postgres containers and volumes left behind by deleted home
|
|
161
|
+
// namespaces. An upgrade is the once-per-version moment to sweep machine
|
|
162
|
+
// state without taxing ordinary commands. The disable switch opts a machine
|
|
163
|
+
// out of every auto-maintenance pass, not this sweep alone.
|
|
164
|
+
if (env.CONSTRUCT_DISABLE_AUTO_CLEANUP !== '1') {
|
|
165
|
+
try {
|
|
166
|
+
const line = formatReclaim(reclaimOrphanedDockerResources({ env }));
|
|
167
|
+
if (line) stdout.write(`${line}\n`);
|
|
168
|
+
} catch { /* reclaim is best-effort */ }
|
|
169
|
+
}
|
|
170
|
+
|
|
157
171
|
// Advance the cleanup stamp so the next CLI invocation doesn't re-run a
|
|
158
172
|
// full cleanup pass under the assumption that the user just upgraded.
|
|
159
173
|
// Without this, every command pays the cleanup cost until something
|
package/lib/wireframe.mjs
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
21
21
|
import { join, resolve } from 'node:path';
|
|
22
|
+
import { INK, FONTS } from './brand-tokens.mjs';
|
|
22
23
|
|
|
23
24
|
const TYPES = ['flow', 'state', 'sequence', 'er', 'layout', 'user-journey'];
|
|
24
25
|
|
|
@@ -134,38 +135,43 @@ function mermaidUserJourney(description, keywords) {
|
|
|
134
135
|
|
|
135
136
|
const SKETCH_CSS = `
|
|
136
137
|
:root {
|
|
137
|
-
--ink:
|
|
138
|
-
--paper:
|
|
139
|
-
--rough:
|
|
140
|
-
--hint:
|
|
141
|
-
--accent:
|
|
138
|
+
--ink: ${INK.inkBody};
|
|
139
|
+
--paper: ${INK.paper};
|
|
140
|
+
--rough: ${INK.muted};
|
|
141
|
+
--hint: ${INK.faint};
|
|
142
|
+
--accent: ${INK.ink};
|
|
143
|
+
--hairline: ${INK.hairline};
|
|
144
|
+
--surface: ${INK.surface};
|
|
142
145
|
}
|
|
143
146
|
* { box-sizing: border-box; }
|
|
144
147
|
body {
|
|
145
148
|
margin: 0; padding: 2rem;
|
|
146
|
-
font-family:
|
|
149
|
+
font-family: ${FONTS.sans};
|
|
147
150
|
background: var(--paper);
|
|
148
151
|
color: var(--ink);
|
|
149
|
-
line-height: 1.
|
|
152
|
+
line-height: 1.45;
|
|
153
|
+
font-size: 15px;
|
|
150
154
|
}
|
|
151
155
|
.wireframe { max-width: 1200px; margin: 0 auto; }
|
|
152
156
|
.wireframe header, .wireframe nav, .wireframe main,
|
|
153
157
|
.wireframe aside, .wireframe section, .wireframe footer,
|
|
154
158
|
.block, .card {
|
|
155
|
-
border:
|
|
156
|
-
border-radius:
|
|
159
|
+
border: 1px solid var(--hairline);
|
|
160
|
+
border-radius: 8px;
|
|
157
161
|
padding: 1rem;
|
|
158
162
|
margin: 0.5rem 0;
|
|
159
163
|
position: relative;
|
|
160
|
-
background: var(--
|
|
161
|
-
box-shadow:
|
|
164
|
+
background: var(--surface);
|
|
165
|
+
box-shadow: none;
|
|
162
166
|
}
|
|
163
|
-
.wireframe header { background:
|
|
167
|
+
.wireframe header { background: var(--surface); }
|
|
164
168
|
.label {
|
|
165
169
|
position: absolute; top: -10px; left: 10px;
|
|
166
170
|
background: var(--paper); padding: 0 6px;
|
|
167
|
-
font-size: 0.
|
|
168
|
-
font-family:
|
|
171
|
+
font-size: 0.75rem; color: var(--rough);
|
|
172
|
+
font-family: ${FONTS.mono};
|
|
173
|
+
letter-spacing: 0.04em;
|
|
174
|
+
text-transform: uppercase;
|
|
169
175
|
}
|
|
170
176
|
.row { display: grid; grid-template-columns: repeat(var(--cols, 3), 1fr); gap: 1rem; }
|
|
171
177
|
.row-2 { --cols: 2; }
|
package/lib/worker/run.mjs
CHANGED
|
@@ -27,6 +27,7 @@ import { mkdirSync, writeFileSync, existsSync } from 'node:fs';
|
|
|
27
27
|
import path from 'node:path';
|
|
28
28
|
|
|
29
29
|
import { emitTraceEvent, newTraceId, newSpanId } from './trace.mjs';
|
|
30
|
+
import { ensureDiskWrite, warnBudgetOnce } from '../resources/budget.mjs';
|
|
30
31
|
|
|
31
32
|
const DEFAULT_TIMEOUT_SECONDS = 300;
|
|
32
33
|
const ARTIFACTS_SUBDIR = '.cx/runtime/worker';
|
|
@@ -120,8 +121,17 @@ export async function runJob({
|
|
|
120
121
|
});
|
|
121
122
|
clearTimeout(timer);
|
|
122
123
|
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
const outBytes = Buffer.byteLength(stdout, 'utf8');
|
|
125
|
+
const errBytes = Buffer.byteLength(stderr, 'utf8');
|
|
126
|
+
const gate = ensureDiskWrite(rootDir, 'worker-logs', outBytes + errBytes, process.env);
|
|
127
|
+
let budgetSkipped = false;
|
|
128
|
+
if (gate.ok) {
|
|
129
|
+
writeFileSync(stdoutPath, stdout, 'utf8');
|
|
130
|
+
writeFileSync(stderrPath, stderr, 'utf8');
|
|
131
|
+
} else {
|
|
132
|
+
budgetSkipped = true;
|
|
133
|
+
warnBudgetOnce(gate.message || 'worker log write blocked by .cx/ disk budget', process.env);
|
|
134
|
+
}
|
|
125
135
|
|
|
126
136
|
const durationMs = Date.now() - started;
|
|
127
137
|
const status = timedOut ? 'timeout' : exitCode === 0 ? 'passed' : 'failed';
|
|
@@ -135,7 +145,10 @@ export async function runJob({
|
|
|
135
145
|
stdoutPath,
|
|
136
146
|
stderrPath,
|
|
137
147
|
durationMs,
|
|
138
|
-
artifacts:
|
|
148
|
+
artifacts: budgetSkipped
|
|
149
|
+
? []
|
|
150
|
+
: [{ path: stdoutPath, kind: 'stdout' }, { path: stderrPath, kind: 'stderr' }],
|
|
151
|
+
budgetSkipped,
|
|
139
152
|
traceId,
|
|
140
153
|
spanId,
|
|
141
154
|
};
|
|
@@ -147,7 +160,7 @@ export async function runJob({
|
|
|
147
160
|
spanId,
|
|
148
161
|
project: job.project,
|
|
149
162
|
taskId: job.taskId,
|
|
150
|
-
metadata: { jobId: job.jobId, status, exitCode, durationMs, stdoutPath, stderrPath, timedOut },
|
|
163
|
+
metadata: { jobId: job.jobId, status, exitCode, durationMs, stdoutPath, stderrPath, timedOut, budgetSkipped },
|
|
151
164
|
});
|
|
152
165
|
|
|
153
166
|
return result;
|
package/lib/worker/trace.mjs
CHANGED
|
@@ -33,6 +33,7 @@ import path from 'node:path';
|
|
|
33
33
|
import { createTelemetryClient } from '../telemetry/client.mjs';
|
|
34
34
|
import { ensureCxDir } from '../project-init-shared.mjs';
|
|
35
35
|
import { appendWithRotationSync } from '../logging/rotate.mjs';
|
|
36
|
+
import { ensureDiskWrite, warnBudgetOnce } from '../resources/budget.mjs';
|
|
36
37
|
|
|
37
38
|
const TRACE_SUBDIR = '.cx/traces';
|
|
38
39
|
|
|
@@ -183,9 +184,16 @@ export function emitTraceEvent({
|
|
|
183
184
|
createdAt: new Date().toISOString(),
|
|
184
185
|
};
|
|
185
186
|
const shardPath = path.join(dir, `${todayShard()}.jsonl`);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
const line = `${JSON.stringify(event)}\n`;
|
|
188
|
+
const gate = ensureDiskWrite(rootDir, 'traces', Buffer.byteLength(line), env);
|
|
189
|
+
if (gate.ok) {
|
|
190
|
+
appendWithRotationSync(shardPath, line, {
|
|
191
|
+
maxBytes: traceMaxBytes(env),
|
|
192
|
+
});
|
|
193
|
+
} else {
|
|
194
|
+
warnBudgetOnce(gate.message || 'trace write blocked by .cx/ disk budget', env);
|
|
195
|
+
return { ...event, budgetSkipped: true, budgetReason: gate.reason || 'budget-exceeded' };
|
|
196
|
+
}
|
|
189
197
|
|
|
190
198
|
// Fire-and-forget export to the configured remote adapter. Silent no-op when not configured.
|
|
191
199
|
exportToRemote(event, env, rootDir);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geraldmaron/construct",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"packageManager": "npm@11.5.1",
|
|
6
6
|
"description": "Construct — agent orchestration layer for OpenCode, Claude Code, and other coding surfaces",
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
"platforms/**",
|
|
27
27
|
"templates/**",
|
|
28
28
|
"templates/distribution/**",
|
|
29
|
-
"db/**",
|
|
30
29
|
"scripts/sync-specialists.mjs",
|
|
31
30
|
"README.md",
|
|
32
31
|
".env.example"
|
|
@@ -57,20 +56,28 @@
|
|
|
57
56
|
"test:unit": "node scripts/run-tests.mjs --exclude=tests/functional",
|
|
58
57
|
"lint:js": "eslint bin/construct \"lib/**/*.mjs\" \"scripts/**/*.mjs\" \"tests/**/*.mjs\"",
|
|
59
58
|
"coverage": "c8 --reporter=text-summary --reporter=lcov --src=lib --src=bin node scripts/run-tests.mjs --exclude=tests/functional",
|
|
60
|
-
"docs:init": "node
|
|
59
|
+
"docs:init": "node -e \"console.error('docs:init is deprecated — use: construct init --docs-preset=lean|product|full'); process.exit(1)\"",
|
|
61
60
|
"docs:update": "node ./bin/construct docs:update",
|
|
62
61
|
"docs:site": "node ./bin/construct docs:site",
|
|
63
|
-
"docs:dev": "npm --prefix apps/docs run dev",
|
|
64
|
-
"docs:build": "npm --prefix apps/docs run build",
|
|
62
|
+
"docs:dev": "node scripts/npm-run.mjs npm --prefix apps/docs run dev",
|
|
63
|
+
"docs:build": "node scripts/npm-run.mjs npm --prefix apps/docs run build",
|
|
65
64
|
"lint:comments": "node ./bin/construct lint:comments",
|
|
66
65
|
"lint:contracts": "node ./bin/construct lint:contracts",
|
|
67
66
|
"lint:agents": "node ./bin/construct lint:agents",
|
|
68
67
|
"build:sea": "esbuild bin/construct --bundle --platform=node --target=node20 --packages=external --outfile=dist/construct-bundle.cjs 2>&1 || (echo 'esbuild not found — install with: npm install -g esbuild' && exit 1)",
|
|
68
|
+
"build:chat": "esbuild apps/chat/tui/index.jsx --bundle --platform=node --format=esm --packages=external --jsx=automatic --jsx-import-source=react --outfile=apps/chat/dist/tui.mjs",
|
|
69
|
+
"build:chat-desktop": "node scripts/build-chat-desktop.mjs",
|
|
70
|
+
"build:chat-desktop:bundle": "node scripts/npm-run.mjs npm install --prefix apps/chat/desktop --include=dev && node scripts/npm-run.mjs npm --prefix apps/chat/desktop run build",
|
|
71
|
+
"build:dashboard": "node scripts/npm-run.mjs npm install --prefix apps/dashboard && node scripts/npm-run.mjs npm --prefix apps/dashboard run build",
|
|
72
|
+
"examples:deck": "node scripts/generate-deck-examples.mjs",
|
|
73
|
+
"examples:distribution": "node scripts/generate-distribution-examples.mjs",
|
|
74
|
+
"clean:artifacts": "node scripts/clean-artifacts.mjs",
|
|
69
75
|
"lint:profiles": "node scripts/lint-profiles.mjs",
|
|
70
76
|
"learning:status": "node scripts/learning-status.mjs",
|
|
71
77
|
"lint:templates": "node scripts/lint-commits-pr.mjs",
|
|
72
78
|
"eval:routing": "node scripts/eval/score-intent-classifier.mjs",
|
|
73
|
-
"
|
|
79
|
+
"adapters": "node lib/adapters-sync.mjs",
|
|
80
|
+
"release:check": "node ./bin/construct doctor && npm test && node ./bin/construct docs:update --check && node ./bin/construct docs:site --check && node ./bin/construct registry:generate-docs --check && node ./bin/construct docs:verify --strict && node scripts/alignment/census.mjs --ratchet && node ./bin/construct dashboard:sync --check && node ./bin/construct lint:comments && node ./bin/construct certify gate && node scripts/lint-commits-pr.mjs",
|
|
74
81
|
"release:gate": "node --test tests/functional/release-gate.functional.test.mjs",
|
|
75
82
|
"audit:published": "node scripts/audit-published-artifact.mjs",
|
|
76
83
|
"version": "node scripts/sync-construct-version.mjs && git add .construct/version",
|
|
@@ -86,23 +93,30 @@
|
|
|
86
93
|
"js-yaml": "^4.2.0"
|
|
87
94
|
},
|
|
88
95
|
"optionalDependencies": {
|
|
96
|
+
"@ai-sdk/anthropic": "^3.0.85",
|
|
97
|
+
"@ai-sdk/openai": "^3.0.73",
|
|
98
|
+
"@ai-sdk/openai-compatible": "^2.0.51",
|
|
89
99
|
"@huggingface/transformers": "^4.2.0",
|
|
90
100
|
"@opentelemetry/api": "^1.9.0",
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
"@opentelemetry/resources": "^2.7.1",
|
|
94
|
-
"@opentelemetry/sdk-trace-node": "^1.25.0",
|
|
95
|
-
"@opentelemetry/semantic-conventions": "^1.25.0",
|
|
101
|
+
"ai": "^6.0.208",
|
|
102
|
+
"ink": "^7.1.0",
|
|
96
103
|
"mammoth": "^1.12.0",
|
|
97
|
-
"
|
|
104
|
+
"pptx-embed-fonts": "^0.0.6",
|
|
105
|
+
"pptxgenjs": "^4.0.1",
|
|
106
|
+
"react": "^19.2.7",
|
|
107
|
+
"unpdf": "^1.6.2",
|
|
108
|
+
"zod": "^4.4.3"
|
|
98
109
|
},
|
|
99
110
|
"overrides": {
|
|
100
|
-
"express-rate-limit": "8.5.1"
|
|
111
|
+
"express-rate-limit": "8.5.1",
|
|
112
|
+
"undici": "6.27.0"
|
|
101
113
|
},
|
|
102
114
|
"devDependencies": {
|
|
103
115
|
"@eslint/js": "^9.39.4",
|
|
104
116
|
"c8": "^11.0.0",
|
|
117
|
+
"esbuild": "^0.28.1",
|
|
105
118
|
"eslint": "^10.4.1",
|
|
106
|
-
"globals": "^17.6.0"
|
|
119
|
+
"globals": "^17.6.0",
|
|
120
|
+
"ink-testing-library": "^4.0.0"
|
|
107
121
|
}
|
|
108
122
|
}
|