agent-wiki-cli 0.3.28__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.
- agent_wiki_cli-0.3.28.dist-info/METADATA +425 -0
- agent_wiki_cli-0.3.28.dist-info/RECORD +47 -0
- agent_wiki_cli-0.3.28.dist-info/WHEEL +5 -0
- agent_wiki_cli-0.3.28.dist-info/entry_points.txt +2 -0
- agent_wiki_cli-0.3.28.dist-info/licenses/LICENSE +21 -0
- agent_wiki_cli-0.3.28.dist-info/top_level.txt +1 -0
- llm_wiki_cli/__init__.py +7 -0
- llm_wiki_cli/cli.py +231 -0
- llm_wiki_cli/commands/__init__.py +1 -0
- llm_wiki_cli/commands/bootstrap_cmd.py +1072 -0
- llm_wiki_cli/commands/bump_cmd.py +55 -0
- llm_wiki_cli/commands/context_cmd.py +427 -0
- llm_wiki_cli/commands/extract_cmd.py +745 -0
- llm_wiki_cli/commands/generate_prompt_cmd.py +89 -0
- llm_wiki_cli/commands/hook_cmd.py +161 -0
- llm_wiki_cli/commands/init_cmd.py +92 -0
- llm_wiki_cli/commands/lint_cmd.py +294 -0
- llm_wiki_cli/commands/migrate_cmd.py +892 -0
- llm_wiki_cli/commands/release_cmd.py +163 -0
- llm_wiki_cli/commands/status_cmd.py +70 -0
- llm_wiki_cli/commands/sync_cmd.py +521 -0
- llm_wiki_cli/commands/trigger_cmd.py +205 -0
- llm_wiki_cli/commands/uninstall_cmd.py +221 -0
- llm_wiki_cli/commands/upgrade_cmd.py +196 -0
- llm_wiki_cli/config.py +318 -0
- llm_wiki_cli/extractors/__init__.py +46 -0
- llm_wiki_cli/extractors/common.py +90 -0
- llm_wiki_cli/extractors/go_extractor.py +143 -0
- llm_wiki_cli/extractors/go_scripts/go.mod +3 -0
- llm_wiki_cli/extractors/go_scripts/main.go +668 -0
- llm_wiki_cli/extractors/python_extractor.py +346 -0
- llm_wiki_cli/extractors/rust_extractor.py +143 -0
- llm_wiki_cli/extractors/rust_scripts/Cargo.lock +110 -0
- llm_wiki_cli/extractors/rust_scripts/Cargo.toml +11 -0
- llm_wiki_cli/extractors/rust_scripts/src/main.rs +803 -0
- llm_wiki_cli/extractors/ts_extractor.py +206 -0
- llm_wiki_cli/extractors/ts_scripts/extract.js +485 -0
- llm_wiki_cli/extractors/ts_scripts/package.json +10 -0
- llm_wiki_cli/services/__init__.py +0 -0
- llm_wiki_cli/services/circuit_breaker.py +79 -0
- llm_wiki_cli/services/io.py +47 -0
- llm_wiki_cli/services/lockfile.py +60 -0
- llm_wiki_cli/services/packages.py +173 -0
- llm_wiki_cli/services/paths.py +31 -0
- llm_wiki_cli/services/schema.py +214 -0
- llm_wiki_cli/services/secure_file.py +22 -0
- llm_wiki_cli/services/versioning.py +193 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""release_cmd — stamp the [Unreleased] CHANGELOG section with the current version.
|
|
2
|
+
|
|
3
|
+
Transforms::
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
...changes...
|
|
7
|
+
|
|
8
|
+
## [0.1.5] - 2026-04-11
|
|
9
|
+
...
|
|
10
|
+
|
|
11
|
+
Into::
|
|
12
|
+
|
|
13
|
+
## [Unreleased]
|
|
14
|
+
|
|
15
|
+
## [0.1.6] - 2026-04-12
|
|
16
|
+
...changes...
|
|
17
|
+
|
|
18
|
+
## [0.1.5] - 2026-04-11
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
And updates the reference links at the bottom of the file.
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import re
|
|
26
|
+
import subprocess
|
|
27
|
+
import sys
|
|
28
|
+
from datetime import date
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
from ..services.versioning import find_version_file, read_version
|
|
32
|
+
|
|
33
|
+
# Matches the [Unreleased] section heading
|
|
34
|
+
_UNRELEASED_RE = re.compile(r"^## \[Unreleased\]", re.MULTILINE)
|
|
35
|
+
# Matches any existing version reference link: [x.y.z]: https://...
|
|
36
|
+
_REF_LINK_RE = re.compile(r"^\[[\w.]+\]: https://.*$", re.MULTILINE)
|
|
37
|
+
|
|
38
|
+
_GITHUB_REPO_RE = re.compile(r"https://github\.com/([^/]+/[^/]+)/compare/")
|
|
39
|
+
|
|
40
|
+
# Matches content between [Unreleased] heading and the next ## heading or end-of-file ref-links
|
|
41
|
+
_UNRELEASED_BODY_RE = re.compile(
|
|
42
|
+
r"^## \[Unreleased\]\s*\n(.*?)(?=^## \[|^\[|\Z)",
|
|
43
|
+
re.MULTILINE | re.DOTALL,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _unreleased_has_content(text: str) -> bool:
|
|
48
|
+
"""Return True if the [Unreleased] section contains at least one non-blank line."""
|
|
49
|
+
m = _UNRELEASED_BODY_RE.search(text)
|
|
50
|
+
if not m:
|
|
51
|
+
return False
|
|
52
|
+
body = m.group(1)
|
|
53
|
+
return any(line.strip() for line in body.splitlines())
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _detect_repo_url(changelog_text: str) -> str | None:
|
|
57
|
+
"""Extract the GitHub compare base URL from existing reference links."""
|
|
58
|
+
m = _GITHUB_REPO_RE.search(changelog_text)
|
|
59
|
+
if m:
|
|
60
|
+
return f"https://github.com/{m.group(1)}"
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def stamp_changelog(changelog_path: Path, version: str, today: str | None = None) -> tuple[str, bool]:
|
|
65
|
+
"""Stamp the [Unreleased] section with *version* and return ``(new_text, stamped)``.
|
|
66
|
+
|
|
67
|
+
*stamped* is ``False`` (and the original text is returned unchanged) when the
|
|
68
|
+
``[Unreleased]`` section is empty — i.e. the agent has not written any entries yet.
|
|
69
|
+
|
|
70
|
+
Raises ``ValueError`` if no ``## [Unreleased]`` section is found.
|
|
71
|
+
"""
|
|
72
|
+
text = changelog_path.read_text(encoding="utf-8")
|
|
73
|
+
release_date = today or date.today().isoformat()
|
|
74
|
+
|
|
75
|
+
if not _UNRELEASED_RE.search(text):
|
|
76
|
+
raise ValueError("No '## [Unreleased]' section found in CHANGELOG.")
|
|
77
|
+
|
|
78
|
+
# Skip stamp when [Unreleased] has no substantive content yet
|
|
79
|
+
if not _unreleased_has_content(text):
|
|
80
|
+
return text, False
|
|
81
|
+
|
|
82
|
+
# Replace the first [Unreleased] heading with [Unreleased] + new version heading
|
|
83
|
+
new_version_heading = f"## [Unreleased]\n\n## [{version}] - {release_date}"
|
|
84
|
+
new_text = _UNRELEASED_RE.sub(new_version_heading, text, count=1)
|
|
85
|
+
|
|
86
|
+
# Update reference links section
|
|
87
|
+
repo_url = _detect_repo_url(text)
|
|
88
|
+
if repo_url:
|
|
89
|
+
# Remove all existing reference links (we'll rebuild them)
|
|
90
|
+
new_text = _REF_LINK_RE.sub("", new_text).rstrip() + "\n"
|
|
91
|
+
|
|
92
|
+
# Find previously highest version to build the compare URL for new version
|
|
93
|
+
# Collect all version tags already mentioned in headings
|
|
94
|
+
heading_versions = list(dict.fromkeys(
|
|
95
|
+
re.findall(r"## \[(\d+\.\d+\.\d+)\]", new_text)
|
|
96
|
+
))
|
|
97
|
+
|
|
98
|
+
links: list[str] = []
|
|
99
|
+
links.append(f"[Unreleased]: {repo_url}/compare/v{version}...HEAD")
|
|
100
|
+
|
|
101
|
+
# Build compare links between consecutive versions (newest first)
|
|
102
|
+
for i, ver in enumerate(heading_versions):
|
|
103
|
+
if i + 1 < len(heading_versions):
|
|
104
|
+
prev = heading_versions[i + 1]
|
|
105
|
+
links.append(f"[{ver}]: {repo_url}/compare/v{prev}...v{ver}")
|
|
106
|
+
else:
|
|
107
|
+
links.append(f"[{ver}]: {repo_url}/releases/tag/v{ver}")
|
|
108
|
+
|
|
109
|
+
new_text += "\n" + "\n".join(links) + "\n"
|
|
110
|
+
|
|
111
|
+
return new_text, True
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def run(args):
|
|
115
|
+
root = getattr(args, "root", ".")
|
|
116
|
+
changelog_path = Path(getattr(args, "changelog", "CHANGELOG.md"))
|
|
117
|
+
|
|
118
|
+
if not changelog_path.exists():
|
|
119
|
+
print(f"Error: {changelog_path} not found.")
|
|
120
|
+
sys.exit(1)
|
|
121
|
+
|
|
122
|
+
# Read version from project file
|
|
123
|
+
version_file = find_version_file(root)
|
|
124
|
+
if version_file is None:
|
|
125
|
+
print("Error: No version file found (pyproject.toml, setup.cfg, package.json, VERSION).")
|
|
126
|
+
sys.exit(1)
|
|
127
|
+
|
|
128
|
+
version = read_version(version_file)
|
|
129
|
+
if version is None:
|
|
130
|
+
print(f"Error: Could not parse version from {version_file}")
|
|
131
|
+
sys.exit(1)
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
new_text, stamped = stamp_changelog(changelog_path, version)
|
|
135
|
+
except ValueError as e:
|
|
136
|
+
print(f"Error: {e}")
|
|
137
|
+
sys.exit(1)
|
|
138
|
+
|
|
139
|
+
if not stamped:
|
|
140
|
+
print("CHANGELOG.md: [Unreleased] is empty — nothing to stamp (run after the agent adds entries).")
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
changelog_path.write_bytes(new_text.encode("utf-8"))
|
|
144
|
+
print(f"CHANGELOG.md: [Unreleased] → [{version}] ({date.today().isoformat()})")
|
|
145
|
+
|
|
146
|
+
if getattr(args, "stage", False):
|
|
147
|
+
try:
|
|
148
|
+
subprocess.run(
|
|
149
|
+
["git", "add", str(changelog_path)],
|
|
150
|
+
check=True,
|
|
151
|
+
capture_output=True,
|
|
152
|
+
text=True,
|
|
153
|
+
)
|
|
154
|
+
except FileNotFoundError:
|
|
155
|
+
print("Error: git not found; could not stage changelog.", file=sys.stderr)
|
|
156
|
+
sys.exit(1)
|
|
157
|
+
except subprocess.CalledProcessError as exc:
|
|
158
|
+
detail = (exc.stderr or exc.stdout or "").strip()
|
|
159
|
+
print(f"Error: git add failed for {changelog_path}.", file=sys.stderr)
|
|
160
|
+
if detail:
|
|
161
|
+
print(detail, file=sys.stderr)
|
|
162
|
+
sys.exit(1)
|
|
163
|
+
print(f"Staged: {changelog_path}")
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from ..config import DEFAULT_WIKI_DIR, IDE_AGENTS, read_config, get_agent_config_path
|
|
7
|
+
from ..services import circuit_breaker
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run(args) -> None:
|
|
11
|
+
wiki_dir = getattr(args, "wiki_dir", DEFAULT_WIKI_DIR)
|
|
12
|
+
wiki_path = Path(wiki_dir)
|
|
13
|
+
git_dir = Path(".git")
|
|
14
|
+
|
|
15
|
+
print("LLM Wiki Status")
|
|
16
|
+
print("=" * 40)
|
|
17
|
+
|
|
18
|
+
# Wiki directory
|
|
19
|
+
if wiki_path.exists():
|
|
20
|
+
entity_count = len(list((wiki_path / "entities").glob("*.md"))) if (wiki_path / "entities").exists() else 0
|
|
21
|
+
module_count = len(list((wiki_path / "modules").glob("*.md"))) if (wiki_path / "modules").exists() else 0
|
|
22
|
+
workflow_count = len(list((wiki_path / "workflows").glob("*.md"))) if (wiki_path / "workflows").exists() else 0
|
|
23
|
+
print(f"Wiki directory: {wiki_dir} (exists)")
|
|
24
|
+
print(f" Entities: {entity_count}")
|
|
25
|
+
print(f" Modules: {module_count}")
|
|
26
|
+
print(f" Workflows: {workflow_count}")
|
|
27
|
+
else:
|
|
28
|
+
print(f"Wiki directory: {wiki_dir} (not found)")
|
|
29
|
+
|
|
30
|
+
# Agent config
|
|
31
|
+
agent_config = get_agent_config_path(wiki_dir)
|
|
32
|
+
if agent_config.exists():
|
|
33
|
+
config = read_config(wiki_dir)
|
|
34
|
+
agent = config.get("agent", "unknown")
|
|
35
|
+
mode = "IDE" if agent in IDE_AGENTS else "CLI"
|
|
36
|
+
print(f"Agent: {agent} ({mode})")
|
|
37
|
+
hints = config.get("quality_hints", True)
|
|
38
|
+
print(f"Quality hints: {'enabled' if hints else 'disabled'}")
|
|
39
|
+
else:
|
|
40
|
+
print("Agent: not configured (run `llm-wiki init --agent <agent>`)")
|
|
41
|
+
|
|
42
|
+
# Hooks
|
|
43
|
+
hooks_dir = git_dir / "hooks"
|
|
44
|
+
if hooks_dir.exists():
|
|
45
|
+
installed = []
|
|
46
|
+
for hook_name in ["post-commit", "pre-commit", "pre-push"]:
|
|
47
|
+
hook_file = hooks_dir / hook_name
|
|
48
|
+
if hook_file.exists():
|
|
49
|
+
content = hook_file.read_text(encoding="utf-8")
|
|
50
|
+
if "LLM Wiki" in content:
|
|
51
|
+
installed.append(hook_name)
|
|
52
|
+
if installed:
|
|
53
|
+
print(f"Hooks: {', '.join(installed)}")
|
|
54
|
+
else:
|
|
55
|
+
print("Hooks: none installed")
|
|
56
|
+
else:
|
|
57
|
+
print("Hooks: no .git/hooks directory")
|
|
58
|
+
|
|
59
|
+
# Circuit breaker
|
|
60
|
+
if git_dir.exists():
|
|
61
|
+
state = circuit_breaker.load_state(git_dir)
|
|
62
|
+
breaker_state = state.get("state", "closed")
|
|
63
|
+
failures = state.get("consecutive_failures", 0)
|
|
64
|
+
if breaker_state == "open":
|
|
65
|
+
print(f"Circuit breaker: OPEN ({failures} consecutive failures)")
|
|
66
|
+
print(" Run `llm-wiki trigger-agent --reset-breaker` to re-enable")
|
|
67
|
+
else:
|
|
68
|
+
print(f"Circuit breaker: closed ({failures} recent failures)")
|
|
69
|
+
else:
|
|
70
|
+
print("Circuit breaker: no .git directory")
|