loki-mode 11.17.1 → 11.17.5
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 +3 -3
- package/VERSION +1 -1
- package/autonomy/grill.sh +24 -2
- package/autonomy/hooks/precompact-flush.sh +51 -0
- package/autonomy/hooks/validate-bash.sh +895 -5
- package/autonomy/lib/chat.sh +8 -3
- package/autonomy/lib/claude-flags.sh +143 -6
- package/autonomy/lib/claude_permissions.py +24 -4
- package/autonomy/lib/decision_record.py +92 -3
- package/autonomy/lib/efficiency_cost.py +102 -0
- package/autonomy/lib/modernize/py_capture.py +62 -3
- package/autonomy/lib/prd-enrich.sh +28 -2
- package/autonomy/lib/voter-agents.sh +61 -6
- package/autonomy/lib/wiki-generator.py +58 -10
- package/autonomy/lib/wiki_llm.py +41 -2
- package/autonomy/loki +391 -28
- package/autonomy/quickstart.sh +1 -1
- package/autonomy/run.sh +201 -10
- package/autonomy/trigger-server.py +70 -2
- package/completions/_loki +3 -0
- package/completions/loki.bash +2 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/audit.py +204 -17
- package/docs/CLI-REFERENCE.md +4 -1
- package/docs/EVALUATING.md +2 -2
- package/docs/INSTALLATION.md +1 -1
- package/docs/environment-variables.md +1 -0
- package/docs/trackers.md +6 -0
- package/docs/v10/BOARD.md +2 -1
- package/docs/v10/CLI-MODERN.md +8 -5
- package/docs/v10/DECISIONS.md +16 -1
- package/docs/v10/ENGINE.md +1 -0
- package/docs/v10/FAILURE-CLASSES.md +60 -1
- package/docs/v10/FOUNDER-QUEUE.md +1 -0
- package/docs/v10/GUIDE-MODERNIZE.md +9 -4
- package/docs/v10/PLAN-V12.md +896 -0
- package/docs/v10/PROGRESS.md +7 -0
- package/docs/v11/COST-HALF.md +3 -1
- package/docs/visual-evidence.md +2 -0
- package/loki-ts/dist/loki.js +1104 -1057
- package/mcp/__init__.py +1 -1
- package/memory/retrieval.py +15 -3
- package/memory/schemas.py +43 -1
- package/memory/storage.py +11 -0
- package/package.json +2 -2
- package/packages/control-plane/dist/server.js +989 -693
- package/packages/control-plane/src/server/legacy/routes-control.ts +5 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/providers/claude.sh +1 -0
- package/providers/loader.sh +26 -0
- package/providers/model_routes.sh +31 -0
- package/src/policies/claude_permissions.js +20 -2
- package/web-app/dist/assets/{AdminPage-OVzN6eLm.js → AdminPage-C0v2p3jN.js} +1 -1
- package/web-app/dist/assets/{Avatar-hCCrxrCZ.js → Avatar-Cgc0QepK.js} +1 -1
- package/web-app/dist/assets/{Badge-CDcpkhbJ.js → Badge-1X4wQ0sU.js} +1 -1
- package/web-app/dist/assets/{Button-8mwWlhEu.js → Button-CG79n6-I.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-D8cf3u22.js → CockpitPage-rSw2BTKk.js} +1 -1
- package/web-app/dist/assets/{ComparePage-CIgkbDRY.js → ComparePage-B9EVp__S.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-99kJH9g2.js → ErrorBoundary-DLQKY8LF.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-C9fj32eS.js → EvidenceReceiptPanel-DE0L1L2Q.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-De43YQ_C.js → GitHubIssuesPanel-DlKtYrtk.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-7g7ZTnkx.js → GitHubPRsPanel-C5c5tY3x.js} +1 -1
- package/web-app/dist/assets/{HomePage-CcNA24ny.js → HomePage-ob1natMp.js} +2 -2
- package/web-app/dist/assets/{LoginPage-BYjlb-IK.js → LoginPage-CCQBgvzB.js} +1 -1
- package/web-app/dist/assets/{MagicPage-C0Dwt9Na.js → MagicPage-CKSuC0Pp.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-jDGmdcgg.js → MetricsPage-nq_ygAuG.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-C_ZO8tLE.js → NotFoundPage-B6gVLTAn.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-B5wnurfQ.js → ProjectPage-pUnvGriO.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage--EyKt5-D.js → ProjectsPage-DMFPGLkE.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-cB0lnfti.js → SettingsPage-DAB_cp87.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-Dl16FDkK.js → ShowcasePage-DTsx_XxI.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-BmAXd4V2.js → SystemSettingsPage-C2UUqWhW.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-BFkWVOZe.js → TeamsPage-C4Sws9U7.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-Bf2kmEUM.js → TemplatesPage-B1-79O4n.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-BItv9Wa6.js → TerminalOutput-CDSVt5TB.js} +1 -1
- package/web-app/dist/assets/{activity-Df2Awg6I.js → activity-CBEULbio.js} +1 -1
- package/web-app/dist/assets/{bell-D-1ztTsJ.js → bell-Dp-j77Qo.js} +1 -1
- package/web-app/dist/assets/{bot-A4qgZsKi.js → bot-Dh1FlTEJ.js} +1 -1
- package/web-app/dist/assets/{check-BPfFpF0c.js → check-C_7N7W4q.js} +1 -1
- package/web-app/dist/assets/{chevron-left-CfSJvmDi.js → chevron-left-DtZJiu3T.js} +1 -1
- package/web-app/dist/assets/{circle-alert-wx2d1dzb.js → circle-alert-CJK4sNof.js} +1 -1
- package/web-app/dist/assets/{clock-pQMai6l-.js → clock--0bfAUkP.js} +1 -1
- package/web-app/dist/assets/{cloud-DWL470r6.js → cloud-CRa9nwiN.js} +1 -1
- package/web-app/dist/assets/{code-xml-BaEQpQLZ.js → code-xml-4gRrVGxx.js} +1 -1
- package/web-app/dist/assets/{database-h-N7WkQN.js → database-C-A2USGl.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-B5oYiD27.js → dollar-sign-BshJt4ML.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-fbTtTy0o.js → file-code-corner-Bd--i_IM.js} +1 -1
- package/web-app/dist/assets/{file-plus-PaF6HMo8.js → file-plus-YiNg-aWn.js} +1 -1
- package/web-app/dist/assets/{globe-Ct-GlWID.js → globe-CfyHbAbI.js} +1 -1
- package/web-app/dist/assets/{hammer-C1oZFX51.js → hammer-BlGC-yqz.js} +1 -1
- package/web-app/dist/assets/{index-C2egrRhz.js → index-BlCg8mla.js} +3 -3
- package/web-app/dist/assets/{layers-Dt5ZK4v2.js → layers-DLiA0AcW.js} +1 -1
- package/web-app/dist/assets/{loader-circle-DrpMziic.js → loader-circle-CkOfS_Q6.js} +1 -1
- package/web-app/dist/assets/{lock-DOez8MmK.js → lock-C5vRE8HM.js} +1 -1
- package/web-app/dist/assets/{package-ClGIkerr.js → package-BPzp43h4.js} +1 -1
- package/web-app/dist/assets/{plus-C004i9ua.js → plus-C4x6sD5J.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-Ddfk-7Sy.js → refresh-cw-DZn-gB9J.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-Bu1KgDjS.js → rotate-ccw-rH63YhnY.js} +1 -1
- package/web-app/dist/assets/{scroll-text-3q1lW5pm.js → scroll-text-DTvZMQuh.js} +1 -1
- package/web-app/dist/assets/{server-pd6x4HW1.js → server-DmwCdhKT.js} +1 -1
- package/web-app/dist/assets/{shield-alert-B9jGJepT.js → shield-alert-Dn5Rtkhx.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-Cdcc9nQf.js → thumbs-up-D_lHdH-1.js} +1 -1
- package/web-app/dist/assets/{trash-2-foSojXHK.js → trash-2-CVfeP0Fb.js} +1 -1
- package/web-app/dist/assets/{trending-down-CTTiqlJ1.js → trending-down-ouPrjBEm.js} +1 -1
- package/web-app/dist/assets/{trending-up-B_MgmRAx.js → trending-up-7H96_KlR.js} +1 -1
- package/web-app/dist/assets/{upload-g6BfALzG.js → upload-kTTgyfD6.js} +1 -1
- package/web-app/dist/assets/{usePolling-DrHwvzuh.js → usePolling-BFIMPvNM.js} +1 -1
- package/web-app/dist/assets/{user-COp75m7l.js → user-UDo6EQfn.js} +1 -1
- package/web-app/dist/index.html +1 -1
|
@@ -110,6 +110,11 @@ complexity = os.environ.get("_VA_COMPLEXITY", "standard")
|
|
|
110
110
|
_effort_bump = {"low": "medium", "medium": "high", "high": "xhigh"}
|
|
111
111
|
def _effort(base):
|
|
112
112
|
return _effort_bump.get(base, base) if complexity == "complex" else base
|
|
113
|
+
# Cost S7: the three base voters run on haiku for small/standard work; the
|
|
114
|
+
# complex tier keeps the original opus/sonnet roster. haiku takes no effort knob.
|
|
115
|
+
_lean = complexity != "complex"
|
|
116
|
+
def _model(default):
|
|
117
|
+
return "haiku" if _lean else default
|
|
113
118
|
prd_clause = (
|
|
114
119
|
f"PRD at {prd}. Read it for ground-truth requirements."
|
|
115
120
|
if prd else
|
|
@@ -118,8 +123,7 @@ prd_clause = (
|
|
|
118
123
|
agents = {
|
|
119
124
|
"requirements-verifier": {
|
|
120
125
|
"description": "Verifies the iteration delivered the PRD-required behavior.",
|
|
121
|
-
"model": "opus",
|
|
122
|
-
"effort": _effort("high"),
|
|
126
|
+
"model": _model("opus"),
|
|
123
127
|
"tools": ["Read", "Grep", "Bash"],
|
|
124
128
|
"prompt": (
|
|
125
129
|
f"You are the requirements-verifier for iteration {iter_n}. "
|
|
@@ -131,8 +135,7 @@ agents = {
|
|
|
131
135
|
},
|
|
132
136
|
"test-auditor": {
|
|
133
137
|
"description": "Audits test coverage and pass status for the iteration.",
|
|
134
|
-
"model": "sonnet",
|
|
135
|
-
"effort": _effort("high"),
|
|
138
|
+
"model": _model("sonnet"),
|
|
136
139
|
"tools": ["Read", "Grep", "Bash"],
|
|
137
140
|
"prompt": (
|
|
138
141
|
f"You are the test-auditor for iteration {iter_n}. "
|
|
@@ -144,8 +147,7 @@ agents = {
|
|
|
144
147
|
},
|
|
145
148
|
"convergence-voter": {
|
|
146
149
|
"description": "Checks code churn and progress signals to judge convergence.",
|
|
147
|
-
"model": "sonnet",
|
|
148
|
-
"effort": _effort("medium"),
|
|
150
|
+
"model": _model("sonnet"),
|
|
149
151
|
"tools": ["Read", "Grep", "Bash"],
|
|
150
152
|
"prompt": (
|
|
151
153
|
f"You are the convergence-voter for iteration {iter_n} on tier {tier}. "
|
|
@@ -156,6 +158,9 @@ agents = {
|
|
|
156
158
|
),
|
|
157
159
|
},
|
|
158
160
|
}
|
|
161
|
+
if not _lean:
|
|
162
|
+
for _k, _b in (("requirements-verifier", "high"), ("test-auditor", "high"), ("convergence-voter", "medium")):
|
|
163
|
+
agents[_k]["effort"] = _effort(_b)
|
|
159
164
|
print(json.dumps(agents, separators=(",", ":")))
|
|
160
165
|
'
|
|
161
166
|
return 0
|
|
@@ -289,6 +294,13 @@ loki_council_dispatch_agents() {
|
|
|
289
294
|
local prompt
|
|
290
295
|
prompt=$(printf 'Loki council iteration %s. Run each declared agent against the current workspace, return one finding per agent matching the provided JSON Schema. Be terse, be honest.' "$iteration")
|
|
291
296
|
|
|
297
|
+
# Cost S7: hand the council the PRD path so voters stop reporting "No PRD was
|
|
298
|
+
# provided". A missing/unreadable PRD adds nothing; voters then vote
|
|
299
|
+
# CANNOT_VALIDATE, which never approves.
|
|
300
|
+
if [ -n "$prd_path" ] && [ -r "$prd_path" ]; then
|
|
301
|
+
prompt="${prompt} PRD file: ${prd_path} (read it for ground-truth requirements)."
|
|
302
|
+
fi
|
|
303
|
+
|
|
292
304
|
# Shared dispatch state (declared once, before either the SDK or claude arm).
|
|
293
305
|
local response=""
|
|
294
306
|
local rc=0
|
|
@@ -375,11 +387,54 @@ loki_council_dispatch_agents() {
|
|
|
375
387
|
# (which has its own timeout + conservative defaults).
|
|
376
388
|
_LOKI_PERM_ARGV=(--dangerously-skip-permissions)
|
|
377
389
|
! type _loki_claude_permission_argv >/dev/null 2>&1 || _loki_claude_permission_argv
|
|
390
|
+
# Cost S7 lean child: council role flags (no hooks/MCP/tools on the main
|
|
391
|
+
# child) and --output-format json so usage lands in the child ledger. The
|
|
392
|
+
# envelope is unwrapped back to the {findings:[...]} object below.
|
|
393
|
+
local _va_lean=() _va_lf
|
|
394
|
+
if type loki_lean_child_flags >/dev/null 2>&1; then
|
|
395
|
+
while IFS= read -r _va_lf; do
|
|
396
|
+
[ -n "$_va_lf" ] && _va_lean+=("$_va_lf")
|
|
397
|
+
done < <(loki_lean_child_flags council 2>/dev/null)
|
|
398
|
+
fi
|
|
399
|
+
local _va_fmt=()
|
|
400
|
+
if loki_claude_flag_supported "--output-format"; then
|
|
401
|
+
_va_fmt=(--output-format json)
|
|
402
|
+
fi
|
|
378
403
|
response=$(timeout "${LOKI_COUNCIL_REVIEW_TIMEOUT:-600}" \
|
|
379
404
|
env CAVEMAN_DEFAULT_MODE=off claude "${_LOKI_PERM_ARGV[@]}" \
|
|
405
|
+
${_va_lean[@]+"${_va_lean[@]}"} \
|
|
406
|
+
${_va_fmt[@]+"${_va_fmt[@]}"} \
|
|
380
407
|
-p "$prompt" \
|
|
381
408
|
--agents "$agents_json" \
|
|
382
409
|
--json-schema "$schema_content" 2>"$stderr_log") || rc=$?
|
|
410
|
+
if [ "$rc" -eq 0 ] && [ -n "$response" ] && [ "${#_va_fmt[@]}" -gt 0 ]; then
|
|
411
|
+
if type loki_child_ledger_append >/dev/null 2>&1; then
|
|
412
|
+
loki_child_ledger_append council "$response" >/dev/null 2>&1 || true
|
|
413
|
+
fi
|
|
414
|
+
local _va_unwrapped _va_upy
|
|
415
|
+
if _va_upy="$(_loki_snapshot_py_tool)"; then
|
|
416
|
+
# Unwrap the CLI envelope: structured_output, else a JSON string in
|
|
417
|
+
# result. A bare {findings:[...]} object passes through unchanged. Any
|
|
418
|
+
# other shape leaves the response as-is, so the parser fails closed.
|
|
419
|
+
_va_unwrapped=$(printf '%s' "$response" | "$_va_upy" -I -S -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]; import json
|
|
420
|
+
try:
|
|
421
|
+
e = json.load(sys.stdin)
|
|
422
|
+
if isinstance(e, dict) and "findings" in e:
|
|
423
|
+
p = e
|
|
424
|
+
else:
|
|
425
|
+
p = e.get("structured_output") if isinstance(e, dict) else None
|
|
426
|
+
if not isinstance(p, dict):
|
|
427
|
+
r = e.get("result") if isinstance(e, dict) else None
|
|
428
|
+
p = json.loads(r) if isinstance(r, str) else None
|
|
429
|
+
if isinstance(p, dict):
|
|
430
|
+
sys.stdout.write(json.dumps(p))
|
|
431
|
+
else:
|
|
432
|
+
sys.exit(1)
|
|
433
|
+
except Exception:
|
|
434
|
+
sys.exit(1)' 2>/dev/null) || _va_unwrapped=""
|
|
435
|
+
if [ -n "$_va_unwrapped" ]; then response="$_va_unwrapped"; fi
|
|
436
|
+
fi
|
|
437
|
+
fi
|
|
383
438
|
fi # end claude-dispatch guard (SDK path skips it, already has $response)
|
|
384
439
|
|
|
385
440
|
# Shared fail-closed check for BOTH paths: any non-zero exit (claude/SDK
|
|
@@ -260,8 +260,10 @@ def _data_flow_diagram(index, modules, entries):
|
|
|
260
260
|
return "\n".join(lines)
|
|
261
261
|
|
|
262
262
|
|
|
263
|
-
def _llm_prose(section, context, fallback):
|
|
264
|
-
"""
|
|
263
|
+
def _llm_prose(section, context, fallback, prose=None):
|
|
264
|
+
"""Prose for a section: pre-batched text if given, else the LLM, else fallback."""
|
|
265
|
+
if prose is not None:
|
|
266
|
+
return prose if prose.strip() else fallback
|
|
265
267
|
prompt = (
|
|
266
268
|
"You are documenting a software project for a team wiki. Write a clear, "
|
|
267
269
|
"factual " + section + " section in markdown. Be concise (under 250 words). "
|
|
@@ -275,6 +277,48 @@ def _llm_prose(section, context, fallback):
|
|
|
275
277
|
return fallback
|
|
276
278
|
|
|
277
279
|
|
|
280
|
+
_BATCH_KEYS = ("architecture overview", "key modules summary", "data flow")
|
|
281
|
+
_BATCH_MARK = "=== SECTION: "
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _batch_prose(context):
|
|
285
|
+
"""One LLM call for all three sections. Returns {key: prose}; missing keys
|
|
286
|
+
are absent so callers use their deterministic fallback (never a second call).
|
|
287
|
+
Output without any marker is taken as the overview only."""
|
|
288
|
+
prompt = (
|
|
289
|
+
"You are documenting a software project for a team wiki. Write three "
|
|
290
|
+
"clear, factual markdown sections, each under 250 words, in this exact "
|
|
291
|
+
"format. Each section starts on its own line with a marker line, then "
|
|
292
|
+
"prose:\n"
|
|
293
|
+
+ "\n".join(_BATCH_MARK + k + " ===" for k in _BATCH_KEYS)
|
|
294
|
+
+ "\nDo NOT use emojis. Do NOT use em dashes or en dashes. Do NOT "
|
|
295
|
+
"invent file paths. Output ONLY the three marked sections with prose "
|
|
296
|
+
"(no headings, no citations).\n\nPROJECT CONTEXT:\n" + context
|
|
297
|
+
)
|
|
298
|
+
out = invoke_llm(prompt, lean=True)
|
|
299
|
+
if not out or not out.strip():
|
|
300
|
+
return {}
|
|
301
|
+
found = {}
|
|
302
|
+
key = None
|
|
303
|
+
for line in out.splitlines():
|
|
304
|
+
t = line.strip()
|
|
305
|
+
if t.startswith(_BATCH_MARK) and t.endswith("==="):
|
|
306
|
+
name = t[len(_BATCH_MARK):-3].strip()
|
|
307
|
+
key = name if name in _BATCH_KEYS else None
|
|
308
|
+
if key:
|
|
309
|
+
found[key] = []
|
|
310
|
+
elif key:
|
|
311
|
+
found[key].append(line)
|
|
312
|
+
prose = {k: "\n".join(v).strip() for k, v in found.items() if "".join(v).strip()}
|
|
313
|
+
if not prose:
|
|
314
|
+
prose = {_BATCH_KEYS[0]: out.strip()}
|
|
315
|
+
missing = [k for k in _BATCH_KEYS if k not in prose]
|
|
316
|
+
if missing:
|
|
317
|
+
print("warning: wiki batched call missing sections (using fallback): "
|
|
318
|
+
+ ", ".join(missing), file=sys.stderr)
|
|
319
|
+
return prose
|
|
320
|
+
|
|
321
|
+
|
|
278
322
|
def _build_context(root, index, modules, entries):
|
|
279
323
|
name = Path(root).name
|
|
280
324
|
lines = [
|
|
@@ -296,14 +340,15 @@ def _build_context(root, index, modules, entries):
|
|
|
296
340
|
return "\n".join(lines)
|
|
297
341
|
|
|
298
342
|
|
|
299
|
-
def _section_overview(root, index, modules, entries, context):
|
|
343
|
+
def _section_overview(root, index, modules, entries, context, batch=None):
|
|
300
344
|
name = Path(root).name
|
|
301
345
|
fallback = (
|
|
302
346
|
"%s is a software project with %d indexed source files. "
|
|
303
347
|
"Entry points: %s. The largest modules by size are listed below."
|
|
304
348
|
% (name, len(index["files"]), ", ".join(entries) or "not detected")
|
|
305
349
|
)
|
|
306
|
-
prose = _llm_prose("architecture overview", context, fallback
|
|
350
|
+
prose = _llm_prose("architecture overview", context, fallback,
|
|
351
|
+
None if batch is None else batch.get("architecture overview", ""))
|
|
307
352
|
# Code-derived citations: the entry points + top modules (real files).
|
|
308
353
|
citations = []
|
|
309
354
|
for e in entries[:3]:
|
|
@@ -318,7 +363,7 @@ def _section_overview(root, index, modules, entries, context):
|
|
|
318
363
|
"body": prose, "citations": citations, "diagram": diagram}
|
|
319
364
|
|
|
320
365
|
|
|
321
|
-
def _section_modules(root, modules, context):
|
|
366
|
+
def _section_modules(root, modules, context, batch=None):
|
|
322
367
|
fallback_lines = ["The following modules carry most of the codebase:"]
|
|
323
368
|
citations = []
|
|
324
369
|
body_parts = []
|
|
@@ -326,6 +371,7 @@ def _section_modules(root, modules, context):
|
|
|
326
371
|
"key modules summary",
|
|
327
372
|
context,
|
|
328
373
|
"Key modules of this project, with their primary definitions:",
|
|
374
|
+
None if batch is None else batch.get("key modules summary", ""),
|
|
329
375
|
)
|
|
330
376
|
body_parts.append(prose)
|
|
331
377
|
body_parts.append("")
|
|
@@ -342,13 +388,14 @@ def _section_modules(root, modules, context):
|
|
|
342
388
|
"body": "\n".join(body_parts), "citations": citations}
|
|
343
389
|
|
|
344
390
|
|
|
345
|
-
def _section_data_flow(root, index, modules, entries, context):
|
|
391
|
+
def _section_data_flow(root, index, modules, entries, context, batch=None):
|
|
346
392
|
fallback = (
|
|
347
393
|
"Execution begins at the entry point(s) (%s) and flows through the "
|
|
348
394
|
"key modules. Trace a request from the entry file into the modules it "
|
|
349
395
|
"imports to follow the data path." % (", ".join(entries) or "not detected")
|
|
350
396
|
)
|
|
351
|
-
prose = _llm_prose("data flow", context, fallback
|
|
397
|
+
prose = _llm_prose("data flow", context, fallback,
|
|
398
|
+
None if batch is None else batch.get("data flow", ""))
|
|
352
399
|
citations = [{"file": e, "line": 1} for e in entries[:4]]
|
|
353
400
|
if not citations and index["files"]:
|
|
354
401
|
citations = [{"file": index["files"][0], "line": 1}]
|
|
@@ -444,10 +491,11 @@ def main(argv=None):
|
|
|
444
491
|
context = _build_context(root, index, modules, entries)
|
|
445
492
|
|
|
446
493
|
log("generating sections (%d files indexed) ..." % len(index["files"]))
|
|
494
|
+
batch = _batch_prose(context)
|
|
447
495
|
sections = [
|
|
448
|
-
_section_overview(root, index, modules, entries, context),
|
|
449
|
-
_section_modules(root, modules, context),
|
|
450
|
-
_section_data_flow(root, index, modules, entries, context),
|
|
496
|
+
_section_overview(root, index, modules, entries, context, batch),
|
|
497
|
+
_section_modules(root, modules, context, batch),
|
|
498
|
+
_section_data_flow(root, index, modules, entries, context, batch),
|
|
451
499
|
]
|
|
452
500
|
|
|
453
501
|
# Enforce the grounding contract: validate every citation against disk.
|
package/autonomy/lib/wiki_llm.py
CHANGED
|
@@ -22,11 +22,48 @@ import subprocess
|
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
_LEAN_FLAGS = (
|
|
26
|
+
("--setting-sources", ["project"]),
|
|
27
|
+
("--strict-mcp-config", []),
|
|
28
|
+
("--mcp-config", ['{"mcpServers":{}}']),
|
|
29
|
+
("--disable-slash-commands", []),
|
|
30
|
+
("--tools=", []),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def lean_claude_flags():
|
|
35
|
+
"""Flags for a one-shot text-only claude child (cost lever L1).
|
|
36
|
+
|
|
37
|
+
--bare only when an API key is present (OAuth-only hosts would fail auth);
|
|
38
|
+
otherwise narrowed setting sources. A flag the installed claude does not
|
|
39
|
+
list in --help is dropped, never passed. Returns [] when claude is absent.
|
|
40
|
+
"""
|
|
41
|
+
claude = _which("claude")
|
|
42
|
+
if not claude:
|
|
43
|
+
return []
|
|
44
|
+
try:
|
|
45
|
+
help_txt = subprocess.run(
|
|
46
|
+
[claude, "--help"], capture_output=True, text=True, timeout=15,
|
|
47
|
+
stdin=subprocess.DEVNULL,
|
|
48
|
+
).stdout
|
|
49
|
+
except (OSError, subprocess.SubprocessError):
|
|
50
|
+
return []
|
|
51
|
+
if os.environ.get("ANTHROPIC_API_KEY") and "--bare" in help_txt:
|
|
52
|
+
return ["--bare", "--tools="] if "--tools" in help_txt else ["--bare"]
|
|
53
|
+
flags = []
|
|
54
|
+
for name, vals in _LEAN_FLAGS:
|
|
55
|
+
if name.rstrip("=") in help_txt:
|
|
56
|
+
flags.append(name)
|
|
57
|
+
flags.extend(vals)
|
|
58
|
+
return flags
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def invoke_llm(prompt, timeout=120, lean=False):
|
|
26
62
|
"""Return the LLM completion string, or None if unavailable.
|
|
27
63
|
|
|
28
64
|
Honors LOKI_WIKI_LLM_STUB for CI. Otherwise shells out to the configured
|
|
29
|
-
provider, mirroring loki docs `_docs_invoke_provider`.
|
|
65
|
+
provider, mirroring loki docs `_docs_invoke_provider`. lean=True adds the
|
|
66
|
+
lean child flags for the claude provider.
|
|
30
67
|
"""
|
|
31
68
|
stub = os.environ.get("LOKI_WIKI_LLM_STUB")
|
|
32
69
|
if stub is not None:
|
|
@@ -64,6 +101,8 @@ def invoke_llm(prompt, timeout=120):
|
|
|
64
101
|
base = cmds.get(provider)
|
|
65
102
|
if base is None or not _which(base[0]):
|
|
66
103
|
return None
|
|
104
|
+
if lean and provider == "claude":
|
|
105
|
+
base = ["claude", "-p"] + lean_claude_flags() + [prompt]
|
|
67
106
|
cmd = ([timeout_cmd, str(timeout)] + base) if timeout_cmd else base
|
|
68
107
|
try:
|
|
69
108
|
out = subprocess.run(
|