loki-mode 7.7.24 → 7.7.26
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/run.sh +2 -2
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +51 -4
- package/dashboard/static/index.html +2 -2
- package/docs/INSTALLATION.md +2 -2
- package/docs/auto-claude-comparison.md +3 -3
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +3 -2
- package/tools/bench_cross_project_lift.py +218 -0
- package/tools/bench_memory_retrieval.py +157 -0
- package/tools/index-codebase.py +474 -0
- package/tools/probe-model-catalog.py +159 -0
- package/tools/regen-state-machine-refs.py +188 -0
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Autonomous spec-to-product system. Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product via the RARV-C closure loop, with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v7.7.
|
|
6
|
+
# Loki Mode v7.7.26
|
|
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.7.
|
|
384
|
+
**v7.7.26 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.7.
|
|
1
|
+
7.7.26
|
package/autonomy/run.sh
CHANGED
|
@@ -4405,7 +4405,7 @@ generate_dashboard() {
|
|
|
4405
4405
|
<body>
|
|
4406
4406
|
<div class="header">
|
|
4407
4407
|
<h1>LOKI MODE</h1>
|
|
4408
|
-
<div class="subtitle">Autonomous
|
|
4408
|
+
<div class="subtitle">Autonomous Spec-to-Product System</div>
|
|
4409
4409
|
<div class="phase" id="phase">Loading...</div>
|
|
4410
4410
|
</div>
|
|
4411
4411
|
<div class="stats">
|
|
@@ -12379,7 +12379,7 @@ main() {
|
|
|
12379
12379
|
echo " ███████╗╚██████╔╝██║ ██╗██║ ██║ ╚═╝ ██║╚██████╔╝██████╔╝███████╗"
|
|
12380
12380
|
echo " ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝"
|
|
12381
12381
|
echo -e "${NC}"
|
|
12382
|
-
echo -e " ${CYAN}Autonomous
|
|
12382
|
+
echo -e " ${CYAN}Autonomous Spec-to-Product System${NC}"
|
|
12383
12383
|
echo -e " ${CYAN}Version: $(cat "$PROJECT_DIR/VERSION" 2>/dev/null || echo "4.x.x")${NC}"
|
|
12384
12384
|
echo ""
|
|
12385
12385
|
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -2751,14 +2751,61 @@ async def get_token_economics():
|
|
|
2751
2751
|
|
|
2752
2752
|
@app.post("/api/memory/consolidate", dependencies=[Depends(auth.require_scope("control"))])
|
|
2753
2753
|
async def consolidate_memory(hours: int = 24):
|
|
2754
|
-
"""
|
|
2755
|
-
|
|
2754
|
+
"""Run the real episodic-to-semantic consolidation pipeline."""
|
|
2755
|
+
memory_dir = _get_loki_dir() / "memory"
|
|
2756
|
+
try:
|
|
2757
|
+
import sys as _sys
|
|
2758
|
+
project_root = str(_Path(__file__).resolve().parent.parent)
|
|
2759
|
+
if project_root not in _sys.path:
|
|
2760
|
+
_sys.path.insert(0, project_root)
|
|
2761
|
+
from memory.storage import MemoryStorage
|
|
2762
|
+
from memory.consolidation import ConsolidationPipeline
|
|
2763
|
+
storage = MemoryStorage(str(memory_dir))
|
|
2764
|
+
pipeline = ConsolidationPipeline(storage=storage, base_path=str(memory_dir))
|
|
2765
|
+
result = pipeline.consolidate(since_hours=hours)
|
|
2766
|
+
d = result.to_dict()
|
|
2767
|
+
return {
|
|
2768
|
+
"status": "ok",
|
|
2769
|
+
"message": f"Consolidated episodes from the last {hours}h",
|
|
2770
|
+
"consolidated": d.get("patterns_created", 0) + d.get("patterns_merged", 0),
|
|
2771
|
+
"patternsCreated": d.get("patterns_created", 0),
|
|
2772
|
+
"patternsMerged": d.get("patterns_merged", 0),
|
|
2773
|
+
"antiPatternsCreated": d.get("anti_patterns_created", 0),
|
|
2774
|
+
"episodesProcessed": d.get("episodes_processed", 0),
|
|
2775
|
+
"durationSeconds": round(d.get("duration_seconds", 0.0), 3),
|
|
2776
|
+
}
|
|
2777
|
+
except Exception as e:
|
|
2778
|
+
raise HTTPException(status_code=503, detail=f"Consolidation unavailable: {e}")
|
|
2756
2779
|
|
|
2757
2780
|
|
|
2758
2781
|
@app.post("/api/memory/retrieve", dependencies=[Depends(auth.require_scope("control"))])
|
|
2759
2782
|
async def retrieve_memory(query: dict = None):
|
|
2760
|
-
"""
|
|
2761
|
-
|
|
2783
|
+
"""Task-aware retrieval against the real memory engine.
|
|
2784
|
+
|
|
2785
|
+
Body: {"goal": str, "phase"?: str, "task_type"?: str, "top_k"?: int}.
|
|
2786
|
+
"""
|
|
2787
|
+
query = query or {}
|
|
2788
|
+
goal = (query.get("goal") or query.get("q") or "").strip()
|
|
2789
|
+
if not goal:
|
|
2790
|
+
return {"results": [], "query": query, "message": "provide a 'goal' to retrieve against"}
|
|
2791
|
+
top_k = int(query.get("top_k", 5))
|
|
2792
|
+
top_k = max(1, min(top_k, 50))
|
|
2793
|
+
memory_dir = _get_loki_dir() / "memory"
|
|
2794
|
+
try:
|
|
2795
|
+
import sys as _sys
|
|
2796
|
+
project_root = str(_Path(__file__).resolve().parent.parent)
|
|
2797
|
+
if project_root not in _sys.path:
|
|
2798
|
+
_sys.path.insert(0, project_root)
|
|
2799
|
+
from memory.storage import MemoryStorage
|
|
2800
|
+
from memory.retrieval import MemoryRetrieval
|
|
2801
|
+
retriever = MemoryRetrieval(MemoryStorage(str(memory_dir)))
|
|
2802
|
+
context = {"goal": goal, "phase": query.get("phase", "development")}
|
|
2803
|
+
if query.get("task_type"):
|
|
2804
|
+
context["task_type"] = query["task_type"]
|
|
2805
|
+
results = retriever.retrieve_task_aware(context, top_k=top_k, token_budget=query.get("token_budget"))
|
|
2806
|
+
return {"results": results, "query": {"goal": goal, "top_k": top_k}, "count": len(results)}
|
|
2807
|
+
except Exception as e:
|
|
2808
|
+
raise HTTPException(status_code=503, detail=f"Retrieval unavailable: {e}")
|
|
2762
2809
|
|
|
2763
2810
|
|
|
2764
2811
|
@app.get("/api/memory/index")
|
|
@@ -13195,7 +13195,7 @@ var LokiDashboard=(()=>{var wt=Object.defineProperty;var ae=Object.getOwnPropert
|
|
|
13195
13195
|
border-radius: 4px;
|
|
13196
13196
|
}
|
|
13197
13197
|
</style>
|
|
13198
|
-
`,a="";this._loading&&this._items.length===0?a='<div class="esc-empty">Loading escalations...</div>':this._error?a='<div class="esc-error">Failed to load escalations: '+this._escapeHtml(this._error)+"</div>":!this._items||this._items.length===0?a='<div class="esc-empty">Escalations: no events yet. Handoff/escalation markdown documents written by the runner under .loki/escalations/ will appear here.</div>':a='<div class="esc-list">'+this._items.map(n=>{let l=this._escapeHtml(n.filename||""),c=this._escapeHtml(this._formatSize(n.size_bytes)),p=this._escapeHtml(this._formatDate(n.modified_at));return'<div class="esc-item" data-filename="'+l+'"><span class="esc-name">'+l+'</span><span class="esc-meta">'+c+" · "+p+"</span></div>"}).join("")+"</div>";let i="";if(this._activeFile){let o=this._escapeHtml(this._activeFile),n;this._activeBodyError?n='<div class="esc-error">Failed to load: '+this._escapeHtml(this._activeBodyError)+"</div>":this._activeBody===null?n='<div class="esc-body">Loading '+o+"...</div>":n='<div class="esc-body">'+this._escapeHtml(this._activeBody)+"</div>",i='<div class="esc-viewer"><div class="esc-viewer-header"><span class="esc-name">'+o+'</span><button class="esc-close-btn" data-action="close">Close</button></div>'+n+"</div>"}t.innerHTML=e+'<div class="esc-wrapper"><div class="esc-explain">Handoff/escalation documents written under .loki/escalations/. Click an entry to view its contents.</div>'+a+i+"</div>",t.querySelectorAll(".esc-item").forEach(o=>{o.addEventListener("click",()=>{let n=o.getAttribute("data-filename");n&&this._openFile(n)})});let r=t.querySelector('.esc-close-btn[data-action="close"]');r&&r.addEventListener("click",()=>this._closeFile())}};typeof customElements<"u"&&!customElements.get("loki-escalations")&&customElements.define("loki-escalations",_t);var yt=class extends h{static get observedAttributes(){return["api-url","theme"]}constructor(){super(),this._transcripts=[],this._loading=!1,this._error=null,this._api=null,this._pollInterval=null}connectedCallback(){super.connectedCallback(),this._setupApi(),this._load(),this._pollInterval=setInterval(()=>this._load(),3e4)}disconnectedCallback(){super.disconnectedCallback(),this._pollInterval&&(clearInterval(this._pollInterval),this._pollInterval=null)}attributeChangedCallback(t,e,a){e!==a&&(t==="api-url"&&this._api&&(this._api.baseUrl=a,this._load()),t==="theme"&&this._applyTheme())}_setupApi(){let t=this.getAttribute("api-url")||(typeof window<"u"?window.location.origin:"");this._api=g({baseUrl:t})}async _load(){this._loading=!0,this._error=null;try{let t=await this._api.get("/api/council/transcripts?limit=10");this._transcripts=Array.isArray(t&&t.transcripts)?t.transcripts:[]}catch(t){this._error=t&&t.message?t.message:String(t),this._transcripts=[]}finally{this._loading=!1,this.render()}}_escapeHtml(t){return t==null?"":String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}_formatTimestamp(t){if(!t)return"--";try{let e=new Date(t);return isNaN(e.getTime())?t:e.toLocaleString()}catch{return t}}_truncate(t,e){if(!t)return"";let a=String(t);return a.length>e?a.slice(0,e)+"...":a}_verdictBadgeHtml(t){let e=String(t||"").toUpperCase();return e==="APPROVE"?'<span class="ct-badge ct-badge-approve">APPROVE</span>':e==="REJECT"?'<span class="ct-badge ct-badge-reject">REJECT</span>':e==="CANNOT_VALIDATE"?'<span class="ct-badge ct-badge-cannot">CANNOT_VALIDATE</span>':'<span class="ct-badge ct-badge-unknown">'+this._escapeHtml(e||"UNKNOWN")+"</span>"}_outcomeBadgeHtml(t){let e=String(t||"").toUpperCase();return e==="APPROVED"?'<span class="ct-badge ct-badge-approve">APPROVED</span>':e==="REJECTED"?'<span class="ct-badge ct-badge-reject">REJECTED</span>':e==="BLOCKED_BY_GATE"?'<span class="ct-badge ct-badge-blocked">BLOCKED BY GATE</span>':'<span class="ct-badge ct-badge-unknown">'+this._escapeHtml(e||"UNKNOWN")+"</span>"}_voterRowHtml(t,e){let a=t.is_contrarian===!0,i=a&&e===!0,s="ct-voter-row";a&&(s+=" ct-voter-contrarian"),i&&(s+=" ct-voter-flipped");let r=this._escapeHtml(t.name||"unknown"),o=this._verdictBadgeHtml(t.verdict),n=this._escapeHtml(this._truncate(t.reasoning,300)),l="",c="";i?(l='<span class="ct-badge ct-badge-override">OVERRIDE</span>',c=`<div class="ct-flip-caption">Devil's Advocate flipped this outcome</div>`):a&&t.triggered&&(l=`<span class="ct-badge ct-badge-da">DEVIL'S ADVOCATE</span>`);let p="";a&&Array.isArray(t.challenges)&&t.challenges.length>0&&(p='<ul class="ct-challenges">'+t.challenges.map(m=>"<li>"+this._escapeHtml(String(m))+"</li>").join("")+"</ul>");let u="";return Array.isArray(t.issues)&&t.issues.length>0&&(u='<ul class="ct-issues">'+t.issues.map(m=>{let f=this._escapeHtml(m.severity||""),x=this._escapeHtml(m.description||"");return'<li><span class="ct-issue-sev ct-issue-sev-'+f.toLowerCase()+'">'+f+"</span> "+x+"</li>"}).join("")+"</ul>"),'<div class="'+s+'"><div class="ct-voter-header"><span class="ct-voter-name">'+r+"</span>"+o+l+"</div>"+(n?'<div class="ct-voter-reason">'+n+"</div>":"")+p+u+c+"</div>"}_transcriptCardHtml(t){let e=this._escapeHtml(String(t.iteration||"--")),a=this._escapeHtml(this._formatTimestamp(t.timestamp)),i=this._escapeHtml(this._truncate(t.task_or_prd,200)),s=this._outcomeBadgeHtml(t.outcome),r=Array.isArray(t.voters)?t.voters:[],o=r.filter(m=>!m.is_contrarian),n=r.filter(m=>m.is_contrarian),l=o.map(m=>this._voterRowHtml(m,!1)).join(""),c="";t.contrarian_triggered&&(c='<div class="ct-contrarian-section"><div class="ct-section-label">Anti-Sycophancy Check</div>'+n.map(f=>this._voterRowHtml(f,t.contrarian_flipped)).join("")+"</div>");let p=typeof t.approve_count=="number"?t.approve_count:"--",u=typeof t.reject_count=="number"?t.reject_count:"--",b=typeof t.threshold=="number"?t.threshold:"--";return'<div class="ct-card"><div class="ct-card-header"><div class="ct-card-meta"><span class="ct-iter-label">Iteration '+e+'</span><span class="ct-ts">'+a+"</span></div>"+s+"</div>"+(i?'<div class="ct-prd-preview">'+i+"</div>":"")+'<div class="ct-tally">Approve: '+p+" · Reject: "+u+" · Threshold: "+b+'</div><div class="ct-voters">'+l+"</div>"+c+"</div>"}render(){let t=this.shadowRoot||this;if(!t)return;let e=`
|
|
13198
|
+
`,a="";this._loading&&this._items.length===0?a='<div class="esc-empty">Loading escalations...</div>':this._error?a='<div class="esc-error">Failed to load escalations: '+this._escapeHtml(this._error)+"</div>":!this._items||this._items.length===0?a='<div class="esc-empty">Escalations: no events yet. Handoff/escalation markdown documents written by the runner under .loki/escalations/ will appear here.</div>':a='<div class="esc-list">'+this._items.map(n=>{let l=this._escapeHtml(n.filename||""),c=this._escapeHtml(this._formatSize(n.size_bytes)),p=this._escapeHtml(this._formatDate(n.modified_at));return'<div class="esc-item" data-filename="'+l+'"><span class="esc-name">'+l+'</span><span class="esc-meta">'+c+" · "+p+"</span></div>"}).join("")+"</div>";let i="";if(this._activeFile){let o=this._escapeHtml(this._activeFile),n;this._activeBodyError?n='<div class="esc-error">Failed to load: '+this._escapeHtml(this._activeBodyError)+"</div>":this._activeBody===null?n='<div class="esc-body">Loading '+o+"...</div>":n='<div class="esc-body">'+this._escapeHtml(this._activeBody)+"</div>",i='<div class="esc-viewer"><div class="esc-viewer-header"><span class="esc-name">'+o+'</span><button class="esc-close-btn" data-action="close">Close</button></div>'+n+"</div>"}t.innerHTML=e+'<div class="esc-wrapper"><div class="esc-explain">Handoff/escalation documents written under .loki/escalations/. Click an entry to view its contents.</div>'+a+i+"</div>",t.querySelectorAll(".esc-item").forEach(o=>{o.addEventListener("click",()=>{let n=o.getAttribute("data-filename");n&&this._openFile(n)})});let r=t.querySelector('.esc-close-btn[data-action="close"]');r&&r.addEventListener("click",()=>this._closeFile())}};typeof customElements<"u"&&!customElements.get("loki-escalations")&&customElements.define("loki-escalations",_t);var yt=class extends h{static get observedAttributes(){return["api-url","theme"]}constructor(){super(),this._transcripts=[],this._hookEvents=[],this._loading=!1,this._error=null,this._api=null,this._pollInterval=null}connectedCallback(){super.connectedCallback(),this._setupApi(),this._load(),this._pollInterval=setInterval(()=>this._load(),3e4)}disconnectedCallback(){super.disconnectedCallback(),this._pollInterval&&(clearInterval(this._pollInterval),this._pollInterval=null)}attributeChangedCallback(t,e,a){e!==a&&(t==="api-url"&&this._api&&(this._api.baseUrl=a,this._load()),t==="theme"&&this._applyTheme())}_setupApi(){let t=this.getAttribute("api-url")||(typeof window<"u"?window.location.origin:"");this._api=g({baseUrl:t})}async _load(){this._loading=!0,this._error=null;try{let t=await this._api.get("/api/council/transcripts?limit=10");this._transcripts=Array.isArray(t&&t.transcripts)?t.transcripts:[]}catch(t){this._error=t&&t.message?t.message:String(t),this._transcripts=[]}try{let t=await this._api.get("/api/council/transcripts?limit=20&type_prefix=claude_hook_");this._hookEvents=Array.isArray(t&&t.hook_events)?t.hook_events:[]}catch{this._hookEvents=[]}finally{this._loading=!1,this.render()}}_escapeHtml(t){return t==null?"":String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}_formatTimestamp(t){if(!t)return"--";try{let e=new Date(t);return isNaN(e.getTime())?t:e.toLocaleString()}catch{return t}}_truncate(t,e){if(!t)return"";let a=String(t);return a.length>e?a.slice(0,e)+"...":a}_verdictBadgeHtml(t){let e=String(t||"").toUpperCase();return e==="APPROVE"?'<span class="ct-badge ct-badge-approve">APPROVE</span>':e==="REJECT"?'<span class="ct-badge ct-badge-reject">REJECT</span>':e==="CANNOT_VALIDATE"?'<span class="ct-badge ct-badge-cannot">CANNOT_VALIDATE</span>':'<span class="ct-badge ct-badge-unknown">'+this._escapeHtml(e||"UNKNOWN")+"</span>"}_outcomeBadgeHtml(t){let e=String(t||"").toUpperCase();return e==="APPROVED"?'<span class="ct-badge ct-badge-approve">APPROVED</span>':e==="REJECTED"?'<span class="ct-badge ct-badge-reject">REJECTED</span>':e==="BLOCKED_BY_GATE"?'<span class="ct-badge ct-badge-blocked">BLOCKED BY GATE</span>':'<span class="ct-badge ct-badge-unknown">'+this._escapeHtml(e||"UNKNOWN")+"</span>"}_voterRowHtml(t,e){let a=t.is_contrarian===!0,i=a&&e===!0,s="ct-voter-row";a&&(s+=" ct-voter-contrarian"),i&&(s+=" ct-voter-flipped");let r=this._escapeHtml(t.name||"unknown"),o=this._verdictBadgeHtml(t.verdict),n=this._escapeHtml(this._truncate(t.reasoning,300)),l="",c="";i?(l='<span class="ct-badge ct-badge-override">OVERRIDE</span>',c=`<div class="ct-flip-caption">Devil's Advocate flipped this outcome</div>`):a&&t.triggered&&(l=`<span class="ct-badge ct-badge-da">DEVIL'S ADVOCATE</span>`);let p="";a&&Array.isArray(t.challenges)&&t.challenges.length>0&&(p='<ul class="ct-challenges">'+t.challenges.map(m=>"<li>"+this._escapeHtml(String(m))+"</li>").join("")+"</ul>");let u="";return Array.isArray(t.issues)&&t.issues.length>0&&(u='<ul class="ct-issues">'+t.issues.map(m=>{let f=this._escapeHtml(m.severity||""),x=this._escapeHtml(m.description||"");return'<li><span class="ct-issue-sev ct-issue-sev-'+f.toLowerCase()+'">'+f+"</span> "+x+"</li>"}).join("")+"</ul>"),'<div class="'+s+'"><div class="ct-voter-header"><span class="ct-voter-name">'+r+"</span>"+o+l+"</div>"+(n?'<div class="ct-voter-reason">'+n+"</div>":"")+p+u+c+"</div>"}_transcriptCardHtml(t){let e=this._escapeHtml(String(t.iteration||"--")),a=this._escapeHtml(this._formatTimestamp(t.timestamp)),i=this._escapeHtml(this._truncate(t.task_or_prd,200)),s=this._outcomeBadgeHtml(t.outcome),r=Array.isArray(t.voters)?t.voters:[],o=r.filter(m=>!m.is_contrarian),n=r.filter(m=>m.is_contrarian),l=o.map(m=>this._voterRowHtml(m,!1)).join(""),c="";t.contrarian_triggered&&(c='<div class="ct-contrarian-section"><div class="ct-section-label">Anti-Sycophancy Check</div>'+n.map(f=>this._voterRowHtml(f,t.contrarian_flipped)).join("")+"</div>");let p=typeof t.approve_count=="number"?t.approve_count:"--",u=typeof t.reject_count=="number"?t.reject_count:"--",b=typeof t.threshold=="number"?t.threshold:"--";return'<div class="ct-card"><div class="ct-card-header"><div class="ct-card-meta"><span class="ct-iter-label">Iteration '+e+'</span><span class="ct-ts">'+a+"</span></div>"+s+"</div>"+(i?'<div class="ct-prd-preview">'+i+"</div>":"")+'<div class="ct-tally">Approve: '+p+" · Reject: "+u+" · Threshold: "+b+'</div><div class="ct-voters">'+l+"</div>"+c+"</div>"}render(){let t=this.shadowRoot||this;if(!t)return;let e=`
|
|
13199
13199
|
<style>
|
|
13200
13200
|
:host { display: block; margin-top: 24px; }
|
|
13201
13201
|
.ct-wrapper {
|
|
@@ -13364,7 +13364,7 @@ var LokiDashboard=(()=>{var wt=Object.defineProperty;var ae=Object.getOwnPropert
|
|
|
13364
13364
|
.ct-badge-da { background: #fdf3d4; color: #8a6c0e; }
|
|
13365
13365
|
.ct-badge-unknown { background: var(--bg-secondary, #F8F4F0); color: var(--text-muted, #939084); }
|
|
13366
13366
|
</style>
|
|
13367
|
-
`,a="";this._loading&&this._transcripts.length===0?a='<div class="ct-empty">Loading council transcripts...</div>':this._error?a='<div class="ct-error">Failed to load transcripts: '+this._escapeHtml(this._error)+"</div>":!this._transcripts||this._transcripts.length===0?a='<div class="ct-empty">No council rounds recorded yet -- transcripts appear after the first iteration vote.</div>':a='<div class="ct-list">'+this._transcripts.map(s=>this._transcriptCardHtml(s)).join("")+"</div>",t.innerHTML=e+'<div class="ct-wrapper"><h3 class="ct-heading">Council Transcripts</h3><div class="ct-explain">Per-iteration voting records from .loki/council/transcripts/. Polls every 30 seconds.</div>'+a+"</div>"}};typeof customElements<"u"&&!customElements.get("loki-council-transcripts")&&customElements.define("loki-council-transcripts",yt);var He="1.4.0";function Re(d={}){return d.theme?_.setTheme(d.theme):d.autoDetectContext!==!1?_.init():R.init(),d.apiUrl&&g({baseUrl:d.apiUrl}),{theme:_.getTheme(),context:_.detectContext()}}return le(Be);})();
|
|
13367
|
+
`,a="";this._loading&&this._transcripts.length===0?a='<div class="ct-empty">Loading council transcripts...</div>':this._error?a='<div class="ct-error">Failed to load transcripts: '+this._escapeHtml(this._error)+"</div>":!this._transcripts||this._transcripts.length===0?a='<div class="ct-empty">No council rounds recorded yet -- transcripts appear after the first iteration vote.</div>':a='<div class="ct-list">'+this._transcripts.map(s=>this._transcriptCardHtml(s)).join("")+"</div>",t.innerHTML=e+'<div class="ct-wrapper"><h3 class="ct-heading">Council Transcripts</h3><div class="ct-explain">Per-iteration voting records from .loki/council/transcripts/. Polls every 30 seconds.</div>'+a+this._hookEventsHtml()+"</div>"}_hookEventsHtml(){let t=Array.isArray(this._hookEvents)?this._hookEvents:[],e;return t.length===0?e='<div class="ct-empty">No live tool activity yet -- Claude hook events stream here while a run is active.</div>':e='<div class="ct-voters">'+t.slice(0,20).map(i=>{let s=this._escapeHtml(i.type||i.event||"event"),r=this._escapeHtml(this._formatTimestamp(i.timestamp||i.ts)),o=this._escapeHtml(this._truncate(i.tool||i.message||i.summary||(i.data?JSON.stringify(i.data):""),120));return'<div class="ct-voter-row"><span class="ct-iter-label">'+s+'</span> <span class="ct-ts">'+r+"</span>"+(o?'<div class="ct-prd-preview">'+o+"</div>":"")+"</div>"}).join("")+"</div>",'<h3 class="ct-heading" style="margin-top:24px;">Live Tool Activity</h3><div class="ct-explain">Claude hook events (PreToolUse / PostToolUse / Stop) streamed from .loki/events.jsonl. Lets you watch background tool calls as they run.</div>'+e}};typeof customElements<"u"&&!customElements.get("loki-council-transcripts")&&customElements.define("loki-council-transcripts",yt);var He="1.4.0";function Re(d={}){return d.theme?_.setTheme(d.theme):d.autoDetectContext!==!1?_.init():R.init(),d.apiUrl&&g({baseUrl:d.apiUrl}),{theme:_.getTheme(),context:_.detectContext()}}return le(Be);})();
|
|
13368
13368
|
|
|
13369
13369
|
|
|
13370
13370
|
// Initialize dashboard when DOM is ready
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -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.7.
|
|
5
|
+
**Version:** v7.7.26
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -447,7 +447,7 @@ cat ~/.claude/skills/loki-mode/SKILL.md | head -10
|
|
|
447
447
|
```yaml
|
|
448
448
|
---
|
|
449
449
|
name: loki-mode
|
|
450
|
-
description:
|
|
450
|
+
description: Autonomous Spec-to-Product System (RARV-C closure loop)
|
|
451
451
|
...
|
|
452
452
|
---
|
|
453
453
|
```
|
|
@@ -107,7 +107,7 @@ Loki Mode covers:
|
|
|
107
107
|
|
|
108
108
|
**Auto-Claude:** Engineering only. No business/marketing agents.
|
|
109
109
|
|
|
110
|
-
**Verdict: Loki Mode wins** -
|
|
110
|
+
**Verdict: Loki Mode wins** - Full spec-to-product lifecycle vs coding only.
|
|
111
111
|
|
|
112
112
|
### 4. Anti-Sycophancy Measures
|
|
113
113
|
Loki Mode implements CONSENSAGENT (ACL 2025):
|
|
@@ -247,7 +247,7 @@ Loki Mode now incorporates proven patterns from Cursor's large-scale agent deplo
|
|
|
247
247
|
|
|
248
248
|
**Loki Mode is better if you want:**
|
|
249
249
|
- Research-backed architecture
|
|
250
|
-
- Full
|
|
250
|
+
- Full spec-to-product lifecycle (not just coding)
|
|
251
251
|
- 41 specialized agents
|
|
252
252
|
- Anti-sycophancy measures
|
|
253
253
|
- MIT license
|
|
@@ -261,7 +261,7 @@ Auto-Claude has better UX and community. Loki Mode has better architecture and c
|
|
|
261
261
|
Auto-Claude is a polished product. Loki Mode is a research-backed system.
|
|
262
262
|
|
|
263
263
|
For pure coding tasks with GUI preference: **Auto-Claude wins**.
|
|
264
|
-
For full autonomous
|
|
264
|
+
For full autonomous spec-to-product delivery with quality guarantees: **Loki Mode wins**.
|
|
265
265
|
|
|
266
266
|
---
|
|
267
267
|
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var _7=Object.defineProperty;var I7=(K)=>K;function P7(K,$){this[K]=I7.bind(null,$)}var v=(K,$)=>{for(var Q in $)_7(K,Q,{get:$[Q],enumerable:!0,configurable:!0,set:P7.bind($,Q)})};var R=(K,$)=>()=>(K&&($=K(K=0)),$);var t=import.meta.require;var e1={};v(e1,{lokiDir:()=>P,homeLokiDir:()=>k1,findRepoRootForVersion:()=>N1,REPO_ROOT:()=>p});import{resolve as u,dirname as S1}from"path";import{fileURLToPath as L7}from"url";import{existsSync as J1}from"fs";import{homedir as R7}from"os";function E7(){let K=i1;for(let $=0;$<6;$++){if(J1(u(K,"VERSION"))&&J1(u(K,"autonomy/run.sh")))return K;let Q=S1(K);if(Q===K)break;K=Q}return u(i1,"..","..","..")}function N1(K){let $=K;for(let Q=0;Q<6;Q++){if(J1(u($,"VERSION"))&&J1(u($,"autonomy/run.sh")))return $;let X=S1($);if(X===$)break;$=X}return u(K,"..","..","..")}function P(){return process.env.LOKI_DIR??u(process.cwd(),".loki")}function k1(){return u(R7(),".loki")}var i1,p;var g=R(()=>{i1=S1(L7(import.meta.url));p=E7()});import{readFileSync as F7}from"fs";import{resolve as w7,dirname as x7}from"path";import{fileURLToPath as S7}from"url";function G1(){if(o!==null)return o;let K="7.7.
|
|
2
|
+
var _7=Object.defineProperty;var I7=(K)=>K;function P7(K,$){this[K]=I7.bind(null,$)}var v=(K,$)=>{for(var Q in $)_7(K,Q,{get:$[Q],enumerable:!0,configurable:!0,set:P7.bind($,Q)})};var R=(K,$)=>()=>(K&&($=K(K=0)),$);var t=import.meta.require;var e1={};v(e1,{lokiDir:()=>P,homeLokiDir:()=>k1,findRepoRootForVersion:()=>N1,REPO_ROOT:()=>p});import{resolve as u,dirname as S1}from"path";import{fileURLToPath as L7}from"url";import{existsSync as J1}from"fs";import{homedir as R7}from"os";function E7(){let K=i1;for(let $=0;$<6;$++){if(J1(u(K,"VERSION"))&&J1(u(K,"autonomy/run.sh")))return K;let Q=S1(K);if(Q===K)break;K=Q}return u(i1,"..","..","..")}function N1(K){let $=K;for(let Q=0;Q<6;Q++){if(J1(u($,"VERSION"))&&J1(u($,"autonomy/run.sh")))return $;let X=S1($);if(X===$)break;$=X}return u(K,"..","..","..")}function P(){return process.env.LOKI_DIR??u(process.cwd(),".loki")}function k1(){return u(R7(),".loki")}var i1,p;var g=R(()=>{i1=S1(L7(import.meta.url));p=E7()});import{readFileSync as F7}from"fs";import{resolve as w7,dirname as x7}from"path";import{fileURLToPath as S7}from"url";function G1(){if(o!==null)return o;let K="7.7.26";if(typeof K==="string"&&K.length>0)return o=K,o;try{let $=x7(S7(import.meta.url)),Q=N1($);o=F7(w7(Q,"VERSION"),"utf-8").trim()}catch{o="unknown"}return o}var o=null;var D1=R(()=>{g()});var $0={};v($0,{runOrThrow:()=>N7,run:()=>k,commandVersion:()=>D7,commandExists:()=>h,ShellError:()=>C1});async function k(K,$={}){let Q=Bun.spawn({cmd:[...K],stdout:"pipe",stderr:"pipe",env:$.env?{...process.env,...$.env}:process.env,cwd:$.cwd}),X,Z;if($.timeoutMs&&$.timeoutMs>0)X=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}Z=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},$.timeoutMs);try{let[W,z,q]=await Promise.all([new Response(Q.stdout).text(),new Response(Q.stderr).text(),Q.exited]);return{stdout:W,stderr:z,exitCode:q}}finally{if(X)clearTimeout(X);if(Z)clearTimeout(Z)}}async function N7(K,$={}){let Q=await k(K,$);if(Q.exitCode!==0)throw new C1(`command failed (${Q.exitCode}): ${K.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function h(K){let $=k7(K),Q=await k(["sh","-c",`command -v ${$}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function k7(K){if(!/^[A-Za-z0-9._/-]+$/.test(K))throw Error(`refused to shell-escape suspect token: ${K}`);return K}async function D7(K,$="--version"){if(!await h(K))return null;let X=await k([K,$],{timeoutMs:5000});if(X.exitCode!==0)return null;return((X.stdout||X.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var C1;var n=R(()=>{C1=class C1 extends Error{message;exitCode;stdout;stderr;constructor(K,$,Q,X){super(K);this.message=K;this.exitCode=$;this.stdout=Q;this.stderr=X;this.name="ShellError"}}});function c(K){return C7?"":K}var C7,E,b,F,T6,O,D,w,H;var a=R(()=>{C7=(process.env.NO_COLOR??"").length>0;E=c("\x1B[0;31m"),b=c("\x1B[0;32m"),F=c("\x1B[1;33m"),T6=c("\x1B[0;34m"),O=c("\x1B[0;36m"),D=c("\x1B[1m"),w=c("\x1B[2m"),H=c("\x1B[0m")});import{existsSync as c7}from"fs";async function i(){if(X1!==void 0)return X1;let K="/opt/homebrew/bin/python3.12";if(c7(K))return X1=K,K;let $=await h("python3.12");if($)return X1=$,$;let Q=await h("python3");return X1=Q,Q}async function s(K,$={}){let Q=await i();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return k([Q,"-c",K],$)}var X1;var Z1=R(()=>{n()});var G0={};v(G0,{runStatus:()=>Q5});import{existsSync as N,readFileSync as W1,readdirSync as W0,statSync as H0}from"fs";import{resolve as x,basename as a7}from"path";async function r7(){if(await h("jq"))return!0;return process.stdout.write(`${E}Error: jq is required but not installed.${H}
|
|
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)
|
|
@@ -585,4 +585,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
585
585
|
`),2}default:return process.stderr.write(`Unknown command: ${$}
|
|
586
586
|
`),process.stderr.write(j7),2}}process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var X6=await Q6(Bun.argv.slice(2));process.exit(X6);
|
|
587
587
|
|
|
588
|
-
//# debugId=
|
|
588
|
+
//# debugId=CE941072AA747DCC64756E2164756E21
|
package/mcp/__init__.py
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
|
-
"version": "7.7.
|
|
4
|
-
"description": "Loki Mode by Autonomi.
|
|
3
|
+
"version": "7.7.26",
|
|
4
|
+
"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 11 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
7
7
|
"agent-orchestration",
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"files": [
|
|
65
65
|
"SKILL.md",
|
|
66
66
|
"VERSION",
|
|
67
|
+
"tools/",
|
|
67
68
|
"autonomy/",
|
|
68
69
|
"providers/",
|
|
69
70
|
"agents/",
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""v7.7.24: cross-project knowledge "lift" report (the memory moat proof).
|
|
3
|
+
|
|
4
|
+
WHAT THIS MEASURES (honestly):
|
|
5
|
+
Loki's moat claim is that knowledge learned on one project helps a
|
|
6
|
+
DIFFERENT project. The transfer mechanism is real and already in the
|
|
7
|
+
codebase: each project's semantic patterns (.loki/memory/semantic/)
|
|
8
|
+
are extracted into an org-wide knowledge graph
|
|
9
|
+
(memory/knowledge_graph.py -> ~/.loki/knowledge/patterns.jsonl), and
|
|
10
|
+
any other project can query that graph (query_patterns).
|
|
11
|
+
|
|
12
|
+
"Lift" here is a RETRIEVAL-COVERAGE metric, not a task-success metric.
|
|
13
|
+
For a target project's set of task goals we count how many RELEVANT
|
|
14
|
+
patterns are retrievable in two conditions:
|
|
15
|
+
baseline: only the target project's own patterns are in the graph
|
|
16
|
+
cross: the target's patterns PLUS sibling projects' patterns
|
|
17
|
+
Lift = (relevant retrieved in cross) - (relevant retrieved in baseline),
|
|
18
|
+
and net-new = relevant patterns that ONLY the sibling projects could
|
|
19
|
+
supply (the target could never have surfaced them alone).
|
|
20
|
+
|
|
21
|
+
WHAT THIS DOES NOT CLAIM:
|
|
22
|
+
- It does NOT claim downstream task success / fewer iterations / lower
|
|
23
|
+
cost. That requires running real LLM tasks end-to-end, which this
|
|
24
|
+
offline harness does not do. Measuring that is a separate, larger
|
|
25
|
+
benchmark.
|
|
26
|
+
- "Relevant" is keyword-overlap against the goal, not semantic ground
|
|
27
|
+
truth. It is a proxy. The number is a coverage signal, not a
|
|
28
|
+
correctness guarantee.
|
|
29
|
+
|
|
30
|
+
The harness is fully self-contained: it seeds synthetic projects in a
|
|
31
|
+
temp dir, points the knowledge graph at a temp knowledge dir, runs both
|
|
32
|
+
conditions, prints a report, and self-cleans. It never touches a real
|
|
33
|
+
~/.loki/knowledge or any real .loki/memory.
|
|
34
|
+
"""
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import argparse
|
|
38
|
+
import json
|
|
39
|
+
import os
|
|
40
|
+
import shutil
|
|
41
|
+
import sys
|
|
42
|
+
import tempfile
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
|
|
45
|
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
46
|
+
_REPO_ROOT = os.path.dirname(_HERE)
|
|
47
|
+
if _REPO_ROOT not in sys.path:
|
|
48
|
+
sys.path.insert(0, _REPO_ROOT)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# Synthetic patterns per source project. Each is a semantic pattern dict
|
|
52
|
+
# matching what memory/knowledge_graph.py reads (name/category/description).
|
|
53
|
+
SOURCE_PROJECTS = {
|
|
54
|
+
"payments-api": [
|
|
55
|
+
{"name": "idempotency-key-on-charge", "category": "reliability",
|
|
56
|
+
"description": "retry-safe charge endpoints require an idempotency key header"},
|
|
57
|
+
{"name": "stripe-webhook-signature-verify", "category": "security",
|
|
58
|
+
"description": "verify stripe webhook signatures before processing payment events"},
|
|
59
|
+
{"name": "decimal-money-never-float", "category": "correctness",
|
|
60
|
+
"description": "represent money as integer cents or Decimal, never float"},
|
|
61
|
+
],
|
|
62
|
+
"auth-service": [
|
|
63
|
+
{"name": "jwt-short-ttl-refresh-rotation", "category": "security",
|
|
64
|
+
"description": "access tokens short ttl with rotating refresh tokens"},
|
|
65
|
+
{"name": "rate-limit-login-by-ip-and-account", "category": "security",
|
|
66
|
+
"description": "rate limit login attempts per ip and per account to stop credential stuffing"},
|
|
67
|
+
{"name": "argon2-password-hash", "category": "security",
|
|
68
|
+
"description": "hash passwords with argon2id not bcrypt for new services"},
|
|
69
|
+
],
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Patterns the TARGET project already knows on its own (so they are NOT
|
|
73
|
+
# net-new from siblings).
|
|
74
|
+
TARGET_OWN_PATTERNS = [
|
|
75
|
+
{"name": "openapi-spec-first", "category": "design",
|
|
76
|
+
"description": "write the openapi spec before implementing the api"},
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
# The target project's task goals. Each goal SHOULD be served by a
|
|
80
|
+
# sibling pattern (that the target lacks). These are the realistic
|
|
81
|
+
# overlaps a new billing+login service would hit.
|
|
82
|
+
TARGET_GOALS = [
|
|
83
|
+
"make the charge endpoint safe to retry",
|
|
84
|
+
"verify incoming payment webhooks are authentic",
|
|
85
|
+
"store monetary amounts without rounding errors",
|
|
86
|
+
"secure login against credential stuffing attacks",
|
|
87
|
+
"choose a password hashing algorithm",
|
|
88
|
+
"design the api contract up front", # served by target's OWN pattern
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _seed_project(root: Path, name: str, patterns: list) -> None:
|
|
93
|
+
semantic = root / name / ".loki" / "memory" / "semantic"
|
|
94
|
+
semantic.mkdir(parents=True, exist_ok=True)
|
|
95
|
+
for i, p in enumerate(patterns):
|
|
96
|
+
with open(semantic / f"pattern_{i}.json", "w") as f:
|
|
97
|
+
json.dump(p, f)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _relevant(pattern: dict, goal: str) -> bool:
|
|
101
|
+
"""Keyword-overlap relevance proxy: any meaningful token from the
|
|
102
|
+
pattern name/description appears in the goal, or vice versa."""
|
|
103
|
+
stop = {"the", "a", "an", "to", "for", "of", "and", "or", "with",
|
|
104
|
+
"without", "is", "are", "be", "up", "on", "in", "by", "not",
|
|
105
|
+
"make", "choose", "store"}
|
|
106
|
+
def toks(s):
|
|
107
|
+
return {t for t in s.lower().replace("-", " ").split() if t not in stop and len(t) > 2}
|
|
108
|
+
goal_t = toks(goal)
|
|
109
|
+
pat_t = toks(pattern.get("name", "")) | toks(pattern.get("description", ""))
|
|
110
|
+
return len(goal_t & pat_t) >= 2
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _coverage(graph, goals, top_k):
|
|
114
|
+
"""For each goal, query the graph and count goals that retrieved at
|
|
115
|
+
least one relevant pattern. Returns (covered_goals, served_by_sibling)."""
|
|
116
|
+
covered = 0
|
|
117
|
+
sibling_served = 0
|
|
118
|
+
details = []
|
|
119
|
+
for goal in goals:
|
|
120
|
+
results = graph.query_patterns(goal, max_results=top_k)
|
|
121
|
+
relevant = [r for r in results if _relevant(r, goal)]
|
|
122
|
+
is_covered = len(relevant) > 0
|
|
123
|
+
# served_by_sibling: at least one relevant result came from a
|
|
124
|
+
# non-target source project.
|
|
125
|
+
from_sibling = any(
|
|
126
|
+
r.get("_source_project", "").rsplit("/", 1)[-1] != "target-billing-login"
|
|
127
|
+
for r in relevant
|
|
128
|
+
)
|
|
129
|
+
if is_covered:
|
|
130
|
+
covered += 1
|
|
131
|
+
if is_covered and from_sibling:
|
|
132
|
+
sibling_served += 1
|
|
133
|
+
details.append({
|
|
134
|
+
"goal": goal,
|
|
135
|
+
"covered": is_covered,
|
|
136
|
+
"relevant_count": len(relevant),
|
|
137
|
+
"served_by_sibling": is_covered and from_sibling,
|
|
138
|
+
})
|
|
139
|
+
return covered, sibling_served, details
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def run(top_k: int, as_json: bool) -> int:
|
|
143
|
+
tmp = tempfile.mkdtemp(prefix="loki-xproj-lift-")
|
|
144
|
+
try:
|
|
145
|
+
from memory.knowledge_graph import OrganizationKnowledgeGraph
|
|
146
|
+
|
|
147
|
+
projects_root = Path(tmp) / "git"
|
|
148
|
+
projects_root.mkdir(parents=True)
|
|
149
|
+
|
|
150
|
+
# Seed sibling source projects + the target project.
|
|
151
|
+
for name, pats in SOURCE_PROJECTS.items():
|
|
152
|
+
_seed_project(projects_root, name, pats)
|
|
153
|
+
_seed_project(projects_root, "target-billing-login", TARGET_OWN_PATTERNS)
|
|
154
|
+
|
|
155
|
+
target_dir = projects_root / "target-billing-login"
|
|
156
|
+
sibling_dirs = [projects_root / n for n in SOURCE_PROJECTS]
|
|
157
|
+
|
|
158
|
+
# BASELINE: knowledge graph built from the target alone.
|
|
159
|
+
base_kg = OrganizationKnowledgeGraph(
|
|
160
|
+
knowledge_dir=str(Path(tmp) / "knowledge-baseline"))
|
|
161
|
+
base_pats = base_kg.extract_patterns([target_dir])
|
|
162
|
+
base_kg.save_patterns(base_kg.deduplicate_patterns(base_pats))
|
|
163
|
+
base_covered, base_sibling, base_detail = _coverage(base_kg, TARGET_GOALS, top_k)
|
|
164
|
+
|
|
165
|
+
# CROSS: knowledge graph built from target + siblings.
|
|
166
|
+
cross_kg = OrganizationKnowledgeGraph(
|
|
167
|
+
knowledge_dir=str(Path(tmp) / "knowledge-cross"))
|
|
168
|
+
cross_pats = cross_kg.extract_patterns([target_dir] + sibling_dirs)
|
|
169
|
+
cross_kg.save_patterns(cross_kg.deduplicate_patterns(cross_pats))
|
|
170
|
+
cross_covered, cross_sibling, cross_detail = _coverage(cross_kg, TARGET_GOALS, top_k)
|
|
171
|
+
|
|
172
|
+
n = len(TARGET_GOALS)
|
|
173
|
+
lift = cross_covered - base_covered
|
|
174
|
+
report = {
|
|
175
|
+
"goals": n,
|
|
176
|
+
"baseline_covered": base_covered,
|
|
177
|
+
"cross_covered": cross_covered,
|
|
178
|
+
"lift_absolute": lift,
|
|
179
|
+
"lift_pct_points": round(100.0 * lift / n, 1),
|
|
180
|
+
"net_new_from_siblings": cross_sibling - base_sibling,
|
|
181
|
+
"top_k": top_k,
|
|
182
|
+
"method": "retrieval-coverage (keyword-overlap relevance proxy), NOT task-success",
|
|
183
|
+
"per_goal": cross_detail,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if as_json:
|
|
187
|
+
print(json.dumps(report, indent=2))
|
|
188
|
+
else:
|
|
189
|
+
print("Cross-project knowledge LIFT report (memory moat proof)")
|
|
190
|
+
print(f" target goals: {n}")
|
|
191
|
+
print(f" covered (target alone): {base_covered}/{n}")
|
|
192
|
+
print(f" covered (target + siblings): {cross_covered}/{n}")
|
|
193
|
+
print(f" LIFT: +{lift} goals "
|
|
194
|
+
f"(+{report['lift_pct_points']} pts)")
|
|
195
|
+
print(f" net-new served by siblings: {report['net_new_from_siblings']}")
|
|
196
|
+
print(f" method: {report['method']}")
|
|
197
|
+
print(" per-goal:")
|
|
198
|
+
for d in cross_detail:
|
|
199
|
+
tag = "sibling" if d["served_by_sibling"] else ("self" if d["covered"] else "MISS")
|
|
200
|
+
print(f" [{tag:7}] {d['goal']}")
|
|
201
|
+
|
|
202
|
+
# Exit non-zero if there is no measurable lift (so it can gate CI:
|
|
203
|
+
# a regression that breaks cross-project transfer would fail here).
|
|
204
|
+
return 0 if lift > 0 else 1
|
|
205
|
+
finally:
|
|
206
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def main():
|
|
210
|
+
ap = argparse.ArgumentParser(description="Cross-project knowledge lift report")
|
|
211
|
+
ap.add_argument("--top-k", type=int, default=5, help="patterns retrieved per goal")
|
|
212
|
+
ap.add_argument("--json", action="store_true", help="emit JSON")
|
|
213
|
+
args = ap.parse_args()
|
|
214
|
+
sys.exit(run(args.top_k, args.json))
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
if __name__ == "__main__":
|
|
218
|
+
main()
|