@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/publish.mjs
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/publish.mjs — `construct publish` orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Single compound run: artifact release gate (default), detect tooling, export
|
|
5
|
+
* markdown (pandoc-ext/diagram + Construct PDF template), optional VHS terminal
|
|
6
|
+
* demo, optional Playwright dashboard demo. --strict requires toolchain and gate.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { exportMarkdown, parsePublishFrontmatter, EXPORT_FORMATS } from './document-export.mjs';
|
|
12
|
+
import { detectPublishPipeline } from './publish-tooling.mjs';
|
|
13
|
+
import { runDemoRecord } from './demo.mjs';
|
|
14
|
+
import { recordDashboardDemo } from './dashboard-demo.mjs';
|
|
15
|
+
import { loadDemoRecording } from './demo-recording.mjs';
|
|
16
|
+
import { recordPlaywrightDemo } from './playwright-demo.mjs';
|
|
17
|
+
import { validateArtifactRelease } from './artifact-release-gate.mjs';
|
|
18
|
+
import { inferArtifactTypeFromPath } from './artifact-type-from-path.mjs';
|
|
19
|
+
|
|
20
|
+
export { parsePublishFrontmatter };
|
|
21
|
+
|
|
22
|
+
export function formatGateFailureMessage(gateResult, { inputPath, cwd = process.cwd() } = {}) {
|
|
23
|
+
const rel = path.relative(cwd, inputPath);
|
|
24
|
+
const type = gateResult.type || 'unknown';
|
|
25
|
+
const lines = [
|
|
26
|
+
`Publish blocked: artifact release gate failed (${type}: ${rel})`,
|
|
27
|
+
...gateResult.errors.map((e) => ` - ${typeof e === 'string' ? e : e.message || String(e)}`),
|
|
28
|
+
];
|
|
29
|
+
if (gateResult.warnings?.length) {
|
|
30
|
+
lines.push('Warnings:');
|
|
31
|
+
for (const w of gateResult.warnings) lines.push(` - ${w}`);
|
|
32
|
+
}
|
|
33
|
+
lines.push(
|
|
34
|
+
'',
|
|
35
|
+
'Remediation:',
|
|
36
|
+
` node bin/construct artifact validate ${rel} --type=${type}`,
|
|
37
|
+
' node bin/construct workflow invoke --workflow-type prd-draft --text "…"',
|
|
38
|
+
' Run the listed specialists to author the artifact (invoke returns a plan only).',
|
|
39
|
+
' See skills/docs/prd-workflow.md — validate must pass before publish.',
|
|
40
|
+
);
|
|
41
|
+
return lines.join('\n');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function runPublish({
|
|
45
|
+
inputPath,
|
|
46
|
+
format = 'pdf',
|
|
47
|
+
outputPath,
|
|
48
|
+
demos = [],
|
|
49
|
+
dashboardDemos = [],
|
|
50
|
+
recordings = [],
|
|
51
|
+
figures = true,
|
|
52
|
+
strict = true,
|
|
53
|
+
sourceOnly = false,
|
|
54
|
+
gate = true,
|
|
55
|
+
artifactType = null,
|
|
56
|
+
cwd = process.cwd(),
|
|
57
|
+
repoRoot,
|
|
58
|
+
env = process.env,
|
|
59
|
+
} = {}) {
|
|
60
|
+
const resolvedIn = path.resolve(cwd, inputPath);
|
|
61
|
+
if (!fs.existsSync(resolvedIn)) {
|
|
62
|
+
return { ok: false, message: `Input not found: ${resolvedIn}` };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let demoNames = demos;
|
|
66
|
+
let dashNames = dashboardDemos;
|
|
67
|
+
let recordingNames = recordings;
|
|
68
|
+
if (!demoNames.length && !dashNames.length && !recordingNames.length) {
|
|
69
|
+
const fm = parsePublishFrontmatter(resolvedIn);
|
|
70
|
+
if (fm?.demo) demoNames = [fm.demo];
|
|
71
|
+
if (fm?.dashboardDemo) dashNames = [fm.dashboardDemo];
|
|
72
|
+
if (fm?.recording) recordingNames = [fm.recording];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const detection = detectPublishPipeline({
|
|
76
|
+
format,
|
|
77
|
+
includeFigures: figures,
|
|
78
|
+
includeTerminalDemo: demoNames.length > 0 && !sourceOnly,
|
|
79
|
+
includeDashboardDemo: (dashNames.length > 0 || recordingNames.length > 0) && !sourceOnly,
|
|
80
|
+
cwd,
|
|
81
|
+
repoRoot,
|
|
82
|
+
env,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const ledger = { export: null, demos: [], dashboardDemos: [], recordings: [], detection, gate: null };
|
|
86
|
+
const resolvedType = artifactType || inferArtifactTypeFromPath(resolvedIn, { rootDir: cwd });
|
|
87
|
+
|
|
88
|
+
if (gate && !sourceOnly && resolvedType) {
|
|
89
|
+
ledger.gate = validateArtifactRelease({
|
|
90
|
+
filePath: resolvedIn,
|
|
91
|
+
type: resolvedType,
|
|
92
|
+
cwd,
|
|
93
|
+
rootDir: repoRoot || cwd,
|
|
94
|
+
});
|
|
95
|
+
if (!ledger.gate.ok && strict) {
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
strict: true,
|
|
99
|
+
gateBlocked: true,
|
|
100
|
+
ledger,
|
|
101
|
+
message: formatGateFailureMessage(ledger.gate, { inputPath: resolvedIn, cwd }),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (strict && !sourceOnly && !detection.present) {
|
|
107
|
+
return { ok: false, strict: true, detection, missing: detection.missing, message: detection.message };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!sourceOnly) {
|
|
111
|
+
const publishDir = path.join(cwd, '.cx', 'publish');
|
|
112
|
+
const defaultOut = path.join(publishDir, `${path.parse(resolvedIn).name}.${format}`);
|
|
113
|
+
const resolvedOut = outputPath ? path.resolve(cwd, outputPath) : defaultOut;
|
|
114
|
+
|
|
115
|
+
ledger.export = exportMarkdown({
|
|
116
|
+
inputPath: resolvedIn,
|
|
117
|
+
outputPath: resolvedOut,
|
|
118
|
+
format,
|
|
119
|
+
figures: figures && !sourceOnly,
|
|
120
|
+
artifactType: resolvedType,
|
|
121
|
+
env,
|
|
122
|
+
cwd,
|
|
123
|
+
repoRoot: repoRoot || cwd,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (!ledger.export.ok && strict && !sourceOnly) {
|
|
127
|
+
return { ok: false, strict: true, ledger, message: ledger.export.message };
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
ledger.export = { ok: true, skipped: 'source-only' };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const name of demoNames) {
|
|
134
|
+
const demoResult = runDemoRecord(name, {
|
|
135
|
+
cwd,
|
|
136
|
+
format: 'mp4',
|
|
137
|
+
sourceOnly: sourceOnly || (!strict && !detection.steps?.terminalDemo?.present),
|
|
138
|
+
required: strict && !sourceOnly,
|
|
139
|
+
surface: 'tape',
|
|
140
|
+
});
|
|
141
|
+
ledger.demos.push(demoResult);
|
|
142
|
+
if (strict && !sourceOnly && demoResult.required && !demoResult.ok) {
|
|
143
|
+
return { ok: false, strict: true, ledger, message: demoResult.message };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (const name of dashNames) {
|
|
148
|
+
const dashResult = recordDashboardDemo(name, { cwd, repoRoot, env });
|
|
149
|
+
ledger.dashboardDemos.push(dashResult);
|
|
150
|
+
if (strict && !sourceOnly && !dashResult.ok) {
|
|
151
|
+
return { ok: false, strict: true, ledger, message: dashResult.message };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for (const name of recordingNames) {
|
|
156
|
+
const recording = loadDemoRecording(name, { cwd, repoRoot: repoRoot || cwd });
|
|
157
|
+
if (!recording) {
|
|
158
|
+
const miss = { ok: false, name, message: `Recording not found: ${name}` };
|
|
159
|
+
ledger.recordings.push(miss);
|
|
160
|
+
if (strict && !sourceOnly) {
|
|
161
|
+
return { ok: false, strict: true, ledger, message: miss.message };
|
|
162
|
+
}
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const recResult = recordPlaywrightDemo(recording, { cwd, repoRoot: repoRoot || cwd, env });
|
|
166
|
+
ledger.recordings.push(recResult);
|
|
167
|
+
if (strict && !sourceOnly && !recResult.ok) {
|
|
168
|
+
return { ok: false, strict: true, ledger, message: recResult.message };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
ok: ledger.export?.ok !== false,
|
|
174
|
+
ledger,
|
|
175
|
+
message: ledger.export?.skipped
|
|
176
|
+
? 'Publish source-only complete'
|
|
177
|
+
: ledger.export?.ok
|
|
178
|
+
? `Published ${path.relative(cwd, ledger.export.outputPath)}`
|
|
179
|
+
: ledger.export?.message || detection.message,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function printPublishHelp() {
|
|
184
|
+
console.log(`Usage: construct publish <markdown> [options]
|
|
185
|
+
|
|
186
|
+
Orchestrates typed artifact distribution: release gate (default), Pandoc export
|
|
187
|
+
with diagram filter and Construct PDF template, optional VHS/Playwright demos.
|
|
188
|
+
|
|
189
|
+
Options:
|
|
190
|
+
--to=<format> ${EXPORT_FORMATS.join(' | ')} (default: pdf)
|
|
191
|
+
--output=<path> Output path (default: .cx/publish/<name>.<format>)
|
|
192
|
+
--type=<doc-type> Artifact type for release gate (inferred from path when omitted)
|
|
193
|
+
--demo=<name> Terminal VHS tape name (repeatable)
|
|
194
|
+
--dashboard-demo=<name> Playwright demo spec basename (repeatable)
|
|
195
|
+
--recording=<name> Playwright recording manifest (repeatable)
|
|
196
|
+
--no-figures Skip pandoc-ext/diagram filter
|
|
197
|
+
--no-gate Skip artifact release gate (maintainer escape hatch)
|
|
198
|
+
--source-only Write sources only; skip rendering
|
|
199
|
+
--no-strict Do not exit 2 when toolchain or gate fails
|
|
200
|
+
--detect Print tooling JSON and exit
|
|
201
|
+
-h, --help Show this message
|
|
202
|
+
|
|
203
|
+
Frontmatter (optional):
|
|
204
|
+
publish:
|
|
205
|
+
demo: resource-guard-rails
|
|
206
|
+
dashboardDemo: cockpit-tour
|
|
207
|
+
recording: agentic-platforms-prd
|
|
208
|
+
|
|
209
|
+
Examples:
|
|
210
|
+
node bin/construct publish docs/prd-platform/brief.md --strict --figures
|
|
211
|
+
node bin/construct publish brief.md --type=research-brief --demo=quickstart
|
|
212
|
+
`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export async function runPublishCli(argv = [], { cwd = process.cwd(), repoRoot } = {}) {
|
|
216
|
+
const options = {
|
|
217
|
+
format: 'pdf',
|
|
218
|
+
output: null,
|
|
219
|
+
type: null,
|
|
220
|
+
demos: [],
|
|
221
|
+
dashboardDemos: [],
|
|
222
|
+
figures: true,
|
|
223
|
+
strict: true,
|
|
224
|
+
gate: true,
|
|
225
|
+
sourceOnly: false,
|
|
226
|
+
detect: false,
|
|
227
|
+
help: false,
|
|
228
|
+
input: null,
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
232
|
+
const arg = argv[i];
|
|
233
|
+
if (arg === '--help' || arg === '-h') { options.help = true; continue; }
|
|
234
|
+
if (arg === '--strict') { options.strict = true; continue; }
|
|
235
|
+
if (arg === '--no-strict') { options.strict = false; continue; }
|
|
236
|
+
if (arg === '--source-only') { options.sourceOnly = true; continue; }
|
|
237
|
+
if (arg === '--no-figures') { options.figures = false; continue; }
|
|
238
|
+
if (arg === '--no-gate') { options.gate = false; continue; }
|
|
239
|
+
if (arg === '--detect') { options.detect = true; continue; }
|
|
240
|
+
if (arg === '--to') { options.format = argv[++i]; continue; }
|
|
241
|
+
if (arg.startsWith('--to=')) { options.format = arg.slice(5); continue; }
|
|
242
|
+
if (arg === '--output') { options.output = argv[++i]; continue; }
|
|
243
|
+
if (arg.startsWith('--output=')) { options.output = arg.slice(9); continue; }
|
|
244
|
+
if (arg === '--type') { options.type = argv[++i]; continue; }
|
|
245
|
+
if (arg.startsWith('--type=')) { options.type = arg.slice(7); continue; }
|
|
246
|
+
if (arg === '--demo') { options.demos.push(argv[++i]); continue; }
|
|
247
|
+
if (arg.startsWith('--demo=')) { options.demos.push(arg.slice(7)); continue; }
|
|
248
|
+
if (arg === '--dashboard-demo') { options.dashboardDemos.push(argv[++i]); continue; }
|
|
249
|
+
if (arg.startsWith('--dashboard-demo=')) { options.dashboardDemos.push(arg.slice(17)); continue; }
|
|
250
|
+
if (!arg.startsWith('--')) options.input = arg;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (options.help) {
|
|
254
|
+
printPublishHelp();
|
|
255
|
+
return { exitCode: 0 };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (options.detect) {
|
|
259
|
+
const detection = detectPublishPipeline({
|
|
260
|
+
format: options.format,
|
|
261
|
+
includeFigures: options.figures,
|
|
262
|
+
includeTerminalDemo: options.demos.length > 0,
|
|
263
|
+
includeDashboardDemo: options.dashboardDemos.length > 0,
|
|
264
|
+
cwd,
|
|
265
|
+
repoRoot,
|
|
266
|
+
});
|
|
267
|
+
console.log(JSON.stringify(detection, null, 2));
|
|
268
|
+
return { exitCode: detection.present ? 0 : 2 };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (!options.input) {
|
|
272
|
+
printPublishHelp();
|
|
273
|
+
return { exitCode: 1 };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const result = runPublish({
|
|
277
|
+
inputPath: options.input,
|
|
278
|
+
format: options.format,
|
|
279
|
+
outputPath: options.output,
|
|
280
|
+
demos: options.demos,
|
|
281
|
+
dashboardDemos: options.dashboardDemos,
|
|
282
|
+
figures: options.figures,
|
|
283
|
+
strict: options.strict,
|
|
284
|
+
gate: options.gate,
|
|
285
|
+
artifactType: options.type,
|
|
286
|
+
sourceOnly: options.sourceOnly,
|
|
287
|
+
cwd,
|
|
288
|
+
repoRoot,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
if (result.ok) {
|
|
292
|
+
console.log(result.message);
|
|
293
|
+
if (result.ledger?.demos?.length) {
|
|
294
|
+
for (const d of result.ledger.demos) {
|
|
295
|
+
if (d.tapePath) console.log(` tape: ${d.tapePath}`);
|
|
296
|
+
if (d.artifactPath) console.log(` demo: ${d.artifactPath}`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return { exitCode: 0, result };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
console.error(result.message);
|
|
303
|
+
const exitCode = result.strict && (result.missing?.length || result.gateBlocked) ? 2 : 1;
|
|
304
|
+
return { exitCode, result };
|
|
305
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/registry/cli.mjs — CLI handlers for registry validate, status, and doc generation.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { validateCapabilityRegistry, loadCapabilityRegistry } from './validate.mjs';
|
|
6
|
+
import { generateCapabilitiesDoc, checkCapabilitiesDocDrift } from './generate-docs.mjs';
|
|
7
|
+
import { triageBoundOrphans, formatConsolidationProposalMarkdown } from './consolidation.mjs';
|
|
8
|
+
|
|
9
|
+
export async function runRegistryStatus(args = [], { rootDir, println = console.log } = {}) {
|
|
10
|
+
const jsonOutput = args.includes('--json');
|
|
11
|
+
const { capabilities = [] } = loadCapabilityRegistry({ rootDir });
|
|
12
|
+
|
|
13
|
+
if (jsonOutput) {
|
|
14
|
+
println(JSON.stringify(capabilities, null, 2));
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
println('Construct Capability Registry');
|
|
19
|
+
println('='.repeat(40));
|
|
20
|
+
println('');
|
|
21
|
+
|
|
22
|
+
for (const cap of capabilities) {
|
|
23
|
+
const tier = cap.criticality ?? '—';
|
|
24
|
+
println(`[${tier}] ${cap.name ?? cap.id} (${cap.id})`);
|
|
25
|
+
if (cap.description) println(` ${cap.description}`);
|
|
26
|
+
const surfaces = Object.entries(cap.surfaces ?? {}).filter(([, v]) => v?.supported);
|
|
27
|
+
if (surfaces.length) {
|
|
28
|
+
println(` surfaces: ${surfaces.map(([n]) => n).join(', ')}`);
|
|
29
|
+
}
|
|
30
|
+
const validated = cap.lastValidated ? cap.lastValidated.slice(0, 10) : 'never';
|
|
31
|
+
println(` humanGate: ${cap.humanGate ?? '—'} lastValidated: ${validated}`);
|
|
32
|
+
println('');
|
|
33
|
+
}
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function runRegistryValidate(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
|
|
38
|
+
const jsonOutput = args.includes('--json');
|
|
39
|
+
const report = validateCapabilityRegistry({ rootDir });
|
|
40
|
+
|
|
41
|
+
if (jsonOutput) {
|
|
42
|
+
println(JSON.stringify(report, null, 2));
|
|
43
|
+
return report.valid ? 0 : 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
println(`Registry: ${report.registryPath}`);
|
|
47
|
+
println(`Entries: ${report.count}`);
|
|
48
|
+
if (report.errors.length) {
|
|
49
|
+
errorln(`Errors (${report.errors.length}):`);
|
|
50
|
+
for (const e of report.errors) errorln(` ✗ ${e}`);
|
|
51
|
+
}
|
|
52
|
+
if (report.warnings.length) {
|
|
53
|
+
println(`Warnings (${report.warnings.length}):`);
|
|
54
|
+
for (const w of report.warnings) println(` ⚠ ${w}`);
|
|
55
|
+
}
|
|
56
|
+
if (report.valid && !report.warnings.length) println('✓ Registry valid');
|
|
57
|
+
else if (report.valid) println('✓ Registry valid (with warnings)');
|
|
58
|
+
else errorln('✗ Registry invalid');
|
|
59
|
+
|
|
60
|
+
return report.valid ? 0 : 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function runRegistryGenerateDocs(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
|
|
64
|
+
if (args.includes('--check')) {
|
|
65
|
+
const { drift } = checkCapabilitiesDocDrift({ rootDir });
|
|
66
|
+
if (drift) {
|
|
67
|
+
errorln('capabilities.md drift — run `construct registry:generate-docs`');
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
println('capabilities.md is up to date');
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
const out = generateCapabilitiesDoc({ rootDir });
|
|
74
|
+
println(`Generated ${out}`);
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function runRegistryConsolidationProposal(args = [], { rootDir, println = console.log } = {}) {
|
|
79
|
+
const triage = triageBoundOrphans({ rootDir });
|
|
80
|
+
println(formatConsolidationProposalMarkdown(triage));
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/registry/consolidation.mjs — bound-orphan triage for skill consolidation proposals.
|
|
3
|
+
*
|
|
4
|
+
* A bound-orphan is a skill file not declared in any specialist's `skills:` array.
|
|
5
|
+
* Profile/composer reachability is classified separately — nothing is deleted here.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
|
|
12
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const REPO_ROOT = path.resolve(MODULE_DIR, '..', '..');
|
|
14
|
+
|
|
15
|
+
function collectSkillFiles(skillsDir) {
|
|
16
|
+
const results = [];
|
|
17
|
+
function walk(dir, prefix = '') {
|
|
18
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
19
|
+
if (entry.name.startsWith('.')) continue;
|
|
20
|
+
const full = path.join(dir, entry.name);
|
|
21
|
+
if (entry.isDirectory()) walk(full, prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
22
|
+
else if (entry.name.endsWith('.md') && entry.name !== 'routing.md') {
|
|
23
|
+
results.push(prefix ? `${prefix}/${entry.name.replace(/\.md$/, '')}` : entry.name.replace(/\.md$/, ''));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
walk(skillsDir);
|
|
28
|
+
return results;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function roleBase(skill) {
|
|
32
|
+
const m = skill.match(/^roles\/([^.]+)(?:\..*)?$/);
|
|
33
|
+
return m ? m[1] : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function collectProfileRoles(root) {
|
|
37
|
+
const dir = path.join(root, 'profiles');
|
|
38
|
+
const roles = new Set();
|
|
39
|
+
const scan = (node) => {
|
|
40
|
+
if (Array.isArray(node)) { node.forEach(scan); return; }
|
|
41
|
+
if (node && typeof node === 'object') {
|
|
42
|
+
if (Array.isArray(node.roles)) for (const r of node.roles) if (typeof r === 'string') roles.add(r);
|
|
43
|
+
for (const v of Object.values(node)) scan(v);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
try {
|
|
47
|
+
for (const f of fs.readdirSync(dir)) {
|
|
48
|
+
if (!f.endsWith('.json')) continue;
|
|
49
|
+
try { scan(JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8'))); } catch { /* skip */ }
|
|
50
|
+
}
|
|
51
|
+
} catch { /* no profiles */ }
|
|
52
|
+
return roles;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Classify bound-orphans for maintainer approval (bind / merge / composer / review).
|
|
57
|
+
*/
|
|
58
|
+
export function triageBoundOrphans({ rootDir = REPO_ROOT } = {}) {
|
|
59
|
+
const registry = JSON.parse(fs.readFileSync(path.join(rootDir, 'specialists', 'registry.json'), 'utf8'));
|
|
60
|
+
const declared = new Set();
|
|
61
|
+
for (const s of registry.specialists ?? []) for (const sk of s.skills ?? []) declared.add(sk);
|
|
62
|
+
|
|
63
|
+
const specialistNames = new Set((registry.specialists ?? []).map((s) => s.name));
|
|
64
|
+
const profileRoles = collectProfileRoles(rootDir);
|
|
65
|
+
const all = collectSkillFiles(path.join(rootDir, 'skills'));
|
|
66
|
+
|
|
67
|
+
const items = [];
|
|
68
|
+
for (const skill of all) {
|
|
69
|
+
if (declared.has(skill)) continue;
|
|
70
|
+
const base = roleBase(skill);
|
|
71
|
+
let category = 'D-review';
|
|
72
|
+
let recommendation = 'Manual review — not declared and no automatic owner signal';
|
|
73
|
+
|
|
74
|
+
if (skill.startsWith('docs/') && skill.endsWith('-workflow')) {
|
|
75
|
+
category = 'A-bind';
|
|
76
|
+
recommendation = `Bind to owner specialist in registry.json (workflow skill)`;
|
|
77
|
+
} else if (base && specialistNames.has(base)) {
|
|
78
|
+
category = 'B-composer';
|
|
79
|
+
recommendation = `Role flavor for cx-${base} — reachable via prompt-composer; document in registry or bind explicitly`;
|
|
80
|
+
} else if (base && profileRoles.has(base)) {
|
|
81
|
+
category = 'B-composer';
|
|
82
|
+
recommendation = `Profile role "${base}" — may load via profile overlay; verify before merge`;
|
|
83
|
+
} else if (skill.startsWith('roles/')) {
|
|
84
|
+
category = 'C-merge';
|
|
85
|
+
recommendation = 'Consider merging into parent roles/<base>.md or binding to specialist';
|
|
86
|
+
} else if (skill.startsWith('docs/')) {
|
|
87
|
+
category = 'A-bind';
|
|
88
|
+
recommendation = 'Bind to cx-docs-keeper or domain owner specialist';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
items.push({ skill, category, recommendation });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const byCategory = Object.fromEntries(['A-bind', 'B-composer', 'C-merge', 'D-review'].map((c) => [c, []]));
|
|
95
|
+
for (const item of items) byCategory[item.category].push(item);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
declaredCount: declared.size,
|
|
99
|
+
fileCount: all.length,
|
|
100
|
+
boundOrphanCount: items.length,
|
|
101
|
+
composerReachableCount: items.filter((i) => i.category === 'B-composer').length,
|
|
102
|
+
trueOrphanCount: items.filter((i) => i.category === 'D-review' || i.category === 'C-merge').length,
|
|
103
|
+
aBindCount: items.filter((i) => i.category === 'A-bind').length,
|
|
104
|
+
byCategory,
|
|
105
|
+
items,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function formatConsolidationProposalMarkdown(triage, { date = new Date().toISOString().slice(0, 10) } = {}) {
|
|
110
|
+
const lines = [
|
|
111
|
+
`# Skill Consolidation Proposal — ${date}`,
|
|
112
|
+
'',
|
|
113
|
+
'Gate: **nothing is deleted until the maintainer approves a list.**',
|
|
114
|
+
'',
|
|
115
|
+
`Reproduce: \`node -e "import('./lib/registry/consolidation.mjs').then(m => console.log(JSON.stringify(m.triageBoundOrphans(),null,2)))"\``,
|
|
116
|
+
'',
|
|
117
|
+
'## Summary',
|
|
118
|
+
'',
|
|
119
|
+
`- **${triage.fileCount}** skill files on disk`,
|
|
120
|
+
`- **${triage.declaredCount}** declared in specialists/registry.json`,
|
|
121
|
+
`- **${triage.boundOrphanCount}** registry bound-orphans (not declared by any specialist)`,
|
|
122
|
+
`- **${triage.composerReachableCount}** composer-reachable (B-composer — intentional via prompt composer)`,
|
|
123
|
+
`- **${triage.trueOrphanCount}** true orphans (C-merge + D-review — need maintainer action)`,
|
|
124
|
+
'',
|
|
125
|
+
'## Categories',
|
|
126
|
+
'',
|
|
127
|
+
'| Category | Count | Action |',
|
|
128
|
+
'|---|---:|---|',
|
|
129
|
+
`| A-bind | ${triage.byCategory['A-bind'].length} | Wire to specialist in registry |`,
|
|
130
|
+
`| B-composer | ${triage.byCategory['B-composer'].length} | Document composer reachability or bind |`,
|
|
131
|
+
`| C-merge | ${triage.byCategory['C-merge'].length} | Propose merge into parent role skill |`,
|
|
132
|
+
`| D-review | ${triage.byCategory['D-review'].length} | Manual review |`,
|
|
133
|
+
'',
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
for (const cat of ['A-bind', 'B-composer', 'C-merge', 'D-review']) {
|
|
137
|
+
const rows = triage.byCategory[cat];
|
|
138
|
+
if (!rows.length) continue;
|
|
139
|
+
lines.push(`## ${cat} (${rows.length})`, '');
|
|
140
|
+
for (const { skill, recommendation } of rows) {
|
|
141
|
+
lines.push(`- \`skills/${skill}.md\` — ${recommendation}`);
|
|
142
|
+
}
|
|
143
|
+
lines.push('');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return lines.join('\n');
|
|
147
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/registry/generate-docs.mjs — generate reference docs from registry/capabilities.json.
|
|
3
|
+
*
|
|
4
|
+
* Writes docs/reference/capabilities.md so the human-readable capability matrix
|
|
5
|
+
* cannot drift from the machine-readable registry.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
|
|
12
|
+
import { loadCapabilityRegistry } from './validate.mjs';
|
|
13
|
+
|
|
14
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const REPO_ROOT = path.resolve(MODULE_DIR, '..', '..');
|
|
16
|
+
|
|
17
|
+
function surfaceSummary(surfaces = {}) {
|
|
18
|
+
return Object.entries(surfaces)
|
|
19
|
+
.filter(([, v]) => v?.supported)
|
|
20
|
+
.map(([name, v]) => {
|
|
21
|
+
const bits = [name];
|
|
22
|
+
if (v.primary) bits.push('primary');
|
|
23
|
+
if (v.tool) bits.push(v.tool);
|
|
24
|
+
if (v.command) bits.push(v.command);
|
|
25
|
+
return bits.join(':');
|
|
26
|
+
})
|
|
27
|
+
.join(', ') || 'none';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function generateCapabilitiesDoc({ rootDir = REPO_ROOT, write = true } = {}) {
|
|
31
|
+
const { capabilities = [] } = loadCapabilityRegistry({ rootDir });
|
|
32
|
+
const byKind = new Map();
|
|
33
|
+
for (const cap of capabilities) {
|
|
34
|
+
const k = cap.kind || 'capability';
|
|
35
|
+
if (!byKind.has(k)) byKind.set(k, []);
|
|
36
|
+
byKind.get(k).push(cap);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const lines = [
|
|
40
|
+
'---',
|
|
41
|
+
'title: Capability Registry',
|
|
42
|
+
'description: Generated from registry/capabilities.json. Do not edit by hand.',
|
|
43
|
+
'---',
|
|
44
|
+
'',
|
|
45
|
+
'> Generated from `registry/capabilities.json`. Re-run `construct registry:generate-docs` to refresh.',
|
|
46
|
+
'',
|
|
47
|
+
`# Capability Registry (${capabilities.length} entries)`,
|
|
48
|
+
'',
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
for (const [kind, items] of [...byKind.entries()].sort()) {
|
|
52
|
+
lines.push(`## ${kind}`, '');
|
|
53
|
+
lines.push('| ID | Name | Criticality | Surfaces | Human gate | Last validated |');
|
|
54
|
+
lines.push('|---|---|---|---|---|---|');
|
|
55
|
+
for (const cap of items.sort((a, b) => a.id.localeCompare(b.id))) {
|
|
56
|
+
const validated = cap.lastValidated ? cap.lastValidated.slice(0, 10) : 'never';
|
|
57
|
+
lines.push(
|
|
58
|
+
`| \`${cap.id}\` | ${cap.name ?? cap.id} | ${cap.criticality ?? '—'} | ${surfaceSummary(cap.surfaces)} | ${cap.humanGate ?? '—'} | ${validated} |`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
lines.push('');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const content = `${lines.join('\n')}\n`;
|
|
65
|
+
const out = path.join(rootDir, 'docs', 'reference', 'capabilities.md');
|
|
66
|
+
if (!write) return { out, content, drift: fs.existsSync(out) ? fs.readFileSync(out, 'utf8') !== content : true };
|
|
67
|
+
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
68
|
+
fs.writeFileSync(out, content);
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function checkCapabilitiesDocDrift({ rootDir = REPO_ROOT } = {}) {
|
|
73
|
+
const { content, drift } = generateCapabilitiesDoc({ rootDir, write: false });
|
|
74
|
+
return { drift, contentLength: content.length };
|
|
75
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/registry/skill-verification.mjs — verification-bar lint for workflow skills.
|
|
3
|
+
*
|
|
4
|
+
* docs/*-workflow.md skills must carry inputs, artifactType, and verificationBar
|
|
5
|
+
* in YAML frontmatter so anti-slop gates apply to the skill layer.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import yaml from 'js-yaml';
|
|
11
|
+
|
|
12
|
+
const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/;
|
|
13
|
+
|
|
14
|
+
function parseFrontmatter(content) {
|
|
15
|
+
const match = content.match(FRONTMATTER_RE);
|
|
16
|
+
if (!match) return null;
|
|
17
|
+
try {
|
|
18
|
+
return yaml.load(match[1]) || {};
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function lintWorkflowSkillVerification(skillsRoot) {
|
|
25
|
+
const errors = [];
|
|
26
|
+
const warnings = [];
|
|
27
|
+
const docsDir = path.join(skillsRoot, 'docs');
|
|
28
|
+
if (!fs.existsSync(docsDir)) return { valid: true, errors, warnings, checked: 0 };
|
|
29
|
+
|
|
30
|
+
for (const entry of fs.readdirSync(docsDir)) {
|
|
31
|
+
if (!entry.endsWith('-workflow.md')) continue;
|
|
32
|
+
const rel = `docs/${entry.replace(/\.md$/, '')}`;
|
|
33
|
+
const filePath = path.join(docsDir, entry);
|
|
34
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
35
|
+
const fm = parseFrontmatter(raw);
|
|
36
|
+
if (!fm) {
|
|
37
|
+
errors.push(`${rel}: missing or invalid YAML frontmatter`);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (!fm.inputs || !Array.isArray(fm.inputs) || fm.inputs.length === 0) {
|
|
41
|
+
warnings.push(`${rel}: missing frontmatter.inputs`);
|
|
42
|
+
}
|
|
43
|
+
if (!fm.artifactType || typeof fm.artifactType !== 'string') {
|
|
44
|
+
warnings.push(`${rel}: missing frontmatter.artifactType`);
|
|
45
|
+
}
|
|
46
|
+
if (!fm.verificationBar || typeof fm.verificationBar !== 'string') {
|
|
47
|
+
warnings.push(`${rel}: missing frontmatter.verificationBar`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
valid: errors.length === 0,
|
|
53
|
+
errors,
|
|
54
|
+
warnings,
|
|
55
|
+
checked: fs.readdirSync(docsDir).filter((f) => f.endsWith('-workflow.md')).length,
|
|
56
|
+
};
|
|
57
|
+
}
|