loki-mode 7.12.0 → 7.14.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 +4 -2
- package/VERSION +1 -1
- package/autonomy/lib/wiki-ask.py +137 -0
- package/autonomy/lib/wiki-generator.py +322 -0
- package/autonomy/lib/wiki_index.py +258 -0
- package/autonomy/lib/wiki_llm.py +140 -0
- package/autonomy/loki +304 -11
- package/autonomy/run.sh +62 -12
- package/bin/loki +1 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +202 -0
- package/dashboard/static/index.html +405 -329
- package/docs/INSTALLATION.md +1 -1
- package/docs/R5-AUTO-WIKI-DESIGN.md +137 -0
- package/docs/R6-ROLLBACK-CHECKPOINT-PLAN.md +107 -0
- package/loki-ts/dist/loki.js +245 -206
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
package/autonomy/loki
CHANGED
|
@@ -13111,6 +13111,9 @@ main() {
|
|
|
13111
13111
|
checkpoint|cp)
|
|
13112
13112
|
cmd_checkpoint "$@"
|
|
13113
13113
|
;;
|
|
13114
|
+
rollback)
|
|
13115
|
+
cmd_rollback "$@"
|
|
13116
|
+
;;
|
|
13114
13117
|
council)
|
|
13115
13118
|
cmd_council "$@"
|
|
13116
13119
|
;;
|
|
@@ -13205,6 +13208,9 @@ main() {
|
|
|
13205
13208
|
docs)
|
|
13206
13209
|
cmd_docs "$@"
|
|
13207
13210
|
;;
|
|
13211
|
+
wiki)
|
|
13212
|
+
cmd_wiki "$@"
|
|
13213
|
+
;;
|
|
13208
13214
|
magic)
|
|
13209
13215
|
cmd_magic "$@"
|
|
13210
13216
|
;;
|
|
@@ -16435,12 +16441,19 @@ SHOW_EOF
|
|
|
16435
16441
|
local restored=0
|
|
16436
16442
|
for item in "$cp_dir"/*; do
|
|
16437
16443
|
[ -e "$item" ] || continue
|
|
16438
|
-
local name
|
|
16444
|
+
local name
|
|
16445
|
+
name=$(basename "$item")
|
|
16439
16446
|
[ "$name" = "metadata.json" ] && continue
|
|
16447
|
+
# R6: do not restore the worktree-snapshot sidecar as state.
|
|
16448
|
+
[ "$name" = "worktree-snapshot.txt" ] && continue
|
|
16440
16449
|
|
|
16441
16450
|
if [ -d "$item" ]; then
|
|
16442
|
-
rm -rf ".loki/$name"
|
|
16443
|
-
|
|
16451
|
+
# R6 data-loss fix: NEVER `rm -rf ".loki/$name"` -- the
|
|
16452
|
+
# checkpoint store lives under .loki/state/checkpoints/, so
|
|
16453
|
+
# blowing away .loki/state/ would destroy every checkpoint
|
|
16454
|
+
# (including this one). Merge-copy directory contents instead.
|
|
16455
|
+
mkdir -p ".loki/$name"
|
|
16456
|
+
cp -r "$item"/. ".loki/$name"/ 2>/dev/null && restored=$((restored + 1))
|
|
16444
16457
|
else
|
|
16445
16458
|
cp "$item" ".loki/$name" 2>/dev/null && restored=$((restored + 1))
|
|
16446
16459
|
fi
|
|
@@ -16448,17 +16461,28 @@ SHOW_EOF
|
|
|
16448
16461
|
|
|
16449
16462
|
echo -e " Restored: ${GREEN}$restored${NC} state items from $cp_id"
|
|
16450
16463
|
|
|
16451
|
-
# Show
|
|
16452
|
-
|
|
16453
|
-
|
|
16464
|
+
# Show how to also restore code. R6: prefer the anchored working-tree
|
|
16465
|
+
# snapshot over `git reset --hard <git_sha>`. git_sha is HEAD (the last
|
|
16466
|
+
# commit), and Loki does not commit per iteration, so a hard reset
|
|
16467
|
+
# discards the iteration's work instead of reconstructing it -- the old
|
|
16468
|
+
# hint was misleading.
|
|
16469
|
+
if git rev-parse --verify "refs/loki/cp/${cp_id}" >/dev/null 2>&1; then
|
|
16454
16470
|
echo ""
|
|
16455
|
-
echo -e " ${YELLOW}Note:${NC} Session state
|
|
16456
|
-
echo " To also
|
|
16471
|
+
echo -e " ${YELLOW}Note:${NC} Session state restored. Code is unchanged."
|
|
16472
|
+
echo " To also restore the working tree to this checkpoint's snapshot:"
|
|
16457
16473
|
echo ""
|
|
16458
|
-
echo -e " ${DIM}git
|
|
16474
|
+
echo -e " ${DIM}git stash apply refs/loki/cp/${cp_id}${NC}"
|
|
16459
16475
|
echo ""
|
|
16460
|
-
echo -e " ${
|
|
16461
|
-
|
|
16476
|
+
echo -e " ${DIM}(restores tracked files; newly-added files are not removed)${NC}"
|
|
16477
|
+
else
|
|
16478
|
+
local cp_sha
|
|
16479
|
+
cp_sha=$(_CP_METADATA="$metadata" python3 -c "import json, os; d=json.load(open(os.environ['_CP_METADATA'])); print(d.get('git_sha','unknown'))" 2>/dev/null)
|
|
16480
|
+
if [ -n "$cp_sha" ] && [ "$cp_sha" != "unknown" ] && [ "$cp_sha" != "not-a-git-repo" ]; then
|
|
16481
|
+
echo ""
|
|
16482
|
+
echo -e " ${YELLOW}Note:${NC} Session state restored, but no working-tree snapshot"
|
|
16483
|
+
echo " was captured for this checkpoint, so code changes since the last"
|
|
16484
|
+
echo -e " commit (${cp_sha:0:8}) are not restorable from here."
|
|
16485
|
+
fi
|
|
16462
16486
|
fi
|
|
16463
16487
|
;;
|
|
16464
16488
|
|
|
@@ -16494,6 +16518,157 @@ SHOW_EOF
|
|
|
16494
16518
|
esac
|
|
16495
16519
|
}
|
|
16496
16520
|
|
|
16521
|
+
# R6: one-command rollback. Top-level, obvious entry point that mirrors the Bun
|
|
16522
|
+
# `loki rollback` subcommands (list/show/to/latest) so both routes are at parity.
|
|
16523
|
+
# Restore is destructive on .loki/ state, so it ALWAYS captures a forced
|
|
16524
|
+
# pre-rollback snapshot first (re-undoability invariant) and then glob-restores
|
|
16525
|
+
# whatever the checkpoint dir contains (works across all three checkpoint writers).
|
|
16526
|
+
cmd_rollback() {
|
|
16527
|
+
local subcommand="${1:-help}"
|
|
16528
|
+
shift 2>/dev/null || true
|
|
16529
|
+
|
|
16530
|
+
local checkpoints_dir=".loki/state/checkpoints"
|
|
16531
|
+
local index_file="$checkpoints_dir/index.jsonl"
|
|
16532
|
+
|
|
16533
|
+
# Resolve the most recent checkpoint id from on-disk cp-*/chk-* dirs, sorted
|
|
16534
|
+
# by mtime (newest last). Mirrors the Bun "latest = last entry" semantics but
|
|
16535
|
+
# is robust to the three differing id formats.
|
|
16536
|
+
_rollback_latest_id() {
|
|
16537
|
+
[ -d "$checkpoints_dir" ] || return 1
|
|
16538
|
+
ls -1dt "$checkpoints_dir"/*/ 2>/dev/null | head -1 | sed 's#/$##' | xargs -I{} basename {} 2>/dev/null
|
|
16539
|
+
}
|
|
16540
|
+
|
|
16541
|
+
# Force a pre-rollback snapshot of current state, then glob-restore the target.
|
|
16542
|
+
# Delegates the actual restore to `cmd_checkpoint rollback`, which already
|
|
16543
|
+
# glob-restores and is shared with the manual path (no duplication).
|
|
16544
|
+
_rollback_restore() {
|
|
16545
|
+
local target_id="$1"
|
|
16546
|
+
local want_code="$2"
|
|
16547
|
+
|
|
16548
|
+
# Validate id (defense in depth; cmd_checkpoint validates again).
|
|
16549
|
+
if [[ ! "$target_id" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
|
16550
|
+
echo -e "${RED}Error: Invalid checkpoint ID${NC}"
|
|
16551
|
+
return 1
|
|
16552
|
+
fi
|
|
16553
|
+
if [ ! -d "$checkpoints_dir/$target_id" ]; then
|
|
16554
|
+
echo -e "${RED}Error: Checkpoint not found: $target_id${NC}"
|
|
16555
|
+
echo "Run 'loki rollback list' to see available checkpoints."
|
|
16556
|
+
return 1
|
|
16557
|
+
fi
|
|
16558
|
+
|
|
16559
|
+
# Re-undoability: snapshot current state before overwriting it.
|
|
16560
|
+
local pre_id
|
|
16561
|
+
pre_id="rb-pre-$(date -u '+%Y%m%d-%H%M%S')"
|
|
16562
|
+
local pre_dir="$checkpoints_dir/$pre_id"
|
|
16563
|
+
mkdir -p "$pre_dir"
|
|
16564
|
+
local saved=0
|
|
16565
|
+
for item in .loki/session.json .loki/dashboard-state.json .loki/CONTINUITY.md .loki/autonomy-state.json .loki/state .loki/queue; do
|
|
16566
|
+
if [ -e "$item" ]; then
|
|
16567
|
+
cp -r "$item" "$pre_dir/" 2>/dev/null && saved=$((saved + 1))
|
|
16568
|
+
fi
|
|
16569
|
+
done
|
|
16570
|
+
_RB_PID="$pre_id" _RB_TS="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" _RB_DIR="$pre_dir" \
|
|
16571
|
+
_RB_INDEX="$index_file" _RB_CHKDIR="$checkpoints_dir" python3 << 'RB_PRE_EOF' 2>/dev/null || true
|
|
16572
|
+
import json, os
|
|
16573
|
+
meta = {"id": os.environ["_RB_PID"], "timestamp": os.environ["_RB_TS"],
|
|
16574
|
+
"message": "pre-rollback snapshot", "created_by": "loki rollback"}
|
|
16575
|
+
d = os.environ["_RB_DIR"]
|
|
16576
|
+
os.makedirs(d, exist_ok=True)
|
|
16577
|
+
with open(os.path.join(d, "metadata.json"), "w") as f:
|
|
16578
|
+
json.dump(meta, f, indent=2)
|
|
16579
|
+
os.makedirs(os.environ["_RB_CHKDIR"], exist_ok=True)
|
|
16580
|
+
with open(os.environ["_RB_INDEX"], "a") as f:
|
|
16581
|
+
f.write(json.dumps({"id": meta["id"], "timestamp": meta["timestamp"], "message": meta["message"]}) + "\n")
|
|
16582
|
+
RB_PRE_EOF
|
|
16583
|
+
echo -e " ${DIM}Saved prior state as ${pre_id} (undo this rollback with: loki rollback to ${pre_id})${NC}"
|
|
16584
|
+
|
|
16585
|
+
# Perform the actual state restore via the shared glob-restore path.
|
|
16586
|
+
cmd_checkpoint rollback "$target_id"
|
|
16587
|
+
|
|
16588
|
+
# Optional code restore from the anchored working-tree snapshot.
|
|
16589
|
+
if [ "$want_code" = "1" ]; then
|
|
16590
|
+
if git rev-parse --verify "refs/loki/cp/${target_id}" >/dev/null 2>&1; then
|
|
16591
|
+
echo ""
|
|
16592
|
+
echo -e " ${YELLOW}Restoring working tree from snapshot refs/loki/cp/${target_id}...${NC}"
|
|
16593
|
+
if git stash apply "refs/loki/cp/${target_id}" 2>/dev/null; then
|
|
16594
|
+
echo -e " ${GREEN}Working tree restored.${NC} (tracked files only; newly-added files were not removed)"
|
|
16595
|
+
else
|
|
16596
|
+
echo -e " ${RED}Could not apply snapshot cleanly.${NC} Resolve conflicts, or run: git stash apply refs/loki/cp/${target_id}"
|
|
16597
|
+
fi
|
|
16598
|
+
else
|
|
16599
|
+
echo ""
|
|
16600
|
+
echo -e " ${YELLOW}No working-tree snapshot anchored for ${target_id}; code not restored.${NC}"
|
|
16601
|
+
fi
|
|
16602
|
+
fi
|
|
16603
|
+
}
|
|
16604
|
+
|
|
16605
|
+
case "$subcommand" in
|
|
16606
|
+
list|ls)
|
|
16607
|
+
cmd_checkpoint list
|
|
16608
|
+
;;
|
|
16609
|
+
show)
|
|
16610
|
+
cmd_checkpoint show "$@"
|
|
16611
|
+
;;
|
|
16612
|
+
to)
|
|
16613
|
+
local target="${1:-}"
|
|
16614
|
+
local want_code=0
|
|
16615
|
+
shift 2>/dev/null || true
|
|
16616
|
+
for a in "$@"; do [ "$a" = "--code" ] && want_code=1; done
|
|
16617
|
+
if [ -z "$target" ]; then
|
|
16618
|
+
echo -e "${RED}Error: Specify a checkpoint ID${NC}"
|
|
16619
|
+
echo "Usage: loki rollback to <id> [--code]"
|
|
16620
|
+
echo "Run 'loki rollback list' to see available checkpoints."
|
|
16621
|
+
return 1
|
|
16622
|
+
fi
|
|
16623
|
+
echo -e "${BOLD}Rolling back to checkpoint: $target${NC}"
|
|
16624
|
+
echo ""
|
|
16625
|
+
_rollback_restore "$target" "$want_code"
|
|
16626
|
+
;;
|
|
16627
|
+
latest)
|
|
16628
|
+
local want_code=0
|
|
16629
|
+
for a in "$@"; do [ "$a" = "--code" ] && want_code=1; done
|
|
16630
|
+
local latest_id
|
|
16631
|
+
latest_id=$(_rollback_latest_id)
|
|
16632
|
+
if [ -z "$latest_id" ]; then
|
|
16633
|
+
echo -e "${RED}No checkpoints found to roll back to.${NC}"
|
|
16634
|
+
return 1
|
|
16635
|
+
fi
|
|
16636
|
+
echo -e "${BOLD}Rolling back to latest checkpoint: $latest_id${NC}"
|
|
16637
|
+
echo ""
|
|
16638
|
+
_rollback_restore "$latest_id" "$want_code"
|
|
16639
|
+
;;
|
|
16640
|
+
help|--help|-h)
|
|
16641
|
+
echo -e "${BOLD}loki rollback${NC} - One-command rollback to a checkpoint"
|
|
16642
|
+
echo ""
|
|
16643
|
+
echo "Restore .loki/ state and iteration/conversation context to a"
|
|
16644
|
+
echo "previous checkpoint. Every rollback first saves your current state"
|
|
16645
|
+
echo "as a pre-rollback snapshot, so you can always undo the undo."
|
|
16646
|
+
echo ""
|
|
16647
|
+
echo "Usage: loki rollback <command> [args]"
|
|
16648
|
+
echo ""
|
|
16649
|
+
echo "Commands:"
|
|
16650
|
+
echo " list List recent checkpoints"
|
|
16651
|
+
echo " show <id> Show checkpoint details"
|
|
16652
|
+
echo " to <id> [--code] Restore to checkpoint <id>"
|
|
16653
|
+
echo " latest [--code] Restore to the most recent checkpoint"
|
|
16654
|
+
echo ""
|
|
16655
|
+
echo " --code Also restore the working tree from the anchored"
|
|
16656
|
+
echo " git snapshot (tracked files only; overwrites them)."
|
|
16657
|
+
echo ""
|
|
16658
|
+
echo "Examples:"
|
|
16659
|
+
echo " loki rollback list"
|
|
16660
|
+
echo " loki rollback latest"
|
|
16661
|
+
echo " loki rollback to cp-3-1717000000"
|
|
16662
|
+
echo " loki rollback to cp-3-1717000000 --code"
|
|
16663
|
+
;;
|
|
16664
|
+
*)
|
|
16665
|
+
echo -e "${RED}Unknown rollback command: $subcommand${NC}"
|
|
16666
|
+
echo "Run 'loki rollback help' for usage."
|
|
16667
|
+
return 1
|
|
16668
|
+
;;
|
|
16669
|
+
esac
|
|
16670
|
+
}
|
|
16671
|
+
|
|
16497
16672
|
# Completion Council management
|
|
16498
16673
|
cmd_council() {
|
|
16499
16674
|
local subcommand="${1:-status}"
|
|
@@ -22633,6 +22808,124 @@ run_debate(
|
|
|
22633
22808
|
log_info "Debate complete"
|
|
22634
22809
|
}
|
|
22635
22810
|
|
|
22811
|
+
# =============================================================================
|
|
22812
|
+
# loki wiki -- auto-generated, cited per-project codebase wiki + Q&A (R5).
|
|
22813
|
+
#
|
|
22814
|
+
# Loki's answer to Devin DeepWiki: a persistent, queryable wiki built from the
|
|
22815
|
+
# codebase, where every section cites the real source files it came from, plus
|
|
22816
|
+
# a grounded `ask` that returns cited answers (file:line). Heavy work lives in
|
|
22817
|
+
# the Python core (autonomy/lib/wiki-generator.py, wiki-ask.py, wiki_index.py);
|
|
22818
|
+
# this is a thin dispatcher, mirroring how cmd_proof delegates to the proof
|
|
22819
|
+
# generator. Generation is incremental (skips when the codebase is unchanged).
|
|
22820
|
+
# =============================================================================
|
|
22821
|
+
cmd_wiki() {
|
|
22822
|
+
local subcmd="${1:-}"
|
|
22823
|
+
shift 2>/dev/null || true
|
|
22824
|
+
|
|
22825
|
+
local lib_dir="${_LOKI_SCRIPT_DIR}/lib"
|
|
22826
|
+
|
|
22827
|
+
case "$subcmd" in
|
|
22828
|
+
generate) _wiki_generate "$lib_dir" "$@" ;;
|
|
22829
|
+
show) _wiki_show "$@" ;;
|
|
22830
|
+
ask) _wiki_ask "$lib_dir" "$@" ;;
|
|
22831
|
+
--help|-h|help|"")
|
|
22832
|
+
echo -e "${BOLD}loki wiki${NC} - Auto-generated, cited codebase wiki + Q&A"
|
|
22833
|
+
echo ""
|
|
22834
|
+
echo "Usage: loki wiki <command> [options]"
|
|
22835
|
+
echo ""
|
|
22836
|
+
echo "Commands:"
|
|
22837
|
+
echo " generate [path] [--force] Build/refresh the cited wiki in .loki/wiki/"
|
|
22838
|
+
echo " show [section] Print the wiki (or one section: architecture|modules|data-flow)"
|
|
22839
|
+
echo " ask \"<question>\" Cited answer grounded in the codebase (file:line)"
|
|
22840
|
+
echo ""
|
|
22841
|
+
echo "Each wiki section cites the real source files it was built from."
|
|
22842
|
+
echo "Generation is incremental: it skips when the codebase is unchanged."
|
|
22843
|
+
echo ""
|
|
22844
|
+
echo "Examples:"
|
|
22845
|
+
echo " loki wiki generate # build wiki for current project"
|
|
22846
|
+
echo " loki wiki show architecture # show one section"
|
|
22847
|
+
echo " loki wiki ask \"how does the cli dispatch commands\""
|
|
22848
|
+
return 0
|
|
22849
|
+
;;
|
|
22850
|
+
*)
|
|
22851
|
+
log_error "Unknown wiki command: $subcmd"
|
|
22852
|
+
echo "Run 'loki wiki --help' for usage."
|
|
22853
|
+
return 1
|
|
22854
|
+
;;
|
|
22855
|
+
esac
|
|
22856
|
+
}
|
|
22857
|
+
|
|
22858
|
+
_wiki_generate() {
|
|
22859
|
+
local lib_dir="$1"; shift
|
|
22860
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
22861
|
+
log_error "python3 is required for 'loki wiki generate'"
|
|
22862
|
+
return 1
|
|
22863
|
+
fi
|
|
22864
|
+
python3 "$lib_dir/wiki-generator.py" "$@"
|
|
22865
|
+
}
|
|
22866
|
+
|
|
22867
|
+
_wiki_show() {
|
|
22868
|
+
local section=""
|
|
22869
|
+
local target="."
|
|
22870
|
+
while [[ $# -gt 0 ]]; do
|
|
22871
|
+
case "$1" in
|
|
22872
|
+
--help|-h)
|
|
22873
|
+
echo "Usage: loki wiki show [section]"
|
|
22874
|
+
echo "Sections: architecture, modules, data-flow"
|
|
22875
|
+
return 0
|
|
22876
|
+
;;
|
|
22877
|
+
-*) log_error "Unknown option: $1"; return 1 ;;
|
|
22878
|
+
*) section="$1"; shift ;;
|
|
22879
|
+
esac
|
|
22880
|
+
done
|
|
22881
|
+
local wiki_dir="$target/.loki/wiki"
|
|
22882
|
+
if [ ! -d "$wiki_dir" ]; then
|
|
22883
|
+
log_error "No wiki found. Run 'loki wiki generate' first."
|
|
22884
|
+
return 1
|
|
22885
|
+
fi
|
|
22886
|
+
if [ -n "$section" ]; then
|
|
22887
|
+
local f="$wiki_dir/${section}.md"
|
|
22888
|
+
if [ ! -f "$f" ]; then
|
|
22889
|
+
log_error "No such section: $section (try: architecture, modules, data-flow)"
|
|
22890
|
+
return 1
|
|
22891
|
+
fi
|
|
22892
|
+
cat "$f"
|
|
22893
|
+
else
|
|
22894
|
+
if [ -f "$wiki_dir/index.md" ]; then
|
|
22895
|
+
cat "$wiki_dir/index.md"
|
|
22896
|
+
else
|
|
22897
|
+
log_error "Wiki index not found. Run 'loki wiki generate'."
|
|
22898
|
+
return 1
|
|
22899
|
+
fi
|
|
22900
|
+
fi
|
|
22901
|
+
}
|
|
22902
|
+
|
|
22903
|
+
_wiki_ask() {
|
|
22904
|
+
local lib_dir="$1"; shift
|
|
22905
|
+
local question=""
|
|
22906
|
+
local extra=()
|
|
22907
|
+
while [[ $# -gt 0 ]]; do
|
|
22908
|
+
case "$1" in
|
|
22909
|
+
--help|-h)
|
|
22910
|
+
echo "Usage: loki wiki ask \"<question>\" [--json] [--k N]"
|
|
22911
|
+
return 0
|
|
22912
|
+
;;
|
|
22913
|
+
--json|--quiet) extra+=("$1"); shift ;;
|
|
22914
|
+
--k) extra+=("--k" "${2:-6}"); shift 2 ;;
|
|
22915
|
+
*) if [ -z "$question" ]; then question="$1"; else question="$question $1"; fi; shift ;;
|
|
22916
|
+
esac
|
|
22917
|
+
done
|
|
22918
|
+
if [ -z "$question" ]; then
|
|
22919
|
+
log_error "Provide a question: loki wiki ask \"how does X work\""
|
|
22920
|
+
return 1
|
|
22921
|
+
fi
|
|
22922
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
22923
|
+
log_error "python3 is required for 'loki wiki ask'"
|
|
22924
|
+
return 1
|
|
22925
|
+
fi
|
|
22926
|
+
python3 "$lib_dir/wiki-ask.py" --question "$question" "${extra[@]}"
|
|
22927
|
+
}
|
|
22928
|
+
|
|
22636
22929
|
cmd_magic() {
|
|
22637
22930
|
local subcmd="${1:-help}"
|
|
22638
22931
|
shift 2>/dev/null || true
|
package/autonomy/run.sh
CHANGED
|
@@ -7443,10 +7443,18 @@ create_checkpoint() {
|
|
|
7443
7443
|
|
|
7444
7444
|
mkdir -p "$checkpoint_dir"
|
|
7445
7445
|
|
|
7446
|
-
# Only checkpoint if there are uncommitted changes
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7446
|
+
# Only checkpoint if there are uncommitted changes.
|
|
7447
|
+
# R6: _LOKI_CP_FORCE=1 bypasses this guard. Used by rollback to guarantee a
|
|
7448
|
+
# pre-rollback snapshot of .loki/ state even when the git tree is clean (the
|
|
7449
|
+
# .loki/ state files about to be overwritten are not git-tracked, so the
|
|
7450
|
+
# clean-tree guard would otherwise skip the safety snapshot). Mirrors the
|
|
7451
|
+
# Bun `forceCreate` seam in checkpoint.ts.
|
|
7452
|
+
if [ "${_LOKI_CP_FORCE:-0}" != "1" ]; then
|
|
7453
|
+
if git diff --quiet 2>/dev/null && git diff --cached --quiet 2>/dev/null; then
|
|
7454
|
+
log_info "No uncommitted changes to checkpoint"
|
|
7455
|
+
_LAST_CHECKPOINT_ID=""
|
|
7456
|
+
return 0
|
|
7457
|
+
fi
|
|
7450
7458
|
fi
|
|
7451
7459
|
|
|
7452
7460
|
# Capture git state
|
|
@@ -7465,7 +7473,9 @@ create_checkpoint() {
|
|
|
7465
7473
|
|
|
7466
7474
|
# Copy critical state files (lightweight -- not full .loki/)
|
|
7467
7475
|
# BUG-ST-009: Include autonomy-state.json in checkpoint backup
|
|
7468
|
-
|
|
7476
|
+
# R6: Include CONTINUITY.md so a rollback also restores iteration/conversation
|
|
7477
|
+
# handoff context, not just machine state. Mirrors Bun COPIED_FILES.
|
|
7478
|
+
for f in state/orchestrator.json autonomy-state.json queue/pending.json queue/completed.json queue/in-progress.json queue/current-task.json CONTINUITY.md; do
|
|
7469
7479
|
if [ -f ".loki/$f" ]; then
|
|
7470
7480
|
local target_dir="$cp_dir/$(dirname "$f")"
|
|
7471
7481
|
mkdir -p "$target_dir"
|
|
@@ -7473,6 +7483,23 @@ create_checkpoint() {
|
|
|
7473
7483
|
fi
|
|
7474
7484
|
done
|
|
7475
7485
|
|
|
7486
|
+
# R6: capture a real working-tree snapshot so code can be truly undone later.
|
|
7487
|
+
# Loki does not commit per iteration, so git_sha (HEAD) cannot reconstruct
|
|
7488
|
+
# this iteration's working tree. `git stash create` builds a commit object
|
|
7489
|
+
# capturing tracked changes WITHOUT disturbing the tree; we then anchor it
|
|
7490
|
+
# under refs/loki/cp/<id> so `git gc` cannot prune the dangling commit. The
|
|
7491
|
+
# snapshot sha goes in a sidecar (worktree-snapshot.txt), NOT metadata.json,
|
|
7492
|
+
# to preserve byte-for-byte parity with the Bun port.
|
|
7493
|
+
# Honest limit: captures tracked changes only (not untracked/ignored files).
|
|
7494
|
+
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
7495
|
+
local snap_sha
|
|
7496
|
+
snap_sha=$(git stash create "loki checkpoint ${checkpoint_id}" 2>/dev/null || echo "")
|
|
7497
|
+
if [ -n "$snap_sha" ]; then
|
|
7498
|
+
git update-ref "refs/loki/cp/${checkpoint_id}" "$snap_sha" 2>/dev/null \
|
|
7499
|
+
&& printf '%s\n' "$snap_sha" > "$cp_dir/worktree-snapshot.txt" 2>/dev/null || true
|
|
7500
|
+
fi
|
|
7501
|
+
fi
|
|
7502
|
+
|
|
7476
7503
|
# Write checkpoint metadata (use python3 json.dumps for safe serialization)
|
|
7477
7504
|
local phase_val
|
|
7478
7505
|
phase_val=$(cat .loki/state/orchestrator.json 2>/dev/null | python3 -c 'import sys,json; print(json.load(sys.stdin).get("currentPhase","unknown"))' 2>/dev/null || echo 'unknown')
|
|
@@ -7531,6 +7558,10 @@ print(json.dumps({'id':m['id'],'ts':m['timestamp'],'iter':m['iteration'],'task':
|
|
|
7531
7558
|
fi
|
|
7532
7559
|
|
|
7533
7560
|
log_info "Checkpoint created: ${checkpoint_id} (git: ${git_sha:0:8})"
|
|
7561
|
+
# R6: expose the id via a global so callers (rollback, run loop) can reference
|
|
7562
|
+
# it without parsing stdout (log_info writes to stdout, so command-substitution
|
|
7563
|
+
# capture would include log lines).
|
|
7564
|
+
_LAST_CHECKPOINT_ID="$checkpoint_id"
|
|
7534
7565
|
}
|
|
7535
7566
|
|
|
7536
7567
|
rollback_to_checkpoint() {
|
|
@@ -7558,11 +7589,18 @@ rollback_to_checkpoint() {
|
|
|
7558
7589
|
|
|
7559
7590
|
log_warn "Rolling back to checkpoint: ${checkpoint_id}"
|
|
7560
7591
|
|
|
7561
|
-
#
|
|
7562
|
-
|
|
7592
|
+
# R6 re-undoability invariant: force a pre-rollback snapshot of CURRENT state
|
|
7593
|
+
# before overwriting, even if the git tree is clean (the .loki/ state we are
|
|
7594
|
+
# about to clobber is not git-tracked). _LOKI_CP_FORCE bypasses the clean-tree
|
|
7595
|
+
# guard. Capture the snapshot id so we can tell the user how to undo the undo.
|
|
7596
|
+
_LOKI_CP_FORCE=1 create_checkpoint "pre-rollback snapshot (before restoring ${checkpoint_id})" "rollback"
|
|
7597
|
+
local pre_rollback_id="${_LAST_CHECKPOINT_ID:-}"
|
|
7598
|
+
if [ -n "$pre_rollback_id" ]; then
|
|
7599
|
+
log_info "Saved prior state as ${pre_rollback_id} (undo this rollback with: loki rollback to ${pre_rollback_id})"
|
|
7600
|
+
fi
|
|
7563
7601
|
|
|
7564
|
-
# Restore state files
|
|
7565
|
-
for f in state/orchestrator.json queue/pending.json queue/completed.json queue/in-progress.json queue/current-task.json; do
|
|
7602
|
+
# Restore state files (R6: CONTINUITY.md restores iteration/conversation context)
|
|
7603
|
+
for f in state/orchestrator.json queue/pending.json queue/completed.json queue/in-progress.json queue/current-task.json CONTINUITY.md; do
|
|
7566
7604
|
if [ -f "${cp_dir}/${f}" ]; then
|
|
7567
7605
|
local target_dir=".loki/$(dirname "$f")"
|
|
7568
7606
|
mkdir -p "$target_dir"
|
|
@@ -7599,9 +7637,17 @@ print(json.dumps({'event':'rollback','checkpoint':os.environ['_RB_CPID'],'git_sh
|
|
|
7599
7637
|
|
|
7600
7638
|
log_info "State files restored from checkpoint: ${checkpoint_id}"
|
|
7601
7639
|
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7640
|
+
# R6: the prior hint `git reset --hard ${git_sha}` was MISLEADING. git_sha is
|
|
7641
|
+
# HEAD (the last commit), and Loki does not commit per iteration, so a hard
|
|
7642
|
+
# reset would discard the iteration's work rather than reconstruct it. The
|
|
7643
|
+
# correct, durable recovery is the anchored working-tree snapshot, if present.
|
|
7644
|
+
if [ -f "${cp_dir}/worktree-snapshot.txt" ]; then
|
|
7645
|
+
log_info "To also restore the working tree to this checkpoint:"
|
|
7646
|
+
log_info " git stash apply refs/loki/cp/${checkpoint_id}"
|
|
7647
|
+
elif [ -n "$git_sha" ] && [ "$git_sha" != "no-git" ]; then
|
|
7648
|
+
log_info "Git SHA at checkpoint (last commit): ${git_sha}"
|
|
7649
|
+
log_info "Note: no working-tree snapshot was captured for this checkpoint;"
|
|
7650
|
+
log_info "code changes since the last commit are not restorable from here."
|
|
7605
7651
|
fi
|
|
7606
7652
|
}
|
|
7607
7653
|
|
|
@@ -12001,6 +12047,10 @@ if __name__ == "__main__":
|
|
|
12001
12047
|
|
|
12002
12048
|
# Checkpoint after each iteration (v5.57.0)
|
|
12003
12049
|
create_checkpoint "iteration-${ITERATION_COUNT} complete" "iteration-${ITERATION_COUNT}"
|
|
12050
|
+
# R6: prominent "you can safely undo this" signal so users run boldly.
|
|
12051
|
+
if [ -n "${_LAST_CHECKPOINT_ID:-}" ]; then
|
|
12052
|
+
log_info "Safety net: checkpoint ${_LAST_CHECKPOINT_ID} saved. Undo this iteration with: loki rollback to ${_LAST_CHECKPOINT_ID}"
|
|
12053
|
+
fi
|
|
12004
12054
|
|
|
12005
12055
|
# Quality gates (v6.10.0 - escalation ladder)
|
|
12006
12056
|
log_step "Post-iteration: running quality gates..."
|
package/bin/loki
CHANGED
|
@@ -116,7 +116,7 @@ fi
|
|
|
116
116
|
# Two-token routes (provider show/list, memory list/index) match on the first
|
|
117
117
|
# token only; the Bun dispatcher handles subcommand routing internally.
|
|
118
118
|
case "${1:-}" in
|
|
119
|
-
version|--version|-v|status|stats|doctor|provider|memory|rollback|internal|kpis|proof)
|
|
119
|
+
version|--version|-v|status|stats|doctor|provider|memory|rollback|internal|kpis|proof|wiki)
|
|
120
120
|
# v7.5.2: rollback added (wires loki-ts/src/commands/rollback.ts).
|
|
121
121
|
# v7.5.3: internal added for autonomy/run.sh phase1-hooks calls.
|
|
122
122
|
# v7.5.28: kpis added (Phase K MVP: read-only KPI snapshot).
|