@grifhinz/logics-manager 2.5.0 → 2.5.2
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/README.md +5 -3
- package/VERSION +1 -1
- package/clients/shared-web/media/css/layout.css +1 -0
- package/clients/viewer/browser-host.js +33 -1
- package/clients/viewer/viewer.css +46 -0
- package/logics_manager/cli.py +7 -1
- package/logics_manager/mcp.py +10 -2
- package/logics_manager/viewer.py +49 -3
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/AlexAgo83/logics-manager/actions/workflows/ci.yml)
|
|
4
4
|
[](LICENSE)
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|

|
|
8
8
|

|
|
@@ -230,13 +230,15 @@ npm install -g @grifhinz/logics-manager@latest
|
|
|
230
230
|
If npm reports a successful update but `logics-manager --version` still shows an older version, another installation is earlier on `PATH`. Diagnose it with:
|
|
231
231
|
|
|
232
232
|
```bash
|
|
233
|
-
|
|
233
|
+
type -a logics-manager
|
|
234
|
+
whence -a logics-manager # zsh
|
|
235
|
+
pipx list
|
|
234
236
|
npm prefix -g
|
|
235
237
|
npm list -g @grifhinz/logics-manager --depth=0
|
|
236
238
|
"$(npm prefix -g)/bin/logics-manager" --version
|
|
237
239
|
```
|
|
238
240
|
|
|
239
|
-
If the direct npm binary shows the expected version, remove the older Python install or move the npm global `bin` directory earlier on `PATH`.
|
|
241
|
+
If the direct npm binary shows the expected version, remove the older Python install or move the npm global `bin` directory earlier on `PATH`. In zsh, run `rehash` or open a new terminal after changing installs so the shell forgets any cached command location.
|
|
240
242
|
|
|
241
243
|
## VS Code Extension
|
|
242
244
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.5.
|
|
1
|
+
2.5.2
|
|
@@ -1751,6 +1751,14 @@
|
|
|
1751
1751
|
<span class="viewer-git__domain-label">${escapeHtml(label)}${key === "changes" ? gitBadgeHtml("changes") : ""}${key === "history" ? gitBadgeHtml("history") : ""}</span><strong>${escapeHtml(count)}</strong>
|
|
1752
1752
|
</button>
|
|
1753
1753
|
`).join("");
|
|
1754
|
+
const renderChangeStats = (entry) => {
|
|
1755
|
+
const additions = Number(entry?.additions);
|
|
1756
|
+
const deletions = Number(entry?.deletions);
|
|
1757
|
+
if (!Number.isFinite(additions) || !Number.isFinite(deletions)) {
|
|
1758
|
+
return "";
|
|
1759
|
+
}
|
|
1760
|
+
return `<span class="viewer-git__file-changes" title="Line changes"><span class="viewer-git__file-additions">+${escapeHtml(additions)}</span><span class="viewer-git__file-deletions">-${escapeHtml(deletions)}</span></span>`;
|
|
1761
|
+
};
|
|
1754
1762
|
const renderFileSections = (allowedKeys) => groupDefs.filter(([key]) => allowedKeys.includes(key)).map(([key, label]) => {
|
|
1755
1763
|
const entries = Array.isArray(payload.groups?.[key]) ? payload.groups[key] : [];
|
|
1756
1764
|
if (!entries.length) {
|
|
@@ -1763,6 +1771,7 @@
|
|
|
1763
1771
|
<li>
|
|
1764
1772
|
<button class="viewer-git__file" type="button" data-viewer-git-file="${escapeHtml(entry.path)}" data-viewer-git-cached="${key === "staged" ? "1" : "0"}">
|
|
1765
1773
|
<span class="viewer-git__file-path">${escapeHtml(entry.from ? `${entry.from} -> ${entry.path}` : entry.path)}</span>
|
|
1774
|
+
${renderChangeStats(entry)}
|
|
1766
1775
|
${entry.logicsType ? `<span class="viewer-git__file-kind">${escapeHtml(entry.logicsType)}</span>` : ""}
|
|
1767
1776
|
</button>
|
|
1768
1777
|
</li>
|
|
@@ -1852,6 +1861,29 @@
|
|
|
1852
1861
|
});
|
|
1853
1862
|
}
|
|
1854
1863
|
|
|
1864
|
+
function gitDiffLineKind(line) {
|
|
1865
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
1866
|
+
return "add";
|
|
1867
|
+
}
|
|
1868
|
+
if (line.startsWith("-") && !line.startsWith("---")) {
|
|
1869
|
+
return "delete";
|
|
1870
|
+
}
|
|
1871
|
+
if (line.startsWith("@@")) {
|
|
1872
|
+
return "hunk";
|
|
1873
|
+
}
|
|
1874
|
+
if (line.startsWith("diff --git") || line.startsWith("index ") || line.startsWith("+++") || line.startsWith("---")) {
|
|
1875
|
+
return "meta";
|
|
1876
|
+
}
|
|
1877
|
+
return "context";
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
function renderGitDiffPreview(content) {
|
|
1881
|
+
return String(content)
|
|
1882
|
+
.split("\n")
|
|
1883
|
+
.map((line) => `<span class="viewer-git__diff-line viewer-git__diff-line--${gitDiffLineKind(line)}">${escapeHtml(line || " ")}</span>`)
|
|
1884
|
+
.join("");
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1855
1887
|
async function loadGitDiff(path, cached, button = null) {
|
|
1856
1888
|
const diffPanel = document.querySelector("[data-viewer-git-diff]");
|
|
1857
1889
|
if (!(diffPanel instanceof HTMLElement) || !path) {
|
|
@@ -1873,7 +1905,7 @@
|
|
|
1873
1905
|
return;
|
|
1874
1906
|
}
|
|
1875
1907
|
const content = payload.diff || payload.message || "No diff is available for this file.";
|
|
1876
|
-
diffPanel.innerHTML = `<div class="viewer-git__diff-meta">${escapeHtml(payload.path || path)} · ${escapeHtml(payload.mode || "worktree")}${payload.truncated ? " · truncated" : ""}</div><pre><code>${
|
|
1908
|
+
diffPanel.innerHTML = `<div class="viewer-git__diff-meta">${escapeHtml(payload.path || path)} · ${escapeHtml(payload.mode || "worktree")}${payload.truncated ? " · truncated" : ""}</div><pre><code>${renderGitDiffPreview(content)}</code></pre>`;
|
|
1877
1909
|
}
|
|
1878
1910
|
|
|
1879
1911
|
function applyGitDomain(domain) {
|
|
@@ -829,6 +829,23 @@
|
|
|
829
829
|
overflow-wrap: anywhere;
|
|
830
830
|
}
|
|
831
831
|
|
|
832
|
+
.viewer-git__file-changes {
|
|
833
|
+
display: inline-flex;
|
|
834
|
+
align-items: center;
|
|
835
|
+
gap: 5px;
|
|
836
|
+
flex: 0 0 auto;
|
|
837
|
+
font-family: var(--vscode-editor-font-family, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
838
|
+
font-size: 11px;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
.viewer-git__file-additions {
|
|
842
|
+
color: #7ee787;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
.viewer-git__file-deletions {
|
|
846
|
+
color: #ffaaa5;
|
|
847
|
+
}
|
|
848
|
+
|
|
832
849
|
.viewer-git__file-kind {
|
|
833
850
|
flex: 0 0 auto;
|
|
834
851
|
padding: 2px 6px;
|
|
@@ -871,6 +888,35 @@
|
|
|
871
888
|
white-space: pre;
|
|
872
889
|
}
|
|
873
890
|
|
|
891
|
+
.viewer-git__diff-line {
|
|
892
|
+
display: block;
|
|
893
|
+
min-height: 1.35em;
|
|
894
|
+
padding: 0 8px;
|
|
895
|
+
border-left: 3px solid transparent;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
.viewer-git__diff-line--add {
|
|
899
|
+
border-left-color: #2ea043;
|
|
900
|
+
background: rgba(46, 160, 67, 0.16);
|
|
901
|
+
color: #7ee787;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
.viewer-git__diff-line--delete {
|
|
905
|
+
border-left-color: #f85149;
|
|
906
|
+
background: rgba(248, 81, 73, 0.16);
|
|
907
|
+
color: #ffaaa5;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
.viewer-git__diff-line--hunk {
|
|
911
|
+
border-left-color: var(--vscode-textLink-foreground, #4ea1ff);
|
|
912
|
+
background: color-mix(in srgb, var(--vscode-textLink-foreground, #4ea1ff) 14%, transparent);
|
|
913
|
+
color: var(--vscode-textLink-foreground, #4ea1ff);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
.viewer-git__diff-line--meta {
|
|
917
|
+
color: var(--vscode-descriptionForeground, #aaaaaa);
|
|
918
|
+
}
|
|
919
|
+
|
|
874
920
|
.viewer-git__commit,
|
|
875
921
|
.viewer-git__state {
|
|
876
922
|
margin: 0;
|
package/logics_manager/cli.py
CHANGED
|
@@ -195,15 +195,21 @@ def _find_executable_paths(command: str) -> list[str]:
|
|
|
195
195
|
def _print_path_conflict_guidance(paths: list[str]) -> None:
|
|
196
196
|
if len(paths) <= 1:
|
|
197
197
|
return
|
|
198
|
+
path_lines = [f" - {path}" for path in paths]
|
|
198
199
|
print(
|
|
199
200
|
"\n".join(
|
|
200
201
|
[
|
|
201
202
|
"",
|
|
202
203
|
"Multiple logics-manager executables are on PATH. If --version still shows an older release, an earlier install is taking precedence.",
|
|
204
|
+
"Detected executables:",
|
|
205
|
+
*path_lines,
|
|
203
206
|
"Diagnose with:",
|
|
204
|
-
"
|
|
207
|
+
" type -a logics-manager",
|
|
208
|
+
" whence -a logics-manager # zsh",
|
|
205
209
|
" pipx list",
|
|
206
210
|
" npm list -g @grifhinz/logics-manager --depth=0",
|
|
211
|
+
"",
|
|
212
|
+
"If you recently changed installs in zsh, run `rehash` or open a new terminal before retrying.",
|
|
207
213
|
]
|
|
208
214
|
)
|
|
209
215
|
)
|
package/logics_manager/mcp.py
CHANGED
|
@@ -427,7 +427,7 @@ def _markdown_file_path(repo_root: Path, raw_path: str, allowed_dirs: tuple[str,
|
|
|
427
427
|
|
|
428
428
|
def _run_command(repo_root: Path, args: list[str]) -> subprocess.CompletedProcess[str]:
|
|
429
429
|
command = [sys.executable, "-m", "logics_manager", *args]
|
|
430
|
-
result = subprocess.run(command, cwd=repo_root, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
430
|
+
result = subprocess.run(command, cwd=repo_root, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, env=_subprocess_env())
|
|
431
431
|
if result.returncode != 0:
|
|
432
432
|
raise McpToolError(
|
|
433
433
|
"command_failed",
|
|
@@ -439,7 +439,7 @@ def _run_command(repo_root: Path, args: list[str]) -> subprocess.CompletedProces
|
|
|
439
439
|
|
|
440
440
|
def _run_json_command(repo_root: Path, args: list[str]) -> dict[str, Any]:
|
|
441
441
|
command = [sys.executable, "-m", "logics_manager", *args]
|
|
442
|
-
result = subprocess.run(command, cwd=repo_root, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
442
|
+
result = subprocess.run(command, cwd=repo_root, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, env=_subprocess_env())
|
|
443
443
|
payload = _json_from_stdout_or_none(result.stdout)
|
|
444
444
|
if payload is None:
|
|
445
445
|
raise McpToolError(
|
|
@@ -450,6 +450,14 @@ def _run_json_command(repo_root: Path, args: list[str]) -> dict[str, Any]:
|
|
|
450
450
|
return payload
|
|
451
451
|
|
|
452
452
|
|
|
453
|
+
def _subprocess_env() -> dict[str, str]:
|
|
454
|
+
env = os.environ.copy()
|
|
455
|
+
source_root = str(Path(__file__).resolve().parents[1])
|
|
456
|
+
existing = env.get("PYTHONPATH")
|
|
457
|
+
env["PYTHONPATH"] = source_root if not existing else os.pathsep.join([source_root, existing])
|
|
458
|
+
return env
|
|
459
|
+
|
|
460
|
+
|
|
453
461
|
def _json_from_stdout(stdout: str) -> dict[str, Any]:
|
|
454
462
|
start = stdout.find("{")
|
|
455
463
|
end = stdout.rfind("}")
|
package/logics_manager/viewer.py
CHANGED
|
@@ -14,6 +14,7 @@ from dataclasses import dataclass
|
|
|
14
14
|
from datetime import datetime
|
|
15
15
|
from http import HTTPStatus
|
|
16
16
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
17
|
+
from importlib import metadata
|
|
17
18
|
from pathlib import Path
|
|
18
19
|
from typing import Any
|
|
19
20
|
from urllib.parse import parse_qs, quote, unquote, urlencode, urlparse
|
|
@@ -56,8 +57,14 @@ NODE_MERMAID_ROOT = REPO_ROOT / "node_modules" / "mermaid" / "dist"
|
|
|
56
57
|
|
|
57
58
|
def _current_version() -> str:
|
|
58
59
|
try:
|
|
59
|
-
|
|
60
|
+
version = (REPO_ROOT / "VERSION").read_text(encoding="utf-8").strip()
|
|
60
61
|
except OSError:
|
|
62
|
+
version = ""
|
|
63
|
+
if version:
|
|
64
|
+
return version
|
|
65
|
+
try:
|
|
66
|
+
return metadata.version("logics-manager")
|
|
67
|
+
except metadata.PackageNotFoundError:
|
|
61
68
|
return "0.0.0"
|
|
62
69
|
|
|
63
70
|
|
|
@@ -489,7 +496,38 @@ def _parse_recent_git_commits(output: str) -> list[dict[str, str]]:
|
|
|
489
496
|
return commits
|
|
490
497
|
|
|
491
498
|
|
|
492
|
-
def
|
|
499
|
+
def _parse_git_numstat(output: str) -> dict[str, dict[str, int]]:
|
|
500
|
+
stats: dict[str, dict[str, int]] = {}
|
|
501
|
+
for line in output.splitlines():
|
|
502
|
+
parts = line.split("\t")
|
|
503
|
+
if len(parts) < 3:
|
|
504
|
+
continue
|
|
505
|
+
raw_additions, raw_deletions, raw_path = parts[:3]
|
|
506
|
+
try:
|
|
507
|
+
additions = int(raw_additions)
|
|
508
|
+
deletions = int(raw_deletions)
|
|
509
|
+
except ValueError:
|
|
510
|
+
continue
|
|
511
|
+
path = raw_path.strip()
|
|
512
|
+
if " => " in path:
|
|
513
|
+
path = path.split(" => ", 1)[1].strip("{}")
|
|
514
|
+
if path:
|
|
515
|
+
stats[path] = {"additions": additions, "deletions": deletions}
|
|
516
|
+
return stats
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def _attach_git_change_stats(groups: dict[str, list[dict[str, Any]]], staged_stats: dict[str, dict[str, int]], worktree_stats: dict[str, dict[str, int]]) -> None:
|
|
520
|
+
for key, entries in groups.items():
|
|
521
|
+
stats_source = staged_stats if key == "staged" else worktree_stats
|
|
522
|
+
for entry in entries:
|
|
523
|
+
path = str(entry.get("path", ""))
|
|
524
|
+
stats = stats_source.get(path) or staged_stats.get(path) or worktree_stats.get(path)
|
|
525
|
+
if stats:
|
|
526
|
+
entry["additions"] = stats["additions"]
|
|
527
|
+
entry["deletions"] = stats["deletions"]
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def _count_unique_git_status_paths(groups: dict[str, list[dict[str, Any]]]) -> int:
|
|
493
531
|
paths: set[str] = set()
|
|
494
532
|
for entries in groups.values():
|
|
495
533
|
for entry in entries:
|
|
@@ -536,6 +574,8 @@ def git_status_payload(repo_root: Path, *, runner: Any | None = None, which: Any
|
|
|
536
574
|
|
|
537
575
|
try:
|
|
538
576
|
status = _run_read_only_git(repo_root, ["status", "--porcelain=v1", "-b"], runner=runner)
|
|
577
|
+
staged_numstat = _run_read_only_git(repo_root, ["diff", "--no-ext-diff", "--numstat", "--cached"], runner=runner)
|
|
578
|
+
worktree_numstat = _run_read_only_git(repo_root, ["diff", "--no-ext-diff", "--numstat"], runner=runner)
|
|
539
579
|
commit = _run_read_only_git(repo_root, ["log", "-1", "--pretty=format:%h %s"], runner=runner)
|
|
540
580
|
recent_commits = _run_read_only_git(
|
|
541
581
|
repo_root,
|
|
@@ -551,12 +591,18 @@ def git_status_payload(repo_root: Path, *, runner: Any | None = None, which: Any
|
|
|
551
591
|
|
|
552
592
|
lines = status.stdout.splitlines()
|
|
553
593
|
branch_info = _parse_git_branch_line(lines[0]) if lines else {"branch": "HEAD", "tracking": "", "ahead": 0, "behind": 0}
|
|
554
|
-
groups: dict[str, list[dict[str,
|
|
594
|
+
groups: dict[str, list[dict[str, Any]]] = {key: [] for key in ("staged", "modified", "deleted", "renamed", "untracked")}
|
|
555
595
|
for line in lines[1:]:
|
|
556
596
|
classified = _classify_porcelain_entry(line)
|
|
557
597
|
if classified:
|
|
558
598
|
group, entry = classified
|
|
559
599
|
groups[group].append(entry)
|
|
600
|
+
if staged_numstat.returncode == 0 or worktree_numstat.returncode == 0:
|
|
601
|
+
_attach_git_change_stats(
|
|
602
|
+
groups,
|
|
603
|
+
_parse_git_numstat(staged_numstat.stdout if staged_numstat.returncode == 0 else ""),
|
|
604
|
+
_parse_git_numstat(worktree_numstat.stdout if worktree_numstat.returncode == 0 else ""),
|
|
605
|
+
)
|
|
560
606
|
counts = {key: len(value) for key, value in groups.items()}
|
|
561
607
|
uncommitted_files = _count_unique_git_status_paths(groups)
|
|
562
608
|
dirty = any(counts.values())
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@grifhinz/logics-manager",
|
|
3
3
|
"displayName": "Logics Orchestrator",
|
|
4
4
|
"description": "Visual orchestration for Logics workflows inside VS Code.",
|
|
5
|
-
"version": "2.5.
|
|
5
|
+
"version": "2.5.2",
|
|
6
6
|
"publisher": "cdx-logics",
|
|
7
7
|
"icon": "clients/shared-web/media/icon.png",
|
|
8
8
|
"repository": {
|