@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
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/audit-specialists.mjs — Specialist/skill corpus audit matrix.
|
|
3
|
+
*
|
|
4
|
+
* Merges live registry, contracts, skills, prompts, and audit-enrichments into
|
|
5
|
+
* one machine-readable matrix. Powers `construct audit specialists` and the
|
|
6
|
+
* human-readable docs/concepts/specialist-skill-audit.md companion.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { auditSkills } from './audit-skills.mjs';
|
|
12
|
+
import { loadArtifactManifest } from './artifact-manifest.mjs';
|
|
13
|
+
import { resolvePerspectiveFromPrompt } from './specialists/prompt-schema.mjs';
|
|
14
|
+
import { validateRoleCards } from './certification/role-cards.mjs';
|
|
15
|
+
import { auditSpecialistContracts } from './certification/specialist-contracts.mjs';
|
|
16
|
+
import { validateAllRoleOverlays } from './certification/role-overlays.mjs';
|
|
17
|
+
import { ROLE_DIRECTIVE_RE } from './role-preload.mjs';
|
|
18
|
+
|
|
19
|
+
function findConstructRoot(startPath = process.cwd()) {
|
|
20
|
+
let current = path.resolve(startPath);
|
|
21
|
+
while (true) {
|
|
22
|
+
if (fs.existsSync(path.join(current, 'specialists', 'registry.json'))) return current;
|
|
23
|
+
const parent = path.dirname(current);
|
|
24
|
+
if (parent === current) return path.resolve(startPath);
|
|
25
|
+
current = parent;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readJson(p) {
|
|
30
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function collectSkillFiles(skillsDir) {
|
|
34
|
+
const results = [];
|
|
35
|
+
function walk(dir, prefix = '') {
|
|
36
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
37
|
+
if (entry.isDirectory()) {
|
|
38
|
+
walk(path.join(dir, entry.name), prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
39
|
+
} else if (entry.name.endsWith('.md') && entry.name !== 'routing.md') {
|
|
40
|
+
results.push(prefix ? `${prefix}/${entry.name.replace(/\.md$/, '')}` : entry.name.replace(/\.md$/, ''));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
try { walk(skillsDir); } catch { /* missing */ }
|
|
45
|
+
return results.sort();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseRoleOverlay(promptPath) {
|
|
49
|
+
if (!fs.existsSync(promptPath)) return null;
|
|
50
|
+
const text = fs.readFileSync(promptPath, 'utf8');
|
|
51
|
+
const m = text.match(ROLE_DIRECTIVE_RE);
|
|
52
|
+
return m ? `roles/${m[1]}` : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function contractsForSpecialist(contracts, name) {
|
|
56
|
+
const id = name.startsWith('cx-') ? name : `cx-${name}`;
|
|
57
|
+
const related = [];
|
|
58
|
+
for (const c of contracts) {
|
|
59
|
+
if (c.producer === id || c.consumer === id) related.push(c.id);
|
|
60
|
+
}
|
|
61
|
+
return related;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function docTypesFromContracts(contracts) {
|
|
65
|
+
const types = new Set();
|
|
66
|
+
for (const c of contracts) {
|
|
67
|
+
const dt = c.trigger?.docAuthoring?.docType;
|
|
68
|
+
if (Array.isArray(dt)) dt.forEach((t) => types.add(t));
|
|
69
|
+
else if (typeof dt === 'string') types.add(dt);
|
|
70
|
+
}
|
|
71
|
+
return [...types];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function enforcementForAgent(agent, contracts) {
|
|
75
|
+
const id = `cx-${agent.name}`;
|
|
76
|
+
const items = ['no-fabrication.md', 'neurodivergent-output.md'];
|
|
77
|
+
const related = contracts.filter((c) => c.producer === id || c.consumer === id);
|
|
78
|
+
if (related.some((c) => c.postconditions?.length)) items.push('contracts.json postconditions');
|
|
79
|
+
if (agent.docArtifacts?.length) items.push('artifact structure lint', 'comment-lint on artifact paths');
|
|
80
|
+
if (agent.watchConditions?.length) items.push('watchConditions routing');
|
|
81
|
+
return items;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function crossChecks({ registry, manifest, skillAudit, allSkills, contractDocTypes }) {
|
|
85
|
+
const issues = [];
|
|
86
|
+
const manifestTypes = new Set(Object.keys(manifest.artifacts ?? {}));
|
|
87
|
+
|
|
88
|
+
for (const agent of registry.specialists ?? []) {
|
|
89
|
+
for (const skill of agent.skills ?? []) {
|
|
90
|
+
if (!allSkills.includes(skill)) {
|
|
91
|
+
issues.push({ kind: 'missing-skill-file', agent: agent.name, skill });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const doc of agent.docArtifacts ?? []) {
|
|
95
|
+
if (!manifestTypes.has(doc)) {
|
|
96
|
+
issues.push({ kind: 'doc-artifact-no-manifest', agent: agent.name, docType: doc });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for (const dt of contractDocTypes) {
|
|
102
|
+
if (!manifestTypes.has(dt)) {
|
|
103
|
+
issues.push({ kind: 'contract-doc-no-manifest', docType: dt });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (skillAudit.missingSkillFiles.length) {
|
|
108
|
+
for (const { agent, skill } of skillAudit.missingSkillFiles) {
|
|
109
|
+
issues.push({ kind: 'registry-missing-skill', agent, skill });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return issues;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function auditSpecialists({ rootDir, silent = false } = {}) {
|
|
117
|
+
const root = rootDir ?? findConstructRoot();
|
|
118
|
+
const registry = readJson(path.join(root, 'specialists', 'registry.json'));
|
|
119
|
+
const contractsDoc = readJson(path.join(root, 'specialists', 'contracts.json'));
|
|
120
|
+
const enrichments = readJson(path.join(root, 'specialists', 'audit-enrichments.json'));
|
|
121
|
+
const manifest = loadArtifactManifest({ rootDir: root });
|
|
122
|
+
const skillAudit = auditSkills({ rootDir: root, silent: true });
|
|
123
|
+
const allSkills = collectSkillFiles(path.join(root, 'skills'));
|
|
124
|
+
const contractDocTypes = docTypesFromContracts(contractsDoc.contracts ?? []);
|
|
125
|
+
const crossCheckIssues = crossChecks({ registry, manifest, skillAudit, allSkills, contractDocTypes });
|
|
126
|
+
|
|
127
|
+
const specialists = (registry.specialists ?? []).map((agent) => {
|
|
128
|
+
const promptPath = agent.promptFile
|
|
129
|
+
? path.join(root, agent.promptFile)
|
|
130
|
+
: null;
|
|
131
|
+
const meta = enrichments.specialists?.[agent.name] ?? {};
|
|
132
|
+
const perspective = resolvePerspectiveFromPrompt(agent.name, { rootDir: root, registry });
|
|
133
|
+
return {
|
|
134
|
+
name: agent.name,
|
|
135
|
+
id: `cx-${agent.name}`,
|
|
136
|
+
humanEquivalent: meta.humanEquivalent ?? 'unknown',
|
|
137
|
+
primaryOutputs: agent.docArtifacts ?? [],
|
|
138
|
+
skillsBound: agent.skills ?? [],
|
|
139
|
+
roleOverlay: promptPath ? parseRoleOverlay(promptPath) : null,
|
|
140
|
+
researchProfile: meta.researchProfile ?? null,
|
|
141
|
+
toneProfile: meta.toneProfile ?? 'direct',
|
|
142
|
+
challengeModel: {
|
|
143
|
+
tension: perspective?.tension ?? null,
|
|
144
|
+
failureMode: perspective?.failureMode ?? null,
|
|
145
|
+
watchConditions: agent.watchConditions ?? [],
|
|
146
|
+
},
|
|
147
|
+
enforcementToday: enforcementForAgent(agent, contractsDoc.contracts ?? []),
|
|
148
|
+
contracts: contractsForSpecialist(contractsDoc.contracts ?? [], agent.name),
|
|
149
|
+
grade: meta.grade ?? 'adequate',
|
|
150
|
+
gap: meta.gap ?? 'Not assessed',
|
|
151
|
+
beadId: meta.beadId ?? 'construct-hcr9',
|
|
152
|
+
skillCount: (agent.skills ?? []).length,
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const roleOverlays = [];
|
|
157
|
+
const rolesDir = path.join(root, 'skills', 'roles');
|
|
158
|
+
if (fs.existsSync(rolesDir)) {
|
|
159
|
+
for (const f of fs.readdirSync(rolesDir).filter((n) => n.endsWith('.md')).sort()) {
|
|
160
|
+
const key = f.replace(/\.md$/, '');
|
|
161
|
+
const meta = enrichments.roleOverlays?.[key] ?? {};
|
|
162
|
+
roleOverlays.push({
|
|
163
|
+
path: `roles/${key}`,
|
|
164
|
+
grade: meta.grade ?? 'adequate',
|
|
165
|
+
gap: meta.gap ?? 'Not assessed',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const workflowSkills = allSkills
|
|
171
|
+
.filter((s) => !s.startsWith('roles/'))
|
|
172
|
+
.map((skillPath) => {
|
|
173
|
+
const manifestEntry = Object.entries(manifest.artifacts ?? {}).find(([, v]) => v.workflowSkill === skillPath);
|
|
174
|
+
return {
|
|
175
|
+
path: skillPath,
|
|
176
|
+
artifactType: manifestEntry?.[0] ?? null,
|
|
177
|
+
toneDefault: manifestEntry?.[1]?.toneDefault ?? null,
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const roleCards = validateRoleCards({ rootDir: root });
|
|
182
|
+
const specialistContracts = auditSpecialistContracts({ rootDir: root });
|
|
183
|
+
const roleOverlayAudit = validateAllRoleOverlays({ rootDir: root });
|
|
184
|
+
if (!roleCards.pass) {
|
|
185
|
+
for (const err of roleCards.errors) crossCheckIssues.push({ kind: 'role-card-missing', detail: err });
|
|
186
|
+
}
|
|
187
|
+
if (!specialistContracts.pass) {
|
|
188
|
+
for (const fail of specialistContracts.failures) {
|
|
189
|
+
crossCheckIssues.push({ kind: 'specialist-contract-fail', specialist: fail.specialistId });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (!roleOverlayAudit.pass) {
|
|
193
|
+
for (const err of roleOverlayAudit.errors.slice(0, 5)) {
|
|
194
|
+
crossCheckIssues.push({ kind: 'role-overlay-fail', detail: err });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const result = {
|
|
199
|
+
version: 1,
|
|
200
|
+
generatedAt: new Date().toISOString(),
|
|
201
|
+
specialistCount: specialists.length,
|
|
202
|
+
roleOverlayCount: roleOverlays.length,
|
|
203
|
+
workflowSkillCount: workflowSkills.length,
|
|
204
|
+
specialists,
|
|
205
|
+
roleOverlays,
|
|
206
|
+
workflowSkills,
|
|
207
|
+
crossCheckIssues,
|
|
208
|
+
roleCards,
|
|
209
|
+
specialistContracts,
|
|
210
|
+
roleOverlayAudit,
|
|
211
|
+
pass: crossCheckIssues.length === 0 && skillAudit.pass,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
if (!silent) {
|
|
215
|
+
const line = (msg) => process.stdout.write(`${msg}\n`);
|
|
216
|
+
line('Construct Specialist Audit');
|
|
217
|
+
line('═══════════════════════════');
|
|
218
|
+
line(` Specialists: ${result.specialistCount}`);
|
|
219
|
+
line(` Role overlays: ${result.roleOverlayCount}`);
|
|
220
|
+
line(` Workflow skills: ${result.workflowSkillCount}`);
|
|
221
|
+
line('');
|
|
222
|
+
if (crossCheckIssues.length === 0) {
|
|
223
|
+
line(' ✓ Cross-checks passed (skills, docArtifacts, manifest, contracts)');
|
|
224
|
+
} else {
|
|
225
|
+
line(` ✗ Cross-check issues (${crossCheckIssues.length}):`);
|
|
226
|
+
for (const issue of crossCheckIssues) line(` - ${JSON.stringify(issue)}`);
|
|
227
|
+
}
|
|
228
|
+
line('');
|
|
229
|
+
line(result.pass ? ' Result: PASS' : ' Result: FAIL');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function formatAuditMarkdown(result) {
|
|
236
|
+
const lines = [
|
|
237
|
+
'# Specialist & skill audit',
|
|
238
|
+
'',
|
|
239
|
+
`Generated: ${result.generatedAt}. Re-run \`construct audit specialists --json\` for the live matrix.`,
|
|
240
|
+
'',
|
|
241
|
+
'## Specialists',
|
|
242
|
+
'',
|
|
243
|
+
'| Specialist | Human equivalent | Outputs | Skills | Role overlay | Research | Tone | Grade | Gap |',
|
|
244
|
+
'|---|---|---|---|---|---|---|---|---|',
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
for (const s of result.specialists) {
|
|
248
|
+
lines.push(
|
|
249
|
+
`| cx-${s.name} | ${s.humanEquivalent} | ${s.primaryOutputs.join(', ') || '—'} | ${s.skillCount} | ${s.roleOverlay ?? '—'} | ${s.researchProfile ?? '—'} | ${s.toneProfile} | ${s.grade} | ${s.gap} |`,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
lines.push('', '## Role overlays', '', '| Overlay | Grade | Gap |', '|---|---|---|');
|
|
254
|
+
for (const r of result.roleOverlays) {
|
|
255
|
+
lines.push(`| ${r.path} | ${r.grade} | ${r.gap} |`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
lines.push('', '## Cross-check issues', '');
|
|
259
|
+
if (result.crossCheckIssues.length === 0) {
|
|
260
|
+
lines.push('None.');
|
|
261
|
+
} else {
|
|
262
|
+
for (const issue of result.crossCheckIssues) {
|
|
263
|
+
lines.push(`- \`${issue.kind}\`: ${JSON.stringify(issue)}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return `${lines.join('\n')}\n`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export async function runAuditSpecialistsCli(args = []) {
|
|
271
|
+
const json = args.includes('--json');
|
|
272
|
+
const markdown = args.includes('--markdown');
|
|
273
|
+
const rootDir = args.find((a) => a.startsWith('--root='))?.split('=')[1];
|
|
274
|
+
const result = auditSpecialists({ rootDir, silent: true });
|
|
275
|
+
|
|
276
|
+
if (markdown) {
|
|
277
|
+
process.stdout.write(formatAuditMarkdown(result));
|
|
278
|
+
} else if (json) {
|
|
279
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
280
|
+
} else {
|
|
281
|
+
auditSpecialists({ rootDir, silent: false });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (!result.pass) process.exit(1);
|
|
285
|
+
}
|
package/lib/audit-trail.mjs
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* Corrupted links cannot be re-derived, so a `chain_reset` boundary line seals
|
|
17
17
|
* the damaged prefix as immutable legacy and re-bases the live chain;
|
|
18
18
|
* verifyChain validates only from the last boundary forward. See
|
|
19
|
-
* docs/concepts/observability.
|
|
19
|
+
* docs/concepts/observability.md.
|
|
20
20
|
*/
|
|
21
21
|
import { readFileSync, existsSync, statSync, mkdirSync } from 'node:fs';
|
|
22
22
|
import { createHash } from 'node:crypto';
|
package/lib/auto-docs.mjs
CHANGED
|
@@ -90,20 +90,25 @@ function buildCoreDocsContract() {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
const DIR_DESCRIPTIONS = {
|
|
93
|
-
|
|
94
|
-
apps: 'User-facing apps shipped from this repo (e.g. apps/docs/, the Next.js docs site)',
|
|
93
|
+
apps: 'User-facing apps shipped from this repo (chat, dashboard, docs)',
|
|
95
94
|
bin: 'CLI entrypoint (`construct`)',
|
|
96
|
-
claude: 'Claude Code integration (agents, settings template)',
|
|
97
95
|
commands: 'Command prompt assets',
|
|
98
|
-
|
|
96
|
+
config: 'Repo-wide controlled vocabulary (tag-vocabulary.json)',
|
|
97
|
+
deploy: 'Terraform and deployment configs',
|
|
99
98
|
docs: 'Architecture notes, runbooks, and documentation contract',
|
|
100
|
-
|
|
101
|
-
lib: 'Core runtime: CLI, hooks, MCP,
|
|
102
|
-
|
|
99
|
+
examples: 'Example projects and persona fixtures',
|
|
100
|
+
lib: 'Core runtime: CLI, hooks, MCP, providers, oracle, sync',
|
|
101
|
+
packages: 'Shared workspace packages (e.g. cx-ui)',
|
|
103
102
|
personas: 'Persona prompt definitions',
|
|
103
|
+
platforms: 'Host adapter capability configs',
|
|
104
|
+
profiles: 'Profile catalog',
|
|
105
|
+
registry: 'Product capability registry',
|
|
104
106
|
rules: 'Coding and quality standards',
|
|
105
|
-
|
|
107
|
+
schemas: 'Registry and config JSON Schema',
|
|
108
|
+
scripts: 'Audit, alignment, release, and sync scripts',
|
|
106
109
|
skills: 'Reusable domain knowledge files',
|
|
110
|
+
specialists: 'Org registry, contracts, and specialist prompts',
|
|
111
|
+
templates: 'Doc and workflow templates',
|
|
107
112
|
tests: 'Test suite',
|
|
108
113
|
};
|
|
109
114
|
|
|
@@ -164,16 +169,16 @@ function buildAgentsTable(rootDir) {
|
|
|
164
169
|
let registry;
|
|
165
170
|
try { registry = JSON.parse(fs.readFileSync(registryPath, 'utf8')); } catch { return ''; }
|
|
166
171
|
|
|
167
|
-
const agents =
|
|
168
|
-
|
|
172
|
+
const agents = [
|
|
173
|
+
...(registry.orchestrator ? [{ ...registry.orchestrator, name: registry.orchestrator.name }] : []),
|
|
174
|
+
...(registry.specialists ?? []),
|
|
175
|
+
];
|
|
176
|
+
if (!agents.length) return '';
|
|
169
177
|
|
|
170
178
|
const rows = ['| Specialist | Tier | Purpose |', '|---|---|---|'];
|
|
171
179
|
for (const agent of agents.slice(0, 30)) {
|
|
172
180
|
const name = agent.name ?? agent.id ?? 'n/a';
|
|
173
181
|
const tier = agent.modelTier ?? agent.tier ?? agent.model_tier ?? 'n/a';
|
|
174
|
-
// Strip em-dashes from descriptions so the prose lint stays clean across
|
|
175
|
-
// AUTO regen. Em-dashes are allowed in the registry's source descriptions
|
|
176
|
-
// but never in the rendered markdown.
|
|
177
182
|
const purpose = (agent.description ?? agent.purpose ?? '')
|
|
178
183
|
.split('\n')[0].slice(0, 80)
|
|
179
184
|
.replace(/ — /g, '. ').replace(/—/g, ',');
|
|
@@ -363,7 +368,9 @@ function renderCommandPage(category, commands) {
|
|
|
363
368
|
lines.push('**Subcommands**');
|
|
364
369
|
lines.push('');
|
|
365
370
|
for (const sub of cmd.subcommands) {
|
|
366
|
-
|
|
371
|
+
const name = typeof sub === 'string' ? sub : sub.name;
|
|
372
|
+
const description = typeof sub === 'string' ? '' : (sub.desc || sub.description || '');
|
|
373
|
+
lines.push(`- \`${name}\`${description ? ` — ${description}` : ''}`);
|
|
367
374
|
}
|
|
368
375
|
lines.push('');
|
|
369
376
|
}
|
|
@@ -474,13 +481,77 @@ export function buildFumadocsReference({ rootDir } = {}) {
|
|
|
474
481
|
written.push(path.relative(rootDir, hooksPage));
|
|
475
482
|
}
|
|
476
483
|
|
|
477
|
-
const
|
|
478
|
-
if (
|
|
479
|
-
const
|
|
480
|
-
if (writeIfChanged(
|
|
481
|
-
written.push(path.relative(rootDir,
|
|
484
|
+
const specialistsContent = renderAgentsPage(rootDir);
|
|
485
|
+
if (specialistsContent) {
|
|
486
|
+
const specialistsPage = path.join(refDir, 'specialists.md');
|
|
487
|
+
if (writeIfChanged(specialistsPage, specialistsContent)) {
|
|
488
|
+
written.push(path.relative(rootDir, specialistsPage));
|
|
489
|
+
}
|
|
490
|
+
const legacyAgentsPage = path.join(refDir, 'agents.md');
|
|
491
|
+
if (fs.existsSync(legacyAgentsPage)) {
|
|
492
|
+
fs.unlinkSync(legacyAgentsPage);
|
|
493
|
+
written.push(`- ${path.relative(rootDir, legacyAgentsPage)}`);
|
|
482
494
|
}
|
|
483
495
|
}
|
|
484
496
|
|
|
485
497
|
return { written };
|
|
486
498
|
}
|
|
499
|
+
|
|
500
|
+
export function checkFumadocsReferenceDrift({ rootDir } = {}) {
|
|
501
|
+
rootDir = rootDir ?? process.cwd();
|
|
502
|
+
const refDir = path.join(rootDir, 'docs', 'reference');
|
|
503
|
+
const cliDir = path.join(refDir, 'cli');
|
|
504
|
+
const drift = [];
|
|
505
|
+
|
|
506
|
+
for (const category of REFERENCE_CATEGORIES) {
|
|
507
|
+
const commands = CLI_COMMANDS_BY_CATEGORY[category] ?? [];
|
|
508
|
+
if (!commands.length) continue;
|
|
509
|
+
const slug = slugify(category);
|
|
510
|
+
const out = path.join(cliDir, `${slug}.md`);
|
|
511
|
+
const content = renderCommandPage(category, commands);
|
|
512
|
+
if (readFile(out) !== content) drift.push(path.relative(rootDir, out));
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const cliPages = REFERENCE_CATEGORIES
|
|
516
|
+
.map((cat) => ({ cat, commands: CLI_COMMANDS_BY_CATEGORY[cat] ?? [] }))
|
|
517
|
+
.filter(({ commands }) => commands.length)
|
|
518
|
+
.map(({ cat }) => slugify(cat));
|
|
519
|
+
const cliMetaContent = JSON.stringify({ title: 'CLI', pages: ['index', ...cliPages] }, null, 2) + '\n';
|
|
520
|
+
const cliMeta = path.join(cliDir, 'meta.json');
|
|
521
|
+
if (readFile(cliMeta) !== cliMetaContent) drift.push(path.relative(rootDir, cliMeta));
|
|
522
|
+
|
|
523
|
+
const cliIndexContent = [
|
|
524
|
+
'---',
|
|
525
|
+
'title: CLI',
|
|
526
|
+
'description: Every construct command, grouped by category. Generated from lib/cli-commands.mjs.',
|
|
527
|
+
'---',
|
|
528
|
+
'',
|
|
529
|
+
'> Generated from `lib/cli-commands.mjs`. Re-run `construct docs:site` to refresh.',
|
|
530
|
+
'',
|
|
531
|
+
'Commands are grouped by what they do. Pick a category below.',
|
|
532
|
+
'',
|
|
533
|
+
...REFERENCE_CATEGORIES.map((cat) => {
|
|
534
|
+
const slug = slugify(cat);
|
|
535
|
+
const commands = CLI_COMMANDS_BY_CATEGORY[cat] ?? [];
|
|
536
|
+
if (!commands.length) return null;
|
|
537
|
+
return `- [${cat}](/reference/cli/${slug}) — ${commands.length} command${commands.length === 1 ? '' : 's'}`;
|
|
538
|
+
}).filter(Boolean),
|
|
539
|
+
'',
|
|
540
|
+
].join('\n');
|
|
541
|
+
const cliIndex = path.join(cliDir, 'index.md');
|
|
542
|
+
if (readFile(cliIndex) !== cliIndexContent) drift.push(path.relative(rootDir, cliIndex));
|
|
543
|
+
|
|
544
|
+
const hooksPage = path.join(refDir, 'hooks.md');
|
|
545
|
+
const hooksContent = renderHooksPage(rootDir);
|
|
546
|
+
if (hooksContent && readFile(hooksPage) !== hooksContent) drift.push(path.relative(rootDir, hooksPage));
|
|
547
|
+
|
|
548
|
+
const specialistsContent = renderAgentsPage(rootDir);
|
|
549
|
+
const specialistsPage = path.join(refDir, 'specialists.md');
|
|
550
|
+
if (specialistsContent && readFile(specialistsPage) !== specialistsContent) {
|
|
551
|
+
drift.push(path.relative(rootDir, specialistsPage));
|
|
552
|
+
}
|
|
553
|
+
const legacyAgentsPage = path.join(refDir, 'agents.md');
|
|
554
|
+
if (fs.existsSync(legacyAgentsPage)) drift.push(path.relative(rootDir, legacyAgentsPage));
|
|
555
|
+
|
|
556
|
+
return { drift };
|
|
557
|
+
}
|
package/lib/beads/drift.mjs
CHANGED
|
@@ -37,6 +37,10 @@ function daysAgo(value) {
|
|
|
37
37
|
return (Date.now() - ts) / (24 * 60 * 60 * 1000);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
function beadUpdatedAt(bead) {
|
|
41
|
+
return bead?.updated || bead?.updatedAt || bead?.updated_at || bead?.created || bead?.createdAt || bead?.created_at || null;
|
|
42
|
+
}
|
|
43
|
+
|
|
40
44
|
function listBeadsJson({ status, runner = spawnSync } = {}) {
|
|
41
45
|
const args = ['list', '--json'];
|
|
42
46
|
if (status) args.push(`--status=${status}`);
|
|
@@ -78,6 +82,20 @@ function titleAppearsInSubject(beadTitle, commitSubject) {
|
|
|
78
82
|
return hits >= 3;
|
|
79
83
|
}
|
|
80
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Oracle auto-raised meta beads must not feed back into hygiene drift counts.
|
|
87
|
+
*/
|
|
88
|
+
export function isOracleMetaBead(bead) {
|
|
89
|
+
const title = String(bead?.title ?? '');
|
|
90
|
+
const labels = Array.isArray(bead?.labels) ? bead.labels : [];
|
|
91
|
+
return title.startsWith('[oracle/') || labels.includes('oracle');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function filterOracleMeta(beads, excludeOracleMeta) {
|
|
95
|
+
if (!excludeOracleMeta) return beads;
|
|
96
|
+
return beads.filter((b) => !isOracleMetaBead(b));
|
|
97
|
+
}
|
|
98
|
+
|
|
81
99
|
/**
|
|
82
100
|
* Run the three drift checks against the live tracker + git state.
|
|
83
101
|
* Pure-ish — accepts a runner injection for tests.
|
|
@@ -86,14 +104,17 @@ export function detectBeadsDrift({
|
|
|
86
104
|
staleOpenDays = DEFAULT_STALE_OPEN_DAYS,
|
|
87
105
|
stuckInProgressDays = DEFAULT_STUCK_IN_PROGRESS_DAYS,
|
|
88
106
|
mergeLookback = DEFAULT_MERGE_LOOKBACK,
|
|
107
|
+
excludeOracleMeta = true,
|
|
89
108
|
runner = spawnSync,
|
|
90
109
|
} = {}) {
|
|
91
|
-
const
|
|
92
|
-
const
|
|
110
|
+
const openRaw = listBeadsJson({ status: 'open', runner });
|
|
111
|
+
const inProgressRaw = listBeadsJson({ status: 'in_progress', runner });
|
|
112
|
+
const open = filterOracleMeta(openRaw, excludeOracleMeta);
|
|
113
|
+
const inProgress = filterOracleMeta(inProgressRaw, excludeOracleMeta);
|
|
93
114
|
const commits = recentCommitSubjects(mergeLookback, { runner });
|
|
94
115
|
|
|
95
|
-
const staleOpen = open.filter((b) => daysAgo(b
|
|
96
|
-
const stuckInProgress = inProgress.filter((b) => daysAgo(b
|
|
116
|
+
const staleOpen = open.filter((b) => daysAgo(beadUpdatedAt(b)) > staleOpenDays);
|
|
117
|
+
const stuckInProgress = inProgress.filter((b) => daysAgo(beadUpdatedAt(b)) > stuckInProgressDays);
|
|
97
118
|
|
|
98
119
|
const mergeDrift = [];
|
|
99
120
|
for (const bead of open) {
|
|
@@ -106,8 +127,8 @@ export function detectBeadsDrift({
|
|
|
106
127
|
}
|
|
107
128
|
|
|
108
129
|
return {
|
|
109
|
-
staleOpen: staleOpen.map((b) => ({ id: b.id, title: b.title, ageDays: Math.round(daysAgo(b
|
|
110
|
-
stuckInProgress: stuckInProgress.map((b) => ({ id: b.id, title: b.title, ageDays: Math.round(daysAgo(b
|
|
130
|
+
staleOpen: staleOpen.map((b) => ({ id: b.id, title: b.title, ageDays: Math.round(daysAgo(beadUpdatedAt(b))) })),
|
|
131
|
+
stuckInProgress: stuckInProgress.map((b) => ({ id: b.id, title: b.title, ageDays: Math.round(daysAgo(beadUpdatedAt(b))) })),
|
|
111
132
|
mergeDrift,
|
|
112
133
|
thresholds: { staleOpenDays, stuckInProgressDays, mergeLookback },
|
|
113
134
|
counts: {
|