@geraldmaron/construct 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -10
- package/bin/construct +286 -220
- package/bin/construct-postinstall.mjs +0 -21
- package/commands/work/optimize-prompts.md +1 -1
- package/examples/distribution/sources/adr.md +2 -2
- package/examples/seed-observations/decisions.md +1 -1
- package/lib/artifact-loop-core.mjs +412 -0
- package/lib/artifact-manifest.mjs +6 -0
- package/lib/artifact-release-gate.mjs +115 -49
- package/lib/audit-rules.mjs +2 -4
- package/lib/audit-skills.mjs +32 -20
- package/lib/audit-specialists.mjs +44 -26
- package/lib/auto-docs.mjs +6 -5
- package/lib/brand-prose.mjs +3 -3
- package/lib/brand-tokens.mjs +2 -2
- package/lib/certification/artifact-fixtures.mjs +4 -1
- package/lib/certification/demo-parity.mjs +6 -32
- package/lib/certification/real-llm-scenarios.mjs +58 -13
- package/lib/certification/role-cards.mjs +7 -6
- package/lib/certification/role-overlays.mjs +4 -4
- package/lib/certification/runner.mjs +9 -2
- package/lib/certification/skill-inventory.mjs +34 -22
- package/lib/certification/skill-scenarios.mjs +21 -8
- package/lib/certification/specialist-contracts.mjs +4 -3
- package/lib/certification/specialist-scenarios.mjs +7 -6
- package/lib/certification/status.mjs +5 -4
- package/lib/cli-commands.mjs +56 -57
- package/lib/comment-lint.mjs +1 -5
- package/lib/completions.mjs +20 -2
- package/lib/config/legacy-config-migration.mjs +125 -0
- package/lib/config/schema.mjs +5 -5
- package/lib/config/source-targets.mjs +80 -0
- package/lib/contracts/validate.mjs +119 -51
- package/lib/decisions/golden.mjs +3 -2
- package/lib/decisions/registry.mjs +11 -6
- package/lib/demo-project.mjs +188 -0
- package/lib/demo-recording.mjs +9 -34
- package/lib/demo-script.mjs +2 -2
- package/lib/demo-surface.mjs +13 -131
- package/lib/demo-tour-renderer.mjs +92 -0
- package/lib/demo.mjs +69 -54
- package/lib/diagram-export.mjs +63 -14
- package/lib/doctor/source-checkout.mjs +3 -4
- package/lib/doctor/watchers/consistency.mjs +90 -22
- package/lib/doctor/watchers/credential-parity.mjs +97 -0
- package/lib/doctor/watchers/mcp-protocol.mjs +1 -1
- package/lib/doctor/watchers/service-health.mjs +4 -32
- package/lib/document-export.mjs +73 -9
- package/lib/document-extract.mjs +10 -198
- package/lib/embed/artifact.mjs +2 -3
- package/lib/embed/cli.mjs +1 -1
- package/lib/embed/config.mjs +1 -2
- package/lib/embed/customer-profiles.mjs +1 -1
- package/lib/embed/daemon.mjs +5 -6
- package/lib/embed/demand-fetch.mjs +114 -2
- package/lib/embed/docs-lifecycle.mjs +9 -5
- package/lib/embed/intake-metrics.mjs +2 -2
- package/lib/embed/notifications.mjs +2 -3
- package/lib/embed/recommendation-store.mjs +25 -22
- package/lib/embed/role-framing.mjs +4 -9
- package/lib/embedded-contract/capability.mjs +4 -2
- package/lib/embedded-contract/contract-version.mjs +1 -1
- package/lib/env-config.mjs +2 -2
- package/lib/flavors/loader.mjs +21 -19
- package/lib/graph/build-from-registry.mjs +31 -7
- package/lib/graph/staleness.mjs +1 -1
- package/lib/headhunt.mjs +82 -35
- package/lib/hooks/agent-tracker.mjs +2 -2
- package/lib/hooks/config-protection.mjs +1 -1
- package/lib/hooks/registry-sync.mjs +3 -3
- package/lib/host-capabilities.mjs +5 -5
- package/lib/improvement/controller.mjs +2 -2
- package/lib/init-unified.mjs +19 -21
- package/lib/intake/classify.mjs +26 -0
- package/lib/intake/daemon.mjs +2 -3
- package/lib/intake/prepare.mjs +10 -3
- package/lib/intake/session-prelude.mjs +1 -1
- package/lib/intake/tables/creative.mjs +9 -9
- package/lib/intake/tables/operations.mjs +4 -4
- package/lib/intake/tables/research.mjs +2 -2
- package/lib/integrations/intake-integrations.mjs +9 -10
- package/lib/knowledge/research-store.mjs +2 -2
- package/lib/mcp/server.mjs +104 -61
- package/lib/mcp/tool-budget.mjs +1 -46
- package/lib/mcp/tool-recovery.mjs +44 -0
- package/lib/mcp/tools/artifact-author.mjs +36 -0
- package/lib/mcp/tools/find-tool.mjs +86 -0
- package/lib/mcp/tools/orchestration-run.mjs +107 -69
- package/lib/mcp/tools/project.mjs +4 -3
- package/lib/mcp/tools/{profile.mjs → scope.mjs} +54 -52
- package/lib/mcp/tools/skills.mjs +39 -22
- package/lib/mcp/tools/telemetry.mjs +2 -1
- package/lib/mcp/tools/workflow.mjs +1 -1
- package/lib/mcp-platform-config.mjs +7 -5
- package/lib/migrations/index.mjs +4 -2
- package/lib/migrations/v2-unified-registry.mjs +35 -0
- package/lib/model-router.mjs +203 -36
- package/lib/models/catalog.mjs +25 -9
- package/lib/models/execution-capability-profile.mjs +3 -3
- package/lib/models/execution-policy.mjs +7 -6
- package/lib/models/provider-poll.mjs +18 -4
- package/lib/opencode-config.mjs +56 -1
- package/lib/opencode-runtime-plugin.mjs +13 -10
- package/lib/oracle/artifact-gate.mjs +14 -4
- package/lib/oracle/cli.mjs +2 -0
- package/lib/oracle/dispatch.mjs +18 -1
- package/lib/oracle/execute.mjs +39 -7
- package/lib/oracle/index.mjs +1 -1
- package/lib/oracle/org-graph.mjs +10 -5
- package/lib/oracle/policy.mjs +6 -0
- package/lib/oracle/read-model.mjs +103 -6
- package/lib/oracle/reconcile.mjs +31 -4
- package/lib/oracle/remediation-dispatch.mjs +53 -0
- package/lib/oracle/routing.mjs +5 -0
- package/lib/oracle/synthesize.mjs +80 -3
- package/lib/orchestration/routing-tables.mjs +44 -10
- package/lib/orchestration/runtime.mjs +2 -0
- package/lib/orchestration/worker.mjs +1 -1
- package/lib/orchestration-policy.mjs +351 -62
- package/lib/overrides/resolver.mjs +1 -12
- package/lib/parity.mjs +37 -11
- package/lib/policy/engine.mjs +80 -15
- package/lib/prompt-composer.js +51 -15
- package/lib/prompt-metadata.mjs +3 -2
- package/lib/providers/connection-probe.mjs +58 -0
- package/lib/providers/copilot-auth.mjs +1 -1
- package/lib/providers/credential-bootstrap.mjs +1 -1
- package/lib/providers/op-run.mjs +3 -4
- package/lib/providers/secret-resolver.mjs +31 -0
- package/lib/publish-tooling.mjs +3 -11
- package/lib/publish.mjs +12 -29
- package/lib/reconcile/mcp-entry-reconcile.mjs +14 -9
- package/lib/reflect.mjs +2 -2
- package/lib/registry/agent-manifest.mjs +190 -0
- package/lib/registry/assemble.mjs +133 -0
- package/lib/registry/catalog.mjs +171 -0
- package/lib/registry/cli.mjs +590 -6
- package/lib/registry/consolidation.mjs +5 -4
- package/lib/registry/docs-sync.mjs +67 -0
- package/lib/registry/loader.mjs +147 -0
- package/lib/registry/org-io.mjs +76 -0
- package/lib/registry/retired-paths.mjs +71 -0
- package/lib/registry/surface-map.mjs +5 -7
- package/lib/registry/validator.mjs +438 -0
- package/lib/research-execution-policy.mjs +173 -0
- package/lib/roles/catalog.mjs +4 -9
- package/lib/roles/fence.mjs +107 -1
- package/lib/roles/flavor-bindings.mjs +68 -0
- package/lib/roles/gateway.mjs +140 -29
- package/lib/roles/manifest.mjs +22 -13
- package/lib/roles/router.mjs +1 -1
- package/lib/runtime-env.mjs +1 -1
- package/lib/scopes/enrich.mjs +67 -0
- package/lib/scopes/hooks.mjs +12 -0
- package/lib/{profiles → scopes}/lifecycle.mjs +70 -70
- package/lib/scopes/loader.mjs +104 -0
- package/lib/scopes/rebrand.mjs +32 -0
- package/lib/scopes/research-profile.mjs +16 -0
- package/lib/scopes/teams.mjs +98 -0
- package/lib/service-manager.mjs +1 -117
- package/lib/setup-prompts.mjs +1 -1
- package/lib/setup.mjs +17 -28
- package/lib/skills/composition-graph.mjs +98 -0
- package/lib/skills/router.mjs +80 -0
- package/lib/specialist-contracts.mjs +17 -18
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/specialists/prompt-schema.mjs +6 -5
- package/lib/specialists/roster.mjs +7 -12
- package/lib/specialists/schema.mjs +9 -6
- package/lib/status.mjs +25 -90
- package/lib/storage/file-lock.mjs +6 -3
- package/lib/telemetry/skill-calls.mjs +1 -1
- package/lib/templates/doc-presentation.mjs +63 -0
- package/lib/templates/visual-requirements.mjs +35 -2
- package/lib/term-format.mjs +107 -2
- package/lib/test-env-setup.mjs +21 -0
- package/lib/ui/components.mjs +42 -0
- package/lib/ui/glyphs.mjs +58 -0
- package/lib/ui/links.mjs +115 -0
- package/lib/ui/theme.mjs +108 -0
- package/lib/uninstall/uninstall.mjs +2 -1
- package/lib/validator.mjs +45 -8
- package/lib/validators/skill-effectiveness.mjs +174 -0
- package/lib/validators/skills.mjs +1 -1
- package/package.json +12 -7
- package/personas/construct.md +12 -3
- package/platforms/claude/CLAUDE.md +1 -1
- package/rules/common/beads-hygiene.md +52 -0
- package/rules/common/neurodivergent-output.md +4 -7
- package/rules/common/research.md +2 -0
- package/scripts/sync-specialists.mjs +260 -49
- package/skills/ai/prompt-optimizer.md +3 -3
- package/skills/brand/output-vibe.md +48 -0
- package/skills/docs/adr-workflow.md +3 -0
- package/skills/docs/backlog-proposal-workflow.md +3 -0
- package/skills/docs/codebase-research-workflow.md +5 -2
- package/skills/docs/customer-profile-workflow.md +3 -0
- package/skills/docs/document-ingest-workflow.md +3 -0
- package/skills/docs/evidence-ingest-workflow.md +4 -1
- package/skills/docs/init-project.md +1 -1
- package/skills/docs/prd-workflow.md +1 -1
- package/skills/docs/prfaq-workflow.md +3 -0
- package/skills/docs/product-intelligence-workflow.md +4 -1
- package/skills/docs/product-signal-workflow.md +3 -0
- package/skills/docs/research-workflow.md +13 -6
- package/skills/docs/runbook-workflow.md +3 -0
- package/skills/docs/strategy-workflow.md +3 -0
- package/skills/docs/user-research-workflow.md +6 -3
- package/skills/operating/orchestration-reference.md +1 -1
- package/skills/roles/{engineer.ai.md → ai-engineer.md} +2 -2
- package/skills/roles/architect.ai-systems.md +1 -1
- package/skills/roles/architect.data.md +1 -1
- package/skills/roles/architect.enterprise.md +1 -1
- package/skills/roles/architect.integration.md +1 -1
- package/skills/roles/architect.md +1 -1
- package/skills/roles/architect.platform.md +1 -1
- package/skills/roles/{product-manager.business-strategy.md → business-strategist.md} +3 -3
- package/skills/roles/data-analyst.experiment.md +1 -1
- package/skills/roles/data-analyst.md +1 -1
- package/skills/roles/data-analyst.product-intelligence.md +1 -1
- package/skills/roles/data-analyst.product.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/{engineer.data.md → data-engineer.md} +2 -2
- package/skills/roles/data-engineer.pipeline.md +2 -2
- package/skills/roles/data-engineer.vector-retrieval.md +2 -2
- package/skills/roles/data-engineer.warehouse.md +2 -2
- package/skills/roles/debugger.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +1 -1
- package/skills/roles/{reviewer.devil-advocate.md → devil-advocate.md} +2 -2
- package/skills/roles/{operator.docs.md → docs-keeper.md} +3 -3
- package/skills/roles/engineer.md +3 -7
- package/skills/roles/{reviewer.evaluator.md → evaluator.md} +2 -2
- package/skills/roles/{researcher.explorer.md → explorer.md} +2 -2
- package/skills/roles/{operator.md → operations.md} +2 -5
- package/skills/roles/orchestrator.md +1 -1
- package/skills/roles/{engineer.platform.md → platform-engineer.md} +2 -2
- package/skills/roles/product-manager.ai-product.md +1 -1
- package/skills/roles/product-manager.enterprise.md +1 -1
- package/skills/roles/product-manager.growth.md +1 -2
- package/skills/roles/product-manager.md +1 -2
- package/skills/roles/product-manager.platform.md +1 -1
- package/skills/roles/product-manager.product.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -1
- package/skills/roles/qa.api-contract.md +1 -1
- package/skills/roles/qa.data-pipeline.md +1 -1
- package/skills/roles/qa.md +1 -1
- package/skills/roles/qa.web-ui.md +1 -1
- package/skills/roles/{operator.release.md → release-manager.md} +3 -3
- package/skills/roles/researcher.md +1 -1
- package/skills/roles/reviewer.md +1 -1
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.appsec.md +1 -1
- package/skills/roles/security.cloud.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +1 -1
- package/skills/roles/security.privacy.md +1 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/{operator.sre.md → sre.md} +3 -3
- package/skills/roles/{qa.test-automation.md → test-automation.md} +2 -2
- package/skills/roles/{reviewer.trace.md → trace-reviewer.md} +2 -2
- package/skills/roles/{researcher.ux.md → ux-researcher.md} +2 -2
- package/skills/routing.json +18 -0
- package/skills/routing.md +1 -1
- package/specialists/artifact-manifest.json +2 -1
- package/specialists/audit-enrichments.json +14 -14
- package/specialists/org/contracts/accessibility-to-qa.json +37 -0
- package/specialists/org/contracts/any-to-business-strategist.json +57 -0
- package/specialists/org/contracts/any-to-debugger.json +38 -0
- package/specialists/org/contracts/any-to-designer.json +33 -0
- package/specialists/org/contracts/any-to-docs-keeper.json +34 -0
- package/specialists/org/contracts/any-to-explorer.json +39 -0
- package/specialists/org/contracts/any-to-sre-incident.json +33 -0
- package/specialists/org/contracts/any-to-trace-reviewer.json +32 -0
- package/specialists/org/contracts/architect-to-ai-engineer.json +32 -0
- package/specialists/org/contracts/architect-to-data-engineer.json +52 -0
- package/specialists/org/contracts/architect-to-devil-advocate.json +38 -0
- package/specialists/org/contracts/architect-to-engineer.json +34 -0
- package/specialists/org/contracts/architect-to-evaluator.json +59 -0
- package/specialists/org/contracts/architect-to-legal-compliance.json +55 -0
- package/specialists/org/contracts/architect-to-operations.json +45 -0
- package/specialists/org/contracts/architect-to-platform-engineer.json +42 -0
- package/specialists/org/contracts/business-strategist-to-product-manager.json +39 -0
- package/specialists/org/contracts/construct-to-orchestrator.json +36 -0
- package/specialists/org/contracts/construct-to-rd-lead.json +53 -0
- package/specialists/org/contracts/data-analyst-to-product-manager.json +43 -0
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +36 -0
- package/specialists/org/contracts/designer-to-accessibility.json +36 -0
- package/specialists/org/contracts/engineer-to-qa.json +34 -0
- package/specialists/org/contracts/engineer-to-reviewer.json +52 -0
- package/specialists/org/contracts/explorer-to-engineer.json +38 -0
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +43 -0
- package/specialists/org/contracts/platform-engineer-to-engineer.json +31 -0
- package/specialists/org/contracts/product-manager-to-architect.json +71 -0
- package/specialists/org/contracts/product-manager-to-data-analyst.json +49 -0
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +51 -0
- package/specialists/org/contracts/qa-to-release-manager.json +42 -0
- package/specialists/org/contracts/qa-to-test-automation.json +42 -0
- package/specialists/org/contracts/rd-lead-to-architect.json +38 -0
- package/specialists/org/contracts/researcher-to-architect.json +40 -0
- package/specialists/org/contracts/researcher-to-product-manager.json +57 -0
- package/specialists/org/contracts/reviewer-to-security.json +41 -0
- package/specialists/org/contracts/sre-to-release-manager.json +36 -0
- package/specialists/org/contracts/test-automation-to-engineer.json +34 -0
- package/specialists/org/contracts/trace-reviewer-to-sre.json +41 -0
- package/specialists/org/contracts/user-to-construct.json +30 -0
- package/specialists/org/groups/engineering-group.json +43 -0
- package/specialists/org/groups/governance-group.json +38 -0
- package/specialists/org/groups/operations-group.json +41 -0
- package/specialists/org/groups/product-group.json +46 -0
- package/specialists/org/groups/quality-group.json +42 -0
- package/specialists/org/groups/strategy-group.json +41 -0
- package/specialists/org/policies/action-approval.json +13 -0
- package/specialists/org/policies/agents-routing.json +13 -0
- package/specialists/org/policies/architecture.json +20 -0
- package/specialists/org/policies/bash-safety.json +13 -0
- package/specialists/org/policies/beads-hygiene.json +13 -0
- package/specialists/org/policies/bootstrap-state.json +13 -0
- package/specialists/org/policies/code-review.json +13 -0
- package/specialists/org/policies/coding-style.json +13 -0
- package/specialists/org/policies/comments.json +13 -0
- package/specialists/org/policies/commit-approval.json +13 -0
- package/specialists/org/policies/contract-preconditions.json +13 -0
- package/specialists/org/policies/deployment.json +20 -0
- package/specialists/org/policies/description.json +14 -0
- package/specialists/org/policies/design-approval.json +19 -0
- package/specialists/org/policies/doc-ownership.json +13 -0
- package/specialists/org/policies/file-path-fence.json +13 -0
- package/specialists/org/policies/framing.json +13 -0
- package/specialists/org/policies/git-workflow.json +13 -0
- package/specialists/org/policies/incident-response.json +15 -0
- package/specialists/org/policies/intake-triage.json +15 -0
- package/specialists/org/policies/neurodivergent-output.json +13 -0
- package/specialists/org/policies/no-fabrication.json +13 -0
- package/specialists/org/policies/patterns.json +13 -0
- package/specialists/org/policies/quality-gate-approval.json +17 -0
- package/specialists/org/policies/release-gates.json +13 -0
- package/specialists/org/policies/research-evidence.json +13 -0
- package/specialists/org/policies/review-before-change.json +13 -0
- package/specialists/org/policies/rollback.json +15 -0
- package/specialists/org/policies/scope-change.json +20 -0
- package/specialists/org/policies/secret-scan.json +13 -0
- package/specialists/org/policies/security-approval.json +17 -0
- package/specialists/org/policies/security.json +13 -0
- package/specialists/org/policies/session-efficiency.json +13 -0
- package/specialists/org/policies/skill-routing.json +13 -0
- package/specialists/org/policies/strategic-prioritization.json +17 -0
- package/specialists/org/policies/testing.json +13 -0
- package/specialists/org/policies/tool-invisibility.json +14 -0
- package/specialists/org/scopes/creative.json +54 -0
- package/specialists/org/scopes/operations.json +51 -0
- package/specialists/org/scopes/research.json +60 -0
- package/specialists/org/scopes/rnd.json +81 -0
- package/specialists/org/specialists/cx-accessibility.json +69 -0
- package/specialists/org/specialists/cx-ai-engineer.json +75 -0
- package/specialists/org/specialists/cx-architect.json +89 -0
- package/specialists/org/specialists/cx-business-strategist.json +72 -0
- package/specialists/org/specialists/cx-data-analyst.json +68 -0
- package/specialists/org/specialists/cx-data-engineer.json +71 -0
- package/specialists/org/specialists/cx-debugger.json +75 -0
- package/specialists/org/specialists/cx-designer.json +85 -0
- package/specialists/org/specialists/cx-devil-advocate.json +71 -0
- package/specialists/org/specialists/cx-docs-keeper.json +82 -0
- package/specialists/org/specialists/cx-engineer.json +106 -0
- package/specialists/org/specialists/cx-evaluator.json +69 -0
- package/specialists/org/specialists/cx-explorer.json +69 -0
- package/specialists/org/specialists/cx-legal-compliance.json +74 -0
- package/specialists/org/specialists/cx-operations.json +72 -0
- package/specialists/org/specialists/cx-oracle.json +46 -0
- package/specialists/org/specialists/cx-orchestrator.json +81 -0
- package/specialists/org/specialists/cx-platform-engineer.json +80 -0
- package/specialists/org/specialists/cx-product-manager.json +102 -0
- package/specialists/org/specialists/cx-qa.json +79 -0
- package/specialists/org/specialists/cx-rd-lead.json +73 -0
- package/specialists/org/specialists/cx-release-manager.json +77 -0
- package/specialists/org/specialists/cx-researcher.json +82 -0
- package/specialists/org/specialists/cx-reviewer.json +71 -0
- package/specialists/org/specialists/cx-security.json +91 -0
- package/specialists/org/specialists/cx-sre.json +94 -0
- package/specialists/org/specialists/cx-test-automation.json +69 -0
- package/specialists/org/specialists/cx-trace-reviewer.json +69 -0
- package/specialists/org/specialists/cx-ux-researcher.json +68 -0
- package/specialists/org/teams/accessibility-team.json +39 -0
- package/specialists/org/teams/design-team.json +40 -0
- package/specialists/org/teams/engineering-team.json +50 -0
- package/specialists/org/teams/governance-team.json +41 -0
- package/specialists/org/teams/operations-team.json +46 -0
- package/specialists/org/teams/product-management-team.json +45 -0
- package/specialists/org/teams/quality-team.json +49 -0
- package/specialists/org/teams/research-team.json +39 -0
- package/specialists/org/teams/strategy-team.json +48 -0
- package/specialists/org/teams/ux-research-team.json +39 -0
- package/specialists/prompts/_shared/validation-contract.md +1 -1
- package/specialists/prompts/_team-template.md +10 -0
- package/specialists/prompts/cx-accessibility.md +1 -0
- package/specialists/prompts/cx-ai-engineer.md +1 -1
- package/specialists/prompts/cx-business-strategist.md +1 -1
- package/specialists/prompts/cx-data-engineer.md +1 -1
- package/specialists/prompts/cx-designer.md +1 -0
- package/specialists/prompts/cx-devil-advocate.md +1 -1
- package/specialists/prompts/cx-docs-keeper.md +1 -1
- package/specialists/prompts/cx-evaluator.md +1 -1
- package/specialists/prompts/cx-explorer.md +1 -1
- package/specialists/prompts/cx-operations.md +1 -1
- package/specialists/prompts/cx-oracle.md +7 -3
- package/specialists/prompts/cx-orchestrator.md +17 -1
- package/specialists/prompts/cx-platform-engineer.md +1 -1
- package/specialists/prompts/cx-product-manager.md +2 -0
- package/specialists/prompts/cx-release-manager.md +1 -1
- package/specialists/prompts/cx-researcher.md +7 -4
- package/specialists/prompts/cx-sre.md +1 -1
- package/specialists/prompts/cx-test-automation.md +2 -2
- package/specialists/prompts/cx-trace-reviewer.md +3 -3
- package/specialists/prompts/cx-ux-researcher.md +2 -1
- package/templates/demos/scripts/architecture-review-adr.json +30 -0
- package/templates/demos/scripts/capability-contract.json +25 -0
- package/templates/demos/scripts/intake-triage.json +25 -0
- package/templates/demos/scripts/profile-doctor-health.json +25 -0
- package/templates/demos/tapes/agentic-platforms-prd.tape +2 -2
- package/templates/demos/tapes/architecture-review-adr.tape +44 -0
- package/templates/demos/tapes/capability-contract.tape +39 -0
- package/templates/demos/tapes/intake-triage.tape +39 -0
- package/templates/demos/tapes/profile-doctor-health.tape +39 -0
- package/templates/distribution/construct-brand.typ +23 -18
- package/templates/distribution/construct-decision.typ +1 -1
- package/templates/distribution/construct-pdf.typ +1 -1
- package/templates/distribution/construct-prd.typ +1 -1
- package/templates/distribution/construct-research.typ +1 -1
- package/templates/distribution/mermaid-puppeteer.json +8 -0
- package/templates/docs/persona-artifact.md +1 -1
- package/templates/docs/prd.md +6 -0
- package/lib/boundary.mjs +0 -127
- package/lib/certification/dashboard-api.mjs +0 -71
- package/lib/chat/cli.mjs +0 -333
- package/lib/chat/command-suggest.mjs +0 -161
- package/lib/chat/commands.mjs +0 -215
- package/lib/chat/config.mjs +0 -142
- package/lib/chat/context-compactor.mjs +0 -250
- package/lib/chat/context-continuation.mjs +0 -253
- package/lib/chat/continuation-source.mjs +0 -58
- package/lib/chat/demo-guide.mjs +0 -61
- package/lib/chat/design-tokens.mjs +0 -91
- package/lib/chat/desktop-binary.mjs +0 -81
- package/lib/chat/desktop-build.mjs +0 -130
- package/lib/chat/desktop-launcher.mjs +0 -133
- package/lib/chat/evidence.mjs +0 -145
- package/lib/chat/export.mjs +0 -74
- package/lib/chat/harness/driver.mjs +0 -91
- package/lib/chat/list-picker.mjs +0 -112
- package/lib/chat/model-picker.mjs +0 -356
- package/lib/chat/openrouter-fallback.mjs +0 -151
- package/lib/chat/permission-prompt.mjs +0 -33
- package/lib/chat/picker-catalog.mjs +0 -45
- package/lib/chat/policy-telemetry.mjs +0 -34
- package/lib/chat/present.mjs +0 -246
- package/lib/chat/session-context.mjs +0 -39
- package/lib/chat/session-persist.mjs +0 -73
- package/lib/chat/session-restore.mjs +0 -71
- package/lib/chat/session-settings.mjs +0 -53
- package/lib/chat/system-prompt.mjs +0 -52
- package/lib/chat/transparency.mjs +0 -93
- package/lib/chat/tui/color-scheme.mjs +0 -42
- package/lib/chat/tui/markdown.mjs +0 -123
- package/lib/chat/tui/presentation.mjs +0 -100
- package/lib/chat/tui/render.mjs +0 -500
- package/lib/chat/tui/turn-block.mjs +0 -284
- package/lib/chat/tui/turn-present.mjs +0 -18
- package/lib/chat/tui/turn-state.mjs +0 -88
- package/lib/chat/tui/usage.mjs +0 -122
- package/lib/chat/web-commands.mjs +0 -146
- package/lib/chat/web-launcher.mjs +0 -63
- package/lib/chat/web-picker-keys.mjs +0 -46
- package/lib/chat/web-session.mjs +0 -159
- package/lib/config/alias.mjs +0 -57
- package/lib/dashboard-demo.mjs +0 -71
- package/lib/dashboard-static.mjs +0 -175
- package/lib/install/desktop-binary-download.mjs +0 -88
- package/lib/profiles/loader.mjs +0 -123
- package/lib/profiles/rebrand.mjs +0 -46
- package/lib/server/auth.mjs +0 -169
- package/lib/server/chat-loop.mjs +0 -622
- package/lib/server/chat.mjs +0 -336
- package/lib/server/cors.mjs +0 -77
- package/lib/server/csrf.mjs +0 -103
- package/lib/server/demo-preview.mjs +0 -63
- package/lib/server/index.mjs +0 -2641
- package/lib/server/insights.mjs +0 -780
- package/lib/server/langfuse-login.mjs +0 -58
- package/lib/server/rate-limit.mjs +0 -93
- package/lib/server/telemetry-login.mjs +0 -100
- package/lib/server/webhook.mjs +0 -512
- package/specialists/contracts.json +0 -1032
- package/specialists/contracts.schema.json +0 -83
- package/specialists/policy-inventory.json +0 -188
- package/specialists/registry.json +0 -1412
- package/specialists/role-manifests.json +0 -217
- package/specialists/teams.json +0 -94
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/specialists/schema.mjs — validation contract for specialists/
|
|
2
|
+
* lib/specialists/schema.mjs — validation contract for specialists/org entries.
|
|
3
3
|
*
|
|
4
4
|
* Catches drift before it ships: missing description, unknown tool names,
|
|
5
5
|
* promptFile pointing at a path that does not resolve, unrecognized
|
|
@@ -23,7 +23,7 @@ export const ALLOWED_TOOLS = new Set([
|
|
|
23
23
|
'WebSearch', 'WebFetch', 'TodoWrite', 'NotebookEdit', 'Task',
|
|
24
24
|
// Construct MCP tools (from construct-mcp server)
|
|
25
25
|
'list_skills', 'get_skill', 'search_skills',
|
|
26
|
-
'list_teams', '
|
|
26
|
+
'list_teams', 'suggest_skills',
|
|
27
27
|
'list_templates', 'get_template',
|
|
28
28
|
'agent_contract', 'orchestration_policy',
|
|
29
29
|
'cx_trace', 'cx_score',
|
|
@@ -90,13 +90,16 @@ export function validateAgentRecord(record, { rootDir, registryKey } = {}) {
|
|
|
90
90
|
* Validate every agent in a registry. Returns { errors: string[], agentCount }.
|
|
91
91
|
*/
|
|
92
92
|
export function validateRegistry(registry, { rootDir } = {}) {
|
|
93
|
-
if (!registry
|
|
93
|
+
if (!registry) {
|
|
94
94
|
return { errors: ['registry.specialists is missing or not an array'], agentCount: 0 };
|
|
95
95
|
}
|
|
96
|
+
const specialists = Array.isArray(registry.specialists)
|
|
97
|
+
? registry.specialists
|
|
98
|
+
: Object.values(registry.specialists || {});
|
|
96
99
|
const errors = [];
|
|
97
100
|
const seenNames = new Set();
|
|
98
|
-
for (let i = 0; i <
|
|
99
|
-
const record =
|
|
101
|
+
for (let i = 0; i < specialists.length; i += 1) {
|
|
102
|
+
const record = specialists[i];
|
|
100
103
|
const recordErrors = validateAgentRecord(record, { rootDir, registryKey: i });
|
|
101
104
|
errors.push(...recordErrors);
|
|
102
105
|
if (record?.name) {
|
|
@@ -104,7 +107,7 @@ export function validateRegistry(registry, { rootDir } = {}) {
|
|
|
104
107
|
seenNames.add(record.name);
|
|
105
108
|
}
|
|
106
109
|
}
|
|
107
|
-
return { errors, agentCount:
|
|
110
|
+
return { errors, agentCount: specialists.length };
|
|
108
111
|
}
|
|
109
112
|
|
|
110
113
|
/**
|
package/lib/status.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/status.mjs — Project health summary for the Construct
|
|
2
|
+
* lib/status.mjs — Project health summary for the Construct CLI.
|
|
3
3
|
*
|
|
4
4
|
* Reads workflow state, plan.md, tracker config, and MCP surface to produce a
|
|
5
5
|
* structured health object. Called by `construct status`, the MCP status tool,
|
|
6
6
|
* and the session-start bootstrap to surface blocked tasks and open questions.
|
|
7
7
|
*/
|
|
8
8
|
import { readFileSync, statSync, existsSync, readdirSync } from 'node:fs';
|
|
9
|
+
import { loadRegistry } from './registry/loader.mjs';
|
|
9
10
|
import { join, dirname } from 'node:path';
|
|
10
11
|
import { homedir } from 'node:os';
|
|
11
12
|
import { fileURLToPath } from 'node:url';
|
|
@@ -23,6 +24,8 @@ import { readEfficiencyLog, summarizeEfficiencyData } from './efficiency.mjs';
|
|
|
23
24
|
import { triggerAutoBackfillIfSparse } from './telemetry/backfill.mjs';
|
|
24
25
|
import { resolveTraceBackend, telemetryProviderLabel } from './telemetry/client.mjs';
|
|
25
26
|
import { doctorRoot } from './config/xdg.mjs';
|
|
27
|
+
import { resolveUiColors } from './ui/theme.mjs';
|
|
28
|
+
import { applyLinks, terminalLinksEnabled } from './ui/links.mjs';
|
|
26
29
|
const TOTAL_BYTES_WARNING_THRESHOLD = 750_000;
|
|
27
30
|
|
|
28
31
|
function readJSON(path) {
|
|
@@ -250,9 +253,9 @@ function parsePort(value, fallback) {
|
|
|
250
253
|
}
|
|
251
254
|
|
|
252
255
|
async function probeHttp(url, { method = 'GET', headers, body, timeout = 5000 } = {}) {
|
|
253
|
-
// 5s is generous on purpose.
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
+
// 5s is generous on purpose. A local service can batch many concurrent requests
|
|
257
|
+
// at startup, which can stall its own event loop just long enough that a 2s
|
|
258
|
+
// timeout fires on a healthy
|
|
256
259
|
// localhost service. Reachability is a yes/no signal — a hung remote dies
|
|
257
260
|
// visibly via ECONNREFUSED long before this, so the extra headroom is free.
|
|
258
261
|
const controller = new AbortController();
|
|
@@ -539,89 +542,11 @@ function traceBackendDefinition(env) {
|
|
|
539
542
|
};
|
|
540
543
|
}
|
|
541
544
|
|
|
542
|
-
|
|
543
|
-
* In-process self-check for when Construct is serving its own dashboard.
|
|
544
|
-
* Rather than skipping the probe and asserting "healthy", we actually measure
|
|
545
|
-
* the process state and surface real problems.
|
|
546
|
-
*
|
|
547
|
-
* Checks:
|
|
548
|
-
* - Heap usage — warn at 80%, critical at 95%
|
|
549
|
-
* - Process uptime — surface as context
|
|
550
|
-
* - Embed daemon — read state file and report if stale or crashed
|
|
551
|
-
*
|
|
552
|
-
* Returns { status: 'healthy'|'degraded'|'unavailable', message: string }
|
|
553
|
-
*/
|
|
554
|
-
function buildSelfCheck() {
|
|
555
|
-
const problems = [];
|
|
556
|
-
const notes = [];
|
|
557
|
-
|
|
558
|
-
// Memory
|
|
559
|
-
try {
|
|
560
|
-
const mem = process.memoryUsage();
|
|
561
|
-
const heapPct = mem.heapUsed / mem.heapTotal;
|
|
562
|
-
if (heapPct >= 0.95) {
|
|
563
|
-
problems.push(`heap ${Math.round(heapPct * 100)}% (critical)`);
|
|
564
|
-
} else if (heapPct >= 0.80) {
|
|
565
|
-
problems.push(`heap ${Math.round(heapPct * 100)}% (high)`);
|
|
566
|
-
} else {
|
|
567
|
-
notes.push(`heap ${Math.round(heapPct * 100)}%`);
|
|
568
|
-
}
|
|
569
|
-
} catch { /* non-fatal */ }
|
|
570
|
-
|
|
571
|
-
// Uptime
|
|
572
|
-
try {
|
|
573
|
-
const uptimeSec = process.uptime();
|
|
574
|
-
const h = Math.floor(uptimeSec / 3600);
|
|
575
|
-
const m = Math.floor((uptimeSec % 3600) / 60);
|
|
576
|
-
notes.push(`up ${h}h${m}m`);
|
|
577
|
-
} catch { /* non-fatal */ }
|
|
578
|
-
|
|
579
|
-
// Embed daemon state
|
|
580
|
-
try {
|
|
581
|
-
const stateFile = join(doctorRoot(), 'runtime', 'embed-daemon.json');
|
|
582
|
-
if (existsSync(stateFile)) {
|
|
583
|
-
const state = JSON.parse(readFileSync(stateFile, 'utf8'));
|
|
584
|
-
if (state.status === 'running' && state.pid) {
|
|
585
|
-
// Verify the PID is actually alive
|
|
586
|
-
try {
|
|
587
|
-
process.kill(state.pid, 0);
|
|
588
|
-
notes.push(`embed daemon pid ${state.pid}`);
|
|
589
|
-
} catch {
|
|
590
|
-
problems.push(`embed daemon pid ${state.pid} is not running (state file stale)`);
|
|
591
|
-
}
|
|
592
|
-
} else if (state.status === 'stopped') {
|
|
593
|
-
notes.push('embed daemon stopped');
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
} catch { /* non-fatal */ }
|
|
597
|
-
|
|
598
|
-
if (problems.length) {
|
|
599
|
-
return {
|
|
600
|
-
status: problems.some(p => p.includes('critical')) ? 'unavailable' : 'degraded',
|
|
601
|
-
message: problems.join('; ') + (notes.length ? ` — ${notes.join(', ')}` : ''),
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
return {
|
|
606
|
-
status: 'healthy',
|
|
607
|
-
message: ['Serving status API', ...notes].join(' — '),
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
function serviceDefinitions(env, dashboardPort, selfDashboard) {
|
|
545
|
+
function serviceDefinitions(env) {
|
|
612
546
|
const memoryPort = parsePort(env.MEMORY_PORT, 8765);
|
|
613
547
|
const bridgePort = parsePort(env.BRIDGE_PORT, 5173);
|
|
614
548
|
|
|
615
549
|
return [
|
|
616
|
-
{
|
|
617
|
-
id: 'dashboard',
|
|
618
|
-
name: 'Dashboard',
|
|
619
|
-
url: `http://127.0.0.1:${dashboardPort}`,
|
|
620
|
-
runtime: 'live',
|
|
621
|
-
note: selfDashboard ? 'Current process' : 'Dashboard API',
|
|
622
|
-
healthyMessage: selfDashboard ? 'Serving status API' : 'Reachable',
|
|
623
|
-
selfCheck: selfDashboard ? buildSelfCheck() : null,
|
|
624
|
-
},
|
|
625
550
|
traceBackendDefinition(env),
|
|
626
551
|
{
|
|
627
552
|
id: 'memory',
|
|
@@ -652,23 +577,25 @@ export async function buildStatus({
|
|
|
652
577
|
cwd = process.cwd(),
|
|
653
578
|
homeDir = homedir(),
|
|
654
579
|
env = process.env,
|
|
655
|
-
dashboardPort,
|
|
656
|
-
selfDashboard = false,
|
|
657
580
|
probeService = defaultProbeService,
|
|
658
581
|
} = {}) {
|
|
659
582
|
if (!rootDir) throw new Error('rootDir is required');
|
|
660
583
|
|
|
661
584
|
const mergedEnv = loadConstructEnv({ rootDir, homeDir, env });
|
|
662
|
-
const resolvedDashboardPort = parsePort(dashboardPort ?? mergedEnv.DASHBOARD_PORT, 4242);
|
|
663
585
|
|
|
664
586
|
const pkg = readJSON(join(rootDir, 'package.json')) ?? {};
|
|
665
|
-
|
|
587
|
+
let registry;
|
|
588
|
+
try {
|
|
589
|
+
registry = loadRegistry({ rootDir });
|
|
590
|
+
} catch {
|
|
591
|
+
registry = { teams: {}, specialists: {}, contracts: {}, policies: {} };
|
|
592
|
+
}
|
|
666
593
|
const settings = readJSON(join(homeDir, '.claude', 'settings.json')) ?? {};
|
|
667
594
|
const features = await checkAllFeatures({ homeDir, cwd, env: mergedEnv });
|
|
668
595
|
const pluginRegistry = loadPluginRegistry({ cwd, homeDir, rootDir, env: mergedEnv });
|
|
669
596
|
const services = [];
|
|
670
597
|
|
|
671
|
-
for (const definition of serviceDefinitions(mergedEnv
|
|
598
|
+
for (const definition of serviceDefinitions(mergedEnv)) {
|
|
672
599
|
if (definition.selfCheck) {
|
|
673
600
|
const check = definition.selfCheck;
|
|
674
601
|
services.push({
|
|
@@ -734,7 +661,7 @@ export async function buildStatus({
|
|
|
734
661
|
: null;
|
|
735
662
|
|
|
736
663
|
const prefix = `${registry.prefix ?? 'cx'}-`;
|
|
737
|
-
const specialists = (registry.specialists ??
|
|
664
|
+
const specialists = Object.values(registry.specialists ?? {}).map((specialist) => ({
|
|
738
665
|
name: `${prefix}${specialist.name}`,
|
|
739
666
|
description: specialist.description ?? '',
|
|
740
667
|
modelTier: specialist.modelTier ?? 'standard',
|
|
@@ -922,7 +849,15 @@ export function formatStatusReport(status) {
|
|
|
922
849
|
lines.push(` ${serviceIcon('degraded')} manifest error — ${error}`);
|
|
923
850
|
}
|
|
924
851
|
}
|
|
925
|
-
|
|
852
|
+
|
|
853
|
+
// Service URLs and repo paths become Cmd-clickable on an interactive stream; on
|
|
854
|
+
// a pipe or in CI links resolve off and colors are empty, so the report stays
|
|
855
|
+
// byte-identical for machine consumers and tests.
|
|
856
|
+
|
|
857
|
+
const colors = resolveUiColors();
|
|
858
|
+
const enabled = terminalLinksEnabled(process.env, { stream: process.stdout });
|
|
859
|
+
const rendered = lines.map((line) => applyLinks(line, colors, { enabled })).join('\n');
|
|
860
|
+
return `${rendered}\n`;
|
|
926
861
|
}
|
|
927
862
|
|
|
928
863
|
export async function printStatus(options = {}) {
|
|
@@ -31,6 +31,7 @@ const LOCK_TIMEOUT_MS = 5_000;
|
|
|
31
31
|
const POLL_INTERVAL_MS = 50;
|
|
32
32
|
|
|
33
33
|
const heldLocks = new Set();
|
|
34
|
+
const SLEEP_ARRAY = new Int32Array(new SharedArrayBuffer(4));
|
|
34
35
|
|
|
35
36
|
function isHolderAlive(holderPid) {
|
|
36
37
|
if (!holderPid || !Number.isFinite(holderPid)) return false;
|
|
@@ -77,6 +78,10 @@ function release(lockPath) {
|
|
|
77
78
|
heldLocks.delete(lockPath);
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
function sleepSync(ms) {
|
|
82
|
+
Atomics.wait(SLEEP_ARRAY, 0, 0, ms);
|
|
83
|
+
}
|
|
84
|
+
|
|
80
85
|
let exitHandlerInstalled = false;
|
|
81
86
|
function installExitHandler() {
|
|
82
87
|
if (exitHandlerInstalled) return;
|
|
@@ -124,9 +129,7 @@ export function withFileLockSync(filePath, fn) {
|
|
|
124
129
|
|
|
125
130
|
while (!tryAcquire(lockPath)) {
|
|
126
131
|
if (Date.now() >= deadline) break;
|
|
127
|
-
|
|
128
|
-
const stop = Date.now() + POLL_INTERVAL_MS;
|
|
129
|
-
while (Date.now() < stop) { /* spin */ }
|
|
132
|
+
sleepSync(POLL_INTERVAL_MS);
|
|
130
133
|
}
|
|
131
134
|
|
|
132
135
|
try {
|
|
@@ -31,7 +31,7 @@ export const DEFAULT_LOG_PATH = path.join(doctorRoot(), 'skill-calls.jsonl');
|
|
|
31
31
|
* Fire-and-forget log of a skill load event.
|
|
32
32
|
*
|
|
33
33
|
* @param {object} event
|
|
34
|
-
* @param {string} event.skillId — path-relative-to-skills/ without the .md, e.g. "roles/engineer
|
|
34
|
+
* @param {string} event.skillId — path-relative-to-skills/ without the .md, e.g. "roles/platform-engineer"
|
|
35
35
|
* @param {'mcp'|'prompt-composer'|'role-preload'|'validation'|'other'} event.source
|
|
36
36
|
* @param {string} [event.callerContext] — optional free-form context (agent name, MCP client id, etc.)
|
|
37
37
|
* @param {object} [opts]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/templates/doc-presentation.mjs — Presentation lint for Construct artifacts.
|
|
3
|
+
*
|
|
4
|
+
* Complements lintDocStructure with spacing, bullet walls, heading hierarchy,
|
|
5
|
+
* and diagram heuristics aligned with skills/brand/output-vibe.md.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
|
|
9
|
+
export function lintDocPresentation(body, { type } = {}) {
|
|
10
|
+
const errors = [];
|
|
11
|
+
const warnings = [];
|
|
12
|
+
const lines = body.split(/\r?\n/);
|
|
13
|
+
|
|
14
|
+
const h1Count = (body.match(/^#\s+/gm) || []).length;
|
|
15
|
+
if (h1Count > 1) errors.push('multiple H1 headings (use single title)');
|
|
16
|
+
|
|
17
|
+
let consecutiveBullets = 0;
|
|
18
|
+
let maxConsecutiveBullets = 0;
|
|
19
|
+
for (const line of lines) {
|
|
20
|
+
if (/^\s*[-*]\s+/.test(line)) {
|
|
21
|
+
consecutiveBullets++;
|
|
22
|
+
maxConsecutiveBullets = Math.max(maxConsecutiveBullets, consecutiveBullets);
|
|
23
|
+
} else if (line.trim() !== '') {
|
|
24
|
+
consecutiveBullets = 0;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (maxConsecutiveBullets > 7) {
|
|
28
|
+
errors.push(`bullet wall: ${maxConsecutiveBullets} consecutive bullets (max 7 without prose bridge)`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (/\n{4,}/.test(body)) {
|
|
32
|
+
warnings.push('more than three consecutive blank lines');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < lines.length; i++) {
|
|
36
|
+
if (/^##\s+/.test(lines[i]) && i > 0 && lines[i - 1].trim() !== '') {
|
|
37
|
+
errors.push(`missing blank line before heading: ${lines[i].trim()}`);
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (/```mermaid[\s\S]*?flowchart/i.test(body)) {
|
|
43
|
+
if (!/error|fail|rollback|escalat/i.test(body)) {
|
|
44
|
+
warnings.push('flowchart may lack error/rollback path (output-vibe expects one non-happy path)');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (/!\[[^\]]*\]\([^)]+\)/.test(body)) {
|
|
49
|
+
if (/!\[\s*\]\(/.test(body)) errors.push('image missing alt text');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (type?.startsWith('prd') && !/\bFR-\d/i.test(body) && !/^## Requirements/m.test(body)) {
|
|
53
|
+
warnings.push('prd-family: consider FR-* or ## Requirements section');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { errors, warnings };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function lintDocPresentationFile(filePath, type) {
|
|
60
|
+
const raw = readFileSync(filePath, 'utf8');
|
|
61
|
+
const body = raw.replace(/^---\n[\s\S]*?\n---\n/, '');
|
|
62
|
+
return lintDocPresentation(body, { type });
|
|
63
|
+
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Paired with docs/guides/concepts/doc-visual-matrix.md; pinned by template tests.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { readFileSync } from 'node:fs';
|
|
9
10
|
import { validateArtifactPostconditions } from '../contracts/validate.mjs';
|
|
10
11
|
import {
|
|
11
12
|
structureRequirementsFromManifest,
|
|
@@ -42,6 +43,31 @@ export function resolveTemplatePath(type, rootDir) {
|
|
|
42
43
|
return `templates/docs/${type}.md`;
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
function hasMarkdownHeading(body, titles) {
|
|
47
|
+
const targets = new Set(titles.map((title) => title.trim().toLowerCase()));
|
|
48
|
+
const headingRe = /^#{1,6}\s+(.+)$/gm;
|
|
49
|
+
let match;
|
|
50
|
+
while ((match = headingRe.exec(body))) {
|
|
51
|
+
if (targets.has(match[1].trim().toLowerCase())) return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function lintPrdDeliveryDepth(body) {
|
|
57
|
+
const errors = [];
|
|
58
|
+
const hasRequirements = hasMarkdownHeading(body, ['Requirements', 'Functional requirements'])
|
|
59
|
+
|| /\*\*FR-\d+\.\d+\*\*:|\bFR-\d+\.\d+:/.test(body);
|
|
60
|
+
if (!hasRequirements) {
|
|
61
|
+
errors.push('prd missing functional requirements (## Requirements or FR-* entries)');
|
|
62
|
+
}
|
|
63
|
+
const hasAcceptance = hasMarkdownHeading(body, ['Acceptance criteria', 'Acceptance'])
|
|
64
|
+
|| /\*Acceptance(\*:|:\*)|\*\*Acceptance\*\*:/i.test(body);
|
|
65
|
+
if (!hasAcceptance) {
|
|
66
|
+
errors.push('prd missing acceptance criteria (## Acceptance criteria or *Acceptance* / *Acceptance:* markers)');
|
|
67
|
+
}
|
|
68
|
+
return errors;
|
|
69
|
+
}
|
|
70
|
+
|
|
45
71
|
export function lintDocStructure(filePath, type) {
|
|
46
72
|
const sectionChecks = (STRUCTURE_REQUIREMENTS[type] || []).map((section) => ({
|
|
47
73
|
id: `${type}-section-${section}`,
|
|
@@ -49,8 +75,15 @@ export function lintDocStructure(filePath, type) {
|
|
|
49
75
|
section,
|
|
50
76
|
}));
|
|
51
77
|
const postconditions = [...sectionChecks, ...(VISUAL_REQUIREMENTS[type] || [])];
|
|
52
|
-
|
|
53
|
-
|
|
78
|
+
let errors = postconditions.length === 0
|
|
79
|
+
? []
|
|
80
|
+
: validateArtifactPostconditions({ contract: { postconditions }, artifactPath: filePath });
|
|
81
|
+
if (type === 'prd') {
|
|
82
|
+
const raw = readFileSync(filePath, 'utf8');
|
|
83
|
+
const body = raw.replace(/^---\n[\s\S]*?\n---\n/, '');
|
|
84
|
+
errors = [...errors, ...lintPrdDeliveryDepth(body)];
|
|
85
|
+
}
|
|
86
|
+
return errors;
|
|
54
87
|
}
|
|
55
88
|
|
|
56
89
|
export function lintDocVisuals(filePath, type) {
|
package/lib/term-format.mjs
CHANGED
|
@@ -9,12 +9,15 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const ESC = "[";
|
|
12
|
+
const ANSI_TOKEN_RE = /\x1b\[[0-9;?]*[ -\/]*[@-~]|\x1b\]8;;.*?(?:\x07|\x1b\\)/gs;
|
|
12
13
|
|
|
13
14
|
const CODES = { bold: "1", dim: "2", reset: "0", red: "31", green: "32", yellow: "33", cyan: "36" };
|
|
14
15
|
|
|
15
16
|
const PALETTE_KEYS = Object.keys(CODES);
|
|
16
17
|
|
|
17
18
|
const EMPTY_PALETTE = Object.freeze(Object.fromEntries(PALETTE_KEYS.map((k) => [k, ""])));
|
|
19
|
+
const GRAPHEME_SEGMENTER = globalThis.Intl?.Segmenter ? new Intl.Segmenter(undefined, { granularity: "grapheme" }) : null;
|
|
20
|
+
const ZERO_WIDTH_RE = /^(?:\p{Mark}|\p{Control}|\p{Format})$/u;
|
|
18
21
|
|
|
19
22
|
// Color is opt-in by the caller AND gated on an interactive, color-capable stream. Any
|
|
20
23
|
// NO_COLOR value and TERM=dumb force plain text; a non-TTY stream (pipe, file, CI) does too.
|
|
@@ -43,6 +46,108 @@ export function termWidth(stream = process.stdout, { fallback = 80, max = 100 }
|
|
|
43
46
|
return Math.min(cols, max);
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
function graphemes(text) {
|
|
50
|
+
const value = String(text);
|
|
51
|
+
if (!value) return [];
|
|
52
|
+
if (!GRAPHEME_SEGMENTER) return [...value];
|
|
53
|
+
return Array.from(GRAPHEME_SEGMENTER.segment(value), (segment) => segment.segment);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isFullwidthCodePoint(codePoint) {
|
|
57
|
+
return codePoint >= 0x1100 && (
|
|
58
|
+
codePoint <= 0x115f ||
|
|
59
|
+
codePoint === 0x2329 ||
|
|
60
|
+
codePoint === 0x232a ||
|
|
61
|
+
(codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
|
|
62
|
+
(codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
|
63
|
+
(codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
|
64
|
+
(codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
|
|
65
|
+
(codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
|
|
66
|
+
(codePoint >= 0xff00 && codePoint <= 0xff60) ||
|
|
67
|
+
(codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
|
68
|
+
(codePoint >= 0x1f300 && codePoint <= 0x1f64f) ||
|
|
69
|
+
(codePoint >= 0x1f680 && codePoint <= 0x1f6ff) ||
|
|
70
|
+
(codePoint >= 0x1f900 && codePoint <= 0x1f9ff) ||
|
|
71
|
+
(codePoint >= 0x20000 && codePoint <= 0x3fffd)
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function graphemeWidth(grapheme) {
|
|
76
|
+
const value = String(grapheme);
|
|
77
|
+
if (!value) return 0;
|
|
78
|
+
if (!GRAPHEME_SEGMENTER && value.length === 1) {
|
|
79
|
+
const codePoint = value.codePointAt(0);
|
|
80
|
+
if (codePoint === undefined) return 0;
|
|
81
|
+
if (ZERO_WIDTH_RE.test(value)) return 0;
|
|
82
|
+
return isFullwidthCodePoint(codePoint) ? 2 : 1;
|
|
83
|
+
}
|
|
84
|
+
if ([...value].every((char) => ZERO_WIDTH_RE.test(char))) return 0;
|
|
85
|
+
for (const char of value) {
|
|
86
|
+
const codePoint = char.codePointAt(0);
|
|
87
|
+
if (codePoint !== undefined && isFullwidthCodePoint(codePoint)) return 2;
|
|
88
|
+
}
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function displayWidth(text) {
|
|
93
|
+
let width = 0;
|
|
94
|
+
for (const token of graphemes(stripAnsi(text))) width += graphemeWidth(token);
|
|
95
|
+
return width;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function clipToWidth(text, width) {
|
|
99
|
+
const input = String(text);
|
|
100
|
+
if (width <= 0 || input === "") return "";
|
|
101
|
+
if (displayWidth(input) <= width) return input;
|
|
102
|
+
|
|
103
|
+
const parts = [];
|
|
104
|
+
let visible = 0;
|
|
105
|
+
let searchIndex = 0;
|
|
106
|
+
let sawAnsi = false;
|
|
107
|
+
let openHyperlink = false;
|
|
108
|
+
|
|
109
|
+
for (const match of input.matchAll(ANSI_TOKEN_RE)) {
|
|
110
|
+
const token = match[0];
|
|
111
|
+
const index = match.index ?? searchIndex;
|
|
112
|
+
const chunk = input.slice(searchIndex, index);
|
|
113
|
+
for (const grapheme of graphemes(chunk)) {
|
|
114
|
+
const nextWidth = graphemeWidth(grapheme);
|
|
115
|
+
if (visible + nextWidth > width) {
|
|
116
|
+
if (sawAnsi) parts.push("\x1b[0m");
|
|
117
|
+
if (openHyperlink) parts.push("\x1b]8;;\x07");
|
|
118
|
+
return parts.join("");
|
|
119
|
+
}
|
|
120
|
+
parts.push(grapheme);
|
|
121
|
+
visible += nextWidth;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
parts.push(token);
|
|
125
|
+
sawAnsi = true;
|
|
126
|
+
if (token.startsWith("\x1b]8;;")) openHyperlink = token !== "\x1b]8;;\x07" && token !== "\x1b]8;;\x1b\\";
|
|
127
|
+
searchIndex = index + token.length;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const tail = input.slice(searchIndex);
|
|
131
|
+
for (const grapheme of graphemes(tail)) {
|
|
132
|
+
const nextWidth = graphemeWidth(grapheme);
|
|
133
|
+
if (visible + nextWidth > width) {
|
|
134
|
+
if (sawAnsi) parts.push("\x1b[0m");
|
|
135
|
+
if (openHyperlink) parts.push("\x1b]8;;\x07");
|
|
136
|
+
return parts.join("");
|
|
137
|
+
}
|
|
138
|
+
parts.push(grapheme);
|
|
139
|
+
visible += nextWidth;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return parts.join("");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function padToWidth(text, width) {
|
|
146
|
+
const input = String(text);
|
|
147
|
+
const padding = Math.max(0, width - displayWidth(input));
|
|
148
|
+
return padding ? `${input}${" ".repeat(padding)}` : input;
|
|
149
|
+
}
|
|
150
|
+
|
|
46
151
|
// Word-wrap one paragraph to width. Existing newlines stay as hard breaks; a single word
|
|
47
152
|
// longer than width is never split — it sits on its own line and overflows rather than
|
|
48
153
|
// becoming two unreadable fragments.
|
|
@@ -58,7 +163,7 @@ export function wrapText(text, width = termWidth()) {
|
|
|
58
163
|
for (const word of hardLine.split(/\s+/)) {
|
|
59
164
|
if (line === "") {
|
|
60
165
|
line = word;
|
|
61
|
-
} else if (line
|
|
166
|
+
} else if (displayWidth(line) + 1 + displayWidth(word) <= width) {
|
|
62
167
|
line += ` ${word}`;
|
|
63
168
|
} else {
|
|
64
169
|
out.push(line);
|
|
@@ -71,5 +176,5 @@ export function wrapText(text, width = termWidth()) {
|
|
|
71
176
|
}
|
|
72
177
|
|
|
73
178
|
export function stripAnsi(text) {
|
|
74
|
-
return String(text).replace(
|
|
179
|
+
return String(text).replace(ANSI_TOKEN_RE, "");
|
|
75
180
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/test-env-setup.mjs — Shared test environment setup.
|
|
3
|
+
*
|
|
4
|
+
* Centralizes XDG environment clearing for all test runners so the logic
|
|
5
|
+
* stays in sync across scripts/run-tests.mjs, npm test:functional, and
|
|
6
|
+
* direct node --test invocations.
|
|
7
|
+
*
|
|
8
|
+
* Tests isolate user state by overriding HOME to a tmp dir, but lib/config/xdg.mjs
|
|
9
|
+
* honors an absolute XDG_CONFIG_HOME/STATE_HOME/CACHE_HOME over HOME. A CI runner or
|
|
10
|
+
* dev shell that exports those vars routes every test's config/state/cache to one
|
|
11
|
+
* shared real location, collapsing per-test HOME isolation: suites cross-pollute (a
|
|
12
|
+
* seeded dashboard token leaks into "config absent" assertions) and writes can land
|
|
13
|
+
* in real host state. Clear them so resolution falls back to each test's HOME, as on
|
|
14
|
+
* a stock local machine; tests that exercise XDG set it explicitly per call.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export function clearXdgVars(env = process.env) {
|
|
18
|
+
delete env.XDG_CONFIG_HOME;
|
|
19
|
+
delete env.XDG_STATE_HOME;
|
|
20
|
+
delete env.XDG_CACHE_HOME;
|
|
21
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/ui/components.mjs — branded line primitives shared across surfaces.
|
|
3
|
+
*
|
|
4
|
+
* Status lines and section labels drawn from the shared glyph registry and a
|
|
5
|
+
* resolved color palette. Every primitive returns a string (never writes) so
|
|
6
|
+
* callers compose freely, and degrades to plain labeled text when color is
|
|
7
|
+
* unavailable so meaning never depends on a hue or glyph alone.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { UNICODE_GLYPHS } from './glyphs.mjs';
|
|
11
|
+
|
|
12
|
+
// A status line pairs a tinted glyph with a message. Tints map to semantic palette
|
|
13
|
+
// keys so success/warn/danger/info read consistently with the rest of the surface.
|
|
14
|
+
|
|
15
|
+
export function statusLine(message, { colors = {}, tint = 'muted', glyph = '•', indent = ' ' } = {}) {
|
|
16
|
+
const color = colors[tint] || colors.muted || '';
|
|
17
|
+
return `${indent}${color}${glyph}${colors.reset || ''} ${message}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function ok(message, { colors = {}, glyphs = UNICODE_GLYPHS } = {}) {
|
|
21
|
+
return statusLine(message, { colors, tint: 'ok', glyph: glyphs.check });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function warn(message, { colors = {} } = {}) {
|
|
25
|
+
return statusLine(message, { colors, tint: 'warn', glyph: '⚠' });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function info(message, { colors = {}, glyphs = UNICODE_GLYPHS } = {}) {
|
|
29
|
+
return statusLine(message, { colors, tint: 'highlight', glyph: glyphs.arrow });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function fail(message, { colors = {}, glyphs = UNICODE_GLYPHS } = {}) {
|
|
33
|
+
return statusLine(message, { colors, tint: 'danger', glyph: glyphs.cross });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// A section label is a tinted glyph plus a heading that opens a logical block in
|
|
37
|
+
// flowing command output, lighter than a full box.
|
|
38
|
+
|
|
39
|
+
export function section(label, { colors = {}, tint = 'highlight', glyph = '▸' } = {}) {
|
|
40
|
+
const color = colors[tint] || colors.highlight || '';
|
|
41
|
+
return `${color}${colors.bold || ''}${glyph} ${label}${colors.reset || ''}`;
|
|
42
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/ui/glyphs.mjs — single glyph registry for every construct surface.
|
|
3
|
+
*
|
|
4
|
+
* One source of truth for status marks, spinner frames, bullets, and box-drawing
|
|
5
|
+
* characters, with an ASCII fallback for terminals that cannot render Unicode.
|
|
6
|
+
* The CLI command surface and adapter diagnostics resolve glyphs here so they never
|
|
7
|
+
* drift apart. Tool status marks map raw status strings to the registry.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const UNICODE_GLYPHS = Object.freeze({
|
|
11
|
+
spinner: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
|
|
12
|
+
bullet: '•',
|
|
13
|
+
check: '✓',
|
|
14
|
+
cross: '✗',
|
|
15
|
+
arrow: '→',
|
|
16
|
+
chevron: '›',
|
|
17
|
+
dot: '·',
|
|
18
|
+
diamond: '◆',
|
|
19
|
+
play: '▸',
|
|
20
|
+
boxH: '─',
|
|
21
|
+
boxV: '│',
|
|
22
|
+
boxTL: '╭',
|
|
23
|
+
boxTR: '╮',
|
|
24
|
+
boxBL: '╰',
|
|
25
|
+
boxBR: '╯',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const ASCII_GLYPHS = Object.freeze({
|
|
29
|
+
spinner: ['|', '/', '-', '\\'],
|
|
30
|
+
bullet: '*',
|
|
31
|
+
check: 'OK',
|
|
32
|
+
cross: 'X',
|
|
33
|
+
arrow: '->',
|
|
34
|
+
chevron: '>',
|
|
35
|
+
dot: '.',
|
|
36
|
+
diamond: '*',
|
|
37
|
+
play: '>',
|
|
38
|
+
boxH: '-',
|
|
39
|
+
boxV: '|',
|
|
40
|
+
boxTL: '+',
|
|
41
|
+
boxTR: '+',
|
|
42
|
+
boxBL: '+',
|
|
43
|
+
boxBR: '+',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export function glyphs({ ascii = false } = {}) {
|
|
47
|
+
return ascii ? ASCII_GLYPHS : UNICODE_GLYPHS;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Tool lifecycle status resolves to a stable mark: done, failed, running, else
|
|
51
|
+
// pending. Callers pass the glyph set so ASCII terminals stay legible.
|
|
52
|
+
|
|
53
|
+
export function statusGlyph(status, set = UNICODE_GLYPHS) {
|
|
54
|
+
if (status === 'completed') return set.check;
|
|
55
|
+
if (status === 'failed') return set.cross;
|
|
56
|
+
if (status === 'in_progress') return set.chevron;
|
|
57
|
+
return set.dot;
|
|
58
|
+
}
|