@geraldmaron/construct 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +10 -7
- 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 +1116 -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 +525 -38
- 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
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/beads-client.mjs — Wrapper for bd commands with lock‑management and queueing.
|
|
3
|
+
*
|
|
4
|
+
* VERSION 2.0: Now uses optimistic locking by default for better concurrency.
|
|
5
|
+
* All external bd calls should go through `runBd()` to ensure serialized access
|
|
6
|
+
* to the embedded Dolt database.
|
|
7
|
+
*
|
|
8
|
+
* Changes:
|
|
9
|
+
* - Reads are now lock-free (concurrent safe)
|
|
10
|
+
* - Writes use optimistic locking with automatic retry
|
|
11
|
+
* - Legacy file-based lock is fallback only
|
|
12
|
+
* - Better error messages and conflict resolution
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { spawnSync } from 'node:child_process';
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
acquireLock,
|
|
22
|
+
releaseLock,
|
|
23
|
+
getLockStatus,
|
|
24
|
+
cleanupStaleLock,
|
|
25
|
+
enqueueRequest,
|
|
26
|
+
removeFromQueue,
|
|
27
|
+
readQueue,
|
|
28
|
+
cleanupStaleQueue,
|
|
29
|
+
} from './beads-lock.mjs';
|
|
30
|
+
|
|
31
|
+
import {
|
|
32
|
+
concurrentRead,
|
|
33
|
+
optimisticWrite,
|
|
34
|
+
batchReadBeads,
|
|
35
|
+
updateBeadOptimistic,
|
|
36
|
+
claimBeadOptimistic,
|
|
37
|
+
shouldUseOptimisticLocking,
|
|
38
|
+
recordOperationStats,
|
|
39
|
+
} from './beads-optimistic.mjs';
|
|
40
|
+
|
|
41
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Configuration and defaults
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
const DEFAULT_OPTIONS = {
|
|
48
|
+
actor: 'construct',
|
|
49
|
+
silent: false,
|
|
50
|
+
timeoutSeconds: 30, // Time to wait for lock before giving up
|
|
51
|
+
commandTimeoutSeconds: 120, // Max runtime for the bd child process
|
|
52
|
+
queuePollMs: 100, // How often to check for lock release when queued
|
|
53
|
+
maxQueueWaitSeconds: 300, // Max total time allowed in queue
|
|
54
|
+
useOptimisticLocking: true, // NEW: Use optimistic locking by default
|
|
55
|
+
fallbackToLegacy: true, // NEW: Fall back to legacy locking on failure
|
|
56
|
+
maxRetries: 5, // NEW: Max retries for optimistic locking
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Track which operations are reads vs writes
|
|
60
|
+
const READ_OPERATIONS = new Set([
|
|
61
|
+
'list', 'show', 'ready', 'search', 'merge-slot', 'check',
|
|
62
|
+
'status', 'log', 'diff', 'note',
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
const WRITE_OPERATIONS = new Set([
|
|
66
|
+
'create', 'update', 'close', 'claim', 'note', 'label',
|
|
67
|
+
'assign', 'link', 'unlink', 'move', 'archive',
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Operation classification
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
function isReadOperation(args) {
|
|
75
|
+
if (!args || args.length === 0) return true;
|
|
76
|
+
const cmd = args[0];
|
|
77
|
+
return READ_OPERATIONS.has(cmd) || !WRITE_OPERATIONS.has(cmd);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isWriteOperation(args) {
|
|
81
|
+
if (!args || args.length === 0) return false;
|
|
82
|
+
const cmd = args[0];
|
|
83
|
+
return WRITE_OPERATIONS.has(cmd);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function extractBeadId(args) {
|
|
87
|
+
// Try to find a bead ID in the arguments (typically the second arg)
|
|
88
|
+
if (args.length >= 2 && args[1] && !args[1].startsWith('--')) {
|
|
89
|
+
return args[1];
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Core wrapper with optimistic locking support
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Run a bd command with appropriate locking strategy.
|
|
100
|
+
*
|
|
101
|
+
* STRATEGY:
|
|
102
|
+
* 1. Read operations: Lock-free concurrent execution
|
|
103
|
+
* 2. Write operations: Optimistic locking with retry
|
|
104
|
+
* 3. Legacy lock: Fallback only when optimistic fails
|
|
105
|
+
*
|
|
106
|
+
* @param {string[]} args - Arguments for `bd`
|
|
107
|
+
* @param {Object} [options]
|
|
108
|
+
* @param {string} options.actor - Who is running this (for logging)
|
|
109
|
+
* @param {number} options.timeoutSeconds - Max seconds to wait for lock
|
|
110
|
+
* @param {number} options.commandTimeoutSeconds - Max seconds to let bd run after lock acquisition
|
|
111
|
+
* @param {boolean} options.silent - Suppress logging of success/queue status
|
|
112
|
+
* @param {boolean} options.useOptimisticLocking - Use optimistic locking (default: true)
|
|
113
|
+
* @param {boolean} options.fallbackToLegacy - Fall back to legacy lock on failure (default: true)
|
|
114
|
+
* @param {number} options.maxRetries - Max retries for optimistic locking (default: 5)
|
|
115
|
+
* @param {string} options.cwd - Working directory (default: process.cwd())
|
|
116
|
+
* @returns {Object} { success: boolean, output: string, error?: string, attempts?: number, method?: string }
|
|
117
|
+
*/
|
|
118
|
+
export async function runBd(args, options = {}) {
|
|
119
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
120
|
+
const cwd = opts.cwd || process.cwd();
|
|
121
|
+
const commandDesc = args.join(' ');
|
|
122
|
+
const isRead = isReadOperation(args);
|
|
123
|
+
const beadId = extractBeadId(args);
|
|
124
|
+
|
|
125
|
+
if (!opts.silent) {
|
|
126
|
+
const opType = isRead ? 'read' : 'write';
|
|
127
|
+
console.error(`[beads] ${opts.actor} ${opType}: bd ${commandDesc}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ==========================================================================
|
|
131
|
+
// PATH 1: Lock-free reads (fastest, most concurrent)
|
|
132
|
+
// ==========================================================================
|
|
133
|
+
if (isRead && opts.useOptimisticLocking) {
|
|
134
|
+
const startTime = Date.now();
|
|
135
|
+
|
|
136
|
+
const result = spawnSync('bd', args, {
|
|
137
|
+
cwd,
|
|
138
|
+
encoding: 'utf8',
|
|
139
|
+
env: { ...process.env, BEADS_ACTOR: opts.actor },
|
|
140
|
+
timeout: Math.max(1, Math.round(opts.commandTimeoutSeconds * 1000)),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const execTime = Date.now() - startTime;
|
|
144
|
+
|
|
145
|
+
// Handle timeout error
|
|
146
|
+
if (result.error?.code === 'ETIMEDOUT') {
|
|
147
|
+
return {
|
|
148
|
+
success: false,
|
|
149
|
+
error: `bd ${commandDesc} timed out after ${opts.commandTimeoutSeconds}s`,
|
|
150
|
+
exitCode: null,
|
|
151
|
+
execTimeMs: execTime,
|
|
152
|
+
method: 'concurrent-read',
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!opts.silent) {
|
|
157
|
+
console.error(`[beads] ✓ Read completed in ${execTime}ms (lock-free)`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
success: result.status === 0 && !result.error,
|
|
162
|
+
output: result.stdout,
|
|
163
|
+
error: result.stderr || result.error?.message,
|
|
164
|
+
exitCode: result.status,
|
|
165
|
+
execTimeMs: execTime,
|
|
166
|
+
method: 'concurrent-read',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ==========================================================================
|
|
171
|
+
// PATH 2: Optimistic locking for writes (scalable, conflict-resistant)
|
|
172
|
+
// ==========================================================================
|
|
173
|
+
if (!isRead && opts.useOptimisticLocking && shouldUseOptimisticLocking()) {
|
|
174
|
+
const startTime = Date.now();
|
|
175
|
+
|
|
176
|
+
const result = await optimisticWrite({
|
|
177
|
+
beadId: beadId || 'unknown',
|
|
178
|
+
cwd,
|
|
179
|
+
maxRetries: opts.maxRetries,
|
|
180
|
+
execute: async () => {
|
|
181
|
+
const execResult = spawnSync('bd', args, {
|
|
182
|
+
cwd,
|
|
183
|
+
encoding: 'utf8',
|
|
184
|
+
env: { ...process.env, BEADS_ACTOR: opts.actor },
|
|
185
|
+
timeout: Math.max(1, Math.round(opts.commandTimeoutSeconds * 1000)),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
if (execResult.status !== 0) {
|
|
189
|
+
throw new Error(execResult.stderr || 'Command failed');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return execResult.stdout;
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const totalTime = Date.now() - startTime;
|
|
197
|
+
|
|
198
|
+
recordOperationStats(commandDesc, result, cwd);
|
|
199
|
+
|
|
200
|
+
if (result.success) {
|
|
201
|
+
if (!opts.silent) {
|
|
202
|
+
console.error(`[beads] ✓ Write completed in ${totalTime}ms (optimistic, ${result.attempts} attempt${result.attempts !== 1 ? 's' : ''})`);
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
success: true,
|
|
206
|
+
output: result.result,
|
|
207
|
+
attempts: result.attempts,
|
|
208
|
+
execTimeMs: totalTime,
|
|
209
|
+
method: 'optimistic-locking',
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Optimistic locking failed
|
|
214
|
+
if (!opts.fallbackToLegacy) {
|
|
215
|
+
return {
|
|
216
|
+
success: false,
|
|
217
|
+
error: result.error,
|
|
218
|
+
attempts: result.attempts,
|
|
219
|
+
method: 'optimistic-locking',
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!opts.silent) {
|
|
224
|
+
console.error(`[beads] ⚠ Optimistic locking failed: ${result.error}`);
|
|
225
|
+
console.error(`[beads] ↘ Falling back to legacy lock...`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ==========================================================================
|
|
230
|
+
// PATH 3: Legacy file-based locking (fallback, most conservative)
|
|
231
|
+
// ==========================================================================
|
|
232
|
+
return runWithLegacyLock(args, opts, cwd, commandDesc);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Legacy locking implementation (original behavior).
|
|
237
|
+
* Used as fallback when optimistic locking fails.
|
|
238
|
+
*/
|
|
239
|
+
async function runWithLegacyLock(args, opts, cwd, commandDesc) {
|
|
240
|
+
// Clean up any stale locks before trying
|
|
241
|
+
cleanupStaleLock({ cwd });
|
|
242
|
+
cleanupStaleQueue({ cwd });
|
|
243
|
+
|
|
244
|
+
// Try to acquire the lock
|
|
245
|
+
const startWait = Date.now();
|
|
246
|
+
const lock = await acquireLock({
|
|
247
|
+
actor: opts.actor,
|
|
248
|
+
command: commandDesc,
|
|
249
|
+
timeoutSeconds: opts.timeoutSeconds,
|
|
250
|
+
cwd,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
if (!lock) {
|
|
254
|
+
// Failed to get lock; add to queue if allowed
|
|
255
|
+
const maxQueueWaitMs = opts.maxQueueWaitSeconds * 1000;
|
|
256
|
+
if (Date.now() - startWait + maxQueueWaitMs > maxQueueWaitMs) {
|
|
257
|
+
return {
|
|
258
|
+
success: false,
|
|
259
|
+
error: `Timeout waiting for lock after ${opts.timeoutSeconds}s`,
|
|
260
|
+
queued: false,
|
|
261
|
+
method: 'legacy-lock',
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const queueEntry = enqueueRequest({
|
|
266
|
+
actor: opts.actor,
|
|
267
|
+
command: commandDesc,
|
|
268
|
+
args,
|
|
269
|
+
cwd,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
if (!opts.silent) {
|
|
273
|
+
console.error(`[beads] ↘ Added to queue (position ${readQueue({ cwd }).length})`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Wait in queue, polling periodically
|
|
277
|
+
while (Date.now() - startWait < opts.maxQueueWaitSeconds * 1000) {
|
|
278
|
+
await new Promise((resolve) => setTimeout(resolve, opts.queuePollMs));
|
|
279
|
+
|
|
280
|
+
cleanupStaleLock({ cwd });
|
|
281
|
+
const status = getLockStatus({ cwd });
|
|
282
|
+
if (!status.locked) {
|
|
283
|
+
// Lock free, remove from queue and try again
|
|
284
|
+
removeFromQueue(queueEntry.timestamp || queueEntry.pid, { cwd });
|
|
285
|
+
return await runBd(args, { ...opts, silent: true });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Queue timeout
|
|
290
|
+
removeFromQueue(queueEntry.timestamp || queueEntry.pid, { cwd });
|
|
291
|
+
return {
|
|
292
|
+
success: false,
|
|
293
|
+
error: `Timeout in queue after ${opts.maxQueueWaitSeconds}s`,
|
|
294
|
+
queued: true,
|
|
295
|
+
waitTimeMs: Date.now() - startWait,
|
|
296
|
+
method: 'legacy-lock',
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const execStart = Date.now();
|
|
301
|
+
let result;
|
|
302
|
+
try {
|
|
303
|
+
result = spawnSync('bd', args, {
|
|
304
|
+
cwd,
|
|
305
|
+
encoding: 'utf8',
|
|
306
|
+
env: { ...process.env, BEADS_ACTOR: opts.actor },
|
|
307
|
+
killSignal: 'SIGTERM',
|
|
308
|
+
timeout: Math.max(1, Math.round(opts.commandTimeoutSeconds * 1000)),
|
|
309
|
+
});
|
|
310
|
+
} finally {
|
|
311
|
+
releaseLock({ cwd });
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const totalTime = Date.now() - startWait;
|
|
315
|
+
const execTime = Date.now() - execStart;
|
|
316
|
+
|
|
317
|
+
if (!opts.silent) {
|
|
318
|
+
console.error(`[beads] ✓ Executed in ${execTime}ms (legacy lock, total: ${totalTime}ms)`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
success: result.status === 0 && !result.error,
|
|
323
|
+
output: result.stdout,
|
|
324
|
+
error: result.error?.code === 'ETIMEDOUT'
|
|
325
|
+
? `bd ${commandDesc} timed out after ${opts.commandTimeoutSeconds}s`
|
|
326
|
+
: (result.stderr || result.error?.message),
|
|
327
|
+
exitCode: result.status,
|
|
328
|
+
waitTimeMs: execStart - startWait,
|
|
329
|
+
execTimeMs: execTime,
|
|
330
|
+
totalTimeMs: totalTime,
|
|
331
|
+
method: 'legacy-lock',
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ---------------------------------------------------------------------------
|
|
336
|
+
// Higher‑level convenience functions (enhanced with optimistic locking)
|
|
337
|
+
// ---------------------------------------------------------------------------
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Quick wrapper that returns parsed JSON output.
|
|
341
|
+
* Uses concurrent read (no locking) for better performance.
|
|
342
|
+
*/
|
|
343
|
+
export async function runBdJson(args, options = {}) {
|
|
344
|
+
const result = await runBd([...args, '--json'], { ...options, useOptimisticLocking: true });
|
|
345
|
+
if (!result.success) {
|
|
346
|
+
throw new Error(`bd failed: ${result.error}`);
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
return JSON.parse(result.output);
|
|
350
|
+
} catch (e) {
|
|
351
|
+
throw new Error(`Failed to parse JSON from bd: ${e.message}\n${result.output}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Get issue list (commonly used).
|
|
357
|
+
* Uses concurrent reads for better performance.
|
|
358
|
+
*/
|
|
359
|
+
export async function listIssues(options = {}) {
|
|
360
|
+
return runBdJson(['list'], { useOptimisticLocking: true, ...options });
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Show a specific issue.
|
|
365
|
+
* Uses concurrent read (no locking needed).
|
|
366
|
+
*/
|
|
367
|
+
export async function showIssue(id, options = {}) {
|
|
368
|
+
return runBdJson(['show', id], { useOptimisticLocking: true, ...options });
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Claim an issue with optimistic locking.
|
|
373
|
+
* Uses optimistic locking for better concurrency.
|
|
374
|
+
*/
|
|
375
|
+
export async function claimIssue(id, options = {}) {
|
|
376
|
+
// Use the specialized optimistic claim function
|
|
377
|
+
const result = await claimBeadOptimistic(id, options);
|
|
378
|
+
|
|
379
|
+
if (result.success) {
|
|
380
|
+
return { success: true, output: JSON.stringify(result.result) };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Fall back to legacy
|
|
384
|
+
return runBd(['update', id, '--claim'], { ...options, useOptimisticLocking: false });
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Close an issue.
|
|
389
|
+
*/
|
|
390
|
+
export async function closeIssue(id, options = {}) {
|
|
391
|
+
return runBd(['close', id], options);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Check ready issues.
|
|
396
|
+
* Uses concurrent read (no locking needed).
|
|
397
|
+
*/
|
|
398
|
+
export async function getReadyIssues(options = {}) {
|
|
399
|
+
return runBdJson(['ready'], { useOptimisticLocking: true, ...options });
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Check merge‑slot availability.
|
|
404
|
+
* Uses concurrent read.
|
|
405
|
+
*/
|
|
406
|
+
export async function getMergeSlotStatus(options = {}) {
|
|
407
|
+
return runBd(['merge-slot', 'check'], { silent: true, useOptimisticLocking: true, ...options });
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Try to acquire merge‑slot (with fallback if it doesn't exist).
|
|
412
|
+
* Uses optimistic locking for the acquire operation.
|
|
413
|
+
*/
|
|
414
|
+
export async function acquireMergeSlot(options = {}) {
|
|
415
|
+
const { success, error } = await runBd(['merge-slot', 'acquire'], {
|
|
416
|
+
silent: true,
|
|
417
|
+
useOptimisticLocking: true,
|
|
418
|
+
...options
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
if (!success && error?.includes('merge slot bead')) {
|
|
422
|
+
// Slot might not exist; create it first
|
|
423
|
+
await runBd(['merge-slot', 'create'], { silent: true, useOptimisticLocking: true, ...options });
|
|
424
|
+
return runBd(['merge-slot', 'acquire'], { silent: true, useOptimisticLocking: true, ...options });
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return { success, error };
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Release merge‑slot.
|
|
432
|
+
*/
|
|
433
|
+
export async function releaseMergeSlot(options = {}) {
|
|
434
|
+
return runBd(['merge-slot', 'release'], { silent: true, useOptimisticLocking: true, ...options });
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Batch read multiple issues efficiently.
|
|
439
|
+
* NEW: Concurrent batch reading for better performance.
|
|
440
|
+
*/
|
|
441
|
+
export async function batchShowIssues(ids, options = {}) {
|
|
442
|
+
return batchReadBeads(ids, options.cwd || process.cwd());
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// ---------------------------------------------------------------------------
|
|
446
|
+
// Status and utility exports
|
|
447
|
+
// ---------------------------------------------------------------------------
|
|
448
|
+
|
|
449
|
+
export { getLockStatus, cleanupStaleLock, cleanupStaleQueue, readQueue } from './beads-lock.mjs';
|
|
450
|
+
export {
|
|
451
|
+
getContentionStats,
|
|
452
|
+
recordOperationStats,
|
|
453
|
+
shouldUseOptimisticLocking
|
|
454
|
+
} from './beads-optimistic.mjs';
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Shortcut to get current queue length.
|
|
458
|
+
*/
|
|
459
|
+
export function getQueueLength(cwd = process.cwd()) {
|
|
460
|
+
return readQueue({ cwd }).length;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Get human‑readable status string (lock + queue + contention stats).
|
|
465
|
+
* ENHANCED: Now includes optimistic locking statistics.
|
|
466
|
+
*/
|
|
467
|
+
export async function getHumanStatus(cwd = process.cwd()) {
|
|
468
|
+
const status = getLockStatus({ cwd });
|
|
469
|
+
|
|
470
|
+
let out = '';
|
|
471
|
+
|
|
472
|
+
// Optimistic locking status
|
|
473
|
+
out += 'Locking Strategy:\n';
|
|
474
|
+
out += ` Mode: ${shouldUseOptimisticLocking() ? 'Optimistic (concurrent reads + versioned writes)' : 'Legacy (exclusive locks)'}\n`;
|
|
475
|
+
|
|
476
|
+
// Try to get stats if available
|
|
477
|
+
try {
|
|
478
|
+
const { getContentionStats } = await import('./beads-optimistic.mjs');
|
|
479
|
+
const stats = await getContentionStats(cwd);
|
|
480
|
+
if (stats.totalOperations > 0) {
|
|
481
|
+
out += ` Operations: ${stats.totalOperations}\n`;
|
|
482
|
+
out += ` Conflicts: ${stats.conflicts} (${((stats.conflicts / stats.totalOperations) * 100).toFixed(1)}%)\n`;
|
|
483
|
+
out += ` Avg attempts: ${stats.avgAttempts?.toFixed(2) || '1.00'}\n`;
|
|
484
|
+
}
|
|
485
|
+
} catch {
|
|
486
|
+
// Stats not available, skip
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
out += '\n';
|
|
490
|
+
|
|
491
|
+
// Legacy lock status (for debugging)
|
|
492
|
+
if (status.lock) {
|
|
493
|
+
const { lock } = status;
|
|
494
|
+
out += `Legacy Lock:\n`;
|
|
495
|
+
out += ` Held by: ${lock.actor} (pid ${lock.pid})\n`;
|
|
496
|
+
out += ` Command: ${lock.command}\n`;
|
|
497
|
+
out += ` Started: ${lock.startedAt}${lock.alive ? '' : ' ⚠️ process dead'}\n`;
|
|
498
|
+
} else if (status.nativeLock) {
|
|
499
|
+
const { nativeLock } = status;
|
|
500
|
+
out += `Native Lock:\n`;
|
|
501
|
+
out += ` Held by: ${nativeLock.command} (pid ${nativeLock.pid})\n`;
|
|
502
|
+
} else {
|
|
503
|
+
out += 'No lock held\n';
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const queue = readQueue({ cwd });
|
|
507
|
+
if (queue.length) {
|
|
508
|
+
out += `\nQueue (${queue.length} waiting):\n`;
|
|
509
|
+
queue.forEach((entry, idx) => {
|
|
510
|
+
const alive = entry.pid && (() => {
|
|
511
|
+
try { process.kill(entry.pid, 0); return true; } catch { return false; }
|
|
512
|
+
})();
|
|
513
|
+
out += ` ${idx + 1}. ${entry.actor} – ${entry.command || entry.args?.join(' ')}${alive ? '' : ' ⚠️ dead'}\n`;
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return out;
|
|
518
|
+
}
|