loki-mode 11.4.2 → 11.6.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.
Files changed (129) hide show
  1. package/SKILL.md +2 -2
  2. package/VERSION +1 -1
  3. package/autonomy/lib/chat.sh +6 -3
  4. package/autonomy/lib/engine10-push.sh +19 -1
  5. package/autonomy/lib/help-core.sh +1 -1
  6. package/autonomy/lib/proof-generator.py +23 -5
  7. package/autonomy/lib/proof-verify.py +607 -5
  8. package/autonomy/lib/run-summary.sh +17 -1
  9. package/autonomy/lib/setup-requirements.json +35 -0
  10. package/autonomy/loki +244 -237
  11. package/autonomy/notification-checker.py +1 -1
  12. package/autonomy/provider-offer.sh +95 -47
  13. package/autonomy/route.sh +38 -6
  14. package/autonomy/run.sh +42 -14
  15. package/autonomy/setup-wizard.sh +19 -10
  16. package/autonomy/verify.sh +40 -1
  17. package/completions/_loki +3 -1
  18. package/completions/loki.bash +9 -3
  19. package/dashboard/__init__.py +3 -10
  20. package/dashboard/control.py +5 -435
  21. package/docs/ARCHITECTURE.md +1 -2
  22. package/docs/CLI-CORE-VERBS.md +2 -6
  23. package/docs/CLI-REFERENCE.md +3 -1
  24. package/docs/COMPONENTS.md +0 -2
  25. package/docs/EVALUATING.md +27 -0
  26. package/docs/HARNESS-INTELLIGENCE-PLAN.md +20 -0
  27. package/docs/INSTALLATION.md +1 -1
  28. package/docs/KNOWN-ISSUES.md +10 -1
  29. package/docs/PROOF-PASSPORT.md +114 -0
  30. package/docs/alternative-installations.md +103 -0
  31. package/docs/authentication.md +19 -1
  32. package/docs/cost-controls.md +139 -0
  33. package/docs/dev/release-checklist.md +9 -8
  34. package/docs/enterprise/security.md +3 -234
  35. package/docs/environment-variables.md +36 -0
  36. package/docs/exit-codes.md +102 -2
  37. package/docs/spec-first-intent.md +29 -0
  38. package/docs/v10/ARCHITECT-CUTS.md +304 -0
  39. package/docs/v10/CLI-MODERN.md +2 -2
  40. package/docs/v10/DECISIONS.md +5 -5
  41. package/docs/v10/ENGINE.md +735 -267
  42. package/docs/v10/GUARDS.md +16 -14
  43. package/docs/v10/GUIDE.md +5 -7
  44. package/docs/v10/PROGRESS.md +12 -1
  45. package/docs/v11/MARKET-SLICES.md +1 -5
  46. package/integrations/openclaw/README.md +20 -0
  47. package/integrations/vibe-kanban.md +12 -50
  48. package/loki-ts/data/model-pricing.json +1 -0
  49. package/loki-ts/dist/loki.js +1156 -1103
  50. package/mcp/__init__.py +1 -1
  51. package/mcp/_sdk_loader.py +1 -1
  52. package/package.json +2 -2
  53. package/packages/control-plane/dist/server.js +2063 -1032
  54. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  55. package/plugins/loki-mode/README.md +41 -1
  56. package/plugins/loki-mode/commands/handoff.md +12 -0
  57. package/plugins/loki-mode/commands/loki-verify.md +9 -2
  58. package/plugins/loki-mode/commands/start.md +20 -0
  59. package/plugins/loki-mode/hooks/hooks.json +11 -0
  60. package/plugins/loki-mode/scripts/loki-stop-verify.sh +84 -0
  61. package/references/quality-control.md +1 -116
  62. package/skills/00-index.md +2 -2
  63. package/skills/artifacts.md +0 -22
  64. package/skills/production.md +2 -38
  65. package/web-app/dist/assets/{AdminPage-CYJ2BWRC.js → AdminPage-DhXdJNQk.js} +1 -1
  66. package/web-app/dist/assets/{Avatar-DdK4wynI.js → Avatar-ixVj76fM.js} +1 -1
  67. package/web-app/dist/assets/{Badge-CrX3MI7J.js → Badge-BGMSp22s.js} +1 -1
  68. package/web-app/dist/assets/{Button-709-wwGM.js → Button-pYJc32tG.js} +1 -1
  69. package/web-app/dist/assets/{CockpitPage-BapdTInO.js → CockpitPage-C1JBBouA.js} +1 -1
  70. package/web-app/dist/assets/{ComparePage-DT99pGvA.js → ComparePage-qD0NV4RK.js} +1 -1
  71. package/web-app/dist/assets/{ErrorBoundary-B0uJSys8.js → ErrorBoundary-C-yxO58j.js} +1 -1
  72. package/web-app/dist/assets/{EvidenceReceiptPanel-BtMFgElP.js → EvidenceReceiptPanel-ByPzuE1S.js} +1 -1
  73. package/web-app/dist/assets/{GitHubIssuesPanel-DjJuU941.js → GitHubIssuesPanel-BNv_2BEJ.js} +1 -1
  74. package/web-app/dist/assets/{GitHubPRsPanel-HXfEX1BC.js → GitHubPRsPanel-CuLZQ9CS.js} +1 -1
  75. package/web-app/dist/assets/{HomePage-Cqyves-S.js → HomePage-CS_FW8Je.js} +2 -2
  76. package/web-app/dist/assets/{LoginPage-jUbtC7_Y.js → LoginPage-DwiNf1Wm.js} +1 -1
  77. package/web-app/dist/assets/{MagicPage-DHRsqjgR.js → MagicPage-CmQybLPZ.js} +1 -1
  78. package/web-app/dist/assets/{MetricsPage-B-pED05R.js → MetricsPage-D9ERdpv8.js} +1 -1
  79. package/web-app/dist/assets/{NotFoundPage-CsbUs21R.js → NotFoundPage-D9ccgdbF.js} +1 -1
  80. package/web-app/dist/assets/{ProjectPage-BtlLEQwB.js → ProjectPage-B9Jslmgv.js} +3 -3
  81. package/web-app/dist/assets/{ProjectsPage-DaRbRjSp.js → ProjectsPage-DnU99Go2.js} +1 -1
  82. package/web-app/dist/assets/{SettingsPage-DALrtP3F.js → SettingsPage-DcTyx6wo.js} +1 -1
  83. package/web-app/dist/assets/{ShowcasePage-CoPHSNP0.js → ShowcasePage-jEY9ghSr.js} +1 -1
  84. package/web-app/dist/assets/{SystemSettingsPage-kOuSN50O.js → SystemSettingsPage-CSGltW-8.js} +1 -1
  85. package/web-app/dist/assets/{TeamsPage-DGWK0LMp.js → TeamsPage-gUuJXb9l.js} +1 -1
  86. package/web-app/dist/assets/{TemplatesPage-C-_10CwA.js → TemplatesPage-37Zhe--v.js} +1 -1
  87. package/web-app/dist/assets/{TerminalOutput-Cz_vPbXL.js → TerminalOutput-B09hRwaR.js} +1 -1
  88. package/web-app/dist/assets/{activity-icIsXJVa.js → activity-q_4MY9J0.js} +1 -1
  89. package/web-app/dist/assets/{bell-DbPR2_4r.js → bell-X8OC_PFP.js} +1 -1
  90. package/web-app/dist/assets/{bot-DvjP3Yh5.js → bot-C-N1PUOD.js} +1 -1
  91. package/web-app/dist/assets/{check-D1OBxHeU.js → check-B2c76Jmp.js} +1 -1
  92. package/web-app/dist/assets/{chevron-left-D09jDZ29.js → chevron-left-BF76YLNw.js} +1 -1
  93. package/web-app/dist/assets/{circle-alert-DyZMLOcC.js → circle-alert-AeFKBD8h.js} +1 -1
  94. package/web-app/dist/assets/{clock-CdpdJTId.js → clock-CiXRf-FH.js} +1 -1
  95. package/web-app/dist/assets/{cloud-CoLTlmoj.js → cloud-DKQ8SRJ5.js} +1 -1
  96. package/web-app/dist/assets/{code-xml-DrkE2ZZY.js → code-xml-DEb6jXWP.js} +1 -1
  97. package/web-app/dist/assets/{database-CMQQuM2R.js → database-6W7e-E9c.js} +1 -1
  98. package/web-app/dist/assets/{dollar-sign-CuioDaOG.js → dollar-sign-Ds2GOuyW.js} +1 -1
  99. package/web-app/dist/assets/{file-code-corner-Cx9j9K1S.js → file-code-corner-CZU0jg52.js} +1 -1
  100. package/web-app/dist/assets/{file-plus-CYD2zaaI.js → file-plus-DTVYnXCN.js} +1 -1
  101. package/web-app/dist/assets/{globe-6w-hnQkL.js → globe-0mUzKYDe.js} +1 -1
  102. package/web-app/dist/assets/{hammer-BpqtautL.js → hammer-BMHZg3YX.js} +1 -1
  103. package/web-app/dist/assets/{index-Dzs-9EgJ.js → index-BvOTvjax.js} +3 -3
  104. package/web-app/dist/assets/{layers-D5hd7jb7.js → layers-Df7TdcWB.js} +1 -1
  105. package/web-app/dist/assets/{loader-circle-C6Y9x_7H.js → loader-circle-kPsVfrSY.js} +1 -1
  106. package/web-app/dist/assets/{lock-D51L7rYh.js → lock-CXYcbEc5.js} +1 -1
  107. package/web-app/dist/assets/{package-BJF8hkP8.js → package-Cqhua1ID.js} +1 -1
  108. package/web-app/dist/assets/{plus-D5kUQ79n.js → plus-lseXhsn-.js} +1 -1
  109. package/web-app/dist/assets/{refresh-cw-BKdlCa7F.js → refresh-cw-OZriI6gD.js} +1 -1
  110. package/web-app/dist/assets/{rotate-ccw-_bd_YI1e.js → rotate-ccw-CKCfjPg8.js} +1 -1
  111. package/web-app/dist/assets/{scroll-text-DbkegIxF.js → scroll-text-DoVyyfKb.js} +1 -1
  112. package/web-app/dist/assets/{server-DAwqFIsq.js → server-DGy7TflW.js} +1 -1
  113. package/web-app/dist/assets/{shield-alert-BuR1tPHu.js → shield-alert-BCzRcMXi.js} +1 -1
  114. package/web-app/dist/assets/{thumbs-up-BF6p3mRd.js → thumbs-up-C_znJd5g.js} +1 -1
  115. package/web-app/dist/assets/{trash-2-BVpnkqqz.js → trash-2-BosCbeeA.js} +1 -1
  116. package/web-app/dist/assets/{trending-down-DpsDvRwW.js → trending-down-CkR5zDkV.js} +1 -1
  117. package/web-app/dist/assets/{trending-up-mFogTjt3.js → trending-up-DpUIfOH4.js} +1 -1
  118. package/web-app/dist/assets/{upload-4T8zpBsi.js → upload-CH9aKc8L.js} +1 -1
  119. package/web-app/dist/assets/{usePolling-DPXQv8WO.js → usePolling-pzxrHJeg.js} +1 -1
  120. package/web-app/dist/assets/{user-BYGIFaTm.js → user-B7OHa-VQ.js} +1 -1
  121. package/web-app/dist/index.html +1 -1
  122. package/dashboard/run.py +0 -63
  123. package/docs/openclaw-integration.md +0 -52
  124. package/docs/v10/HANDOFF-NEXT-WEEK.md +0 -40
  125. package/docs/v10/MORNING-BRIEF-1003.md +0 -25
  126. package/docs/v10/MORNING-BRIEF.md +0 -52
  127. package/docs/v10/TODO.md +0 -35
  128. package/mcp/resources.py +0 -351
  129. package/mcp/tools.py +0 -200
package/autonomy/loki CHANGED
@@ -71,6 +71,15 @@ if [ -f "$_LOKI_SCRIPT_DIR/lib/lock.sh" ]; then
71
71
  source "$_LOKI_SCRIPT_DIR/lib/lock.sh"
72
72
  fi
73
73
 
74
+ # Default per-run spend cap (single source: default_max_cost_usd in loki-ts/data/model-pricing.json,
75
+ # also read by loki-ts/src/e10ext/budget_cap.ts and autonomy/run.sh). Prints a plain number; 100 only if the file is unreadable.
76
+ loki_budget_default_usd() {
77
+ local v=""
78
+ [ -f "$_LOKI_SCRIPT_DIR/../loki-ts/data/model-pricing.json" ] && \
79
+ v="$(sed -n 's/.*"default_max_cost_usd"[[:space:]]*:[[:space:]]*\([0-9][0-9.]*\).*/\1/p' "$_LOKI_SCRIPT_DIR/../loki-ts/data/model-pricing.json" | head -1)"
80
+ printf '%s\n' "${v:-100}"
81
+ }
82
+
74
83
  # Guarded browser opener (loki_open_url). Every browser open goes through it
75
84
  # so tests, CI and non-TTY runs never pop a tab. If missing, nothing opens.
76
85
  if [ -f "$_LOKI_SCRIPT_DIR/lib/browser-open.sh" ]; then
@@ -1838,7 +1847,7 @@ show_help_all() {
1838
1847
  echo " --spec FILE Run against an edited spec (written by 'loki plan <task> --spec'; see also --spec-first)"
1839
1848
  echo " --fresh-prd Regenerate the PRD from the codebase (no-PRD runs; ignores the reusable generated PRD)"
1840
1849
  echo " --compliance PRESET Enable compliance mode (default|healthcare|fintech|government)"
1841
- echo " --budget USD Set cost budget limit (display in dashboard/status)"
1850
+ echo " --budget USD Per-run cost cap in USD (default $(loki_budget_default_usd); auto-pause when exceeded; see docs/cost-controls.md)"
1842
1851
  echo " --bmad-project PATH Use BMAD Method project artifacts as input"
1843
1852
  echo " --openspec PATH Use OpenSpec change directory as input"
1844
1853
  echo " --config PATH Load settings from a config file (.yaml/.json/.env). Alias: --env-file, --vars"
@@ -2608,7 +2617,7 @@ cmd_start() {
2608
2617
  echo " (ignores the reusable generated PRD; aliases: --regen-prd,"
2609
2618
  echo " --regenerate-prd, --regen, or LOKI_PRD_REGEN=1)"
2610
2619
  echo " --compliance PRESET Enable compliance mode (default|healthcare|fintech|government)"
2611
- echo " --budget USD Cost budget limit (auto-pause when exceeded)"
2620
+ echo " --budget USD Per-run cost cap (default $(loki_budget_default_usd); auto-pause when exceeded)"
2612
2621
  echo " --bmad-project PATH Use BMAD Method project artifacts as input"
2613
2622
  echo " --openspec PATH Use OpenSpec change directory as input"
2614
2623
  echo " --mirofish [URL] Enable MiroFish market validation (default: localhost:5001)"
@@ -2651,7 +2660,7 @@ cmd_start() {
2651
2660
  echo " LOKI_AUTO_CONFIRM Set to 'true'/'false' to control prompts (takes precedence over CI)"
2652
2661
  echo " CI Fallback: auto-confirms when 'true' and LOKI_AUTO_CONFIRM is unset"
2653
2662
  echo " LOKI_MAX_ITERATIONS Max iteration count"
2654
- echo " LOKI_BUDGET_LIMIT Cost budget limit in USD"
2663
+ echo " LOKI_BUDGET_LIMIT Cost cap in USD (default $(loki_budget_default_usd); empty means unlimited)"
2655
2664
  echo ""
2656
2665
  echo "Examples:"
2657
2666
  echo " loki start # Interactive, analyze current dir"
@@ -3049,7 +3058,7 @@ cmd_start() {
3049
3058
  echo -e "${RED}--budget must be greater than 0 (a 0 budget pauses before any work)${NC}" >&2
3050
3059
  exit 1
3051
3060
  fi
3052
- export LOKI_BUDGET_LIMIT="$2"
3061
+ export LOKI_BUDGET_LIMIT="$2" LOKI_BUDGET_SOURCE=flag
3053
3062
  shift 2
3054
3063
  else
3055
3064
  echo -e "${RED}--budget requires a USD amount (e.g., --budget 5.00)${NC}"
@@ -3067,7 +3076,7 @@ cmd_start() {
3067
3076
  echo -e "${RED}--budget must be greater than 0 (a 0 budget pauses before any work)${NC}" >&2
3068
3077
  exit 1
3069
3078
  fi
3070
- export LOKI_BUDGET_LIMIT="$budget_val"
3079
+ export LOKI_BUDGET_LIMIT="$budget_val" LOKI_BUDGET_SOURCE=flag
3071
3080
  shift
3072
3081
  ;;
3073
3082
  # v6.84.0: explicit mode flags (override auto-detection)
@@ -3956,15 +3965,8 @@ cmd_start() {
3956
3965
  if ! command -v "$effective_provider" &>/dev/null; then
3957
3966
  echo -e "${RED}Error: Provider CLI '$effective_provider' is not installed.${NC}"
3958
3967
  echo ""
3959
- echo "Install it first:"
3960
- case "$effective_provider" in
3961
- claude) echo " npm install -g @anthropic-ai/claude-code" ;;
3962
- codex) echo " npm install -g @openai/codex" ;;
3963
- cline) echo " npm install -g cline" ;;
3964
- aider) echo " pip install aider-chat" ;;
3965
- opencode) echo " npm install -g opencode-ai" ;;
3966
- *) echo " Check the provider documentation for installation." ;;
3967
- esac
3968
+ echo "Install it first (same fix as loki doctor):"
3969
+ _loki_req_fix_line "$effective_provider"
3968
3970
  echo ""
3969
3971
  echo "Check your environment: loki doctor"
3970
3972
  exit 1
@@ -5170,6 +5172,17 @@ cmd_resume() {
5170
5172
  exit 0
5171
5173
  fi
5172
5174
 
5175
+ # RECOVERY: an interrupted engine10 run is resumed at its phase by loki-ts/src/util/run_outputs.ts
5176
+ # (exit 0 resumed, 4 phase reported, 5 publish failed, anything else: nothing for it, fall through).
5177
+ if ls "$LOKI_DIR"/runs/e10-*/events.jsonl >/dev/null 2>&1; then
5178
+ local _e10_rc=0
5179
+ ( _loki_bun_delegate engine10 resume "$@" ) || _e10_rc=$?
5180
+ case "$_e10_rc" in
5181
+ 0 | 4) return 0 ;;
5182
+ 5) return 1 ;;
5183
+ esac
5184
+ fi
5185
+
5173
5186
  # Check if there is anything to resume before touching signal files
5174
5187
  local has_pause_signal=false
5175
5188
  local has_stop_signal=false
@@ -7119,8 +7132,14 @@ cmd_provider() {
7119
7132
  cmd_provider_list
7120
7133
  ;;
7121
7134
  info)
7135
+ case " $* " in
7136
+ *" --json "*) _loki_bun_delegate provider info "$@" || { echo "loki provider info --json needs bun: ${_LOKI_DELEGATE_WHY}" >&2; return 1; } ;;
7137
+ esac
7122
7138
  cmd_provider_info "$@"
7123
7139
  ;;
7140
+ matrix)
7141
+ _loki_bun_delegate provider matrix "$@" || { echo "loki provider matrix needs bun: ${_LOKI_DELEGATE_WHY}" >&2; return 1; }
7142
+ ;;
7124
7143
  models)
7125
7144
  cmd_provider_models
7126
7145
  ;;
@@ -7133,7 +7152,8 @@ cmd_provider() {
7133
7152
  echo " show Show current provider (default)"
7134
7153
  echo " set Set provider for this project"
7135
7154
  echo " list List available providers"
7136
- echo " info Show provider details"
7155
+ echo " info Show provider details (--json: capability contract)"
7156
+ echo " matrix Show the capability matrix per provider and engine"
7137
7157
  echo " models Show resolved model configuration for all providers"
7138
7158
  echo ""
7139
7159
  echo "Precedence (highest wins):"
@@ -8273,7 +8293,7 @@ PURPLE_LAB_STATE_DIR="${HOME}/.loki/purple-lab"
8273
8293
  PURPLE_LAB_PID_FILE="${PURPLE_LAB_STATE_DIR}/purple-lab.pid"
8274
8294
 
8275
8295
  # Is this pid positively identified as THIS installation's Purple Lab server
8276
- # (cmd_web_start launches exactly `$DASHBOARD_PYTHON $SKILL_DIR/web-app/server.py`
8296
+ # (a server started before the web deprecation launched exactly `$DASHBOARD_PYTHON $SKILL_DIR/web-app/server.py`
8277
8297
  # at :8078), rather than a recycled pid, a decoy that merely mentions the path
8278
8298
  # in an argument, or another installation's server.py at a different SKILL_DIR?
8279
8299
  #
@@ -8486,205 +8506,6 @@ cmd_web_help() {
8486
8506
  echo " loki web stop Stop a standalone web server started the old way"
8487
8507
  }
8488
8508
 
8489
- cmd_web_start() {
8490
- local open_browser=true
8491
- local port="${PURPLE_LAB_DEFAULT_PORT}"
8492
- local prd_file=""
8493
-
8494
- # Parse arguments
8495
- while [[ $# -gt 0 ]]; do
8496
- case "$1" in
8497
- --no-open)
8498
- open_browser=false
8499
- shift
8500
- ;;
8501
- --port)
8502
- if [[ -z "${2:-}" ]]; then
8503
- echo -e "${RED}--port requires a port number${NC}"
8504
- exit 1
8505
- fi
8506
- port="$2"
8507
- shift 2
8508
- ;;
8509
- --prd)
8510
- if [[ -z "${2:-}" ]]; then
8511
- echo -e "${RED}--prd requires a file path${NC}"
8512
- exit 1
8513
- fi
8514
- prd_file="$2"
8515
- shift 2
8516
- ;;
8517
- --help|-h)
8518
- cmd_web_help
8519
- exit 0
8520
- ;;
8521
- *)
8522
- echo -e "${RED}Unknown option: $1${NC}"
8523
- echo "Run 'loki web --help' for usage."
8524
- exit 1
8525
- ;;
8526
- esac
8527
- done
8528
-
8529
- # Validate port: must be numeric and in valid range
8530
- if [[ ! "$port" =~ ^[0-9]+$ ]]; then
8531
- echo -e "${RED}Error: Port must be a number, got '$port'${NC}"
8532
- exit 1
8533
- fi
8534
- if [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
8535
- echo -e "${RED}Error: Port must be between 1 and 65535, got $port${NC}"
8536
- exit 1
8537
- fi
8538
- # Port validation moved below (after PID file check) so we can offer to kill orphans
8539
-
8540
- # Validate --prd file if provided
8541
- if [ -n "$prd_file" ]; then
8542
- if [ ! -f "$prd_file" ]; then
8543
- echo -e "${RED}Error: PRD file not found: $prd_file${NC}"
8544
- exit 1
8545
- fi
8546
- export PURPLE_LAB_PRD
8547
- PURPLE_LAB_PRD=$(cat "$prd_file")
8548
- fi
8549
-
8550
- # Check that web-app dist exists
8551
- local web_dist="${SKILL_DIR}/web-app/dist"
8552
- if [ ! -d "$web_dist" ] || [ ! -f "$web_dist/index.html" ]; then
8553
- echo -e "${RED}Error: Web app not built${NC}"
8554
- echo "Expected files at: $web_dist"
8555
- echo ""
8556
- echo "Build the web app first:"
8557
- echo " cd web-app && npm install && npm run build"
8558
- exit 1
8559
- fi
8560
-
8561
- # Check that server.py exists
8562
- local server_py="${SKILL_DIR}/web-app/server.py"
8563
- if [ ! -f "$server_py" ]; then
8564
- echo -e "${RED}Error: Purple Lab server not found at $server_py${NC}"
8565
- exit 1
8566
- fi
8567
-
8568
- # Ensure Python venv with FastAPI is available
8569
- if ! ensure_dashboard_venv; then
8570
- echo -e "${RED}Error: Failed to set up Python environment${NC}"
8571
- exit 1
8572
- fi
8573
-
8574
- # Check if already running
8575
- mkdir -p "${LOKI_DIR}/purple-lab"
8576
- if [ -f "$PURPLE_LAB_PID_FILE" ]; then
8577
- local existing_pid
8578
- existing_pid=$(cat "$PURPLE_LAB_PID_FILE" 2>/dev/null)
8579
- if [ -n "$existing_pid" ] && kill -0 "$existing_pid" 2>/dev/null; then
8580
- echo -e "${GREEN}Purple Lab already running (PID: $existing_pid)${NC}"
8581
- local url="http://${PURPLE_LAB_DEFAULT_HOST}:${port}/lab/"
8582
- echo -e "Open: ${CYAN}$url${NC}"
8583
- if [ "$open_browser" = true ]; then
8584
- loki_open_url "$url" 2>/dev/null || true
8585
- fi
8586
- return 0
8587
- fi
8588
- fi
8589
-
8590
- # Check if port is already in use -- try to clean up orphaned processes.
8591
- # Only kill it if it is positively identified as THIS installation's own
8592
- # Purple Lab server (never a bare "it's listening on our port" guess): an
8593
- # unrelated process bound to the same port, or another installation's
8594
- # server, must never be signaled.
8595
- if lsof -i:"$port" -sTCP:LISTEN &> /dev/null; then
8596
- local blocking_pid
8597
- blocking_pid=$(lsof -ti:"$port" -sTCP:LISTEN 2>/dev/null | head -1)
8598
- if [ -n "$blocking_pid" ]; then
8599
- if _loki_pid_looks_like_purplelab "$blocking_pid"; then
8600
- echo -e "${YELLOW}Port $port in use by PID $blocking_pid -- stopping it...${NC}"
8601
- kill "$blocking_pid" 2>/dev/null
8602
- sleep 1
8603
- if kill -0 "$blocking_pid" 2>/dev/null; then
8604
- kill -9 "$blocking_pid" 2>/dev/null
8605
- sleep 1
8606
- fi
8607
- # Verify port is now free
8608
- if lsof -i:"$port" -sTCP:LISTEN &> /dev/null; then
8609
- echo -e "${RED}Error: Could not free port $port${NC}"
8610
- echo "Use --port to specify a different port, or manually kill the process."
8611
- exit 1
8612
- fi
8613
- echo -e "${GREEN}Port $port freed.${NC}"
8614
- else
8615
- echo -e "${RED}Error: Port $port is in use by PID $blocking_pid, which is not this installation's Purple Lab server.${NC}"
8616
- echo "Use --port to specify a different port, or manually stop that process."
8617
- exit 1
8618
- fi
8619
- fi
8620
- fi
8621
-
8622
- # Start the server
8623
- local log_dir="${LOKI_DIR}/purple-lab/logs"
8624
- mkdir -p "$log_dir"
8625
- local log_file="$log_dir/purple-lab.log"
8626
-
8627
- echo -e "${CYAN}Starting Purple Lab...${NC}"
8628
-
8629
- PURPLE_LAB_PORT="$port" PURPLE_LAB_HOST="${PURPLE_LAB_DEFAULT_HOST}" \
8630
- LOKI_DIR="$LOKI_DIR" LOKI_SKILL_DIR="$SKILL_DIR" PYTHONPATH="$SKILL_DIR" \
8631
- nohup "$DASHBOARD_PYTHON" "$server_py" > "$log_file" 2>&1 &
8632
- local pid=$!
8633
-
8634
- mkdir -p "$PURPLE_LAB_STATE_DIR"
8635
- echo "$pid" > "$PURPLE_LAB_PID_FILE"
8636
- echo "$port" > "${PURPLE_LAB_STATE_DIR}/port"
8637
- # Also write to local .loki for backward compat
8638
- mkdir -p "${LOKI_DIR}/purple-lab"
8639
- echo "$pid" > "${LOKI_DIR}/purple-lab/purple-lab.pid"
8640
- echo "$port" > "${LOKI_DIR}/purple-lab/port"
8641
-
8642
- # Wait for server to be ready
8643
- local retries=0
8644
- local server_ready=false
8645
- while [ $retries -lt 15 ]; do
8646
- if curl -s "http://${PURPLE_LAB_DEFAULT_HOST}:${port}/lab/api/session/status" > /dev/null 2>&1; then
8647
- server_ready=true
8648
- break
8649
- fi
8650
- sleep 0.5
8651
- retries=$((retries + 1))
8652
- done
8653
-
8654
- if ! kill -0 "$pid" 2>/dev/null; then
8655
- echo -e "${RED}Error: Purple Lab failed to start${NC}"
8656
- echo "Check logs at: $log_file"
8657
- rm -f "$PURPLE_LAB_PID_FILE"
8658
- exit 1
8659
- fi
8660
-
8661
- local url="http://${PURPLE_LAB_DEFAULT_HOST}:${port}/lab/"
8662
-
8663
- if [ "$server_ready" = true ]; then
8664
- echo -e "${GREEN}Purple Lab (project web UI) running at: $url${NC} (PID: $pid)"
8665
- else
8666
- echo -e "${YELLOW}Purple Lab (project web UI) starting at: $url${NC} (PID: $pid)"
8667
- echo "Server may still be loading. Refresh the browser if it does not load immediately."
8668
- fi
8669
- echo -e "Logs: $log_file"
8670
- # v7.28.x UX #5: distinguish the two browser UIs so the ops monitor and the
8671
- # spec-input web UI are never confused for each other.
8672
- echo -e "${DIM}Looking for the live run monitor? loki dashboard (:${DASHBOARD_DEFAULT_PORT:-57374})${NC}"
8673
- echo -e "Stop with: ${CYAN}loki web stop${NC}"
8674
-
8675
- # Open browser only after server is confirmed ready
8676
- if [ "$open_browser" = true ]; then
8677
- if [ "$server_ready" = true ]; then
8678
- echo ""
8679
- loki_open_url "$url" 2>/dev/null || echo "Please open in browser: $url"
8680
- else
8681
- echo ""
8682
- echo -e "${YELLOW}Browser not opened because server is not ready yet.${NC}"
8683
- echo "Open manually when ready: $url"
8684
- fi
8685
- fi
8686
- }
8687
-
8688
8509
  cmd_web_stop() {
8689
8510
  local stopped=false
8690
8511
  local port
@@ -10865,7 +10686,7 @@ cmd_run() {
10865
10686
  echo " --no-dashboard Disable web dashboard"
10866
10687
  echo " --sandbox Run in Docker sandbox (default: off; requires Docker)"
10867
10688
  echo " --no-plan Skip auto-shown PRD analysis at startup"
10868
- echo " --budget USD Set cost budget limit"
10689
+ echo " --budget USD Per-run cost cap (default $(loki_budget_default_usd))"
10869
10690
  echo ""
10870
10691
  echo "Progressive Isolation:"
10871
10692
  echo " --worktree, -w Git worktree isolation (separate branch)"
@@ -12431,6 +12252,7 @@ cmd_export() {
12431
12252
  echo " markdown Human-readable session summary"
12432
12253
  echo " csv Task queue as CSV"
12433
12254
  echo " timeline Iteration timeline as JSON"
12255
+ echo " vibe-kanban Sync .loki/queue tasks into the Vibe Kanban SQLite DB"
12434
12256
  echo ""
12435
12257
  echo "Flags:"
12436
12258
  echo " --sarif [run-id] Write .loki/runs/<id>/findings.sarif (SARIF 2.1.0; needs bun)"
@@ -12441,6 +12263,7 @@ cmd_export() {
12441
12263
  echo " loki export markdown # Readable summary"
12442
12264
  echo " loki export csv tasks.csv # Task queue CSV"
12443
12265
  echo " loki export timeline # Iteration timeline"
12266
+ echo " loki export vibe-kanban # Sync tasks to Vibe Kanban"
12444
12267
  exit 0
12445
12268
  ;;
12446
12269
  json)
@@ -12457,13 +12280,21 @@ cmd_export() {
12457
12280
  require_jq || return 1
12458
12281
  _export_timeline "$output_path"
12459
12282
  ;;
12283
+ vibe-kanban)
12284
+ local vk_script="${SKILL_DIR:-}/scripts/sync-to-vibe-kanban.sh"
12285
+ if [ ! -f "$vk_script" ]; then
12286
+ echo -e "${RED}Error: sync-to-vibe-kanban.sh not found at $vk_script${NC}" >&2
12287
+ return 1
12288
+ fi
12289
+ bash "$vk_script"
12290
+ ;;
12460
12291
  --sarif)
12461
12292
  echo -e "${RED}loki export --sarif needs bun (https://bun.sh).${NC}" >&2
12462
12293
  return 1
12463
12294
  ;;
12464
12295
  *)
12465
12296
  echo -e "${RED}Unknown format: $format${NC}"
12466
- echo "Supported: json, markdown, csv, timeline"
12297
+ echo "Supported: json, markdown, csv, timeline, vibe-kanban"
12467
12298
  exit 1
12468
12299
  ;;
12469
12300
  esac
@@ -13475,6 +13306,100 @@ cmd_config_path() {
13475
13306
  echo "Create a config file with: loki config init"
13476
13307
  }
13477
13308
 
13309
+ # Merge the Loki plugin into a Claude Code settings.json (loki setup-skill --claude-code).
13310
+ # Existing keys are never overwritten, invalid JSON is refused (rc 3), writes are atomic.
13311
+ _setup_skill_claude_code() {
13312
+ local print_only=0 project=0 stop=0 settings=""
13313
+ while [ $# -gt 0 ]; do
13314
+ case "$1" in
13315
+ --print) print_only=1 ;;
13316
+ --project) project=1 ;;
13317
+ --stop-hook) stop=1 ;;
13318
+ --settings) settings="${2:-}"; shift ;;
13319
+ --help|-h)
13320
+ echo "Usage: loki setup-skill --claude-code [--print] [--project] [--stop-hook] [--settings FILE]"
13321
+ echo ""
13322
+ echo "Registers the Loki plugin marketplace and enables the plugin in Claude Code"
13323
+ echo "settings (default ~/.claude/settings.json; --project uses ./.claude/settings.json)."
13324
+ echo "Merges only: existing keys are kept, invalid JSON is refused."
13325
+ echo ""
13326
+ echo " --print Print the settings snippet and change nothing"
13327
+ echo " --stop-hook Also opt in to the Stop-hook fast verifier (LOKI_STOP_VERIFY=1)"
13328
+ return 0
13329
+ ;;
13330
+ *) echo "Unknown option: $1" >&2; return 2 ;;
13331
+ esac
13332
+ shift
13333
+ done
13334
+ command -v python3 >/dev/null 2>&1 || { echo "python3 is required" >&2; return 1; }
13335
+ if [ -z "$settings" ] && [ "$print_only" -eq 0 ]; then
13336
+ if [ "$project" -eq 1 ]; then
13337
+ settings="${TARGET_DIR:-.}/.claude/settings.json"
13338
+ else
13339
+ settings="$HOME/.claude/settings.json"
13340
+ fi
13341
+ fi
13342
+ local out rc
13343
+ out="$(python3 - "$settings" "$stop" "$print_only" <<'PY'
13344
+ import json, os, sys, tempfile
13345
+ path, stop, print_only = sys.argv[1], sys.argv[2] == "1", sys.argv[3] == "1"
13346
+ want = {
13347
+ "extraKnownMarketplaces": {
13348
+ "loki-mode": {"source": {"source": "github", "repo": "asklokesh/loki-mode"}}
13349
+ },
13350
+ "enabledPlugins": {"loki-mode@loki-mode": True},
13351
+ }
13352
+ if stop:
13353
+ want["env"] = {"LOKI_STOP_VERIFY": "1"}
13354
+ if print_only:
13355
+ print(json.dumps(want, indent=2))
13356
+ sys.exit(0)
13357
+ cur = {}
13358
+ if os.path.exists(path):
13359
+ try:
13360
+ raw = open(path).read()
13361
+ cur = json.loads(raw) if raw.strip() else {}
13362
+ except Exception as e:
13363
+ print("REFUSED %s is not valid JSON (%s); fix or move it aside" % (path, e))
13364
+ sys.exit(3)
13365
+ if not isinstance(cur, dict):
13366
+ print("REFUSED %s top level is not an object" % path)
13367
+ sys.exit(3)
13368
+ added = []
13369
+ for top, kv in want.items():
13370
+ node = cur.get(top)
13371
+ if node is None:
13372
+ cur[top] = {}
13373
+ node = cur[top]
13374
+ elif not isinstance(node, dict):
13375
+ print("KEPT %s (existing value is not an object)" % top)
13376
+ continue
13377
+ for k, v in kv.items():
13378
+ if k in node:
13379
+ print("KEPT %s.%s" % (top, k))
13380
+ else:
13381
+ node[k] = v
13382
+ added.append("%s.%s" % (top, k))
13383
+ print("ADDED %s.%s" % (top, k))
13384
+ if added:
13385
+ d = os.path.dirname(os.path.abspath(path))
13386
+ os.makedirs(d, exist_ok=True)
13387
+ fd, tmp = tempfile.mkstemp(dir=d, prefix=".settings.", suffix=".tmp")
13388
+ with os.fdopen(fd, "w") as f:
13389
+ json.dump(cur, f, indent=2)
13390
+ f.write("\n")
13391
+ os.replace(tmp, path)
13392
+ else:
13393
+ print("UNCHANGED %s" % path)
13394
+ PY
13395
+ )"
13396
+ rc=$?
13397
+ printf '%s\n' "$out"
13398
+ [ "$rc" -eq 0 ] || return "$rc"
13399
+ [ "$print_only" -eq 1 ] || echo "Next: in Claude Code run /reload-plugins, then try /loki-mode:start, /loki-mode:loki-verify, /loki-mode:handoff."
13400
+ return 0
13401
+ }
13402
+
13478
13403
  # Set up skill symlinks for all providers
13479
13404
  cmd_setup_skill() {
13480
13405
  # v7.6.2 B-13 fix: --help must print help, not install skills.
@@ -13488,11 +13413,17 @@ cmd_setup_skill() {
13488
13413
  echo "skills directory: ~/.claude/skills/, ~/.codex/skills/, etc."
13489
13414
  echo ""
13490
13415
  echo "Options:"
13416
+ echo " --claude-code Merge the Loki plugin into Claude Code settings (see --claude-code --help)"
13491
13417
  echo " --help, -h Show this help and exit"
13492
13418
  echo ""
13493
13419
  echo "Side effects: writes symlinks/files under ~/.<provider>/skills/loki-mode/."
13494
13420
  return 0
13495
13421
  ;;
13422
+ --claude-code)
13423
+ shift
13424
+ _setup_skill_claude_code "$@"
13425
+ return $?
13426
+ ;;
13496
13427
  esac
13497
13428
  echo -e "${BOLD}Loki Mode Skill Setup${NC}"
13498
13429
  echo ""
@@ -14080,6 +14011,18 @@ _loki_bun_delegate() {
14080
14011
  exec "$bun_bin" "$cli" "$cmd" "$@"
14081
14012
  }
14082
14013
 
14014
+ # One-line fix for a requirement id (a provider name is an id), read from the shared
14015
+ # table autonomy/lib/setup-requirements.json so runtime errors match loki doctor.
14016
+ _loki_req_fix_line() {
14017
+ local _id="$1" _fix=""
14018
+ _fix="$(jq -r --arg id "$_id" '.requirements[] | select(.id == $id) | .fix' "${SKILL_DIR}/autonomy/lib/setup-requirements.json" 2>/dev/null | head -n 1)" || _fix=""
14019
+ if [ -n "$_fix" ]; then
14020
+ echo " $_fix"
14021
+ else
14022
+ echo " Check the provider documentation for installation."
14023
+ fi
14024
+ }
14025
+
14083
14026
  # loki doctor. The one implementation is loki-ts/src/commands/doctor.ts; this is a
14084
14027
  # delegator. Only --airgap stays in bash (_loki_airgap_audit). If the Bun route is
14085
14028
  # unavailable we print a minimal diagnostic (never a second copy of the checks).
@@ -14125,7 +14068,12 @@ cmd_doctor_json() {
14125
14068
  why="${why//\\/\\\\}"; why="${why//\"/\\\"}"
14126
14069
  fix="${fix//\\/\\\\}"; fix="${fix//\"/\\\"}"
14127
14070
  ver="${ver//\\/\\\\}"; ver="${ver//\"/\\\"}"
14128
- printf '{"loki_mode_version":"%s","checks":[],"provider_availability":null,"disk":null,"ai_provider":null,"skills":[],"sentrux":null,"memory":null,"model_catalog":null,"summary":{"passed":0,"failed":1,"warnings":0,"ok":false},"route":"unavailable","reason":"%s","fix":"%s"}\n' "$ver" "$why" "$fix"
14071
+ local reqs=null
14072
+ if declare -F loki_requirements_json >/dev/null 2>&1; then
14073
+ reqs="$(loki_requirements_json 2>/dev/null | jq -c . 2>/dev/null)" || reqs=null
14074
+ [ -n "$reqs" ] || reqs=null
14075
+ fi
14076
+ printf '{"loki_mode_version":"%s","checks":[],"provider_availability":null,"disk":null,"ai_provider":null,"skills":[],"sentrux":null,"memory":null,"model_catalog":null,"requirements":%s,"summary":{"passed":0,"failed":1,"warnings":0,"ok":false},"route":"unavailable","reason":"%s","fix":"%s"}\n' "$ver" "$reqs" "$why" "$fix"
14129
14077
  }
14130
14078
 
14131
14079
  # Architectural-drift gate (v7.5.14, opt-in).
@@ -15686,15 +15634,6 @@ cmd_quick() {
15686
15634
  exit 64
15687
15635
  fi
15688
15636
 
15689
- # v7.29.0: provider pre-flight gate. Offer install on a TTY; print honest
15690
- # manual instructions and exit 2 on non-TTY/CI, before any spend. --help and
15691
- # the no-args usage path already returned/exited above.
15692
- if declare -f provider_offer_gate >/dev/null 2>&1; then
15693
- if ! provider_offer_gate; then
15694
- exit 2
15695
- fi
15696
- fi
15697
-
15698
15637
  # #691: drop the unified config-file aliases from the task description. The
15699
15638
  # main() pre-pass already applied the file; without this, a trailing
15700
15639
  # `--config f.env` would be swept into task_desc (cmd_quick has no arg loop).
@@ -15711,6 +15650,12 @@ cmd_quick() {
15711
15650
  --verbose|-v) if [ "$_quick_task_seen" = "false" ]; then export LOKI_VERBOSE=1; continue; fi ;;
15712
15651
  --config|--vars|--env-file) _skip_next=true; continue ;;
15713
15652
  --config=*|--vars=*|--env-file=*) continue ;;
15653
+ # LM-006: a leading unknown --flag is a usage error, never task text (a typo must not start a paid run).
15654
+ --?*) if [ "$_quick_task_seen" = "false" ] &&[[ "$_qa" != *" "* ]]; then
15655
+ echo -e "${RED}Error: unknown option: $_qa${NC}" >&2
15656
+ echo "Usage: loki quick [--verbose] \"description of what to do\"" >&2
15657
+ exit 64
15658
+ fi ;;
15714
15659
  esac
15715
15660
  _quick_task_seen=true
15716
15661
  _quick_args+=("$_qa")
@@ -15722,6 +15667,16 @@ cmd_quick() {
15722
15667
  exit 64
15723
15668
  fi
15724
15669
 
15670
+ # v7.29.0: provider pre-flight gate. Offer install on a TTY; print honest
15671
+ # manual instructions and exit 2 on non-TTY/CI, before any spend. --help and
15672
+ # every usage error (no task, unknown option, exit 64) is decided above,
15673
+ # so a host with no provider still reports a typo as 64, not 2.
15674
+ if declare -f provider_offer_gate >/dev/null 2>&1; then
15675
+ if ! provider_offer_gate; then
15676
+ exit 2
15677
+ fi
15678
+ fi
15679
+
15725
15680
  local task_desc="$*"
15726
15681
  local version=$(get_version)
15727
15682
  local max_iter="${LOKI_MAX_ITERATIONS:-3}"
@@ -15786,14 +15741,8 @@ QPRDEOF
15786
15741
  if ! command -v "$_quick_provider" &>/dev/null; then
15787
15742
  echo -e "${RED}Error: Provider CLI '$_quick_provider' is not installed.${NC}"
15788
15743
  echo ""
15789
- echo "Install your AI provider CLI first:"
15790
- case "$_quick_provider" in
15791
- claude) echo " npm install -g @anthropic-ai/claude-code" ;;
15792
- codex) echo " npm install -g @openai/codex" ;;
15793
- cline) echo " npm install -g cline" ;;
15794
- aider) echo " pip install aider-chat" ;;
15795
- *) echo " Check the provider documentation for installation." ;;
15796
- esac
15744
+ echo "Install your AI provider CLI first (same fix as loki doctor):"
15745
+ _loki_req_fix_line "$_quick_provider"
15797
15746
  echo ""
15798
15747
  echo "Then verify: loki doctor"
15799
15748
  exit 1
@@ -20708,8 +20657,33 @@ print()
20708
20657
  # Dry-run PRD analysis and cost estimation (v6.18.0; refactored v6.81.1)
20709
20658
  # Thin wrapper around show_prd_plan() so the same analysis is reusable from
20710
20659
  # `loki start`, `loki run`, and other commands without duplicated code.
20660
+ # Read-only view of .loki/state/requirements-map.json (REQTRACE; contract: docs/spec-first-intent.md).
20661
+ _plan_show_requirements() {
20662
+ local as_json="${1:-false}" map="${LOKI_DIR:-.loki}/state/requirements-map.json"
20663
+ if [ ! -s "$map" ]; then
20664
+ if [ "$as_json" = true ]; then printf '{"error":"no requirements map","path":"%s"}\n' "$map"; return 1; fi
20665
+ echo "No requirements map at $map (written after a run that had a PRD checklist, a spec, or a multi-requirement PRD)."
20666
+ return 1
20667
+ fi
20668
+ if [ "$as_json" = true ]; then cat "$map"; return 0; fi
20669
+ LOKI_RM="$map" python3 -I -c "
20670
+ import json, os, sys
20671
+ try:
20672
+ m = json.load(open(os.environ['LOKI_RM']))
20673
+ reqs = m['requirements']
20674
+ except Exception:
20675
+ print('requirements map is unreadable: ' + os.environ['LOKI_RM']); sys.exit(1)
20676
+ cov = sum(1 for r in reqs if r.get('status') == 'covered')
20677
+ print('Requirements: %d/%d covered (revision %s)' % (cov, len(reqs), str(m.get('revision', ''))[:12]))
20678
+ for r in reqs:
20679
+ chk = (' <- ' + ', '.join(r.get('checks', []))) if r.get('checks') else ''
20680
+ print(' %s %-9s %-9s %s%s' % (r.get('id'), r.get('status'), r.get('method'), str(r.get('text', ''))[:80], chk))
20681
+ "
20682
+ }
20683
+
20711
20684
  cmd_plan() {
20712
20685
  local prd_file=""
20686
+ local show_requirements=false
20713
20687
  local show_json=false
20714
20688
  local show_verbose=false
20715
20689
 
@@ -20726,6 +20700,7 @@ cmd_plan() {
20726
20700
  echo "Options:"
20727
20701
  echo " --json Machine-readable JSON output"
20728
20702
  echo " --spec With a task: write the acceptance criteria to .loki/specs/<slug>.md for you to edit"
20703
+ echo " --requirements Print the last run's requirement-to-check map (.loki/state/requirements-map.json), read-only"
20729
20704
  echo " --verbose Show detailed per-iteration breakdown"
20730
20705
  echo " --help, -h Show this help"
20731
20706
  echo ""
@@ -20743,6 +20718,7 @@ cmd_plan() {
20743
20718
  ;;
20744
20719
  --json) show_json=true; shift ;;
20745
20720
  --verbose) show_verbose=true; shift ;;
20721
+ --requirements) show_requirements=true; shift ;;
20746
20722
  *)
20747
20723
  if [ -z "$prd_file" ]; then
20748
20724
  prd_file="$1"
@@ -20752,6 +20728,11 @@ cmd_plan() {
20752
20728
  esac
20753
20729
  done
20754
20730
 
20731
+ if [ "$show_requirements" = true ]; then
20732
+ _plan_show_requirements "$show_json"
20733
+ return $?
20734
+ fi
20735
+
20755
20736
  if [ -z "$prd_file" ]; then
20756
20737
  # Under --json, a machine consumer expects JSON on every path, including
20757
20738
  # errors -- never ANSI-colored prose. Emit a structured error to stdout.
@@ -36094,7 +36075,7 @@ PYEOF
36094
36075
  }
36095
36076
 
36096
36077
  # loki bench - head-to-head benchmark harness (R2).
36097
- # Subcommands: run <task> | vs <task> | list | verify <result.json>.
36078
+ # Subcommands: run <task> | vs <task> | list | verify <result.json> | attribution.
36098
36079
  # Thin pass-through to benchmarks/bench/run.sh (shared python core runner.py).
36099
36080
  # CREDIBILITY: Loki NEVER grades its own success. Success is decided by a
36100
36081
  # held-out acceptance command run by the grader OUTSIDE the agent (exit code).
@@ -36128,6 +36109,7 @@ cmd_bench() {
36128
36109
  echo " verify <file> Recompute task_hash + check tool versions for a result.json"
36129
36110
  echo " oracles Are the graders trustworthy? Hash-bound, no spend, no provider"
36130
36111
  echo " report <files> Build results.json + RESULTS.md from per-tool result-rows"
36112
+ echo " attribution Same-model harness attribution: raw Claude Code vs Loki (--dry-run, --execute)"
36131
36113
  echo ""
36132
36114
  echo "Options:"
36133
36115
  echo " --trials N Number of trials per tool (default 3)"
@@ -36324,7 +36306,9 @@ cmd_proof() {
36324
36306
  echo " reads UNKNOWN when it cannot be computed"
36325
36307
  echo " phases [--json] Measured phase history from real phase_change"
36326
36308
  echo " events; unrecorded boundaries read UNKNOWN"
36327
- echo " show <id> Pretty-print .loki/proofs/<id>/proof.json"
36309
+ echo " show <id> [--json] Compact reviewer summary (label, revision, checks,"
36310
+ echo " requirements, modified tests); --json prints the"
36311
+ echo " full .loki/proofs/<id>/proof.json"
36328
36312
  echo " verify <id> Re-check a receipt against the repo (tamper + drift)."
36329
36313
  echo " Exit 0 clean, 1 tamper/drift, 2 could not check,"
36330
36314
  echo " 64 usage (no id, more than one id, unknown option,"
@@ -36496,6 +36480,14 @@ PYEOF
36496
36480
  exit 0
36497
36481
  ;;
36498
36482
  show)
36483
+ local _ps_json=0 _ps_a _ps_args=()
36484
+ for _ps_a in "$@"; do
36485
+ case "$_ps_a" in
36486
+ --json) _ps_json=1 ;;
36487
+ *) _ps_args+=("$_ps_a") ;;
36488
+ esac
36489
+ done
36490
+ set -- "${_ps_args[@]+"${_ps_args[@]}"}"
36499
36491
  local id="${1:-}"
36500
36492
  if [ -z "$id" ]; then
36501
36493
  echo -e "${RED}Missing proof id.${NC} Use 'loki proof list'."
@@ -36507,6 +36499,21 @@ PYEOF
36507
36499
  echo "Use 'loki proof list' to see available proofs."
36508
36500
  exit 1
36509
36501
  fi
36502
+ if [ "$_ps_json" -eq 0 ]; then
36503
+ # Compact reviewer summary (docs/PROOF-PASSPORT.md).
36504
+ local _ps_py _ps_vr="${_LOKI_SCRIPT_DIR}/lib/proof-verify.py"
36505
+ _ps_py="$(loki_verify_tool_path python3)" || {
36506
+ echo -e "${RED}python3 is unavailable${NC}" >&2
36507
+ exit 2
36508
+ }
36509
+ local _ps_root
36510
+ _ps_root="$(loki_verify_root 2>/dev/null)" || {
36511
+ echo -e "${YELLOW}Summary unavailable (project tree not resolved; not checking the ambient cwd).${NC} Use 'loki proof show ${id} --json' for the full record." >&2
36512
+ exit 2
36513
+ }
36514
+ "$_ps_py" -E "$_ps_vr" summary "$pj" --repo "$_ps_root" --loki-dir "$loki_dir" || exit $?
36515
+ exit 0
36516
+ fi
36510
36517
  if command -v jq &>/dev/null; then
36511
36518
  jq . "$pj"
36512
36519
  else