loki-mode 7.46.0 → 7.47.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.
Files changed (84) hide show
  1. package/README.md +1 -1
  2. package/SKILL.md +2 -2
  3. package/VERSION +1 -1
  4. package/autonomy/completion-council.sh +113 -0
  5. package/autonomy/run.sh +90 -4
  6. package/autonomy/spec-interrogation.sh +549 -0
  7. package/dashboard/__init__.py +1 -1
  8. package/dashboard/auth.py +117 -2
  9. package/docs/ACKNOWLEDGEMENTS.md +1 -1
  10. package/docs/COMPETITIVE-ANALYSIS.md +1 -1
  11. package/docs/INSTALLATION.md +2 -2
  12. package/docs/OPEN-CORE-BOUNDARY.md +6 -5
  13. package/docs/P2-SPEC-ROBUSTNESS-PLAN.md +192 -0
  14. package/docs/R9-OPEN-CORE-HOOKS-PLAN.md +2 -2
  15. package/docs/auto-claude-comparison.md +2 -2
  16. package/docs/certification/README.md +1 -1
  17. package/docs/competitive/bolt-new-analysis.md +1 -1
  18. package/docs/competitive/emergence-others-analysis.md +6 -6
  19. package/docs/competitive/replit-lovable-analysis.md +4 -4
  20. package/docs/enterprise/security.md +43 -3
  21. package/docs/show-hn-post.md +1 -1
  22. package/loki-ts/dist/loki.js +2 -2
  23. package/mcp/__init__.py +1 -1
  24. package/package.json +1 -1
  25. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  26. package/web-app/dist/assets/{AdminPage-CKUOsWZW.js → AdminPage-CcCJ0Sjt.js} +1 -1
  27. package/web-app/dist/assets/{Avatar-CL9Id9Hi.js → Avatar-DK8kmayw.js} +1 -1
  28. package/web-app/dist/assets/{Badge-B12zwlD7.js → Badge-4uAWnemi.js} +1 -1
  29. package/web-app/dist/assets/{Button-CFLVoduT.js → Button-BBMk33tk.js} +1 -1
  30. package/web-app/dist/assets/ComparePage-bt9rwvST.js +1 -0
  31. package/web-app/dist/assets/{GitHubIssuesPanel-CSitxtAX.js → GitHubIssuesPanel-WDbH47UM.js} +1 -1
  32. package/web-app/dist/assets/{GitHubPRsPanel-BIT06FRo.js → GitHubPRsPanel-C2CiYtTx.js} +1 -1
  33. package/web-app/dist/assets/{HomePage-pU_0fGny.js → HomePage-BQk-MUjn.js} +4 -4
  34. package/web-app/dist/assets/{LoginPage-DTZtt2Yb.js → LoginPage-DMOZVGGL.js} +1 -1
  35. package/web-app/dist/assets/{MagicPage-10zfra8o.js → MagicPage-Bzp2Nt1z.js} +1 -1
  36. package/web-app/dist/assets/{MetricsPage-C-wiKUkv.js → MetricsPage-C39JVdsw.js} +1 -1
  37. package/web-app/dist/assets/{NotFoundPage-BDkcmhYe.js → NotFoundPage-6vT_U9UL.js} +1 -1
  38. package/web-app/dist/assets/{ProjectPage-CiCavQ8n.js → ProjectPage-BfFcZp-E.js} +3 -3
  39. package/web-app/dist/assets/{ProjectsPage-BLCXQwwC.js → ProjectsPage-CPMBf8Wt.js} +1 -1
  40. package/web-app/dist/assets/{SettingsPage-PkxtaMyg.js → SettingsPage-BnNN6ETl.js} +1 -1
  41. package/web-app/dist/assets/{ShowcasePage-iECp8Tha.js → ShowcasePage-WDrMf-cx.js} +1 -1
  42. package/web-app/dist/assets/{SystemSettingsPage-DS6Anno1.js → SystemSettingsPage-DX4jb2e8.js} +1 -1
  43. package/web-app/dist/assets/{TeamsPage-ls6h6bNL.js → TeamsPage-BCfqcXzu.js} +1 -1
  44. package/web-app/dist/assets/{TemplatesPage-Bk0QzlPt.js → TemplatesPage-CZvmimDj.js} +1 -1
  45. package/web-app/dist/assets/{TerminalOutput-4-1hWCtZ.js → TerminalOutput-BlRqFwWV.js} +1 -1
  46. package/web-app/dist/assets/{activity-DH3ih2nS.js → activity-CacZsUyr.js} +1 -1
  47. package/web-app/dist/assets/{bell-Gn17S6uv.js → bell-DK2qtHnk.js} +1 -1
  48. package/web-app/dist/assets/{bot-Cbycc3VE.js → bot-CkcUtHad.js} +1 -1
  49. package/web-app/dist/assets/{check-nIAqa-kf.js → check-CbCPjX3M.js} +1 -1
  50. package/web-app/dist/assets/{chevron-left-D2jcWDll.js → chevron-left-5NUKWw3i.js} +1 -1
  51. package/web-app/dist/assets/{circle-alert-CpL4Bhvt.js → circle-alert-S7uFoxC2.js} +1 -1
  52. package/web-app/dist/assets/{clock-IW4Wq86N.js → clock-CaQRrIrs.js} +1 -1
  53. package/web-app/dist/assets/{cloud-Cn8nNuH2.js → cloud-DBAX6c0r.js} +1 -1
  54. package/web-app/dist/assets/{code-xml-BiJBteXf.js → code-xml-De5-EXv3.js} +1 -1
  55. package/web-app/dist/assets/{copy-CnqkyNsi.js → copy-CUkT6k1v.js} +1 -1
  56. package/web-app/dist/assets/{database-CKSReqa5.js → database-BAWf1Gwt.js} +1 -1
  57. package/web-app/dist/assets/{dollar-sign-CDzDY64R.js → dollar-sign-Ji8zk86R.js} +1 -1
  58. package/web-app/dist/assets/{file-code-corner-Box4IwG1.js → file-code-corner-ChtXoBwS.js} +1 -1
  59. package/web-app/dist/assets/{file-plus-DpGqlXF8.js → file-plus-bFa37P76.js} +1 -1
  60. package/web-app/dist/assets/{folder-open-B57dAoBv.js → folder-open-DhXpXscO.js} +1 -1
  61. package/web-app/dist/assets/{git-commit-horizontal-BVbucmO5.js → git-commit-horizontal-DVPeDQ3j.js} +1 -1
  62. package/web-app/dist/assets/{globe-BkOnKl4x.js → globe-BPZgPeeu.js} +1 -1
  63. package/web-app/dist/assets/{hammer-DRbIQ4QU.js → hammer-jLCaujYH.js} +1 -1
  64. package/web-app/dist/assets/{index-CM_b_EhP.js → index-B-0iHBPO.js} +2 -2
  65. package/web-app/dist/assets/{layers-B78BiFiU.js → layers-B1vsrsFW.js} +1 -1
  66. package/web-app/dist/assets/{lightbulb-B-Itbm9g.js → lightbulb-C-uLoq9Y.js} +1 -1
  67. package/web-app/dist/assets/{loader-circle-Oq6NQhW2.js → loader-circle-JTfD-ZuM.js} +1 -1
  68. package/web-app/dist/assets/{lock-DbJ9zxbw.js → lock-G9rxD4gZ.js} +1 -1
  69. package/web-app/dist/assets/{mail-CzMRod6m.js → mail-BJ0PTN_V.js} +1 -1
  70. package/web-app/dist/assets/{package-WZ5osvej.js → package-CXClfLOO.js} +1 -1
  71. package/web-app/dist/assets/{plus-j08lFR-K.js → plus-EoL5OCB7.js} +1 -1
  72. package/web-app/dist/assets/{refresh-cw-CIr7E-g2.js → refresh-cw-BjREUnVq.js} +1 -1
  73. package/web-app/dist/assets/{rotate-ccw-gwoXxDeE.js → rotate-ccw-DahWX07H.js} +1 -1
  74. package/web-app/dist/assets/{save-B8fV_ZpE.js → save-Dek3gCn1.js} +1 -1
  75. package/web-app/dist/assets/{server-D5dO1paz.js → server-D6V1BAia.js} +1 -1
  76. package/web-app/dist/assets/{shield-alert-Du08zhdg.js → shield-alert-BtTK5Sxb.js} +1 -1
  77. package/web-app/dist/assets/{trash-2-DEKSVae5.js → trash-2-BT5o_g0r.js} +1 -1
  78. package/web-app/dist/assets/{trending-down-DBiXUtxJ.js → trending-down-D4Jk7KF3.js} +1 -1
  79. package/web-app/dist/assets/{trending-up-BgmK_tHq.js → trending-up-EQFTzhEo.js} +1 -1
  80. package/web-app/dist/assets/{upload-IaViyeVD.js → upload-JfI5lCSE.js} +1 -1
  81. package/web-app/dist/assets/{usePolling-PiRLqNu6.js → usePolling-BnhPUuGd.js} +1 -1
  82. package/web-app/dist/assets/{user-BB5J8wAF.js → user-DSUiUYtj.js} +1 -1
  83. package/web-app/dist/index.html +1 -1
  84. package/web-app/dist/assets/ComparePage-Dg0UdZAk.js +0 -1
package/dashboard/auth.py CHANGED
@@ -477,6 +477,111 @@ def _base64url_decode(data: str) -> bytes:
477
477
  return base64.urlsafe_b64decode(data)
478
478
 
479
479
 
480
+ # Role precedence (highest privilege first). When a token carries multiple
481
+ # recognized role claims, the highest-privilege match wins.
482
+ _ROLE_PRECEDENCE = ("admin", "operator", "auditor", "viewer")
483
+
484
+
485
+ def _normalize_claim_values(value) -> set[str]:
486
+ """Normalize an OIDC claim value into a lowercased set of strings.
487
+
488
+ Claim values may be a single string, a space-separated string, or a
489
+ list of strings (different providers use different shapes). All are
490
+ flattened into a set of lowercased tokens for matching against ROLES.
491
+ """
492
+ out: set[str] = set()
493
+ if value is None:
494
+ return out
495
+ if isinstance(value, str):
496
+ for part in value.split():
497
+ if part:
498
+ out.add(part.strip().lower())
499
+ elif isinstance(value, (list, tuple, set)):
500
+ for item in value:
501
+ if isinstance(item, str):
502
+ s = item.strip().lower()
503
+ if s:
504
+ out.add(s)
505
+ return out
506
+
507
+
508
+ def _collect_role_claims(claims: dict) -> set[str]:
509
+ """Collect candidate role/group values from standard OIDC claim shapes.
510
+
511
+ Recognized sources (case-insensitive values flattened into one set):
512
+ - A configurable claim named by LOKI_OIDC_ROLES_CLAIM (supports a dotted
513
+ path for nested claims, e.g. "realm_access.roles" for Keycloak).
514
+ - "roles" (generic)
515
+ - "groups" (generic)
516
+ - "realm_access.roles" (Keycloak)
517
+ - "cognito:groups" (AWS Cognito)
518
+
519
+ Note: "groups"/"cognito:groups" typically carry arbitrary group names,
520
+ not Loki role names. Only values that exactly match one of the four
521
+ built-in role names (admin/operator/viewer/auditor, case-insensitive)
522
+ grant a role. Everything else is ignored and the default role applies.
523
+ """
524
+ candidates: set[str] = set()
525
+
526
+ def _read_dotted(path: str):
527
+ node = claims
528
+ for key in path.split("."):
529
+ if isinstance(node, dict) and key in node:
530
+ node = node[key]
531
+ else:
532
+ return None
533
+ return node
534
+
535
+ configured = os.environ.get("LOKI_OIDC_ROLES_CLAIM", "").strip()
536
+ sources = []
537
+ if configured:
538
+ sources.append(configured)
539
+ sources.extend(["roles", "groups", "realm_access.roles", "cognito:groups"])
540
+
541
+ for src in sources:
542
+ if "." in src:
543
+ val = _read_dotted(src)
544
+ else:
545
+ val = claims.get(src)
546
+ candidates |= _normalize_claim_values(val)
547
+
548
+ return candidates
549
+
550
+
551
+ def _default_oidc_role() -> str:
552
+ """Return the configured default OIDC role, validated against ROLES.
553
+
554
+ Defaults to the least-privileged role ("viewer"). If LOKI_OIDC_DEFAULT_ROLE
555
+ is set to an unrecognized value, falls back to "viewer" (never admin).
556
+ """
557
+ configured = os.environ.get("LOKI_OIDC_DEFAULT_ROLE", "").strip().lower()
558
+ if configured in ROLES:
559
+ return configured
560
+ return "viewer"
561
+
562
+
563
+ def _scopes_from_claims(claims: dict) -> tuple[list[str], str]:
564
+ """Map OIDC token claims to Loki scopes via the existing ROLES mapping.
565
+
566
+ Returns a tuple of (scopes, role_name). If no recognized role claim is
567
+ present, the safe default role (viewer, or LOKI_OIDC_DEFAULT_ROLE) is
568
+ applied. This function NEVER returns ["*"]/admin by default: full access
569
+ is granted only when an explicit admin role claim is present.
570
+ """
571
+ candidate_values = _collect_role_claims(claims)
572
+
573
+ matched_role = None
574
+ for role in _ROLE_PRECEDENCE:
575
+ if role in candidate_values:
576
+ matched_role = role
577
+ break
578
+
579
+ if matched_role is None:
580
+ matched_role = _default_oidc_role()
581
+
582
+ return resolve_scopes(matched_role), matched_role
583
+
584
+
480
585
  def validate_oidc_token(token_str: str) -> Optional[dict]:
481
586
  """Validate an OIDC JWT token.
482
587
 
@@ -489,6 +594,12 @@ def validate_oidc_token(token_str: str) -> Optional[dict]:
489
594
  - Audience matches OIDC_AUDIENCE or OIDC_CLIENT_ID
490
595
  - Token is not expired
491
596
 
597
+ On success, role/group claims are mapped to Loki roles (admin/operator/
598
+ viewer/auditor) via _scopes_from_claims. When no recognized role claim is
599
+ present, the least-privileged default role (viewer, configurable via
600
+ LOKI_OIDC_DEFAULT_ROLE) is applied. OIDC users are never granted ["*"]
601
+ unless an explicit admin role claim is present.
602
+
492
603
  SECURITY CRITICAL: Without PyJWT, JWT signatures are NOT cryptographically
493
604
  verified. An attacker can forge tokens with arbitrary claims. For any
494
605
  production deployment, you MUST install PyJWT + cryptography so that
@@ -529,11 +640,13 @@ def validate_oidc_token(token_str: str) -> Optional[dict]:
529
640
  issuer=OIDC_ISSUER,
530
641
  )
531
642
 
643
+ scopes, role = _scopes_from_claims(decoded)
532
644
  return {
533
645
  "id": decoded.get("sub", ""),
534
646
  "name": decoded.get("name", decoded.get("email", decoded.get("sub", ""))),
535
647
  "email": decoded.get("email", ""),
536
- "scopes": ["*"], # OIDC users get full access
648
+ "scopes": scopes, # mapped from OIDC role/group claims
649
+ "role": role,
537
650
  "auth_method": "oidc",
538
651
  "issuer": decoded.get("iss"),
539
652
  }
@@ -602,11 +715,13 @@ def validate_oidc_token(token_str: str) -> Optional[dict]:
602
715
  return None
603
716
 
604
717
  # Return user info from claims
718
+ scopes, role = _scopes_from_claims(claims)
605
719
  return {
606
720
  "id": claims.get("sub", ""),
607
721
  "name": claims.get("name", claims.get("email", claims.get("sub", ""))),
608
722
  "email": claims.get("email", ""),
609
- "scopes": ["*"], # OIDC users get full access
723
+ "scopes": scopes, # mapped from OIDC role/group claims
724
+ "role": role,
610
725
  "auth_method": "oidc",
611
726
  "issuer": claims.get("iss"),
612
727
  }
@@ -336,7 +336,7 @@ Based on research synthesis, the following improvements are planned:
336
336
 
337
337
  This acknowledgements file documents the research and resources that influenced Loki Mode's design. All referenced works retain their original licenses and copyrights.
338
338
 
339
- Loki Mode itself is released under the MIT License.
339
+ Loki Mode itself is released under the Business Source License 1.1 (BUSL-1.1), a source-available license.
340
340
 
341
341
  ---
342
342
 
@@ -45,7 +45,7 @@ GSD is the closest competitor -- a context engineering system that spawns fresh
45
45
  | **Enterprise Security** | `--dangerously-skip-permissions` | MCP sandboxed | Sandboxed | Audit logs, RBAC | Staged autonomy | Sandboxed |
46
46
  | **Cross-Project Learning** | No | AgentDB | No | No | No | Limited |
47
47
  | **Observability** | Dashboard + STATUS.txt | Real-time tracing | Logs | Full tracing | Built-in | Full |
48
- | **Pricing** | Free (OSS) | Free (OSS) | Free (OSS) | $25+/mo | $20-400/mo | $20-500/mo |
48
+ | **Pricing** | Free (source-available) | Free (OSS) | Free (OSS) | $25+/mo | $20-400/mo | $20-500/mo |
49
49
  | **Production Ready** | Experimental | Production | Production | Production | Production | Production |
50
50
  | **Resource Monitoring** | Yes (v2.18.5) | Unknown | No | No | No | No |
51
51
  | **State Recovery** | Yes (checkpoints) | Yes (AgentDB) | Limited | Yes | Git worktrees | Yes |
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v7.46.0
5
+ **Version:** v7.47.0
6
6
 
7
7
  ---
8
8
 
@@ -389,7 +389,7 @@ provider works inside the container. Provide auth with your Anthropic API key:
389
389
  # Run Loki Mode in Docker (Claude provider, API-key auth)
390
390
  docker run --rm -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
391
391
  -v $(pwd):/workspace -w /workspace \
392
- asklokesh/loki-mode:7.46.0 start ./my-spec.md
392
+ asklokesh/loki-mode:7.47.0 start ./my-spec.md
393
393
  ```
394
394
 
395
395
  ##### docker compose + .env (no host install)
@@ -1,15 +1,16 @@
1
1
  # Loki Mode open-core boundary
2
2
 
3
- Loki Mode is and stays open source. This document draws the line between what is
4
- free forever and what hosted/paid/enterprise plans would add on top. R9 ships
3
+ Loki Mode is and stays source-available (BUSL-1.1) and free to self-host. This
4
+ document draws the line between what is free forever and what
5
+ hosted/paid/enterprise plans would add on top. R9 ships
5
6
  the SEAMS for that line; it does not ship a hosted backend, a license server, or
6
7
  any paywall on existing functionality.
7
8
 
8
9
  ## Principle
9
10
 
10
- OSS is fully functional with zero hosted backend. Every capability Loki has
11
- today runs locally, free, with no account, no license key, and no network call
12
- to any Loki service. Hosted/paid features are ADDITIVE convenience and
11
+ The free self-hosted tier is fully functional with zero hosted backend. Every
12
+ capability Loki has today runs locally, free, with no account, no license key,
13
+ and no network call to any Loki service. Hosted/paid features are ADDITIVE convenience and
13
14
  team/enterprise layers, never a removal or gating of something that is free
14
15
  today.
15
16
 
@@ -0,0 +1,192 @@
1
+ # P2 Spec Robustness Plan (P2-1 spec interrogation gate + P2-2 assumption ledger)
2
+
3
+ Status: design for implementation. No version bump, no commit in this arc.
4
+
5
+ ## Goal
6
+
7
+ Loki must stay accurate even when the input spec is WRONG, ambiguous, or
8
+ incomplete. Today two building blocks already detect spec defects but neither
9
+ feeds the autonomous loop:
10
+
11
+ - `autonomy/grill.sh` invokes the provider once with a Devil's-Advocate prompt
12
+ and writes 10-15 hardest spec questions to `.loki/grill/report.md`. It is
13
+ CLI-only (`grep grill autonomy/run.sh` = 0 invocations) and nothing reads its
14
+ output.
15
+ - `autonomy/prd-analyzer.py` detects missing PRD dimensions and has a
16
+ deterministic `_make_assumption()` map, writing `.loki/prd-observations.md`,
17
+ which nothing reads. Its interactive Q&A is inert in non-TTY (autonomous) runs.
18
+
19
+ The fix: run interrogation automatically in DISCOVERY, classify the findings,
20
+ record every spec gap as a first-class ASSUMPTION in a tracked ledger, BLOCK
21
+ completion while high-severity assumptions are unconfirmed-and-unacknowledged,
22
+ and surface the ledger in the proof-of-done output. Defects are SURFACED as
23
+ recorded assumptions, never silently autocorrected.
24
+
25
+ ## Core design decision: auto-acknowledgment lifecycle (prevents the trap)
26
+
27
+ A naive "block completion while any high-severity assumption is unconfirmed"
28
+ hard-blocks EVERY ambiguous run to max-iterations, because in autonomous
29
+ (non-TTY) mode no human can ever set `confirmed=yes`. We never reach the
30
+ "done, plus here is what I assumed" output the goal demands.
31
+
32
+ Resolution: split the gate from the lifecycle.
33
+
34
+ - The gate `council_assumption_ledger_gate` is a PURE function of ledger state.
35
+ It blocks iff an entry has `severity=high AND confirmed=false AND
36
+ acknowledged=false`.
37
+ - The auto-acknowledgment lifecycle lives in run.sh (NOT in the gate). Once an
38
+ assumption has been written into the ledger AND injected into the build prompt
39
+ at least once, run.sh marks it `acknowledged=true`. Default-on; opt-out
40
+ `LOKI_ASSUMPTIONS_REQUIRE_CONFIRM=1` keeps a human-in-the-loop path where only
41
+ `confirmed=true` clears the block.
42
+
43
+ This is the OPPOSITE of silent autocorrect: the assumption is recorded,
44
+ injected into the agent's prompt, and surfaced in proof-of-done. Acknowledgment
45
+ records "Loki has SEEN this gap and proceeded with a stated default", not "Loki
46
+ hid it". The gate still has teeth on the first iteration (high-sev unacknowledged
47
+ blocks) and in the require-confirm path.
48
+
49
+ ## Severity rule (deterministic, no LLM)
50
+
51
+ grill emits no severity. Classify by section / keyword on the read side:
52
+
53
+ - HIGH: security blind spots; scale/reliability blind spots; missing or
54
+ untestable acceptance criteria; any line containing contradiction keywords
55
+ (contradict, conflict, inconsistent, mutually exclusive).
56
+ - MEDIUM: ambiguities; unstated assumptions; underspecified behavior; all
57
+ prd-analyzer missing-dimension assumptions.
58
+
59
+ This guarantees a HIGH tier exists (so the gate has teeth) and is fully
60
+ deterministic (so tests are reproducible).
61
+
62
+ ## Taxonomy mapping (classification, read-side only)
63
+
64
+ grill section -> finding class:
65
+ - "Ambiguities and missing acceptance criteria" -> ambiguous (HIGH if the line
66
+ references acceptance criteria / testable / measurable; else MEDIUM)
67
+ - "Unstated assumptions" -> underspecified (MEDIUM)
68
+ - "Security blind spots" -> missing (HIGH)
69
+ - "Scale and reliability blind spots" -> missing (HIGH)
70
+ - any line with a contradiction keyword (any section) -> contradictory (HIGH)
71
+ - prd-analyzer missing dimensions -> missing (MEDIUM, deterministic default)
72
+
73
+ "None identified." lines are skipped (no fabricated findings).
74
+
75
+ grill output contract is NOT changed (it is parsed by the loki-grill skill).
76
+ We classify a COPY of its markdown; grill.sh stays byte-identical.
77
+
78
+ ## No-fabrication rule for ledger content
79
+
80
+ A grill finding is a QUESTION, not a resolution. The ledger `assumption` field
81
+ for a grill-derived gap is an honest "spec gives no answer; proceeding with the
82
+ implementer default for <area>" plus `affects=<area>`. We do NOT invent a
83
+ specific resolution the build will not actually follow. prd-analyzer assumptions
84
+ reuse its existing deterministic `_make_assumption()` text verbatim.
85
+
86
+ ## Ledger schema (`.loki/assumptions/`)
87
+
88
+ One JSON file per assumption: `.loki/assumptions/<id>.json`, plus a
89
+ human-readable `.loki/assumptions/ledger.md` rollup regenerated on each write.
90
+ Each entry:
91
+
92
+ ```json
93
+ {
94
+ "id": "a-0001",
95
+ "gap": "<the spec defect / unanswered question, verbatim>",
96
+ "assumption": "<honest stated default Loki proceeds with>",
97
+ "why": "<why this assumption / where the gap came from: grill|prd-analyzer>",
98
+ "severity": "high|medium",
99
+ "class": "ambiguous|contradictory|underspecified|missing",
100
+ "affects": "<area, e.g. security, acceptance-criteria, data-model>",
101
+ "source": "grill|prd-analyzer",
102
+ "confirmed": false,
103
+ "acknowledged": false,
104
+ "created_at": "<iso8601>"
105
+ }
106
+ ```
107
+
108
+ Stable id = `a-` + zero-padded counter over existing files (idempotent: a second
109
+ DISCOVERY run with the same findings does not duplicate; dedupe on the `gap`
110
+ text hash).
111
+
112
+ ## Build surface (files + functions)
113
+
114
+ 1. NEW `autonomy/spec-interrogation.sh` (sourced by run.sh; standalone-testable):
115
+ - `spec_interrogation_classify_report <report.md path>`: pure classifier.
116
+ Reads grill markdown, emits one TSV/JSON finding per question line with
117
+ class + severity. Takes a file so a fixture report drives the test with no
118
+ `claude` call.
119
+ - `spec_interrogation_severity_for <section> <line>`: deterministic severity.
120
+ - `spec_ledger_write <gap> <assumption> <why> <severity> <class> <affects>
121
+ <source>`: idempotent writer (dedupe on gap hash) -> `.loki/assumptions/`.
122
+ - `spec_ledger_rebuild_md`: regenerate `.loki/assumptions/ledger.md`.
123
+ - `spec_ledger_high_unresolved_count`: count entries with
124
+ `severity=high AND confirmed=false AND acknowledged=false` (gate input;
125
+ also reused by the summary).
126
+ - `spec_ledger_acknowledge_all`: set `acknowledged=true` on all entries
127
+ (auto-ack lifecycle helper; default path).
128
+ - `spec_interrogation_run <spec_path>`: orchestrator. Default-on
129
+ (`LOKI_SPEC_GRILL=0` opts out). Provider-aware: source grill.sh, call
130
+ `grill_check_provider`; if provider absent, log honest message, skip the
131
+ grill subcall (NO fabricated questions), but STILL fold prd-analyzer
132
+ missing-dimension assumptions into the ledger so degrade surfaces something
133
+ non-blocking. On provider present: run `grill_main` (writes report.md),
134
+ classify it, write ledger entries. Always non-fatal to the run.
135
+
136
+ 2. `autonomy/run.sh` DISCOVERY (~13056, after prd-analyzer + council_init,
137
+ before the iteration loop): source spec-interrogation.sh and call
138
+ `spec_interrogation_run "$prd_path"`. This is the grep-able grill invocation
139
+ the task requires. Best-effort (`|| true`), never blocks startup.
140
+
141
+ 3. `autonomy/run.sh` auto-ack lifecycle: after the build prompt is constructed
142
+ each iteration (assumptions are injected into the prompt via build_prompt),
143
+ call `spec_ledger_acknowledge_all` UNLESS
144
+ `LOKI_ASSUMPTIONS_REQUIRE_CONFIRM=1`. Inject the high-severity assumption
145
+ list into the build prompt (so the agent sees the gaps it must respect).
146
+
147
+ 4. `autonomy/completion-council.sh` `council_assumption_ledger_gate` (new),
148
+ slotted into `council_evaluate` right after `council_evidence_gate`
149
+ (mirrors 2510-2513). Same defensive `COUNCIL_STATE_DIR` default, opt-out
150
+ `LOKI_ASSUMPTION_GATE=0`. Blocks iff `spec_ledger_high_unresolved_count > 0`.
151
+ Writes `.loki/council/assumption-block.json` on block, removes it on pass.
152
+ Also wired into the completion-promise route in run.sh (~14525 pattern) and
153
+ the code_review gate chain (~15013) so the promise path cannot bypass it.
154
+
155
+ 5. `autonomy/run.sh` `build_completion_summary` (~2637): emit an
156
+ "Assumptions recorded: N (M high-severity)" block into COMPLETION.txt and the
157
+ ledger list, plus the count into completion.json. So "done" means "done, plus
158
+ here are the N places your spec was ambiguous and what I assumed."
159
+
160
+ 6. NEW `tests/test-spec-interrogation.sh` (bash convention, ok/bad counters,
161
+ source the module, mktemp fixtures):
162
+ - (a) classifier on a fixture grill report writes classified findings to the
163
+ ledger (ambiguous/contradictory/underspecified/missing + high/medium).
164
+ - (b) a ledger with one high/confirmed:false/acknowledged:false entry makes
165
+ `council_assumption_ledger_gate` return 1 (BLOCK) and write
166
+ assumption-block.json.
167
+ - (c) clean spec (no high-sev entries, or all acknowledged) -> gate returns 0
168
+ (no spurious block), no block file.
169
+ - (d) no provider -> `spec_interrogation_run` degrades cleanly: honest
170
+ message, prd-analyzer assumptions still folded (medium, non-blocking), run
171
+ proceeds, gate passes.
172
+
173
+ ## Gate reachability (resolved open question)
174
+
175
+ The existing gates fire from THREE sites: `council_evaluate` (~2510), the
176
+ completion-promise route (~14525), and the code_review gate chain (~15013). The
177
+ new gate is wired into all three so high-sev unacknowledged assumptions cannot
178
+ slip through the promise path.
179
+
180
+ ## Opt-out knobs (all default-on, intelligent)
181
+
182
+ - `LOKI_SPEC_GRILL=0` -> skip interrogation entirely.
183
+ - `LOKI_ASSUMPTION_GATE=0` -> gate is pass-through.
184
+ - `LOKI_ASSUMPTIONS_REQUIRE_CONFIRM=1` -> require human `confirmed=true`
185
+ (disables auto-ack); the human-in-the-loop path.
186
+
187
+ No "user must decide the type" knob. Classification + severity are automatic.
188
+
189
+ ## Constraints
190
+
191
+ No emojis, no em dashes, no version bump, no commit, no push. Provider-aware,
192
+ degrade cleanly, no fabricated questions when provider absent.
@@ -3,8 +3,8 @@
3
3
  Status: SEAMS implemented (this worktree). NOT a live hosted backend.
4
4
 
5
5
  R9 in the competitive-stickiness arc is the open-core monetization layer: keep
6
- Loki fully open source and free, while adding the SEAMS where hosted, enterprise,
7
- and paid plans would attach later. R9 ships the seams only. There is no Loki
6
+ Loki fully source-available (BUSL-1.1) and free to self-host, while adding the
7
+ SEAMS where hosted, enterprise, and paid plans would attach later. R9 ships the seams only. There is no Loki
8
8
  hosted service, no license-verification backend, and no paid gate on any
9
9
  existing feature. Every honest stub is labeled as such.
10
10
 
@@ -239,7 +239,7 @@ Loki Mode now incorporates proven patterns from Cursor's large-scale agent deplo
239
239
  3. **Anti-Sycophancy** - Blind review prevents false positives
240
240
  4. **Full SDLC** - Business, marketing, growth automation
241
241
  5. **Published Benchmarks** - Verify claims with reproducible tests
242
- 6. **MIT License** - More adoption-friendly
242
+ 6. **Source-available (BUSL-1.1)** - Inspect and self-host the full code
243
243
 
244
244
  ---
245
245
 
@@ -256,7 +256,7 @@ Loki Mode now incorporates proven patterns from Cursor's large-scale agent deplo
256
256
  - Full spec-to-product lifecycle (not just coding)
257
257
  - 41 specialized agent roles
258
258
  - Anti-sycophancy measures
259
- - MIT license
259
+ - Source-available (BUSL-1.1) license
260
260
  - No subscription requirement
261
261
  - Verified benchmarks
262
262
 
@@ -73,7 +73,7 @@ docs/certification/
73
73
 
74
74
  ## Cost and Licensing
75
75
 
76
- This certification program is **free and open source**, released under the same license as Loki Mode. No registration or payment is required.
76
+ This certification program is **free and source-available**, released under the same license as Loki Mode (BUSL-1.1). No registration or payment is required.
77
77
 
78
78
  ## Version
79
79
 
@@ -529,7 +529,7 @@ The "vibe coding" market -- AI tools that generate code from natural language --
529
529
  | Lovable | Lovable | $100M ARR in 8 months | Design-quality prototypes |
530
530
  | Vercel | v0 | Part of Vercel ($3.5B+) | UI component generator |
531
531
  | Replit | Replit | $1.16B valuation | Browser IDE + AI |
532
- | Autonomi | Loki Mode | Open source, early stage | PRD-to-production system |
532
+ | Autonomi | Loki Mode | Source-available (BUSL-1.1), early stage | PRD-to-production system |
533
533
 
534
534
  ### 11.4 Strategic Implication for Loki Mode
535
535
 
@@ -280,7 +280,7 @@ Developers who value open-source tooling, speed, and terminal-native workflows.
280
280
  | Feature | Emergence AI (Agent-E) | Rork | Claude Code CLI | Codex CLI | Loki Mode |
281
281
  |---------|:---------------------:|:----:|:--------------:|:---------:|:---------:|
282
282
  | **Primary Focus** | Web automation | Mobile apps | Coding assistant | Coding assistant | PRD-to-deploy |
283
- | **Open Source** | Partial (Agent-E only) | No | Source-available | Yes (Apache-2.0) | Yes |
283
+ | **Open Source / Source model** | Partial (Agent-E only) | No | Source-available | Yes (Apache-2.0) | Source-available (BUSL-1.1) |
284
284
  | **Multi-Provider** | Yes (OpenAI, Azure, Ollama) | Yes (Gemini, Claude) | Partial (Claude models via Bedrock/Vertex/Foundry) | No (GPT only) | Yes (5 providers, 3+ model families) |
285
285
  | **Multi-Agent** | Yes (2-agent model) | No | Yes (coordinated teams) | Yes (experimental) | Yes (41 agent types) |
286
286
  | **Autonomous Iteration** | No (task-level) | No | Partial (/loop, /schedule) | No (requires prompting) | Yes (RARV loop + completion council) |
@@ -454,7 +454,7 @@ This positioning highlights three unique capabilities no competitor offers toget
454
454
  | Dimension | Codex CLI | Loki Mode |
455
455
  |-----------|-----------|-----------|
456
456
  | Autonomy | Assisted (human prompts each task) | Fully autonomous |
457
- | Open source | Yes (Apache-2.0) | Yes |
457
+ | Source model | Open source (Apache-2.0) | Source-available (BUSL-1.1) |
458
458
  | Speed | 240+ tokens/sec | Depends on provider |
459
459
  | Providers | OpenAI only | 5 providers |
460
460
  | Multi-agent | Experimental (isolated) | 41 agent types, 8 domains |
@@ -467,8 +467,8 @@ This positioning highlights three unique capabilities no competitor offers toget
467
467
  | Focus | Web/workflow automation | Software development |
468
468
  | Pricing | Enterprise contracts | Free + API costs |
469
469
  | Self-hosted | VPC option | Fully self-hosted |
470
- | Open source | Partial | Yes |
471
- | **Loki Mode advantage:** | Purpose-built for software, open source, accessible pricing |
470
+ | Source-available | Partial | Yes (BUSL-1.1) |
471
+ | **Loki Mode advantage:** | Purpose-built for software, source-available, accessible pricing |
472
472
 
473
473
  #### vs. Rork
474
474
  | Dimension | Rork | Loki Mode |
@@ -485,7 +485,7 @@ This positioning highlights three unique capabilities no competitor offers toget
485
485
  "You already use AI for coding. Loki Mode makes it autonomous -- give it a PRD, and it handles planning, implementation, testing, code review, and deployment. Keep using Claude or Codex under the hood."
486
486
 
487
487
  **For engineering leaders evaluating AI tooling:**
488
- "Loki Mode is the only open-source system with enterprise-grade quality gates (8 gates, 3-reviewer blind review, anti-sycophancy checks) that runs autonomously on any AI provider. Self-hosted, no vendor lock-in."
488
+ "Loki Mode is the only source-available system with enterprise-grade quality gates (8 gates, 3-reviewer blind review, anti-sycophancy checks) that runs autonomously on any AI provider. Self-hosted, no vendor lock-in."
489
489
 
490
490
  **For startups and solo developers:**
491
491
  "Go from idea to deployed product overnight. Write a PRD, invoke Loki Mode, and let it build, test, and deploy while you sleep. Works with your existing Claude or OpenAI API key."
@@ -558,7 +558,7 @@ The most significant near-term competitive threat is Anthropic's Agent SDK (http
558
558
  **However, Loki Mode's structural advantages remain:**
559
559
  1. **Multi-provider:** Agent SDK is Claude-only. Loki Mode works with any provider.
560
560
  2. **Battle-tested pipeline:** 8 quality gates, completion council, healing, memory -- these took months to build and validate. A new Agent SDK project starts from zero.
561
- 3. **Open source and self-hosted:** No dependency on Anthropic's platform decisions.
561
+ 3. **Source-available and self-hosted:** No dependency on Anthropic's platform decisions.
562
562
  4. **Research foundation:** Built on patterns from OpenAI, DeepMind, Anthropic, and academic research. Not just engineering, but applied AI safety research (Constitutional AI, anti-sycophancy, alignment faking detection).
563
563
 
564
564
  ---
@@ -26,8 +26,8 @@ Replit and Lovable represent two dominant players in the "vibe coding" / AI app
26
26
 
27
27
  | Metric | Replit | Lovable | Loki Mode |
28
28
  |--------|--------|---------|-----------|
29
- | Valuation | $9B (Mar 2026) | $6.6B (Dec 2025) | Open source |
30
- | ARR | $265M+ (targeting $1B) | $400M+ (Feb 2026) | N/A (open source) |
29
+ | Valuation | $9B (Mar 2026) | $6.6B (Dec 2025) | Source-available (BUSL-1.1) |
30
+ | ARR | $265M+ (targeting $1B) | $400M+ (Feb 2026) | N/A (source-available) |
31
31
  | Users | 50M+ registered | 8M+ users | Developer community |
32
32
  | Total Funding | $650M+ | $653M | $0 |
33
33
  | Employees | ~1,000+ | ~817 | Solo maintainer |
@@ -376,7 +376,7 @@ Replit Agent has evolved rapidly through four major versions:
376
376
 
377
377
  | Metric | Replit Agent | Lovable.dev | Loki Mode |
378
378
  |--------|:-----------:|:-----------:|:---------:|
379
- | Free tier | Yes (limited) | Yes (30 credits/mo) | Yes (open source, free) |
379
+ | Free tier | Yes (limited) | Yes (30 credits/mo) | Yes (source-available, free to self-host) |
380
380
  | Entry paid | $20/mo (Core) | $25/mo (Pro) | $0 (bring your own API key) |
381
381
  | Team plan | $100/mo (Pro) | $50/mo (Business) | $0 |
382
382
  | Cost model | Effort-based credits | Per-prompt credits | API key costs only |
@@ -585,7 +585,7 @@ The term "vibe coding" (coined by Andrej Karpathy) has driven explosive growth i
585
585
 
586
586
  **Medium-term (12 months):** Moderate threat -- as Replit/Lovable improve production quality and add enterprise features, they will attract more professional developers. Loki Mode must ship deployment, dashboard, and Figma integration.
587
587
 
588
- **Long-term (24 months):** High convergence risk -- all platforms will trend toward autonomous, production-grade, multi-agent systems. Loki Mode's quality gates, memory system, and brownfield support will be critical differentiators. The open-source model is an enduring advantage if the community grows.
588
+ **Long-term (24 months):** High convergence risk -- all platforms will trend toward autonomous, production-grade, multi-agent systems. Loki Mode's quality gates, memory system, and brownfield support will be critical differentiators. The source-available, self-hostable model is an enduring advantage if the community grows.
589
589
 
590
590
  ---
591
591
 
@@ -68,11 +68,51 @@ For organizations using identity providers (Okta, Auth0, Azure AD), Loki Mode su
68
68
  ```bash
69
69
  export LOKI_OIDC_ISSUER="https://your-idp.okta.com/oauth2/default"
70
70
  export LOKI_OIDC_CLIENT_ID="your-client-id"
71
- export LOKI_OIDC_CLIENT_SECRET="your-client-secret"
72
- export LOKI_OIDC_REDIRECT_URI="http://localhost:57374/auth/callback"
71
+ # Optional: audience override (defaults to LOKI_OIDC_CLIENT_ID)
72
+ export LOKI_OIDC_AUDIENCE="your-api-audience"
73
73
  ```
74
74
 
75
- The system validates OIDC tokens against the issuer's JWKS endpoint and maps claims to Loki Mode roles.
75
+ The system validates OIDC bearer tokens (presented as `Authorization: Bearer <jwt>`)
76
+ against the issuer's JWKS endpoint. When PyJWT + cryptography are installed, the
77
+ JWT signature is cryptographically verified (RS256/RS384/RS512) along with the
78
+ issuer, audience, and expiry. Without PyJWT, tokens are rejected unless
79
+ `LOKI_OIDC_SKIP_SIGNATURE_VERIFY=true` is set explicitly (insecure, local testing
80
+ only).
81
+
82
+ **Claims-to-roles mapping:**
83
+
84
+ On a valid token, role/group claims are mapped to the four built-in Loki roles
85
+ (`admin`, `operator`, `viewer`, `auditor`). The resolved role's scopes are then
86
+ enforced through the same scope hierarchy used for API tokens. OIDC users are
87
+ NOT granted full access by default.
88
+
89
+ Recognized claim sources (case-insensitive; values may be a string,
90
+ space-separated string, or list):
91
+
92
+ | Claim | Provider |
93
+ |-------|----------|
94
+ | value of `LOKI_OIDC_ROLES_CLAIM` (supports dotted paths, e.g. `realm_access.roles`) | configurable |
95
+ | `roles` | generic |
96
+ | `groups` | generic |
97
+ | `realm_access.roles` | Keycloak |
98
+ | `cognito:groups` | AWS Cognito |
99
+
100
+ Only claim values that exactly match a built-in role name (`admin`, `operator`,
101
+ `viewer`, `auditor`) grant that role. Arbitrary group names (common in `groups`
102
+ or `cognito:groups`) do not map to a role and fall through to the default.
103
+
104
+ When multiple recognized roles are present, the highest-privilege match wins
105
+ (precedence: `admin` > `operator` > `auditor` > `viewer`).
106
+
107
+ **Default role (when no recognized role claim is present):**
108
+
109
+ ```bash
110
+ # Defaults to the least-privileged role (viewer). Never admin.
111
+ export LOKI_OIDC_DEFAULT_ROLE="viewer"
112
+ ```
113
+
114
+ If `LOKI_OIDC_DEFAULT_ROLE` is unset or set to an unrecognized value, it falls
115
+ back to `viewer`. The default is never `admin` / full access.
76
116
 
77
117
  ## Authorization
78
118
 
@@ -24,7 +24,7 @@ I built Loki Mode because I got tired of the copy-paste loop between AI coding a
24
24
 
25
25
  **Test suite:** 683 npm tests, 631 pytest tests, 16 shell tests. Self-reported HumanEval score of 162/164 (98.78%).
26
26
 
27
- Built solo. MIT licensed.
27
+ Built solo. BUSL-1.1 source-available.
28
28
 
29
29
  ## Try it
30
30
 
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var n6=Object.defineProperty;var a6=($)=>$;function s6($,Q){this[$]=a6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)n6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:s6.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var K$=import.meta.require;var S1={};h(S1,{lokiDir:()=>P,homeLokiDir:()=>o$,findRepoRootForVersion:()=>d$,REPO_ROOT:()=>m});import{resolve as n,dirname as l$}from"path";import{fileURLToPath as t6}from"url";import{existsSync as P$}from"fs";import{homedir as r6}from"os";function i6(){let $=N1;for(let Q=0;Q<6;Q++){if(P$(n($,"VERSION"))&&P$(n($,"autonomy/run.sh")))return $;let Z=l$($);if(Z===$)break;$=Z}return n(N1,"..","..","..")}function d$($){let Q=$;for(let Z=0;Z<6;Z++){if(P$(n(Q,"VERSION"))&&P$(n(Q,"autonomy/run.sh")))return Q;let z=l$(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function o$(){return n(r6(),".loki")}var N1,m;var C=L(()=>{N1=l$(t6(import.meta.url));m=i6()});import{readFileSync as e6}from"fs";import{resolve as $Q,dirname as QQ}from"path";import{fileURLToPath as ZQ}from"url";function F$(){if($$!==null)return $$;let $="7.46.0";if(typeof $==="string"&&$.length>0)return $$=$,$$;try{let Q=QQ(ZQ(import.meta.url)),Z=d$(Q);$$=e6($Q(Z,"VERSION"),"utf-8").trim()}catch{$$="unknown"}return $$}var $$=null;var n$=L(()=>{C()});var C1={};h(C1,{runOrThrow:()=>zQ,run:()=>j,commandVersion:()=>KQ,commandExists:()=>f,ShellError:()=>a$});async function j($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,X;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[W,K,U]=await Promise.all([new Response(Z.stdout).text(),new Response(Z.stderr).text(),Z.exited]);return{stdout:W,stderr:K,exitCode:U}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function zQ($,Q={}){let Z=await j($,Q);if(Z.exitCode!==0)throw new a$(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=XQ($),Z=await j(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function XQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function KQ($,Q="--version"){if(!await f($))return null;let z=await j([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var a$;var d=L(()=>{a$=class a$ extends Error{message;exitCode;stdout;stderr;constructor($,Q,Z,z){super($);this.message=$;this.exitCode=Q;this.stdout=Z;this.stderr=z;this.name="ShellError"}}});function a($){return WQ?"":$}var WQ,T,S,I,TZ,w,R,y,q;var c=L(()=>{WQ=(process.env.NO_COLOR??"").length>0;T=a("\x1B[0;31m"),S=a("\x1B[0;32m"),I=a("\x1B[1;33m"),TZ=a("\x1B[0;34m"),w=a("\x1B[0;36m"),R=a("\x1B[1m"),y=a("\x1B[2m"),q=a("\x1B[0m")});import{existsSync as TQ}from"fs";async function Q$(){if(B$!==void 0)return B$;let $="/opt/homebrew/bin/python3.12";if(TQ($))return B$=$,$;let Q=await f("python3.12");if(Q)return B$=Q,Q;let Z=await f("python3");return B$=Z,Z}async function Z$($,Q={}){let Z=await Q$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return j([Z,"-c",$],Q)}var B$;var W$=L(()=>{d()});var t1={};h(t1,{runStatus:()=>gQ});import{existsSync as v,readFileSync as U$,readdirSync as l1,statSync as d1}from"fs";import{resolve as D,basename as xQ}from"path";import{homedir as NQ}from"os";async function DQ(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${q}
2
+ var n6=Object.defineProperty;var a6=($)=>$;function s6($,Q){this[$]=a6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)n6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:s6.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var K$=import.meta.require;var S1={};h(S1,{lokiDir:()=>P,homeLokiDir:()=>o$,findRepoRootForVersion:()=>d$,REPO_ROOT:()=>m});import{resolve as n,dirname as l$}from"path";import{fileURLToPath as t6}from"url";import{existsSync as P$}from"fs";import{homedir as r6}from"os";function i6(){let $=N1;for(let Q=0;Q<6;Q++){if(P$(n($,"VERSION"))&&P$(n($,"autonomy/run.sh")))return $;let Z=l$($);if(Z===$)break;$=Z}return n(N1,"..","..","..")}function d$($){let Q=$;for(let Z=0;Z<6;Z++){if(P$(n(Q,"VERSION"))&&P$(n(Q,"autonomy/run.sh")))return Q;let z=l$(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function o$(){return n(r6(),".loki")}var N1,m;var C=L(()=>{N1=l$(t6(import.meta.url));m=i6()});import{readFileSync as e6}from"fs";import{resolve as $Q,dirname as QQ}from"path";import{fileURLToPath as ZQ}from"url";function F$(){if($$!==null)return $$;let $="7.47.0";if(typeof $==="string"&&$.length>0)return $$=$,$$;try{let Q=QQ(ZQ(import.meta.url)),Z=d$(Q);$$=e6($Q(Z,"VERSION"),"utf-8").trim()}catch{$$="unknown"}return $$}var $$=null;var n$=L(()=>{C()});var C1={};h(C1,{runOrThrow:()=>zQ,run:()=>j,commandVersion:()=>KQ,commandExists:()=>f,ShellError:()=>a$});async function j($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,X;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[W,K,U]=await Promise.all([new Response(Z.stdout).text(),new Response(Z.stderr).text(),Z.exited]);return{stdout:W,stderr:K,exitCode:U}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function zQ($,Q={}){let Z=await j($,Q);if(Z.exitCode!==0)throw new a$(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=XQ($),Z=await j(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function XQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function KQ($,Q="--version"){if(!await f($))return null;let z=await j([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var a$;var d=L(()=>{a$=class a$ extends Error{message;exitCode;stdout;stderr;constructor($,Q,Z,z){super($);this.message=$;this.exitCode=Q;this.stdout=Z;this.stderr=z;this.name="ShellError"}}});function a($){return WQ?"":$}var WQ,T,S,I,TZ,w,R,y,q;var c=L(()=>{WQ=(process.env.NO_COLOR??"").length>0;T=a("\x1B[0;31m"),S=a("\x1B[0;32m"),I=a("\x1B[1;33m"),TZ=a("\x1B[0;34m"),w=a("\x1B[0;36m"),R=a("\x1B[1m"),y=a("\x1B[2m"),q=a("\x1B[0m")});import{existsSync as TQ}from"fs";async function Q$(){if(B$!==void 0)return B$;let $="/opt/homebrew/bin/python3.12";if(TQ($))return B$=$,$;let Q=await f("python3.12");if(Q)return B$=Q,Q;let Z=await f("python3");return B$=Z,Z}async function Z$($,Q={}){let Z=await Q$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return j([Z,"-c",$],Q)}var B$;var W$=L(()=>{d()});var t1={};h(t1,{runStatus:()=>gQ});import{existsSync as v,readFileSync as U$,readdirSync as l1,statSync as d1}from"fs";import{resolve as D,basename as xQ}from"path";import{homedir as NQ}from"os";async function DQ(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${q}
3
3
  `),process.stdout.write(`Install with:
4
4
  `),process.stdout.write(` brew install jq (macOS)
5
5
  `),process.stdout.write(` apt install jq (Debian/Ubuntu)
@@ -789,4 +789,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
789
789
  `),2}default:return process.stderr.write(`Unknown command: ${Q}
790
790
  `),process.stderr.write(o6),2}}p1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var ZZ=await QZ(Bun.argv.slice(2));process.exit(ZZ);
791
791
 
792
- //# debugId=7B01911F8947B6CD64756E2164756E21
792
+ //# debugId=3DD935606FD979EE64756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.46.0'
60
+ __version__ = '7.47.0'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "loki-mode",
3
3
  "mcpName": "io.github.asklokesh/loki-mode",
4
- "version": "7.46.0",
4
+ "version": "7.47.0",
5
5
  "description": "Loki Mode by Autonomi. Autonomous spec-to-product system: takes a PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief to a deployed app via the RARV-C closure loop with 8 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
6
6
  "keywords": [
7
7
  "agent",