@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,688 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import type { Command } from "commander";
|
|
4
|
+
import { getRegistry } from "../registry/type-registry.ts";
|
|
5
|
+
import type { ExpertiseRecord } from "../schemas/record.ts";
|
|
6
|
+
import { resolveActiveWork } from "../utils/active-work.ts";
|
|
7
|
+
import type { DomainRecords } from "../utils/budget.ts";
|
|
8
|
+
import {
|
|
9
|
+
applyBudget,
|
|
10
|
+
DEFAULT_BUDGET,
|
|
11
|
+
estimateTokens,
|
|
12
|
+
formatBudgetSummary,
|
|
13
|
+
} from "../utils/budget.ts";
|
|
14
|
+
import { getExpertisePath, readConfig } from "../utils/config.ts";
|
|
15
|
+
import { getFileModTime, readExpertiseFile } from "../utils/expertise.ts";
|
|
16
|
+
import type { JsonDomain, ManifestDomain, PrimeFormat } from "../utils/format.ts";
|
|
17
|
+
import {
|
|
18
|
+
buildManifestPayload,
|
|
19
|
+
computeTypeCounts,
|
|
20
|
+
formatDomainExpertise,
|
|
21
|
+
formatDomainExpertiseCompact,
|
|
22
|
+
formatDomainExpertisePlain,
|
|
23
|
+
formatDomainExpertiseXml,
|
|
24
|
+
formatJsonOutput,
|
|
25
|
+
formatPrimeManifest,
|
|
26
|
+
formatPrimeOutput,
|
|
27
|
+
formatPrimeOutputCompact,
|
|
28
|
+
formatPrimeOutputPlain,
|
|
29
|
+
formatPrimeOutputXml,
|
|
30
|
+
formatProjectContract,
|
|
31
|
+
getSessionEndReminder,
|
|
32
|
+
shouldAutoFlipToManifest,
|
|
33
|
+
} from "../utils/format.ts";
|
|
34
|
+
import {
|
|
35
|
+
type ActiveContext,
|
|
36
|
+
activeContextHasSignal,
|
|
37
|
+
filterByActiveContext,
|
|
38
|
+
filterByContext,
|
|
39
|
+
getActiveFiles,
|
|
40
|
+
getChangedFiles,
|
|
41
|
+
isGitRepo,
|
|
42
|
+
} from "../utils/git.ts";
|
|
43
|
+
import { runHooks } from "../utils/hooks.ts";
|
|
44
|
+
import { outputJsonError } from "../utils/json-output.ts";
|
|
45
|
+
import { brand, isQuiet } from "../utils/palette.ts";
|
|
46
|
+
import {
|
|
47
|
+
buildSurfaceAnnotations,
|
|
48
|
+
resolveTierWeights,
|
|
49
|
+
sortByTrust,
|
|
50
|
+
} from "../utils/prime-ranking.ts";
|
|
51
|
+
|
|
52
|
+
interface PrimeOptions {
|
|
53
|
+
full?: boolean;
|
|
54
|
+
compact?: boolean;
|
|
55
|
+
manifest?: boolean;
|
|
56
|
+
export?: string;
|
|
57
|
+
domain?: string[];
|
|
58
|
+
excludeDomain?: string[];
|
|
59
|
+
context?: boolean;
|
|
60
|
+
files?: string[];
|
|
61
|
+
all?: boolean;
|
|
62
|
+
budget?: string;
|
|
63
|
+
// Commander parses `--no-limit` as `limit=false` (defaults true), not as
|
|
64
|
+
// `noLimit=true`. Field name follows Commander's parsed-attribute convention.
|
|
65
|
+
limit?: boolean;
|
|
66
|
+
dryRun?: boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface DryRunRecordSummary {
|
|
70
|
+
id: string;
|
|
71
|
+
type: string;
|
|
72
|
+
domain: string;
|
|
73
|
+
tokens: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface DryRunPayload {
|
|
77
|
+
wouldPrime: DryRunRecordSummary[];
|
|
78
|
+
totalTokens: number;
|
|
79
|
+
budgetUsed: number | null;
|
|
80
|
+
budgetTotal: number | null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function resolvePrimeFormat(
|
|
84
|
+
options: PrimeOptions,
|
|
85
|
+
globalFormat: PrimeFormat | undefined,
|
|
86
|
+
): PrimeFormat {
|
|
87
|
+
if (globalFormat) return globalFormat;
|
|
88
|
+
if (options.full) return "markdown";
|
|
89
|
+
if (options.compact) return "compact";
|
|
90
|
+
return "compact";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Produce a rough text representation of a record for token estimation.
|
|
95
|
+
* Delegates to the type registry so custom types and unknown-but-tolerated
|
|
96
|
+
* types (via --allow-unknown-types) get a non-undefined estimate.
|
|
97
|
+
*/
|
|
98
|
+
export function estimateRecordText(record: ExpertiseRecord): string {
|
|
99
|
+
const def = getRegistry().get(record.type);
|
|
100
|
+
if (!def) return `[${record.type}]`;
|
|
101
|
+
return def.formatCompactLine(record);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Strict numeric flag parsing — see mx-5b9578 / src/commands/rank.ts.
|
|
105
|
+
// `Number.parseInt("10abc", 10)` silently returns 10; use regex + Number() so
|
|
106
|
+
// typos like `--budget 5000abc` or `--budget 3.7` are rejected.
|
|
107
|
+
const POSITIVE_INT_RE = /^\d+$/;
|
|
108
|
+
|
|
109
|
+
function parseStrictPositiveInt(raw: string): number | null {
|
|
110
|
+
if (!POSITIVE_INT_RE.test(raw)) return null;
|
|
111
|
+
const n = Number(raw);
|
|
112
|
+
return Number.isFinite(n) && n >= 1 ? n : null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function registerPrimeCommand(program: Command): void {
|
|
116
|
+
program
|
|
117
|
+
.command("prime")
|
|
118
|
+
.description("Generate a priming prompt from expertise records")
|
|
119
|
+
.argument("[domains...]", "optional domain(s) to scope output to")
|
|
120
|
+
.option("--compact", "alias for --format compact")
|
|
121
|
+
.option("--full", "alias for --format markdown (full record details)")
|
|
122
|
+
.option("--manifest", "emit a domain index instead of full records (for monolith projects)")
|
|
123
|
+
.option("--domain <domains...>", "domain(s) to include")
|
|
124
|
+
.option("--exclude-domain <domains...>", "domain(s) to exclude")
|
|
125
|
+
.option("--context", "filter records to only those relevant to changed files")
|
|
126
|
+
.option("--files <paths...>", "filter records to only those relevant to specified files")
|
|
127
|
+
.option(
|
|
128
|
+
"--all",
|
|
129
|
+
"opt out of auto-context-scope and emit the full corpus (full mode only; manifest mode is unaffected)",
|
|
130
|
+
)
|
|
131
|
+
.option("--export <path>", "export output to a file")
|
|
132
|
+
.option("--budget <tokens>", `token budget for output (default: ${DEFAULT_BUDGET})`)
|
|
133
|
+
.option("--no-limit", "disable token budget limit")
|
|
134
|
+
.option(
|
|
135
|
+
"--dry-run",
|
|
136
|
+
"emit JSON summary of records that would be primed (id, type, domain, tokens) without rendering content; respects --budget and skips pre-prime hooks",
|
|
137
|
+
)
|
|
138
|
+
.action(async (domainsArg: string[], options: PrimeOptions) => {
|
|
139
|
+
const globalOpts = program.opts();
|
|
140
|
+
const jsonMode = globalOpts.json === true;
|
|
141
|
+
const verbose = globalOpts.verbose === true;
|
|
142
|
+
try {
|
|
143
|
+
const config = await readConfig();
|
|
144
|
+
const format = resolvePrimeFormat(options, globalOpts.format as PrimeFormat | undefined);
|
|
145
|
+
|
|
146
|
+
if (options.manifest && options.full) {
|
|
147
|
+
const msg = "Cannot combine --manifest with --full.";
|
|
148
|
+
if (jsonMode) {
|
|
149
|
+
outputJsonError("prime", msg);
|
|
150
|
+
} else {
|
|
151
|
+
console.error(chalk.red(`Error: ${msg}`));
|
|
152
|
+
}
|
|
153
|
+
process.exitCode = 1;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (options.dryRun && options.manifest) {
|
|
158
|
+
const msg =
|
|
159
|
+
"Cannot combine --dry-run with --manifest. Manifest mode lists domains, not records; --dry-run previews which records would be primed.";
|
|
160
|
+
if (jsonMode) {
|
|
161
|
+
outputJsonError("prime", msg);
|
|
162
|
+
} else {
|
|
163
|
+
console.error(chalk.red(`Error: ${msg}`));
|
|
164
|
+
}
|
|
165
|
+
process.exitCode = 1;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const requested = [...domainsArg, ...(options.domain ?? [])];
|
|
170
|
+
const unique = [...new Set(requested)];
|
|
171
|
+
|
|
172
|
+
const isScoped =
|
|
173
|
+
unique.length > 0 ||
|
|
174
|
+
(options.excludeDomain ?? []).length > 0 ||
|
|
175
|
+
options.context === true ||
|
|
176
|
+
(options.files !== undefined && options.files.length > 0);
|
|
177
|
+
|
|
178
|
+
if (options.manifest && isScoped) {
|
|
179
|
+
const msg =
|
|
180
|
+
"--manifest cannot be combined with scoping arguments. Manifest mode lists available domains; use `lm prime <domain>` or `lm prime --files <path>` to load records.";
|
|
181
|
+
if (jsonMode) {
|
|
182
|
+
outputJsonError("prime", msg);
|
|
183
|
+
} else {
|
|
184
|
+
console.error(chalk.red(`Error: ${msg}`));
|
|
185
|
+
}
|
|
186
|
+
process.exitCode = 1;
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Mode resolution: explicit flags / config win. When neither is set,
|
|
191
|
+
// auto-flip to manifest above the size threshold (slice 1 of the
|
|
192
|
+
// v0.10 prime overhaul — the prior `consider --manifest` warning is
|
|
193
|
+
// gone because the default *is* the right thing). --dry-run targets
|
|
194
|
+
// record-level preview, so it opts out of auto-flip.
|
|
195
|
+
const configMode = config.prime?.default_mode;
|
|
196
|
+
const explicitMode: "manifest" | "full" | undefined = options.manifest
|
|
197
|
+
? "manifest"
|
|
198
|
+
: options.full
|
|
199
|
+
? "full"
|
|
200
|
+
: configMode;
|
|
201
|
+
|
|
202
|
+
for (const d of unique) {
|
|
203
|
+
if (!(d in config.domains)) {
|
|
204
|
+
if (jsonMode) {
|
|
205
|
+
outputJsonError(
|
|
206
|
+
"prime",
|
|
207
|
+
`Domain "${d}" not found in config. Available domains: ${Object.keys(config.domains).join(", ")}`,
|
|
208
|
+
);
|
|
209
|
+
} else {
|
|
210
|
+
console.error(
|
|
211
|
+
`Error: Domain "${d}" not found in config. Available domains: ${Object.keys(config.domains).join(", ")}`,
|
|
212
|
+
);
|
|
213
|
+
console.error(
|
|
214
|
+
`Hint: Run \`lm add ${d}\` to create this domain, or check .loam/loam.config.yaml`,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
process.exitCode = 1;
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const excluded = options.excludeDomain ?? [];
|
|
223
|
+
for (const d of excluded) {
|
|
224
|
+
if (!(d in config.domains)) {
|
|
225
|
+
if (jsonMode) {
|
|
226
|
+
outputJsonError(
|
|
227
|
+
"prime",
|
|
228
|
+
`Excluded domain "${d}" not found in config. Available domains: ${Object.keys(config.domains).join(", ")}`,
|
|
229
|
+
);
|
|
230
|
+
} else {
|
|
231
|
+
console.error(
|
|
232
|
+
`Error: Excluded domain "${d}" not found in config. Available domains: ${Object.keys(config.domains).join(", ")}`,
|
|
233
|
+
);
|
|
234
|
+
console.error(
|
|
235
|
+
`Hint: Run \`lm add ${d}\` to create this domain, or check .loam/loam.config.yaml`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
process.exitCode = 1;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let targetDomains = unique.length > 0 ? unique : Object.keys(config.domains);
|
|
244
|
+
|
|
245
|
+
targetDomains = targetDomains.filter((d) => !excluded.includes(d));
|
|
246
|
+
|
|
247
|
+
// Resolve changed files for --context or --files filtering
|
|
248
|
+
let filesToFilter: string[] | undefined;
|
|
249
|
+
if (options.context) {
|
|
250
|
+
const cwd = process.cwd();
|
|
251
|
+
if (!isGitRepo(cwd)) {
|
|
252
|
+
const msg = "Not in a git repository. --context requires git.";
|
|
253
|
+
if (jsonMode) {
|
|
254
|
+
outputJsonError("prime", msg);
|
|
255
|
+
} else {
|
|
256
|
+
console.error(chalk.red(`Error: ${msg}`));
|
|
257
|
+
}
|
|
258
|
+
process.exitCode = 1;
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
filesToFilter = getChangedFiles(cwd, "HEAD~1");
|
|
262
|
+
if (filesToFilter.length === 0) {
|
|
263
|
+
if (jsonMode) {
|
|
264
|
+
outputJsonError("prime", "No changed files found. Nothing to filter by.");
|
|
265
|
+
} else {
|
|
266
|
+
console.log("No changed files found. Nothing to filter by.");
|
|
267
|
+
}
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
} else if (options.files && options.files.length > 0) {
|
|
271
|
+
filesToFilter = options.files;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Determine budget settings
|
|
275
|
+
const budgetEnabled = !jsonMode && options.limit !== false;
|
|
276
|
+
let budget: number;
|
|
277
|
+
if (options.budget) {
|
|
278
|
+
const parsed = parseStrictPositiveInt(options.budget);
|
|
279
|
+
if (parsed === null) {
|
|
280
|
+
const msg = `--budget must be a positive integer (got "${options.budget}").`;
|
|
281
|
+
if (jsonMode) {
|
|
282
|
+
outputJsonError("prime", msg);
|
|
283
|
+
} else {
|
|
284
|
+
console.error(chalk.red(`Error: ${msg}`));
|
|
285
|
+
}
|
|
286
|
+
process.exitCode = 1;
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
budget = parsed;
|
|
290
|
+
} else {
|
|
291
|
+
budget = DEFAULT_BUDGET;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Load records once, unfiltered. Both branches (manifest and full)
|
|
295
|
+
// need either counts or the records themselves; one read keeps the
|
|
296
|
+
// auto-flip decision and the format pipeline aligned on the same
|
|
297
|
+
// dataset. Auto-flip threshold uses unfiltered counts so a scoped
|
|
298
|
+
// session in a 200-record corpus still flips to manifest by default.
|
|
299
|
+
interface LoadedDomain {
|
|
300
|
+
domain: string;
|
|
301
|
+
records: ExpertiseRecord[];
|
|
302
|
+
lastUpdated: Date | null;
|
|
303
|
+
}
|
|
304
|
+
const loaded: LoadedDomain[] = [];
|
|
305
|
+
for (const domain of targetDomains) {
|
|
306
|
+
const filePath = getExpertisePath(domain);
|
|
307
|
+
const records = await readExpertiseFile(filePath);
|
|
308
|
+
const lastUpdated = await getFileModTime(filePath);
|
|
309
|
+
loaded.push({ domain, records, lastUpdated });
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Decide effective mode. Explicit flag / config always wins. With
|
|
313
|
+
// no explicit signal and no scoping, auto-flip to manifest above
|
|
314
|
+
// the size threshold (>100 records or >5 domains). --dry-run opts
|
|
315
|
+
// out — it previews records, which manifest mode wouldn't show.
|
|
316
|
+
let useManifest: boolean;
|
|
317
|
+
if (isScoped) {
|
|
318
|
+
useManifest = false;
|
|
319
|
+
} else if (explicitMode === "manifest") {
|
|
320
|
+
useManifest = true;
|
|
321
|
+
} else if (explicitMode === "full") {
|
|
322
|
+
useManifest = false;
|
|
323
|
+
} else if (options.dryRun) {
|
|
324
|
+
useManifest = false;
|
|
325
|
+
} else {
|
|
326
|
+
const totalRecords = loaded.reduce((s, l) => s + l.records.length, 0);
|
|
327
|
+
useManifest = shouldAutoFlipToManifest(totalRecords, loaded.length);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Slice-2 auto-context-scope: in full mode, narrow records to the
|
|
331
|
+
// agent's current working set (git status + active-work resolver)
|
|
332
|
+
// unless the user opts out with `--all`. Explicit scoping (--files,
|
|
333
|
+
// --context, positional domain) takes precedence — those code paths
|
|
334
|
+
// already filter via `filesToFilter` below. JSON mode skips
|
|
335
|
+
// auto-scope so machine consumers see a deterministic corpus.
|
|
336
|
+
let scopedTotal = 0;
|
|
337
|
+
let scopedKept = 0;
|
|
338
|
+
let scopedFromGit = false;
|
|
339
|
+
let activeContext: ActiveContext | null = null;
|
|
340
|
+
if (
|
|
341
|
+
!useManifest &&
|
|
342
|
+
!isScoped &&
|
|
343
|
+
!jsonMode &&
|
|
344
|
+
options.all !== true &&
|
|
345
|
+
filesToFilter === undefined
|
|
346
|
+
) {
|
|
347
|
+
const cwd = process.cwd();
|
|
348
|
+
if (isGitRepo(cwd)) {
|
|
349
|
+
const changedFiles = getActiveFiles(cwd);
|
|
350
|
+
const active = resolveActiveWork({ cwd });
|
|
351
|
+
const trackers = {
|
|
352
|
+
sprout: active.sprout,
|
|
353
|
+
gh: active.gh,
|
|
354
|
+
linear: active.linear,
|
|
355
|
+
bead: active.bead,
|
|
356
|
+
};
|
|
357
|
+
const ctx: ActiveContext = { changedFiles, trackers };
|
|
358
|
+
if (activeContextHasSignal(ctx)) {
|
|
359
|
+
activeContext = ctx;
|
|
360
|
+
scopedFromGit = true;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (filesToFilter !== undefined) {
|
|
366
|
+
for (let i = 0; i < loaded.length; i++) {
|
|
367
|
+
const entry = loaded[i];
|
|
368
|
+
if (!entry) continue;
|
|
369
|
+
const filtered = filterByContext(entry.records, filesToFilter);
|
|
370
|
+
loaded[i] = { ...entry, records: filtered };
|
|
371
|
+
}
|
|
372
|
+
if (!jsonMode) {
|
|
373
|
+
for (let i = loaded.length - 1; i >= 0; i--) {
|
|
374
|
+
const entry = loaded[i];
|
|
375
|
+
if (entry && entry.records.length === 0) loaded.splice(i, 1);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
} else if (activeContext && !useManifest) {
|
|
379
|
+
for (let i = 0; i < loaded.length; i++) {
|
|
380
|
+
const entry = loaded[i];
|
|
381
|
+
if (!entry) continue;
|
|
382
|
+
scopedTotal += entry.records.length;
|
|
383
|
+
const filtered = filterByActiveContext(entry.records, activeContext);
|
|
384
|
+
scopedKept += filtered.length;
|
|
385
|
+
loaded[i] = { ...entry, records: filtered };
|
|
386
|
+
}
|
|
387
|
+
if (scopedFromGit) {
|
|
388
|
+
const sources: string[] = [];
|
|
389
|
+
if (activeContext.changedFiles.length > 0) sources.push("git status");
|
|
390
|
+
const trackerBits: string[] = [];
|
|
391
|
+
for (const t of ["sprout", "gh", "linear", "bead"] as const) {
|
|
392
|
+
const v = activeContext.trackers[t];
|
|
393
|
+
if (v) trackerBits.push(`${t}:${v}`);
|
|
394
|
+
}
|
|
395
|
+
if (trackerBits.length > 0) sources.push(`active-work (${trackerBits.join(", ")})`);
|
|
396
|
+
const source = sources.length > 0 ? sources.join(" + ") : "git status";
|
|
397
|
+
console.error(
|
|
398
|
+
`prime: scoped to ${scopedKept} of ${scopedTotal} records based on ${source}; run with --all for the full corpus`,
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Contract block (write-side gates) leads non-JSON output in both
|
|
404
|
+
// manifest and full modes. Skipped for JSON (consumers parse config
|
|
405
|
+
// separately) and dry-run (output is a JSON record summary).
|
|
406
|
+
const contractBlock =
|
|
407
|
+
jsonMode || options.dryRun ? null : formatProjectContract(config, format);
|
|
408
|
+
|
|
409
|
+
let output: string;
|
|
410
|
+
|
|
411
|
+
if (useManifest) {
|
|
412
|
+
const manifestDomains: ManifestDomain[] = loaded.map((l) => ({
|
|
413
|
+
domain: l.domain,
|
|
414
|
+
count: l.records.length,
|
|
415
|
+
lastUpdated: l.lastUpdated,
|
|
416
|
+
typeCounts: computeTypeCounts(l.records),
|
|
417
|
+
}));
|
|
418
|
+
|
|
419
|
+
if (jsonMode) {
|
|
420
|
+
output = JSON.stringify(
|
|
421
|
+
buildManifestPayload(manifestDomains, config.governance),
|
|
422
|
+
null,
|
|
423
|
+
2,
|
|
424
|
+
);
|
|
425
|
+
} else {
|
|
426
|
+
output = formatPrimeManifest(manifestDomains, config.governance, format);
|
|
427
|
+
// Plain format is the spawn-injection contract — warren / other
|
|
428
|
+
// embedders handle session framing in their own dispatch, so the
|
|
429
|
+
// reminder would be redundant noise inside a system prompt.
|
|
430
|
+
if (format !== "plain") {
|
|
431
|
+
const reminder = getSessionEndReminder(format, config.prime?.session_close);
|
|
432
|
+
if (reminder.length > 0) output += `\n\n${reminder}`;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
} else {
|
|
436
|
+
// Trust-tier ranking (slice 3 of the v0.10 prime overhaul): sort
|
|
437
|
+
// each domain's records by (★ count * star_weight) + classification
|
|
438
|
+
// weight before budget so the most trustworthy records survive
|
|
439
|
+
// truncation. Manifest mode skips this — manifest emits domain
|
|
440
|
+
// counts, not records, so order is irrelevant.
|
|
441
|
+
const tierWeights = resolveTierWeights(config.prime?.tier_weights);
|
|
442
|
+
for (let i = 0; i < loaded.length; i++) {
|
|
443
|
+
const entry = loaded[i];
|
|
444
|
+
if (!entry) continue;
|
|
445
|
+
loaded[i] = { ...entry, records: sortByTrust(entry.records, tierWeights) };
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Build the "why surfaced now" context for downstream formatters.
|
|
449
|
+
// Priority: explicit --files/--context (treat the requested paths as
|
|
450
|
+
// the agent's working set) → auto-context-scope signal → null. The
|
|
451
|
+
// resulting context drives both the file-match and tracker-match
|
|
452
|
+
// branches inside `whySurfaced`; when no signal exists, suffixes
|
|
453
|
+
// fall back to stars / recency / "universal".
|
|
454
|
+
const annotationContext: ActiveContext | null =
|
|
455
|
+
filesToFilter !== undefined
|
|
456
|
+
? { changedFiles: filesToFilter, trackers: {} }
|
|
457
|
+
: activeContext;
|
|
458
|
+
|
|
459
|
+
// --dry-run short-circuits: skip pre-prime hooks (they may have side
|
|
460
|
+
// effects like Slack posts) and emit a JSON summary of which records
|
|
461
|
+
// would be primed under the same budget rules as a real run. Format
|
|
462
|
+
// is irrelevant when dry-running — output is always JSON.
|
|
463
|
+
if (options.dryRun) {
|
|
464
|
+
const dryRunBudget = budgetEnabled ? budget : null;
|
|
465
|
+
const allDomainRecords: DomainRecords[] = loaded.map(({ domain, records }) => ({
|
|
466
|
+
domain,
|
|
467
|
+
records,
|
|
468
|
+
}));
|
|
469
|
+
const keptByDomain = budgetEnabled
|
|
470
|
+
? applyBudget(allDomainRecords, budget, (record) => estimateRecordText(record)).kept
|
|
471
|
+
: allDomainRecords;
|
|
472
|
+
|
|
473
|
+
const wouldPrime: DryRunRecordSummary[] = [];
|
|
474
|
+
let totalTokens = 0;
|
|
475
|
+
for (const { domain, records } of keptByDomain) {
|
|
476
|
+
for (const record of records) {
|
|
477
|
+
const tokens = estimateTokens(estimateRecordText(record));
|
|
478
|
+
wouldPrime.push({
|
|
479
|
+
id: record.id ?? "",
|
|
480
|
+
type: record.type,
|
|
481
|
+
domain,
|
|
482
|
+
tokens,
|
|
483
|
+
});
|
|
484
|
+
totalTokens += tokens;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
const payload: DryRunPayload = {
|
|
488
|
+
wouldPrime,
|
|
489
|
+
totalTokens,
|
|
490
|
+
budgetUsed: dryRunBudget !== null ? totalTokens / dryRunBudget : null,
|
|
491
|
+
budgetTotal: dryRunBudget,
|
|
492
|
+
};
|
|
493
|
+
output = JSON.stringify(payload, null, 2);
|
|
494
|
+
|
|
495
|
+
if (options.export) {
|
|
496
|
+
await writeFile(options.export, `${output}\n`, "utf-8");
|
|
497
|
+
if (!jsonMode && !isQuiet()) {
|
|
498
|
+
console.log(`${brand("✓")} ${brand(`Exported to ${options.export}`)}`);
|
|
499
|
+
}
|
|
500
|
+
} else {
|
|
501
|
+
console.log(output);
|
|
502
|
+
}
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const hookPayload = {
|
|
507
|
+
domains: loaded.map(({ domain, records }) => ({ domain, records })),
|
|
508
|
+
};
|
|
509
|
+
const hookRes = await runHooks<typeof hookPayload>("pre-prime", hookPayload);
|
|
510
|
+
if (hookRes.blocked) {
|
|
511
|
+
const reason = hookRes.blockReason ?? "pre-prime hook blocked output";
|
|
512
|
+
if (jsonMode) {
|
|
513
|
+
outputJsonError("prime", reason);
|
|
514
|
+
} else {
|
|
515
|
+
console.error(chalk.red(`Error: ${reason}`));
|
|
516
|
+
}
|
|
517
|
+
process.exitCode = 1;
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
for (const w of hookRes.warnings) {
|
|
521
|
+
if (!jsonMode) console.error(`Warning: ${w}`);
|
|
522
|
+
}
|
|
523
|
+
// If a hook mutated the payload, replace records on a per-domain
|
|
524
|
+
// basis (matching original order); a hook-emitted domain not in
|
|
525
|
+
// `loaded` is ignored to prevent surfacing data the user didn't ask
|
|
526
|
+
// for in the budget/format paths.
|
|
527
|
+
const mutatedByDomain = new Map<string, ExpertiseRecord[]>();
|
|
528
|
+
if (hookRes.ranAny && hookRes.payload?.domains) {
|
|
529
|
+
for (const d of hookRes.payload.domains) {
|
|
530
|
+
if (d && typeof d.domain === "string" && Array.isArray(d.records)) {
|
|
531
|
+
mutatedByDomain.set(d.domain, d.records);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
const finalLoaded: LoadedDomain[] = loaded.map((l) => {
|
|
536
|
+
const mut = mutatedByDomain.get(l.domain);
|
|
537
|
+
const base = mut ? { ...l, records: mut } : l;
|
|
538
|
+
// Re-sort post-hook so hook-mutated records also follow trust
|
|
539
|
+
// order. The pre-hook sort (on `loaded`) makes the dry-run path
|
|
540
|
+
// match what the real run would emit.
|
|
541
|
+
return { ...base, records: sortByTrust(base.records, tierWeights) };
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
if (jsonMode) {
|
|
545
|
+
const domains: JsonDomain[] = [];
|
|
546
|
+
for (const { domain, records } of finalLoaded) {
|
|
547
|
+
if (!filesToFilter || records.length > 0) {
|
|
548
|
+
domains.push({ domain, entry_count: records.length, records });
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
output = formatJsonOutput(domains);
|
|
552
|
+
} else {
|
|
553
|
+
// Reconstruct legacy structures for the existing budget+format pipeline.
|
|
554
|
+
const allDomainRecords: DomainRecords[] = finalLoaded.map(({ domain, records }) => ({
|
|
555
|
+
domain,
|
|
556
|
+
records,
|
|
557
|
+
}));
|
|
558
|
+
const modTimes = new Map<string, Date | null>();
|
|
559
|
+
for (const { domain, lastUpdated } of finalLoaded) {
|
|
560
|
+
modTimes.set(domain, lastUpdated);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Apply budget filtering
|
|
564
|
+
let domainRecordsToFormat: DomainRecords[];
|
|
565
|
+
let droppedCount = 0;
|
|
566
|
+
let droppedDomainCount = 0;
|
|
567
|
+
|
|
568
|
+
if (budgetEnabled) {
|
|
569
|
+
const result = applyBudget(allDomainRecords, budget, (record) =>
|
|
570
|
+
estimateRecordText(record),
|
|
571
|
+
);
|
|
572
|
+
domainRecordsToFormat = result.kept;
|
|
573
|
+
droppedCount = result.droppedCount;
|
|
574
|
+
droppedDomainCount = result.droppedDomainCount;
|
|
575
|
+
} else {
|
|
576
|
+
domainRecordsToFormat = allDomainRecords;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Build per-record "why surfaced" annotations using the same
|
|
580
|
+
// context (changedFiles + trackers) that drove the filter step.
|
|
581
|
+
// Computed after budget truncation so dropped records don't waste
|
|
582
|
+
// annotation work.
|
|
583
|
+
const annotationsByDomain = new Map<string, Map<string, string>>();
|
|
584
|
+
for (const { domain, records } of domainRecordsToFormat) {
|
|
585
|
+
annotationsByDomain.set(domain, buildSurfaceAnnotations(records, annotationContext));
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Format domain sections
|
|
589
|
+
const domainSections: string[] = [];
|
|
590
|
+
for (const { domain, records } of domainRecordsToFormat) {
|
|
591
|
+
const lastUpdated = modTimes.get(domain) ?? null;
|
|
592
|
+
const annotations = annotationsByDomain.get(domain);
|
|
593
|
+
|
|
594
|
+
switch (format) {
|
|
595
|
+
case "xml":
|
|
596
|
+
domainSections.push(
|
|
597
|
+
formatDomainExpertiseXml(domain, records, lastUpdated, annotations),
|
|
598
|
+
);
|
|
599
|
+
break;
|
|
600
|
+
case "plain":
|
|
601
|
+
domainSections.push(
|
|
602
|
+
formatDomainExpertisePlain(domain, records, lastUpdated, annotations),
|
|
603
|
+
);
|
|
604
|
+
break;
|
|
605
|
+
case "compact":
|
|
606
|
+
domainSections.push(
|
|
607
|
+
formatDomainExpertiseCompact(domain, records, lastUpdated, annotations),
|
|
608
|
+
);
|
|
609
|
+
break;
|
|
610
|
+
default:
|
|
611
|
+
domainSections.push(
|
|
612
|
+
formatDomainExpertise(
|
|
613
|
+
domain,
|
|
614
|
+
records,
|
|
615
|
+
lastUpdated,
|
|
616
|
+
{
|
|
617
|
+
full: options.full || verbose,
|
|
618
|
+
},
|
|
619
|
+
annotations,
|
|
620
|
+
),
|
|
621
|
+
);
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
switch (format) {
|
|
627
|
+
case "xml":
|
|
628
|
+
output = formatPrimeOutputXml(domainSections);
|
|
629
|
+
break;
|
|
630
|
+
case "plain":
|
|
631
|
+
output = formatPrimeOutputPlain(domainSections);
|
|
632
|
+
break;
|
|
633
|
+
case "compact":
|
|
634
|
+
output = formatPrimeOutputCompact(domainSections);
|
|
635
|
+
break;
|
|
636
|
+
default:
|
|
637
|
+
output = formatPrimeOutput(domainSections);
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Append truncation summary before session reminder
|
|
642
|
+
if (droppedCount > 0) {
|
|
643
|
+
output += `\n\n${formatBudgetSummary(droppedCount, droppedDomainCount)}`;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Plain format is the spawn-injection contract — warren / other
|
|
647
|
+
// embedders handle session framing in their own dispatch, so the
|
|
648
|
+
// reminder would be redundant noise inside a system prompt.
|
|
649
|
+
if (format !== "plain") {
|
|
650
|
+
const reminder = getSessionEndReminder(format, config.prime?.session_close);
|
|
651
|
+
if (reminder.length > 0) output += `\n\n${reminder}`;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Lead with the project contract (write-side gates from config) on
|
|
657
|
+
// non-JSON / non-dry-run output. `contractBlock` is null when the
|
|
658
|
+
// project has no gates worth surfacing.
|
|
659
|
+
if (contractBlock) {
|
|
660
|
+
output = `${contractBlock}\n\n${output}`;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (options.export) {
|
|
664
|
+
await writeFile(options.export, `${output}\n`, "utf-8");
|
|
665
|
+
if (!jsonMode && !isQuiet()) {
|
|
666
|
+
console.log(`${brand("✓")} ${brand(`Exported to ${options.export}`)}`);
|
|
667
|
+
}
|
|
668
|
+
} else {
|
|
669
|
+
console.log(output);
|
|
670
|
+
}
|
|
671
|
+
} catch (err) {
|
|
672
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
673
|
+
if (jsonMode) {
|
|
674
|
+
outputJsonError("prime", "No .loam/ directory found. Run `loam init` first.");
|
|
675
|
+
} else {
|
|
676
|
+
console.error(chalk.red("Error: No .loam/ directory found. Run `loam init` first."));
|
|
677
|
+
}
|
|
678
|
+
} else {
|
|
679
|
+
if (jsonMode) {
|
|
680
|
+
outputJsonError("prime", (err as Error).message);
|
|
681
|
+
} else {
|
|
682
|
+
console.error(chalk.red(`Error: ${(err as Error).message}`));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
process.exitCode = 1;
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
}
|