@geraldmaron/construct 1.1.1 → 1.2.1
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 -19
- package/bin/construct +487 -87
- 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 +258 -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/artifact-workflow.mjs +252 -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 +90 -19
- 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 +246 -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 +146 -15
- package/lib/cli-service-inventory.mjs +42 -0
- package/lib/comment-lint.mjs +6 -4
- package/lib/config/intake-policy.mjs +209 -0
- package/lib/config/project-config.mjs +11 -2
- package/lib/config/schema.mjs +86 -6
- package/lib/config/source-targets.mjs +311 -0
- 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/source-checkout.mjs +16 -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 +384 -38
- package/lib/document-extract.mjs +44 -4
- package/lib/document-ingest.mjs +29 -20
- package/lib/embed/auto-sources.mjs +44 -0
- package/lib/embed/cli.mjs +4 -3
- package/lib/embed/daemon.mjs +18 -46
- package/lib/embed/demand-fetch.mjs +50 -19
- package/lib/embed/inbox.mjs +6 -10
- package/lib/embed/worker.mjs +4 -3
- package/lib/embedded-contract/index.mjs +11 -0
- 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/export-branding.mjs +32 -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 +18 -16
- 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 +50 -119
- 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/constants.mjs +29 -0
- package/lib/intake/daemon.mjs +2 -0
- package/lib/intake/git-queue.mjs +9 -8
- package/lib/intake/intake-config.mjs +52 -105
- package/lib/intake/legacy-paths.mjs +5 -0
- 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 +103 -10
- 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/embedded-contract.mjs +14 -0
- 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 +254 -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 +259 -9
- package/lib/server/rate-limit.mjs +5 -3
- package/lib/server/webhook.mjs +3 -2
- 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 +489 -0
- package/specialists/artifact-manifest.schema.json +83 -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-reference.docx +0 -0
- package/templates/distribution/construct-research.typ +38 -0
- package/templates/distribution/construct-web.html +92 -0
- package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
- package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
- package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
- package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/README.md +36 -0
- package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
- package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
- package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
- package/templates/distribution/icons/activity.svg +15 -0
- package/templates/distribution/icons/alert-triangle.svg +17 -0
- package/templates/distribution/icons/book-open.svg +16 -0
- package/templates/distribution/icons/bot.svg +20 -0
- package/templates/distribution/icons/brain.svg +22 -0
- package/templates/distribution/icons/circle-check.svg +16 -0
- package/templates/distribution/icons/clipboard-check.svg +17 -0
- package/templates/distribution/icons/cpu.svg +28 -0
- package/templates/distribution/icons/database.svg +17 -0
- package/templates/distribution/icons/eye.svg +16 -0
- package/templates/distribution/icons/file-text.svg +19 -0
- package/templates/distribution/icons/gauge.svg +16 -0
- package/templates/distribution/icons/git-branch.svg +17 -0
- package/templates/distribution/icons/key.svg +17 -0
- package/templates/distribution/icons/layers.svg +17 -0
- package/templates/distribution/icons/list-checks.svg +19 -0
- package/templates/distribution/icons/lock.svg +16 -0
- package/templates/distribution/icons/message-square.svg +15 -0
- package/templates/distribution/icons/network.svg +19 -0
- package/templates/distribution/icons/route.svg +17 -0
- package/templates/distribution/icons/scale.svg +19 -0
- package/templates/distribution/icons/search.svg +16 -0
- package/templates/distribution/icons/send.svg +16 -0
- package/templates/distribution/icons/server.svg +18 -0
- package/templates/distribution/icons/shield-check.svg +16 -0
- package/templates/distribution/icons/users.svg +18 -0
- package/templates/distribution/icons/webhook.svg +17 -0
- package/templates/distribution/icons/workflow.svg +17 -0
- package/templates/distribution/icons/wrench.svg +15 -0
- package/templates/distribution/run.mjs +18 -1
- package/templates/docs/adr.md +7 -0
- package/templates/docs/construct_guide.md +8 -8
- package/templates/docs/customer-profile.md +4 -0
- package/templates/docs/one-pager.md +4 -0
- package/templates/docs/postmortem.md +31 -0
- package/templates/docs/prd-platform.md +19 -0
- package/templates/docs/prd.md +15 -0
- package/templates/docs/prfaq.md +7 -0
- package/templates/docs/qa-strategy.md +103 -0
- package/templates/docs/research-brief.md +8 -0
- package/templates/docs/rfc-platform.md +12 -0
- package/templates/docs/test-plan.md +7 -0
package/lib/health-check.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
10
10
|
import os from 'node:os';
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import { existsSync, readFileSync } from 'node:fs';
|
|
13
|
+
import { extractOpRef, hasAnySecret, hasSecret } from './providers/secret-resolver.mjs';
|
|
13
14
|
|
|
14
15
|
const isWindows = os.platform() === 'win32';
|
|
15
16
|
|
|
@@ -165,37 +166,73 @@ export async function checkPrerequisites(options = {}) {
|
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
/**
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
169
|
+
* Read a credential raw value from dotenv files and shell rc without invoking
|
|
170
|
+
* `op read`. Returns op:// references as-is so callers can persist or report
|
|
171
|
+
* presence without biometric prompts (ADR-0042).
|
|
171
172
|
*/
|
|
172
|
-
function
|
|
173
|
+
function readCredentialRaw(varName, homeDir) {
|
|
173
174
|
if (process.env[varName]) return process.env[varName];
|
|
174
175
|
|
|
175
|
-
const
|
|
176
|
-
|
|
176
|
+
for (const envPath of [
|
|
177
|
+
path.join(process.cwd(), '.env'),
|
|
178
|
+
path.join(homeDir, '.env'),
|
|
179
|
+
path.join(homeDir, '.construct', 'config.env'),
|
|
180
|
+
]) {
|
|
181
|
+
if (!existsSync(envPath)) continue;
|
|
177
182
|
try {
|
|
178
|
-
const content = readFileSync(
|
|
183
|
+
const content = readFileSync(envPath, 'utf8');
|
|
179
184
|
const m = content.match(new RegExp(`^${varName}=["']?(.+?)["']?$`, 'm'));
|
|
180
185
|
if (m) return m[1].trim();
|
|
181
186
|
} catch { /* skip */ }
|
|
182
187
|
}
|
|
183
188
|
|
|
184
|
-
const
|
|
185
|
-
|
|
189
|
+
const shellFiles = [
|
|
190
|
+
path.join(homeDir, '.zshrc'),
|
|
191
|
+
path.join(homeDir, '.bashrc'),
|
|
192
|
+
path.join(homeDir, '.bash_profile'),
|
|
193
|
+
path.join(homeDir, '.profile'),
|
|
194
|
+
];
|
|
195
|
+
for (const rcPath of shellFiles) {
|
|
196
|
+
if (!existsSync(rcPath)) continue;
|
|
186
197
|
try {
|
|
187
|
-
const content = readFileSync(
|
|
188
|
-
const
|
|
189
|
-
|
|
198
|
+
const content = readFileSync(rcPath, 'utf8');
|
|
199
|
+
const opRe = new RegExp(`export\\s+${varName}=["']?\\$\\(op read '([^']+)'\\)["']?`, 'm');
|
|
200
|
+
const opMatch = content.match(opRe);
|
|
201
|
+
if (opMatch) return opMatch[1];
|
|
202
|
+
const directRe = new RegExp(`^\\s*export\\s+${varName}=(.+)$`, 'm');
|
|
203
|
+
const directMatch = content.match(directRe);
|
|
204
|
+
if (directMatch) return directMatch[1].trim().replace(/^["']|["']$/g, '');
|
|
190
205
|
} catch { /* skip */ }
|
|
191
206
|
}
|
|
192
207
|
|
|
193
|
-
|
|
194
|
-
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Describe where a credential is configured without resolving op:// refs.
|
|
213
|
+
*/
|
|
214
|
+
export function describeCredentialPresence(varName, { homeDir = os.homedir(), cwd = process.cwd() } = {}) {
|
|
215
|
+
const sources = [];
|
|
216
|
+
const direct = process.env[varName];
|
|
217
|
+
if (direct) {
|
|
218
|
+
const ref = extractOpRef(direct);
|
|
219
|
+
sources.push(ref ? `process.env (op:// ref)` : `process.env (plain value)`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
for (const envPath of [
|
|
223
|
+
path.join(cwd, '.env'),
|
|
224
|
+
path.join(homeDir, '.env'),
|
|
225
|
+
path.join(homeDir, '.construct', 'config.env'),
|
|
226
|
+
]) {
|
|
227
|
+
if (!existsSync(envPath)) continue;
|
|
195
228
|
try {
|
|
196
|
-
const content = readFileSync(
|
|
197
|
-
const m = content.match(new RegExp(`^${varName}=(
|
|
198
|
-
if (m)
|
|
229
|
+
const content = readFileSync(envPath, 'utf8');
|
|
230
|
+
const m = content.match(new RegExp(`^${varName}=["']?(.+?)["']?$`, 'm'));
|
|
231
|
+
if (m) {
|
|
232
|
+
const ref = extractOpRef(m[1]);
|
|
233
|
+
const label = envPath.replace(homeDir, '~');
|
|
234
|
+
sources.push(ref ? `${label} (op:// ref)` : `${label} (plain value)`);
|
|
235
|
+
}
|
|
199
236
|
} catch { /* skip */ }
|
|
200
237
|
}
|
|
201
238
|
|
|
@@ -209,47 +246,18 @@ function getEnvFromShell(varName, homeDir) {
|
|
|
209
246
|
if (!existsSync(rcPath)) continue;
|
|
210
247
|
try {
|
|
211
248
|
const content = readFileSync(rcPath, 'utf8');
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const opRef = m[1];
|
|
216
|
-
try {
|
|
217
|
-
const result = spawnSync('op', ['read', opRef], {
|
|
218
|
-
encoding: 'utf8',
|
|
219
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
220
|
-
timeout: 5000,
|
|
221
|
-
});
|
|
222
|
-
if (result.status === 0) return result.stdout.trim();
|
|
223
|
-
} catch { /* op read failed */ }
|
|
249
|
+
const opRe = new RegExp(`export\\s+${varName}=["']?\\$\\(op read '([^']+)'\\)["']?`, 'm');
|
|
250
|
+
if (opRe.test(content)) {
|
|
251
|
+
sources.push(`${rcPath.replace(homeDir, '~')} (op read in rc — use op:// in config.env instead)`);
|
|
224
252
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if (process.env.CONSTRUCT_DEBUG_CREDENTIALS === '1') {
|
|
231
|
-
for (const rcPath of shellFiles) {
|
|
232
|
-
if (!existsSync(rcPath)) continue;
|
|
233
|
-
try {
|
|
234
|
-
const content = readFileSync(rcPath, 'utf8');
|
|
235
|
-
const re = new RegExp(`export\\s+${varName}=["']?\\$\\(op read '([^']+)'\\)["']?`, 'm');
|
|
236
|
-
const m = content.match(re);
|
|
237
|
-
if (m) {
|
|
238
|
-
try {
|
|
239
|
-
const result = spawnSync('op', ['read', m[1]], { encoding: 'utf8', timeout: 5000 });
|
|
240
|
-
if (result.status !== 0) {
|
|
241
|
-
const errMsg = (result.stderr || '').trim().slice(0, 200);
|
|
242
|
-
process.stderr.write(`[credentials] ${varName}: op read '${m[1]}' failed: ${errMsg}\n`);
|
|
243
|
-
}
|
|
244
|
-
} catch (e) {
|
|
245
|
-
process.stderr.write(`[credentials] ${varName}: op CLI error: ${e.message}\n`);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
} catch { /* skip */ }
|
|
249
|
-
}
|
|
253
|
+
const directRe = new RegExp(`^\\s*export\\s+${varName}=`, 'm');
|
|
254
|
+
if (directRe.test(content) && !sources.some((s) => s.includes(rcPath.replace(homeDir, '~')))) {
|
|
255
|
+
sources.push(`${rcPath.replace(homeDir, '~')} (export found)`);
|
|
256
|
+
}
|
|
257
|
+
} catch { /* skip */ }
|
|
250
258
|
}
|
|
251
259
|
|
|
252
|
-
return
|
|
260
|
+
return { configured: hasSecret(varName, { env: process.env, cwd }), sources };
|
|
253
261
|
}
|
|
254
262
|
|
|
255
263
|
/**
|
|
@@ -287,30 +295,34 @@ const CREDENTIAL_VARS = [
|
|
|
287
295
|
];
|
|
288
296
|
|
|
289
297
|
/**
|
|
290
|
-
*
|
|
298
|
+
* Link discovered credential references into ~/.construct/config.env without
|
|
299
|
+
* invoking `op read`. Plain values and op:// refs are persisted as found.
|
|
291
300
|
* @param {Object} options
|
|
292
301
|
* @param {string} options.homeDir
|
|
293
|
-
* @param {boolean} options.writeConfig - Write
|
|
294
|
-
* @returns {Promise<Record<string, string>>}
|
|
302
|
+
* @param {boolean} options.writeConfig - Write keys to config.env (default: false)
|
|
303
|
+
* @returns {Promise<Record<string, string>>} raw values linked (never plaintext from op)
|
|
295
304
|
*/
|
|
296
305
|
export async function resolveCredentials(options = {}) {
|
|
297
|
-
const { homeDir = os.homedir(), writeConfig =
|
|
298
|
-
const
|
|
306
|
+
const { homeDir = os.homedir(), writeConfig = false } = options;
|
|
307
|
+
const linked = {};
|
|
308
|
+
const { parseEnvFile, getUserEnvPath, writeEnvValues } = await import('./env-config.mjs');
|
|
309
|
+
const existing = parseEnvFile(getUserEnvPath(homeDir));
|
|
299
310
|
|
|
300
311
|
for (const varName of CREDENTIAL_VARS) {
|
|
301
|
-
|
|
302
|
-
|
|
312
|
+
if (existing[varName]) continue;
|
|
313
|
+
const raw = readCredentialRaw(varName, homeDir);
|
|
314
|
+
if (!raw) continue;
|
|
315
|
+
const ref = extractOpRef(raw);
|
|
316
|
+
linked[varName] = ref || raw;
|
|
303
317
|
}
|
|
304
318
|
|
|
305
|
-
|
|
306
|
-
if (writeConfig && Object.keys(resolved).length > 0) {
|
|
319
|
+
if (writeConfig && Object.keys(linked).length > 0) {
|
|
307
320
|
try {
|
|
308
|
-
|
|
309
|
-
writeEnvValues(getUserEnvPath(homeDir), resolved);
|
|
321
|
+
writeEnvValues(getUserEnvPath(homeDir), linked);
|
|
310
322
|
} catch { /* best effort */ }
|
|
311
323
|
}
|
|
312
324
|
|
|
313
|
-
return
|
|
325
|
+
return linked;
|
|
314
326
|
}
|
|
315
327
|
|
|
316
328
|
/**
|
|
@@ -327,22 +339,24 @@ export function detectCredentials(options = {}) {
|
|
|
327
339
|
const ghAuth = spawnSync('gh', ['auth', 'status'], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
328
340
|
found.github_cli = ghAuth.status === 0;
|
|
329
341
|
|
|
330
|
-
// GitHub Copilot (
|
|
342
|
+
// GitHub Copilot: a device-flow credential (Construct's own store or the shared
|
|
343
|
+
// github-copilot store) is the usable signal; the VS Code extension and the
|
|
344
|
+
// copilot CLI are softer fallbacks.
|
|
345
|
+
const copilotAuthStore = existsSync(path.join(homeDir, '.construct', 'auth', 'github-copilot.json')) ||
|
|
346
|
+
existsSync(path.join(homeDir, '.config', 'github-copilot', 'apps.json')) ||
|
|
347
|
+
existsSync(path.join(homeDir, '.config', 'github-copilot', 'hosts.json'));
|
|
331
348
|
const copilotDir = path.join(homeDir, '.vscode', 'extensions');
|
|
332
349
|
const copilotExists = existsSync(copilotDir) && existsSync(path.join(homeDir, '.config', 'Code', 'User', 'globalStorage', 'github.copilot'));
|
|
333
|
-
found.copilot = copilotExists || commandExists('copilot');
|
|
350
|
+
found.copilot = copilotAuthStore || copilotExists || commandExists('copilot');
|
|
334
351
|
|
|
335
352
|
// OpenRouter API key
|
|
336
|
-
found.openrouter_key =
|
|
353
|
+
found.openrouter_key = hasAnySecret(['OPENROUTER_API_KEY', 'OPEN_ROUTER_API_KEY'], { env: process.env, cwd: process.cwd() });
|
|
337
354
|
|
|
338
|
-
|
|
339
|
-
found.anthropic_key = !!getEnvFromShell('ANTHROPIC_API_KEY', homeDir);
|
|
355
|
+
found.anthropic_key = hasAnySecret(['ANTHROPIC_API_KEY'], { env: process.env, cwd: process.cwd() });
|
|
340
356
|
|
|
341
|
-
|
|
342
|
-
found.openai_key = !!getEnvFromShell('OPENAI_API_KEY', homeDir);
|
|
357
|
+
found.openai_key = hasAnySecret(['OPENAI_API_KEY'], { env: process.env, cwd: process.cwd() });
|
|
343
358
|
|
|
344
|
-
|
|
345
|
-
found.github_token = !!getEnvFromShell('GITHUB_TOKEN', homeDir) || !!getEnvFromShell('GH_TOKEN', homeDir);
|
|
359
|
+
found.github_token = hasAnySecret(['GITHUB_TOKEN', 'GH_TOKEN'], { env: process.env, cwd: process.cwd() });
|
|
346
360
|
|
|
347
361
|
// VS Code installed
|
|
348
362
|
found.vscode = commandExists('code');
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lib/hooks/artifact-release-gate.mjs — PostToolUse advisory structure/visual gate for typed docs.
|
|
4
|
+
*
|
|
5
|
+
* Runs after Write/Edit/MultiEdit on docs/** and .cx/research/** markdown artifacts.
|
|
6
|
+
* Surfaces manifest structure and visual violations on stderr during drafting; does not
|
|
7
|
+
* block the edit (full release gate runs via `construct artifact validate` and CI).
|
|
8
|
+
*
|
|
9
|
+
* Notice-only: suppressed when stderr is not a TTY, CI=true, or NODE_ENV=test.
|
|
10
|
+
*
|
|
11
|
+
* @lifecycle PostToolUse
|
|
12
|
+
* @matcher Write|Edit|MultiEdit
|
|
13
|
+
* @p95ms 80
|
|
14
|
+
* @maxBlockingScope none (PostToolUse, non-blocking)
|
|
15
|
+
* @exits 0 = pass
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readFileSync } from 'node:fs';
|
|
19
|
+
import { checkArtifactGateNotice, formatArtifactGateNotice } from '../artifact-gate-notice.mjs';
|
|
20
|
+
|
|
21
|
+
function shouldEmitNotice() {
|
|
22
|
+
if (process.env.CI === 'true') return false;
|
|
23
|
+
if (process.env.NODE_ENV === 'test') return false;
|
|
24
|
+
if (!process.stderr.isTTY) return false;
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let filePath = process.env.TOOL_INPUT_FILE_PATH;
|
|
29
|
+
let cwd = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
30
|
+
if (!filePath) {
|
|
31
|
+
try {
|
|
32
|
+
const input = JSON.parse(readFileSync(0, 'utf8'));
|
|
33
|
+
filePath = input?.tool_input?.file_path || input?.tool_input?.path;
|
|
34
|
+
if (input?.cwd) cwd = input.cwd;
|
|
35
|
+
} catch { /* no stdin */ }
|
|
36
|
+
}
|
|
37
|
+
if (!filePath) process.exit(0);
|
|
38
|
+
|
|
39
|
+
const result = checkArtifactGateNotice(filePath, { cwd });
|
|
40
|
+
if (!result || !shouldEmitNotice()) process.exit(0);
|
|
41
|
+
|
|
42
|
+
process.stderr.write(formatArtifactGateNotice(result));
|
|
43
|
+
process.exit(0);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lib/hooks/brand-prose-lint.mjs — PostToolUse hook: block brand drift at write time.
|
|
4
|
+
*
|
|
5
|
+
* Enforces marketing voice, retired typography, and Construct/cli naming on governed
|
|
6
|
+
* markdown and template surfaces. Mirrors scripts/audit/03d-brand.mjs scope.
|
|
7
|
+
*
|
|
8
|
+
* @lifecycle PostToolUse
|
|
9
|
+
* @matcher Write|Edit|MultiEdit
|
|
10
|
+
* @p95ms 40
|
|
11
|
+
* @maxBlockingScope PostToolUse
|
|
12
|
+
* @exits 0 = pass | 2 = block tool call
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
import { formatBrandViolations, lintFile } from '../brand-prose.mjs';
|
|
19
|
+
|
|
20
|
+
let filePath = process.env.TOOL_INPUT_FILE_PATH;
|
|
21
|
+
if (!filePath) {
|
|
22
|
+
try {
|
|
23
|
+
const input = JSON.parse(readFileSync(0, 'utf8'));
|
|
24
|
+
filePath = input?.tool_input?.file_path || input?.tool_input?.path;
|
|
25
|
+
} catch {}
|
|
26
|
+
}
|
|
27
|
+
if (!filePath) process.exit(0);
|
|
28
|
+
|
|
29
|
+
const rootDir = path.resolve(fileURLToPath(import.meta.url), '..', '..', '..');
|
|
30
|
+
const result = lintFile(filePath, { rootDir });
|
|
31
|
+
if (!result.violations.length) process.exit(0);
|
|
32
|
+
|
|
33
|
+
const output = formatBrandViolations([result]);
|
|
34
|
+
process.stderr.write(`${output}\n`);
|
|
35
|
+
process.stderr.write(
|
|
36
|
+
'\nBrand policy blocked this edit (marketing voice, retired fonts, or Construct/cli naming). See docs/reference/branding.md and docs/STYLE.md.\n',
|
|
37
|
+
);
|
|
38
|
+
process.exit(2);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lib/hooks/graph-impact-advisory.mjs — PostToolUse hook: test-impact advisory on code edits.
|
|
4
|
+
*
|
|
5
|
+
* On Write|Edit under lib/, bin/, tests/, or src/, runs forward change-impact
|
|
6
|
+
* over the dependency graph and surfaces affected tests on stderr. Non-blocking;
|
|
7
|
+
* complements `construct impact` CLI and the doctor graph-staleness watcher.
|
|
8
|
+
*
|
|
9
|
+
* @p95ms 50
|
|
10
|
+
* @maxBlockingScope none (PostToolUse, non-blocking)
|
|
11
|
+
*
|
|
12
|
+
* @lifecycle PostToolUse
|
|
13
|
+
* @matcher Write|Edit|MultiEdit
|
|
14
|
+
* @exits 0 = pass
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { readFileSync } from 'node:fs';
|
|
18
|
+
|
|
19
|
+
import { computeImpact } from '../graph/impact.mjs';
|
|
20
|
+
import { checkGraphStaleness } from '../graph/staleness.mjs';
|
|
21
|
+
|
|
22
|
+
const CODE_RE = /^(lib|bin|src|tests)\/.*\.(?:m?js|jsx?|tsx?|cjs)$/;
|
|
23
|
+
|
|
24
|
+
let filePath = process.env.TOOL_INPUT_FILE_PATH;
|
|
25
|
+
let cwd = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
26
|
+
if (!filePath) {
|
|
27
|
+
try {
|
|
28
|
+
const input = JSON.parse(readFileSync(0, 'utf8'));
|
|
29
|
+
filePath = input?.tool_input?.file_path || input?.tool_input?.path;
|
|
30
|
+
if (input?.cwd) cwd = input.cwd;
|
|
31
|
+
} catch { /* stdin optional */ }
|
|
32
|
+
}
|
|
33
|
+
if (!filePath) process.exit(0);
|
|
34
|
+
|
|
35
|
+
const rel = filePath.startsWith(cwd)
|
|
36
|
+
? filePath.slice(cwd.length).replace(/^\/+/, '')
|
|
37
|
+
: filePath;
|
|
38
|
+
|
|
39
|
+
if (!CODE_RE.test(rel)) process.exit(0);
|
|
40
|
+
|
|
41
|
+
const stale = checkGraphStaleness(cwd);
|
|
42
|
+
if (stale.stale) {
|
|
43
|
+
process.stderr.write(
|
|
44
|
+
`[graph-impact] dependency graph stale (${stale.staleReason}). Run \`construct graph build\` before trusting impact output.\n`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const impact = computeImpact({ rootDir: cwd, changedFiles: [rel] });
|
|
49
|
+
if (!impact.graphPresent) {
|
|
50
|
+
process.stderr.write('[graph-impact] no .cx/graph/ present — run `construct graph build` to enable test-impact advisories.\n');
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (impact.affectedTests.length > 0) {
|
|
55
|
+
const preview = impact.affectedTests.slice(0, 5).map((t) => t.replace(/^test:/, '')).join(', ');
|
|
56
|
+
const more = impact.affectedTests.length > 5 ? ` (+${impact.affectedTests.length - 5} more)` : '';
|
|
57
|
+
process.stderr.write(`[graph-impact] ${rel} may affect: ${preview}${more}\n`);
|
|
58
|
+
} else if (impact.coverageGaps.length > 0) {
|
|
59
|
+
process.stderr.write(`[graph-impact] ${rel} has no capability edge in the dependency graph (coverage gap).\n`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
process.exit(0);
|