@geraldmaron/construct 1.0.0 → 1.0.2
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/.env.example +9 -6
- package/LICENSE +201 -21
- package/README.md +132 -257
- package/agents/contracts.json +387 -0
- package/agents/prompts/cx-accessibility.md +8 -0
- package/agents/prompts/cx-ai-engineer.md +92 -0
- package/agents/prompts/cx-architect.md +10 -2
- package/agents/prompts/cx-business-strategist.md +13 -0
- package/agents/prompts/cx-data-analyst.md +80 -0
- package/agents/prompts/cx-data-engineer.md +4 -0
- package/agents/prompts/cx-debugger.md +8 -0
- package/agents/prompts/cx-designer.md +19 -0
- package/agents/prompts/cx-devil-advocate.md +4 -0
- package/agents/prompts/cx-docs-keeper.md +128 -0
- package/agents/prompts/cx-engineer.md +13 -0
- package/agents/prompts/cx-evaluator.md +4 -0
- package/agents/prompts/cx-explorer.md +12 -0
- package/agents/prompts/cx-legal-compliance.md +13 -0
- package/agents/prompts/cx-operations.md +13 -0
- package/agents/prompts/cx-orchestrator.md +107 -4
- package/agents/prompts/cx-platform-engineer.md +75 -0
- package/agents/prompts/cx-product-manager.md +8 -0
- package/agents/prompts/cx-qa.md +100 -0
- package/agents/prompts/cx-rd-lead.md +13 -0
- package/agents/prompts/cx-release-manager.md +8 -0
- package/agents/prompts/cx-researcher.md +21 -1
- package/agents/prompts/cx-reviewer.md +8 -0
- package/agents/prompts/cx-security.md +104 -0
- package/agents/prompts/cx-sre.md +104 -0
- package/agents/prompts/cx-test-automation.md +4 -0
- package/agents/prompts/cx-trace-reviewer.md +7 -3
- package/agents/prompts/cx-ux-researcher.md +4 -0
- package/agents/registry.json +365 -90
- package/agents/role-manifests.json +217 -0
- package/bin/construct +3345 -141
- package/bin/construct-postinstall.mjs +87 -0
- package/commands/build/feature.md +6 -6
- package/commands/plan/feature.md +6 -5
- package/commands/plan/requirements.md +1 -1
- package/commands/ship/status.md +1 -1
- package/commands/work/drive.md +3 -3
- package/commands/work/optimize-prompts.md +3 -3
- package/db/{migrations → schema}/001_init.sql +2 -0
- package/db/schema/002_pgvector.sql +182 -0
- package/db/schema/003_intake.sql +47 -0
- package/examples/README.md +85 -0
- package/examples/internal/roles/architect/bad/clever-plan-without-contracts.md +30 -0
- package/examples/internal/roles/architect/golden/explicit-tradeoff-before-plan.md +23 -0
- package/examples/internal/roles/engineer/bad/speculative-abstraction.md +30 -0
- package/examples/internal/roles/engineer/golden/read-before-write.md +28 -0
- package/examples/internal/roles/orchestrator/bad/everything-becomes-multi-agent.md +29 -0
- package/examples/internal/roles/orchestrator/golden/minimal-dispatch.md +22 -0
- package/examples/internal/roles/qa/bad/coverage-theater.md +30 -0
- package/examples/internal/roles/qa/golden/regression-gate.md +23 -0
- package/examples/internal/roles/reviewer/bad/lgtm-without-verification.md +29 -0
- package/examples/internal/roles/reviewer/golden/find-structural-risk-first.md +28 -0
- package/examples/personas/construct/adversarial/ignore-instruction-to-skip-approval.md +22 -0
- package/examples/personas/construct/bad/commit-without-approval.md +30 -0
- package/examples/personas/construct/boundary/blocked-needs-main-input.md +29 -0
- package/examples/personas/construct/golden/branch-approval-before-mutation.md +36 -0
- package/examples/personas/construct/golden/focused-direct-answer.md +28 -0
- package/examples/provider-plugin/README.md +34 -0
- package/examples/provider-plugin/index.mjs +74 -0
- package/examples/provider-plugin/package.json +15 -0
- package/examples/seed-observations/README.md +38 -0
- package/examples/seed-observations/anti-patterns.md +44 -0
- package/examples/seed-observations/decisions.md +36 -0
- package/examples/seed-observations/patterns.md +42 -0
- package/lib/agent-contracts-enforce.mjs +158 -0
- package/lib/agent-contracts.mjs +231 -0
- package/lib/agents/postconditions.mjs +126 -0
- package/lib/agents/schema.mjs +124 -0
- package/lib/artifact-capture.mjs +183 -0
- package/lib/audit-trail.mjs +149 -0
- package/lib/auto-docs.mjs +245 -65
- package/lib/beads/auto-close.mjs +126 -0
- package/lib/beads/drift.mjs +171 -0
- package/lib/beads-automation.mjs +542 -0
- package/lib/beads-client.mjs +518 -0
- package/lib/beads-lock.mjs +377 -0
- package/lib/beads-optimistic.mjs +365 -0
- package/lib/bootstrap/built-ins.mjs +136 -0
- package/lib/bootstrap/lazy-install.mjs +161 -0
- package/lib/bootstrap/resources.mjs +120 -0
- package/lib/bootstrap.mjs +105 -0
- package/lib/cache-governor.js +213 -0
- package/lib/cache-strategy-anthropic.js +62 -0
- package/lib/cache-strategy-google.js +79 -0
- package/lib/cache-strategy-none.js +30 -0
- package/lib/cache-strategy-openai.js +48 -0
- package/lib/cache-strategy.js +91 -0
- package/lib/claude-allow.mjs +149 -0
- package/lib/cli-commands.mjs +413 -258
- package/lib/codex-config.mjs +3 -1
- package/lib/comment-lint.mjs +55 -6
- package/lib/completions.mjs +21 -6
- package/lib/config/alias.mjs +56 -0
- package/lib/config/project-config.mjs +335 -0
- package/lib/config/schema.mjs +159 -0
- package/lib/context-router.mjs +308 -0
- package/lib/cost-ledger.mjs +177 -0
- package/lib/cost.mjs +171 -10
- package/lib/dashboard-static.mjs +158 -0
- package/lib/deployment-mode.mjs +86 -0
- package/lib/deprecate.mjs +49 -0
- package/lib/dispatch-batch.js +183 -0
- package/lib/distill.mjs +21 -8
- package/lib/doc-stamp.mjs +164 -0
- package/lib/doc-verify.mjs +119 -0
- package/lib/docs-routing.mjs +89 -0
- package/lib/docs-verify.mjs +417 -0
- package/lib/doctor/audit.mjs +71 -0
- package/lib/doctor/cli.mjs +99 -0
- package/lib/doctor/escalate.mjs +29 -0
- package/lib/doctor/index.mjs +140 -0
- package/lib/doctor/report.mjs +170 -0
- package/lib/doctor/watchers/bd-watch.mjs +117 -0
- package/lib/doctor/watchers/cost.mjs +130 -0
- package/lib/doctor/watchers/disk.mjs +122 -0
- package/lib/doctor/watchers/handoffs.mjs +33 -0
- package/lib/doctor/watchers/process-pressure.mjs +60 -0
- package/lib/doctor/watchers/service-health.mjs +188 -0
- package/lib/document-extract.mjs +288 -0
- package/lib/document-ingest.mjs +230 -0
- package/lib/drop.mjs +282 -0
- package/lib/embed/approval-queue.mjs +176 -0
- package/lib/embed/artifact.mjs +349 -0
- package/lib/embed/authority-guard.mjs +155 -0
- package/lib/embed/cli.mjs +408 -0
- package/lib/embed/config.mjs +355 -0
- package/lib/embed/conflict-detection.mjs +264 -0
- package/lib/embed/customer-profiles.mjs +480 -0
- package/lib/embed/daemon.mjs +1309 -0
- package/lib/embed/demand-fetch.mjs +449 -0
- package/lib/embed/docs-lifecycle.mjs +349 -0
- package/lib/embed/inbox-live-watcher.mjs +119 -0
- package/lib/embed/inbox.mjs +343 -0
- package/lib/embed/intake-metrics.mjs +190 -0
- package/lib/embed/jobs/vector-sync.mjs +198 -0
- package/lib/embed/notifications.mjs +75 -0
- package/lib/embed/output.mjs +79 -0
- package/lib/embed/providers/github.mjs +295 -0
- package/lib/embed/providers/jira.mjs +192 -0
- package/lib/embed/providers/linear.mjs +186 -0
- package/lib/embed/providers/registry.mjs +115 -0
- package/lib/embed/providers/slack.mjs +203 -0
- package/lib/embed/recommendation-store.mjs +378 -0
- package/lib/embed/roadmap.mjs +374 -0
- package/lib/embed/role-framing.mjs +110 -0
- package/lib/embed/scheduler.mjs +99 -0
- package/lib/embed/semantic.mjs +325 -0
- package/lib/embed/snapshot.mjs +191 -0
- package/lib/embed/supervision.mjs +235 -0
- package/lib/embed/target-resolver.mjs +186 -0
- package/lib/embed/worker.mjs +62 -0
- package/lib/embed/workspaces.mjs +297 -0
- package/lib/engine/chunker-headings.mjs +110 -0
- package/lib/engine/compressor-heuristic.mjs +100 -0
- package/lib/engine/consolidate.mjs +287 -0
- package/lib/engine/contracts.mjs +126 -0
- package/lib/engine/defaults.mjs +129 -0
- package/lib/engine/eval-retrieval.mjs +148 -0
- package/lib/engine/fuser-rrf.mjs +62 -0
- package/lib/engine/index.mjs +37 -0
- package/lib/engine/registry.mjs +146 -0
- package/lib/engine/reranker-mmr.mjs +90 -0
- package/lib/engine/tokens.mjs +77 -0
- package/lib/entity-store.mjs +280 -0
- package/lib/env-config.mjs +69 -4
- package/lib/evals/retrieval-bench.mjs +159 -0
- package/lib/evaluator-optimizer.mjs +317 -0
- package/lib/features.mjs +159 -29
- package/lib/gates-audit.mjs +236 -0
- package/lib/git-hooks/prepare-commit-msg +58 -0
- package/lib/handoffs/cleanup.mjs +159 -0
- package/lib/handoffs/contract.mjs +162 -0
- package/lib/handoffs/inventory.mjs +120 -0
- package/lib/headhunt.mjs +28 -47
- package/lib/health-check.mjs +399 -0
- package/lib/hook-health.mjs +442 -0
- package/lib/hooks/_lib/log.mjs +82 -0
- package/lib/hooks/adaptive-lint.mjs +26 -2
- package/lib/hooks/agent-tracker.mjs +171 -14
- package/lib/hooks/audit-reads.mjs +109 -0
- package/lib/hooks/audit-trail.mjs +153 -0
- package/lib/hooks/bash-output-logger.mjs +71 -0
- package/lib/hooks/block-no-verify.mjs +41 -0
- package/lib/hooks/ci-status-check.mjs +82 -0
- package/lib/hooks/comment-lint.mjs +29 -7
- package/lib/hooks/config-protection.mjs +39 -18
- package/lib/hooks/context-watch.mjs +137 -0
- package/lib/hooks/context-window-recovery.mjs +4 -20
- package/lib/hooks/dep-audit.mjs +16 -0
- package/lib/hooks/doc-coupling-check.mjs +80 -0
- package/lib/hooks/edit-accumulator.mjs +3 -0
- package/lib/hooks/edit-error-recovery.mjs +3 -0
- package/lib/hooks/edit-guard.mjs +45 -4
- package/lib/hooks/env-check.mjs +3 -0
- package/lib/hooks/guard-bash.mjs +58 -1
- package/lib/hooks/mcp-audit.mjs +18 -20
- package/lib/hooks/mcp-health-check.mjs +36 -0
- package/lib/hooks/model-fallback.mjs +42 -56
- package/lib/hooks/policy-engine.mjs +209 -0
- package/lib/hooks/post-merge-docs-check.mjs +63 -0
- package/lib/hooks/pre-compact.mjs +4 -24
- package/lib/hooks/pre-push-gate.mjs +200 -37
- package/lib/hooks/proactive-activation.mjs +284 -0
- package/lib/hooks/read-tracker.mjs +27 -4
- package/lib/hooks/readme-age-check.mjs +77 -0
- package/lib/hooks/registry-sync.mjs +12 -5
- package/lib/hooks/scan-secrets.mjs +50 -7
- package/lib/hooks/session-optimize.mjs +311 -0
- package/lib/hooks/session-start.mjs +287 -28
- package/lib/hooks/stop-notify.mjs +236 -96
- package/lib/hooks/stop-typecheck.mjs +13 -1
- package/lib/hooks/test-watch.mjs +68 -0
- package/lib/host-capabilities.mjs +31 -10
- package/lib/init-docs.mjs +731 -291
- package/lib/init-unified.mjs +1000 -0
- package/lib/init-update.mjs +168 -0
- package/lib/init.mjs +107 -0
- package/lib/install/first-invocation.mjs +119 -0
- package/lib/install/stage-project.mjs +69 -0
- package/lib/intake/classify.mjs +278 -0
- package/lib/intake/feedback.mjs +273 -0
- package/lib/intake/filesystem-queue.mjs +158 -0
- package/lib/intake/intake-config.mjs +132 -0
- package/lib/intake/postgres-queue.mjs +198 -0
- package/lib/intake/prepare.mjs +139 -0
- package/lib/intake/queue.mjs +83 -0
- package/lib/intake/session-prelude.mjs +50 -0
- package/lib/integrations/intake-integrations.mjs +740 -0
- package/lib/intent-classifier.mjs +253 -0
- package/lib/knowledge/layout.mjs +72 -0
- package/lib/knowledge/rag.mjs +331 -0
- package/lib/knowledge/search.mjs +315 -0
- package/lib/knowledge/trends.mjs +261 -0
- package/lib/logger.mjs +85 -0
- package/lib/mcp/broker.mjs +124 -0
- package/lib/mcp/server.mjs +554 -854
- package/lib/mcp/tools/document.mjs +132 -0
- package/lib/mcp/tools/memory.mjs +209 -0
- package/lib/mcp/tools/project.mjs +349 -0
- package/lib/mcp/tools/skills.mjs +409 -0
- package/lib/mcp/tools/storage.mjs +60 -0
- package/lib/mcp/tools/telemetry.mjs +315 -0
- package/lib/mcp/tools/workflow.mjs +112 -0
- package/lib/mcp-catalog.json +54 -3
- package/lib/mcp-manager.mjs +96 -48
- package/lib/mcp-platform-config.mjs +22 -22
- package/lib/memory-stats.mjs +121 -0
- package/lib/mode-commands.mjs +124 -0
- package/lib/model-free-selector.mjs +184 -0
- package/lib/model-pricing.mjs +152 -0
- package/lib/model-registry.mjs +226 -0
- package/lib/model-router.mjs +362 -386
- package/lib/observation-store.mjs +391 -0
- package/lib/ollama-manager.mjs +428 -0
- package/lib/opencode-config.mjs +13 -3
- package/lib/opencode-runtime-plugin.mjs +70 -38
- package/lib/opencode-telemetry.mjs +64 -6
- package/lib/orchestration-policy.mjs +509 -6
- package/lib/overrides/resolver.mjs +207 -0
- package/lib/parity.mjs +147 -0
- package/lib/paths.mjs +33 -0
- package/lib/performance/generate.mjs +212 -0
- package/lib/plugin-registry.mjs +268 -0
- package/lib/policy/engine.mjs +130 -0
- package/lib/policy/unified-gates.mjs +96 -0
- package/lib/project-detection.mjs +129 -0
- package/lib/project-init-shared.mjs +272 -0
- package/lib/project-profile.mjs +447 -0
- package/lib/prompt-composer.js +434 -0
- package/lib/prompt-metadata.mjs +1 -1
- package/lib/provider-capabilities-anthropic.js +44 -0
- package/lib/provider-capabilities-deepseek.js +37 -0
- package/lib/provider-capabilities-generic.js +26 -0
- package/lib/provider-capabilities-google.js +47 -0
- package/lib/provider-capabilities-openai.js +45 -0
- package/lib/provider-capabilities.js +142 -0
- package/lib/providers/atlassian-confluence/index.mjs +103 -0
- package/lib/providers/atlassian-jira/index.mjs +100 -0
- package/lib/providers/auth-manager.mjs +126 -0
- package/lib/providers/circuit-breaker.mjs +124 -0
- package/lib/providers/contract.mjs +93 -0
- package/lib/providers/github/index.mjs +126 -0
- package/lib/providers/registry.mjs +184 -0
- package/lib/providers/salesforce/index.mjs +100 -0
- package/lib/providers/slack/index.mjs +80 -0
- package/lib/reflect.mjs +137 -0
- package/lib/research-lint.mjs +164 -0
- package/lib/resources/budget.mjs +259 -0
- package/lib/role-preload.mjs +21 -6
- package/lib/roles/approval-surface.mjs +54 -0
- package/lib/roles/cli.mjs +118 -0
- package/lib/roles/event-bus.mjs +79 -0
- package/lib/roles/fence.mjs +84 -0
- package/lib/roles/gateway.mjs +260 -0
- package/lib/roles/hook-emit.mjs +37 -0
- package/lib/roles/manifest.mjs +48 -0
- package/lib/roles/router.mjs +27 -0
- package/lib/runtime-pressure.mjs +360 -0
- package/lib/schema-artifact.mjs +134 -0
- package/lib/schema-infer.mjs +551 -0
- package/lib/server/auth.mjs +168 -0
- package/lib/server/chat.mjs +336 -0
- package/lib/server/cors.mjs +77 -0
- package/lib/server/csrf.mjs +91 -0
- package/lib/server/index.mjs +1927 -78
- package/lib/server/insights.mjs +765 -0
- package/lib/server/rate-limit.mjs +91 -0
- package/lib/server/static/assets/index-ab25c707.js +70 -0
- package/lib/server/static/assets/index-f0c80a2b.css +1 -0
- package/lib/server/static/index.html +12 -817
- package/lib/server/telemetry-login.mjs +108 -0
- package/lib/server/webhook.mjs +510 -0
- package/lib/service-manager.mjs +522 -58
- package/lib/services/pattern-promotion-service.mjs +167 -0
- package/lib/services/telemetry-backend.mjs +178 -0
- package/lib/session-store.mjs +374 -0
- package/lib/setup-prompts.mjs +96 -0
- package/lib/setup.mjs +523 -36
- package/lib/skills-apply.mjs +280 -0
- package/lib/skills-scope.mjs +118 -0
- package/lib/status.mjs +261 -70
- package/lib/storage/admin.mjs +355 -0
- package/lib/storage/backend.mjs +2 -1
- package/lib/storage/backup.mjs +347 -0
- package/lib/storage/embeddings-engine.mjs +133 -0
- package/lib/storage/embeddings-legacy.mjs +85 -0
- package/lib/storage/embeddings-local.mjs +108 -0
- package/lib/storage/embeddings-ollama.mjs +78 -0
- package/lib/storage/embeddings-openai.mjs +85 -0
- package/lib/storage/embeddings.mjs +92 -33
- package/lib/storage/file-lock.mjs +130 -0
- package/lib/storage/fusion.mjs +95 -0
- package/lib/storage/hybrid-query.mjs +34 -27
- package/lib/storage/migrations.mjs +187 -0
- package/lib/storage/postgres-backup.mjs +124 -0
- package/lib/storage/sql-store.mjs +5 -15
- package/lib/storage/state-source.mjs +12 -13
- package/lib/storage/store-version.mjs +115 -0
- package/lib/storage/sync.mjs +144 -35
- package/lib/storage/unified-storage.mjs +550 -0
- package/lib/storage/vector-client.mjs +286 -0
- package/lib/storage/vector-store.mjs +71 -30
- package/lib/task-graph/generate.mjs +135 -0
- package/lib/task-graph/schema.mjs +81 -0
- package/lib/task-graph/store.mjs +71 -0
- package/lib/telemetry/backends/local.mjs +62 -0
- package/lib/telemetry/backends/{langfuse.mjs → remote.mjs} +27 -14
- package/lib/telemetry/backfill.mjs +180 -0
- package/lib/telemetry/eval-datasets.mjs +203 -0
- package/lib/telemetry/{langfuse-ingest.mjs → ingest.mjs} +26 -20
- package/lib/telemetry/intent-verifications.mjs +86 -0
- package/lib/telemetry/llm-judge.mjs +350 -0
- package/lib/telemetry/model-pricing-catalog.mjs +557 -0
- package/lib/telemetry/setup.mjs +151 -0
- package/lib/telemetry/skill-calls.mjs +78 -0
- package/lib/telemetry/team-rollup.mjs +4 -4
- package/lib/token-engine.js +117 -0
- package/lib/token-estimator-anthropic.js +15 -0
- package/lib/token-estimator-deepseek.js +13 -0
- package/lib/token-estimator-default.js +13 -0
- package/lib/token-estimator-google.js +13 -0
- package/lib/token-estimator-openai.js +13 -0
- package/lib/toolkit-env.mjs +1 -1
- package/lib/tty-prompts.mjs +211 -0
- package/lib/uninstall/uninstall.mjs +423 -0
- package/lib/update.mjs +115 -0
- package/lib/upgrade.mjs +141 -0
- package/lib/validator.mjs +51 -2
- package/lib/validators/skills.mjs +142 -0
- package/lib/wireframe.mjs +422 -0
- package/lib/worker/entrypoint.mjs +241 -0
- package/lib/worker/evidence.mjs +107 -0
- package/lib/worker/run.mjs +154 -0
- package/lib/worker/trace.mjs +182 -0
- package/lib/workflow-state.mjs +14 -18
- package/package.json +21 -8
- package/personas/construct.md +53 -51
- package/platforms/claude/CLAUDE.md +1 -1
- package/platforms/claude/settings.template.json +115 -68
- package/platforms/opencode/config.template.json +3 -7
- package/rules/common/agents.md +11 -38
- package/rules/common/beads-hygiene.md +75 -0
- package/rules/common/code-review.md +11 -100
- package/rules/common/coding-style.md +5 -3
- package/rules/common/comments.md +30 -111
- package/rules/common/commit-approval.md +53 -0
- package/rules/common/cx-agent-routing.md +1 -0
- package/rules/common/development-workflow.md +23 -41
- package/rules/common/doc-ownership.md +54 -0
- package/rules/common/efficiency.md +57 -0
- package/rules/common/framing.md +76 -0
- package/rules/common/git-workflow.md +2 -4
- package/rules/common/patterns.md +3 -7
- package/rules/common/performance.md +15 -31
- package/rules/common/release-gates.md +69 -0
- package/rules/common/research.md +107 -0
- package/rules/common/security.md +6 -6
- package/rules/common/skill-composition.md +67 -0
- package/rules/common/testing.md +15 -27
- package/rules/golang/hooks.md +0 -4
- package/rules/policy/bootstrap.yaml +11 -0
- package/rules/policy/drive.yaml +8 -0
- package/rules/policy/task.yaml +9 -0
- package/rules/policy/workflow.yaml +9 -0
- package/rules/python/hooks.md +0 -4
- package/rules/swift/hooks.md +0 -4
- package/rules/typescript/hooks.md +0 -4
- package/rules/web/hooks.md +0 -2
- package/{sync-agents.mjs → scripts/sync-agents.mjs} +306 -61
- package/skills/ai/prompt-optimizer.md +7 -7
- package/skills/compliance/ai-disclosure.md +58 -0
- package/skills/compliance/data-privacy.md +46 -0
- package/skills/compliance/license-audit.md +40 -0
- package/skills/compliance/regulatory-review.md +61 -0
- package/skills/docs/document-ingest-workflow.md +52 -0
- package/skills/docs/evidence-ingest-workflow.md +9 -0
- package/skills/docs/init-docs.md +51 -18
- package/skills/docs/product-intelligence-workflow.md +12 -0
- package/skills/docs/product-signal-workflow.md +4 -0
- package/skills/docs/research-workflow.md +23 -8
- package/skills/docs/runbook-workflow.md +1 -1
- package/skills/operating/orchestration-reference.md +151 -0
- package/skills/roles/architect.md +5 -0
- package/skills/roles/engineer.md +32 -0
- package/skills/roles/operator.md +12 -0
- package/skills/roles/reviewer.md +23 -0
- package/skills/routing.md +1 -1
- package/templates/devcontainer/Dockerfile.devcontainer +38 -0
- package/templates/devcontainer/devcontainer.json +31 -0
- package/templates/distribution/bootstrap.ps1 +142 -0
- package/templates/distribution/bootstrap.sh +196 -0
- package/templates/distribution/run.mjs +187 -0
- package/templates/docs/adr.md +44 -8
- package/templates/docs/changelog-entry.md +43 -0
- package/templates/docs/construct_guide.md +149 -0
- package/templates/docs/evidence-brief.md +2 -2
- package/templates/docs/meta-prd.md +140 -19
- package/templates/docs/onboarding.md +57 -0
- package/templates/docs/prd.md +159 -15
- package/templates/docs/research-brief.md +4 -4
- package/templates/homebrew/construct.rb +67 -0
- package/langfuse/docker-compose.yml +0 -82
- package/lib/eval-harness.mjs +0 -59
- package/lib/hooks/bootstrap-guard.mjs +0 -90
- package/lib/hooks/console-warn.mjs +0 -43
- package/lib/hooks/continuation-enforcer.mjs +0 -72
- package/lib/hooks/drive-guard.mjs +0 -89
- package/lib/hooks/mcp-task-scope.mjs +0 -47
- package/lib/hooks/task-completed-guard.mjs +0 -43
- package/lib/hooks/teammate-idle-guard.mjs +0 -54
- package/lib/hooks/workflow-guard.mjs +0 -62
- package/lib/prompt-composer.mjs +0 -196
- package/lib/review.mjs +0 -429
- package/lib/server/static/app.js +0 -841
- package/lib/telemetry/langfuse-model-sync.mjs +0 -270
- package/rules/common/hooks.md +0 -35
- package/rules/zh/README.md +0 -113
- package/rules/zh/agents.md +0 -55
- package/rules/zh/code-review.md +0 -129
- package/rules/zh/coding-style.md +0 -53
- package/rules/zh/development-workflow.md +0 -49
- package/rules/zh/git-workflow.md +0 -29
- package/rules/zh/hooks.md +0 -35
- package/rules/zh/patterns.md +0 -36
- package/rules/zh/performance.md +0 -60
- package/rules/zh/security.md +0 -34
- package/rules/zh/testing.md +0 -34
package/lib/status.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/status.mjs —
|
|
2
|
+
* lib/status.mjs — Project health summary for the Construct dashboard and CLI.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Reads workflow state, plan.md, tracker config, and MCP surface to produce a
|
|
5
|
+
* structured health object. Called by `construct status`, the MCP status tool,
|
|
6
|
+
* and the session-start bootstrap to surface blocked tasks and open questions.
|
|
5
7
|
*/
|
|
6
8
|
import { readFileSync, statSync, existsSync, readdirSync } from 'node:fs';
|
|
7
9
|
import { join, dirname } from 'node:path';
|
|
@@ -9,17 +11,19 @@ import { homedir } from 'node:os';
|
|
|
9
11
|
import { fileURLToPath } from 'node:url';
|
|
10
12
|
|
|
11
13
|
import { inspectContextState } from './context-state.mjs';
|
|
12
|
-
import { inspectWorkflowHealth, loadWorkflow } from './workflow-state.mjs';
|
|
13
14
|
import { CLI_COMMANDS } from './cli-commands.mjs';
|
|
14
15
|
import { checkAllFeatures } from './features.mjs';
|
|
15
16
|
import { loadConstructEnv } from './env-config.mjs';
|
|
17
|
+
import { describeDeploymentMode, getDeploymentMode, resolveResourceMode } from './deployment-mode.mjs';
|
|
16
18
|
import { getActiveOverlays, getPromotionRequests } from './headhunt.mjs';
|
|
17
19
|
import { resolveExecutionContractModelMetadata, selectModelTierForWorkCategory } from './model-router.mjs';
|
|
20
|
+
import { loadPluginRegistry } from './plugin-registry.mjs';
|
|
18
21
|
import { readCostLog, summarizeCostData, normalizeCostEntry } from './cost.mjs';
|
|
19
22
|
import { readEfficiencyLog, summarizeEfficiencyData } from './efficiency.mjs';
|
|
20
23
|
import { describeSqlStore, sqlStoreHealth } from './storage/sql-store.mjs';
|
|
21
24
|
import { describeVectorStore } from './storage/vector-store.mjs';
|
|
22
25
|
import { describeSqlStoreHealth } from './storage/sql-store.mjs';
|
|
26
|
+
import { triggerAutoBackfillIfSparse } from './telemetry/backfill.mjs';
|
|
23
27
|
const TOTAL_BYTES_WARNING_THRESHOLD = 750_000;
|
|
24
28
|
|
|
25
29
|
function readJSON(path) {
|
|
@@ -122,6 +126,12 @@ export function readSessionUsage(homeDir) {
|
|
|
122
126
|
interactions: 0,
|
|
123
127
|
inputTokens: 0,
|
|
124
128
|
outputTokens: 0,
|
|
129
|
+
reasoningTokens: 0,
|
|
130
|
+
providerTotalTokens: 0,
|
|
131
|
+
billedTotalTokens: 0,
|
|
132
|
+
processedInputTokens: 0,
|
|
133
|
+
cacheReadInputTokens: 0,
|
|
134
|
+
cacheCreationInputTokens: 0,
|
|
125
135
|
totalTokens: 0,
|
|
126
136
|
totalCostUsd: 0,
|
|
127
137
|
summary: 'No token usage recorded yet',
|
|
@@ -137,29 +147,40 @@ export function readSessionUsage(homeDir) {
|
|
|
137
147
|
timestamp: lastEntry.ts || null,
|
|
138
148
|
inputTokens: normalizedLast.inputTokens,
|
|
139
149
|
outputTokens: normalizedLast.outputTokens,
|
|
150
|
+
reasoningTokens: normalizedLast.reasoningTokens,
|
|
140
151
|
cacheReadInputTokens: normalizedLast.cacheReadInputTokens,
|
|
141
152
|
cacheCreationInputTokens: normalizedLast.cacheCreationInputTokens,
|
|
142
153
|
processedInputTokens: normalizedLast.processedInputTokens,
|
|
143
|
-
|
|
154
|
+
providerTotalTokens: normalizedLast.providerTotalTokens,
|
|
155
|
+
billedTotalTokens: normalizedLast.billedTotalTokens,
|
|
156
|
+
totalTokens: normalizedLast.providerTotalTokens,
|
|
144
157
|
costUsd: normalizedLast.costUsd,
|
|
145
158
|
};
|
|
146
159
|
|
|
147
160
|
const data = summarizeCostData(entries);
|
|
148
161
|
|
|
162
|
+
const billed = data.billedTotalTokens || 0;
|
|
163
|
+
const cacheReadPct = billed > 0 ? ((data.cacheReadInputTokens / billed) * 100).toFixed(1) : '?';
|
|
164
|
+
const cacheWritePct = billed > 0 ? ((data.cacheCreationInputTokens / billed) * 100).toFixed(1) : '?';
|
|
165
|
+
const freshPct = billed > 0 ? ((((data.totalInputTokens || 0) + (data.totalOutputTokens || 0)) / billed) * 100).toFixed(1) : '?';
|
|
166
|
+
|
|
149
167
|
return {
|
|
150
168
|
status: 'available',
|
|
151
169
|
interactions: data.interactions,
|
|
152
170
|
inputTokens: data.totalInputTokens,
|
|
153
171
|
outputTokens: data.totalOutputTokens,
|
|
172
|
+
reasoningTokens: data.totalReasoningTokens,
|
|
154
173
|
cacheReadInputTokens: data.cacheReadInputTokens,
|
|
155
174
|
cacheCreationInputTokens: data.cacheCreationInputTokens,
|
|
156
175
|
processedInputTokens: data.processedInputTokens,
|
|
157
176
|
cachedTokens: data.cachedTokens,
|
|
158
177
|
cacheReadRate: data.cacheReadRate,
|
|
159
178
|
cacheHitRate: data.cacheReadRate,
|
|
160
|
-
|
|
179
|
+
providerTotalTokens: data.providerTotalTokens,
|
|
180
|
+
billedTotalTokens: billed,
|
|
181
|
+
totalTokens: data.providerTotalTokens,
|
|
161
182
|
totalCostUsd: data.totalCostUsd,
|
|
162
|
-
summary: `${data.interactions} interaction${data.interactions === 1 ? '' : 's'} · ${data.
|
|
183
|
+
summary: `${data.interactions} interaction${data.interactions === 1 ? '' : 's'} · ${data.providerTotalTokens.toLocaleString()} provider · ${billed.toLocaleString()} billed (${cacheReadPct}% cache read · ${cacheWritePct}% cache write · ${freshPct}% fresh)`,
|
|
163
184
|
lastInteraction,
|
|
164
185
|
source: join(homeDir, '.cx', 'session-cost.jsonl'),
|
|
165
186
|
lastUpdatedAt: lastInteraction.timestamp,
|
|
@@ -229,7 +250,12 @@ function parsePort(value, fallback) {
|
|
|
229
250
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
230
251
|
}
|
|
231
252
|
|
|
232
|
-
async function probeHttp(url, { method = 'GET', headers, body, timeout =
|
|
253
|
+
async function probeHttp(url, { method = 'GET', headers, body, timeout = 5000 } = {}) {
|
|
254
|
+
// 5s is generous on purpose. The dashboard runs many concurrent requests when
|
|
255
|
+
// a page first loads (status + insights + registry + auth + …), which can stall
|
|
256
|
+
// its own event loop just long enough that a 2s timeout fires on a healthy
|
|
257
|
+
// localhost service. Reachability is a yes/no signal — a hung remote dies
|
|
258
|
+
// visibly via ECONNREFUSED long before this, so the extra headroom is free.
|
|
233
259
|
const controller = new AbortController();
|
|
234
260
|
const timer = setTimeout(() => controller.abort(), timeout);
|
|
235
261
|
try {
|
|
@@ -242,6 +268,24 @@ async function probeHttp(url, { method = 'GET', headers, body, timeout = 2000 }
|
|
|
242
268
|
}
|
|
243
269
|
}
|
|
244
270
|
|
|
271
|
+
// Per-service probe-result cache so back-to-back /api/status calls (page reload,
|
|
272
|
+
// React StrictMode double-mount, multiple components polling) don't pound the
|
|
273
|
+
// same probe URLs. TTL is short so a true outage shows within a few seconds.
|
|
274
|
+
const PROBE_CACHE = new Map();
|
|
275
|
+
const PROBE_CACHE_TTL_MS = 3000;
|
|
276
|
+
function probeCacheKey(service) {
|
|
277
|
+
return `${service.id || service.name}\0${service.method || 'GET'}\0${service.probeUrl || service.url}`;
|
|
278
|
+
}
|
|
279
|
+
function readProbeCache(service, now = Date.now()) {
|
|
280
|
+
const entry = PROBE_CACHE.get(probeCacheKey(service));
|
|
281
|
+
if (!entry) return null;
|
|
282
|
+
if (now - entry.at > PROBE_CACHE_TTL_MS) return null;
|
|
283
|
+
return entry.result;
|
|
284
|
+
}
|
|
285
|
+
function writeProbeCache(service, result, now = Date.now()) {
|
|
286
|
+
PROBE_CACHE.set(probeCacheKey(service), { at: now, result });
|
|
287
|
+
}
|
|
288
|
+
|
|
245
289
|
function normalizeProbeResult(result) {
|
|
246
290
|
if (typeof result === 'string') {
|
|
247
291
|
return { status: result };
|
|
@@ -250,28 +294,34 @@ function normalizeProbeResult(result) {
|
|
|
250
294
|
}
|
|
251
295
|
|
|
252
296
|
async function defaultProbeService(service) {
|
|
297
|
+
const cached = readProbeCache(service);
|
|
298
|
+
if (cached) return cached;
|
|
299
|
+
|
|
300
|
+
const headers = service.probeHeaders || (service.method === 'POST' ? { 'Content-Type': 'application/json' } : undefined);
|
|
253
301
|
const result = service.method === 'POST'
|
|
254
|
-
? await probeHttp(service.url, {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (result.
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
return { status: 'unavailable', message: 'Connection refused' };
|
|
302
|
+
? await probeHttp(service.url, { method: 'POST', headers, body: service.body })
|
|
303
|
+
: await probeHttp(service.url, headers ? { headers } : undefined);
|
|
304
|
+
|
|
305
|
+
let outcome;
|
|
306
|
+
if (result.ok) outcome = { status: 'healthy', message: service.healthyMessage ?? 'Reachable' };
|
|
307
|
+
else if (result.statusCode) outcome = { status: 'degraded', message: `HTTP ${result.statusCode}` };
|
|
308
|
+
else if (result.error?.name === 'AbortError') outcome = { status: 'unavailable', message: 'Timed out' };
|
|
309
|
+
else if (result.error?.cause?.code === 'ECONNREFUSED' || result.error?.code === 'ECONNREFUSED') {
|
|
310
|
+
outcome = { status: 'unavailable', message: 'Connection refused' };
|
|
311
|
+
} else {
|
|
312
|
+
outcome = { status: 'unavailable', message: result.error?.message ?? 'Connection failed' };
|
|
266
313
|
}
|
|
267
|
-
|
|
314
|
+
|
|
315
|
+
writeProbeCache(service, outcome);
|
|
316
|
+
return outcome;
|
|
268
317
|
}
|
|
269
318
|
|
|
270
|
-
async function
|
|
271
|
-
const baseUrl = (env.
|
|
272
|
-
const key = env.
|
|
273
|
-
const secret = env.
|
|
274
|
-
if (!key || !secret) return { status: 'unavailable', summary: '
|
|
319
|
+
async function fetchTelemetryStatus(env, { timeout = 2500 } = {}) {
|
|
320
|
+
const baseUrl = (env.CONSTRUCT_TELEMETRY_URL ?? '').replace(/\/$/, '');
|
|
321
|
+
const key = env.CONSTRUCT_TELEMETRY_PUBLIC_KEY;
|
|
322
|
+
const secret = env.CONSTRUCT_TELEMETRY_SECRET_KEY;
|
|
323
|
+
if (!key || !secret) return { status: 'unavailable', summary: 'Telemetry credentials not configured' };
|
|
324
|
+
if (!baseUrl) return { status: 'unavailable', summary: 'CONSTRUCT_TELEMETRY_URL not set' };
|
|
275
325
|
const headers = {
|
|
276
326
|
Authorization: `Basic ${Buffer.from(`${key}:${secret}`).toString('base64')}`,
|
|
277
327
|
};
|
|
@@ -279,7 +329,10 @@ async function fetchLangfuseTelemetryStatus(env, { timeout = 2500 } = {}) {
|
|
|
279
329
|
const timer = setTimeout(() => controller.abort(), timeout);
|
|
280
330
|
try {
|
|
281
331
|
const res = await fetch(`${baseUrl}/api/public/traces?limit=25`, { headers, signal: controller.signal });
|
|
282
|
-
if (
|
|
332
|
+
if (res.status === 401 || res.status === 403) {
|
|
333
|
+
return { status: 'credentials-invalid', summary: `Telemetry credentials rejected (HTTP ${res.status}) — run: construct setup` };
|
|
334
|
+
}
|
|
335
|
+
if (!res.ok) return { status: 'degraded', summary: `Telemetry HTTP ${res.status}` };
|
|
283
336
|
const json = await res.json().catch(() => ({}));
|
|
284
337
|
const traces = Array.isArray(json.data) ? json.data : [];
|
|
285
338
|
const counts = { rich: 0, partial: 0, sparse: 0 };
|
|
@@ -291,8 +344,6 @@ async function fetchLangfuseTelemetryStatus(env, { timeout = 2500 } = {}) {
|
|
|
291
344
|
const metaKeys = trace?.metadata && typeof trace.metadata === 'object' ? Object.keys(trace.metadata).length : 0;
|
|
292
345
|
const hasRichMetadata = metaKeys >= 5;
|
|
293
346
|
const hasPayload = hasInput || hasOutput || metaKeys > 0;
|
|
294
|
-
// Rich: has meaningful input/output AND (at least one observation OR rich metadata with 5+ keys).
|
|
295
|
-
// This correctly scores event-style traces (session, message) that carry metadata instead of spans.
|
|
296
347
|
if ((hasInput || hasOutput) && (observationCount >= 1 || hasRichMetadata)) counts.rich += 1;
|
|
297
348
|
else if (hasPayload || observationCount >= 1) counts.partial += 1;
|
|
298
349
|
else counts.sparse += 1;
|
|
@@ -308,19 +359,18 @@ async function fetchLangfuseTelemetryStatus(env, { timeout = 2500 } = {}) {
|
|
|
308
359
|
|
|
309
360
|
return {
|
|
310
361
|
status,
|
|
311
|
-
backend: 'langfuse',
|
|
312
362
|
total,
|
|
313
363
|
rich: counts.rich,
|
|
314
364
|
partial: counts.partial,
|
|
315
365
|
sparse: counts.sparse,
|
|
316
366
|
coverage: Number(coverage.toFixed(2)),
|
|
317
367
|
summary: total > 0
|
|
318
|
-
? `
|
|
319
|
-
: '
|
|
368
|
+
? `Telemetry reachable · ${total} traces · rich ${counts.rich} · partial ${counts.partial} · sparse ${counts.sparse} · coverage ${(coverage * 100).toFixed(0)}%`
|
|
369
|
+
: 'Telemetry reachable · no traces yet',
|
|
320
370
|
};
|
|
321
371
|
} catch (err) {
|
|
322
372
|
const msg = err?.name === 'AbortError' ? 'timed out' : err.message;
|
|
323
|
-
return { status: 'unavailable', summary: `
|
|
373
|
+
return { status: 'unavailable', summary: `Telemetry ${msg}` };
|
|
324
374
|
} finally {
|
|
325
375
|
clearTimeout(timer);
|
|
326
376
|
}
|
|
@@ -335,7 +385,7 @@ function summarizeRuntime(services) {
|
|
|
335
385
|
const optionalServices = services.filter((service) => service.impactsOverall === false);
|
|
336
386
|
|
|
337
387
|
const healthy = coreServices.filter((service) => service.status === 'healthy').length;
|
|
338
|
-
const degraded = coreServices.filter((service) => service.status === 'degraded').length;
|
|
388
|
+
const degraded = coreServices.filter((service) => service.status === 'degraded' || service.status === 'credentials-invalid').length;
|
|
339
389
|
const unavailable = coreServices.filter((service) => service.status === 'unavailable').length;
|
|
340
390
|
const optionalUnavailable = optionalServices.filter((service) => service.status !== 'healthy').length;
|
|
341
391
|
|
|
@@ -375,42 +425,51 @@ function summarizeIntegrations(features) {
|
|
|
375
425
|
return { counts, summary: summary || 'No integrations detected' };
|
|
376
426
|
}
|
|
377
427
|
|
|
378
|
-
function
|
|
379
|
-
const
|
|
428
|
+
function summarizePlugins(pluginRegistry) {
|
|
429
|
+
const plugins = pluginRegistry?.plugins ?? [];
|
|
430
|
+
const external = plugins.filter((plugin) => !plugin.builtIn);
|
|
431
|
+
const invalidCount = pluginRegistry?.errors?.length ?? 0;
|
|
432
|
+
const totalMcpCount = pluginRegistry?.mcps?.length ?? 0;
|
|
433
|
+
|
|
434
|
+
let status = 'healthy';
|
|
435
|
+
if (invalidCount > 0) status = 'degraded';
|
|
436
|
+
else if (external.length === 0) status = 'configured';
|
|
437
|
+
|
|
438
|
+
const summary = [
|
|
439
|
+
`${plugins.length} plugin${plugins.length === 1 ? '' : 's'}`,
|
|
440
|
+
`${external.length} external`,
|
|
441
|
+
`${totalMcpCount} MCP entries`,
|
|
442
|
+
invalidCount ? `${invalidCount} errors` : null,
|
|
443
|
+
].filter(Boolean).join(' · ');
|
|
444
|
+
|
|
380
445
|
return {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
446
|
+
status,
|
|
447
|
+
total: plugins.length,
|
|
448
|
+
external: external.length,
|
|
449
|
+
builtIn: plugins.length - external.length,
|
|
450
|
+
totalMcpCount,
|
|
451
|
+
invalidCount,
|
|
452
|
+
summary,
|
|
387
453
|
};
|
|
388
454
|
}
|
|
389
455
|
|
|
390
456
|
export function buildPublicHealthSurface({
|
|
391
457
|
cwd = process.cwd(),
|
|
392
458
|
contextInspection = inspectContextState(cwd),
|
|
393
|
-
workflow = loadWorkflow(cwd),
|
|
394
459
|
executionContractModel = null,
|
|
395
460
|
} = {}) {
|
|
396
|
-
const workflowHealth = inspectWorkflowHealth(workflow, { cwd });
|
|
397
|
-
|
|
398
461
|
return {
|
|
399
|
-
activeTask: workflowHealth.activeTask,
|
|
400
462
|
context: {
|
|
401
463
|
hasFile: Boolean(contextInspection?.hasFile),
|
|
402
464
|
source: contextInspection?.source ?? 'missing',
|
|
403
465
|
savedAt: contextInspection?.savedAt ?? null,
|
|
404
466
|
summary: contextInspection?.summary ?? null,
|
|
405
467
|
},
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
currentTaskKey: workflowHealth.currentTaskKey,
|
|
411
|
-
summary: workflowHealth.summary,
|
|
468
|
+
coordination: {
|
|
469
|
+
authority: 'external-tracker-plus-plan',
|
|
470
|
+
fileOwnershipRule: 'single-writer',
|
|
471
|
+
memoryRole: 'cross-session-recall',
|
|
412
472
|
},
|
|
413
|
-
alignment: workflowHealth.alignment,
|
|
414
473
|
metadataPresence: {
|
|
415
474
|
executionContractModel: Boolean(executionContractModel?.version),
|
|
416
475
|
contextState: contextInspection?.source === 'json',
|
|
@@ -419,18 +478,91 @@ export function buildPublicHealthSurface({
|
|
|
419
478
|
}
|
|
420
479
|
|
|
421
480
|
function traceBackendDefinition(env) {
|
|
422
|
-
const
|
|
481
|
+
const url = (env.CONSTRUCT_TELEMETRY_URL ?? '').replace(/\/$/, '');
|
|
482
|
+
const pubKey = env.CONSTRUCT_TELEMETRY_PUBLIC_KEY || '';
|
|
483
|
+
const secKey = env.CONSTRUCT_TELEMETRY_SECRET_KEY || '';
|
|
484
|
+
const auth = pubKey && secKey ? `Basic ${Buffer.from(`${pubKey}:${secKey}`).toString('base64')}` : '';
|
|
423
485
|
return {
|
|
424
|
-
id: '
|
|
425
|
-
name: '
|
|
426
|
-
url:
|
|
427
|
-
probeUrl: `${
|
|
486
|
+
id: 'telemetry',
|
|
487
|
+
name: 'Telemetry',
|
|
488
|
+
url: url || '(not configured)',
|
|
489
|
+
probeUrl: url ? `${url}/api/public/traces?limit=1` : '',
|
|
490
|
+
probeHeaders: auth ? { Authorization: auth } : undefined,
|
|
428
491
|
runtime: 'live',
|
|
429
492
|
note: 'Trace backend',
|
|
430
493
|
healthyMessage: 'Reachable',
|
|
431
494
|
};
|
|
432
495
|
}
|
|
433
496
|
|
|
497
|
+
/**
|
|
498
|
+
* In-process self-check for when Construct is serving its own dashboard.
|
|
499
|
+
* Rather than skipping the probe and asserting "healthy", we actually measure
|
|
500
|
+
* the process state and surface real problems.
|
|
501
|
+
*
|
|
502
|
+
* Checks:
|
|
503
|
+
* - Heap usage — warn at 80%, critical at 95%
|
|
504
|
+
* - Process uptime — surface as context
|
|
505
|
+
* - Embed daemon — read state file and report if stale or crashed
|
|
506
|
+
*
|
|
507
|
+
* Returns { status: 'healthy'|'degraded'|'unavailable', message: string }
|
|
508
|
+
*/
|
|
509
|
+
function buildSelfCheck() {
|
|
510
|
+
const problems = [];
|
|
511
|
+
const notes = [];
|
|
512
|
+
|
|
513
|
+
// Memory
|
|
514
|
+
try {
|
|
515
|
+
const mem = process.memoryUsage();
|
|
516
|
+
const heapPct = mem.heapUsed / mem.heapTotal;
|
|
517
|
+
if (heapPct >= 0.95) {
|
|
518
|
+
problems.push(`heap ${Math.round(heapPct * 100)}% (critical)`);
|
|
519
|
+
} else if (heapPct >= 0.80) {
|
|
520
|
+
problems.push(`heap ${Math.round(heapPct * 100)}% (high)`);
|
|
521
|
+
} else {
|
|
522
|
+
notes.push(`heap ${Math.round(heapPct * 100)}%`);
|
|
523
|
+
}
|
|
524
|
+
} catch { /* non-fatal */ }
|
|
525
|
+
|
|
526
|
+
// Uptime
|
|
527
|
+
try {
|
|
528
|
+
const uptimeSec = process.uptime();
|
|
529
|
+
const h = Math.floor(uptimeSec / 3600);
|
|
530
|
+
const m = Math.floor((uptimeSec % 3600) / 60);
|
|
531
|
+
notes.push(`up ${h}h${m}m`);
|
|
532
|
+
} catch { /* non-fatal */ }
|
|
533
|
+
|
|
534
|
+
// Embed daemon state
|
|
535
|
+
try {
|
|
536
|
+
const stateFile = join(homedir(), '.cx', 'runtime', 'embed-daemon.json');
|
|
537
|
+
if (existsSync(stateFile)) {
|
|
538
|
+
const state = JSON.parse(readFileSync(stateFile, 'utf8'));
|
|
539
|
+
if (state.status === 'running' && state.pid) {
|
|
540
|
+
// Verify the PID is actually alive
|
|
541
|
+
try {
|
|
542
|
+
process.kill(state.pid, 0);
|
|
543
|
+
notes.push(`embed daemon pid ${state.pid}`);
|
|
544
|
+
} catch {
|
|
545
|
+
problems.push(`embed daemon pid ${state.pid} is not running (state file stale)`);
|
|
546
|
+
}
|
|
547
|
+
} else if (state.status === 'stopped') {
|
|
548
|
+
notes.push('embed daemon stopped');
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
} catch { /* non-fatal */ }
|
|
552
|
+
|
|
553
|
+
if (problems.length) {
|
|
554
|
+
return {
|
|
555
|
+
status: problems.some(p => p.includes('critical')) ? 'unavailable' : 'degraded',
|
|
556
|
+
message: problems.join('; ') + (notes.length ? ` — ${notes.join(', ')}` : ''),
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return {
|
|
561
|
+
status: 'healthy',
|
|
562
|
+
message: ['Serving status API', ...notes].join(' — '),
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
434
566
|
function serviceDefinitions(env, dashboardPort, selfDashboard) {
|
|
435
567
|
const memoryPort = parsePort(env.MEMORY_PORT, 8765);
|
|
436
568
|
const bridgePort = parsePort(env.BRIDGE_PORT, 5173);
|
|
@@ -443,7 +575,7 @@ function serviceDefinitions(env, dashboardPort, selfDashboard) {
|
|
|
443
575
|
runtime: 'live',
|
|
444
576
|
note: selfDashboard ? 'Current process' : 'Dashboard API',
|
|
445
577
|
healthyMessage: selfDashboard ? 'Serving status API' : 'Reachable',
|
|
446
|
-
|
|
578
|
+
selfCheck: selfDashboard ? buildSelfCheck() : null,
|
|
447
579
|
},
|
|
448
580
|
traceBackendDefinition(env),
|
|
449
581
|
{
|
|
@@ -486,12 +618,19 @@ export async function buildStatus({
|
|
|
486
618
|
const pkg = readJSON(join(rootDir, 'package.json')) ?? {};
|
|
487
619
|
const registry = readJSON(join(rootDir, 'agents', 'registry.json')) ?? {};
|
|
488
620
|
const settings = readJSON(join(homeDir, '.claude', 'settings.json')) ?? {};
|
|
489
|
-
const features = await checkAllFeatures({ homeDir, env: mergedEnv });
|
|
621
|
+
const features = await checkAllFeatures({ homeDir, cwd, env: mergedEnv });
|
|
622
|
+
const pluginRegistry = loadPluginRegistry({ cwd, homeDir, rootDir, env: mergedEnv });
|
|
490
623
|
const services = [];
|
|
491
624
|
|
|
492
625
|
for (const definition of serviceDefinitions(mergedEnv, resolvedDashboardPort, selfDashboard)) {
|
|
493
|
-
if (definition.
|
|
494
|
-
|
|
626
|
+
if (definition.selfCheck) {
|
|
627
|
+
const check = definition.selfCheck;
|
|
628
|
+
services.push({
|
|
629
|
+
...definition,
|
|
630
|
+
status: check.status,
|
|
631
|
+
message: check.message,
|
|
632
|
+
selfCheck: undefined,
|
|
633
|
+
});
|
|
495
634
|
continue;
|
|
496
635
|
}
|
|
497
636
|
|
|
@@ -500,8 +639,9 @@ export async function buildStatus({
|
|
|
500
639
|
url: definition.probeUrl ?? definition.url,
|
|
501
640
|
}));
|
|
502
641
|
|
|
642
|
+
const { probeHeaders, ...safeDef } = definition;
|
|
503
643
|
services.push({
|
|
504
|
-
...
|
|
644
|
+
...safeDef,
|
|
505
645
|
status: result.status ?? 'unavailable',
|
|
506
646
|
message: result.message ?? '',
|
|
507
647
|
});
|
|
@@ -509,14 +649,16 @@ export async function buildStatus({
|
|
|
509
649
|
|
|
510
650
|
const runtime = summarizeRuntime(services);
|
|
511
651
|
const integrations = summarizeIntegrations(features);
|
|
512
|
-
const
|
|
513
|
-
const workflowSummary = workflowStatusSummary(workflow, cwd);
|
|
652
|
+
const plugins = summarizePlugins(pluginRegistry);
|
|
514
653
|
const contextInspection = inspectContextState(cwd);
|
|
515
654
|
const sessionEfficiency = readSessionEfficiency(homeDir);
|
|
516
655
|
const sessionUsage = readSessionUsage(homeDir);
|
|
517
656
|
const telemetryRichness = readTelemetryRichness(homeDir)
|
|
518
657
|
?? summarizeTelemetryHealth()
|
|
519
|
-
?? await
|
|
658
|
+
?? await fetchTelemetryStatus(mergedEnv);
|
|
659
|
+
|
|
660
|
+
// Fire-and-forget: post backfill observations to sparse traces when coverage is low.
|
|
661
|
+
triggerAutoBackfillIfSparse(telemetryRichness, { ...mergedEnv });
|
|
520
662
|
const efficiencyDigest = summarizeEfficiencyData(readEfficiencyLog(homeDir));
|
|
521
663
|
const activeOverlays = getActiveOverlays(cwd);
|
|
522
664
|
const sqlStore = describeSqlStore(mergedEnv);
|
|
@@ -526,13 +668,12 @@ export async function buildStatus({
|
|
|
526
668
|
const executionContractModel = resolveExecutionContractModelMetadata({
|
|
527
669
|
envValues: mergedEnv,
|
|
528
670
|
registryModels: registry.models ?? {},
|
|
529
|
-
requestedTier: selectModelTierForWorkCategory(
|
|
671
|
+
requestedTier: selectModelTierForWorkCategory(null),
|
|
530
672
|
workCategory: null,
|
|
531
673
|
});
|
|
532
674
|
const publicHealth = buildPublicHealthSurface({
|
|
533
675
|
cwd,
|
|
534
676
|
contextInspection,
|
|
535
|
-
workflow,
|
|
536
677
|
executionContractModel,
|
|
537
678
|
});
|
|
538
679
|
|
|
@@ -563,15 +704,45 @@ export async function buildStatus({
|
|
|
563
704
|
}
|
|
564
705
|
}
|
|
565
706
|
|
|
707
|
+
const deploymentMode = getDeploymentMode(mergedEnv);
|
|
708
|
+
const resourceMode = resolveResourceMode(deploymentMode);
|
|
709
|
+
|
|
566
710
|
return {
|
|
567
711
|
version: pkg.version ?? '1.0.0',
|
|
568
712
|
lastSync: newestMtime(join(homeDir, '.claude', 'agents')),
|
|
713
|
+
deployment: {
|
|
714
|
+
mode: deploymentMode,
|
|
715
|
+
resourceMode,
|
|
716
|
+
description: describeDeploymentMode(deploymentMode),
|
|
717
|
+
},
|
|
569
718
|
system: {
|
|
570
719
|
overall: runtime,
|
|
571
720
|
services,
|
|
572
721
|
integrations,
|
|
722
|
+
plugins,
|
|
573
723
|
},
|
|
574
724
|
features,
|
|
725
|
+
plugins: {
|
|
726
|
+
status: plugins.status,
|
|
727
|
+
summary: plugins.summary,
|
|
728
|
+
directories: pluginRegistry.pluginDirs,
|
|
729
|
+
errors: pluginRegistry.errors,
|
|
730
|
+
entries: pluginRegistry.plugins.map((plugin) => ({
|
|
731
|
+
id: plugin.id,
|
|
732
|
+
name: plugin.name,
|
|
733
|
+
version: plugin.version,
|
|
734
|
+
description: plugin.description,
|
|
735
|
+
capabilities: plugin.capabilities ?? [],
|
|
736
|
+
builtIn: Boolean(plugin.builtIn),
|
|
737
|
+
manifestPath: plugin.manifestPath,
|
|
738
|
+
mcpCount: (plugin.mcps ?? []).length,
|
|
739
|
+
mcps: (plugin.mcps ?? []).map((mcp) => ({
|
|
740
|
+
id: mcp.id,
|
|
741
|
+
name: mcp.name,
|
|
742
|
+
category: mcp.category,
|
|
743
|
+
})),
|
|
744
|
+
})),
|
|
745
|
+
},
|
|
575
746
|
personas,
|
|
576
747
|
specialists,
|
|
577
748
|
hooks,
|
|
@@ -579,7 +750,6 @@ export async function buildStatus({
|
|
|
579
750
|
commands: listCommands(rootDir),
|
|
580
751
|
cliCommands: CLI_COMMANDS,
|
|
581
752
|
mcpServers: Object.keys(settings.mcpServers ?? {}),
|
|
582
|
-
workflow: workflowSummary,
|
|
583
753
|
publicHealth,
|
|
584
754
|
storage: {
|
|
585
755
|
sql: sqlStore,
|
|
@@ -628,7 +798,11 @@ export function formatStatusReport(status) {
|
|
|
628
798
|
lines.push('════════════════');
|
|
629
799
|
lines.push('');
|
|
630
800
|
lines.push(`Overall: ${statusLabel(status.system.overall.status)} · ${status.system.overall.summary}`);
|
|
631
|
-
|
|
801
|
+
if (status.deployment?.mode) {
|
|
802
|
+
const r = status.deployment.resourceMode || {};
|
|
803
|
+
lines.push(`Deployment: ${status.deployment.mode} · queue:${r.queue} · workers:${r.workers} · telemetry:${r.telemetry}`);
|
|
804
|
+
}
|
|
805
|
+
lines.push('Coordination: external tracker + plan.md · single-writer per file · cass-memory for recall');
|
|
632
806
|
if (status.executionContractModel?.selectedTier && status.executionContractModel?.selectedModel) {
|
|
633
807
|
lines.push(`Execution contract: ${status.executionContractModel.selectedTier} · ${status.executionContractModel.selectedModel} (${status.executionContractModel.selectedModelSource})`);
|
|
634
808
|
}
|
|
@@ -652,7 +826,13 @@ export function formatStatusReport(status) {
|
|
|
652
826
|
: '';
|
|
653
827
|
lines.push(`Usage: ${statusLabel(status.sessionUsage.status)} · ${status.sessionUsage.summary}${cacheNote}`);
|
|
654
828
|
if (status.sessionUsage.lastInteraction) {
|
|
655
|
-
lines.push(
|
|
829
|
+
lines.push(
|
|
830
|
+
`Last interaction: ${status.sessionUsage.lastInteraction.providerTotalTokens.toLocaleString()} provider total` +
|
|
831
|
+
` · ${status.sessionUsage.lastInteraction.billedTotalTokens.toLocaleString()} billed total` +
|
|
832
|
+
` (${status.sessionUsage.lastInteraction.inputTokens.toLocaleString()} uncached in / ` +
|
|
833
|
+
`${status.sessionUsage.lastInteraction.outputTokens.toLocaleString()} out / ` +
|
|
834
|
+
`${Number(status.sessionUsage.lastInteraction.reasoningTokens || 0).toLocaleString()} reasoning)`,
|
|
835
|
+
);
|
|
656
836
|
}
|
|
657
837
|
}
|
|
658
838
|
if (status.telemetryRichness) {
|
|
@@ -683,6 +863,17 @@ export function formatStatusReport(status) {
|
|
|
683
863
|
for (const feature of status.features) {
|
|
684
864
|
lines.push(` ${serviceIcon(feature.status)} ${feature.name.padEnd(22)} ${statusLabel(feature.status)} — ${feature.message}`);
|
|
685
865
|
}
|
|
866
|
+
if (status.plugins) {
|
|
867
|
+
lines.push('');
|
|
868
|
+
lines.push(`Plugins: ${statusLabel(status.plugins.status)} · ${status.plugins.summary}`);
|
|
869
|
+
for (const plugin of status.plugins.entries ?? []) {
|
|
870
|
+
const source = plugin.builtIn ? 'built-in' : plugin.manifestPath;
|
|
871
|
+
lines.push(` ${serviceIcon(plugin.builtIn ? 'configured' : 'healthy')} ${plugin.name.padEnd(22)} ${plugin.version} — ${source}`);
|
|
872
|
+
}
|
|
873
|
+
for (const error of status.plugins.errors ?? []) {
|
|
874
|
+
lines.push(` ${serviceIcon('degraded')} manifest error — ${error}`);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
686
877
|
return `${lines.join('\n')}\n`;
|
|
687
878
|
}
|
|
688
879
|
|