@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,431 @@
|
|
|
1
|
+
import type { CustomTypeConfig } from "../schemas/config.ts";
|
|
2
|
+
import {
|
|
3
|
+
compactMeta,
|
|
4
|
+
formatLinks,
|
|
5
|
+
formatOutcome,
|
|
6
|
+
formatRecordMeta,
|
|
7
|
+
idTag,
|
|
8
|
+
truncate,
|
|
9
|
+
xmlEscape,
|
|
10
|
+
} from "../utils/format-helpers.ts";
|
|
11
|
+
import { BUILTIN_DEFS } from "./builtins.ts";
|
|
12
|
+
import { compileSummaryTemplate, extractTemplateTokens } from "./template.ts";
|
|
13
|
+
import type { TypeDefinition } from "./type-registry.ts";
|
|
14
|
+
|
|
15
|
+
const BUILTIN_DEFS_BY_NAME: ReadonlyMap<string, TypeDefinition> = new Map(
|
|
16
|
+
BUILTIN_DEFS.map((d) => [d.name, d]),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export const BUILTIN_TYPE_NAMES: ReadonlySet<string> = new Set([
|
|
20
|
+
"convention",
|
|
21
|
+
"pattern",
|
|
22
|
+
"failure",
|
|
23
|
+
"decision",
|
|
24
|
+
"reference",
|
|
25
|
+
"guide",
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
// Fields owned by BaseRecord. Custom types must NOT redeclare these as
|
|
29
|
+
// required/optional — they're applied automatically by the schema layer.
|
|
30
|
+
const BASE_FIELDS: ReadonlySet<string> = new Set([
|
|
31
|
+
"id",
|
|
32
|
+
"type",
|
|
33
|
+
"classification",
|
|
34
|
+
"recorded_at",
|
|
35
|
+
"evidence",
|
|
36
|
+
"tags",
|
|
37
|
+
"relates_to",
|
|
38
|
+
"supersedes",
|
|
39
|
+
"outcomes",
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
const CUSTOM_NAME_RE = /^[a-z][a-z0-9_]*$/;
|
|
43
|
+
const NAMING_RULE =
|
|
44
|
+
"lowercase letters, digits, and underscores; must start with a letter; no hyphens";
|
|
45
|
+
|
|
46
|
+
// Required + optional fields owned by built-in types. Used to reject custom
|
|
47
|
+
// type aliases whose legacy name collides with a foreign type's field — that
|
|
48
|
+
// would silently swallow a field name another type relies on.
|
|
49
|
+
const BUILTIN_FIELDS: ReadonlySet<string> = new Set(
|
|
50
|
+
BUILTIN_DEFS.flatMap((d) => [...d.required, ...d.optional]),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
function describeNameViolation(value: string): string {
|
|
54
|
+
if (value.length === 0) return "must not be empty";
|
|
55
|
+
if (value.includes("-")) return "contains a hyphen";
|
|
56
|
+
if (/^\d/.test(value)) return "starts with a digit";
|
|
57
|
+
if (!/^[a-z]/.test(value)) return "must start with a lowercase letter";
|
|
58
|
+
if (/[A-Z]/.test(value)) return "contains uppercase letters";
|
|
59
|
+
if (/[^a-z0-9_]/.test(value)) return "contains a character outside [a-z0-9_]";
|
|
60
|
+
return "violates the naming rule";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const VALID_COMPACT_STRATEGIES = new Set(["concat", "merge_outcomes", "keep_latest", "manual"]);
|
|
64
|
+
|
|
65
|
+
const linkArray = {
|
|
66
|
+
type: "array",
|
|
67
|
+
items: { type: "string", pattern: "^([a-z0-9-]+:)?mx-[0-9a-f]{4,8}$" },
|
|
68
|
+
} as const;
|
|
69
|
+
|
|
70
|
+
export function validateCustomTypeConfig(name: string, cfg: CustomTypeConfig): void {
|
|
71
|
+
if (!CUSTOM_NAME_RE.test(name)) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`Invalid custom_types name "${name}" (${describeNameViolation(name)}). Allowed: ${NAMING_RULE}.`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
if (BUILTIN_TYPE_NAMES.has(name)) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Custom type "${name}" shadows a built-in record type. Reserved names: ${[...BUILTIN_TYPE_NAMES].join(", ")}.`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let parent: TypeDefinition | undefined;
|
|
83
|
+
if (cfg.extends !== undefined) {
|
|
84
|
+
if (typeof cfg.extends !== "string" || cfg.extends.length === 0) {
|
|
85
|
+
throw new Error(`Custom type "${name}" extends must be a non-empty string.`);
|
|
86
|
+
}
|
|
87
|
+
if (!BUILTIN_TYPE_NAMES.has(cfg.extends)) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Custom type "${name}" extends "${cfg.extends}" must be a built-in type. Built-ins: ${[...BUILTIN_TYPE_NAMES].join(", ")}. Custom-from-custom inheritance is not supported in v1.`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
parent = BUILTIN_DEFS_BY_NAME.get(cfg.extends);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const childRequired = cfg.required ?? [];
|
|
96
|
+
if (!parent && childRequired.length === 0) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Custom type "${name}" must declare a non-empty "required" array (or set "extends" to inherit from a built-in).`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Track child-only declarations separately from the merged set: a child
|
|
103
|
+
// may redundantly redeclare a parent field (union semantics — silent), but
|
|
104
|
+
// must not declare the same field twice within its own config.
|
|
105
|
+
const childSeen = new Set<string>();
|
|
106
|
+
const mergedSeen = new Set<string>();
|
|
107
|
+
if (parent) {
|
|
108
|
+
for (const f of parent.required) mergedSeen.add(f);
|
|
109
|
+
for (const f of parent.optional) mergedSeen.add(f);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (const f of childRequired) {
|
|
113
|
+
if (typeof f !== "string" || f.length === 0) {
|
|
114
|
+
throw new Error(`Custom type "${name}" has an invalid required field: ${JSON.stringify(f)}.`);
|
|
115
|
+
}
|
|
116
|
+
if (!CUSTOM_NAME_RE.test(f)) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Custom type "${name}" required field "${f}" (${describeNameViolation(f)}). Allowed: ${NAMING_RULE}.`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
if (BASE_FIELDS.has(f)) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Custom type "${name}" cannot declare base field "${f}" as required (base fields: ${[...BASE_FIELDS].join(", ")}).`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
if (childSeen.has(f)) {
|
|
127
|
+
throw new Error(`Custom type "${name}" has duplicate required field "${f}".`);
|
|
128
|
+
}
|
|
129
|
+
childSeen.add(f);
|
|
130
|
+
mergedSeen.add(f);
|
|
131
|
+
}
|
|
132
|
+
for (const f of cfg.optional ?? []) {
|
|
133
|
+
if (typeof f !== "string" || f.length === 0) {
|
|
134
|
+
throw new Error(`Custom type "${name}" has an invalid optional field: ${JSON.stringify(f)}.`);
|
|
135
|
+
}
|
|
136
|
+
if (!CUSTOM_NAME_RE.test(f)) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Custom type "${name}" optional field "${f}" (${describeNameViolation(f)}). Allowed: ${NAMING_RULE}.`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
if (BASE_FIELDS.has(f)) {
|
|
142
|
+
throw new Error(`Custom type "${name}" cannot declare base field "${f}" as optional.`);
|
|
143
|
+
}
|
|
144
|
+
if (childSeen.has(f)) {
|
|
145
|
+
throw new Error(`Custom type "${name}" has field "${f}" in both required and optional.`);
|
|
146
|
+
}
|
|
147
|
+
childSeen.add(f);
|
|
148
|
+
mergedSeen.add(f);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const effectiveDedupKey = cfg.dedup_key ?? parent?.dedupKey;
|
|
152
|
+
if (typeof effectiveDedupKey !== "string" || effectiveDedupKey.length === 0) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`Custom type "${name}" must set "dedup_key" (field name or "content_hash"), or extend a built-in that defines one.`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
if (effectiveDedupKey !== "content_hash" && !mergedSeen.has(effectiveDedupKey)) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`Custom type "${name}" dedup_key "${effectiveDedupKey}" must be declared in required/optional or be "content_hash".`,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (cfg.id_key !== undefined && cfg.id_key !== "content_hash" && !mergedSeen.has(cfg.id_key)) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`Custom type "${name}" id_key "${cfg.id_key}" must be declared in required/optional or be "content_hash".`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
if (cfg.summary === undefined && !parent) {
|
|
168
|
+
throw new Error(`Custom type "${name}" must set "summary" template string.`);
|
|
169
|
+
}
|
|
170
|
+
if (cfg.summary !== undefined && (typeof cfg.summary !== "string" || cfg.summary.length === 0)) {
|
|
171
|
+
throw new Error(`Custom type "${name}" "summary" must be a non-empty string.`);
|
|
172
|
+
}
|
|
173
|
+
if (typeof cfg.summary === "string") {
|
|
174
|
+
// Reject `{tok}` references that won't resolve at render time. Without
|
|
175
|
+
// this, a typo (e.g. {description} on a type whose required field is
|
|
176
|
+
// `content`) renders as empty string in `lm prime` output, silently
|
|
177
|
+
// degrading every record of that type.
|
|
178
|
+
const validTokens = new Set<string>([...BASE_FIELDS, ...mergedSeen]);
|
|
179
|
+
for (const token of extractTemplateTokens(cfg.summary)) {
|
|
180
|
+
if (!validTokens.has(token)) {
|
|
181
|
+
const known = [...validTokens].sort().join(", ");
|
|
182
|
+
throw new Error(
|
|
183
|
+
`Custom type "${name}" summary references unknown field "{${token}}". Declared fields: ${known}.`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (cfg.compact !== undefined && !VALID_COMPACT_STRATEGIES.has(cfg.compact)) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`Custom type "${name}" compact strategy "${cfg.compact}" is invalid. Use one of: ${[...VALID_COMPACT_STRATEGIES].join(", ")}.`,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
if (cfg.extracts_files && cfg.files_field !== undefined && !mergedSeen.has(cfg.files_field)) {
|
|
194
|
+
throw new Error(
|
|
195
|
+
`Custom type "${name}" files_field "${cfg.files_field}" must be declared in required/optional when extracts_files: true.`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
if (cfg.aliases !== undefined) {
|
|
199
|
+
if (typeof cfg.aliases !== "object" || cfg.aliases === null || Array.isArray(cfg.aliases)) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Custom type "${name}" aliases must be a map of canonical field name → legacy alias names.`,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
const seenAliases = new Set<string>();
|
|
205
|
+
for (const [canonical, legacyNames] of Object.entries(cfg.aliases)) {
|
|
206
|
+
if (!mergedSeen.has(canonical)) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
`Custom type "${name}" aliases key "${canonical}" must be declared in required/optional.`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
if (!Array.isArray(legacyNames) || legacyNames.length === 0) {
|
|
212
|
+
throw new Error(
|
|
213
|
+
`Custom type "${name}" aliases entry "${canonical}" must be a non-empty array of legacy field names.`,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
for (const legacy of legacyNames) {
|
|
217
|
+
if (typeof legacy !== "string" || legacy.length === 0) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
`Custom type "${name}" aliases entry "${canonical}" has an invalid legacy name: ${JSON.stringify(legacy)}.`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
if (!CUSTOM_NAME_RE.test(legacy)) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`Custom type "${name}" aliases legacy name "${legacy}" (${describeNameViolation(legacy)}). Allowed: ${NAMING_RULE}.`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
if (BASE_FIELDS.has(legacy)) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`Custom type "${name}" aliases legacy name "${legacy}" collides with a base field.`,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
// Reject aliases legacy name that names a built-in type's field.
|
|
233
|
+
// Aliases run at read time only on records of *this* type, so a
|
|
234
|
+
// foreign-field name on disk isn't actively confused — but
|
|
235
|
+
// declaring one signals a misunderstanding (the user thinks the
|
|
236
|
+
// field is theirs when it belongs to another type) and tends to
|
|
237
|
+
// surface as silent loss when records get reclassified.
|
|
238
|
+
if (BUILTIN_FIELDS.has(legacy) && !mergedSeen.has(legacy)) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
`Custom type "${name}" aliases legacy name "${legacy}" collides with a built-in type's field. Use a name that isn't already owned by another type, or set it as required/optional on this type if the field really belongs here.`,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
if (mergedSeen.has(legacy)) {
|
|
244
|
+
throw new Error(
|
|
245
|
+
`Custom type "${name}" aliases legacy name "${legacy}" is already declared as a current field; aliases are for retired names only.`,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
if (seenAliases.has(legacy)) {
|
|
249
|
+
throw new Error(
|
|
250
|
+
`Custom type "${name}" aliases legacy name "${legacy}" appears under multiple canonical fields; each alias maps to one canonical name.`,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
seenAliases.add(legacy);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function defaultSectionTitle(name: string): string {
|
|
260
|
+
return `${name.charAt(0).toUpperCase() + name.slice(1)}s`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function mergeFieldList(
|
|
264
|
+
parentFields: readonly string[] | undefined,
|
|
265
|
+
childFields: readonly string[] | undefined,
|
|
266
|
+
): string[] {
|
|
267
|
+
const seen = new Set<string>();
|
|
268
|
+
const out: string[] = [];
|
|
269
|
+
for (const f of parentFields ?? []) {
|
|
270
|
+
if (!seen.has(f)) {
|
|
271
|
+
seen.add(f);
|
|
272
|
+
out.push(f);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
for (const f of childFields ?? []) {
|
|
276
|
+
if (!seen.has(f)) {
|
|
277
|
+
seen.add(f);
|
|
278
|
+
out.push(f);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return out;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function getBuiltinTypeDef(name: string): TypeDefinition | undefined {
|
|
285
|
+
return BUILTIN_DEFS_BY_NAME.get(name);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function buildCustomTypeDefinition(name: string, cfg: CustomTypeConfig): TypeDefinition {
|
|
289
|
+
validateCustomTypeConfig(name, cfg);
|
|
290
|
+
|
|
291
|
+
const parent = cfg.extends ? BUILTIN_DEFS_BY_NAME.get(cfg.extends) : undefined;
|
|
292
|
+
|
|
293
|
+
// Union the field arrays parent-first so inherited fields keep their
|
|
294
|
+
// declared order, with the child's own fields appended.
|
|
295
|
+
const required = mergeFieldList(parent?.required, cfg.required);
|
|
296
|
+
// A child can promote a parent's optional field by listing it under
|
|
297
|
+
// `required`. Strip those from the optional list so the field doesn't
|
|
298
|
+
// appear in both arrays.
|
|
299
|
+
const requiredSet = new Set(required);
|
|
300
|
+
const optional = mergeFieldList(parent?.optional, cfg.optional).filter(
|
|
301
|
+
(f) => !requiredSet.has(f),
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
const dedupKey = cfg.dedup_key ?? parent?.dedupKey ?? "content_hash";
|
|
305
|
+
const idKey = cfg.id_key ?? cfg.dedup_key ?? parent?.idKey ?? dedupKey;
|
|
306
|
+
const summaryFn = cfg.summary
|
|
307
|
+
? compileSummaryTemplate(cfg.summary)
|
|
308
|
+
: (parent?.summary ?? (() => ""));
|
|
309
|
+
const extractsFiles = cfg.extracts_files ?? parent?.extractsFiles ?? false;
|
|
310
|
+
const filesField = cfg.files_field ?? parent?.filesField ?? "files";
|
|
311
|
+
const compactStrategy = cfg.compact ?? parent?.compact ?? "manual";
|
|
312
|
+
const sectionTitle = cfg.section_title ?? parent?.sectionTitle ?? defaultSectionTitle(name);
|
|
313
|
+
|
|
314
|
+
const properties: Record<string, unknown> = {
|
|
315
|
+
id: { type: "string", pattern: "^mx-[0-9a-f]{4,8}$" },
|
|
316
|
+
type: { type: "string", const: name },
|
|
317
|
+
classification: { $ref: "#/definitions/classification" },
|
|
318
|
+
recorded_at: { type: "string" },
|
|
319
|
+
evidence: { $ref: "#/definitions/evidence" },
|
|
320
|
+
tags: { type: "array", items: { type: "string" } },
|
|
321
|
+
relates_to: linkArray,
|
|
322
|
+
supersedes: linkArray,
|
|
323
|
+
outcomes: { type: "array", items: { $ref: "#/definitions/outcome" } },
|
|
324
|
+
dir_anchors: { type: "array", items: { type: "string" } },
|
|
325
|
+
supersession_demoted_at: { type: "string" },
|
|
326
|
+
anchor_decay_demoted_at: { type: "string" },
|
|
327
|
+
owner: { type: "string" },
|
|
328
|
+
status: { type: "string", enum: ["draft", "active", "deprecated"] },
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
for (const f of [...required, ...optional]) {
|
|
332
|
+
if (extractsFiles && f === filesField) {
|
|
333
|
+
properties[f] = { type: "array", items: { type: "string" } };
|
|
334
|
+
} else {
|
|
335
|
+
properties[f] = { type: "string" };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const ajvSchema = {
|
|
340
|
+
type: "object",
|
|
341
|
+
properties,
|
|
342
|
+
required: ["type", ...required, "classification", "recorded_at"],
|
|
343
|
+
additionalProperties: false,
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const aliases = cfg.aliases
|
|
347
|
+
? Object.fromEntries(Object.entries(cfg.aliases).map(([k, v]) => [k, [...v]]))
|
|
348
|
+
: undefined;
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
name,
|
|
352
|
+
kind: "custom",
|
|
353
|
+
required,
|
|
354
|
+
optional,
|
|
355
|
+
dedupKey,
|
|
356
|
+
idKey,
|
|
357
|
+
summary: summaryFn,
|
|
358
|
+
extractsFiles,
|
|
359
|
+
filesField,
|
|
360
|
+
compact: compactStrategy,
|
|
361
|
+
sectionTitle,
|
|
362
|
+
ajvSchema,
|
|
363
|
+
...(aliases ? { aliases } : {}),
|
|
364
|
+
formatMarkdown: (records, full) => {
|
|
365
|
+
if (records.length === 0) return "";
|
|
366
|
+
const lines = [`### ${sectionTitle}`];
|
|
367
|
+
for (const rec of records) {
|
|
368
|
+
const r = rec as unknown as Record<string, unknown>;
|
|
369
|
+
let line = `- ${idTag(rec)}**${summaryFn(rec)}**`;
|
|
370
|
+
const detail: string[] = [];
|
|
371
|
+
for (const f of [...required, ...optional]) {
|
|
372
|
+
if (extractsFiles && f === filesField) continue;
|
|
373
|
+
const v = r[f];
|
|
374
|
+
if (v == null || v === "") continue;
|
|
375
|
+
detail.push(`${f}: ${String(v)}`);
|
|
376
|
+
}
|
|
377
|
+
if (detail.length > 0) line += ` — ${detail.join("; ")}`;
|
|
378
|
+
if (extractsFiles) {
|
|
379
|
+
const files = r[filesField];
|
|
380
|
+
if (Array.isArray(files) && files.length > 0) {
|
|
381
|
+
line += ` (${(files as string[]).join(", ")})`;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
line += formatRecordMeta(rec, full);
|
|
385
|
+
lines.push(line);
|
|
386
|
+
}
|
|
387
|
+
return lines.join("\n");
|
|
388
|
+
},
|
|
389
|
+
formatCompactLine: (record) => {
|
|
390
|
+
const r = record as unknown as Record<string, unknown>;
|
|
391
|
+
const links = formatLinks(record);
|
|
392
|
+
const meta = compactMeta(record);
|
|
393
|
+
const outcome = formatOutcome(record.outcomes);
|
|
394
|
+
let filesPart = "";
|
|
395
|
+
if (extractsFiles) {
|
|
396
|
+
const files = r[filesField];
|
|
397
|
+
if (Array.isArray(files) && files.length > 0) {
|
|
398
|
+
filesPart = ` (${(files as string[]).join(", ")})`;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return `- [${name}] ${truncate(summaryFn(record))}${filesPart}${meta}${outcome}${links}`;
|
|
402
|
+
},
|
|
403
|
+
formatXml: (record) => {
|
|
404
|
+
const r = record as unknown as Record<string, unknown>;
|
|
405
|
+
const lines: string[] = [];
|
|
406
|
+
for (const f of [...required, ...optional]) {
|
|
407
|
+
const v = r[f];
|
|
408
|
+
if (v == null) continue;
|
|
409
|
+
if (Array.isArray(v)) {
|
|
410
|
+
lines.push(
|
|
411
|
+
` <${f}>${(v as string[]).map((s) => xmlEscape(String(s))).join(", ")}</${f}>`,
|
|
412
|
+
);
|
|
413
|
+
} else {
|
|
414
|
+
lines.push(` <${f}>${xmlEscape(String(v))}</${f}>`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return lines;
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export function buildCustomTypeDefinitions(
|
|
423
|
+
customTypes: Record<string, CustomTypeConfig> | undefined,
|
|
424
|
+
): TypeDefinition[] {
|
|
425
|
+
if (!customTypes) return [];
|
|
426
|
+
const defs: TypeDefinition[] = [];
|
|
427
|
+
for (const [name, cfg] of Object.entries(customTypes)) {
|
|
428
|
+
defs.push(buildCustomTypeDefinition(name, cfg));
|
|
429
|
+
}
|
|
430
|
+
return defs;
|
|
431
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { LoamConfig } from "../schemas/config.ts";
|
|
2
|
+
import { readConfig } from "../utils/config.ts";
|
|
3
|
+
import { BUILTIN_DEFS, buildBuiltinRegistry } from "./builtins.ts";
|
|
4
|
+
import { buildCustomTypeDefinitions } from "./custom.ts";
|
|
5
|
+
import {
|
|
6
|
+
setRegistry,
|
|
7
|
+
type TypeRegistry,
|
|
8
|
+
TypeRegistry as TypeRegistryCtor,
|
|
9
|
+
} from "./type-registry.ts";
|
|
10
|
+
|
|
11
|
+
// Hoist SHARED_DEFINITIONS through buildBuiltinRegistry to keep one source of
|
|
12
|
+
// truth. We construct a temporary builtin registry just to read its definitions.
|
|
13
|
+
function buildRegistryWithCustomTypes(config: LoamConfig | null): TypeRegistry {
|
|
14
|
+
const disabled = config?.disabled_types ?? [];
|
|
15
|
+
if (config?.custom_types && disabled.length > 0) {
|
|
16
|
+
const disabledSet = new Set(disabled);
|
|
17
|
+
for (const [childName, childCfg] of Object.entries(config.custom_types)) {
|
|
18
|
+
if (childCfg.extends && disabledSet.has(childCfg.extends)) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`Custom type "${childName}" extends "${childCfg.extends}", which is in disabled_types. Re-enable the parent or remove the extends clause.`,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const customDefs = config?.custom_types ? buildCustomTypeDefinitions(config.custom_types) : [];
|
|
27
|
+
const allDefs = customDefs.length === 0 ? [...BUILTIN_DEFS] : [...BUILTIN_DEFS, ...customDefs];
|
|
28
|
+
const knownNames = new Set(allDefs.map((d) => d.name));
|
|
29
|
+
|
|
30
|
+
for (const name of disabled) {
|
|
31
|
+
if (!knownNames.has(name)) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`disabled_types references unregistered type "${name}". Declare it under custom_types or remove it from disabled_types.`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const builtinRegistry = buildBuiltinRegistry();
|
|
39
|
+
return new TypeRegistryCtor(allDefs, builtinRegistry.definitions, disabled);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Called once at CLI startup. Falls back to built-ins-only if no config exists
|
|
43
|
+
// (e.g., before `lm init`) so commands like `lm init` still work.
|
|
44
|
+
export async function initRegistryFromConfig(cwd?: string): Promise<TypeRegistry> {
|
|
45
|
+
let config: LoamConfig | null = null;
|
|
46
|
+
try {
|
|
47
|
+
config = await readConfig(cwd);
|
|
48
|
+
} catch {
|
|
49
|
+
// No .loam/ directory or unreadable config — built-ins only.
|
|
50
|
+
config = null;
|
|
51
|
+
}
|
|
52
|
+
const registry = buildRegistryWithCustomTypes(config);
|
|
53
|
+
setRegistry(registry);
|
|
54
|
+
return registry;
|
|
55
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ExpertiseRecord } from "../schemas/record.ts";
|
|
2
|
+
|
|
3
|
+
// Interpolation only — no conditionals, fallbacks, or transforms in v1 (per
|
|
4
|
+
// epic loam-632e locked design). Both `{field}` and `{{field}}` are accepted
|
|
5
|
+
// because Mustache-style is muscle memory; the double-brace branch is matched
|
|
6
|
+
// first so it's preferred over the single-brace fallback.
|
|
7
|
+
const TOKEN_RE = /\{\{([a-z_][a-z0-9_]*)\}\}|\{([a-z_][a-z0-9_]*)\}/gi;
|
|
8
|
+
|
|
9
|
+
export function extractTemplateTokens(template: string): string[] {
|
|
10
|
+
const tokens: string[] = [];
|
|
11
|
+
for (const match of template.matchAll(TOKEN_RE)) {
|
|
12
|
+
const token = match[1] ?? match[2];
|
|
13
|
+
if (token) tokens.push(token);
|
|
14
|
+
}
|
|
15
|
+
return tokens;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function compileSummaryTemplate(template: string): (record: ExpertiseRecord) => string {
|
|
19
|
+
const tokens: string[] = [];
|
|
20
|
+
const literals: string[] = [];
|
|
21
|
+
let lastIndex = 0;
|
|
22
|
+
for (const match of template.matchAll(TOKEN_RE)) {
|
|
23
|
+
const start = match.index ?? 0;
|
|
24
|
+
literals.push(template.slice(lastIndex, start));
|
|
25
|
+
tokens.push(match[1] ?? match[2] ?? "");
|
|
26
|
+
lastIndex = start + match[0].length;
|
|
27
|
+
}
|
|
28
|
+
literals.push(template.slice(lastIndex));
|
|
29
|
+
|
|
30
|
+
return (record: ExpertiseRecord): string => {
|
|
31
|
+
let out = literals[0] ?? "";
|
|
32
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
33
|
+
const token = tokens[i] ?? "";
|
|
34
|
+
const value = (record as unknown as Record<string, unknown>)[token];
|
|
35
|
+
out += value == null ? "" : String(value);
|
|
36
|
+
out += literals[i + 1] ?? "";
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import Ajv, { type ValidateFunction } from "ajv";
|
|
2
|
+
import type { ExpertiseRecord } from "../schemas/record.ts";
|
|
3
|
+
import { buildBuiltinRegistry } from "./builtins.ts";
|
|
4
|
+
|
|
5
|
+
export interface TypeDefinition {
|
|
6
|
+
name: string;
|
|
7
|
+
kind: "builtin" | "custom";
|
|
8
|
+
required: readonly string[];
|
|
9
|
+
optional: readonly string[];
|
|
10
|
+
dedupKey: string | "content_hash";
|
|
11
|
+
idKey: string;
|
|
12
|
+
summary: (record: ExpertiseRecord) => string;
|
|
13
|
+
extractsFiles: boolean;
|
|
14
|
+
filesField: string;
|
|
15
|
+
compact: "keep_latest" | "concat" | "merge_outcomes" | "manual";
|
|
16
|
+
sectionTitle: string;
|
|
17
|
+
ajvSchema: Record<string, unknown>;
|
|
18
|
+
formatMarkdown: (records: ExpertiseRecord[], full: boolean) => string;
|
|
19
|
+
formatCompactLine: (record: ExpertiseRecord) => string;
|
|
20
|
+
formatXml: (record: ExpertiseRecord) => string[];
|
|
21
|
+
// Phase 3: canonical field name → legacy aliases. Only set on custom types.
|
|
22
|
+
aliases?: Readonly<Record<string, readonly string[]>>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface SharedDefinitions {
|
|
26
|
+
classification: Record<string, unknown>;
|
|
27
|
+
evidence: Record<string, unknown>;
|
|
28
|
+
outcome: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class TypeRegistry {
|
|
32
|
+
readonly definitions: SharedDefinitions;
|
|
33
|
+
private readonly defs: ReadonlyMap<string, TypeDefinition>;
|
|
34
|
+
private readonly order: readonly string[];
|
|
35
|
+
private readonly disabledSet: ReadonlySet<string>;
|
|
36
|
+
readonly validator: ValidateFunction;
|
|
37
|
+
|
|
38
|
+
constructor(
|
|
39
|
+
defs: TypeDefinition[],
|
|
40
|
+
definitions: SharedDefinitions,
|
|
41
|
+
disabled: Iterable<string> = [],
|
|
42
|
+
) {
|
|
43
|
+
this.defs = new Map(defs.map((d) => [d.name, d]));
|
|
44
|
+
this.order = defs.map((d) => d.name);
|
|
45
|
+
this.disabledSet = new Set(disabled);
|
|
46
|
+
this.definitions = definitions;
|
|
47
|
+
this.validator = compileValidator(defs, definitions);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get(name: string): TypeDefinition | undefined {
|
|
51
|
+
return this.defs.get(name);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
enabled(): TypeDefinition[] {
|
|
55
|
+
return this.order.map((n) => this.defs.get(n) as TypeDefinition);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
names(): string[] {
|
|
59
|
+
return [...this.order];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
isDisabled(name: string): boolean {
|
|
63
|
+
return this.disabledSet.has(name);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
disabledNames(): string[] {
|
|
67
|
+
return [...this.disabledSet];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
builtinDefs(): TypeDefinition[] {
|
|
71
|
+
return this.enabled().filter((d) => d.kind === "builtin");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
customDefs(): TypeDefinition[] {
|
|
75
|
+
return this.enabled().filter((d) => d.kind === "custom");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function compileValidator(
|
|
80
|
+
defs: TypeDefinition[],
|
|
81
|
+
definitions: SharedDefinitions,
|
|
82
|
+
): ValidateFunction {
|
|
83
|
+
const schema = {
|
|
84
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
85
|
+
title: "Loam Expertise Record",
|
|
86
|
+
description: "A single expertise record in a Loam domain file",
|
|
87
|
+
type: "object",
|
|
88
|
+
definitions,
|
|
89
|
+
oneOf: defs.map((d) => d.ajvSchema),
|
|
90
|
+
};
|
|
91
|
+
const ajv = new Ajv();
|
|
92
|
+
return ajv.compile(schema);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let _registry: TypeRegistry | null = null;
|
|
96
|
+
|
|
97
|
+
// Lazy fallback to a built-in registry so callers (api.ts, tests) that don't
|
|
98
|
+
// explicitly initialize still get a working registry. CLI/entry points may
|
|
99
|
+
// call setRegistry() to override (e.g., Phase 2's config-derived custom types).
|
|
100
|
+
export function getRegistry(): TypeRegistry {
|
|
101
|
+
if (!_registry) {
|
|
102
|
+
_registry = buildBuiltinRegistry();
|
|
103
|
+
}
|
|
104
|
+
return _registry;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function setRegistry(registry: TypeRegistry): void {
|
|
108
|
+
_registry = registry;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function resetRegistry(): void {
|
|
112
|
+
_registry = null;
|
|
113
|
+
}
|