git-repokit 0.9.10__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.
- git_repokit-0.9.10.dist-info/METADATA +422 -0
- git_repokit-0.9.10.dist-info/RECORD +174 -0
- git_repokit-0.9.10.dist-info/WHEEL +5 -0
- git_repokit-0.9.10.dist-info/entry_points.txt +2 -0
- git_repokit-0.9.10.dist-info/licenses/LICENSE +9 -0
- git_repokit-0.9.10.dist-info/top_level.txt +1 -0
- repokit/__init__.py +8 -0
- repokit/__main__.py +12 -0
- repokit/__version__.py +4 -0
- repokit/_vendor/__init__.py +0 -0
- repokit/_vendor/ailib/__init__.py +1 -0
- repokit/_vendor/ailib/analyzer.py +177 -0
- repokit/_vendor/ailib/backend.py +95 -0
- repokit/_vendor/ailib/cache.py +97 -0
- repokit/_vendor/ailib/parsers.py +52 -0
- repokit/_vendor/ailib/transports/__init__.py +16 -0
- repokit/_vendor/ailib/transports/cli.py +312 -0
- repokit/_vendor/ailib/transports/openai_compat.py +383 -0
- repokit/_vendor/ailib/transports/prompt_file.py +41 -0
- repokit/_vendor/ailib/types.py +224 -0
- repokit/auth_integration.py +244 -0
- repokit/badges.py +183 -0
- repokit/branch_utils.py +364 -0
- repokit/cli.py +2997 -0
- repokit/common_kit.py +202 -0
- repokit/config.py +686 -0
- repokit/defaults.py +220 -0
- repokit/directory_analyzer.py +1244 -0
- repokit/directory_profiles.py +423 -0
- repokit/git_config.py +370 -0
- repokit/github_create.py +142 -0
- repokit/github_setup.py +248 -0
- repokit/guardrails.py +707 -0
- repokit/history_cleaner.py +449 -0
- repokit/history_protection.py +434 -0
- repokit/hooks/pre-commit-guardrails +122 -0
- repokit/hooks/pre-commit-template +28 -0
- repokit/layers.py +510 -0
- repokit/licenses/0bsd.txt +14 -0
- repokit/licenses/LICENSES.md +27 -0
- repokit/licenses/__init__.py +142 -0
- repokit/licenses/agpl-3.0.txt +661 -0
- repokit/licenses/all-rights-reserved.txt +10 -0
- repokit/licenses/apache-2.0.txt +201 -0
- repokit/licenses/bsd-2-clause.txt +24 -0
- repokit/licenses/bsd-3-clause.txt +28 -0
- repokit/licenses/bsl-1.0.txt +23 -0
- repokit/licenses/cc-by-4.0.txt +395 -0
- repokit/licenses/cc-by-sa-4.0.txt +427 -0
- repokit/licenses/cc0-1.0.txt +121 -0
- repokit/licenses/epl-2.0.txt +277 -0
- repokit/licenses/gpl-2.0.txt +339 -0
- repokit/licenses/gpl-3.0.txt +674 -0
- repokit/licenses/isc.txt +15 -0
- repokit/licenses/lgpl-2.1.txt +504 -0
- repokit/licenses/lgpl-3.0.txt +165 -0
- repokit/licenses/mit.txt +21 -0
- repokit/licenses/mpl-2.0.txt +373 -0
- repokit/licenses/unlicense.txt +24 -0
- repokit/readme_blocks.py +112 -0
- repokit/remote_integration.py +627 -0
- repokit/repo_manager.py +2796 -0
- repokit/template_engine.py +391 -0
- repokit/templates/ai/claude/CLAUDE-nested.md.template +25 -0
- repokit/templates/ai/claude/CLAUDE.md.template +98 -0
- repokit/templates/ai/claude/instructions/step1_context_rebuilder.md +33 -0
- repokit/templates/ai/claude/instructions/step2_dev_workflow_process.md +67 -0
- repokit/templates/ai/claude/instructions/step3_context_bridge.md +52 -0
- repokit/templates/common/CHANGELOG.md.template +15 -0
- repokit/templates/common/CONTRIBUTING.md.template +31 -0
- repokit/templates/common/README.md.template +30 -0
- repokit/templates/common/gitattributes.template +17 -0
- repokit/templates/common/gitignore.template +141 -0
- repokit/templates/extras/FUNDING.yml.template +13 -0
- repokit/templates/extras/ROADMAP.md.template +17 -0
- repokit/templates/extras/dependabot.yml.template +22 -0
- repokit/templates/extras/one-offs-README.md.template +28 -0
- repokit/templates/extras/platform-support.md.template +11 -0
- repokit/templates/extras/pull_request_template.md.template +25 -0
- repokit/templates/extras/release-template.md.template +34 -0
- repokit/templates/extras/settings.json.template +11 -0
- repokit/templates/extras/stale.yml.template +14 -0
- repokit/templates/github/CODEOWNERS.template +11 -0
- repokit/templates/github/ISSUE_TEMPLATE/bug-report.md.template +29 -0
- repokit/templates/github/ISSUE_TEMPLATE/feature-request.md.template +19 -0
- repokit/templates/github/issues/notes.md.template +18 -0
- repokit/templates/github/issues/roadmap.md.template +15 -0
- repokit/templates/hooks/pre-commit.template +64 -0
- repokit/templates/layers/archetypes/any/claude-plugin/.claude-plugin/marketplace.json +20 -0
- repokit/templates/layers/archetypes/any/claude-plugin/plugin/.claude-plugin/plugin.json +15 -0
- repokit/templates/layers/archetypes/any/claude-plugin/plugin/README.md +28 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.github/workflows/ci-c-cpp.yml +24 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.repokit-common.toml.rkappend +4 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.vscode/launch.json +29 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.vscode/tasks.json +26 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/CMakeLists.txt +47 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/CMakePresets.json +23 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/cmake/GenerateStamp.cmake +45 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/src/main.cpp +27 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/tests/test_version.cpp +20 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/vcpkg.json +5 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/.vscode/launch.json +27 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/Makefile +10 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/build.bat +7 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/src/__RK_SNAKE__.c +26 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/.vscode/launch.json +17 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/.vscode/tasks.json +20 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/__RK_PROJECT__.sln +19 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/__RK_PROJECT__.vcxproj +41 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/src/main.cpp +26 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/.repokit-common.toml +28 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/.vscode/launch.json +34 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/hooks/hooks.json +15 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/hooks/scripts/session-start.mjs +23 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/tests/manifest.test.js +30 -0
- repokit/templates/layers/archetypes/js-ts/node/.repokit-common.toml.rkappend +6 -0
- repokit/templates/layers/archetypes/js-ts/node/package.json +13 -0
- repokit/templates/layers/archetypes/js-ts/node-package/.github/workflows/release.yml +39 -0
- repokit/templates/layers/archetypes/js-ts/node-package/package.json +32 -0
- repokit/templates/layers/archetypes/js-ts/node-package/src/index.ts +13 -0
- repokit/templates/layers/archetypes/js-ts/node-package/tests/index.test.ts +11 -0
- repokit/templates/layers/archetypes/js-ts/script/.vscode/launch.json +34 -0
- repokit/templates/layers/archetypes/js-ts/script/__RK_PROJECT__.js +37 -0
- repokit/templates/layers/archetypes/js-ts/script/tests/version.test.js +26 -0
- repokit/templates/layers/archetypes/js-ts/ts/package.json +18 -0
- repokit/templates/layers/archetypes/js-ts/ts/tsconfig.json +15 -0
- repokit/templates/layers/archetypes/python/claude-plugin/.github/workflows/ci-python.yml +44 -0
- repokit/templates/layers/archetypes/python/claude-plugin/.repokit-common.toml +21 -0
- repokit/templates/layers/archetypes/python/claude-plugin/.vscode/launch.json +33 -0
- repokit/templates/layers/archetypes/python/claude-plugin/VERSION +1 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/hooks.json +15 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/scripts/run-hook.mjs +42 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/scripts/session_start.py +31 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/tests/test_manifest.py +42 -0
- repokit/templates/layers/archetypes/python/pypi/.github/workflows/release.yml +63 -0
- repokit/templates/layers/archetypes/python/pypi/.vscode/launch.json +43 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/__init__.py +5 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/__main__.py +6 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/_version.py +96 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/cli.py +18 -0
- repokit/templates/layers/archetypes/python/pypi/pyproject.toml +71 -0
- repokit/templates/layers/archetypes/python/pypi/tests/conftest.py +11 -0
- repokit/templates/layers/archetypes/python/pypi/tests/test_version.py +50 -0
- repokit/templates/layers/archetypes/python/script/.github/workflows/ci-python.yml +43 -0
- repokit/templates/layers/archetypes/python/script/.repokit-common.toml +14 -0
- repokit/templates/layers/archetypes/python/script/.vscode/launch.json +33 -0
- repokit/templates/layers/archetypes/python/script/VERSION +1 -0
- repokit/templates/layers/archetypes/python/script/__RK_PROJECT__.py +40 -0
- repokit/templates/layers/archetypes/python/script/tests/test_version.py +25 -0
- repokit/templates/layers/archetypes/python/setuppy/requirements.txt +8 -0
- repokit/templates/layers/archetypes/python/setuppy/setup.py +23 -0
- repokit/templates/layers/languages/c-cpp/.gitignore.rkappend +20 -0
- repokit/templates/layers/languages/c-cpp/.repokit-common.toml +7 -0
- repokit/templates/layers/languages/c-cpp/include/__RK_SNAKE__/version.h +20 -0
- repokit/templates/layers/languages/c-cpp/src/app.rc +25 -0
- repokit/templates/layers/languages/js-ts/.editorconfig +15 -0
- repokit/templates/layers/languages/js-ts/.github/workflows/ci-js-ts.yml +41 -0
- repokit/templates/layers/languages/js-ts/.gitignore.rkappend +13 -0
- repokit/templates/layers/languages/js-ts/.repokit-common.toml +19 -0
- repokit/templates/layers/languages/js-ts/.vscode/launch.json +24 -0
- repokit/templates/layers/languages/js-ts/VERSION +1 -0
- repokit/templates/layers/languages/python/.github/workflows/ci-python.yml +76 -0
- repokit/templates/layers/languages/python/.gitignore.rkappend +15 -0
- repokit/templates/layers/languages/python/.vscode/launch.json +33 -0
- repokit/templates/layers/manifest.toml +302 -0
- repokit/templates/vault/CLAUDE.md +29 -0
- repokit/templates/vault/claude/_maps/MOC-project.md +15 -0
- repokit/templates/vault/claude/_oracle/backlinks.md +3 -0
- repokit/templates/vault/claude/_oracle/concepts.md +12 -0
- repokit/templates/vault/claude/_oracle/manifest.md +10 -0
- repokit/templates/vault/claude/_todo/ACTIVE-WORK.md +18 -0
- repokit/templates/vault/claude/_todo/OPEN-LOOPS.md +14 -0
- repokit/utils.py +316 -0
- repokit/vault.py +248 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Template engine for RepoKit.
|
|
4
|
+
|
|
5
|
+
Handles loading and rendering templates from the package's template directory.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import datetime
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import logging
|
|
12
|
+
import re
|
|
13
|
+
import string
|
|
14
|
+
import uuid
|
|
15
|
+
import importlib.resources as resources
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Dict, Any, Optional, List, Union
|
|
18
|
+
|
|
19
|
+
# The second placeholder dialect: __RK_NAME__ tokens, used by the code-type
|
|
20
|
+
# layers (repokit/layers.py) in file contents AND file names, where a `$` is
|
|
21
|
+
# shell-hostile and collides with CMake's ${VAR}, Make's $$ and Actions' ${{ }}.
|
|
22
|
+
# The structure templates (*.template) keep string.Template's $var; both
|
|
23
|
+
# dialects read one context (token_context) so a project has one set of names.
|
|
24
|
+
RK_TOKEN = re.compile(r"__RK_([A-Z_]+?)__")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def token_context(context: Dict[str, Any], archetype: Optional[str] = None) -> Dict[str, Any]:
|
|
28
|
+
"""The context plus the RK_* names the token dialect resolves.
|
|
29
|
+
|
|
30
|
+
Derived from the same values the $var templates use (project_name,
|
|
31
|
+
description, author, author_email, the GitHub user), after the proof's
|
|
32
|
+
assemble.py names(): PROJECT (my-tool), SNAKE (my_tool), MACRO (MY_TOOL),
|
|
33
|
+
PASCAL (MyTool), ORG, DESCRIPTION, ARCHETYPE, YEAR, DATE, and two GUIDs
|
|
34
|
+
for Visual Studio files. Call it once per project so every file shares
|
|
35
|
+
the GUIDs.
|
|
36
|
+
"""
|
|
37
|
+
name = str(context.get("project_name") or "project")
|
|
38
|
+
words = [w for w in re.split(r"[-_\s]+", name) if w] or [name]
|
|
39
|
+
github = context.get("github")
|
|
40
|
+
# github_org is the repository's owner the structure templates use, so
|
|
41
|
+
# both dialects name the same owner (0.9.6)
|
|
42
|
+
org = (context.get("github_org")
|
|
43
|
+
or context.get("organization")
|
|
44
|
+
or (github.get("username") if isinstance(github, dict) else None)
|
|
45
|
+
or context.get("github_user") or "your-org")
|
|
46
|
+
today = datetime.date.today()
|
|
47
|
+
tokens = {
|
|
48
|
+
"RK_PROJECT": name,
|
|
49
|
+
"RK_SNAKE": "_".join(words).lower(),
|
|
50
|
+
"RK_MACRO": "_".join(words).upper(),
|
|
51
|
+
"RK_PASCAL": "".join(w[:1].upper() + w[1:] for w in words),
|
|
52
|
+
"RK_ORG": str(org),
|
|
53
|
+
"RK_DESCRIPTION": str(context.get("description") or ""),
|
|
54
|
+
"RK_ARCHETYPE": archetype or "",
|
|
55
|
+
"RK_YEAR": str(today.year),
|
|
56
|
+
"RK_DATE": today.isoformat(),
|
|
57
|
+
"RK_GUID_PROJECT": str(uuid.uuid4()).upper(),
|
|
58
|
+
"RK_GUID_SOLUTION": str(uuid.uuid4()).upper(),
|
|
59
|
+
"RK_AUTHOR": str(context.get("author") or context.get("author_name") or ""),
|
|
60
|
+
"RK_EMAIL": str(context.get("author_email") or ""),
|
|
61
|
+
# 0.9.6: the licence the create wrote and the topics as keywords (one
|
|
62
|
+
# form valid in a TOML or a JSON array). RK_LICENSE is a manifest's
|
|
63
|
+
# string (plugin.json); the pyproject lines are whole lines (0.9.8,
|
|
64
|
+
# PEP 639): `license = "<SPDX>"` only for a catalogue licence, and
|
|
65
|
+
# `license-files` whenever a LICENSE is written, else nothing.
|
|
66
|
+
"RK_LICENSE": str(context.get("license_expression") or "UNLICENSED"),
|
|
67
|
+
"RK_LICENSE_FIELD": (f'license = "{context["license_spdx"]}"' if context.get("license_spdx") else ""),
|
|
68
|
+
"RK_LICENSE_FILES": ('license-files = ["LICENSE"]' if context.get("license_file_written") else ""),
|
|
69
|
+
"RK_KEYWORDS": ", ".join(json.dumps(str(t)) for t in (context.get("topics") or [])),
|
|
70
|
+
}
|
|
71
|
+
return {**context, **tokens}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class TemplateEngine:
|
|
75
|
+
"""
|
|
76
|
+
Template engine for loading and rendering templates.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(self, templates_dir: Optional[str] = None, verbose: int = 0):
|
|
80
|
+
"""
|
|
81
|
+
Initialize the template engine.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
templates_dir: Path to template directory (or use package's templates)
|
|
85
|
+
verbose: Verbosity level (0=normal, 1=info, 2=debug)
|
|
86
|
+
"""
|
|
87
|
+
self.verbose = verbose
|
|
88
|
+
|
|
89
|
+
# Configure logging
|
|
90
|
+
self.logger = logging.getLogger("repokit.templates")
|
|
91
|
+
|
|
92
|
+
# Try multiple locations for templates
|
|
93
|
+
self.templates_dirs = []
|
|
94
|
+
|
|
95
|
+
# 1. Use provided template directory if specified
|
|
96
|
+
if templates_dir:
|
|
97
|
+
self.templates_dirs.append(templates_dir)
|
|
98
|
+
|
|
99
|
+
# 2. Try to find templates in the package via importlib.resources
|
|
100
|
+
try:
|
|
101
|
+
pkg_templates = resources.files("repokit").joinpath("templates")
|
|
102
|
+
# resources.as_file will give us a real filesystem path
|
|
103
|
+
with resources.as_file(pkg_templates) as tpl_path:
|
|
104
|
+
self.templates_dirs.append(str(tpl_path))
|
|
105
|
+
except (ImportError, FileNotFoundError):
|
|
106
|
+
# either stdlib importlib.resources is missing (very old Python)
|
|
107
|
+
# or the package isn’t installed with that resource
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
# 3. Check current script directory
|
|
111
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
112
|
+
self.templates_dirs.append(os.path.join(script_dir, "templates"))
|
|
113
|
+
|
|
114
|
+
# 4. Check current working directory
|
|
115
|
+
cwd_templates = os.path.join(os.getcwd(), "repokit", "templates")
|
|
116
|
+
self.templates_dirs.append(cwd_templates)
|
|
117
|
+
|
|
118
|
+
# 5. Check parent directories (for development mode)
|
|
119
|
+
current_dir = os.getcwd()
|
|
120
|
+
for _ in range(3): # Check up to 3 levels up
|
|
121
|
+
parent_dir = os.path.dirname(current_dir)
|
|
122
|
+
if parent_dir == current_dir: # Reached root
|
|
123
|
+
break
|
|
124
|
+
current_dir = parent_dir
|
|
125
|
+
self.templates_dirs.append(
|
|
126
|
+
os.path.join(current_dir, "repokit", "templates")
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Find first valid template directory
|
|
130
|
+
self.templates_dir = None
|
|
131
|
+
for dir_path in self.templates_dirs:
|
|
132
|
+
if os.path.exists(dir_path) and os.path.isdir(dir_path):
|
|
133
|
+
self.templates_dir = dir_path
|
|
134
|
+
break
|
|
135
|
+
|
|
136
|
+
if self.templates_dir:
|
|
137
|
+
if self.verbose >= 1:
|
|
138
|
+
self.logger.info(f"Using templates from: {self.templates_dir}")
|
|
139
|
+
else:
|
|
140
|
+
self.logger.warning(
|
|
141
|
+
f"No template directory found. Tried: {self.templates_dirs}"
|
|
142
|
+
)
|
|
143
|
+
# Use fallback templates
|
|
144
|
+
self.templates_dir = None
|
|
145
|
+
|
|
146
|
+
# Fallback templates for critical files
|
|
147
|
+
self.fallback_templates = {
|
|
148
|
+
"common/README.md.template": """# $project_name\n\n$description\n""",
|
|
149
|
+
"common/gitignore.template": """# Ignore common files\n.DS_Store\n*.log\n""",
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
def get_template_path(
|
|
153
|
+
self, template_name: str, category: str = "common"
|
|
154
|
+
) -> Optional[str]:
|
|
155
|
+
"""
|
|
156
|
+
Get the path to a template file.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
template_name: Name of the template file
|
|
160
|
+
category: Category of the template (common, github, languages/python, etc.)
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Path to the template file or None if not found
|
|
164
|
+
"""
|
|
165
|
+
if not self.templates_dir:
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
# Try direct path first (if template_name includes a category)
|
|
169
|
+
if "/" in template_name:
|
|
170
|
+
direct_path = os.path.join(self.templates_dir, template_name)
|
|
171
|
+
if os.path.exists(direct_path):
|
|
172
|
+
return direct_path
|
|
173
|
+
|
|
174
|
+
# Try with .template extension
|
|
175
|
+
if not template_name.endswith(".template"):
|
|
176
|
+
template_name = f"{template_name}.template"
|
|
177
|
+
|
|
178
|
+
# Look in multiple possible locations
|
|
179
|
+
possible_paths = []
|
|
180
|
+
|
|
181
|
+
# 1. Look in the specified category
|
|
182
|
+
possible_paths.append(os.path.join(self.templates_dir, category, template_name))
|
|
183
|
+
|
|
184
|
+
# 2. Try common category if not found and not already looking there
|
|
185
|
+
if category != "common":
|
|
186
|
+
possible_paths.append(
|
|
187
|
+
os.path.join(self.templates_dir, "common", template_name)
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# 3. Try languages subdirectory
|
|
191
|
+
if "/" not in category and not category.startswith("languages/"):
|
|
192
|
+
possible_paths.append(
|
|
193
|
+
os.path.join(self.templates_dir, "languages", category, template_name)
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# 4. Try root templates directory
|
|
197
|
+
possible_paths.append(os.path.join(self.templates_dir, template_name))
|
|
198
|
+
|
|
199
|
+
# Check all possible paths
|
|
200
|
+
for path in possible_paths:
|
|
201
|
+
if os.path.exists(path):
|
|
202
|
+
if self.verbose >= 2:
|
|
203
|
+
self.logger.debug(f"Found template at: {path}")
|
|
204
|
+
return path
|
|
205
|
+
|
|
206
|
+
# Log the paths we tried
|
|
207
|
+
if self.verbose >= 2:
|
|
208
|
+
self.logger.debug(f"Tried looking for template in: {possible_paths}")
|
|
209
|
+
|
|
210
|
+
return None
|
|
211
|
+
|
|
212
|
+
def load_template(
|
|
213
|
+
self, template_name: str, category: str = "common"
|
|
214
|
+
) -> Optional[str]:
|
|
215
|
+
"""
|
|
216
|
+
Load a template file.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
template_name: Name of the template file
|
|
220
|
+
category: Category of the template (common, github, languages/python, etc.)
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Template content as string or None if not found
|
|
224
|
+
"""
|
|
225
|
+
# First try to find the template file
|
|
226
|
+
template_path = self.get_template_path(template_name, category)
|
|
227
|
+
if template_path:
|
|
228
|
+
try:
|
|
229
|
+
with open(template_path, "r", encoding="utf-8") as f:
|
|
230
|
+
content = f.read()
|
|
231
|
+
|
|
232
|
+
if self.verbose >= 2:
|
|
233
|
+
self.logger.debug(f"Loaded template: {template_path}")
|
|
234
|
+
return content
|
|
235
|
+
except Exception as e:
|
|
236
|
+
self.logger.error(f"Error loading template {template_path}: {str(e)}")
|
|
237
|
+
|
|
238
|
+
# If not found, try fallback templates
|
|
239
|
+
fallback_key = f"{category}/{template_name}"
|
|
240
|
+
if not fallback_key.endswith(".template"):
|
|
241
|
+
fallback_key += ".template"
|
|
242
|
+
|
|
243
|
+
if fallback_key in self.fallback_templates:
|
|
244
|
+
if self.verbose >= 1:
|
|
245
|
+
self.logger.info(f"Using fallback template for: {fallback_key}")
|
|
246
|
+
return self.fallback_templates[fallback_key]
|
|
247
|
+
|
|
248
|
+
# Final fallback for specific template types
|
|
249
|
+
base_template_name = os.path.basename(template_name)
|
|
250
|
+
if base_template_name == "README.md.template":
|
|
251
|
+
return """# $project_name\n\n$description\n"""
|
|
252
|
+
elif base_template_name == "gitignore.template":
|
|
253
|
+
return """# Ignore patterns\n*.log\n.DS_Store\n"""
|
|
254
|
+
|
|
255
|
+
self.logger.warning(
|
|
256
|
+
f"Template not found: {template_name} in category {category}"
|
|
257
|
+
)
|
|
258
|
+
return None
|
|
259
|
+
|
|
260
|
+
def load_template_bytes(self, template_name: str, category: str = "common") -> Optional[bytes]:
|
|
261
|
+
"""load_template, but the file's bytes (a fallback template is UTF-8)."""
|
|
262
|
+
template_path = self.get_template_path(template_name, category)
|
|
263
|
+
if template_path:
|
|
264
|
+
try:
|
|
265
|
+
with open(template_path, "rb") as f:
|
|
266
|
+
return f.read()
|
|
267
|
+
except OSError as e:
|
|
268
|
+
self.logger.error(f"Error loading template {template_path}: {str(e)}")
|
|
269
|
+
text = self.load_template(template_name, category)
|
|
270
|
+
return text.encode("utf-8") if text is not None else None
|
|
271
|
+
|
|
272
|
+
def render_bytes(self, data: bytes, context: Dict[str, Any], dollar: bool) -> bytes:
|
|
273
|
+
"""Substitute placeholders in a file's bytes, changing nothing else.
|
|
274
|
+
|
|
275
|
+
latin-1 maps every byte to one character and back, so a BOM, CRLF
|
|
276
|
+
line endings and bytes outside UTF-8 all survive; the substituted
|
|
277
|
+
values are encoded as UTF-8 first. With ``dollar`` the $var dialect
|
|
278
|
+
(string.Template.safe_substitute: unknown and malformed placeholders
|
|
279
|
+
such as ${CMAKE_VAR}, $<GENEX>, ${{ github }} and $1 are left alone)
|
|
280
|
+
runs first; the __RK_NAME__ tokens are replaced in every case, and an
|
|
281
|
+
unknown token is left in place. Proven on both template populations
|
|
282
|
+
by tests/one-offs/thinking/poc-one-engine/poc_one_engine.py.
|
|
283
|
+
"""
|
|
284
|
+
values = {k: str(v).encode("utf-8").decode("latin-1") for k, v in context.items()}
|
|
285
|
+
text = data.decode("latin-1")
|
|
286
|
+
if dollar:
|
|
287
|
+
text = string.Template(text).safe_substitute(values)
|
|
288
|
+
text = RK_TOKEN.sub(lambda m: values.get("RK_" + m.group(1), m.group(0)), text)
|
|
289
|
+
return text.encode("latin-1")
|
|
290
|
+
|
|
291
|
+
def render_template(self, template_content: str, context: Dict[str, Any]) -> str:
|
|
292
|
+
"""
|
|
293
|
+
Render a template with the given context.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
template_content: Template content as string
|
|
297
|
+
context: Dictionary of variables for template rendering
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Rendered template as string
|
|
301
|
+
"""
|
|
302
|
+
try:
|
|
303
|
+
template = string.Template(template_content)
|
|
304
|
+
return template.safe_substitute(context)
|
|
305
|
+
except Exception as e:
|
|
306
|
+
self.logger.error(f"Error rendering template: {str(e)}")
|
|
307
|
+
# Return original content in case of error
|
|
308
|
+
return template_content
|
|
309
|
+
|
|
310
|
+
def render_template_to_file(
|
|
311
|
+
self,
|
|
312
|
+
template_name: str,
|
|
313
|
+
output_path: str,
|
|
314
|
+
context: Dict[str, Any],
|
|
315
|
+
category: str = "common",
|
|
316
|
+
preserve_existing: bool = False,
|
|
317
|
+
) -> bool:
|
|
318
|
+
"""
|
|
319
|
+
Load, render, and write a template to a file.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
template_name: Name of the template file
|
|
323
|
+
output_path: Path to write the rendered template
|
|
324
|
+
context: Dictionary of variables for template rendering
|
|
325
|
+
category: Category of the template (common, github, languages/python, etc.)
|
|
326
|
+
preserve_existing: If True, don't overwrite existing files
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
True if successful, False otherwise
|
|
330
|
+
"""
|
|
331
|
+
# Check if file exists and should be preserved
|
|
332
|
+
if preserve_existing and os.path.exists(output_path):
|
|
333
|
+
self.logger.info(f"Preserving existing file: {output_path}")
|
|
334
|
+
return True
|
|
335
|
+
|
|
336
|
+
data = self.load_template_bytes(template_name, category)
|
|
337
|
+
if not data:
|
|
338
|
+
return False
|
|
339
|
+
|
|
340
|
+
# Bytes in, bytes out: the template's own line endings reach the file
|
|
341
|
+
# (the templates are pinned to LF by .gitattributes), where the text
|
|
342
|
+
# path used to write CRLF on Windows.
|
|
343
|
+
rendered = self.render_bytes(data, context, dollar=True)
|
|
344
|
+
|
|
345
|
+
try:
|
|
346
|
+
# Create directory if it doesn't exist
|
|
347
|
+
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
|
348
|
+
|
|
349
|
+
with open(output_path, "wb") as f:
|
|
350
|
+
f.write(rendered)
|
|
351
|
+
|
|
352
|
+
if self.verbose >= 2:
|
|
353
|
+
self.logger.debug(f"Rendered template {template_name} to {output_path}")
|
|
354
|
+
return True
|
|
355
|
+
except Exception as e:
|
|
356
|
+
self.logger.error(f"Error writing template to {output_path}: {str(e)}")
|
|
357
|
+
return False
|
|
358
|
+
|
|
359
|
+
def list_templates(self, category: str = None) -> List[str]:
|
|
360
|
+
"""
|
|
361
|
+
List available templates.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
category: Category to list templates from (or all if None)
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
List of template names
|
|
368
|
+
"""
|
|
369
|
+
templates = []
|
|
370
|
+
|
|
371
|
+
# Return fallback templates if no templates directory
|
|
372
|
+
if not self.templates_dir:
|
|
373
|
+
return list(self.fallback_templates.keys())
|
|
374
|
+
|
|
375
|
+
if category:
|
|
376
|
+
category_path = os.path.join(self.templates_dir, category)
|
|
377
|
+
if os.path.exists(category_path) and os.path.isdir(category_path):
|
|
378
|
+
for file in os.listdir(category_path):
|
|
379
|
+
if file.endswith(".template"):
|
|
380
|
+
templates.append(file)
|
|
381
|
+
else:
|
|
382
|
+
# List all templates
|
|
383
|
+
for root, _, files in os.walk(self.templates_dir):
|
|
384
|
+
for file in files:
|
|
385
|
+
if file.endswith(".template"):
|
|
386
|
+
rel_path = os.path.relpath(
|
|
387
|
+
os.path.join(root, file), self.templates_dir
|
|
388
|
+
)
|
|
389
|
+
templates.append(rel_path)
|
|
390
|
+
|
|
391
|
+
return templates
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# CLAUDE.md -- $project_name
|
|
2
|
+
|
|
3
|
+
$description
|
|
4
|
+
|
|
5
|
+
Guidance for Claude Code sessions in this repository. It stays short: the project's notes, designs and boards live in the private vault.
|
|
6
|
+
|
|
7
|
+
## Start here
|
|
8
|
+
|
|
9
|
+
- `private/CLAUDE.md` -- the vault: where the design documents, postmortems, notes and boards are, and how to write there.
|
|
10
|
+
- `README.md` -- what the project is, and how to install, run and test it.
|
|
11
|
+
|
|
12
|
+
## Commands
|
|
13
|
+
|
|
14
|
+
Setup:
|
|
15
|
+
|
|
16
|
+
$setup_block
|
|
17
|
+
|
|
18
|
+
Tests:
|
|
19
|
+
|
|
20
|
+
$test_block
|
|
21
|
+
|
|
22
|
+
## Rules
|
|
23
|
+
|
|
24
|
+
- `private/` is its own git repository, ignored by this one and never pushed. Commit vault files from inside it, by named path.
|
|
25
|
+
- Commit and push only when the person asks.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
**Project**: $project_name
|
|
8
|
+
**Description**: $description
|
|
9
|
+
**Language**: $language
|
|
10
|
+
**Created**: $date
|
|
11
|
+
|
|
12
|
+
## Common Development Commands
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# Run tests
|
|
16
|
+
$test_command
|
|
17
|
+
|
|
18
|
+
# Lint/Format code
|
|
19
|
+
$lint_command
|
|
20
|
+
|
|
21
|
+
# Build/Run
|
|
22
|
+
$build_command
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Test Development Guidelines
|
|
26
|
+
|
|
27
|
+
### Test Script Management
|
|
28
|
+
- **One-off test scripts**: Store in `tests/one-offs/` directory
|
|
29
|
+
- **Test runs**: Always use `test_runs/` directory (not test-runs)
|
|
30
|
+
- **Keep test artifacts**: Don't delete one-off scripts - they may be useful later
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
```bash
|
|
34
|
+
# Create test script
|
|
35
|
+
mkdir -p tests/one-offs
|
|
36
|
+
vim tests/one-offs/test_feature.py
|
|
37
|
+
|
|
38
|
+
# Run tests that create artifacts
|
|
39
|
+
cd test_runs # Standardized location
|
|
40
|
+
python test_feature.py
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Architecture Notes
|
|
44
|
+
|
|
45
|
+
<!-- Add your project-specific architecture notes here -->
|
|
46
|
+
|
|
47
|
+
## Private Branch Guidelines
|
|
48
|
+
|
|
49
|
+
### Documentation Requirements
|
|
50
|
+
|
|
51
|
+
- **ALWAYS** document all work performed in timestamped files under `./private/claude/`
|
|
52
|
+
- Use filename format: `YYYY_MM_DD__HH_MM_SS__(TOPIC).md`
|
|
53
|
+
- Include all commands executed, their outputs, and summaries
|
|
54
|
+
|
|
55
|
+
### Version Control Practices
|
|
56
|
+
|
|
57
|
+
- The `private` branch is LOCAL ONLY - never push to remote repositories
|
|
58
|
+
- Commit frequently to track all changes and edits
|
|
59
|
+
- Make minimal edits to preserve diff readability:
|
|
60
|
+
- Avoid unnecessary variable/function/class name changes
|
|
61
|
+
- Only rename when truly warranted for clarity or correctness
|
|
62
|
+
- Preserve existing code structure when possible
|
|
63
|
+
|
|
64
|
+
### Private Content Structure
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
private/
|
|
68
|
+
├── claude/ # All Claude-assisted work documentation
|
|
69
|
+
│ ├── instructions/ # Core workflow instructions
|
|
70
|
+
│ └── YYYY_MM_DD__HH_MM_SS__(TOPIC).md
|
|
71
|
+
├── convos/ # Conversation logs (protected from commits)
|
|
72
|
+
└── logs/ # System logs (protected from commits)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## The Dev Workflow Process
|
|
76
|
+
|
|
77
|
+
When tackling complex problems or making significant decisions, use **THE PROCESS** - a 5-stage systematic approach:
|
|
78
|
+
|
|
79
|
+
### 🔁 The 5 Stages:
|
|
80
|
+
|
|
81
|
+
1. **Problem Analysis** - Define and understand the full context
|
|
82
|
+
2. **Conceptual Exploration** - Explore the nature and relationships
|
|
83
|
+
3. **Brainstorming Solutions** - Generate and evaluate multiple approaches
|
|
84
|
+
4. **Synthesis and Recommendation** - Combine best elements into optimal solution
|
|
85
|
+
5. **Implementation Plan** - Create actionable roadmap
|
|
86
|
+
|
|
87
|
+
**When to use**: For any complex problem, design decision, bug investigation, or strategic choice.
|
|
88
|
+
|
|
89
|
+
## Additional Instructions
|
|
90
|
+
|
|
91
|
+
Detailed workflow instructions are available in `./private/claude/instructions/`:
|
|
92
|
+
- Context rebuilding between sessions
|
|
93
|
+
- The full Dev Workflow Process
|
|
94
|
+
- Creating conversation summaries
|
|
95
|
+
|
|
96
|
+
## Project-Specific Notes
|
|
97
|
+
|
|
98
|
+
<!-- Add any project-specific guidelines, patterns, or warnings here -->
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Context Rebuilder Instructions
|
|
2
|
+
|
|
3
|
+
When resuming work or switching between sessions, follow these steps to rebuild context:
|
|
4
|
+
|
|
5
|
+
## 1. Initial Context Evaluation
|
|
6
|
+
- Review any provided summaries, notes, or partial conversations
|
|
7
|
+
- Identify the main topic/goal and current project status
|
|
8
|
+
- Note important artifacts, files, or references mentioned
|
|
9
|
+
|
|
10
|
+
## 2. Reconstructing Full Context
|
|
11
|
+
- **Project Objectives**: Clearly restate what is being aimed for
|
|
12
|
+
- **Current Situation**: Summarize current phase, tasks in progress, recent developments
|
|
13
|
+
- **Prior Decisions**: State decisions made with their justification
|
|
14
|
+
- **Known Constraints**: Time, resources, technical limitations
|
|
15
|
+
- **Outstanding Issues**: Open questions, risks, or ambiguities
|
|
16
|
+
|
|
17
|
+
## 3. Handling Ambiguous Context
|
|
18
|
+
If information is unclear, ask specific questions:
|
|
19
|
+
- "Could you clarify the main objective?"
|
|
20
|
+
- "What is the current status of [specific feature]?"
|
|
21
|
+
- "Are there any critical constraints I should know about?"
|
|
22
|
+
|
|
23
|
+
## 4. Context Verification
|
|
24
|
+
Summarize the reconstructed context back to confirm accuracy:
|
|
25
|
+
- "Based on the provided information, here's my understanding..."
|
|
26
|
+
- "Please confirm or correct any misunderstandings"
|
|
27
|
+
|
|
28
|
+
## 5. For Code Projects
|
|
29
|
+
When context is missing, ask for:
|
|
30
|
+
1. Summary of previous conversation
|
|
31
|
+
2. List of project files (`tree` or `ls` output)
|
|
32
|
+
3. Key source files currently being worked on
|
|
33
|
+
4. Any specific technical constraints or requirements
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# The Dev Workflow Process (The Process)
|
|
2
|
+
|
|
3
|
+
A systematic 5-stage approach for tackling complex problems, making design decisions, and solving technical challenges.
|
|
4
|
+
|
|
5
|
+
## 🔁 The 5 Stages
|
|
6
|
+
|
|
7
|
+
### 1. Problem Analysis
|
|
8
|
+
> *"Clarify before solving. Understand before deciding."*
|
|
9
|
+
|
|
10
|
+
- Define the core problem with precision
|
|
11
|
+
- Break into sub-problems or dimensions
|
|
12
|
+
- Explore pros, cons, edge cases, and risks
|
|
13
|
+
- Consider short-term vs long-term implications
|
|
14
|
+
- Identify ambiguities and dependencies
|
|
15
|
+
|
|
16
|
+
### 2. Conceptual Exploration
|
|
17
|
+
> *"Think about how the system works. What are the forces at play?"*
|
|
18
|
+
|
|
19
|
+
- Understand WHY the problem exists
|
|
20
|
+
- Explore mental models and analogies
|
|
21
|
+
- Consider different approach types
|
|
22
|
+
- Examine relationships between elements
|
|
23
|
+
- Think systemically about cause and effect
|
|
24
|
+
|
|
25
|
+
### 3. Brainstorming Solutions
|
|
26
|
+
> *"Don't jump to the first good idea. Explore the option space."*
|
|
27
|
+
|
|
28
|
+
- Generate 3-5 distinct solutions
|
|
29
|
+
- For each: analyze pros, cons, trade-offs
|
|
30
|
+
- Evaluate feasibility, cost, and risks
|
|
31
|
+
- Consider hybrid approaches
|
|
32
|
+
- Think about edge cases each handles
|
|
33
|
+
|
|
34
|
+
### 4. Synthesis and Recommendation
|
|
35
|
+
> *"Choose what's most balanced, strategic, and resilient."*
|
|
36
|
+
|
|
37
|
+
- Combine best elements from various solutions
|
|
38
|
+
- Select approach that best:
|
|
39
|
+
- Solves the core problem
|
|
40
|
+
- Handles edge cases
|
|
41
|
+
- Aligns with long-term goals
|
|
42
|
+
- Balances constraints
|
|
43
|
+
- Justify with evidence from analysis
|
|
44
|
+
|
|
45
|
+
### 5. Implementation Plan
|
|
46
|
+
> *"Make it real. Turn insight into execution."*
|
|
47
|
+
|
|
48
|
+
- Define step-by-step roadmap
|
|
49
|
+
- Include milestones and checkpoints
|
|
50
|
+
- Identify tools and resources needed
|
|
51
|
+
- Plan for contingencies
|
|
52
|
+
- Define success criteria
|
|
53
|
+
|
|
54
|
+
## When to Use The Process
|
|
55
|
+
|
|
56
|
+
**Use for:**
|
|
57
|
+
- Complex multi-faceted problems
|
|
58
|
+
- Important design decisions
|
|
59
|
+
- Performance optimization
|
|
60
|
+
- Architecture choices
|
|
61
|
+
- Debugging mysterious issues
|
|
62
|
+
|
|
63
|
+
**Maybe skip for:**
|
|
64
|
+
- Simple, well-defined tasks
|
|
65
|
+
- Following established patterns
|
|
66
|
+
- Time-critical emergencies (do quick version)
|
|
67
|
+
- Reversible, low-impact changes
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Context Bridge - Conversation Summaries
|
|
2
|
+
|
|
3
|
+
When approaching token limits or needing to transfer context between sessions, create a structured summary.
|
|
4
|
+
|
|
5
|
+
## Summary Structure
|
|
6
|
+
|
|
7
|
+
### 1. Topic Overview
|
|
8
|
+
- Detailed but concise summary of the conversation
|
|
9
|
+
- Main problem, context, and domain area
|
|
10
|
+
|
|
11
|
+
### 2. Current Situation
|
|
12
|
+
- What we're working on right now
|
|
13
|
+
- Current step or phase
|
|
14
|
+
- Recent changes or decisions
|
|
15
|
+
|
|
16
|
+
### 3. Key Artifacts
|
|
17
|
+
- Important files, functions, or systems
|
|
18
|
+
- Code snippets with context
|
|
19
|
+
- External tools or dependencies
|
|
20
|
+
|
|
21
|
+
### 4. Unresolved Issues
|
|
22
|
+
- Open questions or bugs
|
|
23
|
+
- Assumptions to validate
|
|
24
|
+
- Known risks or concerns
|
|
25
|
+
|
|
26
|
+
### 5. Next Steps
|
|
27
|
+
- Clear list of pending actions
|
|
28
|
+
- Priority order if applicable
|
|
29
|
+
- Dependencies between tasks
|
|
30
|
+
|
|
31
|
+
### 6. Assumptions & Constraints
|
|
32
|
+
- Technical constraints
|
|
33
|
+
- Time or resource limits
|
|
34
|
+
- Design assumptions made
|
|
35
|
+
|
|
36
|
+
### 7. Decisions Made
|
|
37
|
+
- What has been decided
|
|
38
|
+
- What has been ruled out
|
|
39
|
+
- Brief justification
|
|
40
|
+
|
|
41
|
+
### 8. External References
|
|
42
|
+
- Links, documentation
|
|
43
|
+
- File paths
|
|
44
|
+
- Tool configurations
|
|
45
|
+
|
|
46
|
+
## Usage Tips
|
|
47
|
+
|
|
48
|
+
- Make summaries copy-pasteable
|
|
49
|
+
- Include enough context for cold start
|
|
50
|
+
- Focus on actionable information
|
|
51
|
+
- Preserve important reasoning
|
|
52
|
+
- Note any time-sensitive items
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to $project_name are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - $date
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- The project's structure, from git-repokit.
|
|
13
|
+
|
|
14
|
+
[Unreleased]: https://github.com/$github_org/$project_name/compare/v0.1.0...HEAD
|
|
15
|
+
[0.1.0]: https://github.com/$github_org/$project_name/releases/tag/v0.1.0
|