@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,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/artifact-release-gate.mjs — Real-org release validation for typed artifacts.
|
|
3
|
+
*
|
|
4
|
+
* Composes structure/visual lint, citation discipline, prose heuristics, and
|
|
5
|
+
* optional reviewer sign-off from the artifact manifest releaseGate block.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { lintDocStructure, lintDocVisuals } from './templates/visual-requirements.mjs';
|
|
11
|
+
import { getArtifactEntry, resolveToneForArtifact } from './artifact-manifest.mjs';
|
|
12
|
+
import { lintFile } from './comment-lint.mjs';
|
|
13
|
+
import {
|
|
14
|
+
missingRequiredReviewers,
|
|
15
|
+
parseReleaseGateFrontmatter,
|
|
16
|
+
} from './artifact-reviewers.mjs';
|
|
17
|
+
import { inferArtifactTypeFromPath } from './artifact-type-from-path.mjs';
|
|
18
|
+
|
|
19
|
+
function countProseParagraphs(body) {
|
|
20
|
+
const blocks = body.split(/\n\s*\n/).map((b) => b.trim()).filter(Boolean);
|
|
21
|
+
let prose = 0;
|
|
22
|
+
for (const b of blocks) {
|
|
23
|
+
const first = b.split('\n')[0].trimStart();
|
|
24
|
+
const isHeading = first.startsWith('#');
|
|
25
|
+
const isList = /^([-*+]\s|\d+[.)]\s|>\s)/.test(first);
|
|
26
|
+
const isTable = first.startsWith('|');
|
|
27
|
+
const isFence = first.startsWith('```');
|
|
28
|
+
if (isHeading || isList || isTable || isFence) continue;
|
|
29
|
+
const sentences = (b.match(/[.!?](\s|$)/g) || []).length;
|
|
30
|
+
if (sentences >= 2 || b.length >= 200) prose++;
|
|
31
|
+
}
|
|
32
|
+
return prose;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function countCitations(body) {
|
|
36
|
+
const patterns = [
|
|
37
|
+
/\bhttps?:\/\/[^\s)]+/g,
|
|
38
|
+
/\barxiv:\s*\d{4}\.\d{4,5}/gi,
|
|
39
|
+
/\[source:[^\]]+\]/gi,
|
|
40
|
+
/\(accessed\s+\d{4}-\d{2}-\d{2}\)/gi,
|
|
41
|
+
];
|
|
42
|
+
const hits = new Set();
|
|
43
|
+
for (const re of patterns) for (const m of body.match(re) || []) hits.add(m.trim().toLowerCase());
|
|
44
|
+
return hits.size;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function validateArtifactRelease({
|
|
48
|
+
filePath,
|
|
49
|
+
type,
|
|
50
|
+
cwd = process.cwd(),
|
|
51
|
+
rootDir,
|
|
52
|
+
reviewersSeen,
|
|
53
|
+
} = {}) {
|
|
54
|
+
const resolvedType = type || inferArtifactTypeFromPath(filePath, { rootDir: cwd });
|
|
55
|
+
const entry = getArtifactEntry(resolvedType, { rootDir });
|
|
56
|
+
if (!entry) {
|
|
57
|
+
return { ok: false, errors: [`Unknown artifact type: ${resolvedType}`], warnings: [], tone: null };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { bypass, reason } = parseReleaseGateFrontmatter(filePath);
|
|
61
|
+
if (bypass) {
|
|
62
|
+
if (!reason) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
errors: ['cx_release_gate: bypass requires cx_release_gate_reason in frontmatter'],
|
|
66
|
+
warnings: [],
|
|
67
|
+
tone: resolveToneForArtifact(resolvedType, { cwd, rootDir }),
|
|
68
|
+
type: resolvedType,
|
|
69
|
+
filePath,
|
|
70
|
+
bypassed: true,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
ok: true,
|
|
75
|
+
errors: [],
|
|
76
|
+
warnings: [`release gate bypassed: ${reason}`],
|
|
77
|
+
tone: resolveToneForArtifact(resolvedType, { cwd, rootDir }),
|
|
78
|
+
type: resolvedType,
|
|
79
|
+
filePath,
|
|
80
|
+
bypassed: true,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const gate = entry.releaseGate ?? {};
|
|
85
|
+
const errors = [];
|
|
86
|
+
const warnings = [];
|
|
87
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
88
|
+
const body = raw.replace(/^---\n[\s\S]*?\n---\n/, '');
|
|
89
|
+
|
|
90
|
+
if (gate.structuralLint !== false) {
|
|
91
|
+
errors.push(...lintDocStructure(filePath, resolvedType));
|
|
92
|
+
errors.push(...lintDocVisuals(filePath, resolvedType));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (gate.citationLint) {
|
|
96
|
+
const isShippedTemplate = filePath.includes(`${path.sep}templates${path.sep}docs${path.sep}`);
|
|
97
|
+
if (!isShippedTemplate) {
|
|
98
|
+
const lint = lintFile(filePath);
|
|
99
|
+
const artifactIssues = [...(lint.errors ?? []), ...(lint.warnings ?? [])].filter((w) => w.kind === 'artifact');
|
|
100
|
+
for (const issue of artifactIssues) errors.push(issue.message || String(issue));
|
|
101
|
+
const citations = countCitations(body);
|
|
102
|
+
const unverified = /\[unverified\]/i.test(body);
|
|
103
|
+
if (citations < 1 && !unverified) {
|
|
104
|
+
errors.push('citationLint: no verifiable sources or [unverified] markers');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const minProse = gate.proseMinimum ?? 0;
|
|
110
|
+
if (minProse > 0) {
|
|
111
|
+
const prose = countProseParagraphs(body);
|
|
112
|
+
if (prose < minProse) {
|
|
113
|
+
errors.push(`proseMinimum: ${prose} paragraphs (need ${minProse})`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const missing = missingRequiredReviewers({
|
|
118
|
+
docType: resolvedType,
|
|
119
|
+
filePath,
|
|
120
|
+
rootDir,
|
|
121
|
+
cwd,
|
|
122
|
+
reviewersSeen,
|
|
123
|
+
});
|
|
124
|
+
if (missing.length > 0) {
|
|
125
|
+
warnings.push(`requiredReviewers not seen in agent log: ${missing.join(', ')}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const tone = resolveToneForArtifact(resolvedType, { cwd, rootDir });
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
ok: errors.length === 0,
|
|
132
|
+
errors,
|
|
133
|
+
warnings,
|
|
134
|
+
tone,
|
|
135
|
+
type: resolvedType,
|
|
136
|
+
filePath,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function runArtifactValidateCli(args = []) {
|
|
141
|
+
const positional = args.filter((a) => !a.startsWith('--'));
|
|
142
|
+
const filePath = positional[0];
|
|
143
|
+
const typeArg = args.find((a) => a.startsWith('--type='));
|
|
144
|
+
const json = args.includes('--json');
|
|
145
|
+
|
|
146
|
+
if (!filePath || !typeArg) {
|
|
147
|
+
process.stderr.write('Usage: construct artifact validate <path> --type=<doc-type> [--json]\n');
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const type = typeArg.split('=')[1];
|
|
152
|
+
const result = validateArtifactRelease({ filePath, type });
|
|
153
|
+
|
|
154
|
+
if (json) {
|
|
155
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
156
|
+
} else {
|
|
157
|
+
process.stdout.write(`Artifact release gate: ${type}\n`);
|
|
158
|
+
process.stdout.write(` Tone: ${result.tone}\n`);
|
|
159
|
+
if (result.errors.length) {
|
|
160
|
+
process.stdout.write(` Errors (${result.errors.length}):\n`);
|
|
161
|
+
for (const e of result.errors) process.stdout.write(` - ${e}\n`);
|
|
162
|
+
}
|
|
163
|
+
if (result.warnings.length) {
|
|
164
|
+
process.stdout.write(` Warnings (${result.warnings.length}):\n`);
|
|
165
|
+
for (const w of result.warnings) process.stdout.write(` - ${w}\n`);
|
|
166
|
+
}
|
|
167
|
+
process.stdout.write(result.ok ? ' Result: PASS\n' : ' Result: FAIL\n');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!result.ok) process.exit(1);
|
|
171
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/artifact-reviewers.mjs — Required reviewer resolution from manifest + agent log.
|
|
3
|
+
*
|
|
4
|
+
* Shared by artifact release gate, contract postconditions, and Oracle read-model.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { getArtifactEntry } from './artifact-manifest.mjs';
|
|
10
|
+
import { inferArtifactTypeFromPath } from './artifact-type-from-path.mjs';
|
|
11
|
+
|
|
12
|
+
const RELEASE_GATE_BYPASS_FIELD = 'cx_release_gate';
|
|
13
|
+
const RELEASE_GATE_REASON_FIELD = 'cx_release_gate_reason';
|
|
14
|
+
|
|
15
|
+
export function readAgentLogReviewers(projectRoot = process.cwd()) {
|
|
16
|
+
const logPath = path.join(projectRoot, '.cx', 'agent-log.jsonl');
|
|
17
|
+
if (!fs.existsSync(logPath)) return new Set();
|
|
18
|
+
const reviewers = new Set();
|
|
19
|
+
for (const line of fs.readFileSync(logPath, 'utf8').split('\n')) {
|
|
20
|
+
if (!line.trim()) continue;
|
|
21
|
+
try {
|
|
22
|
+
const row = JSON.parse(line);
|
|
23
|
+
const id = row.agent || row.specialist;
|
|
24
|
+
if (id) reviewers.add(String(id));
|
|
25
|
+
} catch { /* skip */ }
|
|
26
|
+
}
|
|
27
|
+
return reviewers;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function parseReleaseGateFrontmatter(filePath) {
|
|
31
|
+
try {
|
|
32
|
+
const head = fs.readFileSync(filePath, 'utf8').slice(0, 4096);
|
|
33
|
+
const match = head.match(/^---\n([\s\S]*?)\n---/);
|
|
34
|
+
if (!match) return { bypass: false, reason: null };
|
|
35
|
+
const fm = {};
|
|
36
|
+
for (const line of match[1].split('\n')) {
|
|
37
|
+
const m = line.match(/^([\w-]+)\s*:\s*(.+)$/);
|
|
38
|
+
if (m) fm[m[1]] = m[2].trim().replace(/^['"]|['"]$/g, '');
|
|
39
|
+
}
|
|
40
|
+
const bypass = fm[RELEASE_GATE_BYPASS_FIELD] === 'bypass';
|
|
41
|
+
return { bypass, reason: fm[RELEASE_GATE_REASON_FIELD] ?? null };
|
|
42
|
+
} catch {
|
|
43
|
+
return { bypass: false, reason: null };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function resolveRequiredReviewers({ docType, filePath, rootDir, cwd = process.cwd() }) {
|
|
48
|
+
let type = docType;
|
|
49
|
+
if (!type && filePath) {
|
|
50
|
+
type = inferArtifactTypeFromPath(filePath, { rootDir: cwd });
|
|
51
|
+
}
|
|
52
|
+
if (!type) return [];
|
|
53
|
+
const entry = getArtifactEntry(type, { rootDir });
|
|
54
|
+
return entry?.releaseGate?.requiredReviewers ?? [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function missingRequiredReviewers({
|
|
58
|
+
docType,
|
|
59
|
+
filePath,
|
|
60
|
+
rootDir,
|
|
61
|
+
cwd = process.cwd(),
|
|
62
|
+
reviewersSeen,
|
|
63
|
+
} = {}) {
|
|
64
|
+
const required = resolveRequiredReviewers({ docType, filePath, rootDir, cwd });
|
|
65
|
+
if (required.length === 0) return [];
|
|
66
|
+
const seen = reviewersSeen ?? readAgentLogReviewers(cwd);
|
|
67
|
+
return required.filter((r) => !seen.has(r) && !seen.has(r.replace(/^cx-/, 'cx-')));
|
|
68
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/artifact-type-from-path.mjs — Infer manifest doc type from artifact file paths.
|
|
3
|
+
*
|
|
4
|
+
* Resolves a manifest doc type when --type is omitted.
|
|
5
|
+
* Resolution order: YAML frontmatter cx_doc_type → directory heuristics → filename hints.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { artifactTypes } from './artifact-manifest.mjs';
|
|
11
|
+
|
|
12
|
+
const KNOWN = new Set(artifactTypes());
|
|
13
|
+
|
|
14
|
+
function readFrontmatterType(filePath) {
|
|
15
|
+
try {
|
|
16
|
+
const head = fs.readFileSync(filePath, 'utf8').slice(0, 4096);
|
|
17
|
+
const match = head.match(/^---\n([\s\S]*?)\n---/);
|
|
18
|
+
if (!match) return null;
|
|
19
|
+
for (const line of match[1].split('\n')) {
|
|
20
|
+
const m = line.match(/^(?:cx_doc_type|artifactType|doc_type)\s*:\s*(.+)$/);
|
|
21
|
+
if (m) {
|
|
22
|
+
const value = m[1].trim().replace(/^['"]|['"]$/g, '');
|
|
23
|
+
return KNOWN.has(value) ? value : null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
} catch { /* unreadable */ }
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function inferPrdSubtype(rel, base) {
|
|
31
|
+
const blob = `${rel} ${base}`.toLowerCase();
|
|
32
|
+
if (/meta[\s-]?prd/.test(blob)) return 'meta-prd';
|
|
33
|
+
if (/platform/.test(blob)) return 'prd-platform';
|
|
34
|
+
if (/business/.test(blob)) return 'prd-business';
|
|
35
|
+
return 'prd';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function inferRfcSubtype(rel, base) {
|
|
39
|
+
const blob = `${rel} ${base}`.toLowerCase();
|
|
40
|
+
if (/platform/.test(blob)) return 'rfc-platform';
|
|
41
|
+
return 'rfc';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const PREFIX_RULES = [
|
|
45
|
+
[/^docs\/prd\//, inferPrdSubtype],
|
|
46
|
+
[/^docs\/adr\//, () => 'adr'],
|
|
47
|
+
[/^docs\/rfc\//, inferRfcSubtype],
|
|
48
|
+
[/^docs\/research\//, () => 'research-brief'],
|
|
49
|
+
[/^docs\/runbooks?\//, () => 'runbook'],
|
|
50
|
+
[/^\.cx\/research\//, () => 'research-brief'],
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
export function isArtifactGatePath(relPath) {
|
|
54
|
+
const rel = relPath.replace(/\\/g, '/');
|
|
55
|
+
if (!rel.endsWith('.md')) return false;
|
|
56
|
+
if (rel.startsWith('templates/docs/')) return false;
|
|
57
|
+
if (/^docs\//.test(rel) || /^\.cx\/research\//.test(rel)) return true;
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function inferArtifactTypeFromPath(filePath, { rootDir = process.cwd() } = {}) {
|
|
62
|
+
const rel = path.relative(rootDir, filePath).replace(/\\/g, '/');
|
|
63
|
+
if (!rel.endsWith('.md')) return null;
|
|
64
|
+
|
|
65
|
+
const fromFm = readFrontmatterType(filePath);
|
|
66
|
+
if (fromFm) return fromFm;
|
|
67
|
+
|
|
68
|
+
if (!isArtifactGatePath(rel)) return null;
|
|
69
|
+
|
|
70
|
+
const base = path.basename(filePath, path.extname(filePath));
|
|
71
|
+
for (const [re, resolver] of PREFIX_RULES) {
|
|
72
|
+
if (re.test(rel)) {
|
|
73
|
+
const type = typeof resolver === 'function' ? resolver(rel, base) : resolver;
|
|
74
|
+
return KNOWN.has(type) ? type : null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/artifact-workflow.mjs — intent-to-artifact workflow planning.
|
|
3
|
+
*
|
|
4
|
+
* Planning describes specialist work and manifest ordering without reporting a
|
|
5
|
+
* planned role as executed.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync } from 'node:fs';
|
|
9
|
+
import { loadProjectConfig } from './config/project-config.mjs';
|
|
10
|
+
import {
|
|
11
|
+
BRAND_CAPABLE_FORMATS,
|
|
12
|
+
artifactTypes,
|
|
13
|
+
resolveArtifactType,
|
|
14
|
+
resolveArtifactWorkflowContract,
|
|
15
|
+
} from './artifact-manifest.mjs';
|
|
16
|
+
import { inferArtifactTypeFromPath } from './artifact-type-from-path.mjs';
|
|
17
|
+
import { validateArtifactRelease } from './artifact-release-gate.mjs';
|
|
18
|
+
import { exportMarkdown } from './document-export.mjs';
|
|
19
|
+
|
|
20
|
+
function escapeRegex(value) {
|
|
21
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function findRequestedTypes(input, { rootDir, cwd }) {
|
|
25
|
+
const text = String(input ?? '').toLowerCase();
|
|
26
|
+
const matches = [];
|
|
27
|
+
for (const type of artifactTypes({ rootDir, cwd }).sort((a, b) => b.length - a.length)) {
|
|
28
|
+
const entry = resolveArtifactType(type, { rootDir, cwd }).entry;
|
|
29
|
+
for (const candidate of [type, ...(entry?.aliases ?? [])]) {
|
|
30
|
+
if (new RegExp(`(?:^|[^a-z0-9])${escapeRegex(candidate.toLowerCase())}(?:$|[^a-z0-9])`, 'i').test(text)) {
|
|
31
|
+
matches.push(type);
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return [...new Set(matches)];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function inferFormat(input, requestedFormat) {
|
|
40
|
+
if (requestedFormat) return String(requestedFormat).toLowerCase();
|
|
41
|
+
const text = String(input ?? '').toLowerCase();
|
|
42
|
+
for (const format of ['pptx', 'docx', 'pdf', 'deck', 'html', 'doc', 'rtf', 'odt', 'epub', 'tex', 'txt', 'mdx', 'md']) {
|
|
43
|
+
if (new RegExp(`\\b${format}\\b`, 'i').test(text)) return format;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function inferBranding(input, requestedBranding) {
|
|
49
|
+
if (requestedBranding) return requestedBranding;
|
|
50
|
+
return /\b(?:unbranded|plain|without (?:the )?construct brand)\b/i.test(String(input ?? '')) ? 'plain' : 'construct';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function buildSteps({ contract, input, targetFormat, branding }) {
|
|
54
|
+
const text = String(input ?? '').toLowerCase();
|
|
55
|
+
const needsResearch = Boolean(contract.researchProfile) && /\b(?:research|source|evidence|validate claims)\b/.test(text);
|
|
56
|
+
const needsReview = /\b(?:review|critique|audit|feedback)\b/.test(text) || contract.requiredReviewers.length > 0;
|
|
57
|
+
const rewrite = /\b(?:rewrite|revise|edit|polish|improve)\b/.test(text);
|
|
58
|
+
const steps = [];
|
|
59
|
+
if (needsResearch) steps.push({ id: 'research', action: 'research', roles: [], required: false, reason: `research profile: ${contract.researchProfile}` });
|
|
60
|
+
if (needsReview) steps.push({ id: 'review', action: 'review', roles: contract.reviewerChain, required: contract.requiredReviewers.length > 0, reason: 'review occurs before any rewrite' });
|
|
61
|
+
steps.push({
|
|
62
|
+
id: rewrite ? 'rewrite' : 'author',
|
|
63
|
+
action: rewrite ? 'rewrite' : 'author',
|
|
64
|
+
roles: contract.authorChain,
|
|
65
|
+
required: true,
|
|
66
|
+
reason: rewrite ? 'requested revision after review' : 'manifest author chain',
|
|
67
|
+
});
|
|
68
|
+
if (contract.validation.releaseGate !== false) {
|
|
69
|
+
steps.push({ id: 'validate', action: 'validate', roles: [], required: true, reason: 'manifest validation policy' });
|
|
70
|
+
}
|
|
71
|
+
if (targetFormat && BRAND_CAPABLE_FORMATS.includes(targetFormat) && branding === 'construct') {
|
|
72
|
+
steps.push({ id: 'brand', action: 'brand', roles: [], required: true, reason: 'default Construct branding for brand-capable format' });
|
|
73
|
+
}
|
|
74
|
+
if (targetFormat) steps.push({ id: 'export', action: 'export', roles: [], required: true, format: targetFormat, reason: 'requested distribution format' });
|
|
75
|
+
return steps;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Build an artifact plan from an ordinary request. A missing or ambiguous
|
|
80
|
+
* class is a clarification result, never an implicit PRD route.
|
|
81
|
+
*/
|
|
82
|
+
export function planArtifactWorkflow(request = {}, { cwd = process.cwd(), rootDir } = {}) {
|
|
83
|
+
const input = request.input ?? request.request ?? '';
|
|
84
|
+
const sourcePath = request.sourcePath ?? request.filePath ?? '';
|
|
85
|
+
const project = request.projectConfig ?? loadProjectConfig(cwd).config;
|
|
86
|
+
const requested = request.artifactType ?? request.documentClass ?? null;
|
|
87
|
+
const pathType = sourcePath && existsSync(sourcePath) ? inferArtifactTypeFromPath(sourcePath, { rootDir: cwd }) : null;
|
|
88
|
+
const inferredTypes = requested ? [] : findRequestedTypes(input, { rootDir, cwd });
|
|
89
|
+
const candidates = requested ? [requested] : [...new Set([pathType, ...inferredTypes].filter(Boolean))];
|
|
90
|
+
|
|
91
|
+
if (candidates.length !== 1) {
|
|
92
|
+
return {
|
|
93
|
+
kind: 'artifact-workflow',
|
|
94
|
+
status: 'needs-classification',
|
|
95
|
+
plannedSteps: [],
|
|
96
|
+
executedSteps: [],
|
|
97
|
+
skippedSteps: [],
|
|
98
|
+
producedFiles: [],
|
|
99
|
+
validation: null,
|
|
100
|
+
appliedOverrides: [],
|
|
101
|
+
clarification: candidates.length > 1
|
|
102
|
+
? `Request names multiple document classes (${candidates.join(', ')}). Specify one registered class.`
|
|
103
|
+
: 'Specify a registered document class, use a typed source path, or register the requested class in specialists/artifact-manifest.json.',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const resolution = resolveArtifactType(candidates[0], { rootDir, cwd });
|
|
108
|
+
if (resolution.status !== 'registered') {
|
|
109
|
+
return {
|
|
110
|
+
kind: 'artifact-workflow',
|
|
111
|
+
status: 'needs-classification',
|
|
112
|
+
plannedSteps: [],
|
|
113
|
+
executedSteps: [],
|
|
114
|
+
skippedSteps: [],
|
|
115
|
+
producedFiles: [],
|
|
116
|
+
validation: null,
|
|
117
|
+
appliedOverrides: [],
|
|
118
|
+
clarification: resolution.guidance,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const targetFormat = inferFormat(input, request.format ?? request.targetFormat);
|
|
123
|
+
const requestedBranding = inferBranding(input, request.branding);
|
|
124
|
+
const contract = resolveArtifactWorkflowContract(resolution.type, {
|
|
125
|
+
rootDir,
|
|
126
|
+
cwd,
|
|
127
|
+
projectConfig: project,
|
|
128
|
+
overrides: request.overrides,
|
|
129
|
+
});
|
|
130
|
+
const branding = request.branding ?? (requestedBranding === 'plain' ? 'plain' : (contract.outputs.branding ?? 'construct'));
|
|
131
|
+
const steps = buildSteps({ contract, input, targetFormat, branding });
|
|
132
|
+
const unavailableFormat = targetFormat && !contract.outputs.formats.includes(targetFormat);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
kind: 'artifact-workflow',
|
|
136
|
+
status: unavailableFormat ? 'needs-classification' : 'planned',
|
|
137
|
+
documentClass: contract.documentClass,
|
|
138
|
+
artifactType: contract.type,
|
|
139
|
+
sourcePath: sourcePath || null,
|
|
140
|
+
target: {
|
|
141
|
+
format: targetFormat,
|
|
142
|
+
audience: /\bcustomer(?:-facing)?\b/i.test(input) ? 'customer' : 'internal',
|
|
143
|
+
branding,
|
|
144
|
+
brandCapable: Boolean(targetFormat && BRAND_CAPABLE_FORMATS.includes(targetFormat)),
|
|
145
|
+
},
|
|
146
|
+
contract,
|
|
147
|
+
plannedSteps: steps,
|
|
148
|
+
executedSteps: [],
|
|
149
|
+
skippedSteps: [],
|
|
150
|
+
producedFiles: [],
|
|
151
|
+
validation: null,
|
|
152
|
+
appliedOverrides: contract.appliedOverrides,
|
|
153
|
+
clarification: unavailableFormat ? `${targetFormat} is not an output capability for ${contract.type}.` : null,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Produce a provenance-preserving workflow run report. Specialist authoring
|
|
159
|
+
* and review are never fabricated: Construct can plan them, but only reports
|
|
160
|
+
* them as executed when a host returns execution evidence (not available in
|
|
161
|
+
* the deterministic entry point). Validation/export are local operations and
|
|
162
|
+
* run only with the explicit durable-write approval mode.
|
|
163
|
+
*/
|
|
164
|
+
export function runArtifactWorkflow(request = {}, { cwd = process.cwd(), rootDir } = {}) {
|
|
165
|
+
const plan = planArtifactWorkflow(request, { cwd, rootDir });
|
|
166
|
+
const approvalMode = request.approvalMode ?? 'proposal-only';
|
|
167
|
+
const report = {
|
|
168
|
+
reportVersion: 1,
|
|
169
|
+
kind: 'artifact-workflow-run',
|
|
170
|
+
status: plan.status === 'planned' ? 'planned' : plan.status,
|
|
171
|
+
approval: {
|
|
172
|
+
mode: approvalMode,
|
|
173
|
+
durableWriteAllowed: approvalMode === 'allow-durable-write',
|
|
174
|
+
},
|
|
175
|
+
plan,
|
|
176
|
+
plannedSteps: plan.plannedSteps,
|
|
177
|
+
executedSteps: [],
|
|
178
|
+
skippedSteps: [],
|
|
179
|
+
producedFiles: [],
|
|
180
|
+
validation: null,
|
|
181
|
+
appliedOverrides: plan.appliedOverrides,
|
|
182
|
+
};
|
|
183
|
+
if (plan.status !== 'planned') return report;
|
|
184
|
+
|
|
185
|
+
const pendingReason = approvalMode === 'allow-durable-write'
|
|
186
|
+
? 'specialist execution is host-owned; this local command has no specialist execution evidence'
|
|
187
|
+
: `approval mode '${approvalMode}' permits planning only`;
|
|
188
|
+
for (const step of plan.plannedSteps) {
|
|
189
|
+
if (['review', 'rewrite', 'author', 'research'].includes(step.action)) {
|
|
190
|
+
report.skippedSteps.push({ id: step.id, reason: pendingReason });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (approvalMode !== 'allow-durable-write') {
|
|
194
|
+
for (const step of plan.plannedSteps) {
|
|
195
|
+
if (!['review', 'rewrite', 'author', 'research'].includes(step.action)) {
|
|
196
|
+
report.skippedSteps.push({ id: step.id, reason: pendingReason });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return report;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const sourcePath = plan.sourcePath;
|
|
203
|
+
for (const step of plan.plannedSteps) {
|
|
204
|
+
if (step.action === 'validate') {
|
|
205
|
+
if (!sourcePath) {
|
|
206
|
+
report.skippedSteps.push({ id: step.id, reason: 'validation needs sourcePath or filePath' });
|
|
207
|
+
} else {
|
|
208
|
+
const result = validateArtifactRelease({ filePath: sourcePath, type: plan.artifactType, cwd, rootDir });
|
|
209
|
+
report.validation = result;
|
|
210
|
+
report.executedSteps.push({ id: step.id, result: result.ok ? 'passed' : 'failed' });
|
|
211
|
+
if (!result.ok) {
|
|
212
|
+
report.status = 'validation-failed';
|
|
213
|
+
return report;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (step.action === 'brand') {
|
|
218
|
+
// Branding is applied by the export operation. Keeping this pending until
|
|
219
|
+
// output exists avoids claiming a template was applied when no export ran.
|
|
220
|
+
report.skippedSteps.push({ id: step.id, reason: 'branding is evidenced by the export result' });
|
|
221
|
+
}
|
|
222
|
+
if (step.action === 'export') {
|
|
223
|
+
if (!sourcePath) {
|
|
224
|
+
report.skippedSteps.push({ id: step.id, reason: 'export needs sourcePath or filePath' });
|
|
225
|
+
} else {
|
|
226
|
+
const result = exportMarkdown({
|
|
227
|
+
inputPath: sourcePath,
|
|
228
|
+
outputPath: request.outputPath,
|
|
229
|
+
format: step.format,
|
|
230
|
+
artifactType: plan.artifactType,
|
|
231
|
+
branding: plan.target.branding,
|
|
232
|
+
cwd,
|
|
233
|
+
rootDir,
|
|
234
|
+
});
|
|
235
|
+
if (!result.ok) {
|
|
236
|
+
report.status = 'export-failed';
|
|
237
|
+
report.skippedSteps.push({ id: step.id, reason: result.message });
|
|
238
|
+
return report;
|
|
239
|
+
}
|
|
240
|
+
report.executedSteps.push({ id: step.id, result: 'produced', format: step.format });
|
|
241
|
+
report.producedFiles.push({ path: result.outputPath, format: step.format, branding: result.branding });
|
|
242
|
+
const brandStep = report.skippedSteps.findIndex((entry) => entry.id === 'brand');
|
|
243
|
+
if (brandStep >= 0 && result.branding?.applied === 'construct') {
|
|
244
|
+
report.skippedSteps.splice(brandStep, 1);
|
|
245
|
+
report.executedSteps.push({ id: 'brand', result: 'applied', mechanism: result.branding.mechanism });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
report.status = report.executedSteps.length ? 'completed-local-steps' : 'planned';
|
|
251
|
+
return report;
|
|
252
|
+
}
|
package/lib/audit-skills.mjs
CHANGED
|
@@ -155,6 +155,10 @@ export function auditSkills({ rootDir, silent = false } = {}) {
|
|
|
155
155
|
|
|
156
156
|
export async function runAuditSkillsCli(args = []) {
|
|
157
157
|
const rootDir = args.find((a) => a.startsWith('--root='))?.split('=')[1] ?? undefined;
|
|
158
|
+
if (args.includes('--inventory')) {
|
|
159
|
+
const { runSkillInventoryAuditCli } = await import('./certification/skill-inventory.mjs');
|
|
160
|
+
return runSkillInventoryAuditCli(args.filter((a) => a !== '--inventory'), { rootDir });
|
|
161
|
+
}
|
|
158
162
|
const result = auditSkills({ rootDir });
|
|
159
163
|
if (!result.pass) process.exit(1);
|
|
160
164
|
}
|