gemcode 0.3.95__py3-none-any.whl → 0.3.97__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.
gemcode/agent.py CHANGED
@@ -934,6 +934,8 @@ You have two tools to persist project insights across sessions (auto-memory styl
934
934
  Notes are loaded at session start so future sessions inherit this knowledge.
935
935
 
936
936
  - **`read_project_notes()`** — read current notes **only when starting a real engineering task** (editing, debugging, building). Do NOT call this for greetings or general questions. If notes exist and you're about to work on a task, read them once to avoid re-discovering known information.
937
+
938
+ - **`summarise_session(focus="")`** — use this when the active session has become large or noisy and you want to preserve the important work before continuing. It writes a compact session summary, extracts durable facts into memory, and updates notes so a fresh follow-up session can stay lightweight.
937
939
  """
938
940
 
939
941
  # Inject capability-specific strategy sections only when those caps are on.
gemcode/invoke.py CHANGED
@@ -73,6 +73,10 @@ async def run_turn(
73
73
  # Dynamic risk score: updated each user message; later refined by tool outcomes.
74
74
  # This is intentionally heuristic but configurable via env knobs.
75
75
  if cfg is not None:
76
+ try:
77
+ object.__setattr__(cfg, "_active_session_id", session_id)
78
+ except Exception:
79
+ pass
76
80
  try:
77
81
  import re
78
82
  p = (prompt or "")[:20_000]
gemcode/repl_commands.py CHANGED
@@ -254,6 +254,7 @@ SLASH_COMMANDS: list[tuple[str, str]] = [
254
254
  ("skills", "List GemSkills"),
255
255
  ("status", "Model, capabilities, thinking, limits"),
256
256
  ("style", "Output styles · /style <name>|off"),
257
+ ("summarise", "Summarise current session, persist key points, then reset · /summarize same"),
257
258
  ("thinking", "Thinking verbose/brief/off, budget, level"),
258
259
  ("tools", "Tool inventory · /tools smoke"),
259
260
  ("trust", "Workspace trust · /trust on|off"),
@@ -364,6 +365,8 @@ def slash_help_lines() -> list[str]:
364
365
  " /clear Alias for /session new",
365
366
  " /compact Force context compaction now (summarize history)",
366
367
  " /compact <focus> Compact with custom focus, e.g. /compact test output",
368
+ " /summarise [focus] Save a durable session summary, persist key facts, then start fresh",
369
+ " /summarize [focus] Alias of /summarise",
367
370
  " /review Parallel code review: security + style + correctness",
368
371
  " /review <path> Review a specific file or directory",
369
372
  " /context Show context pressure + last prompt tokens",
gemcode/repl_slash.py CHANGED
@@ -36,6 +36,7 @@ from gemcode.slash_commands import parse_slash_command
36
36
  from gemcode.skills import discover_skill_metas, expand_skill_text, list_supporting_files, load_skill
37
37
  from gemcode.output_styles import discover_output_styles, load_output_style
38
38
  from gemcode.rules import load_rules as _load_rules
39
+ from gemcode.session_summariser import summarise_session
39
40
  from gemcode.trust import is_trusted_root, trust_json_path, trust_root
40
41
 
41
42
 
@@ -1556,6 +1557,52 @@ async def process_repl_slash(
1556
1557
  model_prompt=compact_prompt,
1557
1558
  )
1558
1559
 
1560
+ if name in ("summarise", "summarize"):
1561
+ focus = (sc.args or "").strip()
1562
+ out("Summarising current session into durable memory…")
1563
+ if focus:
1564
+ out(f"Focus: {focus}")
1565
+ out()
1566
+ try:
1567
+ model = (
1568
+ getattr(cfg, "adk_compaction_summarizer_model", None)
1569
+ or getattr(cfg, "model", "")
1570
+ or "gemini-2.5-flash"
1571
+ )
1572
+ result = summarise_session(
1573
+ cfg.project_root,
1574
+ session_id=session_id,
1575
+ model=model,
1576
+ focus=focus,
1577
+ )
1578
+ except Exception as e:
1579
+ out(f"[gemcode] session summarise failed: {e}")
1580
+ out()
1581
+ return ReplSlashResult(skip_model_turn=True)
1582
+
1583
+ if result.get("error"):
1584
+ out(f"[gemcode] {result['error']}")
1585
+ out()
1586
+ return ReplSlashResult(skip_model_turn=True)
1587
+
1588
+ out(f"Saved summary: {result.get('summary_path')}")
1589
+ mem_saved = len(result.get("memory_facts_saved") or [])
1590
+ user_saved = len(result.get("user_facts_saved") or [])
1591
+ open_items = len(result.get("open_items") or [])
1592
+ out(f"Curated memory saved: project={mem_saved}, user={user_saved}, open_items={open_items}")
1593
+ if result.get("notes_status"):
1594
+ out(f"Notes: {result.get('notes_status')}")
1595
+ out("Starting a fresh session so the next turn stays lightweight.")
1596
+ out()
1597
+ _clear_session_loaded_skills(cfg)
1598
+ cfg.pending_attachment_paths.clear()
1599
+ new_id = str(uuid.uuid4())
1600
+ return ReplSlashResult(
1601
+ skip_model_turn=True,
1602
+ new_session_id=new_id,
1603
+ force_rebuild_runner=True,
1604
+ )
1605
+
1559
1606
  if name in ("exit", "quit"):
1560
1607
  return ReplSlashResult(exit_repl=True)
1561
1608
 
@@ -0,0 +1,227 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import sqlite3
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from google.genai import Client
11
+ from google.genai import types
12
+
13
+
14
+ def _session_summary_dir(project_root: Path) -> Path:
15
+ return project_root / ".gemcode" / "session-summaries"
16
+
17
+
18
+ def _session_summary_path(project_root: Path, session_id: str) -> Path:
19
+ safe_id = (session_id or "unknown").strip().replace("/", "_")
20
+ return _session_summary_dir(project_root) / f"{safe_id}.md"
21
+
22
+
23
+ def _load_session_transcript(project_root: Path, session_id: str, *, max_events: int = 120) -> list[str]:
24
+ db = project_root / ".gemcode" / "sessions.sqlite"
25
+ if not db.is_file():
26
+ return []
27
+
28
+ con = sqlite3.connect(str(db), timeout=5)
29
+ cur = con.cursor()
30
+ cur.execute(
31
+ """
32
+ SELECT event_data
33
+ FROM events
34
+ WHERE session_id=?
35
+ ORDER BY timestamp ASC
36
+ LIMIT ?
37
+ """,
38
+ (session_id, int(max_events)),
39
+ )
40
+ rows = cur.fetchall()
41
+ con.close()
42
+
43
+ lines: list[str] = []
44
+ for (raw,) in rows:
45
+ try:
46
+ event = json.loads(raw)
47
+ except Exception:
48
+ continue
49
+ if not isinstance(event, dict):
50
+ continue
51
+
52
+ author = str(event.get("author") or "").strip().lower()
53
+ content = event.get("content") if isinstance(event.get("content"), dict) else {}
54
+ parts = content.get("parts") if isinstance(content.get("parts"), list) else []
55
+ texts: list[str] = []
56
+ for p in parts:
57
+ if isinstance(p, dict):
58
+ t = p.get("text")
59
+ if isinstance(t, str) and t.strip():
60
+ texts.append(t.strip())
61
+ if not texts:
62
+ continue
63
+
64
+ joined = "\n".join(texts)
65
+ if len(joined) > 4000:
66
+ joined = joined[:4000].rstrip() + "\n… [truncated]"
67
+
68
+ who = "User" if author == "user" else "GemCode"
69
+ lines.append(f"{who}: {joined}")
70
+ return lines
71
+
72
+
73
+ def _build_prompt(transcript_lines: list[str], *, focus: str = "") -> str:
74
+ transcript = "\n\n".join(transcript_lines)
75
+ if len(transcript) > 120_000:
76
+ transcript = transcript[:120_000] + "\n\n… [older transcript truncated]"
77
+
78
+ focus_line = f"- Extra focus: {focus}\n" if focus.strip() else ""
79
+ return (
80
+ "You are a session summariser for GemCode.\n"
81
+ "Summarise the session into compact, reusable memory for future runs.\n"
82
+ "Return STRICT JSON only with this schema:\n"
83
+ "{\n"
84
+ ' "title": "short title",\n'
85
+ ' "summary_markdown": "markdown summary",\n'
86
+ ' "memory_facts": ["durable project facts"],\n'
87
+ ' "user_facts": ["durable user preferences"],\n'
88
+ ' "notes_markdown": "compact markdown note for .gemcode/notes.md",\n'
89
+ ' "open_items": ["open tasks or blockers"]\n'
90
+ "}\n"
91
+ "Rules:\n"
92
+ "- Keep summary_markdown concise but high-signal.\n"
93
+ "- Preserve decisions, file paths, commands, errors, fixes, and next steps.\n"
94
+ "- memory_facts/user_facts: 0 to 5 each, only durable non-sensitive facts.\n"
95
+ "- notes_markdown should be compact and useful for the next session.\n"
96
+ "- Never include secrets, API keys, passwords, or tokens.\n"
97
+ f"{focus_line}"
98
+ "\nTranscript:\n"
99
+ f"{transcript}\n"
100
+ )
101
+
102
+
103
+ def _call_summary_model(*, model: str, prompt: str) -> dict[str, Any]:
104
+ api_key = os.environ.get("GOOGLE_API_KEY")
105
+ if not api_key:
106
+ raise RuntimeError("GOOGLE_API_KEY not set")
107
+
108
+ client = Client(api_key=api_key)
109
+ resp = client.models.generate_content(
110
+ model=model,
111
+ contents=[types.Content(role="user", parts=[types.Part(text=prompt)])],
112
+ config=types.GenerateContentConfig(temperature=0.2),
113
+ )
114
+
115
+ out_parts: list[str] = []
116
+ try:
117
+ if resp.candidates:
118
+ c0 = resp.candidates[0]
119
+ content = getattr(c0, "content", None)
120
+ for p in getattr(content, "parts", None) or []:
121
+ t = getattr(p, "text", None)
122
+ if isinstance(t, str) and t:
123
+ out_parts.append(t)
124
+ except Exception:
125
+ pass
126
+
127
+ text = "".join(out_parts).strip()
128
+ if not text:
129
+ raise RuntimeError("session summariser returned empty text")
130
+
131
+ try:
132
+ data = json.loads(text)
133
+ except Exception as e:
134
+ raise RuntimeError(f"session summariser returned invalid JSON: {e}") from e
135
+ if not isinstance(data, dict):
136
+ raise RuntimeError("session summariser returned non-object JSON")
137
+ return data
138
+
139
+
140
+ def summarise_session(
141
+ project_root: Path,
142
+ *,
143
+ session_id: str,
144
+ model: str,
145
+ focus: str = "",
146
+ ) -> dict[str, Any]:
147
+ transcript_lines = _load_session_transcript(project_root, session_id)
148
+ if not transcript_lines:
149
+ return {"error": "session transcript is empty", "session_id": session_id}
150
+
151
+ prompt = _build_prompt(transcript_lines, focus=focus)
152
+ data = _call_summary_model(model=model, prompt=prompt)
153
+
154
+ title = str(data.get("title") or f"Session {session_id[:8]}").strip()[:120]
155
+ summary_markdown = str(data.get("summary_markdown") or "").strip()
156
+ notes_markdown = str(data.get("notes_markdown") or "").strip()
157
+ memory_facts = [str(x).strip() for x in (data.get("memory_facts") or []) if str(x).strip()][:5]
158
+ user_facts = [str(x).strip() for x in (data.get("user_facts") or []) if str(x).strip()][:5]
159
+ open_items = [str(x).strip() for x in (data.get("open_items") or []) if str(x).strip()][:10]
160
+
161
+ out_path = _session_summary_path(project_root, session_id)
162
+ out_path.parent.mkdir(parents=True, exist_ok=True)
163
+ ts = datetime.now().strftime("%Y-%m-%d %H:%M")
164
+
165
+ body_parts = [
166
+ f"# {title}",
167
+ "",
168
+ f"- session_id: `{session_id}`",
169
+ f"- generated_at: {ts}",
170
+ ]
171
+ if focus.strip():
172
+ body_parts.append(f"- focus: {focus}")
173
+ body_parts.extend(["", "## Summary", summary_markdown or "- (empty)"])
174
+
175
+ if open_items:
176
+ body_parts.extend(["", "## Open items", *[f"- {x}" for x in open_items]])
177
+ if memory_facts:
178
+ body_parts.extend(["", "## Durable project facts", *[f"- {x}" for x in memory_facts]])
179
+ if user_facts:
180
+ body_parts.extend(["", "## Durable user facts", *[f"- {x}" for x in user_facts]])
181
+
182
+ out_path.write_text("\n".join(body_parts).rstrip() + "\n", encoding="utf-8")
183
+
184
+ saved_memory: list[str] = []
185
+ saved_user: list[str] = []
186
+ try:
187
+ from gemcode.curated_memory import append_fact
188
+ for fact in memory_facts:
189
+ res = append_fact(project_root, target="memory", text=fact)
190
+ if "error" not in res:
191
+ saved_memory.append(fact)
192
+ for fact in user_facts:
193
+ res = append_fact(project_root, target="user", text=fact)
194
+ if "error" not in res:
195
+ saved_user.append(fact)
196
+ except Exception:
197
+ pass
198
+
199
+ note_status: str | None = None
200
+ if notes_markdown:
201
+ try:
202
+ from gemcode.tools.notes import build_notes_tools
203
+ append_note, _read_note = build_notes_tools(project_root)
204
+ note_text = (
205
+ f"## Session summary — {title}\n"
206
+ f"- Session: `{session_id}`\n"
207
+ f"- Summary file: `{out_path}`\n\n"
208
+ f"{notes_markdown}"
209
+ )
210
+ res = append_note(note_text)
211
+ if isinstance(res, dict):
212
+ note_status = str(res.get("status") or "")
213
+ except Exception:
214
+ note_status = None
215
+
216
+ return {
217
+ "ok": True,
218
+ "session_id": session_id,
219
+ "summary_path": str(out_path),
220
+ "title": title,
221
+ "summary_markdown": summary_markdown,
222
+ "memory_facts_saved": saved_memory,
223
+ "user_facts_saved": saved_user,
224
+ "notes_status": note_status,
225
+ "open_items": open_items,
226
+ }
227
+
gemcode/tools/__init__.py CHANGED
@@ -21,6 +21,7 @@ from gemcode.tools.curated_memory import make_curated_memory_tools
21
21
  from gemcode.tools.compress_memory import make_compress_memory_tool
22
22
  from gemcode.tools.skills import make_skill_tools
23
23
  from gemcode.tools.veomem_tools import make_veomem_tools
24
+ from gemcode.session_summariser import summarise_session
24
25
 
25
26
 
26
27
  def _get_load_memory_tool():
@@ -92,6 +93,30 @@ def build_function_tools(cfg: GemCodeConfig, *, include_subtask: bool = True) ->
92
93
  compress_memory_file = make_compress_memory_tool(cfg)
93
94
  list_skills, load_skill, skills_manifest = make_skill_tools(cfg)
94
95
 
96
+ def summarise_session_tool(focus: str = "") -> dict:
97
+ """
98
+ Summarise the current session into compact reusable memory.
99
+
100
+ Use this when the working session has grown large and you want GemCode to
101
+ extract key points into durable notes + curated memory before continuing.
102
+ """
103
+ session_id = str(getattr(cfg, "_active_session_id", "") or "").strip()
104
+ if not session_id:
105
+ return {"error": "no active session id is available"}
106
+ model = (
107
+ getattr(cfg, "adk_compaction_summarizer_model", None)
108
+ or getattr(cfg, "model", "")
109
+ or "gemini-2.5-flash"
110
+ )
111
+ return summarise_session(
112
+ cfg.project_root,
113
+ session_id=session_id,
114
+ model=model,
115
+ focus=focus,
116
+ )
117
+
118
+ summarise_session_tool.__name__ = "summarise_session"
119
+
95
120
  def checkpoints_list(limit: int = 20) -> dict:
96
121
  """List recent checkpoints created by mutating tools."""
97
122
  return {"checkpoints": _list_checkpoints(cfg.project_root, limit=limit)}
@@ -154,6 +179,7 @@ def build_function_tools(cfg: GemCodeConfig, *, include_subtask: bool = True) ->
154
179
  read_curated_memory,
155
180
  # Optional: compress memory files (markdown only; safe guards apply)
156
181
  compress_memory_file,
182
+ summarise_session_tool,
157
183
  # Optional: VeoMem recall tools (3-step search/timeline/fetch).
158
184
  # Enabled via GEMCODE_VEOMEM=1.
159
185
  # GemSkills (on-demand playbooks)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.95
3
+ Version: 0.3.97
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -1,6 +1,6 @@
1
1
  gemcode/__init__.py,sha256=l0DCRYqK7KM7Fb7u49fqh-5_SlpeIL7r3LjMeJWMgSg,112
2
2
  gemcode/__main__.py,sha256=EX2s1hxq2Yvli_-tnBN3w5Qv4bOjsBBbjyISF0pDIQw,37
3
- gemcode/agent.py,sha256=85w0BhkwJZjaDdDFBMD_H9hOZCbcX6a7pT8e1gA9Qbk,58535
3
+ gemcode/agent.py,sha256=kxE0RR9bxiHn7yqOMm_nsulrgwVj_32RSkuX5coKBgg,58840
4
4
  gemcode/audit.py,sha256=bh9uhXaeh8wqxqoZtz3ZAowd8Ndk1ss-mw9993Vlrgo,469
5
5
  gemcode/autocompact.py,sha256=OE3QbGx2gWN2WVXy28Sd0xyfAJgVR1x6ml9HrX2CB7I,6719
6
6
  gemcode/autotune.py,sha256=zcTGDKC8LSnw0fHuoOcnnh1rz0by8K6MTUKl0_GhT9s,2704
@@ -21,7 +21,7 @@ gemcode/ide_protocol.py,sha256=WJO4KdwyxjQcH1O_vTn7SPuy1ZZMm0eC8_xRLA9RYQo,2108
21
21
  gemcode/ide_stdio.py,sha256=qDZ8qCR0kWipvyxLJ3tbZfAXChZtosi46dLeNuMejFk,11066
22
22
  gemcode/intent_classifier.py,sha256=YfRVEe8gHeKlRkjuSWef1bZ0MPBwyYMp5jymP5Vig5U,8507
23
23
  gemcode/interactions.py,sha256=B0b3QNE_I2i5_HtiebX4ehhjlc4Nbqjf_XbvcTLyJT0,641
24
- gemcode/invoke.py,sha256=ik8biwJVymL0CQ_JVmPqrpnYcn9nFa0-ue4nD-99ssM,11798
24
+ gemcode/invoke.py,sha256=mjCxQGlkZVaIh2eRxsxaojZGrV3FikMqoVCpNo0CV50,11904
25
25
  gemcode/kaira_daemon.py,sha256=Bzkpc96HocfYAV9D5skid_Gi4bJDOLgO5YlD8vbTgyY,6960
26
26
  gemcode/learning.py,sha256=o4Ivczm626NPRiNbSEb7-RvKJMefnv0ZpYt4UB2C3JA,3856
27
27
  gemcode/limits.py,sha256=3j6N8V643X7-nP-cAIf37Xg9bkGpQlEJB3nPptApQWk,2504
@@ -41,12 +41,13 @@ gemcode/pricing.py,sha256=lftp0SwyDqOzHqC2-6XzgZZhjif5PLdCe1Q3wY-p6kQ,3558
41
41
  gemcode/prompt_suggestions.py,sha256=RNEclxtoorRqu-wUlzuyUJ7OLFVOOryGOZBpbaCducI,2544
42
42
  gemcode/query_sanitizer.py,sha256=KqXo5U7igNSgOAH4YpyANlgS--1WnZEdpO4AU4SNQUs,2746
43
43
  gemcode/refine.py,sha256=BijEZ4Z32wGa9aK_WottyAhZF-j0xEqRg5UpjedNv2A,7653
44
- gemcode/repl_commands.py,sha256=x9LQVthWPrbwB3QhUakmIg4puHEnS1Vuax4yKtOLU2Y,19315
45
- gemcode/repl_slash.py,sha256=9vKCaJ_OFl05pTkdpqkjQa5a12XU_Ab1OjXNGk3-dnM,91666
44
+ gemcode/repl_commands.py,sha256=3AVG1uHovZT4fkkRNdKCnwgS3pxLhGvY0KD_0xz0p4o,19572
45
+ gemcode/repl_slash.py,sha256=cV4FdXLCOcpGUJlhuVUeufgto81N9pBarePZQv2L2io,93231
46
46
  gemcode/review_agent.py,sha256=4t7_5-aE60b4-EheJ_eSB_H2eQYf9GppKoui6jw0TME,5264
47
47
  gemcode/rules.py,sha256=Itg02VpifOo6jqGj5xwna_ahaPPb0OVtaeR2cNI0pLE,3018
48
48
  gemcode/session_runtime.py,sha256=MGOWWUz4ZUnWkuaYkc5EZ_uYYIvLjJc1N35X2GUX79k,23489
49
49
  gemcode/session_store.py,sha256=POUT_QQf715c74jbXj0s5vCd4dlAgJz_CLsIWuEUoO0,6051
50
+ gemcode/session_summariser.py,sha256=ZXBD9vgvZQ7gueFyckKcIOirq_15uVjsZqjHDa3yE-o,7237
50
51
  gemcode/skills.py,sha256=nnrzYUCiuEkU_i57p_jJpPHRfw1t2t2EA3pJHNqvpzw,12554
51
52
  gemcode/slash_commands.py,sha256=bcD-S_H7p7AlTli6g2dLPPG46HejPje0Imb3ScDTCaQ,798
52
53
  gemcode/thinking.py,sha256=-1TVkOMG-7CSQN0Mc18EqINkUxWOMBgeTlF7CX9zYL4,4641
@@ -76,7 +77,7 @@ gemcode/query/engine.py,sha256=RuAx7jWLjkvUQcTjRry22GOY27GvAPhkt6r6AZGpokw,1416
76
77
  gemcode/query/stop_hooks.py,sha256=wBInW-hmHoFdTs91Jc7YY52Tx9GT63CU-P9Gbd9ijNQ,1842
77
78
  gemcode/query/token_budget.py,sha256=lmoFx1RNRdafPisO3bxt7p7bkr5lyCMV7N2T7caHwKA,2957
78
79
  gemcode/query/transitions.py,sha256=30xfeexEF1C2pjqvr00dvkdSsnsZ4UQTfi_c4wIhmd0,929
79
- gemcode/tools/__init__.py,sha256=t9Fkta--EovIe8KqG7Y7TMBjbOugu5Zjn5Bzl2-rYrI,6208
80
+ gemcode/tools/__init__.py,sha256=PcY2M19AdGkFF_XFvklEqyGDxuJKa7EGeR6B7GlZPRo,7071
80
81
  gemcode/tools/bash.py,sha256=kedqaL2CU6O9WLPT83FToqJSaRBEGHesJSBH4pRLHlU,13649
81
82
  gemcode/tools/browser.py,sha256=StWRttiyGkR4qaG5urRviJgdoj2hiFB2OuHPItaVzJY,7250
82
83
  gemcode/tools/compress_memory.py,sha256=rI8Uu8DO_io6ltRYgTdlNhQLZjyJCpSObnQ15CBVlQI,10396
@@ -106,9 +107,9 @@ gemcode/web/__init__.py,sha256=EysmUAWs6g-lmMk4VFljKfaHVrEgb_FiIzwQmBdORJc,40
106
107
  gemcode/web/sse_adapter.py,sha256=fXhKxn_bdJJUGqlmvkxLNSYL-ZiIZDaLHtQCF_BheRc,7108
107
108
  gemcode/web/terminal_repl.py,sha256=fQt895g0qcr6VBhXfv_5b_bsC5zHT5-MO0ysBdgi2Fg,3886
108
109
  gemcode/web/web_sse_compat.py,sha256=9A2s-GI7El7AotJqhO263FrLwppCXXkdydZ5EiOQbao,504
109
- gemcode-0.3.95.dist-info/licenses/LICENSE,sha256=TD4524qn-W8Z07GTDnag-9jJPFutFZNB0a1WbMHPC54,8388
110
- gemcode-0.3.95.dist-info/METADATA,sha256=ZzeAbP-mRrSPpSe1fPkB30K-Suj47877QhHBFq8SB-E,17084
111
- gemcode-0.3.95.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
112
- gemcode-0.3.95.dist-info/entry_points.txt,sha256=cZdLTLDiHbks7OSUCuxCh66dCWeQdpLR8BozoqfEjV4,45
113
- gemcode-0.3.95.dist-info/top_level.txt,sha256=UYrjULLBY2bcgK6KI6flomJWmsbDXu7n0rvW2SWFrbo,8
114
- gemcode-0.3.95.dist-info/RECORD,,
110
+ gemcode-0.3.97.dist-info/licenses/LICENSE,sha256=TD4524qn-W8Z07GTDnag-9jJPFutFZNB0a1WbMHPC54,8388
111
+ gemcode-0.3.97.dist-info/METADATA,sha256=-rLJq9S6zklxs9L4oMQ3RVatlE4MkOUlcfvtkgTZvwo,17084
112
+ gemcode-0.3.97.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
113
+ gemcode-0.3.97.dist-info/entry_points.txt,sha256=cZdLTLDiHbks7OSUCuxCh66dCWeQdpLR8BozoqfEjV4,45
114
+ gemcode-0.3.97.dist-info/top_level.txt,sha256=UYrjULLBY2bcgK6KI6flomJWmsbDXu7n0rvW2SWFrbo,8
115
+ gemcode-0.3.97.dist-info/RECORD,,