loki-mode 7.31.0 → 7.32.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/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: Autonomous spec-driven build system with a built-in trust layer. It does not call work done until it is verified (RARV-C closure loop, 11 quality gates, completion council, verified-completion evidence gate). Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v7.31.0
6
+ # Loki Mode v7.32.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -398,4 +398,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
398
398
 
399
399
  ---
400
400
 
401
- **v7.31.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
401
+ **v7.32.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.31.0
1
+ 7.32.0
package/autonomy/loki CHANGED
@@ -392,6 +392,41 @@ _deprecated_alias() {
392
392
  fi
393
393
  }
394
394
 
395
+ # CLI consolidation (Phase B): kpis is a Bun-only command (Phase K, v7.5.28+).
396
+ # It ships in the Bun runtime (loki-ts) and reuses the canonical cost arithmetic
397
+ # in loki-ts/src/runner/budget.ts (PRICING map + calculateCostFromRecords, the
398
+ # cost single-source-of-truth). It is deliberately NOT reimplemented in bash:
399
+ # a bash port would duplicate that arithmetic and risk drift. On the bash route
400
+ # (LOKI_LEGACY_BASH=1, or a machine without Bun) both the canonical
401
+ # `report kpis` and the deprecated `kpis` alias print this honest requirement
402
+ # message rather than the generic "Unknown command" (which would contradict
403
+ # `loki help` listing kpis). --json yields a structured {"available": false}
404
+ # payload so a machine consumer is not handed prose. Exits 1.
405
+ # Usage: _kpis_bash_unavailable "$@" (the alias arm prints the pointer first)
406
+ # --json is honored FLAG-ANYWHERE (not just as $1): a machine consumer that
407
+ # passes the suppression flags first -- e.g. `kpis --quiet --json` or `report
408
+ # kpis -q --json` -- must still get the structured {"available": false} payload
409
+ # on stdout, not prose. This mirrors _deprecated_alias_should_suppress, which
410
+ # also scans all of "$@". Exit 1 either way (the bash route never produces KPIs).
411
+ # Copy points at the CANONICAL `loki report kpis` (the deprecated `loki kpis`
412
+ # alias prints its own pointer first, at the alias arm).
413
+ _kpis_bash_unavailable() {
414
+ local _ka _want_json=""
415
+ for _ka in "$@"; do
416
+ if [ "$_ka" = "--json" ]; then
417
+ _want_json=1
418
+ break
419
+ fi
420
+ done
421
+ if [ -n "$_want_json" ]; then
422
+ printf '{"available": false, "reason": "loki report kpis requires the Bun runtime; it is not implemented on the bash (LOKI_LEGACY_BASH) route"}\n'
423
+ else
424
+ echo "loki report kpis requires the Bun runtime and is not available on the bash route." >&2
425
+ echo "Remove LOKI_LEGACY_BASH=1 (or install Bun: https://bun.sh) to use 'loki report kpis'." >&2
426
+ fi
427
+ exit 1
428
+ }
429
+
395
430
  # Emit learning signal (non-blocking) - SYN-018
396
431
  # Usage: emit_learning_signal <signal_type> [options]
397
432
  # Signal types: user_preference, error_pattern, success_pattern, tool_efficiency, workflow_pattern
@@ -643,10 +678,11 @@ show_help() {
643
678
  echo " report [cmd] Reporting: session|metrics|cost|export|share|dogfood"
644
679
  echo ""
645
680
  echo "Knowledge:"
646
- echo " memory [cmd] Cross-project learnings (list|show|search|stats)"
681
+ echo " analyze [cmd] Codebase intelligence: explain|onboard|code|context"
682
+ echo " memory [cmd] Cross-project learnings (list|show|search|stats|compound)"
647
683
  echo ""
648
684
  echo "Modernize:"
649
- echo " heal <path> Legacy system healing (archaeology, stabilize, modernize)"
685
+ echo " modernize [cmd] Legacy modernization: heal|migrate"
650
686
  echo ""
651
687
  echo "Config:"
652
688
  echo " config [cmd] Manage configuration + provider (show|set|get|provider)"
@@ -656,10 +692,10 @@ show_help() {
656
692
  echo " help Show this help ('loki help aliases' for old names)"
657
693
  echo ""
658
694
  echo "More commands (init, watch, demo, web, api, logs, github,"
659
- echo "import, council, proof, audit, agent, template, magic, onboard, explain,"
660
- echo "code, context, docs, wiki, ci, test, bench, secrets, telemetry, crash,"
661
- echo "worktree, failover, monitor, remote, kpis, ...) are dispatchable and"
662
- echo "documented via 'loki <command> --help'."
695
+ echo "import, council, proof, audit, agent, template, magic, docs, wiki, ci,"
696
+ echo "test, bench, secrets, telemetry, crash, worktree, failover, monitor,"
697
+ echo "remote, ...) are dispatchable and documented via"
698
+ echo "'loki <command> --help'."
663
699
  echo ""
664
700
  echo "Aliases (deprecated): older command names still work; they print a"
665
701
  echo "one-line pointer to stderr and never alter --json output. See the full"
@@ -710,7 +746,7 @@ show_help() {
710
746
  echo " # Session ops + observability"
711
747
  echo " loki status [--json] # Current status"
712
748
  echo " loki report session --efficiency # Token + cost stats"
713
- echo " loki kpis [--json] # Accuracy + efficiency KPI snapshot"
749
+ echo " loki report kpis [--json] # Accuracy + efficiency KPI snapshot"
714
750
  echo " loki doctor [--json] # System prereq + skill symlinks"
715
751
  echo " loki logs # Tail recent log output"
716
752
  echo " loki report export json|markdown|csv|timeline # Export session"
@@ -783,9 +819,18 @@ show_help_aliases() {
783
819
  echo " export report export"
784
820
  echo " share report share"
785
821
  echo " dogfood report dogfood"
822
+ echo " kpis report kpis"
786
823
  echo " trust-metrics trust detail"
824
+ echo " compound memory compound"
825
+ echo " explain analyze explain"
826
+ echo " onboard analyze onboard"
827
+ echo " code analyze code"
828
+ echo " context analyze context"
829
+ echo " ctx analyze context"
830
+ echo " heal modernize heal"
831
+ echo " migrate modernize migrate"
787
832
  echo ""
788
- echo "More groupings (ui, analyze, modernize, new, admin) land in Phase B."
833
+ echo "More groupings (ui, new, admin) land in later Phase B slices."
789
834
  echo "See internal/CLI-CONSOLIDATION-DESIGN.md for the full migration plan."
790
835
  }
791
836
 
@@ -11843,6 +11888,37 @@ cmd_heal_help() {
11843
11888
  echo " loki heal --friction-map ./legacy-app # View friction map"
11844
11889
  }
11845
11890
 
11891
+ # CLI consolidation (Phase B, design item 6): the 'modernize' noun groups the
11892
+ # legacy-modernization surface (heal/migrate) under one entry. It is a THIN
11893
+ # dispatcher: each arm forwards verbatim to the existing cmd_* handler (no
11894
+ # handler logic moves; heal and migrate keep their separate phase vocabularies
11895
+ # and state). The two top-level tokens remain deprecated forwarding aliases.
11896
+ cmd_modernize() {
11897
+ local subcommand="${1:-help}"
11898
+ case "$subcommand" in
11899
+ heal) shift; cmd_heal "$@"; return $? ;;
11900
+ migrate) shift; cmd_migrate "$@"; return $? ;;
11901
+ --help|-h|help)
11902
+ echo -e "${BOLD}loki modernize${NC} - Legacy system modernization"
11903
+ echo ""
11904
+ echo "Usage: loki modernize <subcommand> [args]"
11905
+ echo ""
11906
+ echo "Subcommands (grouped modernization surface):"
11907
+ echo " heal <path> Legacy system healing in phases (was: loki heal)"
11908
+ echo " migrate <path> Codebase migration in phases (was: loki migrate)"
11909
+ echo ""
11910
+ echo "Run 'loki modernize <subcommand> --help' for each. The old"
11911
+ echo "top-level names still work; see 'loki help aliases'."
11912
+ return 0
11913
+ ;;
11914
+ *)
11915
+ echo -e "${RED}Unknown modernize command: $subcommand${NC}" >&2
11916
+ echo "Run 'loki modernize --help' for usage." >&2
11917
+ return 1
11918
+ ;;
11919
+ esac
11920
+ }
11921
+
11846
11922
  cmd_heal() {
11847
11923
  local codebase_path=""
11848
11924
  local phase="${LOKI_HEAL_PHASE:-archaeology}"
@@ -13192,10 +13268,26 @@ def _loki_norm_alias(raw):
13192
13268
  raw = (raw or '').strip().lower()
13193
13269
  return raw if raw in ('haiku', 'sonnet', 'opus', 'fable') else ''
13194
13270
 
13271
+ # Session-pin normalization is BROADER than the override-file allowlist above.
13272
+ # run.sh's session-pin case (run.sh:12331) accepts the four model aliases AND
13273
+ # the three raw tier names (planning|development|fast) -- documented at
13274
+ # skills/model-selection.md:8 -- mapping each through resolve_model_for_tier.
13275
+ # The OVERRIDE file path (_loki_norm_alias) stays narrow because that value is
13276
+ # fed straight to 'claude --model', where tier names are not valid. The session
13277
+ # pin is a tier route, so tier names ARE valid pins. This normalizer mirrors
13278
+ # run.sh's trim+lowercase (interior whitespace preserved, so 'fab le' stays junk
13279
+ # and falls through to the default tier exactly like the runner's '*' arm).
13280
+ def _loki_norm_session_pin(raw):
13281
+ raw = (raw or '').strip().lower()
13282
+ return raw if raw in (
13283
+ 'haiku', 'sonnet', 'opus', 'fable',
13284
+ 'planning', 'development', 'fast',
13285
+ ) else ''
13286
+
13195
13287
  # session_model_env starts from LOKI_SESSION_MODEL; track the lever that set it
13196
13288
  # so the provenance line names the real source (never a false attribution).
13197
13289
  _session_model_source = 'LOKI_SESSION_MODEL'
13198
- session_model_env = _loki_norm_alias(os.environ.get('LOKI_SESSION_MODEL', '')) or 'sonnet'
13290
+ session_model_env = _loki_norm_session_pin(os.environ.get('LOKI_SESSION_MODEL', '')) or 'sonnet'
13199
13291
 
13200
13292
  _fable_override = ''
13201
13293
  try:
@@ -13246,6 +13338,13 @@ def _provider_model_development():
13246
13338
  return (os.environ.get('LOKI_CLAUDE_MODEL_DEVELOPMENT')
13247
13339
  or os.environ.get('LOKI_MODEL_DEVELOPMENT')
13248
13340
  or ('sonnet' if _allow_haiku else 'opus'))
13341
+ def _provider_model_planning():
13342
+ # claude.sh:65 -> LOKI_CLAUDE_MODEL_PLANNING > LOKI_MODEL_PLANNING > opus.
13343
+ # CLAUDE_DEFAULT_PLANNING is always opus (LOKI_ALLOW_HAIKU only lowers the
13344
+ # development and fast defaults, not planning).
13345
+ return (os.environ.get('LOKI_CLAUDE_MODEL_PLANNING')
13346
+ or os.environ.get('LOKI_MODEL_PLANNING')
13347
+ or 'opus')
13249
13348
  _max_tier = (os.environ.get('LOKI_MAX_TIER', '') or '').strip().lower()
13250
13349
  def _loki_clamp_alias(alias):
13251
13350
  if not _max_tier:
@@ -13259,14 +13358,96 @@ def _loki_clamp_alias(alias):
13259
13358
  if _max_tier == 'opus':
13260
13359
  return 'opus' if alias == 'fable' else alias
13261
13360
  return alias
13262
- if _max_tier:
13263
- _clamped = _loki_clamp_alias(session_model_env)
13264
- if _clamped != session_model_env:
13265
- session_model_env = _clamped
13266
- _session_model_source = 'LOKI_MAX_TIER (clamped)'
13267
- if _fable_architect and _loki_clamp_alias('fable') != 'fable':
13268
- # The architect iteration would clamp down too; do not disclose fable.
13269
- _fable_architect = False
13361
+
13362
+ # Session-pin tier route (the runner's NO-OVERRIDE path). When there is no
13363
+ # mid-flight override file, the runner does NOT feed the session alias straight
13364
+ # to --model. It maps the alias to an abstract TIER (run.sh:12331 -- opus->
13365
+ # planning, sonnet->development, haiku->fast, fable->fable) and resolves that
13366
+ # tier through resolve_model_for_tier (claude.sh:353), which reads
13367
+ # PROVIDER_MODEL_PLANNING/DEVELOPMENT/FAST and then applies
13368
+ # loki_apply_max_tier_clamp(model, REAL_tier). This DIFFERS from the override-
13369
+ # path clamp _loki_clamp_alias above: a 'sonnet' SESSION pin dispatches OPUS
13370
+ # (development tier -> PROVIDER_MODEL_DEVELOPMENT=opus on stock config), whereas
13371
+ # a 'sonnet' OVERRIDE file dispatches sonnet (alias fed straight to --model).
13372
+ # Quoting the alias name on the default session path was the task-568 gap (it
13373
+ # understated cost ~1.7x: Sonnet quote vs Opus dispatch). The estimator must
13374
+ # quote the model the runner actually dispatches.
13375
+ #
13376
+ # SYNC: byte-faithful with run.sh's session-pin case + claude.sh
13377
+ # resolve_model_for_tier + loki_apply_max_tier_clamp. The same resolver lives in
13378
+ # the dashboard (dashboard/server.py _resolve_session_pin). Locked by the
13379
+ # session-pin parity matrix and the no-cap stock cross-route cases in
13380
+ # tests/test-model-override.sh.
13381
+ def _resolve_session_pin(alias):
13382
+ # alias -> abstract tier (run.sh:12331). Unknown -> development (the runner's
13383
+ # '*' arm passes CURRENT_TIER through to resolve_model_for_tier, whose '*'
13384
+ # default is PROVIDER_MODEL_DEVELOPMENT, i.e. the development tier).
13385
+ _pin_tier = {
13386
+ 'opus': 'planning',
13387
+ 'sonnet': 'development',
13388
+ 'haiku': 'fast',
13389
+ 'fable': 'fable',
13390
+ # Raw tier-name pins (run.sh:12336 passthrough arm) map to their own
13391
+ # tier, NOT through the alias table. So pin=fast -> fast tier ->
13392
+ # PROVIDER_MODEL_FAST (sonnet stock / haiku ALLOW_HAIKU), matching the
13393
+ # runner's dispatch instead of collapsing onto development.
13394
+ 'planning': 'planning',
13395
+ 'development': 'development',
13396
+ 'fast': 'fast',
13397
+ }.get(alias, 'development')
13398
+ # tier -> model (claude.sh resolve_model_for_tier explicit arms).
13399
+ if _pin_tier == 'planning':
13400
+ _m = _provider_model_planning()
13401
+ elif _pin_tier == 'fast':
13402
+ _m = _provider_model_fast()
13403
+ elif _pin_tier == 'fable':
13404
+ _m = 'fable'
13405
+ else: # development (and the '*' fallthrough)
13406
+ _m = _provider_model_development()
13407
+ # Apply the shared LOKI_MAX_TIER clamp with the REAL tier and resolved model
13408
+ # (loki_apply_max_tier_clamp(model, tier)), NOT the alias==alias override
13409
+ # convention. This is the one place where the two paths legitimately diverge
13410
+ # (e.g. opus pin + sonnet cap + ALLOW_HAIKU: tier route -> sonnet, because the
13411
+ # planning tier downgrades to PROVIDER_MODEL_DEVELOPMENT=sonnet).
13412
+ if not _max_tier:
13413
+ return _m
13414
+ if _max_tier == 'haiku':
13415
+ return _provider_model_fast()
13416
+ if _max_tier == 'sonnet':
13417
+ if _pin_tier in ('planning', 'fable') or _m == 'fable':
13418
+ return _provider_model_development()
13419
+ return _m
13420
+ if _max_tier == 'opus':
13421
+ return 'opus' if _m == 'fable' else _m
13422
+ return _m
13423
+ # Resolve the model the runner will ACTUALLY dispatch for the pinned session,
13424
+ # choosing the correct route:
13425
+ # - OVERRIDE file present: the runner feeds the alias straight to --model via
13426
+ # loki_apply_max_tier_clamp(alias, alias). Use _loki_clamp_alias (the
13427
+ # override-path clamp). A 'sonnet' override dispatches sonnet.
13428
+ # - No override (session pin): the runner maps alias -> tier ->
13429
+ # resolve_model_for_tier -> clamp(model, real_tier). Use _resolve_session_pin.
13430
+ # A 'sonnet' session pin dispatches OPUS (development tier).
13431
+ # This is the task-568 fix: price the actually-dispatched model on BOTH routes.
13432
+ _is_override = (_session_model_source == '.loki/state/model-override')
13433
+ if _is_override:
13434
+ _dispatched_model = _loki_clamp_alias(session_model_env)
13435
+ else:
13436
+ _dispatched_model = _resolve_session_pin(session_model_env)
13437
+
13438
+ # Keep session_model_env (the alias) for token-volume tier mapping and provenance,
13439
+ # but record when the cost ceiling actually changed the dispatched model so the
13440
+ # provenance line is honest about the clamp.
13441
+ if _max_tier and _dispatched_model != session_model_env:
13442
+ _session_model_source = 'LOKI_MAX_TIER (clamped)'
13443
+ # Clear the architect-fable disclosure when the ceiling would clamp the architect
13444
+ # iteration too. This is INDEPENDENT of whether the session model itself changed:
13445
+ # e.g. an opus pin under a sonnet cap leaves the session model opus (no change),
13446
+ # but the iter-0 fable architect tier still clamps down to development. Nesting
13447
+ # this under the session-changed branch would over-quote a Fable iteration the
13448
+ # runner actually dispatches as opus (estimator-vs-runner divergence).
13449
+ if _max_tier and _fable_architect and _loki_clamp_alias('fable') != 'fable':
13450
+ _fable_architect = False
13270
13451
 
13271
13452
  # v7.29.0: Honor LOKI_COMPLEXITY -- the SAME env var the runner honors at
13272
13453
  # run.sh:920 -- so a forced-tier run (e.g. 'loki demo' / 'loki start --simple'
@@ -13282,16 +13463,31 @@ forced_complexity = {'standard': 'moderate'}.get(_forced_complexity_raw, _forced
13282
13463
  if forced_complexity not in ('simple', 'moderate', 'complex', 'enterprise'):
13283
13464
  forced_complexity = ''
13284
13465
 
13285
- # Map session model name to tier key used in tokens_per_tier below.
13286
- # Unknown models fall through to 'development' (Sonnet) as a safe default.
13466
+ # Map session model name to tier key used in tokens_per_tier below. This keys the
13467
+ # token VOLUME per iteration off the work tier the session pin selects (e.g. a
13468
+ # sonnet pin = the development tier = 80k/15k tokens). It does NOT decide the
13469
+ # priced model: that is _dispatched_model, the model the runner actually invokes.
13470
+ # Unknown models fall through to 'development' as a safe default.
13287
13471
  _session_tier_map = {
13288
13472
  'fable': 'advisor',
13289
13473
  'opus': 'planning',
13290
13474
  'sonnet': 'development',
13291
13475
  'haiku': 'fast',
13476
+ # Raw tier-name pins key off their own work tier directly (they ARE tier
13477
+ # names), mirroring run.sh's session-pin passthrough arm.
13478
+ 'planning': 'planning',
13479
+ 'development': 'development',
13480
+ 'fast': 'fast',
13292
13481
  }
13293
13482
  session_tier = _session_tier_map.get(session_model_env, 'development')
13294
13483
 
13484
+ # Pricing-table key (Fable/Opus/Sonnet/Haiku) for the model the runner dispatches.
13485
+ # The loop below uses tokens_per_tier[tier] for token VOLUME but prices by this
13486
+ # model, so the quote reflects the actually-dispatched model on every route.
13487
+ _PRICED_MODEL_KEY = {'fable': 'Fable', 'opus': 'Opus', 'sonnet': 'Sonnet', 'haiku': 'Haiku'}
13488
+ def _priced_model_for(alias):
13489
+ return _PRICED_MODEL_KEY.get((alias or '').strip().lower(), 'Opus')
13490
+
13295
13491
  # Colors (disabled for JSON mode)
13296
13492
  if show_json:
13297
13493
  RED = GREEN = YELLOW = BLUE = CYAN = BOLD = DIM = NC = ''
@@ -13524,7 +13720,19 @@ for i in range(estimated_iterations):
13524
13720
  tier = 'advisor'
13525
13721
 
13526
13722
  info = tokens_per_tier[tier]
13527
- model = info['model']
13723
+ # Token VOLUME comes from the work tier (info), but the priced MODEL is the
13724
+ # model the runner actually dispatches. In legacy rotation the per-tier model
13725
+ # IS the dispatched model. In session-pinned mode (the default) the runner
13726
+ # resolves the pin through provider config (_dispatched_model): a sonnet pin
13727
+ # dispatches opus, so we price Opus, not the tier's static 'Sonnet' label.
13728
+ # The fable-architect iteration 0 genuinely dispatches fable (already cap-
13729
+ # cleared above when it would clamp), so it keeps the advisor tier's Fable.
13730
+ if legacy_tier_switching:
13731
+ model = info['model']
13732
+ elif _fable_architect and i == 0:
13733
+ model = info['model'] # advisor tier -> Fable
13734
+ else:
13735
+ model = _priced_model_for(_dispatched_model)
13528
13736
  inp = info['input']
13529
13737
  out = info['output']
13530
13738
 
@@ -13719,8 +13927,14 @@ else:
13719
13927
  print(' Model distribution: ' + (' | '.join(pinned_parts) if pinned_parts else '(none)'))
13720
13928
  # Provenance: name the lever that actually selected the pinned model, never a
13721
13929
  # false attribution. _session_model_source is the real source (env, override
13722
- # file, or maxTier clamp) tracked at selection time.
13723
- print(f' {DIM}(pinned via {_session_model_source}={session_model_env}; set LOKI_LEGACY_TIER_SWITCHING=true to rotate){NC}')
13930
+ # file, or maxTier clamp) tracked at selection time. When the session pin is a
13931
+ # tier alias that resolves to a DIFFERENT dispatched model (the default: a
13932
+ # 'sonnet' pin -> development tier -> opus), show the resolution so the
13933
+ # distribution above (Opus x4) is legible against the pin name (sonnet).
13934
+ if (not _is_override) and _dispatched_model != session_model_env:
13935
+ print(f' {DIM}(pinned via {_session_model_source}={session_model_env} -> {session_tier} tier -> {_dispatched_model}; set LOKI_LEGACY_TIER_SWITCHING=true to rotate){NC}')
13936
+ else:
13937
+ print(f' {DIM}(pinned via {_session_model_source}={session_model_env}; set LOKI_LEGACY_TIER_SWITCHING=true to rotate){NC}')
13724
13938
  if _fable_architect:
13725
13939
  print(f' {DIM}(+ 1 architecture iteration on Fable via LOKI_FABLE_ARCHITECT=1){NC}')
13726
13940
  if fable_n > 0:
@@ -14052,7 +14266,9 @@ main() {
14052
14266
  cmd_memory "$@"
14053
14267
  ;;
14054
14268
  compound)
14055
- cmd_compound "$@"
14269
+ # CLI consolidation (Phase B): 'compound' -> 'memory compound'.
14270
+ _deprecated_alias compound "memory compound" "$@"
14271
+ cmd_memory compound "$@"
14056
14272
  ;;
14057
14273
  checkpoint|cp)
14058
14274
  # CLI consolidation (Phase A): 'cp' is a deprecated alias of 'checkpoint'.
@@ -14107,8 +14323,13 @@ main() {
14107
14323
  optimize)
14108
14324
  cmd_optimize "$@"
14109
14325
  ;;
14326
+ modernize)
14327
+ cmd_modernize "$@"
14328
+ ;;
14110
14329
  heal)
14111
- cmd_heal "$@"
14330
+ # CLI consolidation (Phase B): 'heal' -> 'modernize heal'.
14331
+ _deprecated_alias heal "modernize heal" "$@"
14332
+ cmd_modernize heal "$@"
14112
14333
  ;;
14113
14334
  verify)
14114
14335
  cmd_verify "$@"
@@ -14123,7 +14344,9 @@ main() {
14123
14344
  cmd_mcp "$@"
14124
14345
  ;;
14125
14346
  migrate)
14126
- cmd_migrate "$@"
14347
+ # CLI consolidation (Phase B): 'migrate' -> 'modernize migrate'.
14348
+ _deprecated_alias migrate "modernize migrate" "$@"
14349
+ cmd_modernize migrate "$@"
14127
14350
  ;;
14128
14351
  cluster)
14129
14352
  cmd_cluster "$@"
@@ -14182,11 +14405,18 @@ main() {
14182
14405
  failover)
14183
14406
  cmd_failover "$@"
14184
14407
  ;;
14408
+ analyze)
14409
+ cmd_analyze "$@"
14410
+ ;;
14185
14411
  onboard)
14186
- cmd_onboard "$@"
14412
+ # CLI consolidation (Phase B): 'onboard' -> 'analyze onboard'.
14413
+ _deprecated_alias onboard "analyze onboard" "$@"
14414
+ cmd_analyze onboard "$@"
14187
14415
  ;;
14188
14416
  explain)
14189
- cmd_explain "$@"
14417
+ # CLI consolidation (Phase B): 'explain' -> 'analyze explain'.
14418
+ _deprecated_alias explain "analyze explain" "$@"
14419
+ cmd_analyze explain "$@"
14190
14420
  ;;
14191
14421
  docs)
14192
14422
  cmd_docs "$@"
@@ -14221,10 +14451,15 @@ main() {
14221
14451
  cmd_bench "$@"
14222
14452
  ;;
14223
14453
  context|ctx)
14224
- cmd_context "$@"
14454
+ # CLI consolidation (Phase B): 'context' (and short alias 'ctx')
14455
+ # -> 'analyze context'.
14456
+ _deprecated_alias "$command" "analyze context" "$@"
14457
+ cmd_analyze context "$@"
14225
14458
  ;;
14226
14459
  code)
14227
- cmd_code "$@"
14460
+ # CLI consolidation (Phase B): 'code' -> 'analyze code'.
14461
+ _deprecated_alias code "analyze code" "$@"
14462
+ cmd_analyze code "$@"
14228
14463
  ;;
14229
14464
  version|--version|-v)
14230
14465
  cmd_version "$@"
@@ -14233,20 +14468,14 @@ main() {
14233
14468
  cmd_completions "$@"
14234
14469
  ;;
14235
14470
  kpis)
14236
- # kpis is a Bun-only command (Phase K, v7.5.28+): it ships in the
14237
- # Bun runtime (loki-ts) and bin/loki routes it there. On the bash
14238
- # route (LOKI_LEGACY_BASH=1, or a machine without Bun installed)
14239
- # there is no bash implementation. Rather than the generic
14240
- # "Unknown command" -- which contradicts `loki help` listing kpis --
14241
- # state the requirement honestly. Respect --json with a structured
14242
- # payload so a machine consumer is not handed prose.
14243
- if [ "${1:-}" = "--json" ]; then
14244
- printf '{"available": false, "reason": "loki kpis requires the Bun runtime; it is not implemented on the bash (LOKI_LEGACY_BASH) route"}\n'
14245
- else
14246
- echo "loki kpis requires the Bun runtime and is not available on the bash route." >&2
14247
- echo "Remove LOKI_LEGACY_BASH=1 (or install Bun: https://bun.sh) to use 'loki kpis'." >&2
14248
- fi
14249
- exit 1
14471
+ # CLI consolidation (Phase B): `kpis` is a deprecated alias of the
14472
+ # canonical `report kpis`. Emit the one-line pointer (suppressed
14473
+ # under --json/-q/--quiet by _deprecated_alias), then print the
14474
+ # honest Bun-requirement message via the shared helper. On a machine
14475
+ # WITH Bun, bin/loki routes `kpis` to the Bun runtime before this arm
14476
+ # is ever reached; this arm is the LOKI_LEGACY_BASH / no-Bun path.
14477
+ _deprecated_alias kpis "report kpis" "$@"
14478
+ _kpis_bash_unavailable "$@"
14250
14479
  ;;
14251
14480
  help|--help|-h)
14252
14481
  # CLI consolidation (Phase A): `loki help aliases` prints the full
@@ -16571,6 +16800,7 @@ except Exception as e:
16571
16800
  echo " export [file] Export to JSON file"
16572
16801
  echo " dedupe Remove duplicate entries"
16573
16802
  echo " clear [type] Clear learnings (all or specific type)"
16803
+ echo " compound [cmd] Compound-solutions knowledge store (was: loki compound)"
16574
16804
  echo ""
16575
16805
  echo "Memory System Commands:"
16576
16806
  echo " index [rebuild] Show or rebuild the memory index layer"
@@ -16938,9 +17168,24 @@ print('removed')
16938
17168
  "
16939
17169
  ;;
16940
17170
 
17171
+ compound)
17172
+ # CLI consolidation (Phase B, design item 7): 'compound' is folded
17173
+ # under the 'memory' noun as 'memory compound'. This arm forwards to
17174
+ # the existing cmd_compound handler unchanged (no handler logic
17175
+ # moves). The top-level 'compound' token stays a deprecated
17176
+ # forwarding alias for back-compat. cmd_memory does not shift before
17177
+ # the case, so $@ here is still 'compound <args>'; drop the leading
17178
+ # 'compound' token, then forward the rest verbatim.
17179
+ shift 2>/dev/null || true
17180
+ cmd_compound "$@"
17181
+ ;;
17182
+
16941
17183
  *)
16942
- echo -e "${RED}Unknown memory command: $subcommand${NC}"
16943
- echo "Run 'loki memory help' for usage."
17184
+ # Error-channel parity with the consolidated noun family (analyze,
17185
+ # modernize): unknown-subcommand diagnostics go to STDERR so a scripted
17186
+ # consumer capturing stdout is not handed error text. Exit 1 unchanged.
17187
+ echo -e "${RED}Unknown memory command: $subcommand${NC}" >&2
17188
+ echo "Run 'loki memory help' for usage." >&2
16944
17189
  exit 1
16945
17190
  ;;
16946
17191
  esac
@@ -17191,13 +17436,13 @@ COMPOUND_RUN_SCRIPT
17191
17436
  ;;
17192
17437
 
17193
17438
  help|--help|-h)
17194
- echo -e "${BOLD}loki compound${NC} - Knowledge compounding system"
17439
+ echo -e "${BOLD}loki memory compound${NC} - Knowledge compounding system"
17195
17440
  echo ""
17196
17441
  echo "Extracts structured solutions from cross-project learnings."
17197
17442
  echo "Solutions are stored as markdown files with YAML frontmatter"
17198
17443
  echo "at ~/.loki/solutions/{category}/ and fed back into future planning."
17199
17444
  echo ""
17200
- echo "Usage: loki compound <command>"
17445
+ echo "Usage: loki memory compound <command> (alias: loki compound)"
17201
17446
  echo ""
17202
17447
  echo "Commands:"
17203
17448
  echo " list List all solutions by category"
@@ -20268,6 +20513,41 @@ METRICS_SCRIPT
20268
20513
  fi
20269
20514
  }
20270
20515
 
20516
+ # CLI consolidation (Phase B, design item 8): the 'analyze' noun groups the
20517
+ # repo-intelligence surface (explain/onboard/code/context) under one entry. It
20518
+ # is a THIN dispatcher: every arm forwards verbatim to the existing cmd_*
20519
+ # handler (no handler logic moves). The four top-level tokens remain deprecated
20520
+ # forwarding aliases for back-compat.
20521
+ cmd_analyze() {
20522
+ local subcommand="${1:-help}"
20523
+ case "$subcommand" in
20524
+ explain) shift; cmd_explain "$@"; return $? ;;
20525
+ onboard) shift; cmd_onboard "$@"; return $? ;;
20526
+ code) shift; cmd_code "$@"; return $? ;;
20527
+ context) shift; cmd_context "$@"; return $? ;;
20528
+ --help|-h|help)
20529
+ echo -e "${BOLD}loki analyze${NC} - Project knowledge and codebase intelligence"
20530
+ echo ""
20531
+ echo "Usage: loki analyze <subcommand> [args]"
20532
+ echo ""
20533
+ echo "Subcommands (grouped knowledge surface):"
20534
+ echo " explain [path] Explain a codebase architecture in prose (was: loki explain)"
20535
+ echo " onboard [path] Analyze a repo and generate CLAUDE.md (was: loki onboard)"
20536
+ echo " code [query] Codebase intelligence queries (was: loki code)"
20537
+ echo " context [cmd] Context-window management (was: loki context)"
20538
+ echo ""
20539
+ echo "Run 'loki analyze <subcommand> --help' for each. The old top-level"
20540
+ echo "names still work; see 'loki help aliases'."
20541
+ return 0
20542
+ ;;
20543
+ *)
20544
+ echo -e "${RED}Unknown analyze command: $subcommand${NC}" >&2
20545
+ echo "Run 'loki analyze --help' for usage." >&2
20546
+ return 1
20547
+ ;;
20548
+ esac
20549
+ }
20550
+
20271
20551
  # Context window management (inspired by Kiro CLI /context command)
20272
20552
  # Shows token usage breakdown and context window utilization
20273
20553
  cmd_context() {
@@ -26251,6 +26531,44 @@ _test_gen_bats() {
26251
26531
  # is emitted at the alias dispatch arm (main()), never here, so `report <sub>`
26252
26532
  # stays clean.
26253
26533
  cmd_report() {
26534
+ # CLI consolidation (Phase B): `report kpis` is the canonical KPI snapshot
26535
+ # entry. kpis is Bun-only; on a machine WITH Bun, bin/loki two-token-routes
26536
+ # `report kpis` (flag-anywhere) to the Bun runtime before this function is
26537
+ # reached. This is the bash-route (LOKI_LEGACY_BASH / no-Bun) path: print
26538
+ # the honest requirement message, NO deprecation pointer (this IS the
26539
+ # canonical form), exit 1. `kpis` is the report subcommand ONLY when it is the
26540
+ # FIRST non-flag token after `report` (so `report kpis --json` and `report
26541
+ # --json kpis` both route here, mirroring the bin/loki + Bun cli.ts routing),
26542
+ # NOT when it appears anywhere. A `kpis` token that is a positional VALUE of a
26543
+ # different report subcommand -- e.g. `report export json kpis`, where `kpis`
26544
+ # is the export OUTPUT FILENAME -- must NOT be hijacked; it falls through to
26545
+ # the export arm below and behaves exactly as on main (v7.31.0): exit 0, file
26546
+ # `kpis` created. Only the subcommand token is stripped before forwarding to
26547
+ # the helper; flags (e.g. --json) are passed through so it still emits the
26548
+ # structured machine payload.
26549
+ local _report_first_sub="" _kpis_args=() _dropped_kpis=""
26550
+ local _a
26551
+ for _a in "$@"; do
26552
+ case "$_a" in
26553
+ -*) _kpis_args+=("$_a") ;;
26554
+ *)
26555
+ if [ -z "$_report_first_sub" ]; then
26556
+ _report_first_sub="$_a"
26557
+ # Drop only the subcommand token itself, once.
26558
+ if [ "$_a" = "kpis" ]; then
26559
+ _dropped_kpis=1
26560
+ continue
26561
+ fi
26562
+ fi
26563
+ _kpis_args+=("$_a")
26564
+ ;;
26565
+ esac
26566
+ done
26567
+ if [ -n "$_dropped_kpis" ]; then
26568
+ # ${arr[@]:-} guards the empty-array expansion under `set -u` on the
26569
+ # bare `report kpis` case (no extra flags) for older bash (3.2/4.2).
26570
+ _kpis_bash_unavailable "${_kpis_args[@]:-}"
26571
+ fi
26254
26572
  case "${1:-}" in
26255
26573
  session) shift; cmd_stats "$@"; return $? ;;
26256
26574
  metrics) shift; cmd_metrics "$@"; return $? ;;
@@ -26283,6 +26601,7 @@ cmd_report_legacy() {
26283
26601
  echo " session Session statistics (was: loki stats)"
26284
26602
  echo " metrics Efficiency/reward metrics (was: loki metrics)"
26285
26603
  echo " cost Token cost breakdown (was: loki cost)"
26604
+ echo " kpis Accuracy + efficiency KPI snapshot (was: loki kpis; Bun runtime only)"
26286
26605
  echo " export Export session data (was: loki export) [json|markdown|csv|timeline]"
26287
26606
  echo " share Share session assets (was: loki share)"
26288
26607
  echo " dogfood Self-development stats (was: loki dogfood)"
@@ -26319,7 +26638,7 @@ cmd_report_legacy() {
26319
26638
  --include-gates) include_gates=true; shift ;;
26320
26639
  --include-agents) include_agents=true; shift ;;
26321
26640
  --include-timeline) include_timeline=true; shift ;;
26322
- *) echo -e "${RED}Unknown option: $1${NC}"; echo "Run 'loki report --help' for usage."; exit 1 ;;
26641
+ *) echo -e "${RED}Unknown option: $1${NC}" >&2; echo "Run 'loki report --help' for usage." >&2; exit 1 ;; # stderr for noun-family error-channel parity (analyze/modernize)
26323
26642
  esac
26324
26643
  done
26325
26644