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/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 > LOKI_PRD_FILE env
1221
+ # Precedence: --brief > --issue > --prd > positional auto-detect >
1222
+ # LOKI_PRD_FILE env
1188
1223
  local detected_type=""
1189
- if [ -n "$explicit_mode" ]; then
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
- export LOKI_MAX_ITERATIONS="$max_iter"
8939
- export LOKI_COMPLEXITY=simple
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).
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.11.0"
10
+ __version__ = "7.13.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -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.