@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,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `agentplate doctor` command.
|
|
3
|
+
*
|
|
4
|
+
* Uses temp directories with real config.yaml to test the doctor scaffold.
|
|
5
|
+
* All check modules return empty arrays (stubs), so tests verify the scaffold
|
|
6
|
+
* structure, not individual check implementations.
|
|
7
|
+
*
|
|
8
|
+
* Real implementations used for: filesystem (temp dirs), config loading.
|
|
9
|
+
* No mocks needed -- all dependencies are cheap and local.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
13
|
+
import { mkdtemp } from "node:fs/promises";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { ValidationError } from "../errors.ts";
|
|
17
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
18
|
+
import { doctorCommand } from "./doctor.ts";
|
|
19
|
+
|
|
20
|
+
describe("doctorCommand", () => {
|
|
21
|
+
let chunks: string[];
|
|
22
|
+
let originalWrite: typeof process.stdout.write;
|
|
23
|
+
let tempDir: string;
|
|
24
|
+
let originalCwd: string;
|
|
25
|
+
|
|
26
|
+
beforeEach(async () => {
|
|
27
|
+
// Spy on stdout
|
|
28
|
+
chunks = [];
|
|
29
|
+
originalWrite = process.stdout.write;
|
|
30
|
+
process.stdout.write = ((chunk: string) => {
|
|
31
|
+
chunks.push(chunk);
|
|
32
|
+
return true;
|
|
33
|
+
}) as typeof process.stdout.write;
|
|
34
|
+
|
|
35
|
+
// Create temp dir with .agentplate/config.yaml structure
|
|
36
|
+
tempDir = await mkdtemp(join(tmpdir(), "doctor-test-"));
|
|
37
|
+
const agentplateDir = join(tempDir, ".agentplate");
|
|
38
|
+
await Bun.write(
|
|
39
|
+
join(agentplateDir, "config.yaml"),
|
|
40
|
+
`project:\n name: test\n root: ${tempDir}\n canonicalBranch: main\n`,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// Change to temp dir so loadConfig() works
|
|
44
|
+
originalCwd = process.cwd();
|
|
45
|
+
process.chdir(tempDir);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
afterEach(async () => {
|
|
49
|
+
process.stdout.write = originalWrite;
|
|
50
|
+
process.chdir(originalCwd);
|
|
51
|
+
await cleanupTempDir(tempDir);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
function output(): string {
|
|
55
|
+
return chunks.join("");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// === Help flag ===
|
|
59
|
+
|
|
60
|
+
describe("help flag", () => {
|
|
61
|
+
test("--help shows help text", async () => {
|
|
62
|
+
await doctorCommand(["--help"], { checkRunners: [] });
|
|
63
|
+
const out = output();
|
|
64
|
+
|
|
65
|
+
expect(out).toContain("doctor");
|
|
66
|
+
expect(out).toContain("Run health checks");
|
|
67
|
+
expect(out).toContain("--json");
|
|
68
|
+
expect(out).toContain("--verbose");
|
|
69
|
+
expect(out).toContain("--category");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("-h shows help text", async () => {
|
|
73
|
+
await doctorCommand(["-h"], { checkRunners: [] });
|
|
74
|
+
const out = output();
|
|
75
|
+
|
|
76
|
+
expect(out).toContain("doctor");
|
|
77
|
+
expect(out).toContain("--help");
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// === JSON output ===
|
|
82
|
+
|
|
83
|
+
describe("JSON output mode", () => {
|
|
84
|
+
test("outputs valid JSON with checks array and summary", async () => {
|
|
85
|
+
await doctorCommand(["--json"], { checkRunners: [] });
|
|
86
|
+
const out = output();
|
|
87
|
+
|
|
88
|
+
const parsed = JSON.parse(out.trim()) as {
|
|
89
|
+
success: boolean;
|
|
90
|
+
command: string;
|
|
91
|
+
checks: unknown[];
|
|
92
|
+
summary: { pass: number; warn: number; fail: number };
|
|
93
|
+
};
|
|
94
|
+
expect(parsed).toBeDefined();
|
|
95
|
+
expect(parsed.success).toBe(true);
|
|
96
|
+
expect(parsed.command).toBe("doctor");
|
|
97
|
+
expect(Array.isArray(parsed.checks)).toBe(true);
|
|
98
|
+
expect(parsed.summary).toBeDefined();
|
|
99
|
+
expect(typeof parsed.summary.pass).toBe("number");
|
|
100
|
+
expect(typeof parsed.summary.warn).toBe("number");
|
|
101
|
+
expect(typeof parsed.summary.fail).toBe("number");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("empty stubs produce zero counts in summary", async () => {
|
|
105
|
+
await doctorCommand(["--json"], { checkRunners: [] });
|
|
106
|
+
const out = output();
|
|
107
|
+
|
|
108
|
+
const parsed = JSON.parse(out.trim()) as {
|
|
109
|
+
success: boolean;
|
|
110
|
+
command: string;
|
|
111
|
+
checks: unknown[];
|
|
112
|
+
summary: { pass: number; warn: number; fail: number };
|
|
113
|
+
};
|
|
114
|
+
expect(parsed.success).toBe(true);
|
|
115
|
+
expect(parsed.command).toBe("doctor");
|
|
116
|
+
expect(parsed.checks).toEqual([]);
|
|
117
|
+
expect(parsed.summary.pass).toBe(0);
|
|
118
|
+
expect(parsed.summary.warn).toBe(0);
|
|
119
|
+
expect(parsed.summary.fail).toBe(0);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// === Human-readable output ===
|
|
124
|
+
|
|
125
|
+
describe("human-readable output", () => {
|
|
126
|
+
test("shows header", async () => {
|
|
127
|
+
await doctorCommand([], { checkRunners: [] });
|
|
128
|
+
const out = output();
|
|
129
|
+
|
|
130
|
+
expect(out).toContain("Agentplate Doctor");
|
|
131
|
+
expect(out).toContain("────────────────");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("shows summary line with zero counts", async () => {
|
|
135
|
+
await doctorCommand([], { checkRunners: [] });
|
|
136
|
+
const out = output();
|
|
137
|
+
|
|
138
|
+
expect(out).toContain("Summary:");
|
|
139
|
+
expect(out).toContain("0 passed");
|
|
140
|
+
expect(out).toContain("0 warning");
|
|
141
|
+
expect(out).toContain("0 failure");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("summary uses singular form for one failure", async () => {
|
|
145
|
+
// This test can't verify "1 failure" without real checks, but we can test the logic
|
|
146
|
+
// by checking that the scaffold doesn't crash on empty checks
|
|
147
|
+
await doctorCommand([], { checkRunners: [] });
|
|
148
|
+
const out = output();
|
|
149
|
+
|
|
150
|
+
// Should show "0 failures" (plural) when count is 0
|
|
151
|
+
expect(out).toContain("0 failure");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("default mode does not show empty categories", async () => {
|
|
155
|
+
await doctorCommand([], { checkRunners: [] });
|
|
156
|
+
const out = output();
|
|
157
|
+
|
|
158
|
+
// Since all stubs return empty arrays, no category headers should appear
|
|
159
|
+
// in non-verbose mode
|
|
160
|
+
expect(out).not.toContain("[dependencies]");
|
|
161
|
+
expect(out).not.toContain("[structure]");
|
|
162
|
+
expect(out).not.toContain("[config]");
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// === --verbose flag ===
|
|
167
|
+
|
|
168
|
+
describe("--verbose flag", () => {
|
|
169
|
+
test("shows header and summary even with no check runners", async () => {
|
|
170
|
+
await doctorCommand(["--verbose"], { checkRunners: [] });
|
|
171
|
+
const out = output();
|
|
172
|
+
|
|
173
|
+
// With no check runners, no categories appear (even in verbose mode)
|
|
174
|
+
expect(out).toContain("Agentplate Doctor");
|
|
175
|
+
expect(out).toContain("Summary:");
|
|
176
|
+
expect(out).toContain("0 passed");
|
|
177
|
+
// No categories should appear with empty checkRunners
|
|
178
|
+
expect(out).not.toContain("[dependencies]");
|
|
179
|
+
expect(out).not.toContain("[structure]");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("verbose mode works with empty check runners", async () => {
|
|
183
|
+
await doctorCommand(["--verbose"], { checkRunners: [] });
|
|
184
|
+
const out = output();
|
|
185
|
+
|
|
186
|
+
// Should still produce valid output with no categories
|
|
187
|
+
expect(out).toContain("Agentplate Doctor");
|
|
188
|
+
expect(out).toContain("Summary:");
|
|
189
|
+
// With no check runners, "No checks" doesn't appear (no categories to show it under)
|
|
190
|
+
expect(out).not.toContain("No checks");
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// === --category flag ===
|
|
195
|
+
|
|
196
|
+
describe("--category flag", () => {
|
|
197
|
+
test("runs only specified category", async () => {
|
|
198
|
+
await doctorCommand(["--category", "dependencies", "--json"], { checkRunners: [] });
|
|
199
|
+
const out = output();
|
|
200
|
+
|
|
201
|
+
const parsed = JSON.parse(out.trim()) as {
|
|
202
|
+
checks: Array<{ category: string }>;
|
|
203
|
+
};
|
|
204
|
+
// Since dependencies stub returns [], checks should be empty
|
|
205
|
+
expect(parsed.checks).toEqual([]);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("validates category name", async () => {
|
|
209
|
+
await expect(
|
|
210
|
+
doctorCommand(["--category", "invalid-category"], { checkRunners: [] }),
|
|
211
|
+
).rejects.toThrow(ValidationError);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("invalid category error mentions valid categories", async () => {
|
|
215
|
+
try {
|
|
216
|
+
await doctorCommand(["--category", "bad"], { checkRunners: [] });
|
|
217
|
+
expect.unreachable("should have thrown");
|
|
218
|
+
} catch (err) {
|
|
219
|
+
expect(err).toBeInstanceOf(ValidationError);
|
|
220
|
+
const message = (err as ValidationError).message;
|
|
221
|
+
expect(message).toContain("Invalid category");
|
|
222
|
+
expect(message).toContain("dependencies");
|
|
223
|
+
expect(message).toContain("structure");
|
|
224
|
+
expect(message).toContain("config");
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("accepts all valid category names", async () => {
|
|
229
|
+
const categories = [
|
|
230
|
+
"dependencies",
|
|
231
|
+
"structure",
|
|
232
|
+
"config",
|
|
233
|
+
"databases",
|
|
234
|
+
"consistency",
|
|
235
|
+
"agents",
|
|
236
|
+
"merge",
|
|
237
|
+
"logs",
|
|
238
|
+
"version",
|
|
239
|
+
];
|
|
240
|
+
|
|
241
|
+
for (const category of categories) {
|
|
242
|
+
chunks = []; // Reset output
|
|
243
|
+
await doctorCommand(["--category", category, "--json"], { checkRunners: [] });
|
|
244
|
+
const out = output();
|
|
245
|
+
// Should not throw, and output should be valid JSON
|
|
246
|
+
JSON.parse(out.trim());
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// === Exit code ===
|
|
252
|
+
|
|
253
|
+
describe("exit code", () => {
|
|
254
|
+
test("returns undefined when all checks pass or warn", async () => {
|
|
255
|
+
const exitCode = await doctorCommand([], { checkRunners: [] });
|
|
256
|
+
expect(exitCode).toBeUndefined();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("returns undefined on success (no failures)", async () => {
|
|
260
|
+
const exitCode = await doctorCommand([], { checkRunners: [] });
|
|
261
|
+
expect(exitCode).toBeUndefined();
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// === Edge cases ===
|
|
266
|
+
|
|
267
|
+
describe("edge cases", () => {
|
|
268
|
+
test("handles multiple flags together", async () => {
|
|
269
|
+
await doctorCommand(["--json", "--verbose", "--category", "config"], {
|
|
270
|
+
checkRunners: [],
|
|
271
|
+
});
|
|
272
|
+
const out = output();
|
|
273
|
+
|
|
274
|
+
const parsed = JSON.parse(out.trim()) as {
|
|
275
|
+
checks: unknown[];
|
|
276
|
+
summary: unknown;
|
|
277
|
+
};
|
|
278
|
+
expect(parsed.checks).toEqual([]);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test("flags can appear in any order", async () => {
|
|
282
|
+
chunks = [];
|
|
283
|
+
await doctorCommand(["--category", "logs", "--json"], { checkRunners: [] });
|
|
284
|
+
const out1 = output();
|
|
285
|
+
|
|
286
|
+
chunks = [];
|
|
287
|
+
await doctorCommand(["--json", "--category", "logs"], { checkRunners: [] });
|
|
288
|
+
const out2 = output();
|
|
289
|
+
|
|
290
|
+
// Both should produce the same JSON
|
|
291
|
+
expect(JSON.parse(out1.trim())).toEqual(JSON.parse(out2.trim()));
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("runs without crashing on minimal config", async () => {
|
|
295
|
+
// The beforeEach already sets up minimal config, so this just
|
|
296
|
+
// verifies the command doesn't crash
|
|
297
|
+
await doctorCommand([], { checkRunners: [] });
|
|
298
|
+
const out = output();
|
|
299
|
+
|
|
300
|
+
expect(out).toContain("Agentplate Doctor");
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
});
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: ap doctor [options]
|
|
3
|
+
*
|
|
4
|
+
* Runs health checks on agentplate subsystems and reports problems.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
import { loadConfig } from "../config.ts";
|
|
10
|
+
import { checkAgents } from "../doctor/agents.ts";
|
|
11
|
+
import { checkConfig } from "../doctor/config-check.ts";
|
|
12
|
+
import { checkConsistency } from "../doctor/consistency.ts";
|
|
13
|
+
import { checkDatabases } from "../doctor/databases.ts";
|
|
14
|
+
import { checkDependencies } from "../doctor/dependencies.ts";
|
|
15
|
+
import { checkEcosystem } from "../doctor/ecosystem.ts";
|
|
16
|
+
import { checkLogs } from "../doctor/logs.ts";
|
|
17
|
+
import { checkMergeQueue } from "../doctor/merge-queue.ts";
|
|
18
|
+
import { checkProviders } from "../doctor/providers.ts";
|
|
19
|
+
import { checkServe } from "../doctor/serve.ts";
|
|
20
|
+
import { checkStructure } from "../doctor/structure.ts";
|
|
21
|
+
import type { DoctorCategory, DoctorCheck, DoctorCheckFn } from "../doctor/types.ts";
|
|
22
|
+
import { checkVersion } from "../doctor/version.ts";
|
|
23
|
+
import { checkWatchdog } from "../doctor/watchdog.ts";
|
|
24
|
+
import { ValidationError } from "../errors.ts";
|
|
25
|
+
import { jsonOutput } from "../json.ts";
|
|
26
|
+
import { color } from "../logging/color.ts";
|
|
27
|
+
import { renderHeader } from "../logging/theme.ts";
|
|
28
|
+
|
|
29
|
+
/** Registry of all check modules in execution order. */
|
|
30
|
+
const ALL_CHECKS: Array<{ category: DoctorCategory; fn: DoctorCheckFn }> = [
|
|
31
|
+
{ category: "dependencies", fn: checkDependencies },
|
|
32
|
+
{ category: "config", fn: checkConfig },
|
|
33
|
+
{ category: "structure", fn: checkStructure },
|
|
34
|
+
{ category: "databases", fn: checkDatabases },
|
|
35
|
+
{ category: "consistency", fn: checkConsistency },
|
|
36
|
+
{ category: "agents", fn: checkAgents },
|
|
37
|
+
{ category: "merge", fn: checkMergeQueue },
|
|
38
|
+
{ category: "logs", fn: checkLogs },
|
|
39
|
+
{ category: "version", fn: checkVersion },
|
|
40
|
+
{ category: "ecosystem", fn: checkEcosystem },
|
|
41
|
+
{ category: "providers", fn: checkProviders },
|
|
42
|
+
{ category: "watchdog", fn: checkWatchdog },
|
|
43
|
+
{ category: "serve", fn: checkServe },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Run doctor checks and return the raw results without printing. Consumed by
|
|
48
|
+
* the web dashboard's `GET /api/doctor` endpoint. The non-serializable `fix`
|
|
49
|
+
* closures drop out automatically during JSON encoding.
|
|
50
|
+
*/
|
|
51
|
+
export async function collectDoctorChecks(
|
|
52
|
+
projectRoot: string,
|
|
53
|
+
category?: DoctorCategory,
|
|
54
|
+
): Promise<DoctorCheck[]> {
|
|
55
|
+
const config = await loadConfig(projectRoot);
|
|
56
|
+
const agentplateDir = join(config.project.root, ".agentplate");
|
|
57
|
+
const checksToRun = category ? ALL_CHECKS.filter((c) => c.category === category) : ALL_CHECKS;
|
|
58
|
+
const results: DoctorCheck[] = [];
|
|
59
|
+
for (const { fn } of checksToRun) {
|
|
60
|
+
results.push(...(await fn(config, agentplateDir)));
|
|
61
|
+
}
|
|
62
|
+
return results;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Execute all fix functions on non-passing fixable checks.
|
|
67
|
+
* Returns a list of human-readable actions taken.
|
|
68
|
+
*/
|
|
69
|
+
async function applyFixes(checks: DoctorCheck[]): Promise<string[]> {
|
|
70
|
+
const fixable = checks.filter((c) => c.fixable && c.status !== "pass" && c.fix);
|
|
71
|
+
const fixed: string[] = [];
|
|
72
|
+
for (const check of fixable) {
|
|
73
|
+
if (check.fix) {
|
|
74
|
+
const actions = await check.fix();
|
|
75
|
+
fixed.push(...actions);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return fixed;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Format human-readable output for doctor checks.
|
|
83
|
+
*/
|
|
84
|
+
function printHumanReadable(
|
|
85
|
+
checks: DoctorCheck[],
|
|
86
|
+
verbose: boolean,
|
|
87
|
+
checkRegistry: Array<{ category: DoctorCategory; fn: DoctorCheckFn }>,
|
|
88
|
+
fixedItems?: string[],
|
|
89
|
+
): void {
|
|
90
|
+
const w = process.stdout.write.bind(process.stdout);
|
|
91
|
+
|
|
92
|
+
w(`${renderHeader("Agentplate Doctor")}\n\n`);
|
|
93
|
+
|
|
94
|
+
// Group checks by category
|
|
95
|
+
const byCategory = new Map<DoctorCategory, DoctorCheck[]>();
|
|
96
|
+
for (const check of checks) {
|
|
97
|
+
const existing = byCategory.get(check.category);
|
|
98
|
+
if (existing) {
|
|
99
|
+
existing.push(check);
|
|
100
|
+
} else {
|
|
101
|
+
byCategory.set(check.category, [check]);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Print each category
|
|
106
|
+
for (const { category } of checkRegistry) {
|
|
107
|
+
const categoryChecks = byCategory.get(category) ?? [];
|
|
108
|
+
if (categoryChecks.length === 0 && !verbose) {
|
|
109
|
+
continue; // Skip empty categories unless verbose
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
w(`${color.bold(`[${category}]`)}\n`);
|
|
113
|
+
|
|
114
|
+
if (categoryChecks.length === 0) {
|
|
115
|
+
w(` ${color.dim("No checks")}\n`);
|
|
116
|
+
} else {
|
|
117
|
+
for (const check of categoryChecks) {
|
|
118
|
+
// Skip passing checks unless verbose
|
|
119
|
+
if (check.status === "pass" && !verbose) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const icon =
|
|
124
|
+
check.status === "pass"
|
|
125
|
+
? color.green("-")
|
|
126
|
+
: check.status === "warn"
|
|
127
|
+
? color.yellow("!")
|
|
128
|
+
: color.red("x");
|
|
129
|
+
|
|
130
|
+
w(` ${icon} ${check.message}\n`);
|
|
131
|
+
|
|
132
|
+
// Print details if present
|
|
133
|
+
if (check.details && check.details.length > 0) {
|
|
134
|
+
for (const detail of check.details) {
|
|
135
|
+
w(` ${color.dim(`→ ${detail}`)}\n`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
w("\n");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Summary
|
|
145
|
+
const pass = checks.filter((c) => c.status === "pass").length;
|
|
146
|
+
const warn = checks.filter((c) => c.status === "warn").length;
|
|
147
|
+
const fail = checks.filter((c) => c.status === "fail").length;
|
|
148
|
+
|
|
149
|
+
w(
|
|
150
|
+
`${color.bold("Summary:")} ${color.green(`${pass} passed`)}, ${color.yellow(`${warn} warning${warn === 1 ? "" : "s"}`)}, ${color.red(`${fail} failure${fail === 1 ? "" : "s"}`)}\n`,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
if (fixedItems && fixedItems.length > 0) {
|
|
154
|
+
w(`\n${color.bold("Fixed:")}\n`);
|
|
155
|
+
for (const item of fixedItems) {
|
|
156
|
+
w(` ${color.green("-")} ${item}\n`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Format JSON output for doctor checks.
|
|
163
|
+
*/
|
|
164
|
+
function printJSON(checks: DoctorCheck[], fixed?: string[]): void {
|
|
165
|
+
const pass = checks.filter((c) => c.status === "pass").length;
|
|
166
|
+
const warn = checks.filter((c) => c.status === "warn").length;
|
|
167
|
+
const fail = checks.filter((c) => c.status === "fail").length;
|
|
168
|
+
|
|
169
|
+
jsonOutput("doctor", {
|
|
170
|
+
checks,
|
|
171
|
+
summary: { pass, warn, fail },
|
|
172
|
+
...(fixed && fixed.length > 0 ? { fixed } : {}),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Options for dependency injection in doctorCommand. */
|
|
177
|
+
export interface DoctorCommandOptions {
|
|
178
|
+
/** Override the check runners (defaults to ALL_CHECKS). Pass [] to skip all checks. */
|
|
179
|
+
checkRunners?: Array<{ category: DoctorCategory; fn: DoctorCheckFn }>;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
interface DoctorActionOpts {
|
|
183
|
+
json?: boolean;
|
|
184
|
+
verbose?: boolean;
|
|
185
|
+
category?: string;
|
|
186
|
+
fix?: boolean;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Run the doctor checks. Returns true if any check failed.
|
|
191
|
+
* Shared by both the Commander action and the programmatic entry point so the
|
|
192
|
+
* exit-code signal never has to travel through `process.exitCode` (which is
|
|
193
|
+
* global mutable state and races with other tests in parallel bun test runs).
|
|
194
|
+
*/
|
|
195
|
+
async function runDoctorChecks(
|
|
196
|
+
opts: DoctorActionOpts,
|
|
197
|
+
checkRunners: Array<{ category: DoctorCategory; fn: DoctorCheckFn }>,
|
|
198
|
+
): Promise<boolean> {
|
|
199
|
+
const json = opts.json ?? false;
|
|
200
|
+
const verbose = opts.verbose ?? false;
|
|
201
|
+
const categoryFilter = opts.category;
|
|
202
|
+
const fix = opts.fix ?? false;
|
|
203
|
+
|
|
204
|
+
if (categoryFilter !== undefined) {
|
|
205
|
+
const validCategories = ALL_CHECKS.map((c) => c.category);
|
|
206
|
+
if (!validCategories.includes(categoryFilter as DoctorCategory)) {
|
|
207
|
+
throw new ValidationError(
|
|
208
|
+
`Invalid category: ${categoryFilter}. Valid categories: ${validCategories.join(", ")}`,
|
|
209
|
+
{
|
|
210
|
+
field: "category",
|
|
211
|
+
value: categoryFilter,
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const cwd = process.cwd();
|
|
218
|
+
const config = await loadConfig(cwd);
|
|
219
|
+
const agentplateDir = join(config.project.root, ".agentplate");
|
|
220
|
+
|
|
221
|
+
const checksToRun = categoryFilter
|
|
222
|
+
? checkRunners.filter((c) => c.category === categoryFilter)
|
|
223
|
+
: checkRunners;
|
|
224
|
+
|
|
225
|
+
let results: DoctorCheck[] = [];
|
|
226
|
+
for (const { fn } of checksToRun) {
|
|
227
|
+
const checkResults = await fn(config, agentplateDir);
|
|
228
|
+
results.push(...checkResults);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let fixedItems: string[] | undefined;
|
|
232
|
+
if (fix) {
|
|
233
|
+
const applied = await applyFixes(results);
|
|
234
|
+
if (applied.length > 0) {
|
|
235
|
+
fixedItems = applied;
|
|
236
|
+
results = [];
|
|
237
|
+
for (const { fn } of checksToRun) {
|
|
238
|
+
const checkResults = await fn(config, agentplateDir);
|
|
239
|
+
results.push(...checkResults);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (json) {
|
|
245
|
+
printJSON(results, fixedItems);
|
|
246
|
+
} else {
|
|
247
|
+
printHumanReadable(results, verbose, checkRunners, fixedItems);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return results.some((c) => c.status === "fail");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function buildDoctorCommand(
|
|
254
|
+
onResult: (hasFailures: boolean) => void,
|
|
255
|
+
checkRunners: Array<{ category: DoctorCategory; fn: DoctorCheckFn }>,
|
|
256
|
+
): Command {
|
|
257
|
+
return new Command("doctor")
|
|
258
|
+
.description("Run health checks on agentplate setup")
|
|
259
|
+
.option("--json", "Output as JSON")
|
|
260
|
+
.option("--verbose", "Show passing checks (default: only problems)")
|
|
261
|
+
.option("--category <name>", "Run only one category")
|
|
262
|
+
.option("--fix", "Attempt to auto-fix issues")
|
|
263
|
+
.addHelpText(
|
|
264
|
+
"after",
|
|
265
|
+
"\nCategories: dependencies, structure, config, databases, consistency, agents, merge, logs, version, ecosystem, providers, watchdog, serve",
|
|
266
|
+
)
|
|
267
|
+
.action(async (opts: DoctorActionOpts) => {
|
|
268
|
+
onResult(await runDoctorChecks(opts, checkRunners));
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Create the Commander command for `agentplate doctor`.
|
|
274
|
+
*/
|
|
275
|
+
export function createDoctorCommand(options?: DoctorCommandOptions): Command {
|
|
276
|
+
return buildDoctorCommand((hasFailures) => {
|
|
277
|
+
if (hasFailures) {
|
|
278
|
+
process.exitCode = 1;
|
|
279
|
+
}
|
|
280
|
+
}, options?.checkRunners ?? ALL_CHECKS);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Entry point for `agentplate doctor [--json] [--verbose] [--category <name>]`.
|
|
285
|
+
*
|
|
286
|
+
* @returns Exit code (1 if any check failed, undefined otherwise)
|
|
287
|
+
*/
|
|
288
|
+
export async function doctorCommand(
|
|
289
|
+
args: string[],
|
|
290
|
+
options?: DoctorCommandOptions,
|
|
291
|
+
): Promise<number | undefined> {
|
|
292
|
+
let hasFailures = false;
|
|
293
|
+
const cmd = buildDoctorCommand((result) => {
|
|
294
|
+
hasFailures = result;
|
|
295
|
+
}, options?.checkRunners ?? ALL_CHECKS);
|
|
296
|
+
cmd.exitOverride();
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
await cmd.parseAsync(args, { from: "user" });
|
|
300
|
+
} catch (err: unknown) {
|
|
301
|
+
if (err && typeof err === "object" && "code" in err) {
|
|
302
|
+
const code = (err as { code: string }).code;
|
|
303
|
+
if (code === "commander.helpDisplayed" || code === "commander.version") {
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
throw err;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return hasFailures ? 1 : undefined;
|
|
311
|
+
}
|