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.
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/backlog.py +40 -4
- package/autonomy/lib/chat.sh +3 -2
- package/autonomy/lib/workspace.py +95 -4
- package/autonomy/loki +185 -106
- package/autonomy/trigger-server.py +550 -5
- package/autonomy/verify.sh +5 -0
- package/bin/loki +1 -1
- package/completions/_loki +34 -1
- package/completions/loki.bash +10 -3
- package/dashboard/__init__.py +1 -1
- package/docs/ARCHITECTURE.md +0 -1
- package/docs/CLI-REFERENCE.md +1 -1
- package/docs/COMPONENTS.md +1 -1
- package/docs/DEPLOY-PLAN.md +13 -3
- package/docs/INSTALLATION.md +1 -1
- package/docs/WORKSPACES.md +1 -1
- package/docs/dev/architecture-reference.md +1 -1
- package/docs/sentry-intake.md +36 -0
- package/docs/v10/D61-SPEED.md +14 -0
- package/docs/v10/PROGRESS.md +22 -0
- package/loki-ts/dist/loki.js +962 -879
- package/mcp/__init__.py +1 -1
- package/package.json +2 -2
- package/packages/control-plane/dist/ask-tools-server.js +13 -4
- package/packages/control-plane/dist/server.js +614 -456
- package/packages/control-plane/src/server/diffstat.ts +10 -2
- package/packages/control-plane/src/server/legacy/routes-control.ts +8 -4
- package/packages/control-plane/src/server/routes/checkpoints.ts +11 -2
- package/packages/control-plane/ui/dist/assets/index-u8cd6TRQ.js +190 -0
- package/packages/control-plane/ui/dist/index.html +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/web-app/dist/assets/{AdminPage-S3BYeapm.js → AdminPage-BdH9iL2I.js} +1 -1
- package/web-app/dist/assets/{Avatar-C1rHwLgn.js → Avatar-C-WixxR6.js} +1 -1
- package/web-app/dist/assets/{Badge-CzlnN5aQ.js → Badge-BFWpRd_L.js} +1 -1
- package/web-app/dist/assets/{Button-B1w5AeGC.js → Button-CE5vG66Y.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-Dyeqjfkc.js → CockpitPage-BAVn10bY.js} +1 -1
- package/web-app/dist/assets/{ComparePage-C6y2GkGR.js → ComparePage-DOecIGfM.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-Dg2fcGkR.js → ErrorBoundary-1rNA5v7f.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-CxbyjvTj.js → EvidenceReceiptPanel-DjOaWfdH.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-Dpys1xDb.js → GitHubIssuesPanel-NNPBuRPD.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-CRVjWoFl.js → GitHubPRsPanel-CL1b--tV.js} +1 -1
- package/web-app/dist/assets/{HomePage-BKnv-oYw.js → HomePage-BsLDN6MW.js} +2 -2
- package/web-app/dist/assets/{LoginPage-BGksmFRd.js → LoginPage-CVIv5bCS.js} +1 -1
- package/web-app/dist/assets/{MagicPage-OCvhp16M.js → MagicPage-BgMBB9lK.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-CJOJpXWC.js → MetricsPage-BHeJlqPC.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-C04p3QvN.js → NotFoundPage-DDzwfyDK.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-BRB8txsY.js → ProjectPage-CuQWEYBF.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-CoUYeQEh.js → ProjectsPage-OoD0RFDZ.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-BkjmJGfM.js → SettingsPage-tMFMKkK5.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-B4vU9TjQ.js → ShowcasePage-DVfLoWdz.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-Z2jiVsni.js → SystemSettingsPage-CThV9N4l.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-Ulm8SISq.js → TeamsPage-CV0nvXI8.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-C_7MM6Ey.js → TemplatesPage-D81EI1L8.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-Cumd7Tgs.js → TerminalOutput-Bj7uKRIe.js} +1 -1
- package/web-app/dist/assets/{activity-CZAUkbiY.js → activity-DoNfNkOL.js} +1 -1
- package/web-app/dist/assets/{bell-BOvSXpEr.js → bell-BWO3OU50.js} +1 -1
- package/web-app/dist/assets/{bot-DOTZxBYj.js → bot-DcvWv_zD.js} +1 -1
- package/web-app/dist/assets/{check-BxpE0B5u.js → check-CfG09Z18.js} +1 -1
- package/web-app/dist/assets/{chevron-left-D6KPvBOA.js → chevron-left-z0fh4x2h.js} +1 -1
- package/web-app/dist/assets/{circle-alert-Bajq86oe.js → circle-alert-C_7GlYul.js} +1 -1
- package/web-app/dist/assets/{clock-CnlHAVJc.js → clock-BsYUp070.js} +1 -1
- package/web-app/dist/assets/{cloud-DjaymjwM.js → cloud-BVWAJ7Rv.js} +1 -1
- package/web-app/dist/assets/{code-xml-BADEw2Ld.js → code-xml-CdZT-6zu.js} +1 -1
- package/web-app/dist/assets/{database-B4aKb5Cq.js → database-Dbkm9t2D.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-DrldXpja.js → dollar-sign-CfIUM5Pd.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-DVebDVR-.js → file-code-corner-CrUKhnj-.js} +1 -1
- package/web-app/dist/assets/{file-plus-BD-N_-Tl.js → file-plus-Cf6u0xHY.js} +1 -1
- package/web-app/dist/assets/{globe-C7ieInC-.js → globe-DsIqaI7w.js} +1 -1
- package/web-app/dist/assets/{hammer-BSugKIji.js → hammer-CA14BeMp.js} +1 -1
- package/web-app/dist/assets/{index-nLQgfvOn.js → index-DpJ_GJuZ.js} +3 -3
- package/web-app/dist/assets/{layers-CHXxU2on.js → layers-Dh5Oqhrj.js} +1 -1
- package/web-app/dist/assets/{loader-circle-BEEz0LbD.js → loader-circle-B0WHIGqt.js} +1 -1
- package/web-app/dist/assets/{lock-CzQGfcnE.js → lock-DkgfEGYF.js} +1 -1
- package/web-app/dist/assets/{package-CcHYQLGd.js → package-LzjmwD1S.js} +1 -1
- package/web-app/dist/assets/{plus-DdzgFEXn.js → plus-BNjA0KSV.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-7x0HbH9Y.js → refresh-cw-k3IacuDX.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-DBahX1Sp.js → rotate-ccw-BFON_xv-.js} +1 -1
- package/web-app/dist/assets/{scroll-text-BB8y8a9Z.js → scroll-text-CaIlSPZB.js} +1 -1
- package/web-app/dist/assets/{server-CfkbW2MH.js → server-CKFUccCG.js} +1 -1
- package/web-app/dist/assets/{shield-alert-DUgD8rBF.js → shield-alert-Q4-rPo5r.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-DGoyY6QC.js → thumbs-up-BYMeh8oV.js} +1 -1
- package/web-app/dist/assets/{trash-2-B6xrSUMr.js → trash-2-BHSUEIHw.js} +1 -1
- package/web-app/dist/assets/{trending-down-B6RGKTOx.js → trending-down-BC_FR2VT.js} +1 -1
- package/web-app/dist/assets/{trending-up-B44DCrt1.js → trending-up-CmA-WmLW.js} +1 -1
- package/web-app/dist/assets/{upload-4kWm33ZY.js → upload-BxLgBa1B.js} +1 -1
- package/web-app/dist/assets/{usePolling-zkUtRQG6.js → usePolling-DKNgwt5n.js} +1 -1
- package/web-app/dist/assets/{user-04cEVIe2.js → user-CA2DlmLc.js} +1 -1
- package/web-app/dist/index.html +1 -1
- package/memory/rag_injector.py +0 -161
- 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.
|
|
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.
|
|
486
|
+
**v11.9.1 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
11.
|
|
1
|
+
11.9.1
|
package/autonomy/lib/backlog.py
CHANGED
|
@@ -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)
|
package/autonomy/lib/chat.sh
CHANGED
|
@@ -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
|
|
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)
|
|
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
|
|
611
|
-
|
|
612
|
-
|
|
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
|
|