loki-mode 7.5.16 → 7.5.17
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/completion-council.sh +12 -6
- package/autonomy/loki +11 -4
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +21 -8
- package/docs/INSTALLATION.md +1 -1
- package/loki-ts/dist/loki.js +3 -3
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v7.5.
|
|
6
|
+
# Loki Mode v7.5.17
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -381,4 +381,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
381
381
|
|
|
382
382
|
---
|
|
383
383
|
|
|
384
|
-
**v7.5.
|
|
384
|
+
**v7.5.17 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.5.
|
|
1
|
+
7.5.17
|
|
@@ -484,7 +484,7 @@ with open(state_file, 'w') as f:
|
|
|
484
484
|
if [ "$_ct_triggered" = "true" ] && [ $approve_count -lt $COUNCIL_SIZE ]; then
|
|
485
485
|
_ct_flipped="true"
|
|
486
486
|
fi
|
|
487
|
-
council_write_transcript "${ITERATION_COUNT:-0}" "$_ct_outcome" "$_ct_triggered" "$_ct_flipped"
|
|
487
|
+
council_write_transcript "${ITERATION_COUNT:-0}" "$_ct_outcome" "$_ct_triggered" "$_ct_flipped" "$effective_threshold"
|
|
488
488
|
|
|
489
489
|
if [ $approve_count -ge $effective_threshold ]; then
|
|
490
490
|
return 0 # Council says DONE
|
|
@@ -500,6 +500,7 @@ with open(state_file, 'w') as f:
|
|
|
500
500
|
# $2 - outcome: APPROVED | REJECTED | BLOCKED_BY_GATE
|
|
501
501
|
# $3 - contrarian_triggered: true | false
|
|
502
502
|
# $4 - contrarian_flipped: true | false
|
|
503
|
+
# $5 - effective_threshold: votes needed for approval (0 = unknown/sentinel)
|
|
503
504
|
#
|
|
504
505
|
# Output: .loki/council/transcripts/iter-<N>-<TIMESTAMP>.json
|
|
505
506
|
#===============================================================================
|
|
@@ -509,6 +510,7 @@ council_write_transcript() {
|
|
|
509
510
|
local outcome="${2:-REJECTED}"
|
|
510
511
|
local contrarian_triggered="${3:-false}"
|
|
511
512
|
local contrarian_flipped="${4:-false}"
|
|
513
|
+
local effective_threshold="${5:-0}"
|
|
512
514
|
local timestamp
|
|
513
515
|
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
514
516
|
# Remove colons and hyphens from timestamp for filename safety
|
|
@@ -532,6 +534,7 @@ council_write_transcript() {
|
|
|
532
534
|
_TASK="$task_or_prd" _PRD="${COUNCIL_PRD_PATH:-}" \
|
|
533
535
|
_ROUND_FILE="${round_file}" _DA_FILE="${da_file}" \
|
|
534
536
|
_MEMBERS_DIR="$COUNCIL_STATE_DIR/votes/iteration-${iteration}" \
|
|
537
|
+
_THRESHOLD="$effective_threshold" \
|
|
535
538
|
_OUT="$transcript_file" \
|
|
536
539
|
python3 -c "
|
|
537
540
|
import json, os, pathlib, re
|
|
@@ -627,7 +630,7 @@ transcript = {
|
|
|
627
630
|
'contrarian_flipped': cf,
|
|
628
631
|
'approve_count': sum(1 for v in non_contrarian if v.get('verdict') == 'APPROVE'),
|
|
629
632
|
'reject_count': sum(1 for v in non_contrarian if v.get('verdict') in ('REJECT', 'CANNOT_VALIDATE')),
|
|
630
|
-
'threshold':
|
|
633
|
+
'threshold': int(os.environ.get('_THRESHOLD', '0')),
|
|
631
634
|
'total_members': len(non_contrarian),
|
|
632
635
|
}
|
|
633
636
|
with open(os.environ['_OUT'], 'w') as f:
|
|
@@ -1512,6 +1515,9 @@ council_evaluate() {
|
|
|
1512
1515
|
return 1 # CONTINUE - can't complete with critical failures
|
|
1513
1516
|
fi
|
|
1514
1517
|
|
|
1518
|
+
# Compute threshold using the same ceiling(2/3) formula as council_vote and council_aggregate_votes
|
|
1519
|
+
local _eval_threshold=$(( (COUNCIL_SIZE * 2 + 2) / 3 ))
|
|
1520
|
+
|
|
1515
1521
|
# Step 1: Aggregate votes from all members
|
|
1516
1522
|
local aggregate_result
|
|
1517
1523
|
aggregate_result=$(council_aggregate_votes)
|
|
@@ -1532,14 +1538,14 @@ council_evaluate() {
|
|
|
1532
1538
|
if [ "$da_result" = "OVERRIDE_CONTINUE" ]; then
|
|
1533
1539
|
log_warn "Council evaluate: devil's advocate overrode unanimous COMPLETE"
|
|
1534
1540
|
# Write transcript: DA triggered and flipped the outcome (Path B)
|
|
1535
|
-
council_write_transcript "${ITERATION_COUNT:-0}" "REJECTED" "true" "true"
|
|
1541
|
+
council_write_transcript "${ITERATION_COUNT:-0}" "REJECTED" "true" "true" "$_eval_threshold"
|
|
1536
1542
|
return 1 # CONTINUE
|
|
1537
1543
|
fi
|
|
1538
1544
|
# Write transcript: DA triggered but did NOT flip (Path B, unanimous COMPLETE confirmed)
|
|
1539
|
-
council_write_transcript "${ITERATION_COUNT:-0}" "APPROVED" "true" "false"
|
|
1545
|
+
council_write_transcript "${ITERATION_COUNT:-0}" "APPROVED" "true" "false" "$_eval_threshold"
|
|
1540
1546
|
else
|
|
1541
1547
|
# Write transcript: not unanimous, DA not triggered (Path B)
|
|
1542
|
-
council_write_transcript "${ITERATION_COUNT:-0}" "APPROVED" "false" "false"
|
|
1548
|
+
council_write_transcript "${ITERATION_COUNT:-0}" "APPROVED" "false" "false" "$_eval_threshold"
|
|
1543
1549
|
fi
|
|
1544
1550
|
|
|
1545
1551
|
log_info "Council evaluate: verdict is COMPLETE"
|
|
@@ -1547,7 +1553,7 @@ council_evaluate() {
|
|
|
1547
1553
|
fi
|
|
1548
1554
|
|
|
1549
1555
|
# Write transcript: aggregate voted CONTINUE (Path B)
|
|
1550
|
-
council_write_transcript "${ITERATION_COUNT:-0}" "REJECTED" "false" "false"
|
|
1556
|
+
council_write_transcript "${ITERATION_COUNT:-0}" "REJECTED" "false" "false" "$_eval_threshold"
|
|
1551
1557
|
log_info "Council evaluate: verdict is CONTINUE"
|
|
1552
1558
|
return 1 # CONTINUE
|
|
1553
1559
|
}
|
package/autonomy/loki
CHANGED
|
@@ -7052,9 +7052,10 @@ cmd_sentrux() {
|
|
|
7052
7052
|
case "$sub" in
|
|
7053
7053
|
baseline)
|
|
7054
7054
|
if ! sentrux_available; then
|
|
7055
|
-
echo -e "${YELLOW}sentrux not installed.${NC} Install
|
|
7055
|
+
echo -e "${YELLOW}sentrux not installed.${NC} Install with one of:" >&2
|
|
7056
7056
|
echo " brew install sentrux/tap/sentrux" >&2
|
|
7057
|
-
echo "
|
|
7057
|
+
echo " curl -fsSL https://raw.githubusercontent.com/sentrux/sentrux/main/install.sh | sh" >&2
|
|
7058
|
+
echo " See also: https://github.com/sentrux/sentrux" >&2
|
|
7058
7059
|
return 2
|
|
7059
7060
|
fi
|
|
7060
7061
|
if sentrux_baseline_save "$target"; then
|
|
@@ -7068,7 +7069,10 @@ cmd_sentrux() {
|
|
|
7068
7069
|
;;
|
|
7069
7070
|
gate)
|
|
7070
7071
|
if ! sentrux_available; then
|
|
7071
|
-
echo -e "${YELLOW}sentrux not installed.${NC}
|
|
7072
|
+
echo -e "${YELLOW}sentrux not installed.${NC} Install with one of:" >&2
|
|
7073
|
+
echo " brew install sentrux/tap/sentrux" >&2
|
|
7074
|
+
echo " curl -fsSL https://raw.githubusercontent.com/sentrux/sentrux/main/install.sh | sh" >&2
|
|
7075
|
+
echo " See also: https://github.com/sentrux/sentrux" >&2
|
|
7072
7076
|
return 2
|
|
7073
7077
|
fi
|
|
7074
7078
|
local diff verdict before after
|
|
@@ -7097,7 +7101,10 @@ cmd_sentrux() {
|
|
|
7097
7101
|
status)
|
|
7098
7102
|
if ! sentrux_available; then
|
|
7099
7103
|
echo "sentrux: not installed (optional)"
|
|
7100
|
-
echo "install
|
|
7104
|
+
echo "install with one of:"
|
|
7105
|
+
echo " brew install sentrux/tap/sentrux"
|
|
7106
|
+
echo " curl -fsSL https://raw.githubusercontent.com/sentrux/sentrux/main/install.sh | sh"
|
|
7107
|
+
echo " See also: https://github.com/sentrux/sentrux"
|
|
7101
7108
|
return 0
|
|
7102
7109
|
fi
|
|
7103
7110
|
local v
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -3786,7 +3786,7 @@ async def force_council_review():
|
|
|
3786
3786
|
async def get_council_transcripts(
|
|
3787
3787
|
limit: int = Query(default=20, ge=1, le=200),
|
|
3788
3788
|
since: Optional[str] = Query(default=None),
|
|
3789
|
-
iter_min: Optional[int] = Query(default=None),
|
|
3789
|
+
iter_min: Optional[int] = Query(default=None, ge=0),
|
|
3790
3790
|
):
|
|
3791
3791
|
"""List council transcript records, sorted descending by iteration number.
|
|
3792
3792
|
|
|
@@ -3795,10 +3795,7 @@ async def get_council_transcripts(
|
|
|
3795
3795
|
since ISO8601 string (optional), filter to transcripts after this time
|
|
3796
3796
|
iter_min int (optional), filter to iteration >= N
|
|
3797
3797
|
"""
|
|
3798
|
-
|
|
3799
|
-
if not transcripts_dir.exists():
|
|
3800
|
-
return {"transcripts": [], "total": 0, "latest_id": None}
|
|
3801
|
-
|
|
3798
|
+
# Validate query params before any early-return so invalid inputs always get 400.
|
|
3802
3799
|
since_dt = None
|
|
3803
3800
|
if since:
|
|
3804
3801
|
try:
|
|
@@ -3806,6 +3803,10 @@ async def get_council_transcripts(
|
|
|
3806
3803
|
except ValueError:
|
|
3807
3804
|
raise HTTPException(status_code=400, detail="Invalid 'since' timestamp format; expected ISO8601")
|
|
3808
3805
|
|
|
3806
|
+
transcripts_dir = _get_loki_dir() / "council" / "transcripts"
|
|
3807
|
+
if not transcripts_dir.exists():
|
|
3808
|
+
return {"transcripts": [], "total": 0, "latest_id": None}
|
|
3809
|
+
|
|
3809
3810
|
records = []
|
|
3810
3811
|
for f in sorted(transcripts_dir.glob("iter-*.json"), reverse=True):
|
|
3811
3812
|
try:
|
|
@@ -3816,6 +3817,9 @@ async def get_council_transcripts(
|
|
|
3816
3817
|
if not isinstance(rec, dict):
|
|
3817
3818
|
logger.warning("Skipping non-object council transcript file: %s", f.name)
|
|
3818
3819
|
continue
|
|
3820
|
+
if not isinstance(rec.get("iteration_id"), str):
|
|
3821
|
+
logger.warning("Skipping transcript missing iteration_id field: %s", f.name)
|
|
3822
|
+
continue
|
|
3819
3823
|
if since_dt is not None:
|
|
3820
3824
|
ts_str = rec.get("timestamp", "")
|
|
3821
3825
|
try:
|
|
@@ -3833,7 +3837,7 @@ async def get_council_transcripts(
|
|
|
3833
3837
|
return {
|
|
3834
3838
|
"transcripts": records,
|
|
3835
3839
|
"total": len(records),
|
|
3836
|
-
"latest_id": records[0]
|
|
3840
|
+
"latest_id": records[0].get("iteration_id") if records else None,
|
|
3837
3841
|
}
|
|
3838
3842
|
|
|
3839
3843
|
|
|
@@ -3851,9 +3855,18 @@ async def get_council_transcript(iteration_id: str):
|
|
|
3851
3855
|
if not transcript_file.exists():
|
|
3852
3856
|
raise HTTPException(status_code=404, detail="Transcript not found")
|
|
3853
3857
|
try:
|
|
3854
|
-
|
|
3858
|
+
rec = json.loads(transcript_file.read_text())
|
|
3855
3859
|
except Exception:
|
|
3856
|
-
raise HTTPException(
|
|
3860
|
+
raise HTTPException(
|
|
3861
|
+
status_code=410,
|
|
3862
|
+
detail=f"Transcript file for {iteration_id} is corrupt; admin should inspect or remove it",
|
|
3863
|
+
)
|
|
3864
|
+
if not isinstance(rec, dict):
|
|
3865
|
+
raise HTTPException(
|
|
3866
|
+
status_code=410,
|
|
3867
|
+
detail=f"Transcript file for {iteration_id} is corrupt; admin should inspect or remove it",
|
|
3868
|
+
)
|
|
3869
|
+
return rec
|
|
3857
3870
|
|
|
3858
3871
|
|
|
3859
3872
|
# =============================================================================
|
package/docs/INSTALLATION.md
CHANGED
package/loki-ts/dist/loki.js
CHANGED
|
@@ -327,7 +327,7 @@ Start a session with: loki start <prd>`}}let Z=p6(X);return{exitCode:0,stdout:Q?
|
|
|
327
327
|
`),process.env.LOKI_LEGACY_BASH==="1"||process.env.LOKI_LEGACY_BASH==="true")process.stdout.write(` ${Y("warn")} LOKI_LEGACY_BASH set: shim routes every command to autonomy/loki (bash)
|
|
328
328
|
`);if(process.env.LOKI_TS_ENTRY)process.stdout.write(` ${Y("pass")} LOKI_TS_ENTRY override: ${process.env.LOKI_TS_ENTRY}
|
|
329
329
|
`);if(process.env.BUN_FROM_SOURCE==="1"||process.env.BUN_FROM_SOURCE==="true")process.stdout.write(` ${Y("pass")} BUN_FROM_SOURCE set: shim prefers loki-ts/src/ over dist/
|
|
330
|
-
`);let h=await s();if(h!==null){let D=(await E([h,"-c","import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"],{timeoutMs:5000})).stdout.trim();if(D.startsWith("3.12"))process.stdout.write(` ${Y("pass")} Python 3.12 (chromadb / sentence-transformers): ${D} at ${h}
|
|
330
|
+
`);let h=await s();if(h!==null){let D=(await E([h,"-c","import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')"],{timeoutMs:5000})).stdout.trim();if(D.startsWith("3.12"))process.stdout.write(` ${Y("pass")} Python 3.12 (chromadb / sentence-transformers): ${D} at ${h}
|
|
331
331
|
`);else if(D)process.stdout.write(` ${Y("warn")} Python 3.12 NOT found -- using ${D} at ${h}; chromadb / sentence-transformers may fail. Install python3.12 (brew install python@3.12 / apt install python3.12).
|
|
332
332
|
`);else process.stdout.write(` ${Y("warn")} Python 3 found at ${h} but version probe failed; chromadb may not work.
|
|
333
333
|
`)}else process.stdout.write(` ${Y("warn")} Python 3 not on PATH -- memory + MCP integrations disabled.
|
|
@@ -425,7 +425,7 @@ Subcommands:
|
|
|
425
425
|
|
|
426
426
|
This command is invoked by autonomy/run.sh between iterations. Users
|
|
427
427
|
should not run it directly -- run \`loki start\` instead.
|
|
428
|
-
`,O5;var z6=S(()=>{g();O1();O5=l1});g();import{readFileSync as H6}from"fs";import{resolve as G6,dirname as V6}from"path";import{fileURLToPath as J6}from"url";var o=null;function r1(){if(o!==null)return o;let $="7.5.
|
|
428
|
+
`,O5;var z6=S(()=>{g();O1();O5=l1});g();import{readFileSync as H6}from"fs";import{resolve as G6,dirname as V6}from"path";import{fileURLToPath as J6}from"url";var o=null;function r1(){if(o!==null)return o;let $="7.5.17";if(typeof $==="string"&&$.length>0)return o=$,o;try{let Q=V6(J6(import.meta.url)),z=E1(Q);o=H6(G6(z,"VERSION"),"utf-8").trim()}catch{o="unknown"}return o}function s1(){return process.stdout.write(`Loki Mode v${r1()}
|
|
429
429
|
`),0}p();n();g();import{readFileSync as T6,existsSync as O6}from"fs";import{resolve as j6}from"path";var _6=["claude","codex","gemini","cline","aider"];function i1(){let $=j6(x(),"state","provider");if(!O6($))return"";try{return T6($,"utf-8").trim()}catch{return""}}function I6($,Q){return $||Q||process.env.LOKI_PROVIDER||"claude"}function L6($){let Q=i1(),z=I6($,Q);switch(process.stdout.write(`${C}Current Provider${U}
|
|
430
430
|
`),process.stdout.write(`
|
|
431
431
|
`),process.stdout.write(`${A}Provider:${U} ${z}
|
|
@@ -508,4 +508,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
508
508
|
`),2}default:return process.stderr.write(`Unknown command: ${Q}
|
|
509
509
|
`),process.stderr.write(Q6),2}}process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var I5=await _5(Bun.argv.slice(2));process.exit(I5);
|
|
510
510
|
|
|
511
|
-
//# debugId=
|
|
511
|
+
//# debugId=9C22E648FE5F7C2364756E2164756E21
|
package/mcp/__init__.py
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
|
-
"version": "7.5.
|
|
3
|
+
"version": "7.5.17",
|
|
4
4
|
"description": "Loki Mode by Autonomi. Multi-agent autonomous SDLC framework. Spec to deployed app: PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief. 5 AI providers (Claude Code, OpenAI Codex, Google Gemini, Cline, Aider). 11 quality gates.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|