loki-mode 7.11.0 → 7.13.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/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 +273 -12
- package/autonomy/run.sh +72 -0
- package/bin/loki +1 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +108 -0
- package/dashboard/static/index.html +394 -329
- package/docs/INSTALLATION.md +1 -1
- package/docs/R5-AUTO-WIKI-DESIGN.md +137 -0
- package/docs/R7-ZERO-CONFIG-FIRST-RUN-PLAN.md +137 -0
- package/loki-ts/dist/loki.js +224 -198
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
package/autonomy/loki
CHANGED
|
@@ -739,6 +739,17 @@ detect_arg_type() {
|
|
|
739
739
|
return 0
|
|
740
740
|
fi
|
|
741
741
|
|
|
742
|
+
# R7 (zero-config first run): a one-line brief. An arg that contains
|
|
743
|
+
# whitespace and matched none of the file/issue/path patterns above is a
|
|
744
|
+
# natural-language brief (e.g. "build a todo app"), NOT a PRD path. This is
|
|
745
|
+
# additive: a single-token arg with no whitespace still falls through to
|
|
746
|
+
# "unknown" (PRD-path back-compat) below. The `--brief` flag is the
|
|
747
|
+
# deterministic escape hatch for the rare single-word brief.
|
|
748
|
+
if [[ "$arg" == *[[:space:]]* ]]; then
|
|
749
|
+
echo "brief"
|
|
750
|
+
return 0
|
|
751
|
+
fi
|
|
752
|
+
|
|
742
753
|
# Fallback: unknown. Caller treats as PRD for backward compat.
|
|
743
754
|
echo "unknown"
|
|
744
755
|
}
|
|
@@ -752,6 +763,7 @@ cmd_start() {
|
|
|
752
763
|
local args=()
|
|
753
764
|
local prd_file=""
|
|
754
765
|
local provider=""
|
|
766
|
+
local brief_text="" # R7: explicit one-line brief (--brief "...")
|
|
755
767
|
local bmad_project_path=""
|
|
756
768
|
local openspec_change_path=""
|
|
757
769
|
local mirofish_url=""
|
|
@@ -784,6 +796,8 @@ cmd_start() {
|
|
|
784
796
|
echo " PRD mode - path ending in .md/.json/.txt/.yaml -> build from PRD"
|
|
785
797
|
echo " ISSUE mode - GitHub/GitLab/Jira/Azure DevOps URL, owner/repo#N,"
|
|
786
798
|
echo " PROJ-123, #123, or bare number -> generate PRD from issue"
|
|
799
|
+
echo " BRIEF mode - a quoted one-line description (with spaces) -> fast"
|
|
800
|
+
echo " zero-config first run; visible artifact + proof quickly"
|
|
787
801
|
echo " no input - analyze current directory, auto-generate PRD"
|
|
788
802
|
echo ""
|
|
789
803
|
echo "Arguments:"
|
|
@@ -793,6 +807,7 @@ cmd_start() {
|
|
|
793
807
|
echo "Explicit mode flags (override auto-detection):"
|
|
794
808
|
echo " --prd FILE Force PRD mode with FILE"
|
|
795
809
|
echo " --issue URL|NUM Force issue mode with URL or number"
|
|
810
|
+
echo " --brief \"TEXT\" Force zero-config brief mode (fast first run)"
|
|
796
811
|
echo ""
|
|
797
812
|
echo "Options:"
|
|
798
813
|
echo " --provider NAME AI provider: claude (default), codex, cline, aider"
|
|
@@ -816,6 +831,7 @@ cmd_start() {
|
|
|
816
831
|
echo " --mirofish-bg Run MiroFish pipeline in background"
|
|
817
832
|
echo " --no-mirofish Disable MiroFish even if env var is set"
|
|
818
833
|
echo " --no-plan Skip auto-shown PRD analysis at startup"
|
|
834
|
+
echo " --brief \"TEXT\" Zero-config fast first run from a one-line brief"
|
|
819
835
|
echo " --yes, -y Skip confirmation prompts (auto-confirm)"
|
|
820
836
|
echo ""
|
|
821
837
|
echo "Issue-mode options (only used when input is an ISSUE-REF):"
|
|
@@ -836,6 +852,8 @@ cmd_start() {
|
|
|
836
852
|
echo ""
|
|
837
853
|
echo "Examples:"
|
|
838
854
|
echo " loki start # Interactive, analyze current dir"
|
|
855
|
+
echo " loki start \"build a todo app\" # BRIEF mode (zero-config fast run)"
|
|
856
|
+
echo " loki start --brief \"snake\" # BRIEF mode (single-word escape hatch)"
|
|
839
857
|
echo " loki start ./prd.md # PRD mode"
|
|
840
858
|
echo " loki start https://github.com/o/r/issues/42 # ISSUE mode (GitHub)"
|
|
841
859
|
echo " loki start 123 # ISSUE mode (current repo GitHub issue)"
|
|
@@ -1065,6 +1083,22 @@ cmd_start() {
|
|
|
1065
1083
|
no_plan=true
|
|
1066
1084
|
shift
|
|
1067
1085
|
;;
|
|
1086
|
+
--brief)
|
|
1087
|
+
# R7: explicit one-line brief (escape hatch for single-word
|
|
1088
|
+
# briefs that detect_arg_type would otherwise treat as a PRD
|
|
1089
|
+
# path). Forces the zero-config first-run brief sub-path.
|
|
1090
|
+
if [[ -n "${2:-}" ]] && [[ "${2:-}" != --* ]]; then
|
|
1091
|
+
brief_text="$2"
|
|
1092
|
+
shift 2
|
|
1093
|
+
else
|
|
1094
|
+
echo -e "${RED}--brief requires a one-line description (e.g., --brief \"build a todo app\")${NC}"
|
|
1095
|
+
exit 1
|
|
1096
|
+
fi
|
|
1097
|
+
;;
|
|
1098
|
+
--brief=*)
|
|
1099
|
+
brief_text="${1#*=}"
|
|
1100
|
+
shift
|
|
1101
|
+
;;
|
|
1068
1102
|
--budget)
|
|
1069
1103
|
if [[ -n "${2:-}" ]]; then
|
|
1070
1104
|
if ! echo "$2" | grep -qE '^[0-9]+(\.[0-9]+)?$'; then
|
|
@@ -1184,12 +1218,21 @@ cmd_start() {
|
|
|
1184
1218
|
done
|
|
1185
1219
|
|
|
1186
1220
|
# v6.84.0: Unified dispatch based on explicit flags or auto-detection
|
|
1187
|
-
# Precedence: --issue > --prd > positional auto-detect >
|
|
1221
|
+
# Precedence: --brief > --issue > --prd > positional auto-detect >
|
|
1222
|
+
# LOKI_PRD_FILE env
|
|
1188
1223
|
local detected_type=""
|
|
1189
|
-
if [ -n "$
|
|
1224
|
+
if [ -n "$brief_text" ]; then
|
|
1225
|
+
# R7: explicit --brief always forces the zero-config brief sub-path.
|
|
1226
|
+
detected_type="brief"
|
|
1227
|
+
elif [ -n "$explicit_mode" ]; then
|
|
1190
1228
|
detected_type="$explicit_mode"
|
|
1191
1229
|
elif [ -n "$positional_arg" ]; then
|
|
1192
1230
|
detected_type=$(detect_arg_type "$positional_arg")
|
|
1231
|
+
# R7: a positional one-line brief (whitespace, not a file/issue) maps to
|
|
1232
|
+
# the brief sub-path; capture the text from the positional arg.
|
|
1233
|
+
if [ "$detected_type" = "brief" ]; then
|
|
1234
|
+
brief_text="$positional_arg"
|
|
1235
|
+
fi
|
|
1193
1236
|
elif [ -n "${LOKI_PRD_FILE:-}" ]; then
|
|
1194
1237
|
detected_type="prd"
|
|
1195
1238
|
prd_file="$LOKI_PRD_FILE"
|
|
@@ -1229,6 +1272,51 @@ cmd_start() {
|
|
|
1229
1272
|
return $?
|
|
1230
1273
|
fi
|
|
1231
1274
|
|
|
1275
|
+
# R7 (zero-config first run): BRIEF mode. The user gave a one-line brief
|
|
1276
|
+
# (`loki start "build a todo app"` or `loki start --brief "..."`). Synthesize
|
|
1277
|
+
# a forward-looking PRD, switch to the lightweight TTFV profile (honest fast:
|
|
1278
|
+
# capped iterations, council off, simple tier, heavy phases off), and mark
|
|
1279
|
+
# LOKI_TTFV=1 so run.sh prints the "what next / go deeper" framing at the
|
|
1280
|
+
# end. Then fall through to the normal flow, which appends prd_file to args
|
|
1281
|
+
# and execs run.sh. This is additive -- nothing here changes the PRD/issue
|
|
1282
|
+
# paths above.
|
|
1283
|
+
if [ "$detected_type" = "brief" ]; then
|
|
1284
|
+
local version
|
|
1285
|
+
version=$(get_version)
|
|
1286
|
+
local _ttfv_max_iter="${LOKI_MAX_ITERATIONS:-3}"
|
|
1287
|
+
mkdir -p "$LOKI_DIR" 2>/dev/null || true
|
|
1288
|
+
local brief_prd="$LOKI_DIR/brief-prd-$$.md"
|
|
1289
|
+
synthesize_brief_prd "$brief_prd" "$brief_text"
|
|
1290
|
+
prd_file="$brief_prd"
|
|
1291
|
+
|
|
1292
|
+
# Apply the shared lightweight profile and flag the TTFV first-run path.
|
|
1293
|
+
# The signal value ("brief") drives the end-of-run wording in run.sh so
|
|
1294
|
+
# the message matches what actually ran (lightweight, council off).
|
|
1295
|
+
set_ttfv_lightweight_profile "$_ttfv_max_iter"
|
|
1296
|
+
export LOKI_TTFV=brief
|
|
1297
|
+
# Skip the heavy auto-plan analysis -- the brief path is the fast path,
|
|
1298
|
+
# and we already printed the upfront framing below.
|
|
1299
|
+
no_plan=true
|
|
1300
|
+
|
|
1301
|
+
echo -e "${BOLD}Loki Mode v$version - Zero-config first run${NC}"
|
|
1302
|
+
echo ""
|
|
1303
|
+
echo -e "${CYAN}Brief:${NC} $brief_text"
|
|
1304
|
+
echo -e "${DIM}Fast first pass: $_ttfv_max_iter iterations max, council off, simple tier.${NC}"
|
|
1305
|
+
echo -e "${DIM}You will get a runnable artifact and a proof-of-run quickly.${NC}"
|
|
1306
|
+
echo -e "${DIM}Go deeper later with: loki start (full RARV-C depth).${NC}"
|
|
1307
|
+
echo -e "${GREEN}Brief PRD written${NC} to $brief_prd"
|
|
1308
|
+
echo ""
|
|
1309
|
+
fi
|
|
1310
|
+
|
|
1311
|
+
# R7: existing-repo no-arg path also gets the TTFV "what next" framing at the
|
|
1312
|
+
# end of the run. Execution here is UNCHANGED (full-depth no-PRD codebase
|
|
1313
|
+
# analysis + generated-PRD-reuse); we only add accurate end-of-run framing.
|
|
1314
|
+
# The signal value ("repo") drives the full-depth wording in run.sh so the
|
|
1315
|
+
# message does not falsely claim a lightweight pass.
|
|
1316
|
+
if [ "$detected_type" = "empty" ] && [ -z "${LOKI_TTFV:-}" ]; then
|
|
1317
|
+
export LOKI_TTFV=repo
|
|
1318
|
+
fi
|
|
1319
|
+
|
|
1232
1320
|
# PRD mode: positional arg is the PRD file (if not already set via --prd)
|
|
1233
1321
|
if [ "$detected_type" = "prd" ] && [ -z "$prd_file" ]; then
|
|
1234
1322
|
prd_file="$positional_arg"
|
|
@@ -8847,6 +8935,65 @@ cmd_demo() {
|
|
|
8847
8935
|
return $start_exit
|
|
8848
8936
|
}
|
|
8849
8937
|
|
|
8938
|
+
# R7 (zero-config first run): set the lightweight TTFV execution profile.
|
|
8939
|
+
# Shared by `cmd_quick` and the `loki start "<brief>"` brief sub-path so the
|
|
8940
|
+
# fast first pass means the same thing in both: capped iterations, completion
|
|
8941
|
+
# council off, simple complexity tier, heavy phases off. This is HONEST fast --
|
|
8942
|
+
# it genuinely shortens the path to first visible value; it does not fake
|
|
8943
|
+
# progress. Depth is opt-in via a plain `loki start` re-run.
|
|
8944
|
+
# Usage: set_ttfv_lightweight_profile [max_iter]
|
|
8945
|
+
set_ttfv_lightweight_profile() {
|
|
8946
|
+
local max_iter="${1:-${LOKI_MAX_ITERATIONS:-3}}"
|
|
8947
|
+
export LOKI_MAX_ITERATIONS="$max_iter"
|
|
8948
|
+
export LOKI_COMPLEXITY=simple
|
|
8949
|
+
export LOKI_COUNCIL_ENABLED=false
|
|
8950
|
+
export LOKI_PHASE_CODE_REVIEW=false
|
|
8951
|
+
export LOKI_PHASE_PERFORMANCE=false
|
|
8952
|
+
export LOKI_PHASE_ACCESSIBILITY=false
|
|
8953
|
+
export LOKI_PHASE_REGRESSION=false
|
|
8954
|
+
export LOKI_PHASE_UAT=false
|
|
8955
|
+
export LOKI_PHASE_WEB_RESEARCH=false
|
|
8956
|
+
}
|
|
8957
|
+
|
|
8958
|
+
# R7: synthesize a forward-looking PRD from a one-line brief. Writes to a
|
|
8959
|
+
# unique path (caller-provided) and echoes nothing -- the file is the output.
|
|
8960
|
+
# Kept DISTINCT from .loki/generated-prd.md (codebase-analysis artifact) so it
|
|
8961
|
+
# never pollutes the v7.8.1 generated-PRD-reuse signature logic. The brief text
|
|
8962
|
+
# is the project intent; the rest is a minimal scaffold the agent fills in.
|
|
8963
|
+
# Usage: synthesize_brief_prd <output_file> <brief_text>
|
|
8964
|
+
synthesize_brief_prd() {
|
|
8965
|
+
local out_file="$1"
|
|
8966
|
+
local brief_text="$2"
|
|
8967
|
+
local out_dir
|
|
8968
|
+
out_dir="$(dirname "$out_file")"
|
|
8969
|
+
mkdir -p "$out_dir" 2>/dev/null || true
|
|
8970
|
+
cat > "$out_file" << BRIEFEOF
|
|
8971
|
+
# Project Brief
|
|
8972
|
+
|
|
8973
|
+
## Overview
|
|
8974
|
+
$brief_text
|
|
8975
|
+
|
|
8976
|
+
## Requirements
|
|
8977
|
+
- Build the smallest working version of the above that a user can see and run.
|
|
8978
|
+
- Prefer a runnable artifact (a page, a CLI, an API endpoint) over scaffolding.
|
|
8979
|
+
- Follow conventional structure for the chosen stack; keep dependencies minimal.
|
|
8980
|
+
- Write a short README describing how to run it.
|
|
8981
|
+
|
|
8982
|
+
## Success Criteria
|
|
8983
|
+
- A user can run the result and observe the core behavior described above.
|
|
8984
|
+
- No errors on a clean start; the happy path works end to end.
|
|
8985
|
+
|
|
8986
|
+
## Constraints
|
|
8987
|
+
- This is a fast first pass (zero-config first run). Keep scope tight.
|
|
8988
|
+
- Do not over-engineer; depth and hardening come on a later full run.
|
|
8989
|
+
- No emojis, no em dashes in code, comments, or docs.
|
|
8990
|
+
|
|
8991
|
+
---
|
|
8992
|
+
|
|
8993
|
+
**Mode:** Brief (zero-config first run, lightweight first pass)
|
|
8994
|
+
BRIEFEOF
|
|
8995
|
+
}
|
|
8996
|
+
|
|
8850
8997
|
# Quick mode - lightweight single-task execution
|
|
8851
8998
|
cmd_quick() {
|
|
8852
8999
|
# v7.6.3 B-11 fix: --help previously fell through to provider invocation
|
|
@@ -8934,16 +9081,9 @@ QPRDEOF
|
|
|
8934
9081
|
echo -e "${GREEN}Quick PRD generated${NC} at $quick_prd"
|
|
8935
9082
|
echo ""
|
|
8936
9083
|
|
|
8937
|
-
# Set lightweight execution environment
|
|
8938
|
-
|
|
8939
|
-
|
|
8940
|
-
export LOKI_COUNCIL_ENABLED=false
|
|
8941
|
-
export LOKI_PHASE_CODE_REVIEW=false
|
|
8942
|
-
export LOKI_PHASE_PERFORMANCE=false
|
|
8943
|
-
export LOKI_PHASE_ACCESSIBILITY=false
|
|
8944
|
-
export LOKI_PHASE_REGRESSION=false
|
|
8945
|
-
export LOKI_PHASE_UAT=false
|
|
8946
|
-
export LOKI_PHASE_WEB_RESEARCH=false
|
|
9084
|
+
# Set lightweight execution environment (shared TTFV profile -- see
|
|
9085
|
+
# set_ttfv_lightweight_profile; same profile the R7 brief sub-path uses).
|
|
9086
|
+
set_ttfv_lightweight_profile "$max_iter"
|
|
8947
9087
|
|
|
8948
9088
|
# Record start for efficiency tracking
|
|
8949
9089
|
record_session_start
|
|
@@ -13065,6 +13205,9 @@ main() {
|
|
|
13065
13205
|
docs)
|
|
13066
13206
|
cmd_docs "$@"
|
|
13067
13207
|
;;
|
|
13208
|
+
wiki)
|
|
13209
|
+
cmd_wiki "$@"
|
|
13210
|
+
;;
|
|
13068
13211
|
magic)
|
|
13069
13212
|
cmd_magic "$@"
|
|
13070
13213
|
;;
|
|
@@ -22493,6 +22636,124 @@ run_debate(
|
|
|
22493
22636
|
log_info "Debate complete"
|
|
22494
22637
|
}
|
|
22495
22638
|
|
|
22639
|
+
# =============================================================================
|
|
22640
|
+
# loki wiki -- auto-generated, cited per-project codebase wiki + Q&A (R5).
|
|
22641
|
+
#
|
|
22642
|
+
# Loki's answer to Devin DeepWiki: a persistent, queryable wiki built from the
|
|
22643
|
+
# codebase, where every section cites the real source files it came from, plus
|
|
22644
|
+
# a grounded `ask` that returns cited answers (file:line). Heavy work lives in
|
|
22645
|
+
# the Python core (autonomy/lib/wiki-generator.py, wiki-ask.py, wiki_index.py);
|
|
22646
|
+
# this is a thin dispatcher, mirroring how cmd_proof delegates to the proof
|
|
22647
|
+
# generator. Generation is incremental (skips when the codebase is unchanged).
|
|
22648
|
+
# =============================================================================
|
|
22649
|
+
cmd_wiki() {
|
|
22650
|
+
local subcmd="${1:-}"
|
|
22651
|
+
shift 2>/dev/null || true
|
|
22652
|
+
|
|
22653
|
+
local lib_dir="${_LOKI_SCRIPT_DIR}/lib"
|
|
22654
|
+
|
|
22655
|
+
case "$subcmd" in
|
|
22656
|
+
generate) _wiki_generate "$lib_dir" "$@" ;;
|
|
22657
|
+
show) _wiki_show "$@" ;;
|
|
22658
|
+
ask) _wiki_ask "$lib_dir" "$@" ;;
|
|
22659
|
+
--help|-h|help|"")
|
|
22660
|
+
echo -e "${BOLD}loki wiki${NC} - Auto-generated, cited codebase wiki + Q&A"
|
|
22661
|
+
echo ""
|
|
22662
|
+
echo "Usage: loki wiki <command> [options]"
|
|
22663
|
+
echo ""
|
|
22664
|
+
echo "Commands:"
|
|
22665
|
+
echo " generate [path] [--force] Build/refresh the cited wiki in .loki/wiki/"
|
|
22666
|
+
echo " show [section] Print the wiki (or one section: architecture|modules|data-flow)"
|
|
22667
|
+
echo " ask \"<question>\" Cited answer grounded in the codebase (file:line)"
|
|
22668
|
+
echo ""
|
|
22669
|
+
echo "Each wiki section cites the real source files it was built from."
|
|
22670
|
+
echo "Generation is incremental: it skips when the codebase is unchanged."
|
|
22671
|
+
echo ""
|
|
22672
|
+
echo "Examples:"
|
|
22673
|
+
echo " loki wiki generate # build wiki for current project"
|
|
22674
|
+
echo " loki wiki show architecture # show one section"
|
|
22675
|
+
echo " loki wiki ask \"how does the cli dispatch commands\""
|
|
22676
|
+
return 0
|
|
22677
|
+
;;
|
|
22678
|
+
*)
|
|
22679
|
+
log_error "Unknown wiki command: $subcmd"
|
|
22680
|
+
echo "Run 'loki wiki --help' for usage."
|
|
22681
|
+
return 1
|
|
22682
|
+
;;
|
|
22683
|
+
esac
|
|
22684
|
+
}
|
|
22685
|
+
|
|
22686
|
+
_wiki_generate() {
|
|
22687
|
+
local lib_dir="$1"; shift
|
|
22688
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
22689
|
+
log_error "python3 is required for 'loki wiki generate'"
|
|
22690
|
+
return 1
|
|
22691
|
+
fi
|
|
22692
|
+
python3 "$lib_dir/wiki-generator.py" "$@"
|
|
22693
|
+
}
|
|
22694
|
+
|
|
22695
|
+
_wiki_show() {
|
|
22696
|
+
local section=""
|
|
22697
|
+
local target="."
|
|
22698
|
+
while [[ $# -gt 0 ]]; do
|
|
22699
|
+
case "$1" in
|
|
22700
|
+
--help|-h)
|
|
22701
|
+
echo "Usage: loki wiki show [section]"
|
|
22702
|
+
echo "Sections: architecture, modules, data-flow"
|
|
22703
|
+
return 0
|
|
22704
|
+
;;
|
|
22705
|
+
-*) log_error "Unknown option: $1"; return 1 ;;
|
|
22706
|
+
*) section="$1"; shift ;;
|
|
22707
|
+
esac
|
|
22708
|
+
done
|
|
22709
|
+
local wiki_dir="$target/.loki/wiki"
|
|
22710
|
+
if [ ! -d "$wiki_dir" ]; then
|
|
22711
|
+
log_error "No wiki found. Run 'loki wiki generate' first."
|
|
22712
|
+
return 1
|
|
22713
|
+
fi
|
|
22714
|
+
if [ -n "$section" ]; then
|
|
22715
|
+
local f="$wiki_dir/${section}.md"
|
|
22716
|
+
if [ ! -f "$f" ]; then
|
|
22717
|
+
log_error "No such section: $section (try: architecture, modules, data-flow)"
|
|
22718
|
+
return 1
|
|
22719
|
+
fi
|
|
22720
|
+
cat "$f"
|
|
22721
|
+
else
|
|
22722
|
+
if [ -f "$wiki_dir/index.md" ]; then
|
|
22723
|
+
cat "$wiki_dir/index.md"
|
|
22724
|
+
else
|
|
22725
|
+
log_error "Wiki index not found. Run 'loki wiki generate'."
|
|
22726
|
+
return 1
|
|
22727
|
+
fi
|
|
22728
|
+
fi
|
|
22729
|
+
}
|
|
22730
|
+
|
|
22731
|
+
_wiki_ask() {
|
|
22732
|
+
local lib_dir="$1"; shift
|
|
22733
|
+
local question=""
|
|
22734
|
+
local extra=()
|
|
22735
|
+
while [[ $# -gt 0 ]]; do
|
|
22736
|
+
case "$1" in
|
|
22737
|
+
--help|-h)
|
|
22738
|
+
echo "Usage: loki wiki ask \"<question>\" [--json] [--k N]"
|
|
22739
|
+
return 0
|
|
22740
|
+
;;
|
|
22741
|
+
--json|--quiet) extra+=("$1"); shift ;;
|
|
22742
|
+
--k) extra+=("--k" "${2:-6}"); shift 2 ;;
|
|
22743
|
+
*) if [ -z "$question" ]; then question="$1"; else question="$question $1"; fi; shift ;;
|
|
22744
|
+
esac
|
|
22745
|
+
done
|
|
22746
|
+
if [ -z "$question" ]; then
|
|
22747
|
+
log_error "Provide a question: loki wiki ask \"how does X work\""
|
|
22748
|
+
return 1
|
|
22749
|
+
fi
|
|
22750
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
22751
|
+
log_error "python3 is required for 'loki wiki ask'"
|
|
22752
|
+
return 1
|
|
22753
|
+
fi
|
|
22754
|
+
python3 "$lib_dir/wiki-ask.py" --question "$question" "${extra[@]}"
|
|
22755
|
+
}
|
|
22756
|
+
|
|
22496
22757
|
cmd_magic() {
|
|
22497
22758
|
local subcmd="${1:-help}"
|
|
22498
22759
|
shift 2>/dev/null || true
|
package/autonomy/run.sh
CHANGED
|
@@ -4121,6 +4121,69 @@ generate_proof_of_run() {
|
|
|
4121
4121
|
return 0
|
|
4122
4122
|
}
|
|
4123
4123
|
|
|
4124
|
+
# print_ttfv_next_steps: R7 zero-config first-run "what next / go deeper"
|
|
4125
|
+
# message. The wording MUST match what actually ran, so it branches on the mode:
|
|
4126
|
+
# - brief: a one-line brief ran on the lightweight profile (council off,
|
|
4127
|
+
# simple tier, capped iterations). Proof contains diffs, cost, time
|
|
4128
|
+
# (council verdicts are absent because the council was disabled).
|
|
4129
|
+
# - repo: a no-arg in-repo run analyzed the codebase and ran at full depth
|
|
4130
|
+
# (council on). Proof contains diffs, cost, time, and council
|
|
4131
|
+
# verdicts.
|
|
4132
|
+
# This function only prints; the caller owns the TTY gate. Never fails the run.
|
|
4133
|
+
# Usage: print_ttfv_next_steps <mode> <result>
|
|
4134
|
+
print_ttfv_next_steps() {
|
|
4135
|
+
local mode="${1:-}"
|
|
4136
|
+
local result="${2:-0}"
|
|
4137
|
+
local loki_dir="${TARGET_DIR:-.}/.loki"
|
|
4138
|
+
local proofs_dir="$loki_dir/proofs"
|
|
4139
|
+
|
|
4140
|
+
echo ""
|
|
4141
|
+
echo "============================================================"
|
|
4142
|
+
if [ "$result" = "0" ]; then
|
|
4143
|
+
echo " First pass complete. Here is what you have:"
|
|
4144
|
+
else
|
|
4145
|
+
echo " First pass ended early. Here is what was produced:"
|
|
4146
|
+
fi
|
|
4147
|
+
echo "============================================================"
|
|
4148
|
+
echo ""
|
|
4149
|
+
echo " What I did:"
|
|
4150
|
+
if [ "$mode" = "brief" ]; then
|
|
4151
|
+
echo " - Worked from your one-line brief on a fast, lightweight first"
|
|
4152
|
+
echo " pass (council off, simple tier, capped iterations)."
|
|
4153
|
+
echo " - Generated a proof-of-run (diffs, cost, time)."
|
|
4154
|
+
else
|
|
4155
|
+
echo " - Analyzed your codebase and generated a PRD, then ran a full"
|
|
4156
|
+
echo " first pass (council on, full RARV-C depth)."
|
|
4157
|
+
echo " - Generated a proof-of-run (diffs, cost, time, council verdicts)."
|
|
4158
|
+
fi
|
|
4159
|
+
echo ""
|
|
4160
|
+
echo " See the visible artifact (proof-of-run):"
|
|
4161
|
+
if [ -d "$proofs_dir" ]; then
|
|
4162
|
+
local latest
|
|
4163
|
+
latest=$(ls -1t "$proofs_dir" 2>/dev/null | head -1)
|
|
4164
|
+
if [ -n "$latest" ]; then
|
|
4165
|
+
echo " loki proof open $latest"
|
|
4166
|
+
echo " (or open $proofs_dir/$latest/index.html)"
|
|
4167
|
+
else
|
|
4168
|
+
echo " loki proof list"
|
|
4169
|
+
fi
|
|
4170
|
+
else
|
|
4171
|
+
echo " loki proof list"
|
|
4172
|
+
fi
|
|
4173
|
+
echo ""
|
|
4174
|
+
if [ "$mode" = "brief" ]; then
|
|
4175
|
+
echo " Go deeper (full RARV-C depth, council-gated):"
|
|
4176
|
+
echo " loki start # continue / harden this project"
|
|
4177
|
+
echo " loki start ./prd.md # build from a full PRD"
|
|
4178
|
+
else
|
|
4179
|
+
echo " Next steps:"
|
|
4180
|
+
echo " loki start ./prd.md # build from a full PRD"
|
|
4181
|
+
echo " loki start \"<one line>\" # fast first pass from a brief"
|
|
4182
|
+
fi
|
|
4183
|
+
echo ""
|
|
4184
|
+
return 0
|
|
4185
|
+
}
|
|
4186
|
+
|
|
4124
4187
|
track_iteration_complete() {
|
|
4125
4188
|
local iteration="$1"
|
|
4126
4189
|
local exit_code="${2:-0}"
|
|
@@ -13335,6 +13398,15 @@ main() {
|
|
|
13335
13398
|
generate_proof_of_run "$result" || true
|
|
13336
13399
|
fi
|
|
13337
13400
|
|
|
13401
|
+
# R7 (zero-config first run): "what next / go deeper" framing. Only when the
|
|
13402
|
+
# CLI flagged this as a TTFV first run and stdout is a TTY, so it stays
|
|
13403
|
+
# silent in CI / pipes and never fires for normal PRD runs. The wording
|
|
13404
|
+
# branches on the mode (brief = lightweight first pass; repo = full-depth
|
|
13405
|
+
# codebase analysis) so the message always matches what actually ran.
|
|
13406
|
+
if [ -n "${LOKI_TTFV:-}" ] && [ -t 1 ]; then
|
|
13407
|
+
print_ttfv_next_steps "${LOKI_TTFV}" "$result" || true
|
|
13408
|
+
fi
|
|
13409
|
+
|
|
13338
13410
|
# Create PR from agent branch if branch protection was enabled
|
|
13339
13411
|
create_session_pr
|
|
13340
13412
|
audit_agent_action "session_stop" "Session ended" "result=$result,iterations=$ITERATION_COUNT"
|
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).
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -7507,6 +7507,114 @@ async def get_proof_html(run_id: str):
|
|
|
7507
7507
|
return FileResponse(str(index_html), media_type="text/html")
|
|
7508
7508
|
|
|
7509
7509
|
|
|
7510
|
+
# ---------------------------------------------------------------------------
|
|
7511
|
+
# R5: Auto-wiki + cited codebase Q&A (Loki's DeepWiki).
|
|
7512
|
+
#
|
|
7513
|
+
# Surfaces the per-project wiki generated by autonomy/lib/wiki-generator.py
|
|
7514
|
+
# (stored under <project>/.loki/wiki/) and the grounded `ask` flow
|
|
7515
|
+
# (autonomy/lib/wiki-ask.py). Citations are file:line and always point at real
|
|
7516
|
+
# code -- the generator/ask scripts validate every citation against the
|
|
7517
|
+
# filesystem before emitting it, so the dashboard never shows a fabricated one.
|
|
7518
|
+
#
|
|
7519
|
+
# The section param is traversal-safe, mirroring _safe_proof_run_dir: only the
|
|
7520
|
+
# known section ids are accepted, so no arbitrary path can be read.
|
|
7521
|
+
# ---------------------------------------------------------------------------
|
|
7522
|
+
_WIKI_SECTIONS = {"architecture", "modules", "data-flow"}
|
|
7523
|
+
|
|
7524
|
+
|
|
7525
|
+
def _wiki_dir() -> _Path:
|
|
7526
|
+
return _get_loki_dir() / "wiki"
|
|
7527
|
+
|
|
7528
|
+
|
|
7529
|
+
def _project_root() -> _Path:
|
|
7530
|
+
"""Resolve the active project root (.loki's parent)."""
|
|
7531
|
+
return _get_loki_dir().parent
|
|
7532
|
+
|
|
7533
|
+
|
|
7534
|
+
@app.get("/api/wiki", dependencies=[Depends(auth.require_scope("read"))])
|
|
7535
|
+
async def get_wiki():
|
|
7536
|
+
"""Return the wiki manifest + section list for the active project."""
|
|
7537
|
+
wiki_dir = _wiki_dir()
|
|
7538
|
+
wiki_json = wiki_dir / "wiki.json"
|
|
7539
|
+
if not wiki_json.is_file():
|
|
7540
|
+
return {"generated": False, "sections": [],
|
|
7541
|
+
"message": "No wiki generated. Run 'loki wiki generate'."}
|
|
7542
|
+
data = _safe_json_read(wiki_json, default=None)
|
|
7543
|
+
if not isinstance(data, dict):
|
|
7544
|
+
raise HTTPException(status_code=500, detail="wiki.json unreadable")
|
|
7545
|
+
manifest = _safe_json_read(wiki_dir / "wiki-manifest.json", default={}) or {}
|
|
7546
|
+
sections = [
|
|
7547
|
+
{"id": s.get("id"), "title": s.get("title"),
|
|
7548
|
+
"citation_count": len(s.get("citations") or [])}
|
|
7549
|
+
for s in data.get("sections", [])
|
|
7550
|
+
if isinstance(s, dict)
|
|
7551
|
+
]
|
|
7552
|
+
return {
|
|
7553
|
+
"generated": True,
|
|
7554
|
+
"project": data.get("project"),
|
|
7555
|
+
"generated_at": data.get("generated_at"),
|
|
7556
|
+
"file_count": data.get("file_count"),
|
|
7557
|
+
"signature": manifest.get("signature"),
|
|
7558
|
+
"sections": sections,
|
|
7559
|
+
}
|
|
7560
|
+
|
|
7561
|
+
|
|
7562
|
+
@app.get("/api/wiki/{section}", dependencies=[Depends(auth.require_scope("read"))])
|
|
7563
|
+
async def get_wiki_section(section: str):
|
|
7564
|
+
"""Return one wiki section (body + validated file:line citations)."""
|
|
7565
|
+
if section not in _WIKI_SECTIONS:
|
|
7566
|
+
raise HTTPException(status_code=400, detail=f"unknown section: {section}")
|
|
7567
|
+
wiki_json = _wiki_dir() / "wiki.json"
|
|
7568
|
+
if not wiki_json.is_file():
|
|
7569
|
+
raise HTTPException(status_code=404, detail="wiki not generated")
|
|
7570
|
+
data = _safe_json_read(wiki_json, default=None)
|
|
7571
|
+
if not isinstance(data, dict):
|
|
7572
|
+
raise HTTPException(status_code=500, detail="wiki.json unreadable")
|
|
7573
|
+
for s in data.get("sections", []):
|
|
7574
|
+
if isinstance(s, dict) and s.get("id") == section:
|
|
7575
|
+
return JSONResponse(content=s)
|
|
7576
|
+
raise HTTPException(status_code=404, detail=f"section not found: {section}")
|
|
7577
|
+
|
|
7578
|
+
|
|
7579
|
+
class WikiAskRequest(BaseModel):
|
|
7580
|
+
question: str = Field(..., min_length=1, max_length=2000)
|
|
7581
|
+
k: int = Field(default=6, ge=1, le=20)
|
|
7582
|
+
|
|
7583
|
+
|
|
7584
|
+
@app.post("/api/wiki/ask", dependencies=[Depends(auth.require_scope("read"))])
|
|
7585
|
+
async def post_wiki_ask(req: WikiAskRequest):
|
|
7586
|
+
"""Grounded, cited codebase Q&A.
|
|
7587
|
+
|
|
7588
|
+
Shells out to autonomy/lib/wiki-ask.py (the single source of truth for the
|
|
7589
|
+
grounding + citation-validation contract) and returns its JSON. Every
|
|
7590
|
+
citation in the response resolves to a real file:line.
|
|
7591
|
+
"""
|
|
7592
|
+
project_root = _project_root()
|
|
7593
|
+
repo_root = _Path(__file__).resolve().parent.parent
|
|
7594
|
+
ask_script = repo_root / "autonomy" / "lib" / "wiki-ask.py"
|
|
7595
|
+
if not ask_script.is_file():
|
|
7596
|
+
raise HTTPException(status_code=503, detail="wiki-ask backend missing")
|
|
7597
|
+
try:
|
|
7598
|
+
proc = subprocess.run(
|
|
7599
|
+
["python3", str(ask_script), "--root", str(project_root),
|
|
7600
|
+
"--question", req.question, "--k", str(req.k), "--json"],
|
|
7601
|
+
capture_output=True, text=True, timeout=180,
|
|
7602
|
+
cwd=str(project_root),
|
|
7603
|
+
)
|
|
7604
|
+
except (OSError, subprocess.SubprocessError) as e:
|
|
7605
|
+
raise HTTPException(status_code=503, detail=f"wiki ask failed: {e}")
|
|
7606
|
+
if proc.returncode == 3:
|
|
7607
|
+
return {"question": req.question, "answer": "",
|
|
7608
|
+
"citations": [], "note": "no relevant code found"}
|
|
7609
|
+
if proc.returncode != 0:
|
|
7610
|
+
raise HTTPException(status_code=500,
|
|
7611
|
+
detail=(proc.stderr or "wiki ask error").strip())
|
|
7612
|
+
try:
|
|
7613
|
+
return JSONResponse(content=json.loads(proc.stdout))
|
|
7614
|
+
except json.JSONDecodeError:
|
|
7615
|
+
raise HTTPException(status_code=500, detail="wiki ask returned bad JSON")
|
|
7616
|
+
|
|
7617
|
+
|
|
7510
7618
|
# ---------------------------------------------------------------------------
|
|
7511
7619
|
# SPA catch-all: serve index.html for any path not matched by API routes
|
|
7512
7620
|
# or static asset mounts. This lets the dashboard UI handle client-side routing.
|