@ag-eco/agentplate-cli 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +462 -0
- package/agents/ap-co-creation.md +90 -0
- package/agents/builder.md +144 -0
- package/agents/coordinator.md +377 -0
- package/agents/lead.md +435 -0
- package/agents/merger.md +164 -0
- package/agents/monitor.md +214 -0
- package/agents/orchestrator.md +239 -0
- package/agents/reviewer.md +140 -0
- package/agents/scout.md +125 -0
- package/agents/supervisor.md +427 -0
- package/package.json +66 -0
- package/src/agents/capabilities.test.ts +85 -0
- package/src/agents/capabilities.ts +125 -0
- package/src/agents/checkpoint.test.ts +88 -0
- package/src/agents/checkpoint.ts +101 -0
- package/src/agents/copilot-hooks-deployer.test.ts +162 -0
- package/src/agents/copilot-hooks-deployer.ts +93 -0
- package/src/agents/guard-rules.test.ts +372 -0
- package/src/agents/guard-rules.ts +97 -0
- package/src/agents/headless-mail-injector.test.ts +709 -0
- package/src/agents/headless-mail-injector.ts +377 -0
- package/src/agents/headless-prompt.test.ts +102 -0
- package/src/agents/headless-prompt.ts +68 -0
- package/src/agents/hooks-deployer.test.ts +3119 -0
- package/src/agents/hooks-deployer.ts +804 -0
- package/src/agents/identity.test.ts +604 -0
- package/src/agents/identity.ts +384 -0
- package/src/agents/lifecycle.test.ts +196 -0
- package/src/agents/lifecycle.ts +183 -0
- package/src/agents/mail-poll-detect.test.ts +153 -0
- package/src/agents/mail-poll-detect.ts +73 -0
- package/src/agents/manifest.test.ts +1026 -0
- package/src/agents/manifest.ts +376 -0
- package/src/agents/overlay.test.ts +1058 -0
- package/src/agents/overlay.ts +490 -0
- package/src/agents/scope-detect.test.ts +190 -0
- package/src/agents/scope-detect.ts +146 -0
- package/src/agents/turn-lock.test.ts +181 -0
- package/src/agents/turn-lock.ts +235 -0
- package/src/agents/turn-runner-dispatch.test.ts +182 -0
- package/src/agents/turn-runner-dispatch.ts +105 -0
- package/src/agents/turn-runner.test.ts +2312 -0
- package/src/agents/turn-runner.ts +1383 -0
- package/src/beads/client.test.ts +217 -0
- package/src/beads/client.ts +230 -0
- package/src/beads/molecules.test.ts +338 -0
- package/src/beads/molecules.ts +198 -0
- package/src/commands/agents.test.ts +328 -0
- package/src/commands/agents.ts +299 -0
- package/src/commands/clean.test.ts +797 -0
- package/src/commands/clean.ts +791 -0
- package/src/commands/completions.test.ts +348 -0
- package/src/commands/completions.ts +981 -0
- package/src/commands/coordinator.test.ts +2975 -0
- package/src/commands/coordinator.ts +1841 -0
- package/src/commands/costs.test.ts +1183 -0
- package/src/commands/costs.ts +599 -0
- package/src/commands/dashboard.test.ts +954 -0
- package/src/commands/dashboard.ts +1212 -0
- package/src/commands/discover.test.ts +288 -0
- package/src/commands/discover.ts +202 -0
- package/src/commands/doctor.test.ts +303 -0
- package/src/commands/doctor.ts +311 -0
- package/src/commands/ecosystem.test.ts +226 -0
- package/src/commands/ecosystem.ts +248 -0
- package/src/commands/errors.test.ts +654 -0
- package/src/commands/errors.ts +197 -0
- package/src/commands/feed.test.ts +709 -0
- package/src/commands/feed.ts +260 -0
- package/src/commands/group.test.ts +475 -0
- package/src/commands/group.ts +546 -0
- package/src/commands/hooks.test.ts +458 -0
- package/src/commands/hooks.ts +263 -0
- package/src/commands/init.test.ts +1011 -0
- package/src/commands/init.ts +967 -0
- package/src/commands/inspect.test.ts +1239 -0
- package/src/commands/inspect.ts +648 -0
- package/src/commands/log.test.ts +1913 -0
- package/src/commands/log.ts +958 -0
- package/src/commands/logs.test.ts +801 -0
- package/src/commands/logs.ts +483 -0
- package/src/commands/mail.test.ts +1501 -0
- package/src/commands/mail.ts +848 -0
- package/src/commands/merge.test.ts +864 -0
- package/src/commands/merge.ts +381 -0
- package/src/commands/metrics.test.ts +458 -0
- package/src/commands/metrics.ts +129 -0
- package/src/commands/monitor.test.ts +191 -0
- package/src/commands/monitor.ts +409 -0
- package/src/commands/nudge.test.ts +579 -0
- package/src/commands/nudge.ts +646 -0
- package/src/commands/orchestrator.ts +42 -0
- package/src/commands/prime.test.ts +612 -0
- package/src/commands/prime.ts +359 -0
- package/src/commands/replay.test.ts +757 -0
- package/src/commands/replay.ts +231 -0
- package/src/commands/run.test.ts +469 -0
- package/src/commands/run.ts +353 -0
- package/src/commands/serve/agent-actions.test.ts +210 -0
- package/src/commands/serve/agent-actions.ts +192 -0
- package/src/commands/serve/build.test.ts +202 -0
- package/src/commands/serve/build.ts +206 -0
- package/src/commands/serve/coordinator-actions.test.ts +339 -0
- package/src/commands/serve/coordinator-actions.ts +410 -0
- package/src/commands/serve/dev.test.ts +168 -0
- package/src/commands/serve/dev.ts +117 -0
- package/src/commands/serve/mail-actions.test.ts +312 -0
- package/src/commands/serve/mail-actions.ts +167 -0
- package/src/commands/serve/rest.test.ts +1680 -0
- package/src/commands/serve/rest.ts +1130 -0
- package/src/commands/serve/static.ts +51 -0
- package/src/commands/serve/ws.test.ts +361 -0
- package/src/commands/serve/ws.ts +332 -0
- package/src/commands/serve.test.ts +459 -0
- package/src/commands/serve.ts +654 -0
- package/src/commands/sling.test.ts +1583 -0
- package/src/commands/sling.ts +1351 -0
- package/src/commands/spec.test.ts +179 -0
- package/src/commands/spec.ts +105 -0
- package/src/commands/status.test.ts +614 -0
- package/src/commands/status.ts +403 -0
- package/src/commands/stop.test.ts +964 -0
- package/src/commands/stop.ts +319 -0
- package/src/commands/supervisor.test.ts +185 -0
- package/src/commands/supervisor.ts +537 -0
- package/src/commands/trace.test.ts +762 -0
- package/src/commands/trace.ts +205 -0
- package/src/commands/update.test.ts +466 -0
- package/src/commands/update.ts +263 -0
- package/src/commands/upgrade.test.ts +48 -0
- package/src/commands/upgrade.ts +240 -0
- package/src/commands/watch.test.ts +257 -0
- package/src/commands/watch.ts +308 -0
- package/src/commands/worktree.test.ts +1297 -0
- package/src/commands/worktree.ts +451 -0
- package/src/config.test.ts +1535 -0
- package/src/config.ts +1064 -0
- package/src/doctor/agents.test.ts +523 -0
- package/src/doctor/agents.ts +399 -0
- package/src/doctor/config-check.test.ts +191 -0
- package/src/doctor/config-check.ts +183 -0
- package/src/doctor/consistency.test.ts +807 -0
- package/src/doctor/consistency.ts +347 -0
- package/src/doctor/databases.test.ts +350 -0
- package/src/doctor/databases.ts +243 -0
- package/src/doctor/dependencies.test.ts +296 -0
- package/src/doctor/dependencies.ts +272 -0
- package/src/doctor/ecosystem.test.ts +308 -0
- package/src/doctor/ecosystem.ts +156 -0
- package/src/doctor/logs.test.ts +253 -0
- package/src/doctor/logs.ts +295 -0
- package/src/doctor/merge-queue.test.ts +315 -0
- package/src/doctor/merge-queue.ts +167 -0
- package/src/doctor/providers.test.ts +409 -0
- package/src/doctor/providers.ts +250 -0
- package/src/doctor/serve.test.ts +95 -0
- package/src/doctor/serve.ts +86 -0
- package/src/doctor/structure.test.ts +423 -0
- package/src/doctor/structure.ts +285 -0
- package/src/doctor/types.ts +43 -0
- package/src/doctor/version.test.ts +241 -0
- package/src/doctor/version.ts +132 -0
- package/src/doctor/watchdog.test.ts +167 -0
- package/src/doctor/watchdog.ts +214 -0
- package/src/e2e/init-sling-lifecycle.test.ts +283 -0
- package/src/errors.test.ts +350 -0
- package/src/errors.ts +217 -0
- package/src/events/store.test.ts +660 -0
- package/src/events/store.ts +369 -0
- package/src/events/tailer.test.ts +719 -0
- package/src/events/tailer.ts +332 -0
- package/src/events/tool-filter.test.ts +330 -0
- package/src/events/tool-filter.ts +126 -0
- package/src/index.ts +533 -0
- package/src/insights/analyzer.test.ts +466 -0
- package/src/insights/analyzer.ts +203 -0
- package/src/insights/quality-gates.test.ts +141 -0
- package/src/insights/quality-gates.ts +156 -0
- package/src/json.test.ts +72 -0
- package/src/json.ts +53 -0
- package/src/loam/client.test.ts +752 -0
- package/src/loam/client.ts +664 -0
- package/src/logging/color.test.ts +252 -0
- package/src/logging/color.ts +105 -0
- package/src/logging/format.test.ts +110 -0
- package/src/logging/format.ts +255 -0
- package/src/logging/logger.test.ts +814 -0
- package/src/logging/logger.ts +266 -0
- package/src/logging/reporter.test.ts +259 -0
- package/src/logging/reporter.ts +110 -0
- package/src/logging/sanitizer.test.ts +190 -0
- package/src/logging/sanitizer.ts +57 -0
- package/src/logging/theme.ts +140 -0
- package/src/mail/broadcast.test.ts +204 -0
- package/src/mail/broadcast.ts +92 -0
- package/src/mail/client.test.ts +774 -0
- package/src/mail/client.ts +236 -0
- package/src/mail/store.test.ts +898 -0
- package/src/mail/store.ts +425 -0
- package/src/merge/lock.test.ts +149 -0
- package/src/merge/lock.ts +140 -0
- package/src/merge/predict.test.ts +387 -0
- package/src/merge/predict.ts +249 -0
- package/src/merge/queue.test.ts +426 -0
- package/src/merge/queue.ts +246 -0
- package/src/merge/resolver.test.ts +1993 -0
- package/src/merge/resolver.ts +926 -0
- package/src/metrics/pricing.test.ts +258 -0
- package/src/metrics/pricing.ts +135 -0
- package/src/metrics/store.test.ts +978 -0
- package/src/metrics/store.ts +501 -0
- package/src/metrics/summary.test.ts +398 -0
- package/src/metrics/summary.ts +178 -0
- package/src/metrics/transcript.test.ts +483 -0
- package/src/metrics/transcript.ts +114 -0
- package/src/runtimes/__fixtures__/claude-stream-fixture.ts +22 -0
- package/src/runtimes/aider.test.ts +124 -0
- package/src/runtimes/aider.ts +147 -0
- package/src/runtimes/amp.test.ts +164 -0
- package/src/runtimes/amp.ts +154 -0
- package/src/runtimes/claude.test.ts +1474 -0
- package/src/runtimes/claude.ts +579 -0
- package/src/runtimes/codex.test.ts +805 -0
- package/src/runtimes/codex.ts +273 -0
- package/src/runtimes/connections.test.ts +214 -0
- package/src/runtimes/connections.ts +103 -0
- package/src/runtimes/copilot.test.ts +707 -0
- package/src/runtimes/copilot.ts +316 -0
- package/src/runtimes/cursor.test.ts +497 -0
- package/src/runtimes/cursor.ts +205 -0
- package/src/runtimes/gemini.test.ts +537 -0
- package/src/runtimes/gemini.ts +243 -0
- package/src/runtimes/goose.test.ts +133 -0
- package/src/runtimes/goose.ts +157 -0
- package/src/runtimes/headless-connection.test.ts +264 -0
- package/src/runtimes/headless-connection.ts +158 -0
- package/src/runtimes/opencode.test.ts +325 -0
- package/src/runtimes/opencode.ts +188 -0
- package/src/runtimes/pi-guards.test.ts +486 -0
- package/src/runtimes/pi-guards.ts +367 -0
- package/src/runtimes/pi.test.ts +789 -0
- package/src/runtimes/pi.ts +305 -0
- package/src/runtimes/registry.test.ts +196 -0
- package/src/runtimes/registry.ts +99 -0
- package/src/runtimes/sapling.test.ts +1267 -0
- package/src/runtimes/sapling.ts +710 -0
- package/src/runtimes/types.ts +266 -0
- package/src/schema-consistency.test.ts +246 -0
- package/src/sessions/compat.test.ts +281 -0
- package/src/sessions/compat.ts +105 -0
- package/src/sessions/store.test.ts +1748 -0
- package/src/sessions/store.ts +858 -0
- package/src/test-helpers.test.ts +124 -0
- package/src/test-helpers.ts +145 -0
- package/src/test-setup.test.ts +31 -0
- package/src/test-setup.ts +28 -0
- package/src/tools/loam/api.ts +368 -0
- package/src/tools/loam/cli.ts +278 -0
- package/src/tools/loam/commands/add.ts +52 -0
- package/src/tools/loam/commands/archive.ts +214 -0
- package/src/tools/loam/commands/audit.ts +276 -0
- package/src/tools/loam/commands/compact.ts +1062 -0
- package/src/tools/loam/commands/completions.ts +79 -0
- package/src/tools/loam/commands/config.ts +381 -0
- package/src/tools/loam/commands/delete-domain.ts +121 -0
- package/src/tools/loam/commands/delete.ts +316 -0
- package/src/tools/loam/commands/diff.ts +200 -0
- package/src/tools/loam/commands/doctor.ts +1113 -0
- package/src/tools/loam/commands/edit.ts +226 -0
- package/src/tools/loam/commands/init.ts +31 -0
- package/src/tools/loam/commands/learn.ts +179 -0
- package/src/tools/loam/commands/move.ts +323 -0
- package/src/tools/loam/commands/onboard.ts +374 -0
- package/src/tools/loam/commands/outcome.ts +185 -0
- package/src/tools/loam/commands/prime.ts +688 -0
- package/src/tools/loam/commands/prune.ts +614 -0
- package/src/tools/loam/commands/query.ts +218 -0
- package/src/tools/loam/commands/rank.ts +180 -0
- package/src/tools/loam/commands/ready.ts +189 -0
- package/src/tools/loam/commands/record.ts +1210 -0
- package/src/tools/loam/commands/restore.ts +166 -0
- package/src/tools/loam/commands/search.ts +327 -0
- package/src/tools/loam/commands/setup.ts +887 -0
- package/src/tools/loam/commands/status.ts +103 -0
- package/src/tools/loam/commands/sync.ts +298 -0
- package/src/tools/loam/commands/update.ts +19 -0
- package/src/tools/loam/commands/upgrade.ts +93 -0
- package/src/tools/loam/commands/validate.ts +190 -0
- package/src/tools/loam/index.ts +62 -0
- package/src/tools/loam/log.ts +127 -0
- package/src/tools/loam/registry/builtins.ts +409 -0
- package/src/tools/loam/registry/custom.ts +431 -0
- package/src/tools/loam/registry/init.ts +55 -0
- package/src/tools/loam/registry/template.ts +40 -0
- package/src/tools/loam/registry/type-registry.ts +113 -0
- package/src/tools/loam/schemas/config-schema.ts +489 -0
- package/src/tools/loam/schemas/config.ts +245 -0
- package/src/tools/loam/schemas/index.ts +18 -0
- package/src/tools/loam/schemas/record-schema.ts +191 -0
- package/src/tools/loam/schemas/record.ts +115 -0
- package/src/tools/loam/utils/active-work.ts +205 -0
- package/src/tools/loam/utils/anchor-validity.ts +80 -0
- package/src/tools/loam/utils/archive.ts +146 -0
- package/src/tools/loam/utils/audit.ts +667 -0
- package/src/tools/loam/utils/bm25.ts +238 -0
- package/src/tools/loam/utils/budget.ts +142 -0
- package/src/tools/loam/utils/config.ts +344 -0
- package/src/tools/loam/utils/dir-anchors.ts +62 -0
- package/src/tools/loam/utils/domain-rules.ts +114 -0
- package/src/tools/loam/utils/expertise.ts +393 -0
- package/src/tools/loam/utils/format-helpers.ts +96 -0
- package/src/tools/loam/utils/format.ts +1234 -0
- package/src/tools/loam/utils/git-context.ts +50 -0
- package/src/tools/loam/utils/git.ts +183 -0
- package/src/tools/loam/utils/hooks.ts +299 -0
- package/src/tools/loam/utils/index.ts +52 -0
- package/src/tools/loam/utils/json-output.ts +13 -0
- package/src/tools/loam/utils/lock.ts +76 -0
- package/src/tools/loam/utils/markers.ts +48 -0
- package/src/tools/loam/utils/numeric-flags.ts +20 -0
- package/src/tools/loam/utils/palette.ts +44 -0
- package/src/tools/loam/utils/prime-ranking.ts +135 -0
- package/src/tools/loam/utils/recipe-discovery.ts +195 -0
- package/src/tools/loam/utils/runtime-flags.ts +28 -0
- package/src/tools/loam/utils/scoring.ts +94 -0
- package/src/tools/loam/utils/version.ts +116 -0
- package/src/tools/sprout/commands/block.ts +64 -0
- package/src/tools/sprout/commands/blocked.ts +86 -0
- package/src/tools/sprout/commands/close.ts +129 -0
- package/src/tools/sprout/commands/completions.ts +198 -0
- package/src/tools/sprout/commands/config.ts +238 -0
- package/src/tools/sprout/commands/create.ts +164 -0
- package/src/tools/sprout/commands/dep.ts +148 -0
- package/src/tools/sprout/commands/doctor.ts +979 -0
- package/src/tools/sprout/commands/init.ts +83 -0
- package/src/tools/sprout/commands/label.ts +178 -0
- package/src/tools/sprout/commands/list.ts +210 -0
- package/src/tools/sprout/commands/migrate.ts +133 -0
- package/src/tools/sprout/commands/onboard.ts +207 -0
- package/src/tools/sprout/commands/plan-show.ts +278 -0
- package/src/tools/sprout/commands/plan.ts +2526 -0
- package/src/tools/sprout/commands/prime.ts +399 -0
- package/src/tools/sprout/commands/ready.ts +245 -0
- package/src/tools/sprout/commands/search.ts +221 -0
- package/src/tools/sprout/commands/show.ts +277 -0
- package/src/tools/sprout/commands/stats.ts +146 -0
- package/src/tools/sprout/commands/sync.ts +134 -0
- package/src/tools/sprout/commands/tpl.ts +364 -0
- package/src/tools/sprout/commands/unblock.ts +115 -0
- package/src/tools/sprout/commands/update.ts +257 -0
- package/src/tools/sprout/commands/upgrade.ts +91 -0
- package/src/tools/sprout/config-schema.ts +152 -0
- package/src/tools/sprout/config.ts +355 -0
- package/src/tools/sprout/filter.ts +107 -0
- package/src/tools/sprout/format.ts +43 -0
- package/src/tools/sprout/id.ts +22 -0
- package/src/tools/sprout/index.ts +204 -0
- package/src/tools/sprout/log.ts +76 -0
- package/src/tools/sprout/markers.ts +22 -0
- package/src/tools/sprout/output.ts +121 -0
- package/src/tools/sprout/plan-backref.ts +93 -0
- package/src/tools/sprout/plan-context.ts +81 -0
- package/src/tools/sprout/plan-domain.ts +139 -0
- package/src/tools/sprout/plan-lifecycle.ts +65 -0
- package/src/tools/sprout/plan-loam.ts +207 -0
- package/src/tools/sprout/plan-schema.ts +209 -0
- package/src/tools/sprout/sort.ts +31 -0
- package/src/tools/sprout/store.ts +172 -0
- package/src/tools/sprout/types.ts +118 -0
- package/src/tools/sprout/validation.ts +119 -0
- package/src/tools/sprout/version.ts +1 -0
- package/src/tools/sprout/yaml.ts +387 -0
- package/src/tools/trellis/commands/archive.ts +87 -0
- package/src/tools/trellis/commands/completions.ts +610 -0
- package/src/tools/trellis/commands/config.ts +382 -0
- package/src/tools/trellis/commands/create.ts +252 -0
- package/src/tools/trellis/commands/diff.ts +150 -0
- package/src/tools/trellis/commands/doctor.ts +771 -0
- package/src/tools/trellis/commands/emit.ts +365 -0
- package/src/tools/trellis/commands/history.ts +83 -0
- package/src/tools/trellis/commands/import.ts +198 -0
- package/src/tools/trellis/commands/init.ts +81 -0
- package/src/tools/trellis/commands/list.ts +103 -0
- package/src/tools/trellis/commands/onboard.ts +156 -0
- package/src/tools/trellis/commands/pin.ts +172 -0
- package/src/tools/trellis/commands/prime.ts +193 -0
- package/src/tools/trellis/commands/render.ts +122 -0
- package/src/tools/trellis/commands/schema.ts +353 -0
- package/src/tools/trellis/commands/show.ts +115 -0
- package/src/tools/trellis/commands/stats.ts +65 -0
- package/src/tools/trellis/commands/sync.ts +112 -0
- package/src/tools/trellis/commands/tree.ts +123 -0
- package/src/tools/trellis/commands/update.ts +330 -0
- package/src/tools/trellis/commands/upgrade.ts +95 -0
- package/src/tools/trellis/commands/validate.ts +166 -0
- package/src/tools/trellis/config-schema.ts +81 -0
- package/src/tools/trellis/config.ts +108 -0
- package/src/tools/trellis/frontmatter.ts +348 -0
- package/src/tools/trellis/id.ts +24 -0
- package/src/tools/trellis/index.ts +209 -0
- package/src/tools/trellis/markers.ts +28 -0
- package/src/tools/trellis/output.ts +84 -0
- package/src/tools/trellis/render.ts +212 -0
- package/src/tools/trellis/store.ts +144 -0
- package/src/tools/trellis/types.ts +82 -0
- package/src/tools/trellis/validate.ts +199 -0
- package/src/tools/trellis/yaml.ts +309 -0
- package/src/tracker/beads.test.ts +454 -0
- package/src/tracker/beads.ts +56 -0
- package/src/tracker/factory.test.ts +90 -0
- package/src/tracker/factory.ts +65 -0
- package/src/tracker/sprout.test.ts +461 -0
- package/src/tracker/sprout.ts +182 -0
- package/src/tracker/types.ts +52 -0
- package/src/trellis/client.test.ts +107 -0
- package/src/trellis/client.ts +179 -0
- package/src/types.ts +970 -0
- package/src/utils/bin.test.ts +10 -0
- package/src/utils/bin.ts +37 -0
- package/src/utils/browser.test.ts +49 -0
- package/src/utils/browser.ts +48 -0
- package/src/utils/fs.test.ts +119 -0
- package/src/utils/fs.ts +62 -0
- package/src/utils/pid.test.ts +152 -0
- package/src/utils/pid.ts +130 -0
- package/src/utils/process-scan.test.ts +53 -0
- package/src/utils/process-scan.ts +76 -0
- package/src/utils/time.test.ts +43 -0
- package/src/utils/time.ts +37 -0
- package/src/utils/version.test.ts +33 -0
- package/src/utils/version.ts +70 -0
- package/src/version.ts +5 -0
- package/src/watchdog/daemon.test.ts +3721 -0
- package/src/watchdog/daemon.ts +1257 -0
- package/src/watchdog/health.test.ts +830 -0
- package/src/watchdog/health.ts +434 -0
- package/src/watchdog/triage.test.ts +205 -0
- package/src/watchdog/triage.ts +205 -0
- package/src/worktree/manager.test.ts +720 -0
- package/src/worktree/manager.ts +405 -0
- package/src/worktree/process.test.ts +172 -0
- package/src/worktree/process.ts +131 -0
- package/src/worktree/tmux.test.ts +1616 -0
- package/src/worktree/tmux.ts +721 -0
- package/templates/CLAUDE.md.tmpl +100 -0
- package/templates/copilot-hooks.json.tmpl +13 -0
- package/templates/hooks.json.tmpl +109 -0
- package/templates/overlay.md.tmpl +88 -0
- package/ui/dist/apple-touch-icon-bdy6teep.png +0 -0
- package/ui/dist/chunk-8s31f05k.css +1 -0
- package/ui/dist/chunk-vm5rz679.js +300 -0
- package/ui/dist/favicon-nzb39vza.svg +4 -0
- package/ui/dist/index.html +17 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { openSessionStore } from "../sessions/compat.ts";
|
|
4
|
+
import type { AgentplateConfig, AgentSession } from "../types.ts";
|
|
5
|
+
import { listWorktrees } from "../worktree/manager.ts";
|
|
6
|
+
import { isProcessAlive, listSessions, sanitizeTmuxName } from "../worktree/tmux.ts";
|
|
7
|
+
import type { DoctorCheck } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Dependencies for consistency checks.
|
|
11
|
+
* Allows injection for testing without module-level mocks.
|
|
12
|
+
*/
|
|
13
|
+
export interface ConsistencyCheckDeps {
|
|
14
|
+
listSessions: () => Promise<Array<{ name: string; pid: number }>>;
|
|
15
|
+
isProcessAlive: (pid: number) => boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Cross-subsystem consistency checks.
|
|
20
|
+
* Validates SessionStore vs worktrees, tmux sessions vs sessions, etc.
|
|
21
|
+
*
|
|
22
|
+
* @param config - Agentplate configuration
|
|
23
|
+
* @param agentplateDir - Absolute path to .agentplate/
|
|
24
|
+
* @param deps - Optional dependencies for testing (defaults to real implementations)
|
|
25
|
+
*/
|
|
26
|
+
export async function checkConsistency(
|
|
27
|
+
config: AgentplateConfig,
|
|
28
|
+
agentplateDir: string,
|
|
29
|
+
deps?: ConsistencyCheckDeps,
|
|
30
|
+
): Promise<DoctorCheck[]> {
|
|
31
|
+
// Use injected dependencies or defaults
|
|
32
|
+
const { listSessions: listSessionsFn, isProcessAlive: isProcessAliveFn } = deps || {
|
|
33
|
+
listSessions,
|
|
34
|
+
isProcessAlive,
|
|
35
|
+
};
|
|
36
|
+
const checks: DoctorCheck[] = [];
|
|
37
|
+
|
|
38
|
+
// Gather data from all three sources
|
|
39
|
+
let worktrees: Array<{ path: string; branch: string; head: string }> = [];
|
|
40
|
+
let tmuxSessions: Array<{ name: string; pid: number }> = [];
|
|
41
|
+
let storeSessions: AgentSession[] = [];
|
|
42
|
+
|
|
43
|
+
// 1. List git worktrees
|
|
44
|
+
try {
|
|
45
|
+
worktrees = await listWorktrees(config.project.root);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
checks.push({
|
|
48
|
+
name: "worktree-listing",
|
|
49
|
+
category: "consistency",
|
|
50
|
+
status: "fail",
|
|
51
|
+
message: "Failed to list git worktrees",
|
|
52
|
+
details: [error instanceof Error ? error.message : String(error)],
|
|
53
|
+
});
|
|
54
|
+
// Can't continue consistency checks without worktree data
|
|
55
|
+
return checks;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 2. List tmux sessions
|
|
59
|
+
try {
|
|
60
|
+
tmuxSessions = await listSessionsFn();
|
|
61
|
+
} catch (error) {
|
|
62
|
+
// Tmux not installed or not running is not necessarily a fatal error
|
|
63
|
+
checks.push({
|
|
64
|
+
name: "tmux-listing",
|
|
65
|
+
category: "consistency",
|
|
66
|
+
status: "warn",
|
|
67
|
+
message: "Failed to list tmux sessions (tmux may not be installed)",
|
|
68
|
+
details: [error instanceof Error ? error.message : String(error)],
|
|
69
|
+
});
|
|
70
|
+
// Continue with empty tmux session list
|
|
71
|
+
tmuxSessions = [];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 3. Open SessionStore and get all sessions
|
|
75
|
+
let storeHandle: ReturnType<typeof openSessionStore>["store"] | null = null;
|
|
76
|
+
try {
|
|
77
|
+
const { store } = openSessionStore(agentplateDir);
|
|
78
|
+
storeHandle = store;
|
|
79
|
+
storeSessions = store.getAll();
|
|
80
|
+
} catch (error) {
|
|
81
|
+
checks.push({
|
|
82
|
+
name: "sessionstore-open",
|
|
83
|
+
category: "consistency",
|
|
84
|
+
status: "fail",
|
|
85
|
+
message: "Failed to open SessionStore",
|
|
86
|
+
details: [error instanceof Error ? error.message : String(error)],
|
|
87
|
+
});
|
|
88
|
+
// Can't do consistency checks without SessionStore
|
|
89
|
+
return checks;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Completed/zombie sessions are retained for history and metrics. Their tmux
|
|
93
|
+
// sessions, PIDs, and worktrees may legitimately be gone.
|
|
94
|
+
const liveSessions = storeSessions.filter((s) => s.state !== "completed" && s.state !== "zombie");
|
|
95
|
+
|
|
96
|
+
// Now perform cross-validation checks
|
|
97
|
+
|
|
98
|
+
// 4. Check for orphaned worktrees (worktree exists but no SessionStore entry)
|
|
99
|
+
// Normalize all paths to handle symlinks like /tmp -> /private/tmp on macOS
|
|
100
|
+
const worktreeBasePath = realpathSync(join(agentplateDir, "worktrees"));
|
|
101
|
+
const agentplateWorktrees = worktrees.filter((wt) => wt.path.startsWith(worktreeBasePath));
|
|
102
|
+
|
|
103
|
+
// Normalize SessionStore paths for comparison
|
|
104
|
+
const storeWorktreePaths = new Set(
|
|
105
|
+
storeSessions.map((s) => {
|
|
106
|
+
try {
|
|
107
|
+
return realpathSync(s.worktreePath);
|
|
108
|
+
} catch {
|
|
109
|
+
// Path doesn't exist, use as-is
|
|
110
|
+
return s.worktreePath;
|
|
111
|
+
}
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const orphanedWorktrees = agentplateWorktrees.filter((wt) => !storeWorktreePaths.has(wt.path));
|
|
116
|
+
|
|
117
|
+
if (orphanedWorktrees.length > 0) {
|
|
118
|
+
checks.push({
|
|
119
|
+
name: "orphaned-worktrees",
|
|
120
|
+
category: "consistency",
|
|
121
|
+
status: "warn",
|
|
122
|
+
message: `Found ${orphanedWorktrees.length} orphaned worktree(s) with no SessionStore entry`,
|
|
123
|
+
details: orphanedWorktrees.map((wt) => `${wt.path} (branch: ${wt.branch})`),
|
|
124
|
+
fixable: true,
|
|
125
|
+
});
|
|
126
|
+
} else {
|
|
127
|
+
checks.push({
|
|
128
|
+
name: "orphaned-worktrees",
|
|
129
|
+
category: "consistency",
|
|
130
|
+
status: "pass",
|
|
131
|
+
message: "No orphaned worktrees found",
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 5. Check for orphaned tmux sessions (tmux session exists but no SessionStore entry)
|
|
136
|
+
const projectName = config.project.name;
|
|
137
|
+
const agentplateTmuxPrefix = `agentplate-${sanitizeTmuxName(projectName)}-`;
|
|
138
|
+
const agentplateTmuxSessions = tmuxSessions.filter((s) =>
|
|
139
|
+
s.name.startsWith(agentplateTmuxPrefix),
|
|
140
|
+
);
|
|
141
|
+
const storeTmuxNames = new Set(storeSessions.map((s) => s.tmuxSession));
|
|
142
|
+
|
|
143
|
+
const orphanedTmux = agentplateTmuxSessions.filter((s) => !storeTmuxNames.has(s.name));
|
|
144
|
+
|
|
145
|
+
if (orphanedTmux.length > 0) {
|
|
146
|
+
checks.push({
|
|
147
|
+
name: "orphaned-tmux",
|
|
148
|
+
category: "consistency",
|
|
149
|
+
status: "warn",
|
|
150
|
+
message: `Found ${orphanedTmux.length} orphaned tmux session(s) with no SessionStore entry`,
|
|
151
|
+
details: orphanedTmux.map((s) => `${s.name} (pid: ${s.pid})`),
|
|
152
|
+
fixable: true,
|
|
153
|
+
});
|
|
154
|
+
} else {
|
|
155
|
+
checks.push({
|
|
156
|
+
name: "orphaned-tmux",
|
|
157
|
+
category: "consistency",
|
|
158
|
+
status: "pass",
|
|
159
|
+
message: "No orphaned tmux sessions found",
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 6. Check for dead processes in SessionStore
|
|
164
|
+
const deadSessions = liveSessions.filter((s) => s.pid !== null && !isProcessAliveFn(s.pid));
|
|
165
|
+
|
|
166
|
+
if (deadSessions.length > 0) {
|
|
167
|
+
checks.push({
|
|
168
|
+
name: "dead-pids",
|
|
169
|
+
category: "consistency",
|
|
170
|
+
status: "warn",
|
|
171
|
+
message: `Found ${deadSessions.length} session(s) with dead PIDs`,
|
|
172
|
+
details: deadSessions.map((s) => `${s.agentName} (pid: ${s.pid}, state: ${s.state})`),
|
|
173
|
+
fixable: true,
|
|
174
|
+
});
|
|
175
|
+
} else {
|
|
176
|
+
checks.push({
|
|
177
|
+
name: "dead-pids",
|
|
178
|
+
category: "consistency",
|
|
179
|
+
status: "pass",
|
|
180
|
+
message: "All SessionStore PIDs are alive or null",
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 7. Check for SessionStore entries with missing worktrees
|
|
185
|
+
const existingWorktreePaths = new Set(worktrees.map((wt) => wt.path));
|
|
186
|
+
const missingWorktrees = liveSessions.filter((s) => {
|
|
187
|
+
// Try to normalize the SessionStore path for comparison
|
|
188
|
+
try {
|
|
189
|
+
const normalizedPath = realpathSync(s.worktreePath);
|
|
190
|
+
return !existingWorktreePaths.has(normalizedPath);
|
|
191
|
+
} catch {
|
|
192
|
+
// Path doesn't exist or can't be resolved, check as-is
|
|
193
|
+
return !existingWorktreePaths.has(s.worktreePath);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (missingWorktrees.length > 0) {
|
|
198
|
+
checks.push({
|
|
199
|
+
name: "missing-worktrees",
|
|
200
|
+
category: "consistency",
|
|
201
|
+
status: "warn",
|
|
202
|
+
message: `Found ${missingWorktrees.length} session(s) with missing worktrees`,
|
|
203
|
+
details: missingWorktrees.map((s) => `${s.agentName}: ${s.worktreePath}`),
|
|
204
|
+
fixable: true,
|
|
205
|
+
});
|
|
206
|
+
} else {
|
|
207
|
+
checks.push({
|
|
208
|
+
name: "missing-worktrees",
|
|
209
|
+
category: "consistency",
|
|
210
|
+
status: "pass",
|
|
211
|
+
message: "All SessionStore worktrees exist",
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 8. Check for SessionStore entries with missing tmux sessions
|
|
216
|
+
const existingTmuxNames = new Set(tmuxSessions.map((s) => s.name));
|
|
217
|
+
const missingTmux = liveSessions.filter(
|
|
218
|
+
(s) => s.tmuxSession.length > 0 && !existingTmuxNames.has(s.tmuxSession),
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
if (missingTmux.length > 0) {
|
|
222
|
+
checks.push({
|
|
223
|
+
name: "missing-tmux",
|
|
224
|
+
category: "consistency",
|
|
225
|
+
status: "warn",
|
|
226
|
+
message: `Found ${missingTmux.length} session(s) with missing tmux sessions`,
|
|
227
|
+
details: missingTmux.map((s) => `${s.agentName}: ${s.tmuxSession}`),
|
|
228
|
+
fixable: true,
|
|
229
|
+
});
|
|
230
|
+
} else {
|
|
231
|
+
checks.push({
|
|
232
|
+
name: "missing-tmux",
|
|
233
|
+
category: "consistency",
|
|
234
|
+
status: "pass",
|
|
235
|
+
message: "All SessionStore tmux sessions exist",
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 8b. Check for orphaned claude spawn PIDs (agentplate-505d).
|
|
240
|
+
//
|
|
241
|
+
// An orphan is a session whose pid is still alive but should not be:
|
|
242
|
+
// - the session reached a terminal state (completed/zombie) yet the
|
|
243
|
+
// spawn didn't exit, or
|
|
244
|
+
// - the tmux container is gone but the claude child survived (was
|
|
245
|
+
// reparented to init when its bash wrapper got SIGHUP).
|
|
246
|
+
// Run `ap clean --all` to reap. Distinct from `dead-pids` (the inverse:
|
|
247
|
+
// session is live but its pid already died).
|
|
248
|
+
const orphanedSpawns: Array<{ session: AgentSession; reason: string }> = [];
|
|
249
|
+
for (const s of storeSessions) {
|
|
250
|
+
if (s.pid === null || !isProcessAliveFn(s.pid)) continue;
|
|
251
|
+
if (s.state === "completed" || s.state === "zombie") {
|
|
252
|
+
orphanedSpawns.push({
|
|
253
|
+
session: s,
|
|
254
|
+
reason: `state=${s.state} but pid ${s.pid} still alive`,
|
|
255
|
+
});
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (s.tmuxSession.length > 0 && !existingTmuxNames.has(s.tmuxSession)) {
|
|
259
|
+
orphanedSpawns.push({
|
|
260
|
+
session: s,
|
|
261
|
+
reason: `tmux session "${s.tmuxSession}" missing but pid ${s.pid} alive`,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (orphanedSpawns.length > 0) {
|
|
267
|
+
checks.push({
|
|
268
|
+
name: "orphan-spawns",
|
|
269
|
+
category: "consistency",
|
|
270
|
+
status: "warn",
|
|
271
|
+
message: `Found ${orphanedSpawns.length} orphaned spawn process(es) — run "ap clean --all" to reap`,
|
|
272
|
+
details: orphanedSpawns.map(({ session, reason }) => `${session.agentName}: ${reason}`),
|
|
273
|
+
fixable: true,
|
|
274
|
+
});
|
|
275
|
+
} else {
|
|
276
|
+
checks.push({
|
|
277
|
+
name: "orphan-spawns",
|
|
278
|
+
category: "consistency",
|
|
279
|
+
status: "pass",
|
|
280
|
+
message: "No orphaned spawn processes detected",
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// 9. Check reviewer-to-builder ratio per lead
|
|
285
|
+
const parentGroups = new Map<string, { builders: number; reviewers: number }>();
|
|
286
|
+
for (const session of storeSessions) {
|
|
287
|
+
if (
|
|
288
|
+
session.parentAgent &&
|
|
289
|
+
(session.capability === "builder" || session.capability === "reviewer")
|
|
290
|
+
) {
|
|
291
|
+
const group = parentGroups.get(session.parentAgent) ?? { builders: 0, reviewers: 0 };
|
|
292
|
+
if (session.capability === "builder") {
|
|
293
|
+
group.builders++;
|
|
294
|
+
} else {
|
|
295
|
+
group.reviewers++;
|
|
296
|
+
}
|
|
297
|
+
parentGroups.set(session.parentAgent, group);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const leadsWithoutReview: string[] = [];
|
|
302
|
+
const leadsWithPartialReview: string[] = [];
|
|
303
|
+
for (const [parent, counts] of parentGroups) {
|
|
304
|
+
if (counts.builders > 0 && counts.reviewers === 0) {
|
|
305
|
+
leadsWithoutReview.push(`${parent}: ${counts.builders} builder(s), 0 reviewers`);
|
|
306
|
+
} else if (counts.builders > 0 && counts.reviewers < counts.builders) {
|
|
307
|
+
leadsWithPartialReview.push(
|
|
308
|
+
`${parent}: ${counts.builders} builder(s), ${counts.reviewers} reviewer(s)`,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (leadsWithoutReview.length > 0) {
|
|
314
|
+
checks.push({
|
|
315
|
+
name: "reviewer-coverage",
|
|
316
|
+
category: "consistency",
|
|
317
|
+
status: "warn",
|
|
318
|
+
message: `${leadsWithoutReview.length} lead(s) spawned builders without any reviewers`,
|
|
319
|
+
details: [...leadsWithoutReview, ...leadsWithPartialReview],
|
|
320
|
+
});
|
|
321
|
+
} else if (leadsWithPartialReview.length > 0) {
|
|
322
|
+
checks.push({
|
|
323
|
+
name: "reviewer-coverage",
|
|
324
|
+
category: "consistency",
|
|
325
|
+
status: "warn",
|
|
326
|
+
message: `${leadsWithPartialReview.length} lead(s) have partial reviewer coverage`,
|
|
327
|
+
details: leadsWithPartialReview,
|
|
328
|
+
});
|
|
329
|
+
} else {
|
|
330
|
+
checks.push({
|
|
331
|
+
name: "reviewer-coverage",
|
|
332
|
+
category: "consistency",
|
|
333
|
+
status: "pass",
|
|
334
|
+
message:
|
|
335
|
+
parentGroups.size > 0
|
|
336
|
+
? "All leads have reviewer coverage for builders"
|
|
337
|
+
: "No builder sessions found (nothing to check)",
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Close the SessionStore
|
|
342
|
+
if (storeHandle) {
|
|
343
|
+
storeHandle.close();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return checks;
|
|
347
|
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
3
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import type { AgentplateConfig } from "../types.ts";
|
|
7
|
+
import { checkDatabases } from "./databases.ts";
|
|
8
|
+
import type { DoctorCheck } from "./types.ts";
|
|
9
|
+
|
|
10
|
+
describe("checkDatabases", () => {
|
|
11
|
+
let tempDir: string;
|
|
12
|
+
let mockConfig: AgentplateConfig;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
tempDir = mkdtempSync(join(tmpdir(), "agentplate-test-"));
|
|
16
|
+
mockConfig = {
|
|
17
|
+
project: { name: "test", root: tempDir, canonicalBranch: "main" },
|
|
18
|
+
agents: {
|
|
19
|
+
manifestPath: "",
|
|
20
|
+
baseDir: "",
|
|
21
|
+
maxConcurrent: 5,
|
|
22
|
+
staggerDelayMs: 100,
|
|
23
|
+
maxDepth: 2,
|
|
24
|
+
maxSessionsPerRun: 0,
|
|
25
|
+
maxAgentsPerLead: 5,
|
|
26
|
+
},
|
|
27
|
+
worktrees: { baseDir: "" },
|
|
28
|
+
taskTracker: { backend: "auto", enabled: true },
|
|
29
|
+
loam: { enabled: true, domains: [], primeFormat: "markdown" },
|
|
30
|
+
merge: { aiResolveEnabled: false, reimagineEnabled: false },
|
|
31
|
+
providers: {
|
|
32
|
+
anthropic: { type: "native" },
|
|
33
|
+
},
|
|
34
|
+
watchdog: {
|
|
35
|
+
tier0Enabled: true,
|
|
36
|
+
tier0IntervalMs: 30000,
|
|
37
|
+
tier1Enabled: false,
|
|
38
|
+
tier2Enabled: false,
|
|
39
|
+
staleThresholdMs: 300000,
|
|
40
|
+
zombieThresholdMs: 600000,
|
|
41
|
+
nudgeIntervalMs: 60000,
|
|
42
|
+
},
|
|
43
|
+
models: {},
|
|
44
|
+
logging: { verbose: false, redactSecrets: true },
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
afterEach(() => {
|
|
49
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("fails when required database files do not exist (merge-queue.db is optional)", () => {
|
|
53
|
+
const checks = checkDatabases(mockConfig, tempDir) as DoctorCheck[];
|
|
54
|
+
|
|
55
|
+
expect(checks).toHaveLength(4);
|
|
56
|
+
expect(checks[0]?.status).toBe("fail");
|
|
57
|
+
expect(checks[0]?.name).toBe("mail.db exists");
|
|
58
|
+
expect(checks[1]?.status).toBe("fail");
|
|
59
|
+
expect(checks[1]?.name).toBe("metrics.db exists");
|
|
60
|
+
expect(checks[2]?.status).toBe("fail");
|
|
61
|
+
expect(checks[2]?.name).toBe("sessions.db exists");
|
|
62
|
+
// merge-queue.db is created lazily on first merge — its absence is normal.
|
|
63
|
+
expect(checks[3]?.status).toBe("pass");
|
|
64
|
+
expect(checks[3]?.name).toBe("merge-queue.db exists");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("passes when databases exist with correct schema", () => {
|
|
68
|
+
// Create mail.db
|
|
69
|
+
const mailDb = new Database(join(tempDir, "mail.db"));
|
|
70
|
+
mailDb.exec("PRAGMA journal_mode=WAL");
|
|
71
|
+
mailDb.exec(`
|
|
72
|
+
CREATE TABLE messages (
|
|
73
|
+
id TEXT PRIMARY KEY,
|
|
74
|
+
from_agent TEXT NOT NULL,
|
|
75
|
+
to_agent TEXT NOT NULL,
|
|
76
|
+
subject TEXT NOT NULL,
|
|
77
|
+
body TEXT NOT NULL,
|
|
78
|
+
type TEXT NOT NULL DEFAULT 'status',
|
|
79
|
+
priority TEXT NOT NULL DEFAULT 'normal',
|
|
80
|
+
thread_id TEXT,
|
|
81
|
+
payload TEXT,
|
|
82
|
+
read INTEGER NOT NULL DEFAULT 0,
|
|
83
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
84
|
+
)
|
|
85
|
+
`);
|
|
86
|
+
mailDb.close();
|
|
87
|
+
|
|
88
|
+
// Create metrics.db
|
|
89
|
+
const metricsDb = new Database(join(tempDir, "metrics.db"));
|
|
90
|
+
metricsDb.exec("PRAGMA journal_mode=WAL");
|
|
91
|
+
metricsDb.exec(`
|
|
92
|
+
CREATE TABLE sessions (
|
|
93
|
+
agent_name TEXT NOT NULL,
|
|
94
|
+
task_id TEXT NOT NULL,
|
|
95
|
+
capability TEXT NOT NULL,
|
|
96
|
+
started_at TEXT NOT NULL,
|
|
97
|
+
completed_at TEXT,
|
|
98
|
+
duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
99
|
+
exit_code INTEGER,
|
|
100
|
+
merge_result TEXT,
|
|
101
|
+
parent_agent TEXT,
|
|
102
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
103
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
104
|
+
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
105
|
+
cache_creation_tokens INTEGER NOT NULL DEFAULT 0,
|
|
106
|
+
estimated_cost_usd REAL,
|
|
107
|
+
model_used TEXT,
|
|
108
|
+
PRIMARY KEY (agent_name, task_id)
|
|
109
|
+
)
|
|
110
|
+
`);
|
|
111
|
+
metricsDb.close();
|
|
112
|
+
|
|
113
|
+
// Create sessions.db
|
|
114
|
+
const sessionsDb = new Database(join(tempDir, "sessions.db"));
|
|
115
|
+
sessionsDb.exec("PRAGMA journal_mode=WAL");
|
|
116
|
+
sessionsDb.exec(`
|
|
117
|
+
CREATE TABLE sessions (
|
|
118
|
+
id TEXT PRIMARY KEY,
|
|
119
|
+
agent_name TEXT NOT NULL UNIQUE,
|
|
120
|
+
capability TEXT NOT NULL,
|
|
121
|
+
worktree_path TEXT NOT NULL,
|
|
122
|
+
branch_name TEXT NOT NULL,
|
|
123
|
+
task_id TEXT NOT NULL,
|
|
124
|
+
tmux_session TEXT NOT NULL,
|
|
125
|
+
state TEXT NOT NULL DEFAULT 'booting',
|
|
126
|
+
pid INTEGER,
|
|
127
|
+
parent_agent TEXT,
|
|
128
|
+
depth INTEGER NOT NULL DEFAULT 0,
|
|
129
|
+
run_id TEXT,
|
|
130
|
+
started_at TEXT NOT NULL,
|
|
131
|
+
last_activity TEXT NOT NULL,
|
|
132
|
+
escalation_level INTEGER NOT NULL DEFAULT 0,
|
|
133
|
+
stalled_since TEXT
|
|
134
|
+
)
|
|
135
|
+
`);
|
|
136
|
+
sessionsDb.exec(`
|
|
137
|
+
CREATE TABLE runs (
|
|
138
|
+
id TEXT PRIMARY KEY,
|
|
139
|
+
started_at TEXT NOT NULL,
|
|
140
|
+
completed_at TEXT,
|
|
141
|
+
agent_count INTEGER NOT NULL DEFAULT 0,
|
|
142
|
+
coordinator_session_id TEXT,
|
|
143
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
144
|
+
)
|
|
145
|
+
`);
|
|
146
|
+
sessionsDb.close();
|
|
147
|
+
|
|
148
|
+
// Create merge-queue.db
|
|
149
|
+
const mergeDb = new Database(join(tempDir, "merge-queue.db"));
|
|
150
|
+
mergeDb.exec("PRAGMA journal_mode=WAL");
|
|
151
|
+
mergeDb.exec(`
|
|
152
|
+
CREATE TABLE merge_queue (
|
|
153
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
154
|
+
branch_name TEXT NOT NULL,
|
|
155
|
+
task_id TEXT NOT NULL,
|
|
156
|
+
agent_name TEXT NOT NULL,
|
|
157
|
+
files_modified TEXT NOT NULL DEFAULT '[]',
|
|
158
|
+
enqueued_at TEXT NOT NULL,
|
|
159
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
160
|
+
resolved_tier TEXT
|
|
161
|
+
)
|
|
162
|
+
`);
|
|
163
|
+
mergeDb.close();
|
|
164
|
+
|
|
165
|
+
const checks = checkDatabases(mockConfig, tempDir) as DoctorCheck[];
|
|
166
|
+
|
|
167
|
+
expect(checks).toHaveLength(4);
|
|
168
|
+
expect(checks.every((c) => c?.status === "pass")).toBe(true);
|
|
169
|
+
expect(checks[0]?.name).toBe("mail.db health");
|
|
170
|
+
expect(checks[1]?.name).toBe("metrics.db health");
|
|
171
|
+
expect(checks[2]?.name).toBe("sessions.db health");
|
|
172
|
+
expect(checks[3]?.name).toBe("merge-queue.db health");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("fails when table is missing", () => {
|
|
176
|
+
// Create mail.db without messages table
|
|
177
|
+
const mailDb = new Database(join(tempDir, "mail.db"));
|
|
178
|
+
mailDb.exec("PRAGMA journal_mode=WAL");
|
|
179
|
+
mailDb.close();
|
|
180
|
+
|
|
181
|
+
// Create other databases properly to isolate the test
|
|
182
|
+
const metricsDb = new Database(join(tempDir, "metrics.db"));
|
|
183
|
+
metricsDb.exec("PRAGMA journal_mode=WAL");
|
|
184
|
+
metricsDb.exec(`
|
|
185
|
+
CREATE TABLE sessions (
|
|
186
|
+
agent_name TEXT NOT NULL,
|
|
187
|
+
task_id TEXT NOT NULL,
|
|
188
|
+
capability TEXT NOT NULL,
|
|
189
|
+
started_at TEXT NOT NULL,
|
|
190
|
+
completed_at TEXT,
|
|
191
|
+
duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
192
|
+
exit_code INTEGER,
|
|
193
|
+
merge_result TEXT,
|
|
194
|
+
parent_agent TEXT,
|
|
195
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
196
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
197
|
+
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
198
|
+
cache_creation_tokens INTEGER NOT NULL DEFAULT 0,
|
|
199
|
+
estimated_cost_usd REAL,
|
|
200
|
+
model_used TEXT,
|
|
201
|
+
PRIMARY KEY (agent_name, task_id)
|
|
202
|
+
)
|
|
203
|
+
`);
|
|
204
|
+
metricsDb.close();
|
|
205
|
+
|
|
206
|
+
const sessionsDb = new Database(join(tempDir, "sessions.db"));
|
|
207
|
+
sessionsDb.exec("PRAGMA journal_mode=WAL");
|
|
208
|
+
sessionsDb.exec(`
|
|
209
|
+
CREATE TABLE sessions (
|
|
210
|
+
id TEXT PRIMARY KEY,
|
|
211
|
+
agent_name TEXT NOT NULL UNIQUE,
|
|
212
|
+
capability TEXT NOT NULL,
|
|
213
|
+
worktree_path TEXT NOT NULL,
|
|
214
|
+
branch_name TEXT NOT NULL,
|
|
215
|
+
task_id TEXT NOT NULL,
|
|
216
|
+
tmux_session TEXT NOT NULL,
|
|
217
|
+
state TEXT NOT NULL DEFAULT 'booting',
|
|
218
|
+
pid INTEGER,
|
|
219
|
+
parent_agent TEXT,
|
|
220
|
+
depth INTEGER NOT NULL DEFAULT 0,
|
|
221
|
+
run_id TEXT,
|
|
222
|
+
started_at TEXT NOT NULL,
|
|
223
|
+
last_activity TEXT NOT NULL,
|
|
224
|
+
escalation_level INTEGER NOT NULL DEFAULT 0,
|
|
225
|
+
stalled_since TEXT
|
|
226
|
+
)
|
|
227
|
+
`);
|
|
228
|
+
sessionsDb.exec(`
|
|
229
|
+
CREATE TABLE runs (
|
|
230
|
+
id TEXT PRIMARY KEY,
|
|
231
|
+
started_at TEXT NOT NULL,
|
|
232
|
+
completed_at TEXT,
|
|
233
|
+
agent_count INTEGER NOT NULL DEFAULT 0,
|
|
234
|
+
coordinator_session_id TEXT,
|
|
235
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
236
|
+
)
|
|
237
|
+
`);
|
|
238
|
+
sessionsDb.close();
|
|
239
|
+
|
|
240
|
+
const checks = checkDatabases(mockConfig, tempDir) as DoctorCheck[];
|
|
241
|
+
|
|
242
|
+
const mailCheck = checks.find((c) => c?.name === "mail.db schema");
|
|
243
|
+
expect(mailCheck?.status).toBe("fail");
|
|
244
|
+
expect(mailCheck?.details).toContain("Missing tables: messages");
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("fails when column is missing", () => {
|
|
248
|
+
// Create messages table without payload column
|
|
249
|
+
const mailDb = new Database(join(tempDir, "mail.db"));
|
|
250
|
+
mailDb.exec("PRAGMA journal_mode=WAL");
|
|
251
|
+
mailDb.exec(`
|
|
252
|
+
CREATE TABLE messages (
|
|
253
|
+
id TEXT PRIMARY KEY,
|
|
254
|
+
from_agent TEXT NOT NULL,
|
|
255
|
+
to_agent TEXT NOT NULL,
|
|
256
|
+
subject TEXT NOT NULL,
|
|
257
|
+
body TEXT NOT NULL,
|
|
258
|
+
type TEXT NOT NULL DEFAULT 'status',
|
|
259
|
+
priority TEXT NOT NULL DEFAULT 'normal',
|
|
260
|
+
thread_id TEXT,
|
|
261
|
+
read INTEGER NOT NULL DEFAULT 0,
|
|
262
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
263
|
+
)
|
|
264
|
+
`);
|
|
265
|
+
mailDb.close();
|
|
266
|
+
|
|
267
|
+
const checks = checkDatabases(mockConfig, tempDir) as DoctorCheck[];
|
|
268
|
+
|
|
269
|
+
const mailCheck = checks.find((c) => c?.name === "mail.db schema");
|
|
270
|
+
expect(mailCheck?.status).toBe("fail");
|
|
271
|
+
expect(mailCheck?.details?.some((d) => d.includes("missing column: payload"))).toBe(true);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("warns when WAL mode is not enabled", () => {
|
|
275
|
+
// Create database without WAL mode
|
|
276
|
+
const mailDb = new Database(join(tempDir, "mail.db"));
|
|
277
|
+
mailDb.exec(`
|
|
278
|
+
CREATE TABLE messages (
|
|
279
|
+
id TEXT PRIMARY KEY,
|
|
280
|
+
from_agent TEXT NOT NULL,
|
|
281
|
+
to_agent TEXT NOT NULL,
|
|
282
|
+
subject TEXT NOT NULL,
|
|
283
|
+
body TEXT NOT NULL,
|
|
284
|
+
type TEXT NOT NULL DEFAULT 'status',
|
|
285
|
+
priority TEXT NOT NULL DEFAULT 'normal',
|
|
286
|
+
thread_id TEXT,
|
|
287
|
+
payload TEXT,
|
|
288
|
+
read INTEGER NOT NULL DEFAULT 0,
|
|
289
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
290
|
+
)
|
|
291
|
+
`);
|
|
292
|
+
mailDb.close();
|
|
293
|
+
|
|
294
|
+
const checks = checkDatabases(mockConfig, tempDir) as DoctorCheck[];
|
|
295
|
+
|
|
296
|
+
const walCheck = checks.find((c) => c?.name === "mail.db WAL mode");
|
|
297
|
+
expect(walCheck?.status).toBe("warn");
|
|
298
|
+
expect(walCheck?.message).toContain("not using WAL mode");
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test("fails when database is corrupted", () => {
|
|
302
|
+
// Create a corrupt database file (just write garbage)
|
|
303
|
+
const { writeFileSync } = require("node:fs");
|
|
304
|
+
writeFileSync(join(tempDir, "mail.db"), "not a valid sqlite database");
|
|
305
|
+
|
|
306
|
+
const checks = checkDatabases(mockConfig, tempDir) as DoctorCheck[];
|
|
307
|
+
|
|
308
|
+
const integrityCheck = checks.find((c) => c?.name === "mail.db integrity");
|
|
309
|
+
expect(integrityCheck?.status).toBe("fail");
|
|
310
|
+
expect(integrityCheck?.message).toContain("Failed to open or validate");
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test("fix() enables WAL mode on database", () => {
|
|
314
|
+
// Create mail.db without WAL mode
|
|
315
|
+
const mailDb = new Database(join(tempDir, "mail.db"));
|
|
316
|
+
mailDb.exec(`
|
|
317
|
+
CREATE TABLE messages (
|
|
318
|
+
id TEXT PRIMARY KEY,
|
|
319
|
+
from_agent TEXT NOT NULL,
|
|
320
|
+
to_agent TEXT NOT NULL,
|
|
321
|
+
subject TEXT NOT NULL,
|
|
322
|
+
body TEXT NOT NULL,
|
|
323
|
+
type TEXT NOT NULL DEFAULT 'status',
|
|
324
|
+
priority TEXT NOT NULL DEFAULT 'normal',
|
|
325
|
+
thread_id TEXT,
|
|
326
|
+
payload TEXT,
|
|
327
|
+
read INTEGER NOT NULL DEFAULT 0,
|
|
328
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
329
|
+
)
|
|
330
|
+
`);
|
|
331
|
+
mailDb.close();
|
|
332
|
+
|
|
333
|
+
const checks = checkDatabases(mockConfig, tempDir) as DoctorCheck[];
|
|
334
|
+
|
|
335
|
+
const walCheck = checks.find((c) => c?.name === "mail.db WAL mode");
|
|
336
|
+
expect(walCheck?.status).toBe("warn");
|
|
337
|
+
expect(walCheck?.fix).toBeDefined();
|
|
338
|
+
|
|
339
|
+
const actions = walCheck?.fix?.();
|
|
340
|
+
expect(Array.isArray(actions)).toBe(true);
|
|
341
|
+
expect((actions as string[]).some((a) => a.includes("WAL mode"))).toBe(true);
|
|
342
|
+
expect((actions as string[]).some((a) => a.includes("mail.db"))).toBe(true);
|
|
343
|
+
|
|
344
|
+
// Verify WAL mode is now enabled
|
|
345
|
+
const verifyDb = new Database(join(tempDir, "mail.db"));
|
|
346
|
+
const journalMode = verifyDb.prepare<{ journal_mode: string }, []>("PRAGMA journal_mode").get();
|
|
347
|
+
verifyDb.close();
|
|
348
|
+
expect(journalMode?.journal_mode?.toLowerCase()).toBe("wal");
|
|
349
|
+
});
|
|
350
|
+
});
|