@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,667 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { basename, join, resolve } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
type AuditConfig,
|
|
6
|
+
type AuditThresholds,
|
|
7
|
+
type LoamConfig,
|
|
8
|
+
resolveAuditThresholds,
|
|
9
|
+
} from "../schemas/config.ts";
|
|
10
|
+
import type { ExpertiseRecord } from "../schemas/record.ts";
|
|
11
|
+
import { TRACKERS, type TrackerName } from "./active-work.ts";
|
|
12
|
+
import { getExpertisePath } from "./config.ts";
|
|
13
|
+
import { isRecordStale, readExpertiseFile } from "./expertise.ts";
|
|
14
|
+
|
|
15
|
+
// Words that indicate a convention encodes a rule or rationale rather than
|
|
16
|
+
// restating code structure. Mirrors the Python prototype's RULE_SIGNALS so
|
|
17
|
+
// existing audit baselines transfer 1:1.
|
|
18
|
+
//
|
|
19
|
+
// Known limitation (V1_PLAN §7): the regex over-counts conventions about
|
|
20
|
+
// Bun-isms ("avoid `process.exit`") and under-counts "we …" phrasings.
|
|
21
|
+
// Threshold defaults assume this skew.
|
|
22
|
+
export const RULE_SIGNAL_WORDS = [
|
|
23
|
+
"because",
|
|
24
|
+
"must not",
|
|
25
|
+
"do not",
|
|
26
|
+
"don't",
|
|
27
|
+
"avoid",
|
|
28
|
+
"always ",
|
|
29
|
+
"never ",
|
|
30
|
+
"prefer",
|
|
31
|
+
"required for",
|
|
32
|
+
"reason:",
|
|
33
|
+
"rationale",
|
|
34
|
+
"so that",
|
|
35
|
+
"otherwise",
|
|
36
|
+
] as const;
|
|
37
|
+
|
|
38
|
+
export function hasRuleSignal(text: string | undefined): boolean {
|
|
39
|
+
if (!text) return false;
|
|
40
|
+
const t = text.toLowerCase();
|
|
41
|
+
return RULE_SIGNAL_WORDS.some((k) => t.includes(k));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type Verdict = "PASS" | "WARN" | "FAIL";
|
|
45
|
+
|
|
46
|
+
export interface MetricVerdict {
|
|
47
|
+
verdict: Verdict;
|
|
48
|
+
value: number;
|
|
49
|
+
threshold: number;
|
|
50
|
+
warn_threshold?: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface DomainMix {
|
|
54
|
+
domain: string;
|
|
55
|
+
total: number;
|
|
56
|
+
type_counts: Record<string, number>;
|
|
57
|
+
high_value_pct: number; // (failure + decision) / total * 100
|
|
58
|
+
first_recorded_at: string | null;
|
|
59
|
+
last_recorded_at: string | null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface TrackerCitation {
|
|
63
|
+
id: string;
|
|
64
|
+
tracker: TrackerName;
|
|
65
|
+
count: number;
|
|
66
|
+
status: string;
|
|
67
|
+
title: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface AuditReport {
|
|
71
|
+
repo: string;
|
|
72
|
+
total_records: number;
|
|
73
|
+
domains: string[];
|
|
74
|
+
ignored_domains: string[];
|
|
75
|
+
type_mix: Array<{ type: string; count: number; pct: number }>;
|
|
76
|
+
evidence: {
|
|
77
|
+
// Deprecated since v0.10.1 (loam-1334) — populated from `with_tracker.sprout`.
|
|
78
|
+
// Kept readable for one release so old JSON consumers don't break. Removed in v0.11.
|
|
79
|
+
with_sprout: number;
|
|
80
|
+
with_tracker: Record<TrackerName, number>;
|
|
81
|
+
with_any_tracker: number;
|
|
82
|
+
with_commit: number;
|
|
83
|
+
with_relates: number;
|
|
84
|
+
floaters: number;
|
|
85
|
+
};
|
|
86
|
+
convention_quality: {
|
|
87
|
+
total: number;
|
|
88
|
+
with_rule_signal: number;
|
|
89
|
+
likely_code_restatement: number;
|
|
90
|
+
} | null;
|
|
91
|
+
// Deprecated since v0.10.1 (loam-1334) — populated from sprout entries in
|
|
92
|
+
// `tracker_citations` for back-compat. Removed in v0.11. Read `tracker_citations`.
|
|
93
|
+
seed_citations: {
|
|
94
|
+
unique: number;
|
|
95
|
+
status_counts: Record<string, number>;
|
|
96
|
+
missing_in_sprout: number;
|
|
97
|
+
top_cited: Array<{ id: string; count: number; status: string; title: string }>;
|
|
98
|
+
};
|
|
99
|
+
tracker_citations: {
|
|
100
|
+
unique: number;
|
|
101
|
+
status_counts: Record<string, number>;
|
|
102
|
+
missing_in_index: number;
|
|
103
|
+
top_cited: TrackerCitation[];
|
|
104
|
+
};
|
|
105
|
+
by_domain: DomainMix[];
|
|
106
|
+
weak_domains: string[];
|
|
107
|
+
thresholds: Required<AuditThresholds>;
|
|
108
|
+
per_domain_thresholds: Record<string, Required<AuditThresholds>>;
|
|
109
|
+
signals: {
|
|
110
|
+
evidence_coverage: MetricVerdict;
|
|
111
|
+
rule_density: MetricVerdict | null;
|
|
112
|
+
floater_rate: MetricVerdict;
|
|
113
|
+
per_domain_max_records: Array<{ domain: string; count: number; verdict: Verdict }>;
|
|
114
|
+
per_domain_max_stale: Array<{ domain: string; count: number; verdict: Verdict }>;
|
|
115
|
+
};
|
|
116
|
+
failures: string[];
|
|
117
|
+
warnings: string[];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface AuditOptions {
|
|
121
|
+
cwd?: string;
|
|
122
|
+
domain?: string;
|
|
123
|
+
ignoreDomains?: string[];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
interface RecordWithDomain {
|
|
127
|
+
record: ExpertiseRecord;
|
|
128
|
+
domain: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface TrackerRef {
|
|
132
|
+
tracker: TrackerName;
|
|
133
|
+
id: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function extractTrackerRefs(record: ExpertiseRecord): TrackerRef[] {
|
|
137
|
+
const ev = record.evidence;
|
|
138
|
+
if (!ev) return [];
|
|
139
|
+
const refs: TrackerRef[] = [];
|
|
140
|
+
for (const tracker of TRACKERS) {
|
|
141
|
+
const value = ev[tracker];
|
|
142
|
+
if (typeof value === "string" && value.length > 0) {
|
|
143
|
+
refs.push({ tracker, id: value });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return refs;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function recordHasTracker(record: ExpertiseRecord): boolean {
|
|
150
|
+
const ev = record.evidence;
|
|
151
|
+
if (!ev) return false;
|
|
152
|
+
return Boolean(ev.sprout || ev.gh || ev.linear || ev.bead);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function recordIsFloater(record: ExpertiseRecord): boolean {
|
|
156
|
+
const ev = record.evidence;
|
|
157
|
+
if (!ev) {
|
|
158
|
+
return !(record.relates_to && record.relates_to.length > 0);
|
|
159
|
+
}
|
|
160
|
+
if (ev.sprout || ev.gh || ev.linear || ev.bead || ev.commit) return false;
|
|
161
|
+
if (record.relates_to && record.relates_to.length > 0) return false;
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
interface TrackerIndexEntry {
|
|
166
|
+
tracker: TrackerName;
|
|
167
|
+
id: string;
|
|
168
|
+
status?: string;
|
|
169
|
+
title?: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
interface SeedRow {
|
|
173
|
+
id: string;
|
|
174
|
+
status?: string;
|
|
175
|
+
title?: string;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function trackerKey(tracker: TrackerName, id: string): string {
|
|
179
|
+
return `${tracker}:${id}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Render a tracker ref for human-readable output. Sprout keep the bare id
|
|
183
|
+
// (back-compat with the pre-loam-1334 top_cited shape); gh uses the
|
|
184
|
+
// GitHub-native `gh#42` form (incoming `#42` is normalized to `gh#42`);
|
|
185
|
+
// linear and bead use a `<tracker>:<id>` prefix.
|
|
186
|
+
export function formatTrackerId(tracker: TrackerName, id: string): string {
|
|
187
|
+
if (tracker === "sprout") return id;
|
|
188
|
+
if (tracker === "gh") return `gh#${id.replace(/^#/, "")}`;
|
|
189
|
+
return `${tracker}:${id}`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function loadTrackerIndex(cwd: string): Promise<Map<string, TrackerIndexEntry>> {
|
|
193
|
+
const index = new Map<string, TrackerIndexEntry>();
|
|
194
|
+
|
|
195
|
+
// Sprout: read .sprout/issues.jsonl (only tracker with local resolution today).
|
|
196
|
+
const sproutPath = join(cwd, ".sprout", "issues.jsonl");
|
|
197
|
+
if (existsSync(sproutPath)) {
|
|
198
|
+
const content = await readFile(sproutPath, "utf-8");
|
|
199
|
+
for (const line of content.split("\n")) {
|
|
200
|
+
const trimmed = line.trim();
|
|
201
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
202
|
+
try {
|
|
203
|
+
const row = JSON.parse(trimmed) as SeedRow;
|
|
204
|
+
if (row && typeof row.id === "string") {
|
|
205
|
+
index.set(trackerKey("sprout", row.id), {
|
|
206
|
+
tracker: "sprout",
|
|
207
|
+
id: row.id,
|
|
208
|
+
status: row.status,
|
|
209
|
+
title: row.title,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
} catch {
|
|
213
|
+
// Skip malformed sprout rows — the audit is read-only and best-effort here.
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// gh / linear / bead: no local resolution. Citations to these trackers fall
|
|
219
|
+
// through to `unresolved` in status_counts (per loam-1334 acceptance).
|
|
220
|
+
// A future seed can layer in networked status lookups (gh CLI, linear API).
|
|
221
|
+
|
|
222
|
+
return index;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function classifyEvidenceCoverage(
|
|
226
|
+
value: number,
|
|
227
|
+
thresholds: Required<AuditThresholds>,
|
|
228
|
+
): MetricVerdict {
|
|
229
|
+
const verdict: Verdict =
|
|
230
|
+
value >= thresholds.evidence_coverage
|
|
231
|
+
? "PASS"
|
|
232
|
+
: value >= thresholds.evidence_coverage_warn
|
|
233
|
+
? "WARN"
|
|
234
|
+
: "FAIL";
|
|
235
|
+
return {
|
|
236
|
+
verdict,
|
|
237
|
+
value,
|
|
238
|
+
threshold: thresholds.evidence_coverage,
|
|
239
|
+
warn_threshold: thresholds.evidence_coverage_warn,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function classifyRuleDensity(value: number, thresholds: Required<AuditThresholds>): MetricVerdict {
|
|
244
|
+
const verdict: Verdict =
|
|
245
|
+
value >= thresholds.rule_density_min
|
|
246
|
+
? "PASS"
|
|
247
|
+
: value >= thresholds.rule_density_warn
|
|
248
|
+
? "WARN"
|
|
249
|
+
: "FAIL";
|
|
250
|
+
return {
|
|
251
|
+
verdict,
|
|
252
|
+
value,
|
|
253
|
+
threshold: thresholds.rule_density_min,
|
|
254
|
+
warn_threshold: thresholds.rule_density_warn,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function classifyFloaterRate(value: number, thresholds: Required<AuditThresholds>): MetricVerdict {
|
|
259
|
+
// Floater rate: PASS when ≤ floater_max. Above floater_max is WARN; above
|
|
260
|
+
// 2× floater_max is FAIL. The 2× FAIL band is asymmetric on purpose — V1_PLAN
|
|
261
|
+
// expects floaters to drop after attribution gates land in v0.11, so the
|
|
262
|
+
// FAIL band should fire only for corpora that are *flagrantly* unattributed.
|
|
263
|
+
const verdict: Verdict =
|
|
264
|
+
value <= thresholds.floater_max
|
|
265
|
+
? "PASS"
|
|
266
|
+
: value <= thresholds.floater_max * 2
|
|
267
|
+
? "WARN"
|
|
268
|
+
: "FAIL";
|
|
269
|
+
return {
|
|
270
|
+
verdict,
|
|
271
|
+
value,
|
|
272
|
+
threshold: thresholds.floater_max,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function readDomainRecords(cwd: string, domains: string[]): Promise<RecordWithDomain[]> {
|
|
277
|
+
const out: RecordWithDomain[] = [];
|
|
278
|
+
for (const domain of domains) {
|
|
279
|
+
const filePath = getExpertisePath(domain, cwd);
|
|
280
|
+
if (!existsSync(filePath)) continue;
|
|
281
|
+
const records = await readExpertiseFile(filePath, { allowUnknownTypes: true });
|
|
282
|
+
for (const record of records) {
|
|
283
|
+
out.push({ record, domain });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return out;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export async function computeAudit(
|
|
290
|
+
config: LoamConfig,
|
|
291
|
+
options: AuditOptions = {},
|
|
292
|
+
): Promise<AuditReport> {
|
|
293
|
+
const cwd = options.cwd ?? process.cwd();
|
|
294
|
+
const auditCfg: AuditConfig | undefined = config.audit;
|
|
295
|
+
|
|
296
|
+
const allDomains = Object.keys(config.domains);
|
|
297
|
+
const cfgIgnored = new Set(auditCfg?.ignore_domains ?? []);
|
|
298
|
+
const cliIgnored = new Set(options.ignoreDomains ?? []);
|
|
299
|
+
const ignored = [...new Set([...cfgIgnored, ...cliIgnored])];
|
|
300
|
+
|
|
301
|
+
let domains: string[];
|
|
302
|
+
if (options.domain) {
|
|
303
|
+
domains = allDomains.filter((d) => d === options.domain);
|
|
304
|
+
} else {
|
|
305
|
+
domains = allDomains.filter((d) => !cfgIgnored.has(d) && !cliIgnored.has(d));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const repo = basename(resolve(cwd)) || cwd;
|
|
309
|
+
|
|
310
|
+
const items = await readDomainRecords(cwd, domains);
|
|
311
|
+
const records = items.map((i) => i.record);
|
|
312
|
+
const total = records.length;
|
|
313
|
+
|
|
314
|
+
const thresholds = resolveAuditThresholds(auditCfg, options.domain);
|
|
315
|
+
const perDomainThresholds: Record<string, Required<AuditThresholds>> = {};
|
|
316
|
+
for (const d of domains) {
|
|
317
|
+
perDomainThresholds[d] = resolveAuditThresholds(auditCfg, d);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Type mix
|
|
321
|
+
const typeCounts = new Map<string, number>();
|
|
322
|
+
for (const r of records) {
|
|
323
|
+
typeCounts.set(r.type, (typeCounts.get(r.type) ?? 0) + 1);
|
|
324
|
+
}
|
|
325
|
+
const type_mix = Array.from(typeCounts.entries())
|
|
326
|
+
.sort((a, b) => b[1] - a[1])
|
|
327
|
+
.map(([type, count]) => ({
|
|
328
|
+
type,
|
|
329
|
+
count,
|
|
330
|
+
pct: total > 0 ? Math.floor((100 * count) / total) : 0,
|
|
331
|
+
}));
|
|
332
|
+
|
|
333
|
+
// Evidence coverage (per-tracker breakdown + roll-ups)
|
|
334
|
+
const withTracker: Record<TrackerName, number> = { sprout: 0, gh: 0, linear: 0, bead: 0 };
|
|
335
|
+
let withCommit = 0;
|
|
336
|
+
let withRelates = 0;
|
|
337
|
+
let withAnyTracker = 0;
|
|
338
|
+
let floaters = 0;
|
|
339
|
+
for (const r of records) {
|
|
340
|
+
for (const ref of extractTrackerRefs(r)) {
|
|
341
|
+
withTracker[ref.tracker]++;
|
|
342
|
+
}
|
|
343
|
+
if (r.evidence?.commit) withCommit++;
|
|
344
|
+
if (r.relates_to && r.relates_to.length > 0) withRelates++;
|
|
345
|
+
if (recordHasTracker(r) || r.evidence?.commit) withAnyTracker++;
|
|
346
|
+
if (recordIsFloater(r)) floaters++;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Tracker citations (generalized from seed_citations per loam-1334).
|
|
350
|
+
const trackerIndex = await loadTrackerIndex(cwd);
|
|
351
|
+
const refCounts = new Map<string, { tracker: TrackerName; id: string; count: number }>();
|
|
352
|
+
for (const r of records) {
|
|
353
|
+
for (const ref of extractTrackerRefs(r)) {
|
|
354
|
+
const key = trackerKey(ref.tracker, ref.id);
|
|
355
|
+
const existing = refCounts.get(key);
|
|
356
|
+
if (existing) {
|
|
357
|
+
existing.count++;
|
|
358
|
+
} else {
|
|
359
|
+
refCounts.set(key, { tracker: ref.tracker, id: ref.id, count: 1 });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const trackerStatusCounts: Record<string, number> = {};
|
|
364
|
+
let trackerMissing = 0;
|
|
365
|
+
for (const [key, entry] of refCounts.entries()) {
|
|
366
|
+
const hit = trackerIndex.get(key);
|
|
367
|
+
if (hit) {
|
|
368
|
+
const status = hit.status ?? "?";
|
|
369
|
+
trackerStatusCounts[status] = (trackerStatusCounts[status] ?? 0) + 1;
|
|
370
|
+
} else if (entry.tracker === "sprout") {
|
|
371
|
+
// Sprout is the one tracker with a local index — a missing entry means
|
|
372
|
+
// the citation points at a sprout id that isn't in .sprout/issues.jsonl.
|
|
373
|
+
trackerMissing++;
|
|
374
|
+
} else {
|
|
375
|
+
// gh/linear/bead have no local index yet — bucket into "unresolved"
|
|
376
|
+
// rather than "missing" so consumers don't confuse the two.
|
|
377
|
+
trackerStatusCounts.unresolved = (trackerStatusCounts.unresolved ?? 0) + 1;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const trackerTopCited: TrackerCitation[] = Array.from(refCounts.values())
|
|
381
|
+
.sort((a, b) => b.count - a.count)
|
|
382
|
+
.slice(0, 8)
|
|
383
|
+
.map(({ tracker, id, count }) => {
|
|
384
|
+
const hit = trackerIndex.get(trackerKey(tracker, id));
|
|
385
|
+
let status: string;
|
|
386
|
+
if (hit) {
|
|
387
|
+
status = hit.status ?? "?";
|
|
388
|
+
} else if (tracker === "sprout") {
|
|
389
|
+
status = "MISSING";
|
|
390
|
+
} else {
|
|
391
|
+
status = "unresolved";
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
id: formatTrackerId(tracker, id),
|
|
395
|
+
tracker,
|
|
396
|
+
count,
|
|
397
|
+
status,
|
|
398
|
+
title: (hit?.title ?? "").slice(0, 55),
|
|
399
|
+
};
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// Sprout-only back-compat shape (deprecated, populated from tracker_citations).
|
|
403
|
+
// Filter the tracker refs/index to sprout entries so old JSON consumers reading
|
|
404
|
+
// `report.seed_citations` still see exactly what they saw pre-loam-1334.
|
|
405
|
+
const sproutRefEntries = Array.from(refCounts.values()).filter((e) => e.tracker === "sprout");
|
|
406
|
+
const sproutStatusCounts: Record<string, number> = {};
|
|
407
|
+
let sproutMissing = 0;
|
|
408
|
+
for (const { tracker, id } of sproutRefEntries) {
|
|
409
|
+
const hit = trackerIndex.get(trackerKey(tracker, id));
|
|
410
|
+
if (hit) {
|
|
411
|
+
const status = hit.status ?? "?";
|
|
412
|
+
sproutStatusCounts[status] = (sproutStatusCounts[status] ?? 0) + 1;
|
|
413
|
+
} else {
|
|
414
|
+
sproutMissing++;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
const sproutTopCited = sproutRefEntries
|
|
418
|
+
.sort((a, b) => b.count - a.count)
|
|
419
|
+
.slice(0, 8)
|
|
420
|
+
.map(({ id, count }) => {
|
|
421
|
+
const hit = trackerIndex.get(trackerKey("sprout", id));
|
|
422
|
+
return {
|
|
423
|
+
id,
|
|
424
|
+
count,
|
|
425
|
+
status: hit?.status ?? "MISSING",
|
|
426
|
+
title: (hit?.title ?? "MISSING").slice(0, 55),
|
|
427
|
+
};
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// Convention quality
|
|
431
|
+
const conventions = records.filter((r) => r.type === "convention");
|
|
432
|
+
let conv_quality: AuditReport["convention_quality"] = null;
|
|
433
|
+
if (conventions.length > 0) {
|
|
434
|
+
const withSignal = conventions.filter((r) =>
|
|
435
|
+
hasRuleSignal((r as { content?: string }).content),
|
|
436
|
+
).length;
|
|
437
|
+
conv_quality = {
|
|
438
|
+
total: conventions.length,
|
|
439
|
+
with_rule_signal: withSignal,
|
|
440
|
+
likely_code_restatement: conventions.length - withSignal,
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Per-domain mix
|
|
445
|
+
const byDomain = new Map<string, RecordWithDomain[]>();
|
|
446
|
+
for (const item of items) {
|
|
447
|
+
const list = byDomain.get(item.domain) ?? [];
|
|
448
|
+
list.push(item);
|
|
449
|
+
byDomain.set(item.domain, list);
|
|
450
|
+
}
|
|
451
|
+
const by_domain: DomainMix[] = [];
|
|
452
|
+
for (const [domain, group] of byDomain.entries()) {
|
|
453
|
+
const counts: Record<string, number> = {};
|
|
454
|
+
let first: string | null = null;
|
|
455
|
+
let last: string | null = null;
|
|
456
|
+
for (const { record } of group) {
|
|
457
|
+
counts[record.type] = (counts[record.type] ?? 0) + 1;
|
|
458
|
+
const ts = record.recorded_at;
|
|
459
|
+
if (ts) {
|
|
460
|
+
if (!first || ts < first) first = ts;
|
|
461
|
+
if (!last || ts > last) last = ts;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const tot = group.length;
|
|
465
|
+
const hv = (counts.failure ?? 0) + (counts.decision ?? 0);
|
|
466
|
+
by_domain.push({
|
|
467
|
+
domain,
|
|
468
|
+
total: tot,
|
|
469
|
+
type_counts: counts,
|
|
470
|
+
high_value_pct: tot > 0 ? Math.floor((100 * hv) / tot) : 0,
|
|
471
|
+
first_recorded_at: first,
|
|
472
|
+
last_recorded_at: last,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
by_domain.sort((a, b) => b.total - a.total);
|
|
476
|
+
|
|
477
|
+
const weak_domains = by_domain
|
|
478
|
+
.filter((d) => d.total >= 5 && d.high_value_pct < 20)
|
|
479
|
+
.map((d) => d.domain);
|
|
480
|
+
|
|
481
|
+
// Verdicts
|
|
482
|
+
const evidence_coverage_rate = total > 0 ? withAnyTracker / total : 0;
|
|
483
|
+
const floater_rate = total > 0 ? floaters / total : 0;
|
|
484
|
+
const rule_density_rate =
|
|
485
|
+
conv_quality && conv_quality.total > 0 ? conv_quality.with_rule_signal / conv_quality.total : 0;
|
|
486
|
+
|
|
487
|
+
const evidence_coverage = classifyEvidenceCoverage(evidence_coverage_rate, thresholds);
|
|
488
|
+
const floater_rate_verdict = classifyFloaterRate(floater_rate, thresholds);
|
|
489
|
+
const rule_density: MetricVerdict | null = conv_quality
|
|
490
|
+
? classifyRuleDensity(rule_density_rate, thresholds)
|
|
491
|
+
: null;
|
|
492
|
+
|
|
493
|
+
const now = new Date();
|
|
494
|
+
const shelfLife = config.classification_defaults.shelf_life;
|
|
495
|
+
const per_domain_max_records = by_domain.map((d) => {
|
|
496
|
+
const t = perDomainThresholds[d.domain] ?? thresholds;
|
|
497
|
+
return {
|
|
498
|
+
domain: d.domain,
|
|
499
|
+
count: d.total,
|
|
500
|
+
verdict: (d.total <= t.max_records_per_domain ? "PASS" : "WARN") as Verdict,
|
|
501
|
+
};
|
|
502
|
+
});
|
|
503
|
+
const per_domain_max_stale = by_domain.map((d) => {
|
|
504
|
+
const t = perDomainThresholds[d.domain] ?? thresholds;
|
|
505
|
+
const group = byDomain.get(d.domain) ?? [];
|
|
506
|
+
const stale = group.filter((i) => isRecordStale(i.record, now, shelfLife)).length;
|
|
507
|
+
return {
|
|
508
|
+
domain: d.domain,
|
|
509
|
+
count: stale,
|
|
510
|
+
verdict: (stale <= t.max_stale ? "PASS" : "WARN") as Verdict,
|
|
511
|
+
};
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
const failures: string[] = [];
|
|
515
|
+
const warnings: string[] = [];
|
|
516
|
+
const trackVerdict = (label: string, v: MetricVerdict | null) => {
|
|
517
|
+
if (!v) return;
|
|
518
|
+
if (v.verdict === "FAIL") failures.push(label);
|
|
519
|
+
else if (v.verdict === "WARN") warnings.push(label);
|
|
520
|
+
};
|
|
521
|
+
trackVerdict("evidence_coverage", evidence_coverage);
|
|
522
|
+
trackVerdict("rule_density", rule_density);
|
|
523
|
+
trackVerdict("floater_rate", floater_rate_verdict);
|
|
524
|
+
for (const row of per_domain_max_records) {
|
|
525
|
+
if (row.verdict === "WARN") warnings.push(`max_records_per_domain[${row.domain}]`);
|
|
526
|
+
}
|
|
527
|
+
for (const row of per_domain_max_stale) {
|
|
528
|
+
if (row.verdict === "WARN") warnings.push(`max_stale[${row.domain}]`);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return {
|
|
532
|
+
repo,
|
|
533
|
+
total_records: total,
|
|
534
|
+
domains,
|
|
535
|
+
ignored_domains: ignored,
|
|
536
|
+
type_mix,
|
|
537
|
+
evidence: {
|
|
538
|
+
with_sprout: withTracker.sprout,
|
|
539
|
+
with_tracker: withTracker,
|
|
540
|
+
with_any_tracker: withAnyTracker,
|
|
541
|
+
with_commit: withCommit,
|
|
542
|
+
with_relates: withRelates,
|
|
543
|
+
floaters,
|
|
544
|
+
},
|
|
545
|
+
convention_quality: conv_quality,
|
|
546
|
+
seed_citations: {
|
|
547
|
+
unique: sproutRefEntries.length,
|
|
548
|
+
status_counts: sproutStatusCounts,
|
|
549
|
+
missing_in_sprout: sproutMissing,
|
|
550
|
+
top_cited: sproutTopCited,
|
|
551
|
+
},
|
|
552
|
+
tracker_citations: {
|
|
553
|
+
unique: refCounts.size,
|
|
554
|
+
status_counts: trackerStatusCounts,
|
|
555
|
+
missing_in_index: trackerMissing,
|
|
556
|
+
top_cited: trackerTopCited,
|
|
557
|
+
},
|
|
558
|
+
by_domain,
|
|
559
|
+
weak_domains,
|
|
560
|
+
thresholds,
|
|
561
|
+
per_domain_thresholds: perDomainThresholds,
|
|
562
|
+
signals: {
|
|
563
|
+
evidence_coverage,
|
|
564
|
+
rule_density,
|
|
565
|
+
floater_rate: floater_rate_verdict,
|
|
566
|
+
per_domain_max_records,
|
|
567
|
+
per_domain_max_stale,
|
|
568
|
+
},
|
|
569
|
+
failures,
|
|
570
|
+
warnings,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// ---- Suggest helpers ----
|
|
575
|
+
|
|
576
|
+
export interface SuggestionGroup {
|
|
577
|
+
action: "archive" | "revise" | "attribute";
|
|
578
|
+
headline: string;
|
|
579
|
+
rationale: string;
|
|
580
|
+
record_ids: string[];
|
|
581
|
+
commands: string[];
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
export interface SuggestPayload {
|
|
585
|
+
groups: SuggestionGroup[];
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
export async function buildSuggestions(
|
|
589
|
+
config: LoamConfig,
|
|
590
|
+
options: AuditOptions = {},
|
|
591
|
+
): Promise<SuggestPayload> {
|
|
592
|
+
const cwd = options.cwd ?? process.cwd();
|
|
593
|
+
const auditCfg = config.audit;
|
|
594
|
+
const cfgIgnored = new Set(auditCfg?.ignore_domains ?? []);
|
|
595
|
+
const cliIgnored = new Set(options.ignoreDomains ?? []);
|
|
596
|
+
let domains = Object.keys(config.domains);
|
|
597
|
+
if (options.domain) {
|
|
598
|
+
domains = domains.filter((d) => d === options.domain);
|
|
599
|
+
} else {
|
|
600
|
+
domains = domains.filter((d) => !cfgIgnored.has(d) && !cliIgnored.has(d));
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const items = await readDomainRecords(cwd, domains);
|
|
604
|
+
const now = new Date();
|
|
605
|
+
const shelfLife = config.classification_defaults.shelf_life;
|
|
606
|
+
|
|
607
|
+
const archive: string[] = [];
|
|
608
|
+
const revise: string[] = [];
|
|
609
|
+
const attribute: string[] = [];
|
|
610
|
+
|
|
611
|
+
for (const { record } of items) {
|
|
612
|
+
const id = record.id ?? "";
|
|
613
|
+
if (!id) continue;
|
|
614
|
+
|
|
615
|
+
if (record.classification !== "foundational" && isRecordStale(record, now, shelfLife)) {
|
|
616
|
+
archive.push(id);
|
|
617
|
+
}
|
|
618
|
+
if (record.type === "convention") {
|
|
619
|
+
const content = (record as { content?: string }).content;
|
|
620
|
+
if (!hasRuleSignal(content)) revise.push(id);
|
|
621
|
+
}
|
|
622
|
+
if (recordIsFloater(record)) {
|
|
623
|
+
attribute.push(id);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const groups: SuggestionGroup[] = [];
|
|
628
|
+
if (archive.length > 0) {
|
|
629
|
+
groups.push({
|
|
630
|
+
action: "archive",
|
|
631
|
+
headline: `${archive.length} stale records past shelf life`,
|
|
632
|
+
rationale:
|
|
633
|
+
"These records are past their classification shelf life and are not foundational. Soft-archive them with `lm prune` (or `lm archive` once it lands in v0.10).",
|
|
634
|
+
record_ids: archive,
|
|
635
|
+
commands: [
|
|
636
|
+
`lm prune --ids ${archive.join(",")} --dry-run`,
|
|
637
|
+
`lm prune --ids ${archive.join(",")}`,
|
|
638
|
+
],
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
if (revise.length > 0) {
|
|
642
|
+
groups.push({
|
|
643
|
+
action: "revise",
|
|
644
|
+
headline: `${revise.length} conventions lack rule-signal language`,
|
|
645
|
+
rationale:
|
|
646
|
+
"Conventions without rule-signal words (because, must not, avoid, always, never, …) often restate code rather than encode a rule. Revise the content or downgrade to a note.",
|
|
647
|
+
record_ids: revise,
|
|
648
|
+
commands: revise.slice(0, 10).map((id) => `lm edit ${id} --content "..."`),
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
if (attribute.length > 0) {
|
|
652
|
+
groups.push({
|
|
653
|
+
action: "attribute",
|
|
654
|
+
headline: `${attribute.length} records have no evidence or relates_to link`,
|
|
655
|
+
rationale:
|
|
656
|
+
"Floater records lack any tracker (sprout/gh/linear/bead), commit, or relates_to link. Attribute them so future audits and pruning can follow the trail.",
|
|
657
|
+
record_ids: attribute,
|
|
658
|
+
commands: attribute
|
|
659
|
+
.slice(0, 10)
|
|
660
|
+
.map(
|
|
661
|
+
(id) =>
|
|
662
|
+
`lm edit ${id} --evidence-sprout <id> # or --evidence-commit <sha> / --relates-to <mx-id>`,
|
|
663
|
+
),
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
return { groups };
|
|
667
|
+
}
|