@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
|
@@ -1,66 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/templates/visual-requirements.mjs — required visuals per document type.
|
|
2
|
+
* lib/templates/visual-requirements.mjs — required visuals and structure per document type.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* enforces them. The shipped template for each listed type must satisfy its own
|
|
8
|
-
* requirements — pinned by tests/template-visuals.test.mjs — so the templates can
|
|
9
|
-
* never drift out of step with the matrix (beads wvbf.10 / wvbf.11).
|
|
4
|
+
* Loads structure and visual requirements from specialists/artifact-manifest.json
|
|
5
|
+
* (single source of truth) and merges legacy types not yet in the manifest.
|
|
6
|
+
* Paired with docs/concepts/doc-visual-matrix.md; pinned by template tests.
|
|
10
7
|
*/
|
|
11
8
|
|
|
12
9
|
import { validateArtifactPostconditions } from '../contracts/validate.mjs';
|
|
10
|
+
import {
|
|
11
|
+
structureRequirementsFromManifest,
|
|
12
|
+
visualRequirementsFromManifest,
|
|
13
|
+
getArtifactEntry,
|
|
14
|
+
} from '../artifact-manifest.mjs';
|
|
15
|
+
|
|
16
|
+
const LEGACY_STRUCTURE = {
|
|
17
|
+
'persona-artifact': ['Goals', 'Frustrations', 'Decision rights', 'Output contract', 'Failure modes', 'Evidence'],
|
|
18
|
+
'skill-artifact': ['What this skill produces', 'When to invoke it', 'Competency rubric', 'Failure modes', 'Worked example'],
|
|
19
|
+
'research-finding': ['SOURCES', 'FINDINGS', 'INFERENCES', 'CONFIDENCE', 'GAPS', 'RECOMMENDATION'],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const STRUCTURE_REQUIREMENTS = {
|
|
23
|
+
...structureRequirementsFromManifest(),
|
|
24
|
+
...LEGACY_STRUCTURE,
|
|
25
|
+
};
|
|
13
26
|
|
|
14
27
|
export const VISUAL_REQUIREMENTS = {
|
|
15
|
-
|
|
16
|
-
{ id: 'runbook-diagnostic-flowchart', check: 'artifact-has-mermaid', diagram: 'flowchart' },
|
|
17
|
-
],
|
|
18
|
-
'incident-report': [
|
|
19
|
-
{ id: 'incident-timeline-table', check: 'artifact-table-has-columns', columns: ['Time (UTC)', 'Event'] },
|
|
20
|
-
],
|
|
21
|
-
rfc: [
|
|
22
|
-
{ id: 'rfc-sequence-diagram', check: 'artifact-has-mermaid', diagram: 'sequenceDiagram' },
|
|
23
|
-
],
|
|
28
|
+
...visualRequirementsFromManifest(),
|
|
24
29
|
};
|
|
25
30
|
|
|
26
31
|
export function visualRequirementTypes() {
|
|
27
32
|
return Object.keys(VISUAL_REQUIREMENTS);
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
// The structural floor of the quality rubric (ADR-0018): the sections a domain
|
|
31
|
-
// expert expects for each doc type. Each shipped template must satisfy its own
|
|
32
|
-
// entry — pinned by tests/structure-requirements.test.mjs — so a template can
|
|
33
|
-
// never quietly drop a required section. Section requirements compose with the
|
|
34
|
-
// VISUAL_REQUIREMENTS above; lintDocStructure runs both.
|
|
35
|
-
|
|
36
|
-
export const STRUCTURE_REQUIREMENTS = {
|
|
37
|
-
adr: ['Problem', 'Decision', 'Rejected alternatives', 'Consequences', 'Reversibility'],
|
|
38
|
-
rfc: ['Summary', 'Motivation', 'Proposed design', 'Risks', 'Verification'],
|
|
39
|
-
prd: ['Problem', 'Goals', 'Success metrics', 'Risks and mitigations'],
|
|
40
|
-
'research-brief': ['Sources', 'Findings', 'Confidence summary', 'Recommendation'],
|
|
41
|
-
'incident-report': ['Summary', 'Severity rationale', 'Impact', 'Timeline', 'Trigger', 'Root cause', 'Contributing factors', 'Action items'],
|
|
42
|
-
runbook: ['Alert trigger', 'Symptoms', 'Impact', 'Severity and response', 'Diagnostic steps', 'Remediation', 'Rollback', 'Escalation'],
|
|
43
|
-
strategy: ['Vision', 'Bets', 'Non-bets', 'North Star Metric', 'Metrics', 'Milestones', 'Risks'],
|
|
44
|
-
'signal-brief': ['Signal', 'Evidence', 'Counter-signal', 'What would make this actionable'],
|
|
45
|
-
prfaq: ['Problem statement', 'Press release', 'External FAQ', 'Internal FAQ', 'Evidence appendix'],
|
|
46
|
-
'customer-profile': ['Snapshot', 'Active pain points', 'Open asks', 'Evidence links'],
|
|
47
|
-
'one-pager': ['Problem', 'Proposal', 'Why now', 'Success measure', 'Cost', 'Asks'],
|
|
48
|
-
'product-intelligence-report': ['Executive readout', 'Evidence base', 'Themes', 'Customer asks', 'Product implications', 'Recommended actions', 'Gaps and risks'],
|
|
49
|
-
'backlog-proposal': ['Source evidence', 'Proposed changes', 'Duplicate and conflict check', 'Approval request'],
|
|
50
|
-
'persona-artifact': ['Goals', 'Frustrations', 'Decision rights', 'Output contract', 'Failure modes', 'Evidence'],
|
|
51
|
-
'skill-artifact': ['What this skill produces', 'When to invoke it', 'Competency rubric', 'Failure modes', 'Worked example'],
|
|
52
|
-
'research-finding': ['SOURCES', 'FINDINGS', 'INFERENCES', 'CONFIDENCE', 'GAPS', 'RECOMMENDATION'],
|
|
53
|
-
};
|
|
54
|
-
|
|
55
35
|
export function structureRequirementTypes() {
|
|
56
36
|
return [...new Set([...Object.keys(STRUCTURE_REQUIREMENTS), ...Object.keys(VISUAL_REQUIREMENTS)])];
|
|
57
37
|
}
|
|
58
38
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
39
|
+
export function resolveTemplatePath(type, rootDir) {
|
|
40
|
+
const entry = getArtifactEntry(type, { rootDir });
|
|
41
|
+
if (entry?.template) return entry.template;
|
|
42
|
+
return `templates/docs/${type}.md`;
|
|
43
|
+
}
|
|
44
|
+
|
|
64
45
|
export function lintDocStructure(filePath, type) {
|
|
65
46
|
const sectionChecks = (STRUCTURE_REQUIREMENTS[type] || []).map((section) => ({
|
|
66
47
|
id: `${type}-section-${section}`,
|
|
@@ -72,11 +53,6 @@ export function lintDocStructure(filePath, type) {
|
|
|
72
53
|
return validateArtifactPostconditions({ contract: { postconditions }, artifactPath: filePath });
|
|
73
54
|
}
|
|
74
55
|
|
|
75
|
-
/**
|
|
76
|
-
* Lint one document against the visual requirements for its type. Returns an
|
|
77
|
-
* array of violation strings (empty when satisfied, or when the type has no
|
|
78
|
-
* declared visual requirements).
|
|
79
|
-
*/
|
|
80
56
|
export function lintDocVisuals(filePath, type) {
|
|
81
57
|
const postconditions = VISUAL_REQUIREMENTS[type];
|
|
82
58
|
if (!postconditions) return [];
|
|
@@ -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;
|