lgit-cli 3.7.0__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.
Files changed (54) hide show
  1. lgit/__init__.py +75 -0
  2. lgit/__main__.py +8 -0
  3. lgit/analysis.py +326 -0
  4. lgit/api.py +1077 -0
  5. lgit/cache.py +338 -0
  6. lgit/changelog.py +523 -0
  7. lgit/cli.py +1104 -0
  8. lgit/compose.py +2110 -0
  9. lgit/config.py +437 -0
  10. lgit/diffing.py +384 -0
  11. lgit/errors.py +137 -0
  12. lgit/git.py +852 -0
  13. lgit/map_reduce.py +508 -0
  14. lgit/markdown_output.py +709 -0
  15. lgit/models.py +924 -0
  16. lgit/normalization.py +411 -0
  17. lgit/patch.py +784 -0
  18. lgit/profile.py +426 -0
  19. lgit/py.typed +0 -0
  20. lgit/repo.py +287 -0
  21. lgit/resources/__init__.py +1 -0
  22. lgit/resources/commit_types.json +242 -0
  23. lgit/resources/prompts/analysis/default.md +237 -0
  24. lgit/resources/prompts/analysis/markdown.md +112 -0
  25. lgit/resources/prompts/changelog/default.md +89 -0
  26. lgit/resources/prompts/changelog/markdown.md +60 -0
  27. lgit/resources/prompts/compose-bind/default.md +40 -0
  28. lgit/resources/prompts/compose-bind/markdown.md +41 -0
  29. lgit/resources/prompts/compose-intent/default.md +63 -0
  30. lgit/resources/prompts/compose-intent/markdown.md +59 -0
  31. lgit/resources/prompts/fast/default.md +46 -0
  32. lgit/resources/prompts/fast/markdown.md +51 -0
  33. lgit/resources/prompts/map/default.md +67 -0
  34. lgit/resources/prompts/map/markdown.md +63 -0
  35. lgit/resources/prompts/reduce/default.md +81 -0
  36. lgit/resources/prompts/reduce/markdown.md +68 -0
  37. lgit/resources/prompts/summary/default.md +74 -0
  38. lgit/resources/prompts/summary/markdown.md +77 -0
  39. lgit/resources/validation_data.json +1 -0
  40. lgit/rewrite.py +392 -0
  41. lgit/style.py +295 -0
  42. lgit/templates.py +385 -0
  43. lgit/testing/__init__.py +62 -0
  44. lgit/testing/compare.py +57 -0
  45. lgit/testing/fixture.py +386 -0
  46. lgit/testing/report.py +201 -0
  47. lgit/testing/runner.py +256 -0
  48. lgit/tokens.py +90 -0
  49. lgit/validation.py +545 -0
  50. lgit_cli-3.7.0.dist-info/METADATA +288 -0
  51. lgit_cli-3.7.0.dist-info/RECORD +54 -0
  52. lgit_cli-3.7.0.dist-info/WHEEL +4 -0
  53. lgit_cli-3.7.0.dist-info/entry_points.txt +2 -0
  54. lgit_cli-3.7.0.dist-info/licenses/LICENSE +21 -0
lgit/repo.py ADDED
@@ -0,0 +1,287 @@
1
+ """Repository metadata detection for prompt context."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import re
7
+ import tomllib
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+ from typing import Any, Self
11
+
12
+
13
+ @dataclass(slots=True)
14
+ class RepoMetadata:
15
+ """Detected repository language, framework, and workspace metadata."""
16
+
17
+ language: str | None = None
18
+ framework: str | None = None
19
+ package_manager: str | None = None
20
+ is_monorepo: bool = False
21
+ package_count: int | None = None
22
+
23
+ @classmethod
24
+ def detect(cls, dir: str | Path) -> Self:
25
+ """Detect repository metadata from ``dir``."""
26
+
27
+ root = Path(dir)
28
+ for detector in (_detect_rust, _detect_node, _detect_python, _detect_go):
29
+ meta = detector(root)
30
+ if meta is not None:
31
+ return meta
32
+ return cls()
33
+
34
+ def format_for_prompt(self) -> str | None:
35
+ """Format detected metadata for LLM prompt injection."""
36
+
37
+ if not self.language:
38
+ return None
39
+ lines: list[str] = []
40
+ language = self.language
41
+ if self.is_monorepo:
42
+ if self.package_count is not None:
43
+ language = f"{language} (workspace, {self.package_count} packages)"
44
+ else:
45
+ language = f"{language} (workspace)"
46
+ lines.append(f"Language: {language}")
47
+ if self.framework:
48
+ lines.append(f"Framework: {self.framework}")
49
+ if self.package_manager:
50
+ lines.append(f"Package manager: {self.package_manager}")
51
+ return "\n".join(lines)
52
+
53
+
54
+ def detect(dir: str | Path = ".") -> RepoMetadata:
55
+ """Detect repository metadata from ``dir``."""
56
+
57
+ return RepoMetadata.detect(dir)
58
+
59
+
60
+ def _detect_rust(root: Path) -> RepoMetadata | None:
61
+ manifest = root / "Cargo.toml"
62
+ if not manifest.exists():
63
+ return None
64
+ content = _read_text(manifest)
65
+ meta = RepoMetadata(language="Rust", package_manager="cargo")
66
+ data = _read_toml(manifest)
67
+ workspace = data.get("workspace") if isinstance(data, dict) else None
68
+ if isinstance(workspace, dict):
69
+ meta.is_monorepo = True
70
+ members = workspace.get("members")
71
+ if isinstance(members, list):
72
+ meta.package_count = len(members)
73
+ elif "[workspace]" in content:
74
+ meta.is_monorepo = True
75
+ meta.package_count = _count_workspace_members(content)
76
+ meta.framework = _detect_framework(content, _RUST_FRAMEWORKS)
77
+ return meta
78
+
79
+
80
+ def _detect_node(root: Path) -> RepoMetadata | None:
81
+ package_json = root / "package.json"
82
+ if not package_json.exists():
83
+ return None
84
+ content = _read_text(package_json)
85
+ data = _read_json(package_json)
86
+ deps = _node_dependency_names(data, content)
87
+ is_typescript = "typescript" in deps or (root / "tsconfig.json").exists()
88
+ meta = RepoMetadata(language="TypeScript" if is_typescript else "JavaScript")
89
+ if (root / "pnpm-lock.yaml").exists():
90
+ meta.package_manager = "pnpm"
91
+ elif (root / "yarn.lock").exists():
92
+ meta.package_manager = "yarn"
93
+ elif (root / "bun.lockb").exists() or (root / "bun.lock").exists():
94
+ meta.package_manager = "bun"
95
+ else:
96
+ meta.package_manager = "npm"
97
+ workspaces = data.get("workspaces") if isinstance(data, dict) else None
98
+ if workspaces or (root / "pnpm-workspace.yaml").exists():
99
+ meta.is_monorepo = True
100
+ meta.package_count = _count_node_workspaces(workspaces)
101
+ meta.framework = _detect_dependency_framework(deps, _NODE_FRAMEWORKS)
102
+ return meta
103
+
104
+
105
+ def _detect_python(root: Path) -> RepoMetadata | None:
106
+ pyproject = root / "pyproject.toml"
107
+ setup_py = root / "setup.py"
108
+ requirements = root / "requirements.txt"
109
+ if not pyproject.exists() and not setup_py.exists() and not requirements.exists():
110
+ return None
111
+ meta = RepoMetadata(language="Python")
112
+ chunks: list[str] = []
113
+ data: dict[str, Any] = {}
114
+ if pyproject.exists():
115
+ chunks.append(_read_text(pyproject))
116
+ data = _read_toml(pyproject)
117
+ if requirements.exists():
118
+ chunks.append(_read_text(requirements))
119
+ if setup_py.exists():
120
+ chunks.append(_read_text(setup_py))
121
+ text = "\n".join(chunks).lower()
122
+ tool = data.get("tool") if isinstance(data, dict) else None
123
+ tool = tool if isinstance(tool, dict) else {}
124
+ if "poetry" in tool:
125
+ meta.package_manager = "poetry"
126
+ elif "uv" in tool or (root / "uv.lock").exists():
127
+ meta.package_manager = "uv"
128
+ elif "pdm" in tool or (root / "pdm.lock").exists():
129
+ meta.package_manager = "pdm"
130
+ elif (root / "Pipfile").exists():
131
+ meta.package_manager = "pipenv"
132
+ else:
133
+ meta.package_manager = "pip"
134
+ meta.framework = _detect_framework(text, _PYTHON_FRAMEWORKS)
135
+ if (root / "pyproject.toml").exists() and _has_python_workspace(tool):
136
+ meta.is_monorepo = True
137
+ return meta
138
+
139
+
140
+ def _detect_go(root: Path) -> RepoMetadata | None:
141
+ go_mod = root / "go.mod"
142
+ if not go_mod.exists():
143
+ return None
144
+ content = _read_text(go_mod).lower()
145
+ return RepoMetadata(
146
+ language="Go",
147
+ framework=_detect_framework(content, _GO_FRAMEWORKS),
148
+ package_manager="go mod",
149
+ )
150
+
151
+
152
+ _RUST_FRAMEWORKS = (
153
+ ("axum", "Axum"),
154
+ ("actix-web", "Actix Web"),
155
+ ("rocket", "Rocket"),
156
+ ("warp", "Warp"),
157
+ ("tide", "Tide"),
158
+ ("poem", "Poem"),
159
+ ("tower-http", "Tower HTTP"),
160
+ ("hyper", "Hyper"),
161
+ ("tokio", "Tokio async runtime"),
162
+ ("bevy", "Bevy game engine"),
163
+ ("iced", "Iced GUI"),
164
+ ("egui", "egui GUI"),
165
+ ("tauri", "Tauri"),
166
+ ("leptos", "Leptos"),
167
+ ("yew", "Yew"),
168
+ ("dioxus", "Dioxus"),
169
+ )
170
+
171
+ _NODE_FRAMEWORKS = (
172
+ ("next", "Next.js"),
173
+ ("nuxt", "Nuxt"),
174
+ ("@angular/core", "Angular"),
175
+ ("vue", "Vue"),
176
+ ("react", "React"),
177
+ ("svelte", "Svelte"),
178
+ ("solid-js", "SolidJS"),
179
+ ("express", "Express"),
180
+ ("fastify", "Fastify"),
181
+ ("hono", "Hono"),
182
+ ("nestjs", "NestJS"),
183
+ ("@nestjs/core", "NestJS"),
184
+ ("electron", "Electron"),
185
+ ("expo", "Expo"),
186
+ ("react-native", "React Native"),
187
+ )
188
+
189
+ _PYTHON_FRAMEWORKS = (
190
+ ("fastapi", "FastAPI"),
191
+ ("django", "Django"),
192
+ ("flask", "Flask"),
193
+ ("starlette", "Starlette"),
194
+ ("litestar", "Litestar"),
195
+ ("sanic", "Sanic"),
196
+ ("tornado", "Tornado"),
197
+ ("aiohttp", "aiohttp"),
198
+ ("pytorch", "PyTorch"),
199
+ ("torch", "PyTorch"),
200
+ ("tensorflow", "TensorFlow"),
201
+ ("jax", "JAX"),
202
+ ("transformers", "Hugging Face"),
203
+ )
204
+
205
+ _GO_FRAMEWORKS = (
206
+ ("github.com/gin-gonic/gin", "Gin"),
207
+ ("github.com/labstack/echo", "Echo"),
208
+ ("github.com/gofiber/fiber", "Fiber"),
209
+ ("github.com/go-chi/chi", "chi"),
210
+ ("github.com/gorilla/mux", "Gorilla Mux"),
211
+ ("connectrpc.com/connect", "Connect"),
212
+ ("google.golang.org/grpc", "gRPC"),
213
+ )
214
+
215
+
216
+ def _read_text(path: Path) -> str:
217
+ try:
218
+ return path.read_text(encoding="utf-8")
219
+ except OSError:
220
+ return ""
221
+
222
+
223
+ def _read_toml(path: Path) -> dict[str, Any]:
224
+ try:
225
+ return tomllib.loads(_read_text(path))
226
+ except Exception:
227
+ return {}
228
+
229
+
230
+ def _read_json(path: Path) -> dict[str, Any]:
231
+ try:
232
+ value = json.loads(_read_text(path))
233
+ except Exception:
234
+ return {}
235
+ return value if isinstance(value, dict) else {}
236
+
237
+
238
+ def _detect_framework(content: str, frameworks: tuple[tuple[str, str], ...]) -> str | None:
239
+ lowered = content.lower()
240
+ for needle, name in frameworks:
241
+ if needle.lower() in lowered:
242
+ return name
243
+ return None
244
+
245
+
246
+ def _detect_dependency_framework(deps: set[str], frameworks: tuple[tuple[str, str], ...]) -> str | None:
247
+ for package, name in frameworks:
248
+ if package in deps:
249
+ return name
250
+ return None
251
+
252
+
253
+ def _node_dependency_names(data: dict[str, Any], fallback: str) -> set[str]:
254
+ deps: set[str] = set()
255
+ for field in ("dependencies", "devDependencies", "peerDependencies", "optionalDependencies"):
256
+ values = data.get(field)
257
+ if isinstance(values, dict):
258
+ deps.update(str(key) for key in values)
259
+ if not deps:
260
+ deps.update(re.findall(r'"([@\w./-]+)"\s*:', fallback))
261
+ return deps
262
+
263
+
264
+ def _count_node_workspaces(workspaces: Any) -> int | None:
265
+ if isinstance(workspaces, list):
266
+ return len(workspaces)
267
+ if isinstance(workspaces, dict):
268
+ packages = workspaces.get("packages")
269
+ if isinstance(packages, list):
270
+ return len(packages)
271
+ return None
272
+
273
+
274
+ def _count_workspace_members(content: str) -> int | None:
275
+ match = re.search(r"members\s*=\s*\[([^\]]*)\]", content, re.S)
276
+ if not match:
277
+ return None
278
+ return len(re.findall(r'"[^"]+"', match.group(1)))
279
+
280
+
281
+ def _has_python_workspace(tool: dict[str, Any]) -> bool:
282
+ uv = tool.get("uv")
283
+ pdm = tool.get("pdm")
284
+ return (isinstance(uv, dict) and "workspace" in uv) or (isinstance(pdm, dict) and "workspace" in pdm)
285
+
286
+
287
+ __all__ = ["RepoMetadata", "detect"]
@@ -0,0 +1 @@
1
+ """Bundled JSON and prompt resources for llm-git."""
@@ -0,0 +1,242 @@
1
+ {
2
+ "classifier_hint": "CRITICAL disambiguation rules:\n- feat vs refactor: feat=ANY observable behavior change OR new public API; refactor=provably unchanged (same tests, same API). When in doubt, prefer feat.\n- fix vs hotfix: hotfix=critical production emergency; fix=normal bug.\n- fix vs security: security=proactive hardening, CVE patches, auth hardening; fix=non-security bugs.\n- deps vs chore: deps=dependency version bumps only; chore=other maintenance (tooling, scripts).\n- deps vs build: build=build system scripts/config; deps=bumping library versions in manifests.\n- config vs chore: config=application/runtime config; chore=dev tooling and housekeeping.\n- ux vs feat: ux=existing feature made easier/clearer; feat=new capability.\n- init=bootstrap commit for a project or major subsystem; use once.\n- wip=in-progress save-point; prefer a real type for finished commits.\n- hack=deliberate temporary workaround; body must note intent to revisit.\n- merge=merge/sync commits with no standalone logic change.",
3
+ "types": [
4
+ {
5
+ "name": "feat",
6
+ "description": "New public API surface OR user-observable capability/behavior change",
7
+ "diff_indicators": ["pub fn", "pub struct", "pub enum", "export function", "#[arg]"],
8
+ "examples": [
9
+ "Added pub fn process_batch() → feat (new API)",
10
+ "Migrated HTTP client to async → feat (behavior change)"
11
+ ],
12
+ "hint": "ANY observable behavior change OR new public API. When unsure between feat and refactor, prefer feat.",
13
+ "aliases": ["feature", "features", "enhancement", "enhancements"]
14
+ },
15
+ {
16
+ "name": "fix",
17
+ "description": "Fixes incorrect behavior (bugs, crashes, wrong outputs, race conditions)",
18
+ "diff_indicators": ["unwrap() → ?", "bounds check", "off-by-one", "error handling"],
19
+ "examples": [
20
+ "Corrected off-by-one in pagination offset → fix (wrong output)",
21
+ "Guarded against panic on empty input → fix (crash)"
22
+ ],
23
+ "hint": "Non-security bug fixes. Security-motivated fix → security; production emergency → hotfix.",
24
+ "aliases": ["bug", "bugfix", "bugfixes"]
25
+ },
26
+ {
27
+ "name": "refactor",
28
+ "description": "Internal restructuring with provably unchanged behavior",
29
+ "diff_indicators": ["rename", "extract", "consolidate", "reorganize"],
30
+ "examples": [
31
+ "Renamed internal module structure → refactor (no API change)",
32
+ "Extracted shared helper from three call sites → refactor"
33
+ ],
34
+ "hint": "Requires proof: same tests pass, same API. If behavior changes, use feat.",
35
+ "aliases": ["refactoring", "refactors", "rework", "rewrite", "cleanup"]
36
+ },
37
+ {
38
+ "name": "docs",
39
+ "description": "Documentation only changes",
40
+ "diff_indicators": ["///", "//!", "README", "/** */"],
41
+ "file_patterns": ["*.md", "doc comments"],
42
+ "examples": [
43
+ "Expanded README usage section → docs",
44
+ "Added rustdoc examples to public API → docs"
45
+ ],
46
+ "hint": "Excludes prompt template files (prompts/*.md). Prompt changes are functional — use feat/fix/refactor.",
47
+ "aliases": ["doc", "documentation"]
48
+ },
49
+ {
50
+ "name": "test",
51
+ "description": "Adding or modifying tests",
52
+ "diff_indicators": ["#[test]", "assert_eq!", "describe(", "it(", "expect("],
53
+ "file_patterns": ["*_test.rs", "tests/", "*.test.ts", "*.spec.ts"],
54
+ "examples": [
55
+ "Added unit tests for parser edge cases → test",
56
+ "Migrated suite to table-driven cases → test"
57
+ ],
58
+ "hint": "Test-only changes. Production code touched alongside tests uses that code's type.",
59
+ "aliases": ["tests", "testing"]
60
+ },
61
+ {
62
+ "name": "chore",
63
+ "description": "Housekeeping: tooling scripts, editor config, miscellaneous maintenance not covered by other types",
64
+ "file_patterns": [".gitignore", "*.lock", ".editorconfig"],
65
+ "examples": [
66
+ "Updated .gitignore and editor config → chore",
67
+ "Refreshed lockfile after unrelated change → chore"
68
+ ],
69
+ "hint": "Use deps for version bumps, config for app/env config, build for build scripts.",
70
+ "aliases": ["housekeeping", "maintenance", "misc"]
71
+ },
72
+ {
73
+ "name": "style",
74
+ "description": "Formatting, whitespace changes (no logic change)",
75
+ "diff_indicators": ["whitespace", "formatting", "indentation"],
76
+ "examples": [
77
+ "Reformatted with rustfmt, no logic change → style",
78
+ "Normalized import ordering → style"
79
+ ],
80
+ "hint": "Variable/function renames are refactor, not style.",
81
+ "aliases": ["format", "formatting", "reformat", "whitespace", "lint", "fmt"]
82
+ },
83
+ {
84
+ "name": "perf",
85
+ "description": "Performance improvements (proven faster)",
86
+ "diff_indicators": ["optimization", "cache", "batch", "preallocate"],
87
+ "examples": [
88
+ "Cached repeated lookups to cut allocations → perf",
89
+ "Replaced O(n^2) scan with hashmap → perf"
90
+ ],
91
+ "hint": "Requires evidence of a measurable improvement; speculative tidying is refactor.",
92
+ "aliases": ["performance", "optimize", "optimise", "optimization"]
93
+ },
94
+ {
95
+ "name": "build",
96
+ "description": "Build system and toolchain configuration (build scripts, compiler/bundler settings)",
97
+ "file_patterns": ["Cargo.toml", "package.json", "Makefile", "build.rs"],
98
+ "examples": [
99
+ "Added release profile to Cargo.toml → build",
100
+ "Wired a new build script step → build"
101
+ ],
102
+ "hint": "Build scripts and tooling config. Bumping library versions in manifests → deps.",
103
+ "aliases": ["buildsystem", "builder"]
104
+ },
105
+ {
106
+ "name": "ci",
107
+ "description": "CI/CD configuration",
108
+ "file_patterns": [".github/workflows/", ".gitlab-ci.yml", ".circleci/"],
109
+ "examples": [
110
+ "Added a test matrix to GitHub Actions → ci",
111
+ "Cached cargo registry between CI runs → ci"
112
+ ],
113
+ "hint": "Pipeline definitions and CI scripts. Runtime app config → config; IaC → infra.",
114
+ "aliases": ["pipeline", "workflow", "actions", "cicd"]
115
+ },
116
+ {
117
+ "name": "revert",
118
+ "description": "Reverts a previous commit",
119
+ "diff_indicators": ["Revert", "This reverts commit"],
120
+ "examples": ["Reverted commit abc123 introducing the regression → revert"],
121
+ "hint": "Undoing a prior commit. A fresh fix that supersedes it is fix, not revert.",
122
+ "aliases": ["reverts", "reversion"]
123
+ },
124
+ {
125
+ "name": "deps",
126
+ "description": "Dependency version bumps (Cargo.toml, package.json, go.mod, requirements.txt, etc.)",
127
+ "diff_indicators": ["version = \"", "\"^", "~>", "go.sum"],
128
+ "file_patterns": ["Cargo.toml", "package.json", "go.mod", "requirements.txt", "pyproject.toml"],
129
+ "examples": [
130
+ "Bumped serde 1.0.197 → 1.0.210 → deps",
131
+ "Upgraded transitive deps via lockfile → deps"
132
+ ],
133
+ "hint": "Version bumps only. Build system changes belong in build; lockfile-only changes can be deps.",
134
+ "aliases": ["dependency", "dependencies", "dep", "bump"]
135
+ },
136
+ {
137
+ "name": "security",
138
+ "description": "Security hardening, CVE patches, auth improvements, input sanitization, rate limiting",
139
+ "diff_indicators": ["sanitize", "auth", "CVE", "rate limit", "HMAC", "escape"],
140
+ "examples": [
141
+ "Sanitized user input before the SQL query → security",
142
+ "Added rate limiting to the login endpoint → security"
143
+ ],
144
+ "hint": "Use for proactive hardening too, not just bug fixes. Security-motivated fix → security, not fix.",
145
+ "aliases": ["sec", "vuln", "vulnerability"]
146
+ },
147
+ {
148
+ "name": "config",
149
+ "description": "Application or environment configuration changes (.env, settings, feature flags, runtime config)",
150
+ "file_patterns": [".env", "settings.toml", "config.yaml"],
151
+ "examples": [
152
+ "Added a feature flag for the new pipeline → config",
153
+ "Raised the default request timeout → config"
154
+ ],
155
+ "hint": "App/runtime config. Build system config → build; CI config → ci; dev tooling → chore.",
156
+ "aliases": ["configuration", "settings", "cfg"]
157
+ },
158
+ {
159
+ "name": "ux",
160
+ "description": "Usability and ergonomics improvements to existing interfaces (CLI flags, error messages, output formatting)",
161
+ "diff_indicators": ["help text", "error message", "prompt", "--flag"],
162
+ "examples": [
163
+ "Clarified an ambiguous error message → ux",
164
+ "Added a short alias for an existing flag → ux"
165
+ ],
166
+ "hint": "Existing feature made easier/clearer → ux. New capability → feat.",
167
+ "aliases": ["ui", "usability", "ergonomics"]
168
+ },
169
+ {
170
+ "name": "release",
171
+ "description": "Version bump and release preparation (CHANGELOG.md updates, version files, release tags)",
172
+ "file_patterns": ["CHANGELOG.md", "CHANGELOG", "VERSION"],
173
+ "examples": [
174
+ "Bumped version to 2.1.0 and updated CHANGELOG → release",
175
+ "Cut the v3 release notes → release"
176
+ ],
177
+ "hint": "Only for the release commit itself. Code changes alongside a release use their own type.",
178
+ "aliases": ["releases", "versioning"]
179
+ },
180
+ {
181
+ "name": "hotfix",
182
+ "description": "Critical production fix requiring immediate patch, often on a dedicated hotfix branch",
183
+ "examples": [
184
+ "Patched a production auth bypass on the hotfix branch → hotfix",
185
+ "Emergency rollback of a broken migration → hotfix"
186
+ ],
187
+ "hint": "Reserve for genuine production emergencies. Normal bugs → fix.",
188
+ "aliases": ["hotfixes"]
189
+ },
190
+ {
191
+ "name": "infra",
192
+ "description": "Infrastructure-as-code changes (Terraform, Kubernetes manifests, Ansible, cloud config)",
193
+ "file_patterns": ["*.tf", "helm/", "terraform/", "k8s/", "ansible/"],
194
+ "examples": [
195
+ "Added a Terraform module for RDS → infra",
196
+ "Tuned Kubernetes resource limits → infra"
197
+ ],
198
+ "hint": "IaC and cloud provisioning. App config → config; CI pipelines → ci.",
199
+ "aliases": ["infrastructure", "iac"]
200
+ },
201
+ {
202
+ "name": "init",
203
+ "description": "Initial commit bootstrapping a project, module, or major subsystem",
204
+ "examples": [
205
+ "Scaffolded a new crate with cargo init → init",
206
+ "Bootstrapped the auth subsystem skeleton → init"
207
+ ],
208
+ "hint": "Use once per project/module bootstrap. Subsequent setup → chore or build.",
209
+ "aliases": ["initial", "bootstrap", "scaffold", "initialization"]
210
+ },
211
+ {
212
+ "name": "merge",
213
+ "description": "Merge or sync commit with no standalone logic change (merge branches, sync forks)",
214
+ "examples": [
215
+ "Merged the release branch back into main → merge",
216
+ "Synced fork with upstream → merge"
217
+ ],
218
+ "hint": "Only when the commit is purely a merge. Squashed logic changes → use the appropriate type.",
219
+ "aliases": ["merges", "sync", "merging"]
220
+ },
221
+ {
222
+ "name": "hack",
223
+ "description": "Deliberate temporary workaround or shortcut with known technical debt",
224
+ "examples": [
225
+ "Hardcoded a timeout pending the upstream fix → hack",
226
+ "Stubbed the flaky call behind a guard → hack"
227
+ ],
228
+ "hint": "Must signal intent to revisit in the body (e.g., TODO: replace once X lands).",
229
+ "aliases": ["hacks", "workaround", "hacky"]
230
+ },
231
+ {
232
+ "name": "wip",
233
+ "description": "Incomplete in-progress work not ready for review or release",
234
+ "examples": [
235
+ "Saved a partial refactor mid-progress → wip",
236
+ "Checkpointed exploratory spike → wip"
237
+ ],
238
+ "hint": "Prefer a real type for finished commits. Use wip only for explicit save-points.",
239
+ "aliases": ["inprogress", "work-in-progress"]
240
+ }
241
+ ]
242
+ }