loki-mode 11.7.1 → 11.9.1

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 (92) hide show
  1. package/SKILL.md +2 -2
  2. package/VERSION +1 -1
  3. package/autonomy/lib/backlog.py +40 -4
  4. package/autonomy/lib/chat.sh +3 -2
  5. package/autonomy/lib/workspace.py +95 -4
  6. package/autonomy/loki +185 -106
  7. package/autonomy/trigger-server.py +550 -5
  8. package/autonomy/verify.sh +5 -0
  9. package/bin/loki +1 -1
  10. package/completions/_loki +34 -1
  11. package/completions/loki.bash +10 -3
  12. package/dashboard/__init__.py +1 -1
  13. package/docs/ARCHITECTURE.md +0 -1
  14. package/docs/CLI-REFERENCE.md +1 -1
  15. package/docs/COMPONENTS.md +1 -1
  16. package/docs/DEPLOY-PLAN.md +13 -3
  17. package/docs/INSTALLATION.md +1 -1
  18. package/docs/WORKSPACES.md +1 -1
  19. package/docs/dev/architecture-reference.md +1 -1
  20. package/docs/sentry-intake.md +36 -0
  21. package/docs/v10/D61-SPEED.md +14 -0
  22. package/docs/v10/PROGRESS.md +22 -0
  23. package/loki-ts/dist/loki.js +962 -879
  24. package/mcp/__init__.py +1 -1
  25. package/package.json +2 -2
  26. package/packages/control-plane/dist/ask-tools-server.js +13 -4
  27. package/packages/control-plane/dist/server.js +614 -456
  28. package/packages/control-plane/src/server/diffstat.ts +10 -2
  29. package/packages/control-plane/src/server/legacy/routes-control.ts +8 -4
  30. package/packages/control-plane/src/server/routes/checkpoints.ts +11 -2
  31. package/packages/control-plane/ui/dist/assets/index-u8cd6TRQ.js +190 -0
  32. package/packages/control-plane/ui/dist/index.html +1 -1
  33. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  34. package/web-app/dist/assets/{AdminPage-S3BYeapm.js → AdminPage-BdH9iL2I.js} +1 -1
  35. package/web-app/dist/assets/{Avatar-C1rHwLgn.js → Avatar-C-WixxR6.js} +1 -1
  36. package/web-app/dist/assets/{Badge-CzlnN5aQ.js → Badge-BFWpRd_L.js} +1 -1
  37. package/web-app/dist/assets/{Button-B1w5AeGC.js → Button-CE5vG66Y.js} +1 -1
  38. package/web-app/dist/assets/{CockpitPage-Dyeqjfkc.js → CockpitPage-BAVn10bY.js} +1 -1
  39. package/web-app/dist/assets/{ComparePage-C6y2GkGR.js → ComparePage-DOecIGfM.js} +1 -1
  40. package/web-app/dist/assets/{ErrorBoundary-Dg2fcGkR.js → ErrorBoundary-1rNA5v7f.js} +1 -1
  41. package/web-app/dist/assets/{EvidenceReceiptPanel-CxbyjvTj.js → EvidenceReceiptPanel-DjOaWfdH.js} +1 -1
  42. package/web-app/dist/assets/{GitHubIssuesPanel-Dpys1xDb.js → GitHubIssuesPanel-NNPBuRPD.js} +1 -1
  43. package/web-app/dist/assets/{GitHubPRsPanel-CRVjWoFl.js → GitHubPRsPanel-CL1b--tV.js} +1 -1
  44. package/web-app/dist/assets/{HomePage-BKnv-oYw.js → HomePage-BsLDN6MW.js} +2 -2
  45. package/web-app/dist/assets/{LoginPage-BGksmFRd.js → LoginPage-CVIv5bCS.js} +1 -1
  46. package/web-app/dist/assets/{MagicPage-OCvhp16M.js → MagicPage-BgMBB9lK.js} +1 -1
  47. package/web-app/dist/assets/{MetricsPage-CJOJpXWC.js → MetricsPage-BHeJlqPC.js} +1 -1
  48. package/web-app/dist/assets/{NotFoundPage-C04p3QvN.js → NotFoundPage-DDzwfyDK.js} +1 -1
  49. package/web-app/dist/assets/{ProjectPage-BRB8txsY.js → ProjectPage-CuQWEYBF.js} +3 -3
  50. package/web-app/dist/assets/{ProjectsPage-CoUYeQEh.js → ProjectsPage-OoD0RFDZ.js} +1 -1
  51. package/web-app/dist/assets/{SettingsPage-BkjmJGfM.js → SettingsPage-tMFMKkK5.js} +1 -1
  52. package/web-app/dist/assets/{ShowcasePage-B4vU9TjQ.js → ShowcasePage-DVfLoWdz.js} +1 -1
  53. package/web-app/dist/assets/{SystemSettingsPage-Z2jiVsni.js → SystemSettingsPage-CThV9N4l.js} +1 -1
  54. package/web-app/dist/assets/{TeamsPage-Ulm8SISq.js → TeamsPage-CV0nvXI8.js} +1 -1
  55. package/web-app/dist/assets/{TemplatesPage-C_7MM6Ey.js → TemplatesPage-D81EI1L8.js} +1 -1
  56. package/web-app/dist/assets/{TerminalOutput-Cumd7Tgs.js → TerminalOutput-Bj7uKRIe.js} +1 -1
  57. package/web-app/dist/assets/{activity-CZAUkbiY.js → activity-DoNfNkOL.js} +1 -1
  58. package/web-app/dist/assets/{bell-BOvSXpEr.js → bell-BWO3OU50.js} +1 -1
  59. package/web-app/dist/assets/{bot-DOTZxBYj.js → bot-DcvWv_zD.js} +1 -1
  60. package/web-app/dist/assets/{check-BxpE0B5u.js → check-CfG09Z18.js} +1 -1
  61. package/web-app/dist/assets/{chevron-left-D6KPvBOA.js → chevron-left-z0fh4x2h.js} +1 -1
  62. package/web-app/dist/assets/{circle-alert-Bajq86oe.js → circle-alert-C_7GlYul.js} +1 -1
  63. package/web-app/dist/assets/{clock-CnlHAVJc.js → clock-BsYUp070.js} +1 -1
  64. package/web-app/dist/assets/{cloud-DjaymjwM.js → cloud-BVWAJ7Rv.js} +1 -1
  65. package/web-app/dist/assets/{code-xml-BADEw2Ld.js → code-xml-CdZT-6zu.js} +1 -1
  66. package/web-app/dist/assets/{database-B4aKb5Cq.js → database-Dbkm9t2D.js} +1 -1
  67. package/web-app/dist/assets/{dollar-sign-DrldXpja.js → dollar-sign-CfIUM5Pd.js} +1 -1
  68. package/web-app/dist/assets/{file-code-corner-DVebDVR-.js → file-code-corner-CrUKhnj-.js} +1 -1
  69. package/web-app/dist/assets/{file-plus-BD-N_-Tl.js → file-plus-Cf6u0xHY.js} +1 -1
  70. package/web-app/dist/assets/{globe-C7ieInC-.js → globe-DsIqaI7w.js} +1 -1
  71. package/web-app/dist/assets/{hammer-BSugKIji.js → hammer-CA14BeMp.js} +1 -1
  72. package/web-app/dist/assets/{index-nLQgfvOn.js → index-DpJ_GJuZ.js} +3 -3
  73. package/web-app/dist/assets/{layers-CHXxU2on.js → layers-Dh5Oqhrj.js} +1 -1
  74. package/web-app/dist/assets/{loader-circle-BEEz0LbD.js → loader-circle-B0WHIGqt.js} +1 -1
  75. package/web-app/dist/assets/{lock-CzQGfcnE.js → lock-DkgfEGYF.js} +1 -1
  76. package/web-app/dist/assets/{package-CcHYQLGd.js → package-LzjmwD1S.js} +1 -1
  77. package/web-app/dist/assets/{plus-DdzgFEXn.js → plus-BNjA0KSV.js} +1 -1
  78. package/web-app/dist/assets/{refresh-cw-7x0HbH9Y.js → refresh-cw-k3IacuDX.js} +1 -1
  79. package/web-app/dist/assets/{rotate-ccw-DBahX1Sp.js → rotate-ccw-BFON_xv-.js} +1 -1
  80. package/web-app/dist/assets/{scroll-text-BB8y8a9Z.js → scroll-text-CaIlSPZB.js} +1 -1
  81. package/web-app/dist/assets/{server-CfkbW2MH.js → server-CKFUccCG.js} +1 -1
  82. package/web-app/dist/assets/{shield-alert-DUgD8rBF.js → shield-alert-Q4-rPo5r.js} +1 -1
  83. package/web-app/dist/assets/{thumbs-up-DGoyY6QC.js → thumbs-up-BYMeh8oV.js} +1 -1
  84. package/web-app/dist/assets/{trash-2-B6xrSUMr.js → trash-2-BHSUEIHw.js} +1 -1
  85. package/web-app/dist/assets/{trending-down-B6RGKTOx.js → trending-down-BC_FR2VT.js} +1 -1
  86. package/web-app/dist/assets/{trending-up-B44DCrt1.js → trending-up-CmA-WmLW.js} +1 -1
  87. package/web-app/dist/assets/{upload-4kWm33ZY.js → upload-BxLgBa1B.js} +1 -1
  88. package/web-app/dist/assets/{usePolling-zkUtRQG6.js → usePolling-DKNgwt5n.js} +1 -1
  89. package/web-app/dist/assets/{user-04cEVIe2.js → user-CA2DlmLc.js} +1 -1
  90. package/web-app/dist/index.html +1 -1
  91. package/memory/rag_injector.py +0 -161
  92. package/packages/control-plane/ui/dist/assets/index-DbLzr87u.js +0 -190
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: An autonomous software factory that knows what it is supposed to deliver, and proves it did. Use when the user says Loki Mode or asks to build, fix or verify software autonomously.
4
4
  ---
5
5
 
6
- # Loki Mode v11.7.1
6
+ # Loki Mode v11.9.1
7
7
 
8
8
  **Current entry points (Loki 10 engine):** `loki "<task>"`, `loki owner/repo#N` and `loki quick "<task>"` run the Loki 10 engine and end with a signed receipt (`loki verify`). `loki backlog owner/repo --all` runs many issues in parallel. Guide: `docs/v10/GUIDE.md`. The skill instructions below, and `loki start`, describe the previous bash loop. PRD-file starts (`loki start ./prd.md`) and the opencode provider still run on it until FC38-SWEEP ports them; the legacy-engine command and the LOKI_ENGINE switch remain removed. Prefer `loki owner/repo#N` for issues.
9
9
 
@@ -483,4 +483,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
483
483
 
484
484
  ---
485
485
 
486
- **v11.7.1 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
486
+ **v11.9.1 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 11.7.1
1
+ 11.9.1
@@ -195,7 +195,9 @@ def load_dag(path, group):
195
195
  if task.lstrip().startswith("-"):
196
196
  raise ValueError("unit %s task text starts with '-' and would be read as an option" % uid)
197
197
  units.append({"n": n, "id": uid, "deps": deps[uid], "write_set": list(u.get("writeSet") or []),
198
- "task": task})
198
+ "task": task, "done": u.get("done") is True,
199
+ "base": str(u["base"]) if re.match(r"^[0-9a-f]{7,64}$", str(u.get("base") or "")) else "",
200
+ "spec": u["spec"] if isinstance(u.get("spec"), str) and os.path.isabs(u["spec"]) else ""})
199
201
  return group, units
200
202
 
201
203
 
@@ -313,7 +315,10 @@ def main(argv):
313
315
  "cmd": [launcher, u["task"], "--no-pr"],
314
316
  "deps": [key_of[d] for d in u["deps"]],
315
317
  "env": {"LOKI_GROUP_ID": group, "LOKI_UNIT_ID": u["id"], "LOKI_UNIT_N": str(u["n"]),
316
- "LOKI_UNIT_DEPS": ",".join(u["deps"]), "LOKI_UNIT_WRITE_SET": "\n".join(u["write_set"])}}
318
+ "LOKI_UNIT_DEPS": ",".join(u["deps"]), "LOKI_UNIT_WRITE_SET": "\n".join(u["write_set"])},
319
+ "done": u["done"], "base": u["base"]}
320
+ if u["spec"]: # frozen group acceptance for unit_mode (wall.ts)
321
+ spec[k]["env"].update(LOKI_UNIT_SPEC=u["spec"], LOKI_SPEED="1")
317
322
  state = {k: {"status": "queued", "ok": None, "cost": None, "ran": False} for k in spec}
318
323
  for k in state:
319
324
  say("backlog: %s queued" % spec[k]["label"])
@@ -331,6 +336,32 @@ def main(argv):
331
336
  signal.signal(signal.SIGTERM, stop_children)
332
337
  signal.signal(signal.SIGINT, stop_children)
333
338
 
339
+ def harvest(k, wt):
340
+ """Copy the newest unit receipt and events into the group dir before the worktree goes."""
341
+ gid = spec[k]["env"].get("LOKI_GROUP_ID")
342
+ uid = spec[k]["env"].get("LOKI_UNIT_ID")
343
+ if not (spec[k]["unit"] and gid and uid):
344
+ return
345
+ try:
346
+ runs = os.path.join(wt, ".loki", "runs")
347
+ cands = [os.path.join(runs, d) for d in os.listdir(runs)
348
+ if not os.path.islink(os.path.join(runs, d))
349
+ and os.path.isfile(os.path.join(runs, d, "receipt.json"))]
350
+ if not cands:
351
+ return
352
+ src_dir = max(cands, key=lambda d: os.path.getmtime(os.path.join(d, "receipt.json")))
353
+ dst_dir = os.path.join(top, ".loki", "groups", gid, "group", "units", uid)
354
+ os.makedirs(dst_dir, mode=0o700, exist_ok=True)
355
+ for name in ("receipt.json", "events.jsonl"):
356
+ src, dst = os.path.join(src_dir, name), os.path.join(dst_dir, name)
357
+ if os.path.islink(src) or os.path.islink(dst) or not os.path.isfile(src):
358
+ continue
359
+ tmp = dst + ".tmp%d" % os.getpid()
360
+ shutil.copyfile(src, tmp)
361
+ os.replace(tmp, dst)
362
+ except OSError:
363
+ pass
364
+
334
365
  def finish(k):
335
366
  nonlocal unmeasured
336
367
  p, log, wt = running.pop(k)
@@ -348,6 +379,7 @@ def main(argv):
348
379
  add_spend(usd)
349
380
  state[k].update(status=status, ok=ok, cost=usd)
350
381
  say("backlog: %s %s" % (spec[k]["label"], status))
382
+ harvest(k, wt)
351
383
  if ok:
352
384
  git("worktree", "remove", "--force", wt, cwd=top)
353
385
  if not spec[k]["unit"]: # a passing unit branch is kept for the integrator
@@ -362,13 +394,13 @@ def main(argv):
362
394
  # Shared worktree prep (D51-B05): per-base lock, clean start point, deps copied.
363
395
  git("branch", "-D", s["branch"], cwd=top)
364
396
  try:
365
- worktree_prep.prepare_worktree(top, wt, s["branch"])
397
+ worktree_prep.prepare_worktree(top, wt, s["branch"], base=s.get("base") or None)
366
398
  r = None
367
399
  except (RuntimeError, subprocess.CalledProcessError) as e:
368
400
  msg = getattr(e, "stderr", None) or str(e)
369
401
  r = subprocess.CompletedProcess([], 1, "", msg)
370
402
  else:
371
- r = git("worktree", "add", "-B", s["branch"], wt, "HEAD", cwd=top)
403
+ r = git("worktree", "add", "-B", s["branch"], wt, s.get("base") or "HEAD", cwd=top)
372
404
  if r is not None and r.returncode != 0:
373
405
  err = (r.stderr.strip().splitlines() or ["git error"])[-1]
374
406
  state[k].update(status="FAILED: worktree: " + redact(err, token), ok=False)
@@ -414,6 +446,10 @@ def main(argv):
414
446
  k = next_ready()
415
447
  if k is None:
416
448
  break
449
+ if spec[k].get("done"):
450
+ state[k].update(status="VERIFIED (resumed)", ok=True)
451
+ say("backlog: %s VERIFIED (resumed)" % spec[k]["label"])
452
+ continue
417
453
  launch(k)
418
454
  if running:
419
455
  time.sleep(0.2)
@@ -47,7 +47,7 @@ Loki chat commands:
47
47
  /attach attach to the current or most recent loki start run in this repo
48
48
  /quick <task> loki quick
49
49
  /edit <change> apply one change (build mode for this turn)
50
- /verify loki verify
50
+ /verify [path] [--max-age 7d] loki verify (a receipt path, group receipt, or age limit)
51
51
  /receipt loki receipt
52
52
  /model <id> set model for this session
53
53
  /provider <name> switch provider (claude, codex, cline, aider, opencode)
@@ -351,7 +351,8 @@ Question: $line" || true
351
351
  /edit)
352
352
  [ -n "$rest" ] || { echo "usage: /edit <change>"; continue; }
353
353
  _chat_send build "$rest" ;;
354
- /verify) _chat_do "$loki" verify ;;
354
+ /verify) # shellcheck disable=SC2086
355
+ _chat_do "$loki" verify $rest ;;
355
356
  /receipt) _chat_do "$loki" receipt ;;
356
357
  /cost) _chat_do "$loki" cost ;;
357
358
  /undo) # shellcheck disable=SC2086
@@ -585,6 +585,85 @@ def _dep_tokens(root):
585
585
  return toks
586
586
 
587
587
 
588
+ _SRC_EXT = (".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go")
589
+ _SKIP_DIRS = {"node_modules", ".git", "dist", "build", "vendor", "venv", ".venv", "__pycache__", ".loki"}
590
+ _SCAN_MAX_FILES = 400
591
+ _SCAN_MAX_BYTES = 262144
592
+ _JS_SPEC = re.compile(r"""(?:from\s*|require\s*\(\s*|import\s*\(?\s*)["']([^"']+)["']""")
593
+ _PY_IMPORT = re.compile(r"^\s*(?:from\s+([A-Za-z_][\w.]*)\s+import\b|import\s+([A-Za-z_][\w.]*))")
594
+ _GO_IMPORT = re.compile(r'^\s*(?:import\s+)?(?:[\w.]+\s+)?"([^"]+)"\s*$')
595
+ _SPEC_KEY = re.compile(r"""["']?(?:inputSpec|input|target)["']?\s*[:=]\s*["']?([^"',\s}]+)""")
596
+ _GEN_CONFIG = re.compile(
597
+ r"^(openapitools\.json|openapi-generator.*\.(?:ya?ml|json)|orval\.config\.\w+|\.orvalrc(?:\.\w+)?)$")
598
+
599
+
600
+ def _spec_hits(path, ext):
601
+ """Yield (line number, imported specifier) for one source file."""
602
+ text = _read(path)
603
+ if text is None or len(text) > _SCAN_MAX_BYTES:
604
+ return
605
+ for n, line in enumerate(text.splitlines(), 1):
606
+ if ext == ".py":
607
+ m = _PY_IMPORT.match(line)
608
+ if m:
609
+ yield n, (m.group(1) or m.group(2)).lower().replace("_", "-")
610
+ elif ext == ".go":
611
+ m = _GO_IMPORT.match(line)
612
+ if m:
613
+ yield n, m.group(1).lower()
614
+ else:
615
+ for m in _JS_SPEC.finditer(line):
616
+ yield n, m.group(1).lower()
617
+
618
+
619
+ def _import_evidence(root, ids):
620
+ """First file:line per sibling whose package/module name a source file imports (bounded scan)."""
621
+ found, seen = {}, 0
622
+ for dirpath, dirs, files in os.walk(root):
623
+ dirs[:] = sorted(d for d in dirs if d not in _SKIP_DIRS)
624
+ for fn in sorted(files):
625
+ ext = os.path.splitext(fn)[1]
626
+ if ext not in _SRC_EXT:
627
+ continue
628
+ seen += 1
629
+ if seen > _SCAN_MAX_FILES:
630
+ return found
631
+ path = os.path.join(dirpath, fn)
632
+ for n, spec in _spec_hits(path, ext):
633
+ if spec.startswith("."):
634
+ continue
635
+ for o, oids in ids.items():
636
+ if o not in found and any(spec == i or spec.startswith(i + "/") or spec.startswith(i + ".")
637
+ for i in oids):
638
+ found[o] = "%s:%d (imports %s)" % (os.path.relpath(path, root), n, spec)
639
+ return found
640
+
641
+
642
+ def _openapi_evidence(root, roots):
643
+ """file:line per sibling whose checkout holds the spec a client generator config points at."""
644
+ found = {}
645
+ try:
646
+ names = sorted(os.listdir(root))
647
+ except OSError:
648
+ return found
649
+ for fn in names:
650
+ path = os.path.join(root, fn)
651
+ if not _GEN_CONFIG.match(fn) or not os.path.isfile(path):
652
+ continue
653
+ text = _read(path) or ""
654
+ for n, line in enumerate(text.splitlines(), 1):
655
+ for m in _SPEC_KEY.finditer(line):
656
+ val = m.group(1)
657
+ if "://" in val:
658
+ continue
659
+ target = os.path.realpath(os.path.join(root, val))
660
+ for o, oroot in roots.items():
661
+ if oroot and o not in found and os.path.realpath(oroot) != os.path.realpath(root) and \
662
+ (target + os.sep).startswith(os.path.realpath(oroot) + os.sep):
663
+ found[o] = "%s:%d (spec %s)" % (fn, n, val)
664
+ return found
665
+
666
+
588
667
  def infer(name, workspaces, say=say):
589
668
  """Print suggested `after:` edges from manifests; never writes loki.yaml."""
590
669
  ws = workspaces.get(name)
@@ -598,7 +677,7 @@ def infer(name, workspaces, say=say):
598
677
  os.path.expanduser("~"), ".loki", "repos", _slug(e["repo"]))
599
678
  roots[e["repo"]] = root if os.path.isdir(root) else None
600
679
  ids = {e["repo"]: _identities(e, roots[e["repo"]]) if roots[e["repo"]] else set() for e in entries}
601
- proposed = {}
680
+ proposed, evidence = {}, {}
602
681
  for e in entries:
603
682
  repo = e["repo"]
604
683
  if not roots[repo]:
@@ -607,12 +686,21 @@ def infer(name, workspaces, say=say):
607
686
  continue
608
687
  toks = _dep_tokens(roots[repo])
609
688
  deps = list(e.get("after") or [])
610
- for other in entries:
611
- o = other["repo"]
612
- if o == repo or o in deps:
689
+ others = {x["repo"]: ids[x["repo"]] for x in entries if x["repo"] != repo}
690
+ imp = _import_evidence(roots[repo], others)
691
+ api = _openapi_evidence(roots[repo], {o: roots[o] for o in others})
692
+ for o in others:
693
+ if o in deps:
613
694
  continue
614
695
  if any(t == i or t.startswith(i + "/") for t in toks for i in ids[o]):
615
696
  deps.append(o)
697
+ evidence[(repo, o)] = "manifest dependency"
698
+ elif o in api:
699
+ deps.append(o)
700
+ evidence[(repo, o)] = api[o]
701
+ elif o in imp:
702
+ deps.append(o)
703
+ evidence[(repo, o)] = imp[o]
616
704
  proposed[repo] = deps
617
705
  try:
618
706
  order_repos({"repos": [{"repo": e["repo"], "after": proposed[e["repo"]]} for e in entries]})
@@ -623,6 +711,9 @@ def infer(name, workspaces, say=say):
623
711
  for e in entries:
624
712
  say("%s" % e["repo"])
625
713
  say(" after: [%s]" % ", ".join(proposed[e["repo"]]))
714
+ for d in proposed[e["repo"]]:
715
+ if (e["repo"], d) in evidence:
716
+ say(" %s: %s" % (d, evidence[(e["repo"], d)]))
626
717
  return 0
627
718
 
628
719