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 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.16
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.16 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
384
+ **v7.5.17 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.5.16
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': 2,
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 via:" >&2
7055
+ echo -e "${YELLOW}sentrux not installed.${NC} Install with one of:" >&2
7056
7056
  echo " brew install sentrux/tap/sentrux" >&2
7057
- echo " or download from https://github.com/sentrux/sentrux/releases" >&2
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} Run 'loki sentrux baseline' for setup hints." >&2
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: brew install sentrux/tap/sentrux"
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
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.5.16"
10
+ __version__ = "7.5.17"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -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
- transcripts_dir = _get_loki_dir() / "council" / "transcripts"
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]["iteration_id"] if records else None,
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
- return json.loads(transcript_file.read_text())
3858
+ rec = json.loads(transcript_file.read_text())
3855
3859
  except Exception:
3856
- raise HTTPException(status_code=500, detail="Corrupt transcript file")
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
  # =============================================================================
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v7.5.16
5
+ **Version:** v7.5.17
6
6
 
7
7
  ---
8
8
 
@@ -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.16";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()}
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=F75824E4E6AD1D6064756E2164756E21
511
+ //# debugId=9C22E648FE5F7C2364756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.5.16'
60
+ __version__ = '7.5.17'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "7.5.16",
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",