@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,967 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: ap init [--force] [--yes|-y] [--name <name>]
|
|
3
|
+
*
|
|
4
|
+
* Scaffolds the `.agentplate/` directory in the current project with:
|
|
5
|
+
* - config.yaml (serialized from DEFAULT_CONFIG)
|
|
6
|
+
* - agent-manifest.json (starter agent definitions)
|
|
7
|
+
* - hooks.json (central hooks config)
|
|
8
|
+
* - Required subdirectories (agents/, worktrees/, specs/, logs/)
|
|
9
|
+
* - .gitignore entries for transient files
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Database } from "bun:sqlite";
|
|
13
|
+
import { mkdir, readdir, stat } from "node:fs/promises";
|
|
14
|
+
import { basename, join } from "node:path";
|
|
15
|
+
import { DEFAULT_CONFIG } from "../config.ts";
|
|
16
|
+
import { ValidationError } from "../errors.ts";
|
|
17
|
+
import { jsonOutput } from "../json.ts";
|
|
18
|
+
import { printHint, printSuccess, printWarning } from "../logging/color.ts";
|
|
19
|
+
import type { AgentManifest, AgentplateConfig } from "../types.ts";
|
|
20
|
+
|
|
21
|
+
const AGENTPLATE_DIR = ".agentplate";
|
|
22
|
+
|
|
23
|
+
// ---- Ecosystem Bootstrap ----
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Spawner abstraction for testability.
|
|
27
|
+
* Wraps Bun.spawn for running sibling CLI tools.
|
|
28
|
+
*/
|
|
29
|
+
export type Spawner = (
|
|
30
|
+
args: string[],
|
|
31
|
+
opts?: { cwd?: string },
|
|
32
|
+
) => Promise<{ exitCode: number; stdout: string; stderr: string }>;
|
|
33
|
+
|
|
34
|
+
const defaultSpawner: Spawner = async (args, opts) => {
|
|
35
|
+
try {
|
|
36
|
+
const proc = Bun.spawn(args, {
|
|
37
|
+
cwd: opts?.cwd,
|
|
38
|
+
stdout: "pipe",
|
|
39
|
+
stderr: "pipe",
|
|
40
|
+
});
|
|
41
|
+
const exitCode = await proc.exited;
|
|
42
|
+
const stdout = await new Response(proc.stdout).text();
|
|
43
|
+
const stderr = await new Response(proc.stderr).text();
|
|
44
|
+
return { exitCode, stdout, stderr };
|
|
45
|
+
} catch (err) {
|
|
46
|
+
// Binary not found (ENOENT) or other spawn failure — treat as non-zero exit
|
|
47
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
48
|
+
return { exitCode: 1, stdout: "", stderr: message };
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
interface SiblingTool {
|
|
53
|
+
name: string;
|
|
54
|
+
cli: string;
|
|
55
|
+
dotDir: string;
|
|
56
|
+
initCmd: string[];
|
|
57
|
+
onboardCmd: string[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const SIBLING_TOOLS: SiblingTool[] = [
|
|
61
|
+
{ name: "loam", cli: "lm", dotDir: ".loam", initCmd: ["init"], onboardCmd: ["onboard"] },
|
|
62
|
+
{ name: "sprout", cli: "sr", dotDir: ".sprout", initCmd: ["init"], onboardCmd: ["onboard"] },
|
|
63
|
+
{ name: "trellis", cli: "tl", dotDir: ".trellis", initCmd: ["init"], onboardCmd: ["onboard"] },
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
type ToolStatus = "initialized" | "already_initialized" | "skipped";
|
|
67
|
+
type OnboardStatus = "appended" | "current";
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Resolve the set of sibling tools to bootstrap.
|
|
71
|
+
*
|
|
72
|
+
* If opts.tools is set (comma-separated list of names), filter to those.
|
|
73
|
+
* Otherwise start with all three and remove any skipped via skip flags.
|
|
74
|
+
*/
|
|
75
|
+
export function resolveToolSet(opts: InitOptions): SiblingTool[] {
|
|
76
|
+
if (opts.tools) {
|
|
77
|
+
const requested = opts.tools.split(",").map((t) => t.trim());
|
|
78
|
+
return SIBLING_TOOLS.filter((t) => requested.includes(t.name));
|
|
79
|
+
}
|
|
80
|
+
return SIBLING_TOOLS.filter((t) => {
|
|
81
|
+
if (t.name === "loam" && opts.skipLoam) return false;
|
|
82
|
+
if (t.name === "sprout" && opts.skipSprout) return false;
|
|
83
|
+
if (t.name === "trellis" && opts.skipTrellis) return false;
|
|
84
|
+
return true;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function isToolInstalled(cli: string, spawner: Spawner): Promise<boolean> {
|
|
89
|
+
try {
|
|
90
|
+
const result = await spawner([cli, "--version"]);
|
|
91
|
+
return result.exitCode === 0;
|
|
92
|
+
} catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function initSiblingTool(
|
|
98
|
+
tool: SiblingTool,
|
|
99
|
+
projectRoot: string,
|
|
100
|
+
spawner: Spawner,
|
|
101
|
+
): Promise<ToolStatus> {
|
|
102
|
+
const installed = await isToolInstalled(tool.cli, spawner);
|
|
103
|
+
if (!installed) {
|
|
104
|
+
printWarning(
|
|
105
|
+
`${tool.name} not installed — skipping`,
|
|
106
|
+
`install: npm i -g @ag-eco/${tool.name}-cli`,
|
|
107
|
+
);
|
|
108
|
+
return "skipped";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let result: { exitCode: number; stdout: string; stderr: string };
|
|
112
|
+
try {
|
|
113
|
+
result = await spawner([tool.cli, ...tool.initCmd], { cwd: projectRoot });
|
|
114
|
+
} catch (err) {
|
|
115
|
+
// Spawn failure (e.g. ENOENT) — treat as not installed
|
|
116
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
117
|
+
printWarning(`${tool.name} init failed`, message);
|
|
118
|
+
return "skipped";
|
|
119
|
+
}
|
|
120
|
+
if (result.exitCode !== 0) {
|
|
121
|
+
// Check if dot directory already exists (already initialized)
|
|
122
|
+
try {
|
|
123
|
+
await stat(join(projectRoot, tool.dotDir));
|
|
124
|
+
return "already_initialized";
|
|
125
|
+
} catch {
|
|
126
|
+
// Directory doesn't exist — real failure
|
|
127
|
+
printWarning(`${tool.name} init failed`, result.stderr.trim() || result.stdout.trim());
|
|
128
|
+
return "skipped";
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
printSuccess(`Bootstrapped ${tool.name}`);
|
|
133
|
+
return "initialized";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function onboardTool(
|
|
137
|
+
tool: SiblingTool,
|
|
138
|
+
projectRoot: string,
|
|
139
|
+
spawner: Spawner,
|
|
140
|
+
): Promise<OnboardStatus> {
|
|
141
|
+
const installed = await isToolInstalled(tool.cli, spawner);
|
|
142
|
+
if (!installed) return "current";
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const result = await spawner([tool.cli, ...tool.onboardCmd], { cwd: projectRoot });
|
|
146
|
+
return result.exitCode === 0 ? "appended" : "current";
|
|
147
|
+
} catch {
|
|
148
|
+
return "current";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Known runtime CLI candidates in detection priority order.
|
|
154
|
+
* First installed runtime wins.
|
|
155
|
+
*/
|
|
156
|
+
const RUNTIME_CANDIDATES: Array<{ name: string; cli: string }> = [
|
|
157
|
+
{ name: "claude", cli: "claude" },
|
|
158
|
+
{ name: "copilot", cli: "copilot" },
|
|
159
|
+
{ name: "gemini", cli: "gemini" },
|
|
160
|
+
{ name: "opencode", cli: "opencode" },
|
|
161
|
+
{ name: "sapling", cli: "sp" },
|
|
162
|
+
{ name: "pi", cli: "pi" },
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Detect the default runtime by checking which coding agent CLIs are installed.
|
|
167
|
+
*
|
|
168
|
+
* Uses `which <cli>` via the spawner abstraction so detection is testable
|
|
169
|
+
* without real binaries on PATH. Returns the first installed runtime by
|
|
170
|
+
* priority order, or "claude" as the safe fallback.
|
|
171
|
+
*
|
|
172
|
+
* @param spawner - Spawner abstraction (defaults to Bun.spawn wrapper)
|
|
173
|
+
* @returns Runtime name suitable for config.runtime.default
|
|
174
|
+
*/
|
|
175
|
+
export async function detectDefaultRuntime(spawner: Spawner): Promise<string> {
|
|
176
|
+
for (const { name, cli } of RUNTIME_CANDIDATES) {
|
|
177
|
+
const result = await spawner(["which", cli]);
|
|
178
|
+
if (result.exitCode === 0) {
|
|
179
|
+
return name;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return "claude";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Set up .gitattributes with merge=union entries for JSONL files.
|
|
187
|
+
*
|
|
188
|
+
* Only adds entries not already present. Returns true if file was modified.
|
|
189
|
+
*/
|
|
190
|
+
async function setupGitattributes(projectRoot: string): Promise<boolean> {
|
|
191
|
+
const entries = [".loam/expertise/*.jsonl merge=union", ".sprout/issues.jsonl merge=union"];
|
|
192
|
+
|
|
193
|
+
const gitattrsPath = join(projectRoot, ".gitattributes");
|
|
194
|
+
let existing = "";
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
existing = await Bun.file(gitattrsPath).text();
|
|
198
|
+
} catch {
|
|
199
|
+
// File doesn't exist yet — will be created
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const missing = entries.filter((e) => !existing.includes(e));
|
|
203
|
+
if (missing.length === 0) return false;
|
|
204
|
+
|
|
205
|
+
const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
206
|
+
await Bun.write(gitattrsPath, `${existing}${separator}${missing.join("\n")}\n`);
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Detect the project name from git or fall back to directory name.
|
|
212
|
+
*/
|
|
213
|
+
async function detectProjectName(root: string): Promise<string> {
|
|
214
|
+
// Try git remote origin
|
|
215
|
+
try {
|
|
216
|
+
const proc = Bun.spawn(["git", "remote", "get-url", "origin"], {
|
|
217
|
+
cwd: root,
|
|
218
|
+
stdout: "pipe",
|
|
219
|
+
stderr: "pipe",
|
|
220
|
+
});
|
|
221
|
+
const exitCode = await proc.exited;
|
|
222
|
+
if (exitCode === 0) {
|
|
223
|
+
const url = (await new Response(proc.stdout).text()).trim();
|
|
224
|
+
// Extract repo name from URL: git@host:user/repo.git or https://host/user/repo.git
|
|
225
|
+
const match = url.match(/\/([^/]+?)(?:\.git)?$/);
|
|
226
|
+
if (match?.[1]) {
|
|
227
|
+
return match[1];
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
} catch {
|
|
231
|
+
// Git not available or not a git repo
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return basename(root);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Detect the canonical branch name from git.
|
|
239
|
+
*/
|
|
240
|
+
async function detectCanonicalBranch(root: string): Promise<string> {
|
|
241
|
+
try {
|
|
242
|
+
const proc = Bun.spawn(["git", "symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
243
|
+
cwd: root,
|
|
244
|
+
stdout: "pipe",
|
|
245
|
+
stderr: "pipe",
|
|
246
|
+
});
|
|
247
|
+
const exitCode = await proc.exited;
|
|
248
|
+
if (exitCode === 0) {
|
|
249
|
+
const ref = (await new Response(proc.stdout).text()).trim();
|
|
250
|
+
// refs/remotes/origin/main -> main
|
|
251
|
+
const branch = ref.split("/").pop();
|
|
252
|
+
if (branch) {
|
|
253
|
+
return branch;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
} catch {
|
|
257
|
+
// Not available
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Fall back to checking current branch
|
|
261
|
+
try {
|
|
262
|
+
const proc = Bun.spawn(["git", "branch", "--show-current"], {
|
|
263
|
+
cwd: root,
|
|
264
|
+
stdout: "pipe",
|
|
265
|
+
stderr: "pipe",
|
|
266
|
+
});
|
|
267
|
+
const exitCode = await proc.exited;
|
|
268
|
+
if (exitCode === 0) {
|
|
269
|
+
const branch = (await new Response(proc.stdout).text()).trim();
|
|
270
|
+
if (branch) {
|
|
271
|
+
return branch;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
} catch {
|
|
275
|
+
// Not available
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return "main";
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Serialize an AgentplateConfig to YAML format.
|
|
283
|
+
*
|
|
284
|
+
* Handles nested objects with indentation, scalar values,
|
|
285
|
+
* arrays with `- item` syntax, and empty arrays as `[]`.
|
|
286
|
+
*/
|
|
287
|
+
function serializeConfigToYaml(config: AgentplateConfig): string {
|
|
288
|
+
const lines: string[] = [];
|
|
289
|
+
lines.push("# Agentplate configuration");
|
|
290
|
+
lines.push("# See: https://github.com/agentplate/agentplate");
|
|
291
|
+
lines.push("");
|
|
292
|
+
|
|
293
|
+
serializeObject(config as unknown as Record<string, unknown>, lines, 0);
|
|
294
|
+
|
|
295
|
+
return `${lines.join("\n")}\n`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Recursively serialize an object to YAML lines.
|
|
300
|
+
*/
|
|
301
|
+
function serializeObject(obj: Record<string, unknown>, lines: string[], depth: number): void {
|
|
302
|
+
const indent = " ".repeat(depth);
|
|
303
|
+
|
|
304
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
305
|
+
if (value === null || value === undefined) {
|
|
306
|
+
lines.push(`${indent}${key}: null`);
|
|
307
|
+
} else if (typeof value === "object" && !Array.isArray(value)) {
|
|
308
|
+
lines.push(`${indent}${key}:`);
|
|
309
|
+
serializeObject(value as Record<string, unknown>, lines, depth + 1);
|
|
310
|
+
} else if (Array.isArray(value)) {
|
|
311
|
+
if (value.length === 0) {
|
|
312
|
+
lines.push(`${indent}${key}: []`);
|
|
313
|
+
} else {
|
|
314
|
+
lines.push(`${indent}${key}:`);
|
|
315
|
+
const itemIndent = " ".repeat(depth + 1);
|
|
316
|
+
const propIndent = " ".repeat(depth + 2);
|
|
317
|
+
for (const item of value) {
|
|
318
|
+
if (item !== null && typeof item === "object" && !Array.isArray(item)) {
|
|
319
|
+
// Object array item: "- firstKey: firstVal\n otherKey: otherVal"
|
|
320
|
+
const entries = Object.entries(item as Record<string, unknown>);
|
|
321
|
+
if (entries.length > 0) {
|
|
322
|
+
const [firstKey, firstVal] = entries[0] ?? [];
|
|
323
|
+
lines.push(`${itemIndent}- ${firstKey}: ${formatYamlValue(firstVal)}`);
|
|
324
|
+
for (let j = 1; j < entries.length; j++) {
|
|
325
|
+
const [k, v] = entries[j] ?? [];
|
|
326
|
+
lines.push(`${propIndent}${k}: ${formatYamlValue(v)}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
} else {
|
|
330
|
+
lines.push(`${itemIndent}- ${formatYamlValue(item)}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
} else {
|
|
335
|
+
lines.push(`${indent}${key}: ${formatYamlValue(value)}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Format a scalar value for YAML output.
|
|
342
|
+
*/
|
|
343
|
+
function formatYamlValue(value: unknown): string {
|
|
344
|
+
if (typeof value === "string") {
|
|
345
|
+
// Quote strings that could be misinterpreted
|
|
346
|
+
if (
|
|
347
|
+
value === "" ||
|
|
348
|
+
value === "true" ||
|
|
349
|
+
value === "false" ||
|
|
350
|
+
value === "null" ||
|
|
351
|
+
value.includes(":") ||
|
|
352
|
+
value.includes("#") ||
|
|
353
|
+
value.includes("'") ||
|
|
354
|
+
value.includes('"') ||
|
|
355
|
+
value.includes("\n") ||
|
|
356
|
+
/^\d/.test(value)
|
|
357
|
+
) {
|
|
358
|
+
// Use double quotes, escaping inner double quotes
|
|
359
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
360
|
+
}
|
|
361
|
+
return value;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (typeof value === "number") {
|
|
365
|
+
return String(value);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (typeof value === "boolean") {
|
|
369
|
+
return value ? "true" : "false";
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (value === null || value === undefined) {
|
|
373
|
+
return "null";
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return String(value);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Build the starter agent manifest.
|
|
381
|
+
*/
|
|
382
|
+
export function buildAgentManifest(): AgentManifest {
|
|
383
|
+
const agents: AgentManifest["agents"] = {
|
|
384
|
+
scout: {
|
|
385
|
+
file: "scout.md",
|
|
386
|
+
model: "haiku",
|
|
387
|
+
tools: ["Read", "Glob", "Grep", "Bash"],
|
|
388
|
+
capabilities: ["explore", "research"],
|
|
389
|
+
canSpawn: false,
|
|
390
|
+
constraints: ["read-only"],
|
|
391
|
+
},
|
|
392
|
+
builder: {
|
|
393
|
+
file: "builder.md",
|
|
394
|
+
model: "sonnet",
|
|
395
|
+
tools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash"],
|
|
396
|
+
capabilities: ["implement", "refactor", "fix"],
|
|
397
|
+
canSpawn: false,
|
|
398
|
+
constraints: [],
|
|
399
|
+
},
|
|
400
|
+
reviewer: {
|
|
401
|
+
file: "reviewer.md",
|
|
402
|
+
model: "sonnet",
|
|
403
|
+
tools: ["Read", "Glob", "Grep", "Bash"],
|
|
404
|
+
capabilities: ["review", "validate"],
|
|
405
|
+
canSpawn: false,
|
|
406
|
+
constraints: ["read-only"],
|
|
407
|
+
},
|
|
408
|
+
lead: {
|
|
409
|
+
file: "lead.md",
|
|
410
|
+
model: "opus",
|
|
411
|
+
tools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash", "Task"],
|
|
412
|
+
capabilities: ["coordinate", "implement", "review"],
|
|
413
|
+
canSpawn: true,
|
|
414
|
+
constraints: [],
|
|
415
|
+
},
|
|
416
|
+
merger: {
|
|
417
|
+
file: "merger.md",
|
|
418
|
+
model: "sonnet",
|
|
419
|
+
tools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash"],
|
|
420
|
+
capabilities: ["merge", "resolve-conflicts"],
|
|
421
|
+
canSpawn: false,
|
|
422
|
+
constraints: [],
|
|
423
|
+
},
|
|
424
|
+
coordinator: {
|
|
425
|
+
file: "coordinator.md",
|
|
426
|
+
model: "opus",
|
|
427
|
+
tools: ["Read", "Glob", "Grep", "Bash"],
|
|
428
|
+
capabilities: ["coordinate", "dispatch", "escalate"],
|
|
429
|
+
canSpawn: true,
|
|
430
|
+
constraints: ["read-only", "no-worktree"],
|
|
431
|
+
},
|
|
432
|
+
orchestrator: {
|
|
433
|
+
file: "orchestrator.md",
|
|
434
|
+
model: "opus",
|
|
435
|
+
tools: ["Read", "Glob", "Grep", "Bash"],
|
|
436
|
+
capabilities: ["orchestrate", "coordinate", "dispatch", "escalate"],
|
|
437
|
+
canSpawn: true,
|
|
438
|
+
constraints: ["read-only", "no-worktree"],
|
|
439
|
+
},
|
|
440
|
+
monitor: {
|
|
441
|
+
file: "monitor.md",
|
|
442
|
+
model: "sonnet",
|
|
443
|
+
tools: ["Read", "Glob", "Grep", "Bash"],
|
|
444
|
+
capabilities: ["monitor", "patrol"],
|
|
445
|
+
canSpawn: false,
|
|
446
|
+
constraints: ["read-only", "no-worktree"],
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
// Build capability index: map each capability to agent names that declare it
|
|
451
|
+
const capabilityIndex: Record<string, string[]> = {};
|
|
452
|
+
for (const [name, def] of Object.entries(agents)) {
|
|
453
|
+
for (const cap of def.capabilities) {
|
|
454
|
+
const existing = capabilityIndex[cap];
|
|
455
|
+
if (existing) {
|
|
456
|
+
existing.push(name);
|
|
457
|
+
} else {
|
|
458
|
+
capabilityIndex[cap] = [name];
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return { version: "1.0", agents, capabilityIndex };
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Build the hooks.json content for the project orchestrator.
|
|
468
|
+
*
|
|
469
|
+
* Always generates from scratch (not from the agent template, which contains
|
|
470
|
+
* {{AGENT_NAME}} placeholders and space indentation). Uses tab indentation
|
|
471
|
+
* to match Biome formatting rules.
|
|
472
|
+
*/
|
|
473
|
+
export function buildHooksJson(): string {
|
|
474
|
+
// Tool name extraction: reads hook stdin JSON and extracts tool_name field.
|
|
475
|
+
// Claude Code sends {"tool_name":"Bash","tool_input":{...}} on stdin for
|
|
476
|
+
// PreToolUse/PostToolUse hooks.
|
|
477
|
+
const toolNameExtract =
|
|
478
|
+
'read -r INPUT; TOOL_NAME=$(echo "$INPUT" | sed \'s/.*"tool_name": *"\\([^"]*\\)".*/\\1/\');';
|
|
479
|
+
|
|
480
|
+
const hooks = {
|
|
481
|
+
hooks: {
|
|
482
|
+
SessionStart: [
|
|
483
|
+
{
|
|
484
|
+
matcher: "",
|
|
485
|
+
hooks: [
|
|
486
|
+
{
|
|
487
|
+
type: "command",
|
|
488
|
+
command: "ap prime --agent orchestrator",
|
|
489
|
+
},
|
|
490
|
+
],
|
|
491
|
+
},
|
|
492
|
+
],
|
|
493
|
+
UserPromptSubmit: [
|
|
494
|
+
{
|
|
495
|
+
matcher: "",
|
|
496
|
+
hooks: [
|
|
497
|
+
{
|
|
498
|
+
type: "command",
|
|
499
|
+
command: "ap mail check --inject --agent orchestrator",
|
|
500
|
+
},
|
|
501
|
+
],
|
|
502
|
+
},
|
|
503
|
+
],
|
|
504
|
+
PreToolUse: [
|
|
505
|
+
{
|
|
506
|
+
matcher: "Bash",
|
|
507
|
+
hooks: [
|
|
508
|
+
{
|
|
509
|
+
type: "command",
|
|
510
|
+
command:
|
|
511
|
+
'read -r INPUT; CMD=$(echo "$INPUT" | sed \'s/.*"command": *"\\([^"]*\\)".*/\\1/\'); if echo "$CMD" | grep -qE \'\\bgit\\s+push\\b\'; then echo \'{"decision":"block","reason":"git push is blocked by agentplate — merge locally, push manually when ready"}\'; exit 0; fi;',
|
|
512
|
+
},
|
|
513
|
+
],
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
matcher: "",
|
|
517
|
+
hooks: [
|
|
518
|
+
{
|
|
519
|
+
type: "command",
|
|
520
|
+
command: `${toolNameExtract} ap log tool-start --agent orchestrator --tool-name "$TOOL_NAME"`,
|
|
521
|
+
},
|
|
522
|
+
],
|
|
523
|
+
},
|
|
524
|
+
],
|
|
525
|
+
PostToolUse: [
|
|
526
|
+
{
|
|
527
|
+
matcher: "",
|
|
528
|
+
hooks: [
|
|
529
|
+
{
|
|
530
|
+
type: "command",
|
|
531
|
+
command: `${toolNameExtract} ap log tool-end --agent orchestrator --tool-name "$TOOL_NAME"`,
|
|
532
|
+
},
|
|
533
|
+
],
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
matcher: "Bash",
|
|
537
|
+
hooks: [
|
|
538
|
+
{
|
|
539
|
+
type: "command",
|
|
540
|
+
command:
|
|
541
|
+
"read -r INPUT; if echo \"$INPUT\" | grep -q 'git commit'; then loam diff HEAD~1 2>/dev/null || true; fi",
|
|
542
|
+
},
|
|
543
|
+
],
|
|
544
|
+
},
|
|
545
|
+
],
|
|
546
|
+
Stop: [
|
|
547
|
+
{
|
|
548
|
+
matcher: "",
|
|
549
|
+
hooks: [
|
|
550
|
+
{
|
|
551
|
+
type: "command",
|
|
552
|
+
command: "ap log session-end --agent orchestrator",
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
type: "command",
|
|
556
|
+
command: "loam learn",
|
|
557
|
+
},
|
|
558
|
+
],
|
|
559
|
+
},
|
|
560
|
+
],
|
|
561
|
+
PreCompact: [
|
|
562
|
+
{
|
|
563
|
+
matcher: "",
|
|
564
|
+
hooks: [
|
|
565
|
+
{
|
|
566
|
+
type: "command",
|
|
567
|
+
command: "ap prime --agent orchestrator --compact",
|
|
568
|
+
},
|
|
569
|
+
],
|
|
570
|
+
},
|
|
571
|
+
],
|
|
572
|
+
},
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
return `${JSON.stringify(hooks, null, "\t")}\n`;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Migrate existing SQLite databases on --force reinit.
|
|
580
|
+
*
|
|
581
|
+
* Opens each DB, enables WAL mode, and re-runs CREATE TABLE/INDEX IF NOT EXISTS
|
|
582
|
+
* to apply any schema additions without losing existing data.
|
|
583
|
+
*/
|
|
584
|
+
async function migrateExistingDatabases(agentplatePath: string): Promise<string[]> {
|
|
585
|
+
const migrated: string[] = [];
|
|
586
|
+
|
|
587
|
+
// Migrate mail.db
|
|
588
|
+
const mailDbPath = join(agentplatePath, "mail.db");
|
|
589
|
+
if (await Bun.file(mailDbPath).exists()) {
|
|
590
|
+
const db = new Database(mailDbPath);
|
|
591
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
592
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
593
|
+
db.exec(`
|
|
594
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
595
|
+
id TEXT PRIMARY KEY,
|
|
596
|
+
from_agent TEXT NOT NULL,
|
|
597
|
+
to_agent TEXT NOT NULL,
|
|
598
|
+
subject TEXT NOT NULL,
|
|
599
|
+
body TEXT NOT NULL,
|
|
600
|
+
type TEXT NOT NULL DEFAULT 'status',
|
|
601
|
+
priority TEXT NOT NULL DEFAULT 'normal',
|
|
602
|
+
thread_id TEXT,
|
|
603
|
+
read INTEGER NOT NULL DEFAULT 0,
|
|
604
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
605
|
+
)`);
|
|
606
|
+
db.exec(`
|
|
607
|
+
CREATE INDEX IF NOT EXISTS idx_inbox ON messages(to_agent, read);
|
|
608
|
+
CREATE INDEX IF NOT EXISTS idx_thread ON messages(thread_id)`);
|
|
609
|
+
db.close();
|
|
610
|
+
migrated.push("mail.db");
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Migrate metrics.db
|
|
614
|
+
const metricsDbPath = join(agentplatePath, "metrics.db");
|
|
615
|
+
if (await Bun.file(metricsDbPath).exists()) {
|
|
616
|
+
const db = new Database(metricsDbPath);
|
|
617
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
618
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
619
|
+
db.exec(`
|
|
620
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
621
|
+
agent_name TEXT NOT NULL,
|
|
622
|
+
task_id TEXT NOT NULL,
|
|
623
|
+
capability TEXT NOT NULL,
|
|
624
|
+
started_at TEXT NOT NULL,
|
|
625
|
+
completed_at TEXT,
|
|
626
|
+
duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
627
|
+
exit_code INTEGER,
|
|
628
|
+
merge_result TEXT,
|
|
629
|
+
parent_agent TEXT,
|
|
630
|
+
PRIMARY KEY (agent_name, task_id)
|
|
631
|
+
)`);
|
|
632
|
+
db.close();
|
|
633
|
+
migrated.push("metrics.db");
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return migrated;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Content for .agentplate/.gitignore — runtime state that should not be tracked.
|
|
641
|
+
* Uses wildcard+whitelist pattern: ignore everything, whitelist tracked files.
|
|
642
|
+
* Auto-healed by ap prime on each session start.
|
|
643
|
+
* Config files (config.yaml, agent-manifest.json, hooks.json) remain tracked.
|
|
644
|
+
*/
|
|
645
|
+
export const AGENTPLATE_GITIGNORE = `# Wildcard+whitelist: ignore everything, whitelist tracked files
|
|
646
|
+
# Auto-healed by ap prime on each session start
|
|
647
|
+
*
|
|
648
|
+
!.gitignore
|
|
649
|
+
!config.yaml
|
|
650
|
+
!agent-manifest.json
|
|
651
|
+
!hooks.json
|
|
652
|
+
!groups.json
|
|
653
|
+
!agent-defs/
|
|
654
|
+
!agent-defs/**
|
|
655
|
+
!README.md
|
|
656
|
+
`;
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Content for .agentplate/README.md — explains the directory to contributors.
|
|
660
|
+
*/
|
|
661
|
+
export const AGENTPLATE_README = `# .agentplate/
|
|
662
|
+
|
|
663
|
+
This directory is managed by [agentplate](https://github.com/hgoudat/agentplate) — a multi-agent orchestration system for Claude Code.
|
|
664
|
+
|
|
665
|
+
Agentplate turns a single Claude Code session into a multi-agent team by spawning worker agents in git worktrees via tmux, coordinating them through a custom SQLite mail system, and merging their work back with tiered conflict resolution.
|
|
666
|
+
|
|
667
|
+
## Key Commands
|
|
668
|
+
|
|
669
|
+
- \`ap init\` — Initialize this directory
|
|
670
|
+
- \`ap status\` — Show active agents and state
|
|
671
|
+
- \`ap sling <id>\` — Spawn a worker agent
|
|
672
|
+
- \`ap mail check\` — Check agent messages
|
|
673
|
+
- \`ap merge\` — Merge agent work back
|
|
674
|
+
- \`ap dashboard\` — Live TUI monitoring
|
|
675
|
+
- \`ap doctor\` — Run health checks
|
|
676
|
+
|
|
677
|
+
## Structure
|
|
678
|
+
|
|
679
|
+
- \`config.yaml\` — Project configuration
|
|
680
|
+
- \`agent-manifest.json\` — Agent registry
|
|
681
|
+
- \`hooks.json\` — Claude Code hooks config
|
|
682
|
+
- \`agent-defs/\` — Agent definition files (.md)
|
|
683
|
+
- \`specs/\` — Task specifications
|
|
684
|
+
- \`agents/\` — Per-agent state and identity
|
|
685
|
+
- \`worktrees/\` — Git worktrees (gitignored)
|
|
686
|
+
- \`logs/\` — Agent logs (gitignored)
|
|
687
|
+
`;
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Write .agentplate/.gitignore for runtime state files.
|
|
691
|
+
* Always overwrites to support --force reinit and auto-healing via prime.
|
|
692
|
+
*/
|
|
693
|
+
export async function writeAgentplateGitignore(agentplatePath: string): Promise<void> {
|
|
694
|
+
const gitignorePath = join(agentplatePath, ".gitignore");
|
|
695
|
+
await Bun.write(gitignorePath, AGENTPLATE_GITIGNORE);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Write .agentplate/README.md explaining the directory to contributors.
|
|
700
|
+
* Always overwrites to support --force reinit.
|
|
701
|
+
*/
|
|
702
|
+
export async function writeAgentplateReadme(agentplatePath: string): Promise<void> {
|
|
703
|
+
const readmePath = join(agentplatePath, "README.md");
|
|
704
|
+
await Bun.write(readmePath, AGENTPLATE_README);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
export interface InitOptions {
|
|
708
|
+
yes?: boolean;
|
|
709
|
+
name?: string;
|
|
710
|
+
force?: boolean;
|
|
711
|
+
/** Comma-separated list of ecosystem tools to bootstrap (e.g. "loam,sprout"). Default: all. */
|
|
712
|
+
tools?: string;
|
|
713
|
+
skipLoam?: boolean;
|
|
714
|
+
skipSprout?: boolean;
|
|
715
|
+
skipTrellis?: boolean;
|
|
716
|
+
/** Skip the onboard step (injecting CLAUDE.md sections for ecosystem tools). */
|
|
717
|
+
skipOnboard?: boolean;
|
|
718
|
+
/** Output final result as JSON envelope. */
|
|
719
|
+
json?: boolean;
|
|
720
|
+
/** Injectable spawner for testability. */
|
|
721
|
+
_spawner?: Spawner;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Print a success status line.
|
|
726
|
+
*/
|
|
727
|
+
function printCreated(relativePath: string): void {
|
|
728
|
+
printSuccess("Created", relativePath);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Entry point for `ap init [--force] [--yes|-y] [--name <name>]`.
|
|
733
|
+
*
|
|
734
|
+
* Scaffolds the .agentplate/ directory structure in the current working directory.
|
|
735
|
+
*
|
|
736
|
+
* @param opts - Command options
|
|
737
|
+
*/
|
|
738
|
+
export async function initCommand(opts: InitOptions): Promise<void> {
|
|
739
|
+
const force = opts.force ?? false;
|
|
740
|
+
const yes = opts.yes ?? false;
|
|
741
|
+
const projectRoot = process.cwd();
|
|
742
|
+
const spawner = opts._spawner ?? defaultSpawner;
|
|
743
|
+
const agentplatePath = join(projectRoot, AGENTPLATE_DIR);
|
|
744
|
+
|
|
745
|
+
// 0. Verify we're inside a git repository
|
|
746
|
+
const gitCheck = Bun.spawn(["git", "rev-parse", "--is-inside-work-tree"], {
|
|
747
|
+
cwd: projectRoot,
|
|
748
|
+
stdout: "pipe",
|
|
749
|
+
stderr: "pipe",
|
|
750
|
+
});
|
|
751
|
+
const gitCheckExit = await gitCheck.exited;
|
|
752
|
+
if (gitCheckExit !== 0) {
|
|
753
|
+
throw new ValidationError("agentplate requires a git repository. Run 'git init' first.", {
|
|
754
|
+
field: "git",
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// 1. Check if .agentplate/ already exists
|
|
759
|
+
const existingDir = Bun.file(join(agentplatePath, "config.yaml"));
|
|
760
|
+
if (await existingDir.exists()) {
|
|
761
|
+
if (!force && !yes) {
|
|
762
|
+
process.stdout.write(
|
|
763
|
+
"Warning: .agentplate/ already initialized in this project.\n" +
|
|
764
|
+
"Use --force or --yes to reinitialize.\n",
|
|
765
|
+
);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
const flag = yes ? "--yes" : "--force";
|
|
769
|
+
process.stdout.write(`Reinitializing .agentplate/ (${flag})\n\n`);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// 2. Detect project info
|
|
773
|
+
const projectName = opts.name ?? (await detectProjectName(projectRoot));
|
|
774
|
+
const canonicalBranch = await detectCanonicalBranch(projectRoot);
|
|
775
|
+
let defaultRuntime = "claude";
|
|
776
|
+
try {
|
|
777
|
+
defaultRuntime = await detectDefaultRuntime(spawner);
|
|
778
|
+
} catch {
|
|
779
|
+
// Non-fatal: fall back to claude if runtime detection fails
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
process.stdout.write(`Initializing agentplate for "${projectName}"...\n\n`);
|
|
783
|
+
|
|
784
|
+
// 3. Create directory structure
|
|
785
|
+
const dirs = [
|
|
786
|
+
AGENTPLATE_DIR,
|
|
787
|
+
join(AGENTPLATE_DIR, "agents"),
|
|
788
|
+
join(AGENTPLATE_DIR, "agent-defs"),
|
|
789
|
+
join(AGENTPLATE_DIR, "worktrees"),
|
|
790
|
+
join(AGENTPLATE_DIR, "specs"),
|
|
791
|
+
join(AGENTPLATE_DIR, "logs"),
|
|
792
|
+
];
|
|
793
|
+
|
|
794
|
+
for (const dir of dirs) {
|
|
795
|
+
await mkdir(join(projectRoot, dir), { recursive: true });
|
|
796
|
+
printCreated(`${dir}/`);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// 3b. Deploy agent definition .md files from agentplate install directory
|
|
800
|
+
const agentplateAgentsDir = join(import.meta.dir, "..", "..", "agents");
|
|
801
|
+
const agentDefsTarget = join(agentplatePath, "agent-defs");
|
|
802
|
+
const agentDefFiles = await readdir(agentplateAgentsDir);
|
|
803
|
+
for (const fileName of agentDefFiles) {
|
|
804
|
+
if (!fileName.endsWith(".md")) continue;
|
|
805
|
+
if (fileName === "supervisor.md") continue; // Deprecated: not deployed to new projects
|
|
806
|
+
const source = Bun.file(join(agentplateAgentsDir, fileName));
|
|
807
|
+
const content = await source.text();
|
|
808
|
+
await Bun.write(join(agentDefsTarget, fileName), content);
|
|
809
|
+
printCreated(`${AGENTPLATE_DIR}/agent-defs/${fileName}`);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// 4. Write config.yaml
|
|
813
|
+
const config = structuredClone(DEFAULT_CONFIG);
|
|
814
|
+
config.project.name = projectName;
|
|
815
|
+
config.project.root = projectRoot;
|
|
816
|
+
config.project.canonicalBranch = canonicalBranch;
|
|
817
|
+
if (config.runtime) {
|
|
818
|
+
config.runtime.default = defaultRuntime;
|
|
819
|
+
// New projects default to headless Claude spawns; the UI (`ap serve`) is the
|
|
820
|
+
// primary operator surface and tmux is opt-in via `--no-headless`. Existing
|
|
821
|
+
// projects keep tmux until they edit their config (agentplate-caec).
|
|
822
|
+
config.runtime.claudeHeadlessByDefault = true;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const configYaml = serializeConfigToYaml(config);
|
|
826
|
+
const configPath = join(agentplatePath, "config.yaml");
|
|
827
|
+
await Bun.write(configPath, configYaml);
|
|
828
|
+
printCreated(`${AGENTPLATE_DIR}/config.yaml`);
|
|
829
|
+
|
|
830
|
+
// 5. Write agent-manifest.json
|
|
831
|
+
const manifest = buildAgentManifest();
|
|
832
|
+
const manifestPath = join(agentplatePath, "agent-manifest.json");
|
|
833
|
+
await Bun.write(manifestPath, `${JSON.stringify(manifest, null, "\t")}\n`);
|
|
834
|
+
printCreated(`${AGENTPLATE_DIR}/agent-manifest.json`);
|
|
835
|
+
|
|
836
|
+
// 6. Write hooks.json
|
|
837
|
+
const hooksContent = buildHooksJson();
|
|
838
|
+
const hooksPath = join(agentplatePath, "hooks.json");
|
|
839
|
+
await Bun.write(hooksPath, hooksContent);
|
|
840
|
+
printCreated(`${AGENTPLATE_DIR}/hooks.json`);
|
|
841
|
+
|
|
842
|
+
// 7. Write .agentplate/.gitignore for runtime state
|
|
843
|
+
await writeAgentplateGitignore(agentplatePath);
|
|
844
|
+
printCreated(`${AGENTPLATE_DIR}/.gitignore`);
|
|
845
|
+
|
|
846
|
+
// 7b. Write .agentplate/README.md
|
|
847
|
+
await writeAgentplateReadme(agentplatePath);
|
|
848
|
+
printCreated(`${AGENTPLATE_DIR}/README.md`);
|
|
849
|
+
|
|
850
|
+
// 8. Migrate existing SQLite databases on --force reinit
|
|
851
|
+
if (force || yes) {
|
|
852
|
+
const migrated = await migrateExistingDatabases(agentplatePath);
|
|
853
|
+
for (const dbName of migrated) {
|
|
854
|
+
printSuccess("Migrated", dbName);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// 9. Bootstrap sibling ecosystem tools
|
|
859
|
+
const toolSet = resolveToolSet(opts);
|
|
860
|
+
const toolResults: Record<string, { status: ToolStatus; path: string }> = {
|
|
861
|
+
agentplate: { status: "initialized", path: agentplatePath },
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
if (toolSet.length > 0) {
|
|
865
|
+
process.stdout.write("\n");
|
|
866
|
+
process.stdout.write("Bootstrapping ecosystem tools...\n\n");
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
for (const tool of toolSet) {
|
|
870
|
+
const status = await initSiblingTool(tool, projectRoot, spawner);
|
|
871
|
+
toolResults[tool.name] = {
|
|
872
|
+
status,
|
|
873
|
+
path: join(projectRoot, tool.dotDir),
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// 10. Set up .gitattributes with merge=union for JSONL files
|
|
878
|
+
const gitattrsUpdated = await setupGitattributes(projectRoot);
|
|
879
|
+
if (gitattrsUpdated) {
|
|
880
|
+
printCreated(".gitattributes");
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// 11. Run onboard for each tool (inject CLAUDE.md sections)
|
|
884
|
+
const onboardResults: Record<string, OnboardStatus> = {};
|
|
885
|
+
if (!opts.skipOnboard) {
|
|
886
|
+
for (const tool of toolSet) {
|
|
887
|
+
if (toolResults[tool.name]?.status !== "skipped") {
|
|
888
|
+
const status = await onboardTool(tool, projectRoot, spawner);
|
|
889
|
+
onboardResults[tool.name] = status;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// 12. Auto-commit scaffold files so ecosystem dirs are tracked before agents create branches.
|
|
895
|
+
// Without this, agent branches that add files to .loam/.sprout/.trellis cause
|
|
896
|
+
// untracked-vs-tracked conflicts in ap merge (agentplate-fe42).
|
|
897
|
+
let scaffoldCommitted = false;
|
|
898
|
+
const pathsToAdd: string[] = [AGENTPLATE_DIR];
|
|
899
|
+
|
|
900
|
+
// Add .gitattributes if it exists
|
|
901
|
+
try {
|
|
902
|
+
await stat(join(projectRoot, ".gitattributes"));
|
|
903
|
+
pathsToAdd.push(".gitattributes");
|
|
904
|
+
} catch {
|
|
905
|
+
// not present — skip
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// Add CLAUDE.md if it exists (may have been modified by onboard)
|
|
909
|
+
try {
|
|
910
|
+
await stat(join(projectRoot, "CLAUDE.md"));
|
|
911
|
+
pathsToAdd.push("CLAUDE.md");
|
|
912
|
+
} catch {
|
|
913
|
+
// not present — skip
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// Add sibling tool dirs that were created
|
|
917
|
+
for (const tool of SIBLING_TOOLS) {
|
|
918
|
+
try {
|
|
919
|
+
await stat(join(projectRoot, tool.dotDir));
|
|
920
|
+
pathsToAdd.push(tool.dotDir);
|
|
921
|
+
} catch {
|
|
922
|
+
// not present — skip
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
const addResult = await spawner(["git", "add", ...pathsToAdd], { cwd: projectRoot });
|
|
927
|
+
if (addResult.exitCode !== 0) {
|
|
928
|
+
printWarning("Scaffold commit skipped", addResult.stderr.trim() || "git add failed");
|
|
929
|
+
} else {
|
|
930
|
+
// git diff --cached --quiet exits 0 if nothing staged, 1 if changes are staged
|
|
931
|
+
const diffResult = await spawner(["git", "diff", "--cached", "--quiet"], {
|
|
932
|
+
cwd: projectRoot,
|
|
933
|
+
});
|
|
934
|
+
if (diffResult.exitCode !== 0) {
|
|
935
|
+
// Changes are staged — commit them
|
|
936
|
+
const commitResult = await spawner(
|
|
937
|
+
["git", "commit", "-m", "chore: initialize agentplate and ecosystem tools"],
|
|
938
|
+
{ cwd: projectRoot },
|
|
939
|
+
);
|
|
940
|
+
if (commitResult.exitCode === 0) {
|
|
941
|
+
printSuccess("Committed", "scaffold files");
|
|
942
|
+
scaffoldCommitted = true;
|
|
943
|
+
} else {
|
|
944
|
+
printWarning("Scaffold commit failed", commitResult.stderr.trim() || "git commit failed");
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// 13. Output final result
|
|
950
|
+
if (opts.json) {
|
|
951
|
+
jsonOutput("init", {
|
|
952
|
+
project: projectName,
|
|
953
|
+
tools: toolResults,
|
|
954
|
+
onboard: onboardResults,
|
|
955
|
+
gitattributes: gitattrsUpdated,
|
|
956
|
+
scaffoldCommitted,
|
|
957
|
+
});
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
printSuccess("Initialized");
|
|
962
|
+
printHint("Next: run `ap hooks install` to enable Claude Code hooks.");
|
|
963
|
+
printHint("Then: `ap coordinator start` and `ap serve` — open http://localhost:7321");
|
|
964
|
+
printHint(
|
|
965
|
+
" (UI is the primary operator surface; pass `--no-headless` to ap sling for tmux attach)",
|
|
966
|
+
);
|
|
967
|
+
}
|