loki-mode 7.82.0 → 7.83.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, 8 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.82.0
6
+ # Loki Mode v7.83.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -406,4 +406,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
406
406
 
407
407
  ---
408
408
 
409
- **v7.82.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
409
+ **v7.83.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.82.0
1
+ 7.83.0
package/autonomy/loki CHANGED
@@ -265,6 +265,30 @@ _loki_new_session_exec() {
265
265
  fi
266
266
  }
267
267
 
268
+ # Actionable PRD-not-found exit. Both PRD-path existence guards in cmd_start route
269
+ # through here so they cannot drift: the user always learns the two recoveries
270
+ # (fix the path, or drop the arg to auto-generate from the codebase). If a sibling
271
+ # .md file looks like a near-miss of the bad path, surface it honestly as a hint.
272
+ # Prints to stderr and exits 1. Honest: only suggests a "did you mean" when a real
273
+ # .md sibling exists; never fabricates a path.
274
+ _prd_not_found_die() {
275
+ local bad_path="$1"
276
+ echo -e "${RED}Error: PRD file not found: $bad_path${NC}" >&2
277
+ echo -e "${YELLOW}Hint: provide a path to an existing .md/.json/.txt/.yaml/.yml file, or run 'loki start' with no args to analyze the current directory.${NC}" >&2
278
+ # Best-effort near-miss suggestion: if a .md file sits in the same directory
279
+ # as the bad path, point at it. Read-only; silent when nothing matches.
280
+ local dir base suggestion
281
+ dir="$(dirname "$bad_path" 2>/dev/null)" || dir="."
282
+ base="$(basename "$bad_path" 2>/dev/null)"
283
+ if [ -d "$dir" ] && [ -n "$base" ]; then
284
+ suggestion="$(ls -1 "$dir"/*.md 2>/dev/null | head -1)"
285
+ if [ -n "$suggestion" ]; then
286
+ echo -e "${YELLOW}Did you mean: $suggestion ?${NC}" >&2
287
+ fi
288
+ fi
289
+ exit 1
290
+ }
291
+
268
292
  # Anonymous usage telemetry
269
293
  PROJECT_DIR="$SKILL_DIR"
270
294
  _TELEMETRY_SCRIPT="$SKILL_DIR/autonomy/telemetry.sh"
@@ -714,6 +738,7 @@ show_help() {
714
738
  echo ""
715
739
  echo "Session:"
716
740
  echo " status [--json] Show current status (--json for machine-readable)"
741
+ echo " next Run the right next step for you (resume / ship / why)"
717
742
  echo " stop Stop execution immediately"
718
743
  echo " pause Pause after current session"
719
744
  echo " resume Resume paused execution"
@@ -745,7 +770,7 @@ show_help() {
745
770
  echo " version Show version"
746
771
  echo " help Show this help ('loki help aliases' for old names)"
747
772
  echo ""
748
- echo "More commands (grill, spec, deploy, cleanup, init, watch, demo, web, api,"
773
+ echo "More commands (ship, grill, spec, deploy, cleanup, init, watch, demo, web, api,"
749
774
  echo "logs, github, import, council, proof, audit, compliance, agent, template,"
750
775
  echo "magic, docs, wiki, ci, test, bench, secrets, telemetry, crash, worktree,"
751
776
  echo "failover, monitor, remote, ...) are dispatchable and documented via"
@@ -909,6 +934,7 @@ show_landing() {
909
934
  echo -e "First time here? ${CYAN}loki doctor${NC} checks your setup (an AI provider CLI is required)."
910
935
  echo ""
911
936
  echo "Get started:"
937
+ echo -e " ${CYAN}loki quickstart${NC} Guided first build from your idea (no PRD needed)"
912
938
  echo -e " ${CYAN}loki start ./prd.md${NC} Build from a spec (PRD file, GitHub issue, or no arg)"
913
939
  echo -e " ${CYAN}loki demo${NC} Build a sample todo app end to end (real run)"
914
940
  echo -e " ${CYAN}loki dashboard start${NC} Start the live run monitor (then: loki dashboard open)"
@@ -1686,9 +1712,7 @@ cmd_start() {
1686
1712
  case "$prd_file" in
1687
1713
  *.md|*.json|*.txt|*.yaml|*.yml)
1688
1714
  if [ ! -f "$prd_file" ]; then
1689
- echo "Error: PRD file not found: $prd_file" >&2
1690
- echo "Hint: provide a path to an existing .md/.json/.txt/.yaml/.yml file, or run 'loki start' with no args to analyze the current directory." >&2
1691
- exit 1
1715
+ _prd_not_found_die "$prd_file"
1692
1716
  fi
1693
1717
  ;;
1694
1718
  esac
@@ -1711,8 +1735,7 @@ cmd_start() {
1711
1735
  case "$prd_file" in
1712
1736
  *.md|*.json|*.txt|*.yaml|*.yml)
1713
1737
  if [ ! -f "$prd_file" ]; then
1714
- echo -e "${RED}Error: PRD file not found: $prd_file${NC}" >&2
1715
- exit 1
1738
+ _prd_not_found_die "$prd_file"
1716
1739
  fi
1717
1740
  ;;
1718
1741
  esac
@@ -2013,7 +2036,13 @@ cmd_start() {
2013
2036
  fi
2014
2037
  if [ -n "$_existing_pid" ] && kill -0 "$_existing_pid" 2>/dev/null; then
2015
2038
  echo -e "${RED}Error: another loki instance is running (pid $_existing_pid).${NC}" >&2
2016
- echo -e "${YELLOW}Run 'loki stop' first, then retry 'loki start'.${NC}" >&2
2039
+ # 'loki stop' is folder-scoped by default (v7.7.30), so it only stops a
2040
+ # run in THIS directory. If the live instance is in another project,
2041
+ # 'loki stop' here reports "No active session" and the user is stuck in
2042
+ # a start-says-running / stop-says-clean loop. Point at the global stop
2043
+ # and at 'loki status' to find the live run.
2044
+ echo -e "${YELLOW}Run 'loki stop' here, or 'loki stop --all' if it is running in another project.${NC}" >&2
2045
+ echo -e "${YELLOW}See 'loki status' for the live run, then retry 'loki start'.${NC}" >&2
2017
2046
  exit 1
2018
2047
  fi
2019
2048
  # PID is stale (file present but process gone). Log + remove + continue.
@@ -2954,6 +2983,323 @@ cmd_resume() {
2954
2983
  fi
2955
2984
  }
2956
2985
 
2986
+ # Resolve the terminal-run status from the same already-captured artifacts that
2987
+ # cmd_why reads (no new state, no spend). Echoes a single token: the resolved
2988
+ # status string (e.g. council_approved, paused, max_iterations_reached), or the
2989
+ # literal "none" when no run exists here yet. Used by cmd_next so its status->
2990
+ # action mapping reads exactly the same state cmd_why does (no drift).
2991
+ # Read-only; never fabricates.
2992
+ _loki_resolve_run_status() {
2993
+ local loki_dir="${LOKI_DIR:-.loki}"
2994
+ local state_file="$loki_dir/autonomy-state.json"
2995
+ if [ -n "${LOKI_SESSION_ID:-}" ] && [ -f "$loki_dir/sessions/${LOKI_SESSION_ID}/autonomy-state.json" ]; then
2996
+ state_file="$loki_dir/sessions/${LOKI_SESSION_ID}/autonomy-state.json"
2997
+ fi
2998
+ local completion_file="$loki_dir/state/completion.json"
2999
+
3000
+ if [ ! -f "$state_file" ] && [ ! -f "$completion_file" ]; then
3001
+ printf '%s\n' "none"
3002
+ return 0
3003
+ fi
3004
+
3005
+ _LOKI_NA_STATE="$state_file" _LOKI_NA_COMPLETION="$completion_file" python3 - <<'NASTATUS'
3006
+ import json, os
3007
+ def load(p):
3008
+ try:
3009
+ with open(p) as f: return json.load(f)
3010
+ except Exception: return {}
3011
+ state = load(os.environ.get("_LOKI_NA_STATE", ""))
3012
+ comp = load(os.environ.get("_LOKI_NA_COMPLETION", ""))
3013
+ # Mirror cmd_why's COMP_OUTCOME_ALIASES so a completion-only state (a rotated or
3014
+ # crashed run with no live status, only completion.json's outcome) resolves to
3015
+ # the canonical status loki next's action map keys on. Without this, an outcome
3016
+ # of "complete"/"max_iterations" drifted to the default "loki status" instead of
3017
+ # "loki ship"/"loki resume".
3018
+ COMP_OUTCOME_ALIASES = {
3019
+ "complete": "council_approved",
3020
+ "max_iterations": "max_iterations_reached",
3021
+ "intervention": "paused",
3022
+ }
3023
+ status = state.get("status")
3024
+ if not status:
3025
+ raw_outcome = comp.get("outcome") or ""
3026
+ status = COMP_OUTCOME_ALIASES.get(raw_outcome, raw_outcome) or "unknown"
3027
+ print(status)
3028
+ NASTATUS
3029
+ }
3030
+
3031
+ # Map a resolved status to the single concrete next-action SENTENCE (the same
3032
+ # wording cmd_why prints under "What to do"). Echoes the action string. Kept in
3033
+ # lockstep with cmd_why's GUIDE map so why and next agree.
3034
+ _loki_next_action() {
3035
+ local status="${1:-unknown}"
3036
+ case "$status" in
3037
+ council_approved)
3038
+ echo "Review the diff and open a PR (loki ship, or git push + gh pr create)." ;;
3039
+ council_force_approved)
3040
+ echo "Review the diff carefully before merging -- convergence was not unanimous." ;;
3041
+ completion_promise_fulfilled)
3042
+ echo "Verify the promised outcome, then review and PR (loki ship)." ;;
3043
+ max_iterations_reached)
3044
+ echo "Raise LOKI_MAX_ITERATIONS (or narrow the spec), then resume (loki resume)." ;;
3045
+ max_retries_exceeded)
3046
+ echo "Read .loki/logs for the recurring error, fix the root cause, then re-run (loki why)." ;;
3047
+ failed)
3048
+ echo "Read the failure (loki why), fix it, then re-run." ;;
3049
+ policy_blocked)
3050
+ echo "Review the blocking finding (loki why); address it or use the documented override." ;;
3051
+ budget_exceeded)
3052
+ echo "Raise LOKI_BUDGET_LIMIT or accept the partial result, then resume (loki resume)." ;;
3053
+ paused)
3054
+ echo "Resume with: loki resume." ;;
3055
+ interrupted)
3056
+ echo "Resume with: loki resume." ;;
3057
+ stopped)
3058
+ echo "Start a new build (loki start), or resume if you meant to continue." ;;
3059
+ force_stopped)
3060
+ echo "Start a new build, or investigate why a force-stop was needed (loki why)." ;;
3061
+ running)
3062
+ echo "A build looks active (or crashed mid-run); check loki status." ;;
3063
+ none)
3064
+ echo "Run a build first: loki start <spec>." ;;
3065
+ *)
3066
+ echo "Check loki status and loki why for detail." ;;
3067
+ esac
3068
+ }
3069
+
3070
+ # loki next -- one command that runs the right next step for you.
3071
+ # Reads the SAME already-captured state as cmd_why (no new state, no spend),
3072
+ # resolves the terminal status, and proposes the single mapped next step, then
3073
+ # runs it on confirmation. Default on a TTY is confirm-then-run; --yes runs it
3074
+ # without asking; --dry-run and any non-TTY context only PRINT (honest: never
3075
+ # auto-acts in CI / when piped). Strictly additive: reuses cmd_resume, cmd_ship,
3076
+ # cmd_why, and cmd_status; never touches the build path.
3077
+ cmd_next() {
3078
+ local assume_yes=false
3079
+ local dry_run=false
3080
+ case "${1:-}" in
3081
+ --help|-h|help)
3082
+ echo -e "${BOLD}Loki Mode -- run the right next step for you${NC}"
3083
+ echo ""
3084
+ echo "Usage: loki next [--yes] [--dry-run]"
3085
+ echo ""
3086
+ echo "Reads the last build's recorded outcome (the same state 'loki why'"
3087
+ echo "reads -- no new run, no cost) and carries you forward by running the"
3088
+ echo "single mapped next step: resume a paused/interrupted run, finish a"
3089
+ echo "council-approved build (loki ship), or route a failure to 'loki why'."
3090
+ echo ""
3091
+ echo "Default on a terminal: it shows the proposed step and asks before"
3092
+ echo "running it. Non-interactive (piped/CI) it only prints the step;"
3093
+ echo "it never acts on its own there."
3094
+ echo ""
3095
+ echo "Options:"
3096
+ echo " --yes Run the proposed step without asking"
3097
+ echo " --dry-run Print the proposed step only; do not run it"
3098
+ echo " --help, -h Show this help and exit"
3099
+ return 0
3100
+ ;;
3101
+ --yes|-y) assume_yes=true ;;
3102
+ --dry-run) dry_run=true ;;
3103
+ "") : ;;
3104
+ *) echo -e "${RED}Unknown flag: $1${NC}" >&2; echo "Usage: loki next [--yes] [--dry-run]" >&2; return 1 ;;
3105
+ esac
3106
+ # Allow a second flag (e.g. --dry-run --yes order-agnostic).
3107
+ case "${2:-}" in
3108
+ --yes|-y) assume_yes=true ;;
3109
+ --dry-run) dry_run=true ;;
3110
+ esac
3111
+
3112
+ local status
3113
+ status="$(_loki_resolve_run_status)"
3114
+ if [ "$status" = "none" ]; then
3115
+ echo "loki next: no run found here yet." >&2
3116
+ echo "Run a build first: loki start <spec>" >&2
3117
+ return 1
3118
+ fi
3119
+
3120
+ local action_text
3121
+ action_text="$(_loki_next_action "$status")"
3122
+
3123
+ # Map the status to the concrete callable that carries the user forward.
3124
+ # action_cmd is a human label; the case below dispatches to the real function.
3125
+ local action_cmd=""
3126
+ case "$status" in
3127
+ paused|interrupted) action_cmd="loki resume" ;;
3128
+ council_approved|council_force_approved|completion_promise_fulfilled)
3129
+ action_cmd="loki ship" ;;
3130
+ max_iterations_reached|budget_exceeded) action_cmd="loki resume" ;;
3131
+ failed|max_retries_exceeded|policy_blocked|force_stopped)
3132
+ action_cmd="loki why" ;;
3133
+ *) action_cmd="loki status" ;;
3134
+ esac
3135
+
3136
+ echo -e "${BOLD}Loki: next${NC}"
3137
+ echo " Last outcome : $status"
3138
+ echo " Next step : $action_text"
3139
+ echo " Will run : $action_cmd"
3140
+ echo ""
3141
+
3142
+ # Honesty gate: only ACT on an interactive terminal with consent, or with
3143
+ # --yes. --dry-run and non-TTY always print-only.
3144
+ if [ "$dry_run" = true ]; then
3145
+ echo "(dry-run: not running. Run '$action_cmd' yourself, or 'loki next --yes'.)"
3146
+ return 0
3147
+ fi
3148
+ if [ "$assume_yes" != true ]; then
3149
+ if [ ! -t 0 ] || [ ! -t 1 ]; then
3150
+ echo "(non-interactive: not running. Run '$action_cmd', or 'loki next --yes'.)"
3151
+ return 0
3152
+ fi
3153
+ local reply=""
3154
+ printf "Run '%s' now? [y/N] " "$action_cmd"
3155
+ read -r reply || reply=""
3156
+ case "$reply" in
3157
+ y|Y|yes|YES) : ;;
3158
+ *) echo "Skipped. Run '$action_cmd' when ready."; return 0 ;;
3159
+ esac
3160
+ fi
3161
+
3162
+ # Dispatch to the real command. For budget/iteration caps, print the env-var
3163
+ # guidance first (the resume alone will not lift the cap), then resume.
3164
+ case "$status" in
3165
+ max_iterations_reached)
3166
+ echo "Note: raise the cap first if you want more iterations, e.g.:"
3167
+ echo " LOKI_MAX_ITERATIONS=<n> loki resume"
3168
+ echo ""
3169
+ ;;
3170
+ budget_exceeded)
3171
+ echo "Note: raise the budget first if you want to continue, e.g.:"
3172
+ echo " LOKI_BUDGET_LIMIT=<usd> loki resume"
3173
+ echo ""
3174
+ ;;
3175
+ esac
3176
+ case "$action_cmd" in
3177
+ "loki resume") cmd_resume ;;
3178
+ "loki ship") cmd_ship ;;
3179
+ "loki why") cmd_why ;;
3180
+ *) cmd_status ;;
3181
+ esac
3182
+ return $?
3183
+ }
3184
+
3185
+ # loki ship -- the build is done, now get it out (review + PR advice in one flow).
3186
+ # Composes existing surfaces only: optionally opens the running app (--preview),
3187
+ # runs the SAME diff quality gates as cmd_review, and on a clean-enough result
3188
+ # prints the exact branch-aware PR command via the shared print_pr_advice (the
3189
+ # same advisory cmd_deploy uses). PRINT-ONLY for shipping: it NEVER pushes and
3190
+ # NEVER deploys; it prints (and clipboards, as the advisory already does) the
3191
+ # git push + PR command for YOU to run. If review finds HIGH/CRITICAL it stops
3192
+ # and points at the findings instead of advising a PR.
3193
+ cmd_ship() {
3194
+ local do_preview=false
3195
+ local review_args=()
3196
+ while [ $# -gt 0 ]; do
3197
+ case "${1:-}" in
3198
+ --help|-h|help)
3199
+ echo -e "${BOLD}Loki Mode -- finish the build: review, then PR advice (print-only)${NC}"
3200
+ echo ""
3201
+ echo "Usage: loki ship [--preview] [--yes] [review options]"
3202
+ echo ""
3203
+ echo "Runs the natural finish line in one command:"
3204
+ echo " 1. (with --preview) opens the app Loki built and started locally"
3205
+ echo " 2. runs 'loki review' on your uncommitted/branch diff (the same"
3206
+ echo " quality gates)"
3207
+ echo " 3. on a clean-enough result, PRINTS the exact branch-aware"
3208
+ echo " git push + pull-request command for YOU to run"
3209
+ echo ""
3210
+ echo "It is advisory only for shipping: it NEVER runs 'git push', NEVER"
3211
+ echo "creates a PR, and NEVER deploys. You run the printed command."
3212
+ echo "If review finds HIGH or CRITICAL findings it stops and points you"
3213
+ echo "at the findings instead of advising a PR (exit code 1)."
3214
+ echo ""
3215
+ echo "Options:"
3216
+ echo " --preview Open the running local app first (loki preview)"
3217
+ echo " --yes, -y Pass through to review (skip its confirmation prompt)"
3218
+ echo " --help, -h Show this help and exit"
3219
+ echo ""
3220
+ echo "Any other options are passed through to 'loki review'"
3221
+ echo "(e.g. --staged, --since <commit>, --severity high)."
3222
+ return 0
3223
+ ;;
3224
+ --preview) do_preview=true ;;
3225
+ *) review_args+=("$1") ;;
3226
+ esac
3227
+ shift
3228
+ done
3229
+
3230
+ # 1. Optional preview of the running local app. Only when an app-runner state
3231
+ # file exists, so we never imply an app is running when it is not.
3232
+ if [ "$do_preview" = true ]; then
3233
+ local loki_dir="${LOKI_DIR:-.loki}"
3234
+ if [ -f "$loki_dir/state/app-runner.json" ] || [ -f "$loki_dir/app-runner.json" ]; then
3235
+ echo -e "${BOLD}Opening the running app...${NC}"
3236
+ cmd_preview || true
3237
+ echo ""
3238
+ else
3239
+ echo "(no running app detected; skipping preview. Start one with loki start.)"
3240
+ echo ""
3241
+ fi
3242
+ fi
3243
+
3244
+ # 2. Run the SAME diff quality gates as cmd_review. Capture its documented
3245
+ # exit code (0 clean, 1 HIGH, 2 CRITICAL). set -e safe: capture via || rc=$?.
3246
+ echo -e "${BOLD}Running quality gates on the diff (loki review)...${NC}"
3247
+ echo ""
3248
+ local rc=0
3249
+ cmd_review "${review_args[@]}" || rc=$?
3250
+
3251
+ if [ "$rc" -ge 1 ]; then
3252
+ echo ""
3253
+ if [ "$rc" -ge 2 ]; then
3254
+ echo -e "${RED}Review found CRITICAL findings. Not advising a PR yet.${NC}" >&2
3255
+ else
3256
+ echo -e "${YELLOW}Review found HIGH findings. Not advising a PR yet.${NC}" >&2
3257
+ fi
3258
+ echo "Fix the findings above (or run 'loki review' for the full report), then 'loki ship' again." >&2
3259
+ return 1
3260
+ fi
3261
+
3262
+ # 3. Clean enough: print the branch-aware PR command (the SAME advisory
3263
+ # cmd_deploy uses). Derive base/head exactly as cmd_deploy does.
3264
+ echo ""
3265
+ echo -e "${GREEN}Review is clean (no HIGH or CRITICAL findings).${NC}"
3266
+ echo ""
3267
+ local dir="."
3268
+ local head="" base=""
3269
+ head="$(git -C "$dir" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")"
3270
+ [ -n "$head" ] || head="HEAD"
3271
+ case "$head" in
3272
+ loki/*)
3273
+ if [ -s "$dir/.loki/state/base-branch.txt" ]; then
3274
+ base="$(head -n1 "$dir/.loki/state/base-branch.txt" 2>/dev/null || echo "")"
3275
+ fi
3276
+ ;;
3277
+ esac
3278
+ if [ -z "$base" ]; then
3279
+ local origin_head=""
3280
+ origin_head="$(git -C "$dir" symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || echo "")"
3281
+ if [ -n "$origin_head" ]; then
3282
+ base="${origin_head##*/}"
3283
+ fi
3284
+ fi
3285
+
3286
+ if declare -F print_pr_advice >/dev/null 2>&1; then
3287
+ if [ -n "$base" ]; then
3288
+ print_pr_advice "$base" "$head" "$dir"
3289
+ else
3290
+ echo "Could not determine the PR base branch automatically; set it manually:"
3291
+ print_pr_advice "manually-set-base" "$head" "$dir"
3292
+ fi
3293
+ else
3294
+ echo "To open a pull request:"
3295
+ echo " git push -u origin ${head}"
3296
+ echo " Open a pull request for ${head} (set the base branch manually)."
3297
+ fi
3298
+ echo ""
3299
+ echo -e "${DIM}Loki does not push or deploy for you; run the command above when ready.${NC}"
3300
+ return 0
3301
+ }
3302
+
2957
3303
  # loki why -- actionable failure/outcome diagnosis (B5).
2958
3304
  # Reads the already-captured run artifacts (no new state): the terminal run state
2959
3305
  # (.loki/<autonomy-state>.json: status, lastExitCode, iterationCount), the durable
@@ -3284,6 +3630,43 @@ cmd_status() {
3284
3630
  fi
3285
3631
  fi
3286
3632
 
3633
+ # v7.82: surface the single most valuable, most shareable fact -- where the
3634
+ # built app is running -- when the app runner reports a live app. Display-only
3635
+ # read of .loki/app-runner/state.json (same shape build_completion_summary
3636
+ # reads); silent when no app is running. The --json path (cmd_status_json) is
3637
+ # untouched, so machine output stays byte-identical.
3638
+ if [ -f "$LOKI_DIR/app-runner/state.json" ]; then
3639
+ local app_url
3640
+ app_url=$(_LOKI_APP_STATE="$LOKI_DIR/app-runner/state.json" python3 -c "
3641
+ import json, os
3642
+ try:
3643
+ d = json.load(open(os.environ['_LOKI_APP_STATE']))
3644
+ print(d.get('url', '') if d.get('status') == 'running' else '')
3645
+ except Exception:
3646
+ print('')" 2>/dev/null)
3647
+ if [ -n "$app_url" ]; then
3648
+ echo ""
3649
+ echo -e "${GREEN}App running:${NC} ${BOLD}${app_url}${NC} ${DIM}- open it to try it${NC}"
3650
+ fi
3651
+ fi
3652
+
3653
+ # v7.82: completion-aware one-liner. When a finished run left a completion
3654
+ # record, show its outcome and point at \`loki why\` for the full story. Reads
3655
+ # the persisted completion.json (no recompute); silent when absent.
3656
+ if [ -f "$LOKI_DIR/state/completion.json" ]; then
3657
+ local last_outcome
3658
+ last_outcome=$(_LOKI_CJ="$LOKI_DIR/state/completion.json" python3 -c "
3659
+ import json, os
3660
+ try:
3661
+ d = json.load(open(os.environ['_LOKI_CJ']))
3662
+ print(d.get('outcome', '') or '')
3663
+ except Exception:
3664
+ print('')" 2>/dev/null)
3665
+ if [ -n "$last_outcome" ]; then
3666
+ echo -e "${DIM}Last run: ${last_outcome} - run 'loki why' for details${NC}"
3667
+ fi
3668
+ fi
3669
+
3287
3670
  echo ""
3288
3671
  echo -e "${DIM} Tip: loki analyze context show - detailed token breakdown${NC}"
3289
3672
  echo -e "${DIM} Tip: loki analyze code overview - codebase intelligence${NC}"
@@ -4866,7 +5249,7 @@ cmd_welcome_terminal() {
4866
5249
  echo -e " ${CYAN}Cross-project memory${NC} Lessons compound, so agents stop repeating mistakes."
4867
5250
  echo -e " ${CYAN}Provider-agnostic${NC} Claude, Codex, Cline, or Aider. Your keys, your infra."
4868
5251
  echo ""
4869
- echo -e " Quick start: ${BOLD}loki start ./prd.md${NC}"
5252
+ echo -e " Quick start: ${BOLD}loki quickstart${NC} (from your idea) or ${BOLD}loki start ./prd.md${NC} (from a spec)"
4870
5253
  echo -e " Docs: ${BOLD}https://www.autonomi.dev/docs${NC}"
4871
5254
  echo ""
4872
5255
  if _loki_welcome_analytics_on; then
@@ -9524,11 +9907,17 @@ cmd_doctor() {
9524
9907
  return 1
9525
9908
  elif [ "$warn_count" -gt 0 ]; then
9526
9909
  echo -e "${YELLOW}All required checks passed with some warnings.${NC}"
9527
- return 0
9528
9910
  else
9529
9911
  echo -e "${GREEN}All checks passed. System is ready for Loki Mode.${NC}"
9530
- return 0
9531
9912
  fi
9913
+ # Setup verified (no required check failed): hand the user straight to a
9914
+ # first build with a copy-paste command, so they never dead-end here. Gated
9915
+ # on fail_count==0 above, so a failing setup is never told to build. The
9916
+ # --json path returns long before this code, so machine output is untouched.
9917
+ echo ""
9918
+ echo "Next: loki quickstart (guided first build from your idea, no PRD needed)"
9919
+ echo " or loki demo (builds a sample todo app end to end) or loki start ./prd.md"
9920
+ return 0
9532
9921
  }
9533
9922
 
9534
9923
  # JSON output for loki doctor --json
@@ -15750,6 +16139,9 @@ main() {
15750
16139
  why)
15751
16140
  cmd_why "$@"
15752
16141
  ;;
16142
+ next)
16143
+ cmd_next "$@"
16144
+ ;;
15753
16145
  stats)
15754
16146
  # CLI consolidation (Phase A): 'stats' is a deprecated alias of
15755
16147
  # 'report session'. On the Bun route this arm is never reached
@@ -15773,6 +16165,9 @@ main() {
15773
16165
  deploy)
15774
16166
  cmd_deploy "$@"
15775
16167
  ;;
16168
+ ship)
16169
+ cmd_ship "$@"
16170
+ ;;
15776
16171
  open)
15777
16172
  # CLI consolidation (Phase A): 'open' is a deprecated alias of 'preview'.
15778
16173
  _deprecated_alias open preview "$@"
@@ -23474,12 +23869,15 @@ _code_diff() {
23474
23869
  # Output shell completion scripts
23475
23870
  cmd_completions() {
23476
23871
  local shell="${1:-bash}"
23872
+ # Consume the subcommand so the install arm can read an optional shell
23873
+ # override as its own positional ($1). bash/zsh raw-output arms ignore $@.
23874
+ shift 2>/dev/null || true
23477
23875
  local skill_dir
23478
-
23876
+
23479
23877
  # Find the skill directory (where autonomy/loki is located)
23480
23878
  skill_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
23481
23879
  local completions_dir="$skill_dir/completions"
23482
-
23880
+
23483
23881
  case "$shell" in
23484
23882
  bash)
23485
23883
  if [ -f "$completions_dir/loki.bash" ]; then
@@ -23497,24 +23895,77 @@ cmd_completions() {
23497
23895
  exit 1
23498
23896
  fi
23499
23897
  ;;
23898
+ install)
23899
+ # One-command install: detect the current shell, append a single
23900
+ # guarded line to the right rc file exactly once (idempotent via a
23901
+ # sentinel comment), then tell the user how to activate it. This
23902
+ # replaces the error-prone manual recipe with something that just
23903
+ # works after one command.
23904
+ local detected target_shell rc_file
23905
+ detected="$(basename "${SHELL:-}" 2>/dev/null)"
23906
+ # Allow an explicit override: loki completions install bash|zsh
23907
+ target_shell="${1:-$detected}"
23908
+ case "$target_shell" in
23909
+ bash)
23910
+ rc_file="${HOME}/.bashrc"
23911
+ ;;
23912
+ zsh)
23913
+ rc_file="${HOME}/.zshrc"
23914
+ ;;
23915
+ *)
23916
+ echo -e "${RED}Could not detect a supported shell (got: ${target_shell:-unknown}).${NC}" >&2
23917
+ echo "Specify one explicitly: loki completions install bash | loki completions install zsh" >&2
23918
+ exit 1
23919
+ ;;
23920
+ esac
23921
+ local sentinel="# loki-mode completions"
23922
+ if [ -f "$rc_file" ] && grep -qF "$sentinel" "$rc_file" 2>/dev/null; then
23923
+ echo "Loki completions already installed in ${rc_file} (no change)."
23924
+ echo "Activate now with: source ${rc_file}"
23925
+ return 0
23926
+ fi
23927
+ # Append the sentinel plus a single eval line that re-loads the
23928
+ # completion script at shell start. eval (not redirect) is the
23929
+ # correct persistent form: it sources the script into the
23930
+ # interactive shell each time the rc file runs.
23931
+ {
23932
+ printf '\n%s\n' "$sentinel"
23933
+ printf '%s\n' "eval \"\$(loki completions ${target_shell})\""
23934
+ } >> "$rc_file" || {
23935
+ echo -e "${RED}Failed to write to ${rc_file}.${NC}" >&2
23936
+ exit 1
23937
+ }
23938
+ echo "Installed Loki ${target_shell} completions into ${rc_file}."
23939
+ echo "Activate now with: source ${rc_file} (or restart your shell)"
23940
+ return 0
23941
+ ;;
23500
23942
  *)
23501
23943
  echo -e "${BOLD}Loki Shell Completions${NC}"
23502
23944
  echo ""
23503
23945
  echo "Output shell completion scripts for bash or zsh."
23504
23946
  echo ""
23505
23947
  echo "Usage: loki completions <shell>"
23948
+ echo " loki completions install [bash|zsh]"
23506
23949
  echo ""
23507
23950
  echo "Shells:"
23508
- echo " bash Bash completion script"
23509
- echo " zsh Zsh completion script"
23951
+ echo " bash Bash completion script"
23952
+ echo " zsh Zsh completion script"
23953
+ echo " install Auto-install completions into your shell rc file"
23510
23954
  echo ""
23511
23955
  echo "Installation:"
23512
23956
  echo ""
23513
- echo "Bash:"
23514
- echo " eval \"\$(loki completions bash)\" >> ~/.bashrc"
23957
+ echo " One command (recommended):"
23958
+ echo " loki completions install"
23959
+ echo ""
23960
+ echo " Manual (persistent), Bash:"
23961
+ echo " echo 'eval \"\$(loki completions bash)\"' >> ~/.bashrc"
23962
+ echo " Manual (persistent), Zsh:"
23963
+ echo " echo 'eval \"\$(loki completions zsh)\"' >> ~/.zshrc"
23515
23964
  echo ""
23516
- echo "Zsh:"
23517
- echo " eval \"\$(loki completions zsh)\" >> ~/.zshrc"
23965
+ echo " Session only (current shell), Bash:"
23966
+ echo " eval \"\$(loki completions bash)\""
23967
+ echo " Session only (current shell), Zsh:"
23968
+ echo " eval \"\$(loki completions zsh)\""
23518
23969
  echo ""
23519
23970
  exit 1
23520
23971
  ;;
package/autonomy/run.sh CHANGED
@@ -2674,34 +2674,35 @@ except Exception:
2674
2674
  fi
2675
2675
 
2676
2676
  # ---- Durable human-readable file: .loki/COMPLETION.txt --------------------
2677
+ # Presentation-only receipt: a single fixed-width label column, the live-app
2678
+ # URL elevated right under the outcome headline, and a consistent rule line.
2679
+ # Stays pure ASCII (no emoji, no dashes, no color codes) so it pastes cleanly
2680
+ # into a PR description or a chat. Same facts and values as before.
2677
2681
  {
2678
2682
  echo "Loki Mode run summary"
2679
2683
  echo "====================="
2680
2684
  echo ""
2681
- echo "Outcome: $outcome_label"
2682
- echo "Branch: $branch"
2683
- echo "Files changed: $files_changed (+$insertions / -$deletions)"
2684
- echo "Finished: $ts"
2685
- echo ""
2686
- if [ -n "$delegate_branch" ]; then
2687
- echo "Delegate branch: $delegate_branch"
2688
- fi
2689
- if [ -n "$pr_url" ]; then
2690
- echo "Pull request: $pr_url"
2691
- elif [ "$outcome" = "complete" ]; then
2692
- echo "Pull request: not opened (set LOKI_DELEGATE_PR=1 to open one)"
2693
- fi
2694
- echo ""
2685
+ printf '%-14s %s\n' "Outcome:" "$outcome_label"
2695
2686
  if [ -n "$live_app_url" ]; then
2696
2687
  # Compute the dashboard scheme the same way start_dashboard does
2697
2688
  # (url_scheme is local to that function, not visible here).
2698
2689
  local _dash_scheme="http"
2699
2690
  [ -n "${LOKI_TLS_CERT:-}" ] && [ -n "${LOKI_TLS_KEY:-}" ] && _dash_scheme="https"
2700
- echo "Your app is live at: $live_app_url (served locally on this machine)"
2701
- echo " Dashboard: ${_dash_scheme}://127.0.0.1:${DASHBOARD_PORT:-57374}/ (App Runner -> Live App)"
2702
- echo ""
2691
+ printf '%-14s %s\n' "Live app:" "$live_app_url (served locally on this machine)"
2692
+ printf '%-14s %s\n' "Dashboard:" "${_dash_scheme}://127.0.0.1:${DASHBOARD_PORT:-57374}/ (App Runner -> Live App)"
2693
+ fi
2694
+ printf '%-14s %s\n' "Branch:" "$branch"
2695
+ printf '%-14s %s\n' "Files:" "$files_changed (+$insertions / -$deletions)"
2696
+ printf '%-14s %s\n' "Finished:" "$ts"
2697
+ if [ -n "$delegate_branch" ]; then
2698
+ printf '%-14s %s\n' "Delegate:" "$delegate_branch"
2699
+ fi
2700
+ if [ -n "$pr_url" ]; then
2701
+ printf '%-14s %s\n' "Pull request:" "$pr_url"
2702
+ elif [ "$outcome" = "complete" ]; then
2703
+ printf '%-14s %s\n' "Pull request:" "not opened (set LOKI_DELEGATE_PR=1 to open one)"
2703
2704
  fi
2704
- echo "Tasks: pending=$pending in_progress=$in_progress completed=$completed failed=$failed"
2705
+ printf '%-14s %s\n' "Tasks:" "pending=$pending in_progress=$in_progress completed=$completed failed=$failed"
2705
2706
  echo ""
2706
2707
  if [ -n "$evidence_inconclusive_line" ]; then
2707
2708
  echo "$evidence_inconclusive_line"
@@ -2901,10 +2902,130 @@ emit_completion_summary() {
2901
2902
  local outcome="${1:-complete}"
2902
2903
  local urgency="${2:-normal}"
2903
2904
  build_completion_summary "$outcome"
2905
+ # Render the screenshot-worthy completion card inline on a foreground TTY run,
2906
+ # AFTER the durable files are written. The card re-reads the persisted
2907
+ # completion.json (never recomputes) so it can never diverge from the file.
2908
+ print_completion_card
2904
2909
  send_notification "${_LOKI_SUMMARY_TITLE:-Run finished}" "${_LOKI_SUMMARY_BODY:-}" "$urgency"
2905
2910
  return 0
2906
2911
  }
2907
2912
 
2913
+ #===============================================================================
2914
+ # print_completion_card (visible-delight completion card)
2915
+ #
2916
+ # Display-only. Renders a boxed summary card to the interactive TTY at the close
2917
+ # of a foreground run: outcome, branch, files changed (+ins/-del), the live-app
2918
+ # URL with a "try it" line when an app is running, the copy-pasteable review
2919
+ # command, and the recorded-assumptions count. This is the single most
2920
+ # screenshot-worthy moment of a run, which was previously only written to a file.
2921
+ #
2922
+ # It reads ONLY from the already-persisted .loki/state/completion.json (written
2923
+ # by build_completion_summary just before this is called) and the app-runner
2924
+ # state, so the card and the durable file are guaranteed identical. Nothing is
2925
+ # computed or written here.
2926
+ #
2927
+ # Gate (same shape as the HUD at the colors block): interactive stdout, not
2928
+ # --bg, and not opted out via LOKI_COMPLETION_CARD=0. Off-TTY / --bg / --json
2929
+ # paths emit nothing, so machine output stays byte-identical. Wrapped so any
2930
+ # internal failure still returns 0 and can never abort the completion path.
2931
+ #===============================================================================
2932
+ print_completion_card() {
2933
+ # Gate first: emit nothing unless interactive TTY, not background, not opted out.
2934
+ if ! { [ -t 1 ] && [ "${BACKGROUND_MODE:-false}" != "true" ] && [ "${LOKI_COMPLETION_CARD:-1}" != "0" ]; }; then
2935
+ return 0
2936
+ fi
2937
+
2938
+ local loki_dir="${TARGET_DIR:-.}/.loki"
2939
+ local _cj="$loki_dir/state/completion.json"
2940
+ [ -f "$_cj" ] || return 0
2941
+
2942
+ # Pull the fields we render straight from the persisted record. The python3
2943
+ # call emits one field per line in a fixed order; we read them line by line
2944
+ # so empty fields (e.g. no pr_url) keep their positions (a single delimiter
2945
+ # split would collapse adjacent empties). Any failure leaves the card
2946
+ # unrendered. Trailing-newline guard: NUL-free, fields are single-line.
2947
+ local _fields
2948
+ _fields="$(python3 -c "
2949
+ import json,sys
2950
+ try:
2951
+ d=json.load(open(sys.argv[1]))
2952
+ except Exception:
2953
+ sys.exit(0)
2954
+ def g(k):
2955
+ v=d.get(k,'')
2956
+ return '' if v is None else str(v).replace('\n',' ')
2957
+ for k in ['outcome','branch','files_changed','insertions','deletions',
2958
+ 'review_cmd','pr_url','assumptions_total','assumptions_high']:
2959
+ print(g(k))
2960
+ " "$_cj" 2>/dev/null)" || return 0
2961
+ [ -z "$_fields" ] && return 0
2962
+
2963
+ local _outcome _branch _files _ins _del _review _pr _atotal _ahigh
2964
+ {
2965
+ IFS= read -r _outcome
2966
+ IFS= read -r _branch
2967
+ IFS= read -r _files
2968
+ IFS= read -r _ins
2969
+ IFS= read -r _del
2970
+ IFS= read -r _review
2971
+ IFS= read -r _pr
2972
+ IFS= read -r _atotal
2973
+ IFS= read -r _ahigh
2974
+ } <<EOF
2975
+ $_fields
2976
+ EOF
2977
+
2978
+ # Human outcome label (mirror build_completion_summary's mapping).
2979
+ local _label
2980
+ case "$_outcome" in
2981
+ complete) _label="Completed" ;;
2982
+ max_iterations) _label="Max iterations" ;;
2983
+ stopped) _label="Stopped" ;;
2984
+ force_stopped) _label="Stopped (not verified-complete)" ;;
2985
+ failed) _label="Failed" ;;
2986
+ intervention) _label="Needs input" ;;
2987
+ *) _label="$_outcome" ;;
2988
+ esac
2989
+
2990
+ # Live app URL (best-effort), same read as build_completion_summary.
2991
+ local _url="" _app_state="$loki_dir/app-runner/state.json"
2992
+ if [ -f "$_app_state" ]; then
2993
+ _url="$(python3 -c "import json,sys
2994
+ try:
2995
+ d=json.load(open(sys.argv[1]))
2996
+ print(d.get('url','') if d.get('status')=='running' else '')
2997
+ except Exception:
2998
+ print('')" "$_app_state" 2>/dev/null)" || _url=""
2999
+ fi
3000
+
3001
+ # Box width matches log_header (66 inner columns). Render lines that fit;
3002
+ # this is decoration, so over-wide content is simply not boxed-truncated
3003
+ # (the durable file carries the full text).
3004
+ echo ""
3005
+ echo -e "${GREEN}+================================================================+${NC}"
3006
+ echo -e "${GREEN}|${NC} ${BOLD}Loki Mode: ${_label}${NC}"
3007
+ echo -e "${GREEN}|${NC}"
3008
+ if [ -n "$_url" ]; then
3009
+ echo -e "${GREEN}|${NC} ${BOLD}${CYAN}Your app is live at ${_url}${NC} ${DIM}- open it to try it${NC}"
3010
+ echo -e "${GREEN}|${NC}"
3011
+ fi
3012
+ echo -e "${GREEN}|${NC} Branch: ${BOLD}${_branch}${NC}"
3013
+ echo -e "${GREEN}|${NC} Files: ${BOLD}${_files}${NC} changed ${GREEN}+${_ins}${NC} / ${RED}-${_del}${NC}"
3014
+ case "$_atotal" in ''|0) : ;; *)
3015
+ echo -e "${GREEN}|${NC} Spec assumptions recorded: ${BOLD}${_atotal}${NC} (${_ahigh} high) ${DIM}see .loki/assumptions/ledger.md${NC}"
3016
+ ;;
3017
+ esac
3018
+ if [ -n "$_pr" ]; then
3019
+ echo -e "${GREEN}|${NC} Pull request: ${_pr}"
3020
+ fi
3021
+ echo -e "${GREEN}|${NC}"
3022
+ echo -e "${GREEN}|${NC} ${DIM}Review the work:${NC}"
3023
+ echo -e "${GREEN}|${NC} ${_review}"
3024
+ echo -e "${GREEN}+================================================================+${NC}"
3025
+ echo ""
3026
+ return 0
3027
+ }
3028
+
2908
3029
  #===============================================================================
2909
3030
  # on_run_complete (Slice 3: opt-in local git output on success)
2910
3031
  #
@@ -16593,6 +16714,35 @@ main() {
16593
16714
  exit 1
16594
16715
  fi
16595
16716
 
16717
+ # v7.82: one-line "Building:" headline under the start banner so the opening
16718
+ # frame reflects the user's own intent (their PRD / brief / this codebase),
16719
+ # not a generic banner. Display-only, derived from already-resolved values:
16720
+ # PRD basename for a file, the recorded brief text for a brief run, or fixed
16721
+ # text for a no-arg in-repo run. Truncated to one tidy line. Gated to an
16722
+ # interactive TTY, not --bg, and opt-out via LOKI_START_HEADLINE=0, so the
16723
+ # off-TTY / background path is byte-identical (no output). Best-effort; a
16724
+ # failure here must never affect parsing or the build flow.
16725
+ if [ -t 1 ] && [ "${BACKGROUND_MODE:-false}" != "true" ] && [ "${LOKI_START_HEADLINE:-1}" != "0" ]; then
16726
+ local _headline=""
16727
+ if [ -n "$PRD_PATH" ]; then
16728
+ _headline="$(basename "$PRD_PATH" 2>/dev/null || echo "$PRD_PATH")"
16729
+ elif [ -f ".loki/state/brief.txt" ]; then
16730
+ # Recorded one-line brief (written by cmd_start). Collapse to a single
16731
+ # line and truncate to ~60 chars so the banner stays clean.
16732
+ local _brief
16733
+ _brief="$(tr '\n' ' ' < .loki/state/brief.txt 2>/dev/null | sed 's/ */ /g; s/^ //; s/ $//')"
16734
+ if [ -n "$_brief" ]; then
16735
+ if [ "${#_brief}" -gt 60 ]; then
16736
+ _brief="${_brief:0:57}..."
16737
+ fi
16738
+ _headline="\"$_brief\""
16739
+ fi
16740
+ fi
16741
+ [ -z "$_headline" ] && _headline="analyzing this codebase"
16742
+ echo -e " ${BOLD}${CYAN}Building: ${_headline}${NC}"
16743
+ echo ""
16744
+ fi
16745
+
16596
16746
  # Handle background mode
16597
16747
  if [ "$BACKGROUND_MODE" = "true" ]; then
16598
16748
  # Initialize .loki directory first
package/completions/_loki CHANGED
@@ -11,7 +11,7 @@ function _loki {
11
11
  case $state in
12
12
  (args)
13
13
  case $line[1] in
14
- start)
14
+ start|quick)
15
15
  _loki_start
16
16
  ;;
17
17
  council)
@@ -101,7 +101,7 @@ function _loki {
101
101
  '1:subcommand:(list show open share)'
102
102
  ;;
103
103
  completions)
104
- _arguments '1:shell:(bash zsh)'
104
+ _arguments '1:shell:(bash zsh install)'
105
105
  ;;
106
106
  monitor)
107
107
  _directories
@@ -122,6 +122,7 @@ function _loki_commands {
122
122
  commands=(
123
123
  'start:Start Loki Mode'
124
124
  'quick:Quick single-task mode'
125
+ 'quickstart:Guided first build from your idea'
125
126
  'monitor:Monitor Docker Compose services with auto-fix'
126
127
  'demo:Interactive 60-second demo'
127
128
  'init:Interactive PRD builder'
@@ -129,11 +130,16 @@ function _loki_commands {
129
130
  'pause:Pause execution'
130
131
  'resume:Resume execution'
131
132
  'status:Show status'
133
+ 'next:Run the right next step for the current build'
134
+ 'ship:Finish the build (gates + PR advice) in one command'
135
+ 'why:Explain why the last run did what it did'
132
136
  'dashboard:Dashboard commands'
137
+ 'web:Start the web app surface'
133
138
  'logs:View session logs'
134
139
  'serve:Start API server'
135
140
  'api:API server commands'
136
141
  'sandbox:Docker sandbox commands'
142
+ 'docker:Run Loki in a Docker container'
137
143
  'notify:Notification commands'
138
144
  'import:Import GitHub issues'
139
145
  'issue:GitHub issue commands'
@@ -153,6 +159,8 @@ function _loki_commands {
153
159
  'metrics:Session productivity report'
154
160
  'share:Share session report as GitHub Gist'
155
161
  'proof:Inspect/share proof-of-run artifacts'
162
+ 'preview:Preview the locally-running app'
163
+ 'deploy:Deploy the built product (CI/CD-aware)'
156
164
  'context:Context window management'
157
165
  'ctx:Context window management (alias)'
158
166
  'code:Codebase intelligence'
@@ -234,7 +242,17 @@ function _loki_start {
234
242
  '-y[Skip confirmation prompts]' \
235
243
  '--budget[Cost budget limit in USD]:amount:' \
236
244
  '--max-iterations[Max iterations]:number:' \
237
- '*:PRD File:_files'
245
+ '*:spec file:_loki_spec_files'
246
+ }
247
+
248
+ # Bias spec completion toward the file shapes Loki accepts (.md/.json/.txt/
249
+ # .yaml/.yml), while still offering directories so the user can navigate into a
250
+ # subfolder. Mirrors the bash completion. Makes the most-common command
251
+ # tab-driven and typo-proof, cutting "PRD file not found" mistakes.
252
+ function _loki_spec_files {
253
+ _alternative \
254
+ 'specs:spec file:_files -g "*.(md|json|txt|yaml|yml)"' \
255
+ 'dirs:directory:_files -/'
238
256
  }
239
257
 
240
258
  function _loki_council {
@@ -5,7 +5,7 @@ _loki_completion() {
5
5
  _init_completion || return
6
6
 
7
7
  # Main subcommands (must match autonomy/loki main case statement)
8
- local main_commands="start quick monitor demo init stop pause resume status dashboard web serve api sandbox notify import github issue config provider reset memory compound checkpoint council dogfood projects enterprise secrets doctor watchdog audit metrics syslog onboard share proof explain plan report cost kpis stats test ci watch telemetry agent context ctx code run export review optimize heal modernize migrate cluster worktree wt trigger failover remote deploy docker mcp magic assets analyze compliance crash open otel preview quickstart rc rollback self-update sentrux setup-skill spec state template trust trust-metrics ultracode update verify voice why wiki bench cleanup logs grill docs cp version completions help"
8
+ local main_commands="start quick monitor demo init stop pause resume status next ship dashboard web serve api sandbox notify import github issue config provider reset memory compound checkpoint council dogfood projects enterprise secrets doctor watchdog audit metrics syslog onboard share proof explain plan report cost kpis stats test ci watch telemetry agent context ctx code run export review optimize heal modernize migrate cluster worktree wt trigger failover remote deploy docker mcp magic assets analyze compliance crash open otel preview quickstart rc rollback self-update sentrux setup-skill spec state template trust trust-metrics ultracode update verify voice why wiki bench cleanup logs grill docs cp version completions help"
9
9
 
10
10
  # 1. If we are on the first argument (subcommand)
11
11
  if [[ $cword -eq 1 ]]; then
@@ -15,7 +15,7 @@ _loki_completion() {
15
15
 
16
16
  # 2. Handle subcommands and their specific flags/args
17
17
  case "${words[1]}" in
18
- start)
18
+ start|quick)
19
19
  # If the previous word was --provider, show provider names
20
20
  if [[ "$prev" == "--provider" ]]; then
21
21
  # Active providers (loki rejects gemini since v7.5.18; cline/aider
@@ -32,8 +32,23 @@ _loki_completion() {
32
32
  return 0
33
33
  fi
34
34
 
35
- # Otherwise, default to file completion (for PRD files)
36
- COMPREPLY=( $(compgen -f -- "$cur") )
35
+ # Bias toward the spec-shaped files Loki actually accepts
36
+ # (.md/.json/.txt/.yaml/.yml), plus directories so the user can still
37
+ # navigate into a subfolder. This makes the most-common command
38
+ # tab-driven and typo-proof, cutting "PRD file not found" mistakes.
39
+ # If the spec-filtered set is empty (no specs here), fall back to
40
+ # generic file completion so nothing the user could type is lost.
41
+ local _specs _dirs _eg_was_set=0
42
+ shopt -q extglob && _eg_was_set=1
43
+ shopt -s extglob
44
+ _specs=$(compgen -f -X '!*.@(md|json|txt|yaml|yml)' -- "$cur")
45
+ [[ "$_eg_was_set" -eq 0 ]] && shopt -u extglob
46
+ _dirs=$(compgen -d -- "$cur")
47
+ if [[ -n "$_specs" || -n "$_dirs" ]]; then
48
+ COMPREPLY=( $_specs $_dirs )
49
+ else
50
+ COMPREPLY=( $(compgen -f -- "$cur") )
51
+ fi
37
52
  ;;
38
53
 
39
54
  council)
@@ -198,7 +213,7 @@ _loki_completion() {
198
213
  ;;
199
214
 
200
215
  completions)
201
- COMPREPLY=( $(compgen -W "bash zsh" -- "$cur") )
216
+ COMPREPLY=( $(compgen -W "bash zsh install" -- "$cur") )
202
217
  ;;
203
218
 
204
219
  context|ctx)
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.82.0"
10
+ __version__ = "7.83.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v7.82.0
5
+ **Version:** v7.83.0
6
6
 
7
7
  ---
8
8
 
@@ -395,7 +395,7 @@ provider works inside the container. Provide auth with your Anthropic API key:
395
395
  # Run Loki Mode in Docker (Claude provider, API-key auth)
396
396
  docker run --rm -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
397
397
  -v $(pwd):/workspace -w /workspace \
398
- asklokesh/loki-mode:7.82.0 start ./my-spec.md
398
+ asklokesh/loki-mode:7.83.0 start ./my-spec.md
399
399
  ```
400
400
 
401
401
  ##### docker compose + .env (no host install)
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var QQ=Object.defineProperty;var ZQ=($)=>$;function zQ($,Q){this[$]=ZQ.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)QQ($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:zQ.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var q$=import.meta.require;var h1={};b(h1,{lokiDir:()=>P,homeLokiDir:()=>i$,findRepoRootForVersion:()=>t$,REPO_ROOT:()=>g});import{resolve as a,dirname as r$}from"path";import{fileURLToPath as XQ}from"url";import{existsSync as R$}from"fs";import{homedir as KQ}from"os";function qQ(){let $=b1;for(let Q=0;Q<6;Q++){if(R$(a($,"VERSION"))&&R$(a($,"autonomy/run.sh")))return $;let Z=r$($);if(Z===$)break;$=Z}return a(b1,"..","..","..")}function t$($){let Q=$;for(let Z=0;Z<6;Z++){if(R$(a(Q,"VERSION"))&&R$(a(Q,"autonomy/run.sh")))return Q;let z=r$(Q);if(z===Q)break;Q=z}return a($,"..","..","..")}function P(){return process.env.LOKI_DIR??a(process.cwd(),".loki")}function i$(){return a(KQ(),".loki")}var b1,g;var C=L(()=>{b1=r$(XQ(import.meta.url));g=qQ()});import{readFileSync as VQ}from"fs";import{resolve as JQ,dirname as UQ}from"path";import{fileURLToPath as WQ}from"url";function E$(){if(Q$!==null)return Q$;let $="7.82.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=UQ(WQ(import.meta.url)),Z=t$(Q);Q$=VQ(JQ(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var e$=L(()=>{C()});var g1={};b(g1,{runOrThrow:()=>HQ,run:()=>F,readStreamCapped:()=>m1,commandVersion:()=>BQ,commandExists:()=>f,ShellError:()=>$1,MAX_STDOUT_BYTES:()=>v1});async function m1($,Q=v1){let Z=$.getReader(),z=new TextDecoder,X="",q=0;try{while(q<Q){let{done:K,value:U}=await Z.read();if(K)break;if(!U)continue;if(q+=U.byteLength,q>Q){let J=U.byteLength-(q-Q);X+=z.decode(U.subarray(0,J),{stream:!0});break}X+=z.decode(U,{stream:!0})}X+=z.decode()}finally{try{await Z.cancel()}catch{}Z.releaseLock()}return X}async function F($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,X;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[q,K,U]=await Promise.all([m1(Z.stdout),new Response(Z.stderr).text(),Z.exited]);return{stdout:q,stderr:K,exitCode:U}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function HQ($,Q={}){let Z=await F($,Q);if(Z.exitCode!==0)throw new $1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=GQ($),Z=await F(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function GQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function BQ($,Q="--version"){if(!await f($))return null;let z=await F([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var v1=16777216,$1;var d=L(()=>{$1=class $1 extends Error{message;exitCode;stdout;stderr;constructor($,Q,Z,z){super($);this.message=$;this.exitCode=Q;this.stdout=Z;this.stderr=z;this.name="ShellError"}}});function s($){return YQ?"":$}var YQ,O,S,_,_Z,I,k,h,V;var c=L(()=>{YQ=(process.env.NO_COLOR??"").length>0;O=s("\x1B[0;31m"),S=s("\x1B[0;32m"),_=s("\x1B[1;33m"),_Z=s("\x1B[0;34m"),I=s("\x1B[0;36m"),k=s("\x1B[1m"),h=s("\x1B[2m"),V=s("\x1B[0m")});import{existsSync as jQ}from"fs";async function Z$(){if(Y$!==void 0)return Y$;let $="/opt/homebrew/bin/python3.12";if(jQ($))return Y$=$,$;let Q=await f("python3.12");if(Q)return Y$=Q,Q;let Z=await f("python3");return Y$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return F([Z,"-c",$],Q)}var Y$;var V$=L(()=>{d()});var X0={};b(X0,{runStatus:()=>oQ});import{existsSync as y,readFileSync as U$,readdirSync as r1,statSync as t1}from"fs";import{resolve as D,basename as vQ}from"path";import{homedir as mQ}from"os";function i1($){let Q=Math.trunc($);if(Q>=1e6)return`${(Math.trunc(Q/1e6*10)/10).toFixed(1)}M`;if(Q>=1000)return`${(Math.trunc(Q/1000*10)/10).toFixed(1)}K`;return String(Q)}function e1($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*N$/Q);if(X>N$)X=N$;let q=N$-X,K=S;if(z>=80)K=O;else if(z>=50)K=_;let U="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),J=i1($),W=i1(Q);return` ${k}${Z}${V} ${K}[${U}]${V} ${z}% (${J} / ${W})`}async function fQ(){if(await f("jq"))return!0;return process.stdout.write(`${O}Error: jq is required but not installed.${V}
2
+ var QQ=Object.defineProperty;var ZQ=($)=>$;function zQ($,Q){this[$]=ZQ.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)QQ($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:zQ.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var q$=import.meta.require;var h1={};b(h1,{lokiDir:()=>P,homeLokiDir:()=>i$,findRepoRootForVersion:()=>t$,REPO_ROOT:()=>g});import{resolve as a,dirname as r$}from"path";import{fileURLToPath as XQ}from"url";import{existsSync as R$}from"fs";import{homedir as KQ}from"os";function qQ(){let $=b1;for(let Q=0;Q<6;Q++){if(R$(a($,"VERSION"))&&R$(a($,"autonomy/run.sh")))return $;let Z=r$($);if(Z===$)break;$=Z}return a(b1,"..","..","..")}function t$($){let Q=$;for(let Z=0;Z<6;Z++){if(R$(a(Q,"VERSION"))&&R$(a(Q,"autonomy/run.sh")))return Q;let z=r$(Q);if(z===Q)break;Q=z}return a($,"..","..","..")}function P(){return process.env.LOKI_DIR??a(process.cwd(),".loki")}function i$(){return a(KQ(),".loki")}var b1,g;var C=L(()=>{b1=r$(XQ(import.meta.url));g=qQ()});import{readFileSync as VQ}from"fs";import{resolve as JQ,dirname as UQ}from"path";import{fileURLToPath as WQ}from"url";function E$(){if(Q$!==null)return Q$;let $="7.83.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=UQ(WQ(import.meta.url)),Z=t$(Q);Q$=VQ(JQ(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var e$=L(()=>{C()});var g1={};b(g1,{runOrThrow:()=>HQ,run:()=>F,readStreamCapped:()=>m1,commandVersion:()=>BQ,commandExists:()=>f,ShellError:()=>$1,MAX_STDOUT_BYTES:()=>v1});async function m1($,Q=v1){let Z=$.getReader(),z=new TextDecoder,X="",q=0;try{while(q<Q){let{done:K,value:U}=await Z.read();if(K)break;if(!U)continue;if(q+=U.byteLength,q>Q){let J=U.byteLength-(q-Q);X+=z.decode(U.subarray(0,J),{stream:!0});break}X+=z.decode(U,{stream:!0})}X+=z.decode()}finally{try{await Z.cancel()}catch{}Z.releaseLock()}return X}async function F($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,X;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[q,K,U]=await Promise.all([m1(Z.stdout),new Response(Z.stderr).text(),Z.exited]);return{stdout:q,stderr:K,exitCode:U}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function HQ($,Q={}){let Z=await F($,Q);if(Z.exitCode!==0)throw new $1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=GQ($),Z=await F(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function GQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function BQ($,Q="--version"){if(!await f($))return null;let z=await F([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var v1=16777216,$1;var d=L(()=>{$1=class $1 extends Error{message;exitCode;stdout;stderr;constructor($,Q,Z,z){super($);this.message=$;this.exitCode=Q;this.stdout=Z;this.stderr=z;this.name="ShellError"}}});function s($){return YQ?"":$}var YQ,O,S,_,_Z,I,k,h,V;var c=L(()=>{YQ=(process.env.NO_COLOR??"").length>0;O=s("\x1B[0;31m"),S=s("\x1B[0;32m"),_=s("\x1B[1;33m"),_Z=s("\x1B[0;34m"),I=s("\x1B[0;36m"),k=s("\x1B[1m"),h=s("\x1B[2m"),V=s("\x1B[0m")});import{existsSync as jQ}from"fs";async function Z$(){if(Y$!==void 0)return Y$;let $="/opt/homebrew/bin/python3.12";if(jQ($))return Y$=$,$;let Q=await f("python3.12");if(Q)return Y$=Q,Q;let Z=await f("python3");return Y$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return F([Z,"-c",$],Q)}var Y$;var V$=L(()=>{d()});var X0={};b(X0,{runStatus:()=>oQ});import{existsSync as y,readFileSync as U$,readdirSync as r1,statSync as t1}from"fs";import{resolve as D,basename as vQ}from"path";import{homedir as mQ}from"os";function i1($){let Q=Math.trunc($);if(Q>=1e6)return`${(Math.trunc(Q/1e6*10)/10).toFixed(1)}M`;if(Q>=1000)return`${(Math.trunc(Q/1000*10)/10).toFixed(1)}K`;return String(Q)}function e1($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*N$/Q);if(X>N$)X=N$;let q=N$-X,K=S;if(z>=80)K=O;else if(z>=50)K=_;let U="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),J=i1($),W=i1(Q);return` ${k}${Z}${V} ${K}[${U}]${V} ${z}% (${J} / ${W})`}async function fQ(){if(await f("jq"))return!0;return process.stdout.write(`${O}Error: jq is required but not installed.${V}
3
3
  `),process.stdout.write(`Install with:
4
4
  `),process.stdout.write(` brew install jq (macOS)
5
5
  `),process.stdout.write(` apt install jq (Debian/Ubuntu)
@@ -410,8 +410,11 @@ Start a session with: loki start <prd>`}}let X=tQ(z);return{exitCode:0,stdout:Q?
410
410
 
411
411
  `),$.fail>0)return process.stdout.write(`${O}Some required prerequisites are missing.${V}
412
412
  `),process.stdout.write(`Install missing dependencies and run 'loki doctor' again.
413
- `),1;if($.warn>0)return process.stdout.write(`${_}All required checks passed with some warnings.${V}
414
- `),0;return process.stdout.write(`${S}All checks passed. System is ready for Loki Mode.${V}
413
+ `),1;if($.warn>0)process.stdout.write(`${_}All required checks passed with some warnings.${V}
414
+ `);else process.stdout.write(`${S}All checks passed. System is ready for Loki Mode.${V}
415
+ `);return process.stdout.write(`
416
+ `),process.stdout.write(`Next: loki quickstart (guided first build from your idea, no PRD needed)
417
+ `),process.stdout.write(` or loki demo (builds a sample todo app end to end) or loki start ./prd.md
415
418
  `),0}async function Y3($){let Q=!1;for(let Z of $)if(Z==="--json")Q=!0;else if(Z==="--help"||Z==="-h")return G3(),0;else return process.stderr.write(`${O}Unknown option: ${Z}${V}
416
419
  `),process.stderr.write(`Usage: loki doctor [--json]
417
420
  `),1;if(Q){let Z=await _0();return process.stdout.write(JSON.stringify(Z,null,2)+`
@@ -793,4 +796,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
793
796
  `),2}default:return process.stderr.write(`Unknown command: ${Q}
794
797
  `),process.stderr.write($Q),2}}s1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var qZ=await KZ(Bun.argv.slice(2));process.exit(qZ);
795
798
 
796
- //# debugId=3E8A7AD89D7DCCD164756E2164756E21
799
+ //# debugId=EFFD39A320D7F56564756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.82.0'
60
+ __version__ = '7.83.0'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "loki-mode",
3
3
  "mcpName": "io.github.asklokesh/loki-mode",
4
- "version": "7.82.0",
4
+ "version": "7.83.0",
5
5
  "description": "Loki Mode by Autonomi. Autonomous spec-to-product system: takes a PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief to a deployed app via the RARV-C closure loop with 8 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
6
6
  "keywords": [
7
7
  "agent",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "loki-mode",
4
4
  "displayName": "Loki Mode",
5
- "version": "7.82.0",
5
+ "version": "7.83.0",
6
6
  "description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 8 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
7
7
  "author": {
8
8
  "name": "Autonomi",