@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,707 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdtemp } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
6
|
+
import type { ResolvedModel } from "../types.ts";
|
|
7
|
+
import { CopilotRuntime, ensureCopilotTrustedFolders } from "./copilot.ts";
|
|
8
|
+
import type { SpawnOpts } from "./types.ts";
|
|
9
|
+
|
|
10
|
+
describe("CopilotRuntime", () => {
|
|
11
|
+
const runtime = new CopilotRuntime();
|
|
12
|
+
|
|
13
|
+
describe("id and instructionPath", () => {
|
|
14
|
+
test("id is 'copilot'", () => {
|
|
15
|
+
expect(runtime.id).toBe("copilot");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("instructionPath is .github/copilot-instructions.md", () => {
|
|
19
|
+
expect(runtime.instructionPath).toBe(".github/copilot-instructions.md");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("buildSpawnCommand", () => {
|
|
24
|
+
test("bypass permission mode includes --allow-all-tools", () => {
|
|
25
|
+
const opts: SpawnOpts = {
|
|
26
|
+
model: "sonnet",
|
|
27
|
+
permissionMode: "bypass",
|
|
28
|
+
cwd: "/tmp/worktree",
|
|
29
|
+
env: {},
|
|
30
|
+
};
|
|
31
|
+
const cmd = runtime.buildSpawnCommand(opts);
|
|
32
|
+
expect(cmd).toBe("copilot --model claude-sonnet-4-6 --allow-all-tools");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("ask permission mode omits permission flag", () => {
|
|
36
|
+
const opts: SpawnOpts = {
|
|
37
|
+
model: "opus",
|
|
38
|
+
permissionMode: "ask",
|
|
39
|
+
cwd: "/tmp/worktree",
|
|
40
|
+
env: {},
|
|
41
|
+
};
|
|
42
|
+
const cmd = runtime.buildSpawnCommand(opts);
|
|
43
|
+
expect(cmd).toBe("copilot --model claude-opus-4-6");
|
|
44
|
+
expect(cmd).not.toContain("--allow-all-tools");
|
|
45
|
+
expect(cmd).not.toContain("--permission-mode");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("appendSystemPrompt is ignored (copilot has no such flag)", () => {
|
|
49
|
+
const opts: SpawnOpts = {
|
|
50
|
+
model: "sonnet",
|
|
51
|
+
permissionMode: "bypass",
|
|
52
|
+
cwd: "/tmp/worktree",
|
|
53
|
+
env: {},
|
|
54
|
+
appendSystemPrompt: "You are a builder agent.",
|
|
55
|
+
};
|
|
56
|
+
const cmd = runtime.buildSpawnCommand(opts);
|
|
57
|
+
expect(cmd).toBe("copilot --model claude-sonnet-4-6 --allow-all-tools");
|
|
58
|
+
expect(cmd).not.toContain("append-system-prompt");
|
|
59
|
+
expect(cmd).not.toContain("You are a builder agent");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("appendSystemPromptFile is ignored (copilot has no such flag)", () => {
|
|
63
|
+
const opts: SpawnOpts = {
|
|
64
|
+
model: "opus",
|
|
65
|
+
permissionMode: "bypass",
|
|
66
|
+
cwd: "/project",
|
|
67
|
+
env: {},
|
|
68
|
+
appendSystemPromptFile: "/project/.agentplate/agent-defs/coordinator.md",
|
|
69
|
+
};
|
|
70
|
+
const cmd = runtime.buildSpawnCommand(opts);
|
|
71
|
+
expect(cmd).toBe("copilot --model claude-opus-4-6 --allow-all-tools");
|
|
72
|
+
expect(cmd).not.toContain("cat");
|
|
73
|
+
expect(cmd).not.toContain("coordinator.md");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("cwd and env are not embedded in command string", () => {
|
|
77
|
+
const opts: SpawnOpts = {
|
|
78
|
+
model: "sonnet",
|
|
79
|
+
permissionMode: "bypass",
|
|
80
|
+
cwd: "/some/specific/path",
|
|
81
|
+
env: { GITHUB_TOKEN: "gh-test-123" },
|
|
82
|
+
};
|
|
83
|
+
const cmd = runtime.buildSpawnCommand(opts);
|
|
84
|
+
expect(cmd).not.toContain("/some/specific/path");
|
|
85
|
+
expect(cmd).not.toContain("gh-test-123");
|
|
86
|
+
expect(cmd).not.toContain("GITHUB_TOKEN");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("known aliases expand to fully-qualified names, unknown models pass through", () => {
|
|
90
|
+
const cases: Array<[string, string]> = [
|
|
91
|
+
["sonnet", "claude-sonnet-4-6"],
|
|
92
|
+
["opus", "claude-opus-4-6"],
|
|
93
|
+
["haiku", "claude-haiku-4-5"],
|
|
94
|
+
["gpt-4o", "gpt-4o"],
|
|
95
|
+
["openrouter/gpt-5", "openrouter/gpt-5"],
|
|
96
|
+
];
|
|
97
|
+
for (const [alias, expected] of cases) {
|
|
98
|
+
const opts: SpawnOpts = {
|
|
99
|
+
model: alias,
|
|
100
|
+
permissionMode: "bypass",
|
|
101
|
+
cwd: "/tmp",
|
|
102
|
+
env: {},
|
|
103
|
+
};
|
|
104
|
+
const cmd = runtime.buildSpawnCommand(opts);
|
|
105
|
+
expect(cmd).toContain(`--model ${expected}`);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("produces identical output for same inputs (deterministic)", () => {
|
|
110
|
+
const opts: SpawnOpts = {
|
|
111
|
+
model: "sonnet",
|
|
112
|
+
permissionMode: "bypass",
|
|
113
|
+
cwd: "/tmp/worktree",
|
|
114
|
+
env: {},
|
|
115
|
+
};
|
|
116
|
+
const cmd1 = runtime.buildSpawnCommand(opts);
|
|
117
|
+
const cmd2 = runtime.buildSpawnCommand(opts);
|
|
118
|
+
expect(cmd1).toBe(cmd2);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe("expandModel", () => {
|
|
123
|
+
test("expands 'sonnet' to claude-sonnet-4-6", () => {
|
|
124
|
+
expect(runtime.expandModel("sonnet")).toBe("claude-sonnet-4-6");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("expands 'opus' to claude-opus-4-6", () => {
|
|
128
|
+
expect(runtime.expandModel("opus")).toBe("claude-opus-4-6");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("expands 'haiku' to claude-haiku-4-5", () => {
|
|
132
|
+
expect(runtime.expandModel("haiku")).toBe("claude-haiku-4-5");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("passes through fully-qualified names unchanged", () => {
|
|
136
|
+
expect(runtime.expandModel("claude-sonnet-4-6")).toBe("claude-sonnet-4-6");
|
|
137
|
+
expect(runtime.expandModel("gpt-4o")).toBe("gpt-4o");
|
|
138
|
+
expect(runtime.expandModel("openrouter/gpt-5")).toBe("openrouter/gpt-5");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("passes through unknown aliases unchanged", () => {
|
|
142
|
+
expect(runtime.expandModel("my-custom-model")).toBe("my-custom-model");
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("buildPrintCommand", () => {
|
|
147
|
+
test("basic command without model includes --allow-all-tools", () => {
|
|
148
|
+
const argv = runtime.buildPrintCommand("Summarize this diff");
|
|
149
|
+
expect(argv).toEqual(["copilot", "-p", "Summarize this diff", "--allow-all-tools"]);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("command with model override appends --model flag (alias expanded)", () => {
|
|
153
|
+
const argv = runtime.buildPrintCommand("Classify this error", "haiku");
|
|
154
|
+
expect(argv).toEqual([
|
|
155
|
+
"copilot",
|
|
156
|
+
"-p",
|
|
157
|
+
"Classify this error",
|
|
158
|
+
"--allow-all-tools",
|
|
159
|
+
"--model",
|
|
160
|
+
"claude-haiku-4-5",
|
|
161
|
+
]);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("command with fully-qualified model passes through unchanged", () => {
|
|
165
|
+
const argv = runtime.buildPrintCommand("Summarize", "gpt-4o");
|
|
166
|
+
expect(argv).toEqual([
|
|
167
|
+
"copilot",
|
|
168
|
+
"-p",
|
|
169
|
+
"Summarize",
|
|
170
|
+
"--allow-all-tools",
|
|
171
|
+
"--model",
|
|
172
|
+
"gpt-4o",
|
|
173
|
+
]);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("model undefined omits --model flag", () => {
|
|
177
|
+
const argv = runtime.buildPrintCommand("Hello", undefined);
|
|
178
|
+
expect(argv).not.toContain("--model");
|
|
179
|
+
expect(argv).toContain("--allow-all-tools");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("--allow-all-tools always present regardless of model", () => {
|
|
183
|
+
const withModel = runtime.buildPrintCommand("prompt", "opus");
|
|
184
|
+
const withoutModel = runtime.buildPrintCommand("prompt");
|
|
185
|
+
expect(withModel).toContain("--allow-all-tools");
|
|
186
|
+
expect(withoutModel).toContain("--allow-all-tools");
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe("detectReady", () => {
|
|
191
|
+
test("returns loading for empty pane", () => {
|
|
192
|
+
const state = runtime.detectReady("");
|
|
193
|
+
expect(state).toEqual({ phase: "loading" });
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("returns loading for partial content (prompt only, no status bar)", () => {
|
|
197
|
+
const state = runtime.detectReady("Welcome to Copilot!\n\u276f");
|
|
198
|
+
expect(state).toEqual({ phase: "loading" });
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("returns loading for partial content (status bar only, no prompt)", () => {
|
|
202
|
+
const state = runtime.detectReady("shift+tab to toggle");
|
|
203
|
+
expect(state).toEqual({ phase: "loading" });
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("returns ready for ❯ + shift+tab", () => {
|
|
207
|
+
const state = runtime.detectReady("GitHub Copilot\n\u276f\nshift+tab to chat");
|
|
208
|
+
expect(state).toEqual({ phase: "ready" });
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("returns ready for ❯ + esc", () => {
|
|
212
|
+
const state = runtime.detectReady("GitHub Copilot\n\u276f\nesc to cancel");
|
|
213
|
+
expect(state).toEqual({ phase: "ready" });
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("returns ready for 'copilot' keyword + shift+tab (case-insensitive)", () => {
|
|
217
|
+
const state = runtime.detectReady("Copilot Agent Ready\nshift+tab");
|
|
218
|
+
expect(state).toEqual({ phase: "ready" });
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("returns ready for 'copilot' keyword + esc (case-insensitive)", () => {
|
|
222
|
+
const state = runtime.detectReady("GitHub Copilot v1.0\npress esc to exit");
|
|
223
|
+
expect(state).toEqual({ phase: "ready" });
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("case-insensitive match for 'COPILOT'", () => {
|
|
227
|
+
const state = runtime.detectReady("GITHUB COPILOT\nESC");
|
|
228
|
+
expect(state).toEqual({ phase: "ready" });
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("returns loading for random pane content", () => {
|
|
232
|
+
const state = runtime.detectReady("Loading...\nPlease wait");
|
|
233
|
+
expect(state).toEqual({ phase: "loading" });
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("no trust dialog phase — trust text is ignored", () => {
|
|
237
|
+
// Copilot has no trust dialog; this should just test loading/ready states
|
|
238
|
+
const state = runtime.detectReady("trust this folder");
|
|
239
|
+
// Without prompt+statusbar indicators, remains loading
|
|
240
|
+
expect(state).toEqual({ phase: "loading" });
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("Shift+Tab (capital) is matched case-insensitively", () => {
|
|
244
|
+
const state = runtime.detectReady("\u276f\nShift+Tab to toggle");
|
|
245
|
+
expect(state).toEqual({ phase: "ready" });
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe("buildEnv", () => {
|
|
250
|
+
test("returns empty object when model has no env", () => {
|
|
251
|
+
const model: ResolvedModel = { model: "sonnet" };
|
|
252
|
+
const env = runtime.buildEnv(model);
|
|
253
|
+
expect(env).toEqual({});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("returns model.env when present", () => {
|
|
257
|
+
const model: ResolvedModel = {
|
|
258
|
+
model: "gpt-4o",
|
|
259
|
+
env: { GITHUB_TOKEN: "gh-test-123", COPILOT_API_URL: "https://api.github.com" },
|
|
260
|
+
};
|
|
261
|
+
const env = runtime.buildEnv(model);
|
|
262
|
+
expect(env).toEqual({
|
|
263
|
+
GITHUB_TOKEN: "gh-test-123",
|
|
264
|
+
COPILOT_API_URL: "https://api.github.com",
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("returns empty object when model.env is undefined", () => {
|
|
269
|
+
const model: ResolvedModel = { model: "opus", env: undefined };
|
|
270
|
+
const env = runtime.buildEnv(model);
|
|
271
|
+
expect(env).toEqual({});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("env is safe to spread into session env", () => {
|
|
275
|
+
const model: ResolvedModel = { model: "sonnet" };
|
|
276
|
+
const env = runtime.buildEnv(model);
|
|
277
|
+
const combined = { ...env, AGENTPLATE_AGENT_NAME: "builder-1" };
|
|
278
|
+
expect(combined).toEqual({ AGENTPLATE_AGENT_NAME: "builder-1" });
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
describe("deployConfig", () => {
|
|
283
|
+
let tempDir: string;
|
|
284
|
+
|
|
285
|
+
beforeEach(async () => {
|
|
286
|
+
tempDir = await mkdtemp(join(tmpdir(), "agentplate-copilot-test-"));
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
afterEach(async () => {
|
|
290
|
+
await cleanupTempDir(tempDir);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("writes overlay to .github/copilot-instructions.md when provided", async () => {
|
|
294
|
+
const worktreePath = join(tempDir, "worktree");
|
|
295
|
+
|
|
296
|
+
await runtime.deployConfig(
|
|
297
|
+
worktreePath,
|
|
298
|
+
{ content: "# Copilot Instructions\nYou are a builder." },
|
|
299
|
+
{
|
|
300
|
+
agentName: "test-builder",
|
|
301
|
+
capability: "builder",
|
|
302
|
+
worktreePath,
|
|
303
|
+
},
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
const overlayPath = join(worktreePath, ".github", "copilot-instructions.md");
|
|
307
|
+
const content = await Bun.file(overlayPath).text();
|
|
308
|
+
expect(content).toBe("# Copilot Instructions\nYou are a builder.");
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test("creates .github directory if it does not exist", async () => {
|
|
312
|
+
const worktreePath = join(tempDir, "new-worktree");
|
|
313
|
+
|
|
314
|
+
await runtime.deployConfig(
|
|
315
|
+
worktreePath,
|
|
316
|
+
{ content: "# Instructions" },
|
|
317
|
+
{ agentName: "test", capability: "builder", worktreePath },
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
const githubDirExists = await Bun.file(
|
|
321
|
+
join(worktreePath, ".github", "copilot-instructions.md"),
|
|
322
|
+
).exists();
|
|
323
|
+
expect(githubDirExists).toBe(true);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test("skips overlay write when overlay is undefined", async () => {
|
|
327
|
+
const worktreePath = join(tempDir, "worktree");
|
|
328
|
+
|
|
329
|
+
await runtime.deployConfig(worktreePath, undefined, {
|
|
330
|
+
agentName: "coordinator",
|
|
331
|
+
capability: "coordinator",
|
|
332
|
+
worktreePath,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// No overlay written — copilot-instructions.md should not exist.
|
|
336
|
+
const overlayPath = join(worktreePath, ".github", "copilot-instructions.md");
|
|
337
|
+
const overlayExists = await Bun.file(overlayPath).exists();
|
|
338
|
+
expect(overlayExists).toBe(false);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("does not write settings.local.json (Copilot uses its own hooks format)", async () => {
|
|
342
|
+
const worktreePath = join(tempDir, "worktree");
|
|
343
|
+
|
|
344
|
+
await runtime.deployConfig(
|
|
345
|
+
worktreePath,
|
|
346
|
+
{ content: "# Instructions" },
|
|
347
|
+
{ agentName: "test-builder", capability: "builder", worktreePath },
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
// Copilot does not deploy Claude Code hooks.
|
|
351
|
+
const settingsPath = join(worktreePath, ".claude", "settings.local.json");
|
|
352
|
+
const settingsExists = await Bun.file(settingsPath).exists();
|
|
353
|
+
expect(settingsExists).toBe(false);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
test("writes .github/hooks/hooks.json with Copilot schema when deployConfig is called", async () => {
|
|
357
|
+
const worktreePath = join(tempDir, "worktree-hooks");
|
|
358
|
+
|
|
359
|
+
await runtime.deployConfig(
|
|
360
|
+
worktreePath,
|
|
361
|
+
{ content: "# Instructions" },
|
|
362
|
+
{ agentName: "test-builder", capability: "builder", worktreePath },
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
const hooksPath = join(worktreePath, ".github", "hooks", "hooks.json");
|
|
366
|
+
const hooksExists = await Bun.file(hooksPath).exists();
|
|
367
|
+
expect(hooksExists).toBe(true);
|
|
368
|
+
|
|
369
|
+
const hooksContent = JSON.parse(await Bun.file(hooksPath).text()) as Record<string, unknown>;
|
|
370
|
+
// Copilot schema: top-level "hooks" key with onSessionStart array
|
|
371
|
+
expect(hooksContent).toHaveProperty("hooks");
|
|
372
|
+
const hooks = hooksContent.hooks as Record<string, unknown>;
|
|
373
|
+
expect(hooks).toHaveProperty("onSessionStart");
|
|
374
|
+
expect(Array.isArray(hooks.onSessionStart)).toBe(true);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test("hooks.json contains agentName substituted in commands", async () => {
|
|
378
|
+
const worktreePath = join(tempDir, "worktree-agentname");
|
|
379
|
+
|
|
380
|
+
await runtime.deployConfig(worktreePath, undefined, {
|
|
381
|
+
agentName: "my-test-agent",
|
|
382
|
+
capability: "builder",
|
|
383
|
+
worktreePath,
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
const hooksPath = join(worktreePath, ".github", "hooks", "hooks.json");
|
|
387
|
+
const raw = await Bun.file(hooksPath).text();
|
|
388
|
+
expect(raw).toContain("my-test-agent");
|
|
389
|
+
expect(raw).not.toContain("{{AGENT_NAME}}");
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
describe("parseTranscript", () => {
|
|
394
|
+
let tempDir: string;
|
|
395
|
+
|
|
396
|
+
beforeEach(async () => {
|
|
397
|
+
tempDir = await mkdtemp(join(tmpdir(), "agentplate-copilot-transcript-test-"));
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
afterEach(async () => {
|
|
401
|
+
await cleanupTempDir(tempDir);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("returns null for non-existent file", async () => {
|
|
405
|
+
const result = await runtime.parseTranscript(join(tempDir, "does-not-exist.jsonl"));
|
|
406
|
+
expect(result).toBeNull();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test("parses Claude-style transcript (type:assistant, message.usage)", async () => {
|
|
410
|
+
const transcriptPath = join(tempDir, "session.jsonl");
|
|
411
|
+
const entry = JSON.stringify({
|
|
412
|
+
type: "assistant",
|
|
413
|
+
message: {
|
|
414
|
+
model: "claude-sonnet-4-6",
|
|
415
|
+
usage: {
|
|
416
|
+
input_tokens: 100,
|
|
417
|
+
output_tokens: 50,
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
});
|
|
421
|
+
await Bun.write(transcriptPath, `${entry}\n`);
|
|
422
|
+
|
|
423
|
+
const result = await runtime.parseTranscript(transcriptPath);
|
|
424
|
+
expect(result).not.toBeNull();
|
|
425
|
+
expect(result?.inputTokens).toBe(100);
|
|
426
|
+
expect(result?.outputTokens).toBe(50);
|
|
427
|
+
expect(result?.model).toBe("claude-sonnet-4-6");
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test("parses Pi-style transcript (type:message_end, top-level tokens)", async () => {
|
|
431
|
+
const transcriptPath = join(tempDir, "session.jsonl");
|
|
432
|
+
const modelEntry = JSON.stringify({ type: "model_change", model: "gpt-4o" });
|
|
433
|
+
const tokenEntry = JSON.stringify({
|
|
434
|
+
type: "message_end",
|
|
435
|
+
inputTokens: 200,
|
|
436
|
+
outputTokens: 75,
|
|
437
|
+
});
|
|
438
|
+
await Bun.write(transcriptPath, `${modelEntry}\n${tokenEntry}\n`);
|
|
439
|
+
|
|
440
|
+
const result = await runtime.parseTranscript(transcriptPath);
|
|
441
|
+
expect(result).not.toBeNull();
|
|
442
|
+
expect(result?.inputTokens).toBe(200);
|
|
443
|
+
expect(result?.outputTokens).toBe(75);
|
|
444
|
+
expect(result?.model).toBe("gpt-4o");
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test("aggregates multiple Claude-style turns", async () => {
|
|
448
|
+
const transcriptPath = join(tempDir, "session.jsonl");
|
|
449
|
+
const entry1 = JSON.stringify({
|
|
450
|
+
type: "assistant",
|
|
451
|
+
message: {
|
|
452
|
+
model: "claude-sonnet-4-6",
|
|
453
|
+
usage: { input_tokens: 100, output_tokens: 50 },
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
const entry2 = JSON.stringify({
|
|
457
|
+
type: "assistant",
|
|
458
|
+
message: {
|
|
459
|
+
model: "claude-sonnet-4-6",
|
|
460
|
+
usage: { input_tokens: 200, output_tokens: 75 },
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
await Bun.write(transcriptPath, `${entry1}\n${entry2}\n`);
|
|
464
|
+
|
|
465
|
+
const result = await runtime.parseTranscript(transcriptPath);
|
|
466
|
+
expect(result?.inputTokens).toBe(300);
|
|
467
|
+
expect(result?.outputTokens).toBe(125);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
test("aggregates multiple Pi-style turns", async () => {
|
|
471
|
+
const transcriptPath = join(tempDir, "session.jsonl");
|
|
472
|
+
const entry1 = JSON.stringify({ type: "message_end", inputTokens: 100, outputTokens: 40 });
|
|
473
|
+
const entry2 = JSON.stringify({ type: "message_end", inputTokens: 150, outputTokens: 60 });
|
|
474
|
+
await Bun.write(transcriptPath, `${entry1}\n${entry2}\n`);
|
|
475
|
+
|
|
476
|
+
const result = await runtime.parseTranscript(transcriptPath);
|
|
477
|
+
expect(result?.inputTokens).toBe(250);
|
|
478
|
+
expect(result?.outputTokens).toBe(100);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
test("top-level model field is picked up from any entry", async () => {
|
|
482
|
+
const transcriptPath = join(tempDir, "session.jsonl");
|
|
483
|
+
const modelEntry = JSON.stringify({ model: "copilot-4" });
|
|
484
|
+
const tokenEntry = JSON.stringify({
|
|
485
|
+
type: "assistant",
|
|
486
|
+
message: { usage: { input_tokens: 10, output_tokens: 5 } },
|
|
487
|
+
});
|
|
488
|
+
await Bun.write(transcriptPath, `${modelEntry}\n${tokenEntry}\n`);
|
|
489
|
+
|
|
490
|
+
const result = await runtime.parseTranscript(transcriptPath);
|
|
491
|
+
expect(result?.model).toBe("copilot-4");
|
|
492
|
+
expect(result?.inputTokens).toBe(10);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
test("message.model takes precedence over top-level model when both present", async () => {
|
|
496
|
+
const transcriptPath = join(tempDir, "session.jsonl");
|
|
497
|
+
const entry = JSON.stringify({
|
|
498
|
+
type: "assistant",
|
|
499
|
+
model: "top-level-model",
|
|
500
|
+
message: {
|
|
501
|
+
model: "message-model",
|
|
502
|
+
usage: { input_tokens: 10, output_tokens: 5 },
|
|
503
|
+
},
|
|
504
|
+
});
|
|
505
|
+
await Bun.write(transcriptPath, `${entry}\n`);
|
|
506
|
+
|
|
507
|
+
const result = await runtime.parseTranscript(transcriptPath);
|
|
508
|
+
// message.model is processed after top-level model in same entry,
|
|
509
|
+
// so message.model wins for assistant entries.
|
|
510
|
+
expect(result?.model).toBe("message-model");
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
test("mixed Claude-style and Pi-style in same transcript", async () => {
|
|
514
|
+
const transcriptPath = join(tempDir, "session.jsonl");
|
|
515
|
+
const claudeEntry = JSON.stringify({
|
|
516
|
+
type: "assistant",
|
|
517
|
+
message: {
|
|
518
|
+
model: "claude-sonnet-4-6",
|
|
519
|
+
usage: { input_tokens: 100, output_tokens: 40 },
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
const piEntry = JSON.stringify({
|
|
523
|
+
type: "message_end",
|
|
524
|
+
inputTokens: 50,
|
|
525
|
+
outputTokens: 20,
|
|
526
|
+
});
|
|
527
|
+
await Bun.write(transcriptPath, `${claudeEntry}\n${piEntry}\n`);
|
|
528
|
+
|
|
529
|
+
const result = await runtime.parseTranscript(transcriptPath);
|
|
530
|
+
expect(result?.inputTokens).toBe(150);
|
|
531
|
+
expect(result?.outputTokens).toBe(60);
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
test("skips non-relevant entry types", async () => {
|
|
535
|
+
const transcriptPath = join(tempDir, "session.jsonl");
|
|
536
|
+
const userEntry = JSON.stringify({ type: "user", message: { content: "hello" } });
|
|
537
|
+
const assistantEntry = JSON.stringify({
|
|
538
|
+
type: "assistant",
|
|
539
|
+
message: {
|
|
540
|
+
model: "claude-sonnet-4-6",
|
|
541
|
+
usage: { input_tokens: 50, output_tokens: 25 },
|
|
542
|
+
},
|
|
543
|
+
});
|
|
544
|
+
await Bun.write(transcriptPath, `${userEntry}\n${assistantEntry}\n`);
|
|
545
|
+
|
|
546
|
+
const result = await runtime.parseTranscript(transcriptPath);
|
|
547
|
+
expect(result?.inputTokens).toBe(50);
|
|
548
|
+
expect(result?.outputTokens).toBe(25);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
test("skips malformed lines and continues parsing", async () => {
|
|
552
|
+
const transcriptPath = join(tempDir, "session.jsonl");
|
|
553
|
+
const goodEntry = JSON.stringify({
|
|
554
|
+
type: "assistant",
|
|
555
|
+
message: { model: "gpt-4o", usage: { input_tokens: 30, output_tokens: 15 } },
|
|
556
|
+
});
|
|
557
|
+
await Bun.write(transcriptPath, `not json at all\n${goodEntry}\n{broken`);
|
|
558
|
+
|
|
559
|
+
const result = await runtime.parseTranscript(transcriptPath);
|
|
560
|
+
expect(result).not.toBeNull();
|
|
561
|
+
expect(result?.inputTokens).toBe(30);
|
|
562
|
+
expect(result?.outputTokens).toBe(15);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
test("returns zero tokens for empty transcript", async () => {
|
|
566
|
+
const transcriptPath = join(tempDir, "empty.jsonl");
|
|
567
|
+
await Bun.write(transcriptPath, "");
|
|
568
|
+
|
|
569
|
+
const result = await runtime.parseTranscript(transcriptPath);
|
|
570
|
+
expect(result).not.toBeNull();
|
|
571
|
+
expect(result?.inputTokens).toBe(0);
|
|
572
|
+
expect(result?.outputTokens).toBe(0);
|
|
573
|
+
expect(result?.model).toBe("");
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
describe("ensureCopilotTrustedFolders", () => {
|
|
579
|
+
let tempDir: string;
|
|
580
|
+
|
|
581
|
+
beforeEach(async () => {
|
|
582
|
+
tempDir = await mkdtemp(join(tmpdir(), "agentplate-copilot-trust-test-"));
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
afterEach(async () => {
|
|
586
|
+
await cleanupTempDir(tempDir);
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
test("creates config.json with trustedFolders when file does not exist", async () => {
|
|
590
|
+
const configDir = join(tempDir, "github-copilot");
|
|
591
|
+
await ensureCopilotTrustedFolders("/some/worktree", configDir);
|
|
592
|
+
|
|
593
|
+
const configPath = join(configDir, "config.json");
|
|
594
|
+
const content = JSON.parse(await Bun.file(configPath).text()) as Record<string, unknown>;
|
|
595
|
+
expect(Array.isArray(content.trustedFolders)).toBe(true);
|
|
596
|
+
expect(content.trustedFolders).toContain("/some/worktree");
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
test("creates config directory if it does not exist", async () => {
|
|
600
|
+
const configDir = join(tempDir, "nested", "github-copilot");
|
|
601
|
+
await ensureCopilotTrustedFolders("/my/worktree", configDir);
|
|
602
|
+
|
|
603
|
+
const configPath = join(configDir, "config.json");
|
|
604
|
+
const exists = await Bun.file(configPath).exists();
|
|
605
|
+
expect(exists).toBe(true);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
test("appends to existing trustedFolders without duplicates", async () => {
|
|
609
|
+
const configDir = join(tempDir, "github-copilot");
|
|
610
|
+
await ensureCopilotTrustedFolders("/first/path", configDir);
|
|
611
|
+
await ensureCopilotTrustedFolders("/second/path", configDir);
|
|
612
|
+
|
|
613
|
+
const configPath = join(configDir, "config.json");
|
|
614
|
+
const content = JSON.parse(await Bun.file(configPath).text()) as Record<string, unknown>;
|
|
615
|
+
expect(Array.isArray(content.trustedFolders)).toBe(true);
|
|
616
|
+
const trusted = content.trustedFolders as string[];
|
|
617
|
+
expect(trusted).toContain("/first/path");
|
|
618
|
+
expect(trusted).toContain("/second/path");
|
|
619
|
+
expect(trusted.length).toBe(2);
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
test("does not duplicate existing entries", async () => {
|
|
623
|
+
const configDir = join(tempDir, "github-copilot");
|
|
624
|
+
await ensureCopilotTrustedFolders("/some/worktree", configDir);
|
|
625
|
+
await ensureCopilotTrustedFolders("/some/worktree", configDir);
|
|
626
|
+
|
|
627
|
+
const configPath = join(configDir, "config.json");
|
|
628
|
+
const content = JSON.parse(await Bun.file(configPath).text()) as Record<string, unknown>;
|
|
629
|
+
const trusted = content.trustedFolders as string[];
|
|
630
|
+
expect(trusted.filter((p) => p === "/some/worktree").length).toBe(1);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
test("preserves other config keys when updating trustedFolders", async () => {
|
|
634
|
+
const configDir = join(tempDir, "github-copilot");
|
|
635
|
+
const configPath = join(configDir, "config.json");
|
|
636
|
+
await Bun.write(
|
|
637
|
+
configPath,
|
|
638
|
+
`${JSON.stringify({ someOtherKey: "value", trustedFolders: [] }, null, "\t")}\n`,
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
await ensureCopilotTrustedFolders("/new/worktree", configDir);
|
|
642
|
+
|
|
643
|
+
const content = JSON.parse(await Bun.file(configPath).text()) as Record<string, unknown>;
|
|
644
|
+
expect(content.someOtherKey).toBe("value");
|
|
645
|
+
expect(content.trustedFolders as string[]).toContain("/new/worktree");
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
test("handles invalid JSON in existing config by starting fresh", async () => {
|
|
649
|
+
const configDir = join(tempDir, "github-copilot");
|
|
650
|
+
await Bun.write(join(configDir, "config.json"), "not valid json");
|
|
651
|
+
await ensureCopilotTrustedFolders("/new/worktree", configDir);
|
|
652
|
+
|
|
653
|
+
const configPath = join(configDir, "config.json");
|
|
654
|
+
const content = JSON.parse(await Bun.file(configPath).text()) as Record<string, unknown>;
|
|
655
|
+
expect(content.trustedFolders as string[]).toContain("/new/worktree");
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
test("treats non-array trustedFolders as empty and replaces it", async () => {
|
|
659
|
+
const configDir = join(tempDir, "github-copilot");
|
|
660
|
+
const configPath = join(configDir, "config.json");
|
|
661
|
+
await Bun.write(
|
|
662
|
+
configPath,
|
|
663
|
+
`${JSON.stringify({ trustedFolders: "not-an-array" }, null, "\t")}\n`,
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
await ensureCopilotTrustedFolders("/my/worktree", configDir);
|
|
667
|
+
|
|
668
|
+
const content = JSON.parse(await Bun.file(configPath).text()) as Record<string, unknown>;
|
|
669
|
+
expect(Array.isArray(content.trustedFolders)).toBe(true);
|
|
670
|
+
expect(content.trustedFolders as string[]).toContain("/my/worktree");
|
|
671
|
+
});
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
describe("CopilotRuntime.prepareWorktree", () => {
|
|
675
|
+
let tempDir: string;
|
|
676
|
+
|
|
677
|
+
beforeEach(async () => {
|
|
678
|
+
tempDir = await mkdtemp(join(tmpdir(), "agentplate-copilot-prepworktree-test-"));
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
afterEach(async () => {
|
|
682
|
+
await cleanupTempDir(tempDir);
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
test("prepareWorktree is defined on CopilotRuntime", () => {
|
|
686
|
+
const runtime = new CopilotRuntime();
|
|
687
|
+
expect(typeof runtime.prepareWorktree).toBe("function");
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
test("calling prepareWorktree with a worktreePath does not throw", async () => {
|
|
691
|
+
// We can't inject configDir through the interface method, but we can verify
|
|
692
|
+
// it doesn't throw. The actual file write goes to real homedir, which is
|
|
693
|
+
// tested via ensureCopilotTrustedFolders directly.
|
|
694
|
+
const runtime = new CopilotRuntime();
|
|
695
|
+
await expect(runtime.prepareWorktree("/test/worktree")).resolves.toBeUndefined();
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
describe("CopilotRuntime integration: registry resolves 'copilot'", () => {
|
|
700
|
+
test("getRuntime('copilot') returns CopilotRuntime", async () => {
|
|
701
|
+
const { getRuntime } = await import("./registry.ts");
|
|
702
|
+
const rt = getRuntime("copilot");
|
|
703
|
+
expect(rt).toBeInstanceOf(CopilotRuntime);
|
|
704
|
+
expect(rt.id).toBe("copilot");
|
|
705
|
+
expect(rt.instructionPath).toBe(".github/copilot-instructions.md");
|
|
706
|
+
});
|
|
707
|
+
});
|