@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
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/docs-lifecycle.mjs — documentation maintenance job for embed mode.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Detect stale, missing, or outdated docs across targets
|
|
6
|
+
* - Generate/update docs as artifacts in the appropriate docs lane
|
|
7
|
+
* - Route writes through the authority guard (direct-write for low-risk, approval-queued for high-risk)
|
|
8
|
+
* - Treat ALL doc types uniformly: adrs, prds, memos, notes, intake, roadmaps
|
|
9
|
+
*
|
|
10
|
+
* Doc types and risk levels:
|
|
11
|
+
* - LOW risk (direct-write): status updates, timestamps, cross-refs, roadmap refresh, notes
|
|
12
|
+
* - HIGH risk (approval-queued): new ADRs, new PRDs, issue creation, external posts
|
|
13
|
+
*
|
|
14
|
+
* Each doc type has a generator that produces markdown content from snapshot data,
|
|
15
|
+
* observations, and role lens context.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { resolveTargets } from './target-resolver.mjs';
|
|
21
|
+
import { buildRoleLens } from './role-framing.mjs';
|
|
22
|
+
import { WORKSPACE_DOCS_LANES } from './config.mjs';
|
|
23
|
+
import { recommendArtifacts } from './artifact.mjs';
|
|
24
|
+
import { createRecommendation, autoSuppressStale, recommendationStats, isRecommendationActive } from './recommendation-store.mjs';
|
|
25
|
+
import { detectConflicts } from './conflict-detection.mjs';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Risk classification for doc operations.
|
|
29
|
+
*/
|
|
30
|
+
const RISK_LEVEL = {
|
|
31
|
+
// Low risk — can be written autonomously
|
|
32
|
+
'notes': 'low',
|
|
33
|
+
'roadmap': 'low',
|
|
34
|
+
'status-update': 'low',
|
|
35
|
+
'cross-ref': 'low',
|
|
36
|
+
// High risk — needs approval
|
|
37
|
+
'adrs': 'high',
|
|
38
|
+
'prds': 'high',
|
|
39
|
+
'memos': 'high',
|
|
40
|
+
'intake': 'high',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Staleness thresholds (ms). If a doc hasn't been updated in this long, flag it.
|
|
45
|
+
*/
|
|
46
|
+
const STALE_THRESHOLD = {
|
|
47
|
+
roadmap: 6 * 60 * 60 * 1000, // 6 hours
|
|
48
|
+
notes: 24 * 60 * 60 * 1000, // 24 hours
|
|
49
|
+
adrs: 7 * 24 * 60 * 60 * 1000, // 7 days (decisions shouldn't go stale fast)
|
|
50
|
+
prds: 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
51
|
+
memos: 3 * 24 * 60 * 60 * 1000, // 3 days
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Scan a target's docs lanes and identify gaps or staleness.
|
|
56
|
+
*
|
|
57
|
+
* @param {object} target - Resolved target from target-resolver
|
|
58
|
+
* @param {object} [opts]
|
|
59
|
+
* @param {object} [opts.snapshot] - Latest snapshot data
|
|
60
|
+
* @param {object} [opts.roleLens] - Combined role lens
|
|
61
|
+
* @returns {DocGap[]} List of detected gaps
|
|
62
|
+
*/
|
|
63
|
+
export function detectDocGaps(target, opts = {}) {
|
|
64
|
+
const gaps = [];
|
|
65
|
+
|
|
66
|
+
if (target.access === 'remote') {
|
|
67
|
+
// Remote-only targets: can't inspect filesystem, skip for now
|
|
68
|
+
return gaps;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const docsBase = join(target.path, 'docs');
|
|
72
|
+
if (!existsSync(docsBase)) {
|
|
73
|
+
gaps.push({ type: 'missing-structure', severity: 'high', summary: `No docs/ directory at ${target.path}` });
|
|
74
|
+
return gaps;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check each lane
|
|
78
|
+
for (const lane of WORKSPACE_DOCS_LANES) {
|
|
79
|
+
const lanePath = join(docsBase, lane);
|
|
80
|
+
if (!existsSync(lanePath)) {
|
|
81
|
+
gaps.push({ type: 'missing-lane', lane, severity: 'medium', summary: `Missing docs lane: ${lane}` });
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Check for staleness
|
|
86
|
+
const files = safeReaddir(lanePath);
|
|
87
|
+
if (!files.length && lane !== 'intake') {
|
|
88
|
+
gaps.push({ type: 'empty-lane', lane, severity: 'low', summary: `Empty docs lane: ${lane}` });
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const threshold = STALE_THRESHOLD[lane];
|
|
93
|
+
if (threshold) {
|
|
94
|
+
for (const file of files) {
|
|
95
|
+
const filePath = join(lanePath, file);
|
|
96
|
+
try {
|
|
97
|
+
const stat = statSync(filePath);
|
|
98
|
+
const age = Date.now() - stat.mtimeMs;
|
|
99
|
+
if (age > threshold) {
|
|
100
|
+
gaps.push({
|
|
101
|
+
type: 'stale',
|
|
102
|
+
lane,
|
|
103
|
+
file,
|
|
104
|
+
severity: 'low',
|
|
105
|
+
age,
|
|
106
|
+
summary: `Stale: ${lane}/${file} (${Math.round(age / 86400000)}d old)`,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
} catch { /* skip unreadable */ }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check for roadmap presence
|
|
115
|
+
const roadmapPath = join(docsBase, 'roadmap.md');
|
|
116
|
+
const altRoadmapPath = join(target.path, '.cx', 'roadmap.md');
|
|
117
|
+
if (!existsSync(roadmapPath) && !existsSync(altRoadmapPath)) {
|
|
118
|
+
if (opts.snapshot?.sections?.length) {
|
|
119
|
+
gaps.push({ type: 'missing-roadmap', severity: 'low', summary: 'No roadmap.md found — can generate from snapshot' });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Role-lens driven gap detection
|
|
124
|
+
if (opts.roleLens?.artifactBias?.length) {
|
|
125
|
+
for (const biasLane of opts.roleLens.artifactBias) {
|
|
126
|
+
const lanePath = join(docsBase, biasLane);
|
|
127
|
+
const files = existsSync(lanePath) ? safeReaddir(lanePath) : [];
|
|
128
|
+
if (!files.length) {
|
|
129
|
+
gaps.push({
|
|
130
|
+
type: 'role-gap',
|
|
131
|
+
lane: biasLane,
|
|
132
|
+
severity: 'medium',
|
|
133
|
+
summary: `Role-prioritized lane "${biasLane}" is empty (${opts.roleLens.roles?.primary ?? 'configured role'} focuses here)`,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return gaps;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Scan pending intake packets for patterns that warrant doc creation.
|
|
144
|
+
* Returns 'recommend-artifact' gaps when multiple items share a topic.
|
|
145
|
+
*/
|
|
146
|
+
export function detectIntakeDocGaps(rootDir) {
|
|
147
|
+
const gaps = [];
|
|
148
|
+
const pendingDir = join(rootDir, '.cx', 'intake', 'pending');
|
|
149
|
+
if (!existsSync(pendingDir)) return gaps;
|
|
150
|
+
|
|
151
|
+
let pendingFiles;
|
|
152
|
+
try {
|
|
153
|
+
pendingFiles = readdirSync(pendingDir).filter((f) => f.endsWith('.json'));
|
|
154
|
+
} catch (err) {
|
|
155
|
+
process.stderr.write('[docs-lifecycle.mjs] read-pending-intake: ' + (err?.message ?? String(err)) + '\n');
|
|
156
|
+
return gaps;
|
|
157
|
+
}
|
|
158
|
+
if (pendingFiles.length < 2) return gaps; // need 2+ items for a pattern
|
|
159
|
+
|
|
160
|
+
// Read all pending packets
|
|
161
|
+
const packets = [];
|
|
162
|
+
for (const file of pendingFiles) {
|
|
163
|
+
try {
|
|
164
|
+
const data = JSON.parse(readFileSync(join(pendingDir, file), 'utf8'));
|
|
165
|
+
if (data?.triage) packets.push({ id: file.replace('.json', ''), ...data });
|
|
166
|
+
} catch { /* skip malformed */ }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Group by intakeType
|
|
170
|
+
const byType = {};
|
|
171
|
+
for (const p of packets) {
|
|
172
|
+
const t = p.triage?.intakeType || 'unknown';
|
|
173
|
+
byType[t] = byType[t] || [];
|
|
174
|
+
byType[t].push(p);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Recommend artifacts when multiple items share a type
|
|
178
|
+
for (const [type, items] of Object.entries(byType)) {
|
|
179
|
+
if (items.length < 2) continue;
|
|
180
|
+
|
|
181
|
+
const typeToLane = {
|
|
182
|
+
'customer-feedback': 'prds',
|
|
183
|
+
'bug-report': 'postmortems',
|
|
184
|
+
'incident-report': 'postmortems',
|
|
185
|
+
'feature-request': 'rfcs',
|
|
186
|
+
'support-ticket': 'runbooks',
|
|
187
|
+
'compliance': 'adrs',
|
|
188
|
+
};
|
|
189
|
+
const lane = typeToLane[type];
|
|
190
|
+
if (!lane) continue;
|
|
191
|
+
|
|
192
|
+
const lanePath = join(rootDir, 'docs', lane);
|
|
193
|
+
const exists = existsSync(lanePath) && safeReaddir(lanePath).length > 0;
|
|
194
|
+
|
|
195
|
+
gaps.push({
|
|
196
|
+
type: 'recommend-artifact',
|
|
197
|
+
lane,
|
|
198
|
+
severity: 'medium',
|
|
199
|
+
summary: `${items.length} pending intake items of type "${type}" — ${exists ? 'consider reviewing existing' : 'consider creating'} ${lane} docs`,
|
|
200
|
+
payload: { count: items.length, intakeType: type, intakeIds: items.map((i) => i.id) },
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return gaps;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Run the full docs lifecycle check for all resolved targets.
|
|
209
|
+
*
|
|
210
|
+
* @param {object} opts
|
|
211
|
+
* @param {object} opts.config - Normalized embed config
|
|
212
|
+
* @param {object} opts.providerRegistry
|
|
213
|
+
* @param {object} [opts.snapshot] - Latest snapshot
|
|
214
|
+
* @param {object} [opts.authorityGuard]
|
|
215
|
+
* @param {object[]} [opts.signals] - Source signals for target discovery
|
|
216
|
+
* @returns {Promise<DocsLifecycleResult>}
|
|
217
|
+
*/
|
|
218
|
+
export async function runDocsLifecycle(opts) {
|
|
219
|
+
const { config, providerRegistry, snapshot, authorityGuard, signals } = opts;
|
|
220
|
+
|
|
221
|
+
const targets = await resolveTargets(config, providerRegistry, { signals });
|
|
222
|
+
const roleLens = buildRoleLens(config.roles);
|
|
223
|
+
const allGaps = [];
|
|
224
|
+
const actions = [];
|
|
225
|
+
const recommendations = [];
|
|
226
|
+
const conflicts = [];
|
|
227
|
+
|
|
228
|
+
// Auto-suppress stale recommendations before generating new ones
|
|
229
|
+
const suppressed = autoSuppressStale();
|
|
230
|
+
|
|
231
|
+
for (const target of targets) {
|
|
232
|
+
const gaps = detectDocGaps(target, { snapshot, roleLens });
|
|
233
|
+
allGaps.push(...gaps.map((g) => ({ ...g, target: targetLabel(target) })));
|
|
234
|
+
|
|
235
|
+
// Check intake patterns for artifact recommendations (uses recommendation store for dedup)
|
|
236
|
+
const intakeGaps = detectIntakeDocGaps(target.path);
|
|
237
|
+
for (const gap of intakeGaps) {
|
|
238
|
+
const payload = gap.payload || {};
|
|
239
|
+
const recResult = createRecommendation({
|
|
240
|
+
type: gap.lane?.replace(/s$/, '') || 'prd',
|
|
241
|
+
title: payload.intakeType
|
|
242
|
+
? `${payload.intakeType.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase())} documentation`
|
|
243
|
+
: `Documentation for ${gap.lane}`,
|
|
244
|
+
reason: gap.summary,
|
|
245
|
+
signalCount: payload.count || 1,
|
|
246
|
+
customerImpact: payload.count >= 5 ? 2 : payload.count >= 3 ? 1 : 0,
|
|
247
|
+
recencyBonus: 2,
|
|
248
|
+
sourceSignalIds: payload.intakeIds || [],
|
|
249
|
+
lane: gap.lane,
|
|
250
|
+
});
|
|
251
|
+
recommendations.push(recResult);
|
|
252
|
+
allGaps.push({ ...gap, target: targetLabel(target), recommendation: recResult });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Check existing artifact coverage (PRDs, ADRs, RFCs)
|
|
256
|
+
if (snapshot) {
|
|
257
|
+
const artifactRecs = recommendArtifacts(snapshot, { rootDir: target.path });
|
|
258
|
+
for (const rec of artifactRecs) {
|
|
259
|
+
// Use recommendation store for dedup
|
|
260
|
+
const { active, existing } = isRecommendationActive(rec.type, rec.title);
|
|
261
|
+
if (!active) {
|
|
262
|
+
const recResult = createRecommendation({
|
|
263
|
+
type: rec.type,
|
|
264
|
+
title: rec.title,
|
|
265
|
+
reason: rec.reason,
|
|
266
|
+
signalCount: 1,
|
|
267
|
+
});
|
|
268
|
+
recommendations.push(recResult);
|
|
269
|
+
allGaps.push({
|
|
270
|
+
type: 'recommend-artifact',
|
|
271
|
+
lane: rec.type,
|
|
272
|
+
severity: 'medium',
|
|
273
|
+
summary: `${rec.title}: ${rec.reason}`,
|
|
274
|
+
payload: rec,
|
|
275
|
+
target: targetLabel(target),
|
|
276
|
+
recommendation: recResult,
|
|
277
|
+
});
|
|
278
|
+
} else {
|
|
279
|
+
// Already recommended — still surface in gaps but mark as existing
|
|
280
|
+
allGaps.push({
|
|
281
|
+
type: 'recommend-artifact',
|
|
282
|
+
lane: rec.type,
|
|
283
|
+
severity: 'low',
|
|
284
|
+
summary: `Already recommended: ${rec.title}`,
|
|
285
|
+
payload: { ...rec, existingRecommendation: existing?.id },
|
|
286
|
+
target: targetLabel(target),
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Run conflict detection on pending intake items
|
|
293
|
+
if (snapshot?.intakePackets?.length) {
|
|
294
|
+
try {
|
|
295
|
+
const packetConflicts = await detectConflicts(snapshot.intakePackets[0], { rootDir: target.path });
|
|
296
|
+
conflicts.push(...packetConflicts.map(c => ({ ...c, target: targetLabel(target) })));
|
|
297
|
+
} catch (err) {
|
|
298
|
+
process.stderr.write(`[docs-lifecycle] Conflict detection error: ${err.message}\n`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Determine actions for each gap
|
|
303
|
+
for (const gap of gaps) {
|
|
304
|
+
const risk = RISK_LEVEL[gap.lane] ?? (gap.type === 'missing-roadmap' ? 'low' : 'high');
|
|
305
|
+
|
|
306
|
+
if (risk === 'low') {
|
|
307
|
+
// Can act autonomously
|
|
308
|
+
actions.push({ gap, target, action: 'auto-fix', risk });
|
|
309
|
+
} else {
|
|
310
|
+
// Queue for approval
|
|
311
|
+
if (authorityGuard) {
|
|
312
|
+
const decision = await authorityGuard.check('publishDocs', {
|
|
313
|
+
description: gap.summary,
|
|
314
|
+
payload: { lane: gap.lane, target: targetLabel(target) },
|
|
315
|
+
});
|
|
316
|
+
actions.push({ gap, target, action: decision.allowed ? 'approved' : 'queued', risk, queueId: decision.queueId });
|
|
317
|
+
} else {
|
|
318
|
+
actions.push({ gap, target, action: 'skipped-no-guard', risk });
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const stats = recommendationStats();
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
gaps: allGaps,
|
|
328
|
+
actions,
|
|
329
|
+
targets: targets.length,
|
|
330
|
+
recommendations,
|
|
331
|
+
conflicts,
|
|
332
|
+
suppressedStale: suppressed,
|
|
333
|
+
recommendationStats: stats,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function targetLabel(t) {
|
|
338
|
+
if (t.type === 'workspace') return 'workspace';
|
|
339
|
+
return t.ref ?? t.path ?? 'unknown';
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function safeReaddir(dir) {
|
|
343
|
+
try {
|
|
344
|
+
return readdirSync(dir).filter((f) => !f.startsWith('.'));
|
|
345
|
+
} catch (err) {
|
|
346
|
+
process.stderr.write('[docs-lifecycle.mjs] safeReaddir: ' + (err?.message ?? String(err)) + '\n');
|
|
347
|
+
return [];
|
|
348
|
+
}
|
|
349
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/embed/inbox-live-watcher.mjs — reactive fs.watch wrapper around InboxWatcher.
|
|
3
|
+
*
|
|
4
|
+
* Registers a node:fs.watch listener on each inbox directory and triggers
|
|
5
|
+
* `InboxWatcher.poll()` shortly after filesystem activity settles, so files
|
|
6
|
+
* dropped into `.cx/inbox/` flow through ingest + the review queue in
|
|
7
|
+
* seconds rather than waiting for the scheduler's next interval.
|
|
8
|
+
*
|
|
9
|
+
* Two limits keep this stable across platforms:
|
|
10
|
+
*
|
|
11
|
+
* - Debounce window (default 750ms): batches a multi-file paste into one
|
|
12
|
+
* poll. fs.watch fires per-entry on Linux and sometimes twice per entry
|
|
13
|
+
* on macOS; debouncing collapses both cases.
|
|
14
|
+
*
|
|
15
|
+
* - In-flight guard: if a poll is already running when an event fires,
|
|
16
|
+
* mark the watcher dirty and re-poll once the current one finishes.
|
|
17
|
+
* Prevents overlapping pgvector writes on rapid bursts.
|
|
18
|
+
*
|
|
19
|
+
* Inbox dirs are flat by design, so recursive watching is not required —
|
|
20
|
+
* top-level fs.watch is enough and avoids the cross-platform recursive
|
|
21
|
+
* issues that plague larger trees. fs.watch silently misses events on
|
|
22
|
+
* network mounts and certain Docker volume drivers; correctness lives in
|
|
23
|
+
* the scheduler poll, this watcher is a latency layer.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { watch } from 'node:fs';
|
|
27
|
+
|
|
28
|
+
const DEFAULT_DEBOUNCE_MS = 750;
|
|
29
|
+
|
|
30
|
+
export class InboxLiveWatcher {
|
|
31
|
+
#inboxWatcher;
|
|
32
|
+
#debounceMs;
|
|
33
|
+
#watchFn;
|
|
34
|
+
#onPoll;
|
|
35
|
+
#onError;
|
|
36
|
+
#handles = [];
|
|
37
|
+
#pending = false;
|
|
38
|
+
#running = false;
|
|
39
|
+
#debounceTimer = null;
|
|
40
|
+
#started = false;
|
|
41
|
+
|
|
42
|
+
constructor({
|
|
43
|
+
inboxWatcher,
|
|
44
|
+
debounceMs = DEFAULT_DEBOUNCE_MS,
|
|
45
|
+
watchFn = watch,
|
|
46
|
+
onPoll = () => {},
|
|
47
|
+
onError = () => {},
|
|
48
|
+
} = {}) {
|
|
49
|
+
if (!inboxWatcher) throw new Error('InboxLiveWatcher: inboxWatcher is required');
|
|
50
|
+
this.#inboxWatcher = inboxWatcher;
|
|
51
|
+
this.#debounceMs = debounceMs;
|
|
52
|
+
this.#watchFn = watchFn;
|
|
53
|
+
this.#onPoll = onPoll;
|
|
54
|
+
this.#onError = onError;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
start() {
|
|
58
|
+
if (this.#started) return { watched: this.#handles.length };
|
|
59
|
+
this.#started = true;
|
|
60
|
+
|
|
61
|
+
const dirs = this.#inboxWatcher.dirs();
|
|
62
|
+
for (const dir of dirs) {
|
|
63
|
+
try {
|
|
64
|
+
const handle = this.#watchFn(dir, { persistent: true }, (eventType, filename) => {
|
|
65
|
+
if (filename && filename.startsWith('.')) return;
|
|
66
|
+
this.#trigger();
|
|
67
|
+
});
|
|
68
|
+
if (handle && typeof handle.on === 'function') {
|
|
69
|
+
handle.on('error', (err) => this.#onError(err, dir));
|
|
70
|
+
}
|
|
71
|
+
this.#handles.push({ dir, handle });
|
|
72
|
+
} catch (err) {
|
|
73
|
+
this.#onError(err, dir);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { watched: this.#handles.length, dirs };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
stop() {
|
|
80
|
+
if (!this.#started) return;
|
|
81
|
+
this.#started = false;
|
|
82
|
+
if (this.#debounceTimer) {
|
|
83
|
+
clearTimeout(this.#debounceTimer);
|
|
84
|
+
this.#debounceTimer = null;
|
|
85
|
+
}
|
|
86
|
+
for (const { handle } of this.#handles) {
|
|
87
|
+
try { handle.close?.(); } catch { /* ignore */ }
|
|
88
|
+
}
|
|
89
|
+
this.#handles = [];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#trigger() {
|
|
93
|
+
if (this.#debounceTimer) clearTimeout(this.#debounceTimer);
|
|
94
|
+
this.#debounceTimer = setTimeout(() => {
|
|
95
|
+
this.#debounceTimer = null;
|
|
96
|
+
this.#runPoll();
|
|
97
|
+
}, this.#debounceMs);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async #runPoll() {
|
|
101
|
+
if (this.#running) {
|
|
102
|
+
this.#pending = true;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
this.#running = true;
|
|
106
|
+
try {
|
|
107
|
+
const result = await this.#inboxWatcher.poll();
|
|
108
|
+
this.#onPoll(result);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
this.#onError(err, null);
|
|
111
|
+
} finally {
|
|
112
|
+
this.#running = false;
|
|
113
|
+
if (this.#pending) {
|
|
114
|
+
this.#pending = false;
|
|
115
|
+
setImmediate(() => this.#runPoll());
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|