codigest 0.4.1__tar.gz → 0.4.3__tar.gz

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 (47) hide show
  1. codigest-0.4.3/.github/workflows/publish.yml +51 -0
  2. codigest-0.4.3/.github/workflows/tests.yml +15 -0
  3. {codigest-0.4.1 → codigest-0.4.3}/CITATION.cff +1 -1
  4. {codigest-0.4.1 → codigest-0.4.3}/PKG-INFO +11 -3
  5. {codigest-0.4.1 → codigest-0.4.3}/README.md +10 -2
  6. {codigest-0.4.1 → codigest-0.4.3}/pyproject.toml +1 -1
  7. codigest-0.4.3/server.json +28 -0
  8. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/commands/mcp.py +11 -5
  9. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/commands/term.py +11 -3
  10. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/core/termstore.py +15 -1
  11. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/shell/hook.ps1 +1 -1
  12. {codigest-0.4.1 → codigest-0.4.3}/tests/test_termstore.py +14 -0
  13. {codigest-0.4.1 → codigest-0.4.3}/uv.lock +1 -1
  14. codigest-0.4.1/.github/workflows/publish.yml +0 -73
  15. {codigest-0.4.1 → codigest-0.4.3}/.gitignore +0 -0
  16. {codigest-0.4.1 → codigest-0.4.3}/.python-version +0 -0
  17. {codigest-0.4.1 → codigest-0.4.3}/LICENSE +0 -0
  18. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/__init__.py +0 -0
  19. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/__main__.py +0 -0
  20. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/commands/cap.py +0 -0
  21. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/commands/clean.py +0 -0
  22. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/commands/diff.py +0 -0
  23. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/commands/digest.py +0 -0
  24. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/commands/init.py +0 -0
  25. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/commands/scan.py +0 -0
  26. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/commands/semdiff.py +0 -0
  27. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/commands/status.py +0 -0
  28. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/commands/tree.py +0 -0
  29. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/config.py +0 -0
  30. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/core/cache.py +0 -0
  31. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/core/common.py +0 -0
  32. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/core/git_ops.py +0 -0
  33. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/core/output.py +0 -0
  34. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/core/processor.py +0 -0
  35. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/core/prompts.py +0 -0
  36. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/core/redact.py +0 -0
  37. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/core/resolver.py +0 -0
  38. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/core/runner.py +0 -0
  39. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/core/scanner.py +0 -0
  40. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/core/semdiff.py +0 -0
  41. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/core/shadow.py +0 -0
  42. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/core/shellhist.py +0 -0
  43. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/core/structure.py +0 -0
  44. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/core/tags.py +0 -0
  45. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/core/tokenizer.py +0 -0
  46. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/main.py +0 -0
  47. {codigest-0.4.1 → codigest-0.4.3}/src/codigest/options.py +0 -0
@@ -0,0 +1,51 @@
1
+ name: Publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ jobs:
9
+ pypi:
10
+ name: Build and publish to PyPI
11
+ runs-on: ubuntu-latest
12
+ environment: release
13
+ permissions:
14
+ id-token: write # PyPI Trusted Publishing
15
+ contents: read
16
+ steps:
17
+ - uses: actions/checkout@v7
18
+ - uses: astral-sh/setup-uv@v10.3.0
19
+ - run: uv python install 3.15
20
+ - run: uv build
21
+ - uses: pypa/gh-action-pypi-publish@release/v1
22
+
23
+ mcp:
24
+ name: Publish to the MCP registry
25
+ needs: pypi
26
+ runs-on: ubuntu-latest
27
+ permissions:
28
+ id-token: write # GitHub OIDC login for io.github.NaNIsaac/*
29
+ contents: read
30
+ steps:
31
+ - uses: actions/checkout@v7
32
+
33
+ - name: Set server.json version from the tag
34
+ run: |
35
+ VERSION="${GITHUB_REF_NAME#v}"
36
+ jq --arg v "$VERSION" '.version = $v | .packages[0].version = $v' server.json > server.tmp
37
+ mv server.tmp server.json
38
+ # The registry verifies the package on PyPI; wait until the release is visible.
39
+ for i in $(seq 30); do
40
+ curl -sf "https://pypi.org/pypi/codigest/$VERSION/json" > /dev/null && exit 0
41
+ sleep 10
42
+ done
43
+ echo "codigest $VERSION not visible on PyPI" && exit 1
44
+
45
+ - name: Install mcp-publisher
46
+ run: curl -sL https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz | tar xz mcp-publisher
47
+
48
+ - name: Publish
49
+ run: |
50
+ ./mcp-publisher login github-oidc
51
+ ./mcp-publisher publish
@@ -0,0 +1,15 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v7
13
+ - uses: astral-sh/setup-uv@v10.3.0
14
+ - run: uv python install 3.15
15
+ - run: uv run --with pytest pytest -q
@@ -14,7 +14,7 @@ authors:
14
14
  repository-code: "https://github.com/NaNIsaac/codigest"
15
15
  url: "https://pypi.org/project/codigest/"
16
16
  license: MIT
17
- version: 0.4.1
17
+ version: 0.4.3
18
18
  date-released: "2026-10-15"
19
19
  keywords:
20
20
  - LLM
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: codigest
3
- Version: 0.4.1
3
+ Version: 0.4.3
4
4
  Summary: Human-Centered Codebase Context Manager for LLMs
5
5
  Project-URL: Homepage, https://github.com/NaNIsaac/codigest
6
6
  Project-URL: Repository, https://github.com/NaNIsaac/codigest
@@ -26,6 +26,12 @@ Description-Content-Type: text/markdown
26
26
 
27
27
  # Codigest
28
28
 
29
+ [![PyPI](https://img.shields.io/pypi/v/codigest)](https://pypi.org/project/codigest/)
30
+ [![Downloads](https://static.pepy.tech/badge/codigest)](https://pepy.tech/project/codigest)
31
+ [![Tests](https://github.com/NaNIsaac/codigest/actions/workflows/tests.yml/badge.svg)](https://github.com/NaNIsaac/codigest/actions/workflows/tests.yml)
32
+
33
+ <!-- mcp-name: io.github.NaNIsaac/codigest -->
34
+
29
35
  **Codigest** is a standalone CLI tool designed to extract, structure, and track the context of your codebase for Large Language Models (LLMs).
30
36
 
31
37
  Unlike simple copy-paste tools, Codigest employs a **Context Anchor** system (Shadow Git) to track changes locally without polluting your main version control history. It also features **Semantic Analysis** to detect structural changes in your code.
@@ -35,7 +41,8 @@ Unlike simple copy-paste tools, Codigest employs a **Context Anchor** system (Sh
35
41
  ## Quickstart
36
42
 
37
43
  ```bash
38
- uv tool install codigest
44
+ uvx codigest scan # try it without installing
45
+ uv tool install codigest # or install the `cdg` command
39
46
 
40
47
  cdg scan # whole project as LLM-ready XML (copied to clipboard)
41
48
  cdg init && cdg diff # set an anchor, later get only what changed
@@ -231,6 +238,7 @@ cdg term # last command
231
238
  cdg term -n 3 # last 3 commands
232
239
  cdg term -i 2 # only the command from 2 runs ago
233
240
  cdg term -f # last failed command
241
+ cdg term --here -n 3 # last 3 commands run in this project
234
242
 
235
243
  cdg term path # where captures are stored (override with CODIGEST_HOME)
236
244
  cdg term clear # delete all captures
@@ -242,7 +250,7 @@ cdg term clear # delete all captures
242
250
 
243
251
  | Tool | Returns |
244
252
  | --- | --- |
245
- | `terminal_history` | Recent commands with output and exit code (`count`, `failed`) |
253
+ | `terminal_history` | Recent commands with output and exit code (`count`, `failed`, `here`: only this project, default on) |
246
254
  | `scan` | Project snapshot (`targets`, `resolve`, `message`) |
247
255
  | `diff` / `semdiff` | Text / structural changes since the anchor |
248
256
  | `digest` | Signatures and docstrings only |
@@ -1,5 +1,11 @@
1
1
  # Codigest
2
2
 
3
+ [![PyPI](https://img.shields.io/pypi/v/codigest)](https://pypi.org/project/codigest/)
4
+ [![Downloads](https://static.pepy.tech/badge/codigest)](https://pepy.tech/project/codigest)
5
+ [![Tests](https://github.com/NaNIsaac/codigest/actions/workflows/tests.yml/badge.svg)](https://github.com/NaNIsaac/codigest/actions/workflows/tests.yml)
6
+
7
+ <!-- mcp-name: io.github.NaNIsaac/codigest -->
8
+
3
9
  **Codigest** is a standalone CLI tool designed to extract, structure, and track the context of your codebase for Large Language Models (LLMs).
4
10
 
5
11
  Unlike simple copy-paste tools, Codigest employs a **Context Anchor** system (Shadow Git) to track changes locally without polluting your main version control history. It also features **Semantic Analysis** to detect structural changes in your code.
@@ -9,7 +15,8 @@ Unlike simple copy-paste tools, Codigest employs a **Context Anchor** system (Sh
9
15
  ## Quickstart
10
16
 
11
17
  ```bash
12
- uv tool install codigest
18
+ uvx codigest scan # try it without installing
19
+ uv tool install codigest # or install the `cdg` command
13
20
 
14
21
  cdg scan # whole project as LLM-ready XML (copied to clipboard)
15
22
  cdg init && cdg diff # set an anchor, later get only what changed
@@ -205,6 +212,7 @@ cdg term # last command
205
212
  cdg term -n 3 # last 3 commands
206
213
  cdg term -i 2 # only the command from 2 runs ago
207
214
  cdg term -f # last failed command
215
+ cdg term --here -n 3 # last 3 commands run in this project
208
216
 
209
217
  cdg term path # where captures are stored (override with CODIGEST_HOME)
210
218
  cdg term clear # delete all captures
@@ -216,7 +224,7 @@ cdg term clear # delete all captures
216
224
 
217
225
  | Tool | Returns |
218
226
  | --- | --- |
219
- | `terminal_history` | Recent commands with output and exit code (`count`, `failed`) |
227
+ | `terminal_history` | Recent commands with output and exit code (`count`, `failed`, `here`: only this project, default on) |
220
228
  | `scan` | Project snapshot (`targets`, `resolve`, `message`) |
221
229
  | `diff` / `semdiff` | Text / structural changes since the anchor |
222
230
  | `digest` | Signatures and docstrings only |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codigest"
3
- version = "0.4.1"
3
+ version = "0.4.3"
4
4
  description = "Human-Centered Codebase Context Manager for LLMs"
5
5
  authors = [{ name = "Isaac Yong", email = "isaacyong@naver.com" }]
6
6
  readme = "README.md"
@@ -0,0 +1,28 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.NaNIsaac/codigest",
4
+ "title": "Codigest",
5
+ "description": "Terminal output, project snapshots and semantic diffs as LLM-ready context",
6
+ "repository": {
7
+ "url": "https://github.com/NaNIsaac/codigest",
8
+ "source": "github"
9
+ },
10
+ "version": "0.4.3",
11
+ "packages": [
12
+ {
13
+ "registryType": "pypi",
14
+ "identifier": "codigest",
15
+ "version": "0.4.3",
16
+ "runtimeHint": "uvx",
17
+ "transport": {
18
+ "type": "stdio"
19
+ },
20
+ "packageArguments": [
21
+ {
22
+ "type": "positional",
23
+ "value": "mcp"
24
+ }
25
+ ]
26
+ }
27
+ ]
28
+ }
@@ -15,6 +15,7 @@ exactly what a human would paste, and stdout stays clean for the protocol.
15
15
  import json
16
16
  import subprocess
17
17
  import sys
18
+ from pathlib import Path
18
19
  from importlib.metadata import PackageNotFoundError, version
19
20
  from typing import Any
20
21
 
@@ -40,6 +41,11 @@ TOOLS: list[JSON] = [
40
41
  "properties": {
41
42
  "count": {"type": "integer", "minimum": 1, "default": 1, "description": "How many recent commands"},
42
43
  "failed": {"type": "boolean", "default": False, "description": "Only commands that exited non-zero"},
44
+ "here": {
45
+ "type": "boolean",
46
+ "default": True,
47
+ "description": "Only commands run in the server's working directory (the project) or below",
48
+ },
43
49
  },
44
50
  },
45
51
  },
@@ -81,12 +87,12 @@ TOOLS: list[JSON] = [
81
87
 
82
88
  def _terminal_history(args: JSON) -> str:
83
89
  count = int(args.get("count", 1))
84
- entries = termstore.load_entries()
85
- if args.get("failed"):
86
- entries = [e for e in entries if e.exit_code not in (None, 0)]
87
- if not entries:
90
+ if not (entries := termstore.load_entries()):
91
+ # Nothing captured at all: native shell history still names the commands.
88
92
  entries, _ = shellhist.read_recent(count)
89
- return termstore.render(entries[-count:])
93
+ return termstore.render(entries)
94
+ under = Path.cwd() if args.get("here", True) else None
95
+ return termstore.render(termstore.select(entries, failed=bool(args.get("failed")), under=under)[-count:])
90
96
 
91
97
 
92
98
  def _cli(command: str, args: JSON) -> str:
@@ -40,6 +40,11 @@ FailedOption = Annotated[
40
40
  typer.Option("-f", "--failed", help="Only commands that exited non-zero"),
41
41
  ]
42
42
 
43
+ HereOption = Annotated[
44
+ bool,
45
+ typer.Option("--here", help="Only commands run in the current directory or below"),
46
+ ]
47
+
43
48
  HistoryOnlyOption = Annotated[
44
49
  bool,
45
50
  typer.Option(
@@ -68,6 +73,7 @@ def handle(
68
73
  count: CountOption = 1,
69
74
  index: IndexOption = None,
70
75
  failed: FailedOption = False,
76
+ here: HereOption = False,
71
77
  history_only: HistoryOnlyOption = False,
72
78
  copy: CopyOption = True,
73
79
  stdout: StdoutOption = False,
@@ -80,6 +86,7 @@ def handle(
80
86
  cdg term -n 3 # the last 3 commands
81
87
  cdg term -i 3 # only the command from 3 runs ago
82
88
  cdg term -f # the last failed command
89
+ cdg term --here -n 3 # the last 3 commands run in this project
83
90
  """
84
91
  if ctx.invoked_subcommand:
85
92
  return
@@ -107,10 +114,11 @@ def handle(
107
114
  _diagnose(log)
108
115
  raise typer.Exit(code=1)
109
116
 
110
- if failed:
111
- entries = [e for e in entries if e.exit_code not in (None, 0)]
117
+ if failed or here:
118
+ entries = termstore.select(entries, failed=failed, under=Path.cwd() if here else None)
112
119
  if not entries:
113
- log.print("[yellow]No failed commands captured.[/yellow]")
120
+ what = " and ".join(w for w, on in (("failed", failed), ("in this directory", here)) if on)
121
+ log.print(f"[yellow]No captured commands match ({what}).[/yellow]")
114
122
  raise typer.Exit(code=1)
115
123
 
116
124
  if index:
@@ -131,6 +131,20 @@ def _truncate(text: str) -> tuple[str, bool]:
131
131
  # Read / write
132
132
  # --------------------------------------------------------------------------
133
133
 
134
+ def _inside(path: str, root: Path) -> bool:
135
+ path, base = (os.path.normcase(os.path.abspath(p)) for p in (path, root))
136
+ return path == base or path.startswith(base.rstrip(os.sep) + os.sep)
137
+
138
+
139
+ def select(entries: list[Entry], *, failed: bool = False, under: Path | None = None) -> list[Entry]:
140
+ """Keeps entries that exited non-zero (`failed`) and/or ran inside `under` or below it."""
141
+ if failed:
142
+ entries = [e for e in entries if e.exit_code not in (None, 0)]
143
+ if under is not None:
144
+ entries = [e for e in entries if e.cwd and _inside(e.cwd, under)]
145
+ return entries
146
+
147
+
134
148
  def load_entries() -> list[Entry]:
135
149
  """Loads all stored entries, oldest first. Corrupt lines are skipped."""
136
150
  path = get_history_path()
@@ -183,7 +197,7 @@ def _trim(path: Path) -> None:
183
197
  """Rewrites the log keeping only the most recent MAX_ENTRIES lines."""
184
198
  with open(path, "r", encoding="utf-8", errors="replace") as f:
185
199
  lines = f.readlines()
186
- # ponytail: no file lock. Concurrent hook ingests can drop an entry appended
200
+ # No file lock: Concurrent hook ingests can drop an entry appended
187
201
  # between read and replace; the slack makes rewrites (and that window) rare.
188
202
  if len(lines) <= MAX_ENTRIES + _TRIM_SLACK:
189
203
  return
@@ -74,7 +74,7 @@ if (-not (Get-Module PSReadLine) -and $Host.Name -eq 'ConsoleHost') {
74
74
  }
75
75
  if (-not (Get-Module PSReadLine)) { return }
76
76
 
77
- # ponytail: denylist heuristic for programs that need a real TTY (editors, pagers,
77
+ # Denylist heuristic for programs that need a real TTY (editors, pagers,
78
78
  # REPLs, ssh). Extend with $env:CODIGEST_CAP_SKIP (a regex) when one slips through.
79
79
  $global:__cdgSkip = '^\s*(vim?|nvim|nano|emacs|less|more|ssh|htop|btop|top|fzf|lazygit|claude|codex|wsl|exit)(\.exe)?\b' +
80
80
  '|^\s*(cdg|codigest)(\.exe)?\s+(term|mcp)\b' +
@@ -32,6 +32,20 @@ def test_mcp_lists_tools_and_ignores_notifications():
32
32
  assert mcp.handle_message({"jsonrpc": "2.0", "id": 2, "method": "nope"})["error"]["code"] == -32601
33
33
 
34
34
 
35
+
36
+ def test_select_filters_failed_and_directory(tmp_path):
37
+ project, other = tmp_path / "proj", tmp_path / "proj-other"
38
+ entries = [
39
+ Entry(command="a", exit_code=1, cwd=str(project / "sub")),
40
+ Entry(command="b", exit_code=0, cwd=str(project)),
41
+ Entry(command="c", exit_code=2, cwd=str(other)), # shares the prefix, not the directory
42
+ Entry(command="d", exit_code=None, cwd=""),
43
+ ]
44
+ assert [e.command for e in termstore.select(entries, under=project)] == ["a", "b"]
45
+ assert [e.command for e in termstore.select(entries, failed=True)] == ["a", "c"]
46
+ assert [e.command for e in termstore.select(entries, failed=True, under=project)] == ["a"]
47
+
48
+
35
49
  if __name__ == "__main__":
36
50
  test_crlf_output_survives_normalization()
37
51
  test_progress_redraw_keeps_last_frame()
@@ -16,7 +16,7 @@ wheels = [
16
16
 
17
17
  [[package]]
18
18
  name = "codigest"
19
- version = "0.4.1"
19
+ version = "0.4.3"
20
20
  source = { editable = "." }
21
21
  dependencies = [
22
22
  { name = "loguru" },
@@ -1,73 +0,0 @@
1
- name: Publish Python distributions to PyPI and GitHub Pages
2
-
3
- on:
4
- push:
5
- tags:
6
- - 'v*' # v0.3.0 태그 푸시 시 배포 실행
7
-
8
- jobs:
9
- build-n-publish:
10
- name: Build and publish Python distributions to PyPI
11
- runs-on: ubuntu-latest
12
- # v* 태그일 때만 실행
13
- if: startsWith(github.ref, 'refs/tags/v')
14
- environment: release
15
- permissions:
16
- id-token: write # Trusted Publishing을 위한 필수 권한
17
- contents: read
18
-
19
- steps:
20
- - uses: actions/checkout@v4
21
-
22
- # uv 설치 (가장 빠르고 확실한 방법)
23
- - name: Install uv
24
- uses: astral-sh/setup-uv@v5
25
-
26
- # Python 3.14 설정 (uv 이용)
27
- - name: Set up Python
28
- run: uv python install 3.15
29
-
30
- # 의존성 설치 및 빌드 (uv build 하나로 해결)
31
- - name: Build package
32
- run: uv build
33
-
34
- # PyPI 배포 (기존 형식 유지)
35
- - name: Publish package distributions to PyPI
36
- uses: pypa/gh-action-pypi-publish@release/v1
37
-
38
- deploy-docs:
39
- name: Build and deploy Sphinx docs to GitHub Pages
40
- runs-on: ubuntu-latest
41
- if: github.ref == 'refs/heads/main'
42
- permissions:
43
- contents: write
44
- steps:
45
- - uses: actions/checkout@v4
46
-
47
- - name: Install uv
48
- uses: astral-sh/setup-uv@v5
49
-
50
- - name: Set up Python
51
- run: uv python install 3.15
52
-
53
- - name: Install Python and Sphinx dependencies
54
- run: |
55
- uv pip install --system --upgrade pip
56
- if [ -f docs/requirements.txt ]; then uv pip install --system -r docs/requirements.txt; fi
57
-
58
- - name: Build HTML docs with Sphinx
59
- run: |
60
- if [ -d "docs" ]; then
61
- sphinx-build -b html docs/source docs/html
62
- touch docs/html/.nojekyll
63
- else
64
- echo "No docs folder found, skipping build."
65
- mkdir -p docs/html
66
- touch docs/html/.nojekyll
67
- fi
68
-
69
- - name: Deploy to GitHub Pages
70
- uses: peaceiris/actions-gh-pages@v3
71
- with:
72
- github_token: ${{ secrets.GITHUB_TOKEN }}
73
- publish_dir: docs/html
File without changes
File without changes
File without changes
File without changes