@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,205 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
export type TrackerName = "sprout" | "gh" | "linear" | "bead";
|
|
6
|
+
|
|
7
|
+
export const TRACKERS: readonly TrackerName[] = ["sprout", "gh", "linear", "bead"];
|
|
8
|
+
|
|
9
|
+
export interface ResolverHit {
|
|
10
|
+
tracker: TrackerName;
|
|
11
|
+
matches: string[];
|
|
12
|
+
source: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type Resolver = (cwd: string) => ResolverHit;
|
|
16
|
+
|
|
17
|
+
export interface ActiveWorkResult {
|
|
18
|
+
sprout?: string;
|
|
19
|
+
gh?: string;
|
|
20
|
+
linear?: string;
|
|
21
|
+
bead?: string;
|
|
22
|
+
warnings: string[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ActiveWorkOptions {
|
|
26
|
+
cwd?: string;
|
|
27
|
+
resolvers?: Resolver[];
|
|
28
|
+
// Explicit overrides (e.g. from `--evidence-sprout`). When set, the resolver
|
|
29
|
+
// chain for that tracker is short-circuited and the override is used as-is.
|
|
30
|
+
// Empty string is treated as "not set".
|
|
31
|
+
overrides?: Partial<Record<TrackerName, string | undefined>>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getCurrentBranch(cwd: string): string {
|
|
35
|
+
try {
|
|
36
|
+
const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
37
|
+
cwd,
|
|
38
|
+
encoding: "utf-8",
|
|
39
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
40
|
+
}).trim();
|
|
41
|
+
return branch === "HEAD" ? "" : branch;
|
|
42
|
+
} catch {
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function readJsonl<T>(path: string): T[] {
|
|
48
|
+
if (!existsSync(path)) return [];
|
|
49
|
+
let raw: string;
|
|
50
|
+
try {
|
|
51
|
+
raw = readFileSync(path, "utf-8");
|
|
52
|
+
} catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
const out: T[] = [];
|
|
56
|
+
for (const line of raw.split("\n")) {
|
|
57
|
+
const trimmed = line.trim();
|
|
58
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
59
|
+
try {
|
|
60
|
+
out.push(JSON.parse(trimmed) as T);
|
|
61
|
+
} catch {
|
|
62
|
+
// skip malformed lines — caller may have other valid rows
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Sprout: <project>-<4–8 hex>. e.g. "loam-f3d0", "sprout-a051".
|
|
69
|
+
const SPROUT_ID_RE = /\b([a-z][a-z0-9]*-[a-f0-9]{4,8})\b/g;
|
|
70
|
+
// Linear: <UPPER>-<digits>. e.g. "WEB-123", "ENG-4567". Two+ leading uppercase
|
|
71
|
+
// chars so single-letter prefixes don't collide with branch nicknames.
|
|
72
|
+
const LINEAR_ID_RE = /\b([A-Z]{2,}[A-Z0-9]*-\d+)\b/g;
|
|
73
|
+
// Bead: bd-<digits> or bead-<digits>.
|
|
74
|
+
const BEAD_ID_RE = /\b((?:bd|bead)-\d+)\b/gi;
|
|
75
|
+
// GH: gh-<digits> or #<digits>.
|
|
76
|
+
const GH_ID_RE = /(?:^|[^a-zA-Z0-9])(?:gh-|#)(\d+)\b/gi;
|
|
77
|
+
|
|
78
|
+
function matchAll(re: RegExp, text: string, group = 1): string[] {
|
|
79
|
+
const out = new Set<string>();
|
|
80
|
+
for (const m of text.matchAll(re)) {
|
|
81
|
+
const val = m[group];
|
|
82
|
+
if (val) out.add(val);
|
|
83
|
+
}
|
|
84
|
+
return [...out].sort();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const sproutResolver: Resolver = (cwd) => {
|
|
88
|
+
type SproutIssue = { id?: unknown; status?: unknown };
|
|
89
|
+
const issues = readJsonl<SproutIssue>(join(cwd, ".sprout", "issues.jsonl"));
|
|
90
|
+
const inProgress: string[] = [];
|
|
91
|
+
for (const i of issues) {
|
|
92
|
+
if (i.status === "in_progress" && typeof i.id === "string") inProgress.push(i.id);
|
|
93
|
+
}
|
|
94
|
+
if (inProgress.length > 0) {
|
|
95
|
+
return {
|
|
96
|
+
tracker: "sprout",
|
|
97
|
+
matches: [...new Set(inProgress)].sort(),
|
|
98
|
+
source: "in_progress",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const branch = getCurrentBranch(cwd);
|
|
102
|
+
const branchMatches = matchAll(SPROUT_ID_RE, branch);
|
|
103
|
+
if (branchMatches.length > 0) {
|
|
104
|
+
return { tracker: "sprout", matches: branchMatches, source: "branch" };
|
|
105
|
+
}
|
|
106
|
+
return { tracker: "sprout", matches: [], source: "none" };
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const ghResolver: Resolver = (cwd) => {
|
|
110
|
+
const branch = getCurrentBranch(cwd);
|
|
111
|
+
if (branch) {
|
|
112
|
+
try {
|
|
113
|
+
const out = execFileSync("gh", ["pr", "view", "--json", "number", "--jq", ".number"], {
|
|
114
|
+
cwd,
|
|
115
|
+
encoding: "utf-8",
|
|
116
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
117
|
+
}).trim();
|
|
118
|
+
if (out && /^\d+$/.test(out)) {
|
|
119
|
+
return { tracker: "gh", matches: [`#${out}`], source: "gh-cli" };
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
// gh missing, unauthenticated, or no PR linked — fall through
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const branchMatches = matchAll(GH_ID_RE, branch).map((n) => `#${n}`);
|
|
126
|
+
if (branchMatches.length > 0) {
|
|
127
|
+
return { tracker: "gh", matches: branchMatches, source: "branch" };
|
|
128
|
+
}
|
|
129
|
+
return { tracker: "gh", matches: [], source: "none" };
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export const linearResolver: Resolver = (cwd) => {
|
|
133
|
+
const branch = getCurrentBranch(cwd);
|
|
134
|
+
const branchMatches = matchAll(LINEAR_ID_RE, branch);
|
|
135
|
+
if (branchMatches.length > 0) {
|
|
136
|
+
return { tracker: "linear", matches: branchMatches, source: "branch" };
|
|
137
|
+
}
|
|
138
|
+
return { tracker: "linear", matches: [], source: "none" };
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export const beadResolver: Resolver = (cwd) => {
|
|
142
|
+
type BeadIssue = { id?: unknown; status?: unknown };
|
|
143
|
+
const issues = readJsonl<BeadIssue>(join(cwd, ".beads", "issues.jsonl"));
|
|
144
|
+
const inProgress: string[] = [];
|
|
145
|
+
for (const i of issues) {
|
|
146
|
+
if (i.status === "in_progress" && typeof i.id === "string") inProgress.push(i.id);
|
|
147
|
+
}
|
|
148
|
+
if (inProgress.length > 0) {
|
|
149
|
+
return {
|
|
150
|
+
tracker: "bead",
|
|
151
|
+
matches: [...new Set(inProgress)].sort(),
|
|
152
|
+
source: "in_progress",
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const branch = getCurrentBranch(cwd);
|
|
156
|
+
const branchMatches = matchAll(BEAD_ID_RE, branch).map((s) => s.toLowerCase());
|
|
157
|
+
if (branchMatches.length > 0) {
|
|
158
|
+
return {
|
|
159
|
+
tracker: "bead",
|
|
160
|
+
matches: [...new Set(branchMatches)].sort(),
|
|
161
|
+
source: "branch",
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return { tracker: "bead", matches: [], source: "none" };
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export const DEFAULT_RESOLVERS: Resolver[] = [
|
|
168
|
+
sproutResolver,
|
|
169
|
+
ghResolver,
|
|
170
|
+
linearResolver,
|
|
171
|
+
beadResolver,
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
export function resolveActiveWork(options: ActiveWorkOptions = {}): ActiveWorkResult {
|
|
175
|
+
const cwd = options.cwd ?? process.cwd();
|
|
176
|
+
const resolvers = options.resolvers ?? DEFAULT_RESOLVERS;
|
|
177
|
+
const overrides = options.overrides ?? {};
|
|
178
|
+
const result: ActiveWorkResult = { warnings: [] };
|
|
179
|
+
|
|
180
|
+
const hitsByTracker = new Map<TrackerName, ResolverHit>();
|
|
181
|
+
for (const resolver of resolvers) {
|
|
182
|
+
const hit = resolver(cwd);
|
|
183
|
+
// First resolver to claim a tracker wins; later ones for the same tracker
|
|
184
|
+
// are ignored so callers can prepend a custom resolver to override.
|
|
185
|
+
if (!hitsByTracker.has(hit.tracker)) hitsByTracker.set(hit.tracker, hit);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
for (const tracker of TRACKERS) {
|
|
189
|
+
const override = overrides[tracker];
|
|
190
|
+
if (typeof override === "string" && override !== "") {
|
|
191
|
+
result[tracker] = override;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const hit = hitsByTracker.get(tracker);
|
|
195
|
+
if (!hit) continue;
|
|
196
|
+
if (hit.matches.length === 1) {
|
|
197
|
+
result[tracker] = hit.matches[0];
|
|
198
|
+
} else if (hit.matches.length > 1) {
|
|
199
|
+
result.warnings.push(
|
|
200
|
+
`active-work: multiple ${tracker} candidates [${hit.matches.join(", ")}] from ${hit.source}; pass --evidence-${tracker} <id> to disambiguate`,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import type { ExpertiseRecord } from "../schemas/record.ts";
|
|
4
|
+
|
|
5
|
+
export type AnchorKind = "file" | "dir" | "evidence_file";
|
|
6
|
+
|
|
7
|
+
export interface RecordAnchor {
|
|
8
|
+
kind: AnchorKind;
|
|
9
|
+
path: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface AnchorValidity {
|
|
13
|
+
total: number;
|
|
14
|
+
valid: number;
|
|
15
|
+
broken: RecordAnchor[];
|
|
16
|
+
// `null` when the record has zero anchors — callers should treat that as
|
|
17
|
+
// "applies globally, no decay signal" rather than 100% valid (a 0/0 record
|
|
18
|
+
// would otherwise compute NaN and falsely trigger the threshold).
|
|
19
|
+
validFraction: number | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Enumerate every filesystem anchor a record carries: the typed `files[]`
|
|
24
|
+
* (PatternRecord/ReferenceRecord), `dir_anchors[]` (any record), and
|
|
25
|
+
* `evidence.file` (single string). Returns canonical kind+path pairs so
|
|
26
|
+
* `--explain` output can attribute each broken entry.
|
|
27
|
+
*/
|
|
28
|
+
export function getRecordAnchors(record: ExpertiseRecord): RecordAnchor[] {
|
|
29
|
+
const anchors: RecordAnchor[] = [];
|
|
30
|
+
if ("files" in record && Array.isArray(record.files)) {
|
|
31
|
+
for (const f of record.files) {
|
|
32
|
+
if (typeof f === "string" && f.length > 0) {
|
|
33
|
+
anchors.push({ kind: "file", path: f });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(record.dir_anchors)) {
|
|
38
|
+
for (const d of record.dir_anchors) {
|
|
39
|
+
if (typeof d === "string" && d.length > 0) {
|
|
40
|
+
anchors.push({ kind: "dir", path: d });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (record.evidence?.file && typeof record.evidence.file === "string") {
|
|
45
|
+
anchors.push({ kind: "evidence_file", path: record.evidence.file });
|
|
46
|
+
}
|
|
47
|
+
return anchors;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function computeAnchorValidity(
|
|
51
|
+
record: ExpertiseRecord,
|
|
52
|
+
projectRoot: string,
|
|
53
|
+
): AnchorValidity {
|
|
54
|
+
const anchors = getRecordAnchors(record);
|
|
55
|
+
const total = anchors.length;
|
|
56
|
+
if (total === 0) {
|
|
57
|
+
return { total: 0, valid: 0, broken: [], validFraction: null };
|
|
58
|
+
}
|
|
59
|
+
const broken: RecordAnchor[] = [];
|
|
60
|
+
let valid = 0;
|
|
61
|
+
for (const a of anchors) {
|
|
62
|
+
if (existsSync(resolve(projectRoot, a.path))) {
|
|
63
|
+
valid++;
|
|
64
|
+
} else {
|
|
65
|
+
broken.push(a);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { total, valid, broken, validFraction: valid / total };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* True when the record is older than the configured grace period, so we
|
|
73
|
+
* shouldn't punish anchor decay on a record that was just written (e.g., a
|
|
74
|
+
* file moved/created between `lm record` and the next `lm prune`).
|
|
75
|
+
*/
|
|
76
|
+
export function passedAnchorGrace(record: ExpertiseRecord, now: Date, graceDays: number): boolean {
|
|
77
|
+
const recordedAt = new Date(record.recorded_at);
|
|
78
|
+
const ageDays = (now.getTime() - recordedAt.getTime()) / (1000 * 60 * 60 * 24);
|
|
79
|
+
return ageDays > graceDays;
|
|
80
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { mkdir, rename, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import type { ExpertiseRecord } from "../schemas/record.ts";
|
|
5
|
+
import { getLoamDir, validateDomainName } from "./config.ts";
|
|
6
|
+
import { readExpertiseFile, writeExpertiseFile } from "./expertise.ts";
|
|
7
|
+
import { withFileLock } from "./lock.ts";
|
|
8
|
+
|
|
9
|
+
const ARCHIVE_DIR = "archive";
|
|
10
|
+
|
|
11
|
+
export const ARCHIVE_BANNER = "# ARCHIVED — not for active use. Run `lm restore <id>` to revive.";
|
|
12
|
+
|
|
13
|
+
export function getArchiveDir(cwd: string = process.cwd()): string {
|
|
14
|
+
return join(getLoamDir(cwd), ARCHIVE_DIR);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getArchivePath(domain: string, cwd: string = process.cwd()): string {
|
|
18
|
+
validateDomainName(domain);
|
|
19
|
+
return join(getArchiveDir(cwd), `${domain}.jsonl`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Read archived records from an archive file. The file's leading banner
|
|
24
|
+
* comment is stripped by readExpertiseFile (which skips `#`-prefixed lines).
|
|
25
|
+
*/
|
|
26
|
+
export async function readArchiveFile(filePath: string): Promise<ExpertiseRecord[]> {
|
|
27
|
+
return readExpertiseFile(filePath, { allowUnknownTypes: true });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Write archive records back, prefixed with the standard banner. Uses the
|
|
32
|
+
* same temp-file + rename atomic pattern as writeExpertiseFile.
|
|
33
|
+
*/
|
|
34
|
+
export async function writeArchiveFile(
|
|
35
|
+
filePath: string,
|
|
36
|
+
records: ExpertiseRecord[],
|
|
37
|
+
): Promise<void> {
|
|
38
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
39
|
+
const body = records.map((r) => JSON.stringify(r)).join("\n");
|
|
40
|
+
const content = `${ARCHIVE_BANNER}\n${body}${records.length > 0 ? "\n" : ""}`;
|
|
41
|
+
const tmpPath = `${filePath}.tmp.${randomBytes(8).toString("hex")}`;
|
|
42
|
+
await writeFile(tmpPath, content, "utf-8");
|
|
43
|
+
try {
|
|
44
|
+
await rename(tmpPath, filePath);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
try {
|
|
47
|
+
await unlink(tmpPath);
|
|
48
|
+
} catch {
|
|
49
|
+
/* best-effort cleanup */
|
|
50
|
+
}
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Move records from a live expertise file to the corresponding archive file.
|
|
57
|
+
* Each archived record gets `status: "archived"`, an `archived_at` timestamp,
|
|
58
|
+
* and an `archive_reason` (use values from `ArchiveReason` as a convention;
|
|
59
|
+
* free-text suffix permitted, e.g. "manual: wrong domain"). A pre-existing
|
|
60
|
+
* `archive_reason` on the record (e.g. from a re-archive on restore rollback)
|
|
61
|
+
* is preserved. Locks the archive file during the merge+write; the caller is
|
|
62
|
+
* responsible for locking the source expertise file before invoking.
|
|
63
|
+
*/
|
|
64
|
+
export async function archiveRecords(
|
|
65
|
+
domain: string,
|
|
66
|
+
records: ExpertiseRecord[],
|
|
67
|
+
now: Date,
|
|
68
|
+
reason: string,
|
|
69
|
+
cwd: string = process.cwd(),
|
|
70
|
+
): Promise<void> {
|
|
71
|
+
if (records.length === 0) return;
|
|
72
|
+
const archivePath = getArchivePath(domain, cwd);
|
|
73
|
+
const stamped = records.map((r) => ({
|
|
74
|
+
...r,
|
|
75
|
+
status: "archived" as const,
|
|
76
|
+
archived_at: r.archived_at ?? now.toISOString(),
|
|
77
|
+
archive_reason: r.archive_reason ?? reason,
|
|
78
|
+
}));
|
|
79
|
+
await mkdir(dirname(archivePath), { recursive: true });
|
|
80
|
+
await withFileLock(archivePath, async () => {
|
|
81
|
+
const existing = await readArchiveFile(archivePath);
|
|
82
|
+
await writeArchiveFile(archivePath, [...existing, ...stamped]);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Canonical archive_reason values. Free-text suffix permitted via raw string;
|
|
88
|
+
* the union is just the convention for the five known code paths.
|
|
89
|
+
*/
|
|
90
|
+
export type ArchiveReason = "stale" | "superseded" | "anchor_decay" | "manual" | "compacted";
|
|
91
|
+
|
|
92
|
+
/** Strip soft-archive lifecycle fields. Used when restoring a record to live. */
|
|
93
|
+
export function stripArchiveFields(record: ExpertiseRecord): ExpertiseRecord {
|
|
94
|
+
const { status, archived_at, archive_reason, ...rest } = record as ExpertiseRecord & {
|
|
95
|
+
status?: unknown;
|
|
96
|
+
archived_at?: unknown;
|
|
97
|
+
archive_reason?: unknown;
|
|
98
|
+
};
|
|
99
|
+
void status;
|
|
100
|
+
void archived_at;
|
|
101
|
+
void archive_reason;
|
|
102
|
+
return rest as ExpertiseRecord;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Remove a record from an archive file by id (mutates archive on disk under a
|
|
107
|
+
* lock). Returns the removed record, or null if not found.
|
|
108
|
+
*/
|
|
109
|
+
export async function removeFromArchive(
|
|
110
|
+
domain: string,
|
|
111
|
+
recordId: string,
|
|
112
|
+
cwd: string = process.cwd(),
|
|
113
|
+
): Promise<ExpertiseRecord | null> {
|
|
114
|
+
const archivePath = getArchivePath(domain, cwd);
|
|
115
|
+
return withFileLock(archivePath, async () => {
|
|
116
|
+
const records = await readArchiveFile(archivePath);
|
|
117
|
+
const idx = records.findIndex((r) => r.id === recordId);
|
|
118
|
+
if (idx === -1) return null;
|
|
119
|
+
const [removed] = records.splice(idx, 1);
|
|
120
|
+
await writeArchiveFile(archivePath, records);
|
|
121
|
+
return removed ?? null;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export type RestoreResult = { ok: true } | { ok: false; conflict: ExpertiseRecord };
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Append a single record to a live expertise file under a lock. Used by
|
|
129
|
+
* `lm restore` to push an archived record back into circulation. Refuses
|
|
130
|
+
* to write when a live record with the same id already exists — restoring
|
|
131
|
+
* over a live id silently created duplicates pre-v0.8.1.
|
|
132
|
+
*/
|
|
133
|
+
export async function restoreToExpertise(
|
|
134
|
+
expertisePath: string,
|
|
135
|
+
record: ExpertiseRecord,
|
|
136
|
+
): Promise<RestoreResult> {
|
|
137
|
+
return withFileLock(expertisePath, async () => {
|
|
138
|
+
const existing = await readExpertiseFile(expertisePath);
|
|
139
|
+
if (record.id !== undefined) {
|
|
140
|
+
const conflict = existing.find((r) => r.id === record.id);
|
|
141
|
+
if (conflict) return { ok: false, conflict };
|
|
142
|
+
}
|
|
143
|
+
await writeExpertiseFile(expertisePath, [...existing, record]);
|
|
144
|
+
return { ok: true };
|
|
145
|
+
});
|
|
146
|
+
}
|