@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,167 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { utimes } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import type { AgentplateConfig } from "../types.ts";
|
|
7
|
+
import { checkWatchdog } from "./watchdog.ts";
|
|
8
|
+
|
|
9
|
+
describe("checkWatchdog", () => {
|
|
10
|
+
let tempDir: string;
|
|
11
|
+
let mockConfig: AgentplateConfig;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
tempDir = mkdtempSync(join(tmpdir(), "agentplate-watchdog-test-"));
|
|
15
|
+
mockConfig = {
|
|
16
|
+
project: { name: "test", root: tempDir, canonicalBranch: "main" },
|
|
17
|
+
agents: {
|
|
18
|
+
manifestPath: "",
|
|
19
|
+
baseDir: "",
|
|
20
|
+
maxConcurrent: 5,
|
|
21
|
+
staggerDelayMs: 100,
|
|
22
|
+
maxDepth: 2,
|
|
23
|
+
maxSessionsPerRun: 0,
|
|
24
|
+
maxAgentsPerLead: 5,
|
|
25
|
+
},
|
|
26
|
+
worktrees: { baseDir: "" },
|
|
27
|
+
taskTracker: { backend: "auto", enabled: true },
|
|
28
|
+
loam: { enabled: true, domains: [], primeFormat: "markdown" },
|
|
29
|
+
merge: { aiResolveEnabled: false, reimagineEnabled: false },
|
|
30
|
+
providers: {
|
|
31
|
+
anthropic: { type: "native" },
|
|
32
|
+
},
|
|
33
|
+
watchdog: {
|
|
34
|
+
tier0Enabled: true,
|
|
35
|
+
tier0IntervalMs: 30000,
|
|
36
|
+
tier1Enabled: false,
|
|
37
|
+
tier2Enabled: false,
|
|
38
|
+
staleThresholdMs: 300000,
|
|
39
|
+
zombieThresholdMs: 600000,
|
|
40
|
+
nudgeIntervalMs: 60000,
|
|
41
|
+
},
|
|
42
|
+
models: {},
|
|
43
|
+
logging: { verbose: false, redactSecrets: true },
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("all checks skip when tier0Enabled is false — returns single pass check", async () => {
|
|
52
|
+
mockConfig.watchdog.tier0Enabled = false;
|
|
53
|
+
const checks = await checkWatchdog(mockConfig, tempDir);
|
|
54
|
+
|
|
55
|
+
expect(checks).toHaveLength(1);
|
|
56
|
+
expect(checks[0]?.status).toBe("pass");
|
|
57
|
+
expect(checks[0]?.message).toContain("disabled");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("PID file missing — returns warn about daemon not running", async () => {
|
|
61
|
+
const checks = await checkWatchdog(mockConfig, tempDir);
|
|
62
|
+
|
|
63
|
+
const pidCheck = checks.find((c) => c.name === "watchdog pid file");
|
|
64
|
+
expect(pidCheck).toBeDefined();
|
|
65
|
+
expect(pidCheck?.status).toBe("warn");
|
|
66
|
+
expect(pidCheck?.message).toContain("PID file not found");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("PID file corrupted — returns fail with fixable", async () => {
|
|
70
|
+
writeFileSync(join(tempDir, "watchdog.pid"), "not-a-pid");
|
|
71
|
+
const checks = await checkWatchdog(mockConfig, tempDir);
|
|
72
|
+
|
|
73
|
+
const integrityCheck = checks.find((c) => c.name === "watchdog pid integrity");
|
|
74
|
+
expect(integrityCheck).toBeDefined();
|
|
75
|
+
expect(integrityCheck?.status).toBe("fail");
|
|
76
|
+
expect(integrityCheck?.fixable).toBe(true);
|
|
77
|
+
expect(integrityCheck?.details?.some((d) => d.includes("not-a-pid"))).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("PID file with valid PID but process not running — returns warn (stale PID)", async () => {
|
|
81
|
+
// PID 999999999 is extremely unlikely to exist
|
|
82
|
+
writeFileSync(join(tempDir, "watchdog.pid"), "999999999");
|
|
83
|
+
const checks = await checkWatchdog(mockConfig, tempDir);
|
|
84
|
+
|
|
85
|
+
const processCheck = checks.find((c) => c.name === "watchdog process");
|
|
86
|
+
expect(processCheck).toBeDefined();
|
|
87
|
+
expect(processCheck?.status).toBe("warn");
|
|
88
|
+
expect(processCheck?.message).toContain("stale PID file");
|
|
89
|
+
expect(processCheck?.fixable).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("PID file with current process PID — returns pass", async () => {
|
|
93
|
+
writeFileSync(join(tempDir, "watchdog.pid"), String(process.pid));
|
|
94
|
+
const checks = await checkWatchdog(mockConfig, tempDir);
|
|
95
|
+
|
|
96
|
+
const processCheck = checks.find((c) => c.name === "watchdog process");
|
|
97
|
+
expect(processCheck).toBeDefined();
|
|
98
|
+
expect(processCheck?.status).toBe("pass");
|
|
99
|
+
expect(processCheck?.message).toContain("running");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("PID file older than 24 hours — returns staleness warn", async () => {
|
|
103
|
+
const pidFile = join(tempDir, "watchdog.pid");
|
|
104
|
+
writeFileSync(pidFile, String(process.pid));
|
|
105
|
+
|
|
106
|
+
// Set mtime 25 hours ago
|
|
107
|
+
const twentyFiveHoursAgo = new Date(Date.now() - 25 * 60 * 60 * 1000);
|
|
108
|
+
await utimes(pidFile, twentyFiveHoursAgo, twentyFiveHoursAgo);
|
|
109
|
+
|
|
110
|
+
const checks = await checkWatchdog(mockConfig, tempDir);
|
|
111
|
+
|
|
112
|
+
const stalenessCheck = checks.find((c) => c.name === "watchdog pid staleness");
|
|
113
|
+
expect(stalenessCheck).toBeDefined();
|
|
114
|
+
expect(stalenessCheck?.status).toBe("warn");
|
|
115
|
+
expect(stalenessCheck?.message).toContain("older than 24 hours");
|
|
116
|
+
expect(stalenessCheck?.details?.some((d) => d.includes("hours"))).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("Tier 2 monitor check skipped when tier2Enabled=false — no monitor check in results", async () => {
|
|
120
|
+
mockConfig.watchdog.tier2Enabled = false;
|
|
121
|
+
writeFileSync(join(tempDir, "watchdog.pid"), String(process.pid));
|
|
122
|
+
const checks = await checkWatchdog(mockConfig, tempDir);
|
|
123
|
+
|
|
124
|
+
const monitorCheck = checks.find((c) => c.name === "tier2 monitor");
|
|
125
|
+
expect(monitorCheck).toBeUndefined();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("Tier 1 triage check skipped when tier1Enabled=false — no triage check in results", async () => {
|
|
129
|
+
mockConfig.watchdog.tier1Enabled = false;
|
|
130
|
+
writeFileSync(join(tempDir, "watchdog.pid"), String(process.pid));
|
|
131
|
+
const checks = await checkWatchdog(mockConfig, tempDir);
|
|
132
|
+
|
|
133
|
+
const triageCheck = checks.find((c) => c.name === "tier1 triage");
|
|
134
|
+
expect(triageCheck).toBeUndefined();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("Tier 2 monitor check warns when no monitor session found", async () => {
|
|
138
|
+
mockConfig.watchdog.tier2Enabled = true;
|
|
139
|
+
writeFileSync(join(tempDir, "watchdog.pid"), String(process.pid));
|
|
140
|
+
// No sessions.db or sessions.json — openSessionStore creates empty DB
|
|
141
|
+
const checks = await checkWatchdog(mockConfig, tempDir);
|
|
142
|
+
|
|
143
|
+
const monitorCheck = checks.find((c) => c.name === "tier2 monitor");
|
|
144
|
+
expect(monitorCheck).toBeDefined();
|
|
145
|
+
// Either warns about not running or store unavailable — both are acceptable
|
|
146
|
+
expect(monitorCheck?.status).toBe("warn");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("Tier 1 triage check does not crash when enabled", async () => {
|
|
150
|
+
mockConfig.watchdog.tier1Enabled = true;
|
|
151
|
+
writeFileSync(join(tempDir, "watchdog.pid"), String(process.pid));
|
|
152
|
+
// getRuntime will succeed (defaults to "claude" which is always registered)
|
|
153
|
+
let checks: Awaited<ReturnType<typeof checkWatchdog>>;
|
|
154
|
+
try {
|
|
155
|
+
checks = await checkWatchdog(mockConfig, tempDir);
|
|
156
|
+
} catch {
|
|
157
|
+
// Should not throw
|
|
158
|
+
expect(false).toBe(true);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const triageCheck = checks.find((c) => c.name === "tier1 triage");
|
|
163
|
+
expect(triageCheck).toBeDefined();
|
|
164
|
+
// Either pass or warn — depending on environment; it should not throw
|
|
165
|
+
expect(triageCheck?.status === "pass" || triageCheck?.status === "warn").toBe(true);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { stat, unlink } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { getRuntime } from "../runtimes/registry.ts";
|
|
5
|
+
import { openSessionStore } from "../sessions/compat.ts";
|
|
6
|
+
import { findRunningWatchdogProcesses } from "../utils/process-scan.ts";
|
|
7
|
+
import { isProcessRunning } from "../watchdog/health.ts";
|
|
8
|
+
import type { DoctorCheck, DoctorCheckFn } from "./types.ts";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Watchdog subsystem health checks.
|
|
12
|
+
* Validates PID file integrity, process liveness, and tier availability.
|
|
13
|
+
*/
|
|
14
|
+
export const checkWatchdog: DoctorCheckFn = async (
|
|
15
|
+
config,
|
|
16
|
+
agentplateDir,
|
|
17
|
+
): Promise<DoctorCheck[]> => {
|
|
18
|
+
const checks: DoctorCheck[] = [];
|
|
19
|
+
|
|
20
|
+
// If tier0 is disabled, skip all checks with a single pass result
|
|
21
|
+
if (!config.watchdog.tier0Enabled) {
|
|
22
|
+
checks.push({
|
|
23
|
+
name: "watchdog disabled",
|
|
24
|
+
category: "watchdog",
|
|
25
|
+
status: "pass",
|
|
26
|
+
message: "Watchdog daemon is disabled (tier0Enabled: false)",
|
|
27
|
+
});
|
|
28
|
+
return checks;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const pidFilePath = join(agentplateDir, "watchdog.pid");
|
|
32
|
+
|
|
33
|
+
// Check 1: PID file exists and is readable
|
|
34
|
+
if (!existsSync(pidFilePath)) {
|
|
35
|
+
checks.push({
|
|
36
|
+
name: "watchdog pid file",
|
|
37
|
+
category: "watchdog",
|
|
38
|
+
status: "warn",
|
|
39
|
+
message: "Watchdog PID file not found — daemon may not be running",
|
|
40
|
+
});
|
|
41
|
+
} else {
|
|
42
|
+
// Check 2: PID file not corrupted
|
|
43
|
+
const pidText = await Bun.file(pidFilePath).text();
|
|
44
|
+
const pid = Number.parseInt(pidText.trim(), 10);
|
|
45
|
+
|
|
46
|
+
if (Number.isNaN(pid) || pid <= 0) {
|
|
47
|
+
checks.push({
|
|
48
|
+
name: "watchdog pid integrity",
|
|
49
|
+
category: "watchdog",
|
|
50
|
+
status: "fail",
|
|
51
|
+
message: "Watchdog PID file is corrupted",
|
|
52
|
+
details: [`Raw content: ${pidText.trim()}`],
|
|
53
|
+
fixable: true,
|
|
54
|
+
fix: async () => {
|
|
55
|
+
await unlink(pidFilePath);
|
|
56
|
+
return ["Removed corrupted watchdog PID file"];
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
} else {
|
|
60
|
+
// Check 3: PID alive via isProcessRunning()
|
|
61
|
+
const alive = isProcessRunning(pid);
|
|
62
|
+
if (!alive) {
|
|
63
|
+
checks.push({
|
|
64
|
+
name: "watchdog process",
|
|
65
|
+
category: "watchdog",
|
|
66
|
+
status: "warn",
|
|
67
|
+
message: "Watchdog process is not running (stale PID file)",
|
|
68
|
+
details: [`PID: ${pid}`],
|
|
69
|
+
fixable: true,
|
|
70
|
+
fix: async () => {
|
|
71
|
+
await unlink(pidFilePath);
|
|
72
|
+
return ["Removed stale watchdog PID file"];
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
} else {
|
|
76
|
+
checks.push({
|
|
77
|
+
name: "watchdog process",
|
|
78
|
+
category: "watchdog",
|
|
79
|
+
status: "pass",
|
|
80
|
+
message: "Watchdog daemon is running",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check 4: PID file staleness > 24h
|
|
85
|
+
const fileStat = await stat(pidFilePath);
|
|
86
|
+
const ageMs = Date.now() - fileStat.mtimeMs;
|
|
87
|
+
const twentyFourHoursMs = 24 * 60 * 60 * 1000;
|
|
88
|
+
if (ageMs > twentyFourHoursMs) {
|
|
89
|
+
const ageHours = Math.round(ageMs / (1000 * 60 * 60));
|
|
90
|
+
checks.push({
|
|
91
|
+
name: "watchdog pid staleness",
|
|
92
|
+
category: "watchdog",
|
|
93
|
+
status: "warn",
|
|
94
|
+
message: "Watchdog PID file is older than 24 hours",
|
|
95
|
+
details: [`File age: ${ageHours} hours`],
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check 5: Tier 2 monitor running if tier2Enabled
|
|
102
|
+
if (config.watchdog.tier2Enabled) {
|
|
103
|
+
try {
|
|
104
|
+
const { store } = openSessionStore(agentplateDir);
|
|
105
|
+
try {
|
|
106
|
+
const sessions = store.getAll();
|
|
107
|
+
const monitorActive = sessions.some(
|
|
108
|
+
(s) => s.capability === "monitor" && s.state !== "completed" && s.state !== "zombie",
|
|
109
|
+
);
|
|
110
|
+
if (!monitorActive) {
|
|
111
|
+
checks.push({
|
|
112
|
+
name: "tier2 monitor",
|
|
113
|
+
category: "watchdog",
|
|
114
|
+
status: "warn",
|
|
115
|
+
message: "Tier 2 monitor is enabled but not running",
|
|
116
|
+
});
|
|
117
|
+
} else {
|
|
118
|
+
checks.push({
|
|
119
|
+
name: "tier2 monitor",
|
|
120
|
+
category: "watchdog",
|
|
121
|
+
status: "pass",
|
|
122
|
+
message: "Tier 2 monitor agent is active",
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
} finally {
|
|
126
|
+
store.close();
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
checks.push({
|
|
130
|
+
name: "tier2 monitor",
|
|
131
|
+
category: "watchdog",
|
|
132
|
+
status: "warn",
|
|
133
|
+
message: "Tier 2 monitor check skipped — session store unavailable",
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Check 6: multi-daemon detection (agentplate-8ef6).
|
|
139
|
+
// Earlier releases had no exclusion lock, so multiple `ap watch` daemons
|
|
140
|
+
// could run simultaneously. We scan the process table for `ap watch`
|
|
141
|
+
// processes and flag any case with more than one. This is observational —
|
|
142
|
+
// even with the lock now in place, a corrupted/missing PID file could
|
|
143
|
+
// still let a foreign daemon slip past, and we want doctor to catch it.
|
|
144
|
+
try {
|
|
145
|
+
const watchProcs = await findRunningWatchdogProcesses();
|
|
146
|
+
if (watchProcs.length > 1) {
|
|
147
|
+
const lockOwner = existsSync(pidFilePath)
|
|
148
|
+
? Number.parseInt((await Bun.file(pidFilePath).text()).trim(), 10)
|
|
149
|
+
: Number.NaN;
|
|
150
|
+
const lockOwnerLabel = Number.isFinite(lockOwner) ? `${lockOwner}` : "(none)";
|
|
151
|
+
const pidList = watchProcs.map((p) => p.pid).join(", ");
|
|
152
|
+
checks.push({
|
|
153
|
+
name: "watchdog multi-daemon",
|
|
154
|
+
category: "watchdog",
|
|
155
|
+
status: "fail",
|
|
156
|
+
message: `${watchProcs.length} 'ap watch' daemons running concurrently — only one should be live`,
|
|
157
|
+
details: [
|
|
158
|
+
`Live PIDs: ${pidList}`,
|
|
159
|
+
`PID-file owner: ${lockOwnerLabel}`,
|
|
160
|
+
"Run 'ap watch --kill-others' to terminate the foreign daemons.",
|
|
161
|
+
],
|
|
162
|
+
fixable: true,
|
|
163
|
+
fix: async () => {
|
|
164
|
+
const ownerPid = Number.isFinite(lockOwner) ? lockOwner : null;
|
|
165
|
+
const messages: string[] = [];
|
|
166
|
+
for (const proc of watchProcs) {
|
|
167
|
+
if (proc.pid === ownerPid) continue;
|
|
168
|
+
try {
|
|
169
|
+
process.kill(proc.pid, "SIGTERM");
|
|
170
|
+
messages.push(`Killed foreign watchdog PID ${proc.pid}`);
|
|
171
|
+
} catch {
|
|
172
|
+
messages.push(`PID ${proc.pid} already gone`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (messages.length === 0) {
|
|
176
|
+
messages.push("No foreign watchdogs to kill — fix is a no-op");
|
|
177
|
+
}
|
|
178
|
+
return messages;
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
// Process scan failure is non-fatal — leave a soft warning instead of
|
|
184
|
+
// failing the whole doctor run.
|
|
185
|
+
checks.push({
|
|
186
|
+
name: "watchdog multi-daemon",
|
|
187
|
+
category: "watchdog",
|
|
188
|
+
status: "warn",
|
|
189
|
+
message: "Could not scan process table for foreign 'ap watch' daemons",
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Check 7: Tier 1 triage available if tier1Enabled
|
|
194
|
+
if (config.watchdog.tier1Enabled) {
|
|
195
|
+
try {
|
|
196
|
+
getRuntime(config?.runtime?.printCommand ?? config?.runtime?.default, config);
|
|
197
|
+
checks.push({
|
|
198
|
+
name: "tier1 triage",
|
|
199
|
+
category: "watchdog",
|
|
200
|
+
status: "pass",
|
|
201
|
+
message: "Tier 1 triage runtime is available",
|
|
202
|
+
});
|
|
203
|
+
} catch {
|
|
204
|
+
checks.push({
|
|
205
|
+
name: "tier1 triage",
|
|
206
|
+
category: "watchdog",
|
|
207
|
+
status: "warn",
|
|
208
|
+
message: "Tier 1 triage is enabled but runtime is not available",
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return checks;
|
|
214
|
+
};
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { readdir, stat } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { createManifestLoader } from "../agents/manifest.ts";
|
|
5
|
+
import { writeOverlay } from "../agents/overlay.ts";
|
|
6
|
+
import type { Spawner } from "../commands/init.ts";
|
|
7
|
+
import { initCommand } from "../commands/init.ts";
|
|
8
|
+
import { loadConfig } from "../config.ts";
|
|
9
|
+
import { cleanupTempDir, createTempGitRepo } from "../test-helpers.ts";
|
|
10
|
+
import type { OverlayConfig } from "../types.ts";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* E2E test: init→sling lifecycle on a throwaway external project.
|
|
14
|
+
*
|
|
15
|
+
* Validates the "project-agnostic" promise by running agentplate init on a
|
|
16
|
+
* fresh temp git repo (NOT the agentplate repo itself), then verifying all
|
|
17
|
+
* artifacts, loading config + manifest via real APIs, and generating an overlay.
|
|
18
|
+
*
|
|
19
|
+
* Uses real filesystem and real git repos.
|
|
20
|
+
* Uses a no-op spawner so ecosystem CLIs (lm/sr/tl) don't need to be installed in CI.
|
|
21
|
+
* Suppresses stdout because initCommand prints status lines.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** No-op spawner that treats all ecosystem tools as "not installed". */
|
|
25
|
+
const noopSpawner: Spawner = async () => ({ exitCode: 1, stdout: "", stderr: "not found" });
|
|
26
|
+
|
|
27
|
+
const EXPECTED_AGENT_DEFS = [
|
|
28
|
+
"builder.md",
|
|
29
|
+
"coordinator.md",
|
|
30
|
+
"lead.md",
|
|
31
|
+
"merger.md",
|
|
32
|
+
"monitor.md",
|
|
33
|
+
"orchestrator.md",
|
|
34
|
+
"ap-co-creation.md",
|
|
35
|
+
"reviewer.md",
|
|
36
|
+
"scout.md",
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
describe("E2E: init→sling lifecycle on external project", () => {
|
|
40
|
+
let tempDir: string;
|
|
41
|
+
let originalCwd: string;
|
|
42
|
+
let originalWrite: typeof process.stdout.write;
|
|
43
|
+
|
|
44
|
+
beforeEach(async () => {
|
|
45
|
+
tempDir = await createTempGitRepo();
|
|
46
|
+
originalCwd = process.cwd();
|
|
47
|
+
process.chdir(tempDir);
|
|
48
|
+
|
|
49
|
+
// Suppress stdout noise from initCommand
|
|
50
|
+
originalWrite = process.stdout.write;
|
|
51
|
+
process.stdout.write = (() => true) as typeof process.stdout.write;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
afterEach(async () => {
|
|
55
|
+
process.chdir(originalCwd);
|
|
56
|
+
process.stdout.write = originalWrite;
|
|
57
|
+
await cleanupTempDir(tempDir);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("init creates all expected artifacts", async () => {
|
|
61
|
+
await initCommand({ _spawner: noopSpawner });
|
|
62
|
+
|
|
63
|
+
const agentplateDir = join(tempDir, ".agentplate");
|
|
64
|
+
|
|
65
|
+
// config.yaml exists
|
|
66
|
+
const configFile = Bun.file(join(agentplateDir, "config.yaml"));
|
|
67
|
+
expect(await configFile.exists()).toBe(true);
|
|
68
|
+
|
|
69
|
+
// agent-manifest.json exists and is valid JSON
|
|
70
|
+
const manifestFile = Bun.file(join(agentplateDir, "agent-manifest.json"));
|
|
71
|
+
expect(await manifestFile.exists()).toBe(true);
|
|
72
|
+
const manifestText = await manifestFile.text();
|
|
73
|
+
const manifestJson = JSON.parse(manifestText);
|
|
74
|
+
expect(manifestJson).toBeDefined();
|
|
75
|
+
expect(manifestJson.version).toBe("1.0");
|
|
76
|
+
expect(typeof manifestJson.agents).toBe("object");
|
|
77
|
+
|
|
78
|
+
// hooks.json exists
|
|
79
|
+
const hooksFile = Bun.file(join(agentplateDir, "hooks.json"));
|
|
80
|
+
expect(await hooksFile.exists()).toBe(true);
|
|
81
|
+
|
|
82
|
+
// .gitignore exists
|
|
83
|
+
const gitignoreFile = Bun.file(join(agentplateDir, ".gitignore"));
|
|
84
|
+
expect(await gitignoreFile.exists()).toBe(true);
|
|
85
|
+
|
|
86
|
+
// agent-defs/ contains all 9 agent definition files (supervisor deprecated)
|
|
87
|
+
const agentDefsDir = join(agentplateDir, "agent-defs");
|
|
88
|
+
const agentDefFiles = (await readdir(agentDefsDir)).filter((f) => f.endsWith(".md")).sort();
|
|
89
|
+
expect(agentDefFiles).toEqual(EXPECTED_AGENT_DEFS);
|
|
90
|
+
|
|
91
|
+
// Required subdirectories exist
|
|
92
|
+
const expectedDirs = ["agents", "worktrees", "specs", "logs"];
|
|
93
|
+
for (const dirName of expectedDirs) {
|
|
94
|
+
const dirPath = join(agentplateDir, dirName);
|
|
95
|
+
const dirStat = await stat(dirPath);
|
|
96
|
+
expect(dirStat.isDirectory()).toBe(true);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("loadConfig returns valid config pointing to temp dir", async () => {
|
|
101
|
+
await initCommand({ _spawner: noopSpawner });
|
|
102
|
+
|
|
103
|
+
const config = await loadConfig(tempDir);
|
|
104
|
+
|
|
105
|
+
// project.root should point to the temp directory
|
|
106
|
+
expect(config.project.root).toBe(tempDir);
|
|
107
|
+
|
|
108
|
+
// agents.baseDir should be the relative path to agent-defs
|
|
109
|
+
expect(config.agents.baseDir).toBe(".agentplate/agent-defs");
|
|
110
|
+
|
|
111
|
+
// canonicalBranch should be detected (main for our test repos)
|
|
112
|
+
expect(config.project.canonicalBranch).toBeTruthy();
|
|
113
|
+
|
|
114
|
+
// name should be set (from dir basename or git remote)
|
|
115
|
+
expect(config.project.name).toBeTruthy();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("manifest loads successfully with all 8 agents (supervisor deprecated)", async () => {
|
|
119
|
+
await initCommand({ _spawner: noopSpawner });
|
|
120
|
+
|
|
121
|
+
const manifestPath = join(tempDir, ".agentplate", "agent-manifest.json");
|
|
122
|
+
const agentDefsDir = join(tempDir, ".agentplate", "agent-defs");
|
|
123
|
+
const loader = createManifestLoader(manifestPath, agentDefsDir);
|
|
124
|
+
|
|
125
|
+
const manifest = await loader.load();
|
|
126
|
+
|
|
127
|
+
// All 8 agents present (supervisor removed: deprecated, use lead instead)
|
|
128
|
+
const agentNames = Object.keys(manifest.agents).sort();
|
|
129
|
+
expect(agentNames).toEqual([
|
|
130
|
+
"builder",
|
|
131
|
+
"coordinator",
|
|
132
|
+
"lead",
|
|
133
|
+
"merger",
|
|
134
|
+
"monitor",
|
|
135
|
+
"orchestrator",
|
|
136
|
+
"reviewer",
|
|
137
|
+
"scout",
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
// Each agent has a valid file reference
|
|
141
|
+
for (const [_name, def] of Object.entries(manifest.agents)) {
|
|
142
|
+
expect(def.file).toEndWith(".md");
|
|
143
|
+
// Verify the referenced .md file actually exists
|
|
144
|
+
const mdFile = Bun.file(join(agentDefsDir, def.file));
|
|
145
|
+
expect(await mdFile.exists()).toBe(true);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Validation returns no errors
|
|
149
|
+
const errors = loader.validate();
|
|
150
|
+
expect(errors).toEqual([]);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("manifest capability index is consistent", async () => {
|
|
154
|
+
await initCommand({ _spawner: noopSpawner });
|
|
155
|
+
|
|
156
|
+
const manifestPath = join(tempDir, ".agentplate", "agent-manifest.json");
|
|
157
|
+
const agentDefsDir = join(tempDir, ".agentplate", "agent-defs");
|
|
158
|
+
const loader = createManifestLoader(manifestPath, agentDefsDir);
|
|
159
|
+
|
|
160
|
+
const manifest = await loader.load();
|
|
161
|
+
|
|
162
|
+
// capabilityIndex should map capabilities to agent names
|
|
163
|
+
expect(Object.keys(manifest.capabilityIndex).length).toBeGreaterThan(0);
|
|
164
|
+
|
|
165
|
+
// Each capability in the index should reference agents that declare it
|
|
166
|
+
for (const [cap, names] of Object.entries(manifest.capabilityIndex)) {
|
|
167
|
+
for (const name of names) {
|
|
168
|
+
const agent = manifest.agents[name];
|
|
169
|
+
expect(agent).toBeDefined();
|
|
170
|
+
expect(agent?.capabilities).toContain(cap);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("overlay generation works for external project", async () => {
|
|
176
|
+
await initCommand({ _spawner: noopSpawner });
|
|
177
|
+
|
|
178
|
+
const agentDefsDir = join(tempDir, ".agentplate", "agent-defs");
|
|
179
|
+
const baseDefinition = await Bun.file(join(agentDefsDir, "builder.md")).text();
|
|
180
|
+
|
|
181
|
+
const overlayConfig: OverlayConfig = {
|
|
182
|
+
agentName: "test-agent",
|
|
183
|
+
taskId: "test-bead-001",
|
|
184
|
+
specPath: null,
|
|
185
|
+
branchName: "agentplate/test-agent/test-bead-001",
|
|
186
|
+
worktreePath: join(tempDir, ".agentplate", "worktrees", "test-agent"),
|
|
187
|
+
fileScope: [],
|
|
188
|
+
loamDomains: [],
|
|
189
|
+
parentAgent: null,
|
|
190
|
+
depth: 0,
|
|
191
|
+
canSpawn: false,
|
|
192
|
+
capability: "builder",
|
|
193
|
+
baseDefinition,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// Write the overlay into a subdirectory of the temp dir (simulating a worktree)
|
|
197
|
+
const worktreePath = join(tempDir, ".agentplate", "worktrees", "test-agent");
|
|
198
|
+
const { mkdir } = await import("node:fs/promises");
|
|
199
|
+
await mkdir(worktreePath, { recursive: true });
|
|
200
|
+
|
|
201
|
+
await writeOverlay(worktreePath, overlayConfig, tempDir);
|
|
202
|
+
|
|
203
|
+
// Verify the overlay was written
|
|
204
|
+
const overlayPath = join(worktreePath, ".claude", "CLAUDE.md");
|
|
205
|
+
const overlayFile = Bun.file(overlayPath);
|
|
206
|
+
expect(await overlayFile.exists()).toBe(true);
|
|
207
|
+
|
|
208
|
+
const content = await overlayFile.text();
|
|
209
|
+
|
|
210
|
+
// Verify template placeholders were replaced
|
|
211
|
+
expect(content).toContain("test-agent");
|
|
212
|
+
expect(content).toContain("test-bead-001");
|
|
213
|
+
expect(content).toContain("agentplate/test-agent/test-bead-001");
|
|
214
|
+
expect(content).not.toContain("{{AGENT_NAME}}");
|
|
215
|
+
expect(content).not.toContain("{{BEAD_ID}}");
|
|
216
|
+
expect(content).not.toContain("{{BRANCH_NAME}}");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("full init→config→manifest→overlay pipeline succeeds", async () => {
|
|
220
|
+
// This test validates the entire lifecycle in sequence:
|
|
221
|
+
// init → load config → load manifest → generate overlay
|
|
222
|
+
|
|
223
|
+
// Step 1: Init
|
|
224
|
+
await initCommand({ _spawner: noopSpawner });
|
|
225
|
+
|
|
226
|
+
// Step 2: Load config
|
|
227
|
+
const config = await loadConfig(tempDir);
|
|
228
|
+
expect(config.project.root).toBe(tempDir);
|
|
229
|
+
|
|
230
|
+
// Step 3: Load manifest using config paths
|
|
231
|
+
const manifestPath = join(config.project.root, config.agents.manifestPath);
|
|
232
|
+
const agentDefsDir = join(config.project.root, config.agents.baseDir);
|
|
233
|
+
const loader = createManifestLoader(manifestPath, agentDefsDir);
|
|
234
|
+
await loader.load();
|
|
235
|
+
|
|
236
|
+
// Verify builder agent exists (the one we'll use for overlay)
|
|
237
|
+
const builder = loader.getAgent("builder");
|
|
238
|
+
expect(builder).toBeDefined();
|
|
239
|
+
expect(builder?.canSpawn).toBe(false);
|
|
240
|
+
|
|
241
|
+
// Verify lead agent can spawn
|
|
242
|
+
const lead = loader.getAgent("lead");
|
|
243
|
+
expect(lead).toBeDefined();
|
|
244
|
+
expect(lead?.canSpawn).toBe(true);
|
|
245
|
+
|
|
246
|
+
// Step 4: Generate overlay using a realistic config
|
|
247
|
+
const builderDef = await Bun.file(join(agentDefsDir, "builder.md")).text();
|
|
248
|
+
const overlayConfig: OverlayConfig = {
|
|
249
|
+
agentName: "lifecycle-builder",
|
|
250
|
+
taskId: "lifecycle-001",
|
|
251
|
+
specPath: join(tempDir, ".agentplate", "specs", "lifecycle-001.md"),
|
|
252
|
+
branchName: "agentplate/lifecycle-builder/lifecycle-001",
|
|
253
|
+
worktreePath: join(tempDir, ".agentplate", "worktrees", "lifecycle-builder"),
|
|
254
|
+
fileScope: ["src/main.ts", "src/utils.ts"],
|
|
255
|
+
loamDomains: ["typescript"],
|
|
256
|
+
parentAgent: "orchestrator",
|
|
257
|
+
depth: 0,
|
|
258
|
+
canSpawn: false,
|
|
259
|
+
capability: "builder",
|
|
260
|
+
baseDefinition: builderDef,
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const worktreePath = join(tempDir, ".agentplate", "worktrees", "lifecycle-builder");
|
|
264
|
+
const { mkdir } = await import("node:fs/promises");
|
|
265
|
+
await mkdir(worktreePath, { recursive: true });
|
|
266
|
+
|
|
267
|
+
await writeOverlay(worktreePath, overlayConfig, tempDir);
|
|
268
|
+
|
|
269
|
+
const overlayContent = await Bun.file(join(worktreePath, ".claude", "CLAUDE.md")).text();
|
|
270
|
+
|
|
271
|
+
// Verify all overlay fields rendered correctly
|
|
272
|
+
expect(overlayContent).toContain("lifecycle-builder");
|
|
273
|
+
expect(overlayContent).toContain("lifecycle-001");
|
|
274
|
+
expect(overlayContent).toContain("agentplate/lifecycle-builder/lifecycle-001");
|
|
275
|
+
expect(overlayContent).toContain("orchestrator");
|
|
276
|
+
expect(overlayContent).toContain("`src/main.ts`");
|
|
277
|
+
expect(overlayContent).toContain("`src/utils.ts`");
|
|
278
|
+
expect(overlayContent).toContain("lm prime typescript");
|
|
279
|
+
|
|
280
|
+
// No unresolved placeholders
|
|
281
|
+
expect(overlayContent).not.toMatch(/\{\{[A-Z_]+\}\}/);
|
|
282
|
+
});
|
|
283
|
+
});
|