localcode 0.3.7__py3-none-any.whl → 0.3.9__py3-none-any.whl

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.
localcode/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  __all__ = ["__version__"]
2
2
 
3
- __version__ = "0.3.7"
3
+ __version__ = "0.3.9"
localcode/agent/loop.py CHANGED
@@ -1240,24 +1240,44 @@ def run_agent_loop(
1240
1240
  and not ran_build_or_test(bash_history)
1241
1241
  ):
1242
1242
  _build_verify_nudges += 1
1243
+ # TIER-2 VERIFICATION: don't just NUDGE the model to typecheck
1244
+ # (a small model skips it or mishandles output) — RUN the
1245
+ # project's real typecheck/lint ourselves and feed the concrete
1246
+ # errors back. Catches semantic errors (wrong names, missing
1247
+ # imports) the per-write syntax check can't. Light: once per
1248
+ # turn, at completion. See tools.project_check.
1249
+ _proj_errors = None
1250
+ try:
1251
+ from ..tools.project_check import run_project_check
1252
+ out.print_info("Verifying — running the project's typecheck…")
1253
+ _proj_errors = run_project_check(str(app.repo_root))
1254
+ except Exception:
1255
+ _proj_errors = None
1243
1256
  try:
1244
1257
  from ..events import emit as _emit_bg
1245
- _emit_bg("auto_nudge", signal="build_unverified", round_idx=round_num)
1258
+ _emit_bg("auto_nudge", signal="build_unverified",
1259
+ round_idx=round_num, had_errors=bool(_proj_errors))
1246
1260
  except Exception:
1247
1261
  pass
1248
- out.print_info(
1249
- "Changed code but never built/ran it nudging it to build "
1250
- "and fix before finishing."
1251
- )
1252
- messages.append({"role": "user", "content": (
1253
- "SYSTEM: You changed code but never built, type-checked, or ran "
1254
- "it this turn. Run the project's build/typecheck now (e.g. "
1255
- "`npm run build` or `npx tsc --noEmit` for TypeScript; the test "
1256
- "command or an import smoke-check for Python), FIX every error it "
1257
- "reports, then finish. Do not claim it works without building it."
1258
- )})
1262
+ if _proj_errors:
1263
+ out.print_info("Typecheck found errors sending them back to fix.")
1264
+ messages.append({"role": "user", "content": (
1265
+ "SYSTEM: the project's typecheck/build was run for you and "
1266
+ "reported errors. FIX each one (targeted edit_file changes), "
1267
+ "then finish. Do not claim it works until these are gone:\n\n"
1268
+ + _proj_errors
1269
+ )})
1270
+ else:
1271
+ # No checker available, or it passed fall back to advising.
1272
+ out.print_info("Changed code but never built/ran it — nudging to verify.")
1273
+ messages.append({"role": "user", "content": (
1274
+ "SYSTEM: You changed code but never built, type-checked, or ran "
1275
+ "it. Run the project's build/typecheck (npm run build / npx tsc "
1276
+ "--noEmit for TS; tests or an import smoke-check for Python), fix "
1277
+ "every error, then finish. Don't claim it works without building."
1278
+ )})
1259
1279
  _ephemeral_nudge_indices.append(len(messages) - 1)
1260
- continue # don't accept completion — let it build + fix first
1280
+ continue # don't accept completion — let it fix first
1261
1281
  if _goal_state.goal_type == "run_or_launch":
1262
1282
  _task_port = int(getattr(_task_state, "active_port", 0) or 0)
1263
1283
  content = ground_run_or_launch_text(content, _task_port)
@@ -0,0 +1,118 @@
1
+ """Tier-2 verification: run the project's OWN typecheck/lint once, return errors.
2
+
3
+ The big agents catch semantic errors with a persistent language server (LSP).
4
+ opencode's own docs say that for many projects it's better to run the project's
5
+ diagnostic CLI directly — LSPs "get out of sync, use significant memory … and
6
+ slow down agent workflows." That's decisive for localcode: a 16 GB Mac already
7
+ running a large local model can't spare an always-on tsserver/pyright.
8
+
9
+ So instead we run the project's real typecheck ONCE, when the model is about to
10
+ finish an unverified build, and feed the concrete errors back deterministically
11
+ (the model can't skip it, and gets ground truth like "line 37: 'getCardsForCard'
12
+ does not exist" — the semantic errors a per-write syntax check can't catch).
13
+
14
+ Light: one bounded subprocess at completion, not per-edit. Returns None when the
15
+ project is clean or no checker is available.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import os
21
+ import re
22
+ import shutil
23
+ import subprocess
24
+
25
+ _TIMEOUT = 60.0
26
+ _MAX_ERR_CHARS = 2500
27
+ _ANSI = re.compile(r"\x1b\[[0-9;]*m")
28
+
29
+
30
+ def run_project_check(repo_root: str) -> str | None:
31
+ """Run the first available typecheck/lint for the project; return a bounded
32
+ error summary, or None if clean / nothing to run."""
33
+ env = {**os.environ, "NO_COLOR": "1", "FORCE_COLOR": "0"}
34
+ for label, argv, cwd in _detect_commands(repo_root):
35
+ try:
36
+ r = subprocess.run(
37
+ argv, cwd=cwd, capture_output=True, text=True,
38
+ timeout=_TIMEOUT, env=env,
39
+ )
40
+ except Exception:
41
+ continue
42
+ if r.returncode != 0:
43
+ out = _ANSI.sub("", ((r.stdout or "") + "\n" + (r.stderr or "")).strip())
44
+ # Keep only the most useful lines, capped, so we don't flood a
45
+ # small model's context with a wall of output.
46
+ lines = [l for l in out.splitlines() if l.strip()][:40]
47
+ if lines:
48
+ return f"[{label}] reported errors:\n" + "\n".join(lines)[:_MAX_ERR_CHARS]
49
+ return None
50
+
51
+
52
+ def _detect_commands(repo_root: str) -> list[tuple[str, list[str], str]]:
53
+ """Ordered (label, argv, cwd) checkers to try. First that runs wins."""
54
+ cmds: list[tuple[str, list[str], str]] = []
55
+
56
+ # ── JS / TS ── prefer a project "typecheck" script, else tsc --noEmit.
57
+ pj_dir = _nearest_with(repo_root, "package.json")
58
+ if pj_dir:
59
+ node_modules = os.path.join(pj_dir, "node_modules")
60
+ binp = os.path.join(node_modules, ".bin")
61
+ scripts = {}
62
+ try:
63
+ scripts = (json.load(open(os.path.join(pj_dir, "package.json"))) or {}).get("scripts", {})
64
+ except Exception:
65
+ scripts = {}
66
+ # Only run node-based checks once deps are installed (else every import
67
+ # is a false "cannot find module" error that would derail the model).
68
+ if os.path.isdir(node_modules):
69
+ if "typecheck" in scripts:
70
+ cmds.append(("npm run typecheck", ["npm", "run", "--silent", "typecheck"], pj_dir))
71
+ elif os.path.exists(os.path.join(pj_dir, "tsconfig.json")) and os.path.exists(os.path.join(binp, "tsc")):
72
+ cmds.append(("tsc --noEmit", [os.path.join(binp, "tsc"), "--noEmit", "--pretty", "false"], pj_dir))
73
+ elif "lint" in scripts:
74
+ cmds.append(("npm run lint", ["npm", "run", "--silent", "lint"], pj_dir))
75
+
76
+ # ── Python ── ruff for REAL errors only (E9 syntax + F pyflakes: undefined
77
+ # names, bad imports) — not style noise. Falls back to compileall (syntax).
78
+ if _has_ext(repo_root, ".py"):
79
+ if shutil.which("ruff"):
80
+ cmds.append(("ruff", ["ruff", "check", "--select", "E9,F",
81
+ "--no-cache", "--output-format", "concise", "."], repo_root))
82
+ elif shutil.which("python3") or shutil.which("python"):
83
+ py = shutil.which("python3") or shutil.which("python")
84
+ cmds.append(("python -m compileall", [py, "-m", "compileall", "-q", "."], repo_root))
85
+
86
+ # ── Go ── the compiler catches type/undefined errors (like tsc). go.mod.
87
+ go_dir = _nearest_with(repo_root, "go.mod")
88
+ if go_dir and shutil.which("go"):
89
+ cmds.append(("go build", ["go", "build", "./..."], go_dir))
90
+
91
+ # ── Rust ── cargo check is the type-checker (fast, no codegen). Cargo.toml.
92
+ rust_dir = _nearest_with(repo_root, "Cargo.toml")
93
+ if rust_dir and shutil.which("cargo"):
94
+ cmds.append(("cargo check", ["cargo", "check", "--message-format", "short"], rust_dir))
95
+
96
+ # (Interpreted langs — Ruby/PHP/shell — have no whole-project type-check;
97
+ # their per-file syntax linters run in the Tier-1 syntax_check on each write.)
98
+ return cmds
99
+
100
+
101
+ def _nearest_with(root: str, filename: str) -> str | None:
102
+ """The shallowest directory under root that contains `filename`."""
103
+ root = os.path.abspath(root)
104
+ if os.path.exists(os.path.join(root, filename)):
105
+ return root
106
+ for base, dirs, files in os.walk(root):
107
+ dirs[:] = [d for d in dirs if d not in (".git", "node_modules", "dist", "build", ".venv")]
108
+ if filename in files:
109
+ return base
110
+ return None
111
+
112
+
113
+ def _has_ext(root: str, ext: str) -> bool:
114
+ for base, dirs, files in os.walk(os.path.abspath(root)):
115
+ dirs[:] = [d for d in dirs if d not in (".git", "node_modules", "dist", "build", ".venv")]
116
+ if any(f.endswith(ext) for f in files):
117
+ return True
118
+ return False
@@ -1,23 +1,22 @@
1
- """One-shot syntax validation for files the agent writes.
2
-
3
- Poor-man's LSP. The big agents (Claude Code, opencode) run a persistent
4
- language server and feed its diagnostics back after each edit; codex uses
5
- verified structured patches. A persistent LSP is too heavy for localcode's
6
- target a 16 GB Mac already running a large local model. So instead we run a
7
- FAST, ONE-SHOT syntax check after each write and, if it fails, append the exact
8
- error to the tool result.
9
-
10
- Why this matters for a LOCAL model specifically: a small quantized model drifts
11
- into structural typos (`useState(0]`, an unterminated string, a missing bracket,
12
- `getCardsForCard`). With no verification the model has to NOTICE its own typo —
13
- which weak models are bad at so it re-reads the broken file, the broken code
14
- enters its context, and it reproduces the error (self-conditioning loop). A
15
- deterministic check gives it GROUND TRUTH ("line 37: unexpected token") so it
16
- fixes the exact line instead of guessing.
17
-
18
- Best-effort and non-blocking: the write ALWAYS succeeds; this only appends a
19
- warning. Any checker failure/absence returns None (no false alarms). Disable
20
- with LOCALCODE_SYNTAX_CHECK=0.
1
+ """One-shot syntax validation for files the agent writes — in-process, offline.
2
+
3
+ Tier-1 verification. The big agents use a persistent language server (LSP);
4
+ opencode's own docs say that's often worse than running diagnostics directly
5
+ (LSPs "get out of sync, use significant memory, slow down agent workflows") —
6
+ decisive for a 16 GB Mac already running a large local model.
7
+
8
+ Crucially this must work with NO tools the user has to install and NO network:
9
+ it's bundled. We parse with tree-sitter (grammars compiled into the wheel — one
10
+ ~4.5 MB native extension covering 100+ languages) and flag any syntax error
11
+ in-process. So when a small quantized model drifts into a structural typo
12
+ (`useState(0]`, an unterminated string, a missing bracket), the write tool
13
+ appends the exact error and line, and the model fixes THAT line instead of
14
+ re-reading the broken file and reproducing the error (the self-conditioning
15
+ loop). Python/JSON keep their native checkers for sharper messages.
16
+
17
+ Semantic/type errors (wrong names, missing imports) are caught separately by
18
+ project_check at completion (tsc/ruff/go/cargo). Disable with
19
+ LOCALCODE_SYNTAX_CHECK=0.
21
20
  """
22
21
  from __future__ import annotations
23
22
 
@@ -27,25 +26,34 @@ import subprocess
27
26
 
28
27
  _TIMEOUT = 5.0
29
28
 
29
+ # File extension → tree-sitter language name (all bundled in the language pack).
30
+ _TS_LANG = {
31
+ ".js": "javascript", ".mjs": "javascript", ".cjs": "javascript",
32
+ ".jsx": "tsx", ".ts": "typescript", ".mts": "typescript", ".cts": "typescript",
33
+ ".tsx": "tsx", ".go": "go", ".rs": "rust", ".rb": "ruby", ".php": "php",
34
+ ".sh": "bash", ".bash": "bash", ".zsh": "bash", ".lua": "lua",
35
+ ".c": "c", ".h": "c", ".cpp": "cpp", ".cc": "cpp", ".cxx": "cpp",
36
+ ".hpp": "cpp", ".hh": "cpp", ".java": "java", ".kt": "kotlin",
37
+ ".swift": "swift", ".cs": "csharp", ".scala": "scala",
38
+ ".css": "css", ".scss": "scss", ".html": "html", ".xml": "xml",
39
+ ".yaml": "yaml", ".yml": "yaml", ".toml": "toml", ".sql": "sql",
40
+ }
30
41
 
31
- def check_syntax(path: str, content: str) -> str | None:
32
- """Return a one-line syntax-error string, or None if OK / not checkable.
33
42
 
34
- In-process for JSON/Python (instant, zero deps); a fast one-shot subprocess
35
- for JS (node --check) and TS/JSX (esbuild, when the project has it).
36
- """
43
+ def check_syntax(path: str, content: str) -> str | None:
44
+ """Return a one-line syntax-error string, or None if OK / not checkable."""
37
45
  if os.environ.get("LOCALCODE_SYNTAX_CHECK") == "0":
38
46
  return None
39
47
  ext = os.path.splitext(path)[1].lower()
40
48
  try:
49
+ # Native checkers give the sharpest messages; use them where free.
41
50
  if ext == ".json":
42
51
  return _check_json(content)
43
52
  if ext in (".py", ".pyi"):
44
53
  return _check_python(content, path)
45
- if ext in (".js", ".mjs", ".cjs"):
46
- return _check_node(path)
47
- if ext in (".ts", ".tsx", ".jsx", ".mts", ".cts"):
48
- return _check_esbuild(path)
54
+ lang = _TS_LANG.get(ext)
55
+ if lang:
56
+ return _check_treesitter(content, lang)
49
57
  except Exception:
50
58
  return None
51
59
  return None
@@ -68,66 +76,47 @@ def _check_python(content: str, path: str) -> str | None:
68
76
  return f"Python syntax error at {loc}: {e.msg}"
69
77
 
70
78
 
71
- def _check_node(path: str) -> str | None:
72
- import shutil
73
- if not shutil.which("node"):
74
- return None
75
- r = subprocess.run(
76
- ["node", "--check", path],
77
- capture_output=True, text=True, timeout=_TIMEOUT,
78
- )
79
- if r.returncode != 0:
80
- return _first_error_line(r.stderr)
81
- return None
82
-
83
-
84
- def _find_bin(start: str, name: str) -> str | None:
85
- """Walk up from the file's dir to find node_modules/.bin/<name>."""
86
- d = os.path.dirname(os.path.abspath(start))
87
- for _ in range(25):
88
- cand = os.path.join(d, "node_modules", ".bin", name)
89
- if os.path.exists(cand):
90
- return cand
91
- parent = os.path.dirname(d)
92
- if parent == d:
93
- break
94
- d = parent
95
- return None
96
-
97
-
98
- def _check_esbuild(path: str) -> str | None:
99
- # esbuild parses TS/TSX/JSX and reports syntax errors instantly. Only used
100
- # when the project has it (Vite/most JS projects do post-install); if not
101
- # present we skip rather than risk a false alarm from a wrong parser.
102
- esbuild = _find_bin(path, "esbuild")
103
- if not esbuild:
104
- return None
105
- loader = "tsx" if path.endswith((".tsx", ".jsx")) else "ts"
106
- r = subprocess.run(
107
- [esbuild, path, f"--loader={loader}", "--log-level=error"],
108
- capture_output=True, text=True, timeout=_TIMEOUT,
109
- )
110
- if r.returncode != 0 and r.stderr.strip():
111
- return _first_error_line(r.stderr)
112
- return None
79
+ def _check_treesitter(content: str, lang: str) -> str | None:
80
+ """Parse with a bundled tree-sitter grammar; report the first syntax error.
113
81
 
114
-
115
- def _first_error_line(stderr: str) -> str | None:
116
- """Pull the human-meaningful error out of a node/esbuild stderr dump.
117
-
118
- Prefer the actual `SyntaxError: …` / `error: …` message, skipping node's
119
- internal loader/stack frames (`node:internal/…`, ` at …`).
82
+ No external tools, no network — the grammars are compiled into the wheel.
83
+ Returns None if tree-sitter isn't available (graceful) or the file parses.
120
84
  """
121
- lines = [l.rstrip() for l in stderr.splitlines()]
122
- # First choice: the real error message line.
123
- for s in lines:
124
- t = s.strip()
125
- if t.startswith(("SyntaxError", "TypeError", "ReferenceError")) or " error:" in t.lower() or t.lower().startswith("error"):
126
- return t[:200]
127
- # Fallback: the "file:line" location node prints first (skip internals/stack).
128
- for s in lines:
129
- t = s.strip()
130
- if not t or t.startswith(("node:internal", "at ", "^")):
131
- continue
132
- return t[:200]
85
+ try:
86
+ from tree_sitter_language_pack import get_language
87
+ from tree_sitter import Parser
88
+ except Exception:
89
+ return None
90
+ try:
91
+ parser = Parser(get_language(lang))
92
+ tree = parser.parse(content.encode("utf-8"))
93
+ except Exception:
94
+ return None
95
+ root = tree.root_node
96
+ if not root.has_error:
97
+ return None
98
+ node = _first_error_node(root)
99
+ if node is None:
100
+ return f"syntax error detected in this {lang} file (unbalanced brackets/quotes or a stray token)"
101
+ line = node.start_point[0] + 1
102
+ col = node.start_point[1] + 1
103
+ snippet = ""
104
+ try:
105
+ src_line = content.splitlines()[node.start_point[0]].strip()
106
+ snippet = f" — near: {src_line[:80]}"
107
+ except Exception:
108
+ snippet = ""
109
+ kind = "missing token" if node.is_missing else "unexpected token"
110
+ return f"syntax error (line {line} col {col}): {kind}{snippet}"
111
+
112
+
113
+ def _first_error_node(root):
114
+ """DFS for the first ERROR or MISSING node (the actual syntax fault)."""
115
+ stack = [root]
116
+ while stack:
117
+ n = stack.pop()
118
+ if n.is_error or n.is_missing:
119
+ return n
120
+ # push children in order so we return the earliest error
121
+ stack.extend(reversed(n.children))
133
122
  return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: localcode
3
- Version: 0.3.7
3
+ Version: 0.3.9
4
4
  Summary: High-performance AI coding on consumer hardware.
5
5
  Author: LocalCode contributors
6
6
  License-Expression: Apache-2.0
@@ -35,6 +35,8 @@ Requires-Dist: prompt_toolkit>=3.0.52
35
35
  Requires-Dist: rich>=13.7.1
36
36
  Requires-Dist: scikit-learn>=1.3.0
37
37
  Requires-Dist: textual>=8.2.4
38
+ Requires-Dist: tree-sitter>=0.23.0
39
+ Requires-Dist: tree-sitter-language-pack>=1.0.0
38
40
  Requires-Dist: tomli>=2.0.0; python_version < "3.11"
39
41
  Provides-Extra: voice
40
42
  Requires-Dist: pywhispercpp>=1.2.0; extra == "voice"
@@ -1,4 +1,4 @@
1
- localcode/__init__.py,sha256=7scw5ecMd3F-dCMkmY63HI0FKsdj4ANkgybevu64WuQ,49
1
+ localcode/__init__.py,sha256=dv_OdIY5KS_HiRjBMSmtKnar5Axf3OVYIDjhWUodlAY,49
2
2
  localcode/__main__.py,sha256=_4Tblte9F1KOpB-oGBQuTlaSN-IncLmhihqhBkpH3pU,69
3
3
  localcode/_subproc_env.py,sha256=eltq_kKvqGj22pIFivB5BZft9-s5fziBW0lsV9FhEV4,2383
4
4
  localcode/app.py,sha256=FSwpce6Jfn7zJmhL1ipaqTq0xKh0MxCMnEnMQuFV8L8,67820
@@ -71,7 +71,7 @@ localcode/agent/context.py,sha256=adV_WlFBHMDHsWdndRgrkZqRweDD9plyqMP_2X74WAw,37
71
71
  localcode/agent/goal.py,sha256=X87Q3rsdp70wOfkpbh35M4qFWSNyklX3RuRWjv-pCXs,6264
72
72
  localcode/agent/helpers.py,sha256=vutFyCtnca0UnF0wgk7LdsqVWdiqyYm4YnFOxtnCJ_A,15004
73
73
  localcode/agent/hooks.py,sha256=XadEbSM7gHtKlLrqSKnvcQFcoWoNUXdlMdIPS-EwBGA,15841
74
- localcode/agent/loop.py,sha256=VcD7fjkoJtx8pECsL2tp-pMSom5qOkec1fy9nDgj3EE,99760
74
+ localcode/agent/loop.py,sha256=Gp4Zt0fRpQkE1YLZjrF9xtzM4qlJodyFeGGupx_U16k,101105
75
75
  localcode/agent/prompt_context.py,sha256=abJPLMhTzuVcsW4AfMBIW-SEqmrU69DhI-0vGZllZVU,13707
76
76
  localcode/agent/prompts.py,sha256=LCJBM2mV_SKc1OXtH0-ih9OPQnhrTh3vTlrOgLUiVDA,14845
77
77
  localcode/agent/recovery.py,sha256=-2vnYhhUGZD_reR6CaMOYNsG5XRf6YDsjTqF-1P-St4,16831
@@ -109,9 +109,10 @@ localcode/tools/launch_app.py,sha256=TVoq1ZQEIm2yQ607LGkJwbIC3TkvaLsBpTJgYIGX1gM
109
109
  localcode/tools/list_files.py,sha256=wN2dXU01rrKB_HGh6i0GfM9qvBwx7Qoa3S5WZlArma0,1205
110
110
  localcode/tools/multi_edit.py,sha256=WkGibiGkr_J-Ld4CtxR24icdy1kGQuPEh6kKDrKe8Ss,4780
111
111
  localcode/tools/plan_mode.py,sha256=2RU6QH1IJoOTKuoAr7Ga7uTaebAS8v7OFt5v1zLEXFw,2886
112
+ localcode/tools/project_check.py,sha256=B3Q6IB2XfllW3Nlxwz2pCCQwF_b0VSUBnrpCHS4eOE8,5508
112
113
  localcode/tools/read_file.py,sha256=gH852nnPiVD2Go9VcdS_EFbtnqYaGOPyAk5jo1cNbX8,4556
113
114
  localcode/tools/skill_tool.py,sha256=vTVPyRWxICtDCVYWMiAMInGnc-wGAk2L1EZn-6e79nE,1478
114
- localcode/tools/syntax_check.py,sha256=wojE6Ke6nMe3ptyh9T7zLp5roGKYaez3TwSpnp8nE8A,4869
115
+ localcode/tools/syntax_check.py,sha256=tpymYFeXs7LYU5qmfHGJeVX0WUdFYBineI9GN3jONYU,4726
115
116
  localcode/tools/todo_write.py,sha256=8A4k9MzoqOtzJ6y2Gd3lEGkINcpV3JVCd2lYBmdd6qI,5801
116
117
  localcode/tools/web_fetch.py,sha256=UPhMrLipwN5xJx5-zr634gL4p6nIRqYI2_7tbUw_8Go,2770
117
118
  localcode/tools/web_search.py,sha256=ILfBBx50p00OjA939HzdDmdao4QLL8sRuxlBoUDjmC8,1122
@@ -132,9 +133,9 @@ localcode/tui/widgets/chat_log.py,sha256=D5DUkrQXL_ilxyC8N1xIPLw4Pqeh0jSWt1mPgho
132
133
  localcode/tui/widgets/voice_visualizer.py,sha256=zOhrIxaMcg-F2Y8LvHJTddq227phmiTjtEyRbkx1XxU,4709
133
134
  localcode/tui/widgets/messages/__init__.py,sha256=952AZ1qVMGUIqPIry7mwxnYbMkgPlcY5FAwZtEa5YPg,967
134
135
  localcode/tui/widgets/messages/diff.py,sha256=khDf_MThrQz0S62-t-m7i3OU5cdNolUk1TCeKMRPDQI,10910
135
- localcode-0.3.7.dist-info/licenses/LICENSE,sha256=Ahg0cteZ-6bIRB7KC15WEtdqWnzy8HY_yzsZBoVRql0,11294
136
- localcode-0.3.7.dist-info/METADATA,sha256=Qg7wa61uxkFvD8SkVHWHQ1kEzZmnlK6H8e_SFlzm-0o,7813
137
- localcode-0.3.7.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
138
- localcode-0.3.7.dist-info/entry_points.txt,sha256=rE7elTGUvMsBd-Quy22cuTVJ-zGVOmx0nEuC6ni9Tg4,87
139
- localcode-0.3.7.dist-info/top_level.txt,sha256=58eL3Rw8v0OGmYNxjx8uS4ERWatrzUbyCL5inlukkTo,10
140
- localcode-0.3.7.dist-info/RECORD,,
136
+ localcode-0.3.9.dist-info/licenses/LICENSE,sha256=Ahg0cteZ-6bIRB7KC15WEtdqWnzy8HY_yzsZBoVRql0,11294
137
+ localcode-0.3.9.dist-info/METADATA,sha256=iT5SYY0r4-zju8UthKU8MTPF4w_3HRk0H11CIB_8dQ0,7896
138
+ localcode-0.3.9.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
139
+ localcode-0.3.9.dist-info/entry_points.txt,sha256=rE7elTGUvMsBd-Quy22cuTVJ-zGVOmx0nEuC6ni9Tg4,87
140
+ localcode-0.3.9.dist-info/top_level.txt,sha256=58eL3Rw8v0OGmYNxjx8uS4ERWatrzUbyCL5inlukkTo,10
141
+ localcode-0.3.9.dist-info/RECORD,,