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.
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/chat.sh +6 -3
- package/autonomy/lib/engine10-push.sh +19 -1
- package/autonomy/lib/help-core.sh +1 -1
- package/autonomy/lib/proof-generator.py +23 -5
- package/autonomy/lib/proof-verify.py +607 -5
- package/autonomy/lib/run-summary.sh +17 -1
- package/autonomy/lib/setup-requirements.json +35 -0
- package/autonomy/loki +244 -237
- package/autonomy/notification-checker.py +1 -1
- package/autonomy/provider-offer.sh +95 -47
- package/autonomy/route.sh +38 -6
- package/autonomy/run.sh +42 -14
- package/autonomy/setup-wizard.sh +19 -10
- package/autonomy/verify.sh +40 -1
- package/completions/_loki +3 -1
- package/completions/loki.bash +9 -3
- package/dashboard/__init__.py +3 -10
- package/dashboard/control.py +5 -435
- package/docs/ARCHITECTURE.md +1 -2
- package/docs/CLI-CORE-VERBS.md +2 -6
- package/docs/CLI-REFERENCE.md +3 -1
- package/docs/COMPONENTS.md +0 -2
- package/docs/EVALUATING.md +27 -0
- package/docs/HARNESS-INTELLIGENCE-PLAN.md +20 -0
- package/docs/INSTALLATION.md +1 -1
- package/docs/KNOWN-ISSUES.md +10 -1
- package/docs/PROOF-PASSPORT.md +114 -0
- package/docs/alternative-installations.md +103 -0
- package/docs/authentication.md +19 -1
- package/docs/cost-controls.md +139 -0
- package/docs/dev/release-checklist.md +9 -8
- package/docs/enterprise/security.md +3 -234
- package/docs/environment-variables.md +36 -0
- package/docs/exit-codes.md +102 -2
- package/docs/spec-first-intent.md +29 -0
- package/docs/v10/ARCHITECT-CUTS.md +304 -0
- package/docs/v10/CLI-MODERN.md +2 -2
- package/docs/v10/DECISIONS.md +5 -5
- package/docs/v10/ENGINE.md +735 -267
- package/docs/v10/GUARDS.md +16 -14
- package/docs/v10/GUIDE.md +5 -7
- package/docs/v10/PROGRESS.md +12 -1
- package/docs/v11/MARKET-SLICES.md +1 -5
- package/integrations/openclaw/README.md +20 -0
- package/integrations/vibe-kanban.md +12 -50
- package/loki-ts/data/model-pricing.json +1 -0
- package/loki-ts/dist/loki.js +1156 -1103
- package/mcp/__init__.py +1 -1
- package/mcp/_sdk_loader.py +1 -1
- package/package.json +2 -2
- package/packages/control-plane/dist/server.js +2063 -1032
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/plugins/loki-mode/README.md +41 -1
- package/plugins/loki-mode/commands/handoff.md +12 -0
- package/plugins/loki-mode/commands/loki-verify.md +9 -2
- package/plugins/loki-mode/commands/start.md +20 -0
- package/plugins/loki-mode/hooks/hooks.json +11 -0
- package/plugins/loki-mode/scripts/loki-stop-verify.sh +84 -0
- package/references/quality-control.md +1 -116
- package/skills/00-index.md +2 -2
- package/skills/artifacts.md +0 -22
- package/skills/production.md +2 -38
- package/web-app/dist/assets/{AdminPage-CYJ2BWRC.js → AdminPage-DhXdJNQk.js} +1 -1
- package/web-app/dist/assets/{Avatar-DdK4wynI.js → Avatar-ixVj76fM.js} +1 -1
- package/web-app/dist/assets/{Badge-CrX3MI7J.js → Badge-BGMSp22s.js} +1 -1
- package/web-app/dist/assets/{Button-709-wwGM.js → Button-pYJc32tG.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-BapdTInO.js → CockpitPage-C1JBBouA.js} +1 -1
- package/web-app/dist/assets/{ComparePage-DT99pGvA.js → ComparePage-qD0NV4RK.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-B0uJSys8.js → ErrorBoundary-C-yxO58j.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-BtMFgElP.js → EvidenceReceiptPanel-ByPzuE1S.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-DjJuU941.js → GitHubIssuesPanel-BNv_2BEJ.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-HXfEX1BC.js → GitHubPRsPanel-CuLZQ9CS.js} +1 -1
- package/web-app/dist/assets/{HomePage-Cqyves-S.js → HomePage-CS_FW8Je.js} +2 -2
- package/web-app/dist/assets/{LoginPage-jUbtC7_Y.js → LoginPage-DwiNf1Wm.js} +1 -1
- package/web-app/dist/assets/{MagicPage-DHRsqjgR.js → MagicPage-CmQybLPZ.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-B-pED05R.js → MetricsPage-D9ERdpv8.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-CsbUs21R.js → NotFoundPage-D9ccgdbF.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-BtlLEQwB.js → ProjectPage-B9Jslmgv.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-DaRbRjSp.js → ProjectsPage-DnU99Go2.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-DALrtP3F.js → SettingsPage-DcTyx6wo.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-CoPHSNP0.js → ShowcasePage-jEY9ghSr.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-kOuSN50O.js → SystemSettingsPage-CSGltW-8.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-DGWK0LMp.js → TeamsPage-gUuJXb9l.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-C-_10CwA.js → TemplatesPage-37Zhe--v.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-Cz_vPbXL.js → TerminalOutput-B09hRwaR.js} +1 -1
- package/web-app/dist/assets/{activity-icIsXJVa.js → activity-q_4MY9J0.js} +1 -1
- package/web-app/dist/assets/{bell-DbPR2_4r.js → bell-X8OC_PFP.js} +1 -1
- package/web-app/dist/assets/{bot-DvjP3Yh5.js → bot-C-N1PUOD.js} +1 -1
- package/web-app/dist/assets/{check-D1OBxHeU.js → check-B2c76Jmp.js} +1 -1
- package/web-app/dist/assets/{chevron-left-D09jDZ29.js → chevron-left-BF76YLNw.js} +1 -1
- package/web-app/dist/assets/{circle-alert-DyZMLOcC.js → circle-alert-AeFKBD8h.js} +1 -1
- package/web-app/dist/assets/{clock-CdpdJTId.js → clock-CiXRf-FH.js} +1 -1
- package/web-app/dist/assets/{cloud-CoLTlmoj.js → cloud-DKQ8SRJ5.js} +1 -1
- package/web-app/dist/assets/{code-xml-DrkE2ZZY.js → code-xml-DEb6jXWP.js} +1 -1
- package/web-app/dist/assets/{database-CMQQuM2R.js → database-6W7e-E9c.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-CuioDaOG.js → dollar-sign-Ds2GOuyW.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-Cx9j9K1S.js → file-code-corner-CZU0jg52.js} +1 -1
- package/web-app/dist/assets/{file-plus-CYD2zaaI.js → file-plus-DTVYnXCN.js} +1 -1
- package/web-app/dist/assets/{globe-6w-hnQkL.js → globe-0mUzKYDe.js} +1 -1
- package/web-app/dist/assets/{hammer-BpqtautL.js → hammer-BMHZg3YX.js} +1 -1
- package/web-app/dist/assets/{index-Dzs-9EgJ.js → index-BvOTvjax.js} +3 -3
- package/web-app/dist/assets/{layers-D5hd7jb7.js → layers-Df7TdcWB.js} +1 -1
- package/web-app/dist/assets/{loader-circle-C6Y9x_7H.js → loader-circle-kPsVfrSY.js} +1 -1
- package/web-app/dist/assets/{lock-D51L7rYh.js → lock-CXYcbEc5.js} +1 -1
- package/web-app/dist/assets/{package-BJF8hkP8.js → package-Cqhua1ID.js} +1 -1
- package/web-app/dist/assets/{plus-D5kUQ79n.js → plus-lseXhsn-.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-BKdlCa7F.js → refresh-cw-OZriI6gD.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-_bd_YI1e.js → rotate-ccw-CKCfjPg8.js} +1 -1
- package/web-app/dist/assets/{scroll-text-DbkegIxF.js → scroll-text-DoVyyfKb.js} +1 -1
- package/web-app/dist/assets/{server-DAwqFIsq.js → server-DGy7TflW.js} +1 -1
- package/web-app/dist/assets/{shield-alert-BuR1tPHu.js → shield-alert-BCzRcMXi.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-BF6p3mRd.js → thumbs-up-C_znJd5g.js} +1 -1
- package/web-app/dist/assets/{trash-2-BVpnkqqz.js → trash-2-BosCbeeA.js} +1 -1
- package/web-app/dist/assets/{trending-down-DpsDvRwW.js → trending-down-CkR5zDkV.js} +1 -1
- package/web-app/dist/assets/{trending-up-mFogTjt3.js → trending-up-DpUIfOH4.js} +1 -1
- package/web-app/dist/assets/{upload-4T8zpBsi.js → upload-CH9aKc8L.js} +1 -1
- package/web-app/dist/assets/{usePolling-DPXQv8WO.js → usePolling-pzxrHJeg.js} +1 -1
- package/web-app/dist/assets/{user-BYGIFaTm.js → user-B7OHa-VQ.js} +1 -1
- package/web-app/dist/index.html +1 -1
- package/dashboard/run.py +0 -63
- package/docs/openclaw-integration.md +0 -52
- package/docs/v10/HANDOFF-NEXT-WEEK.md +0 -40
- package/docs/v10/MORNING-BRIEF-1003.md +0 -25
- package/docs/v10/MORNING-BRIEF.md +0 -52
- package/docs/v10/TODO.md +0 -35
- package/mcp/resources.py +0 -351
- 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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
# (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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>
|
|
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
|