jearless 0.10.4__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 (45) hide show
  1. jearless-0.10.4/.gitignore +220 -0
  2. jearless-0.10.4/.pre-commit-config.yaml +5 -0
  3. jearless-0.10.4/.version-hook.yml +49 -0
  4. jearless-0.10.4/CHANGELOG.md +17 -0
  5. jearless-0.10.4/LICENSE +21 -0
  6. jearless-0.10.4/PKG-INFO +141 -0
  7. jearless-0.10.4/PLAN.md +44 -0
  8. jearless-0.10.4/README.md +112 -0
  9. jearless-0.10.4/env.yml +4 -0
  10. jearless-0.10.4/pyproject.toml +55 -0
  11. jearless-0.10.4/snapshots/timeline-v0.10.4.mmd +14 -0
  12. jearless-0.10.4/snapshots/timeline-v0.9.4.mmd +13 -0
  13. jearless-0.10.4/src/jearless/__init__.py +12 -0
  14. jearless-0.10.4/src/jearless/add.py +122 -0
  15. jearless-0.10.4/src/jearless/changelog.py +81 -0
  16. jearless-0.10.4/src/jearless/cli.py +107 -0
  17. jearless-0.10.4/src/jearless/config.py +69 -0
  18. jearless-0.10.4/src/jearless/doctor.py +83 -0
  19. jearless-0.10.4/src/jearless/duplicate.py +86 -0
  20. jearless-0.10.4/src/jearless/errors.py +5 -0
  21. jearless-0.10.4/src/jearless/gitwrap.py +69 -0
  22. jearless-0.10.4/src/jearless/registry.py +105 -0
  23. jearless-0.10.4/src/jearless/release.py +83 -0
  24. jearless-0.10.4/src/jearless/retro.py +69 -0
  25. jearless-0.10.4/src/jearless/slug.py +28 -0
  26. jearless-0.10.4/src/jearless/sprint.py +33 -0
  27. jearless-0.10.4/src/jearless/state.py +86 -0
  28. jearless-0.10.4/src/jearless/take.py +63 -0
  29. jearless-0.10.4/src/jearless/timeline.py +121 -0
  30. jearless-0.10.4/tasks.yml +79 -0
  31. jearless-0.10.4/tests/conftest.py +39 -0
  32. jearless-0.10.4/tests/test_add.py +119 -0
  33. jearless-0.10.4/tests/test_changelog.py +83 -0
  34. jearless-0.10.4/tests/test_cli.py +40 -0
  35. jearless-0.10.4/tests/test_config.py +64 -0
  36. jearless-0.10.4/tests/test_doctor.py +76 -0
  37. jearless-0.10.4/tests/test_duplicate.py +47 -0
  38. jearless-0.10.4/tests/test_gitwrap.py +28 -0
  39. jearless-0.10.4/tests/test_registry.py +63 -0
  40. jearless-0.10.4/tests/test_release.py +91 -0
  41. jearless-0.10.4/tests/test_retro.py +80 -0
  42. jearless-0.10.4/tests/test_slug.py +19 -0
  43. jearless-0.10.4/tests/test_sprint.py +83 -0
  44. jearless-0.10.4/tests/test_take.py +64 -0
  45. jearless-0.10.4/tests/test_timeline.py +78 -0
@@ -0,0 +1,220 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ *.lcov
51
+ .hypothesis/
52
+ .pytest_cache/
53
+ cover/
54
+
55
+ # Translations
56
+ *.mo
57
+ *.pot
58
+
59
+ # Django stuff:
60
+ *.log
61
+ local_settings.py
62
+ db.sqlite3
63
+ db.sqlite3-journal
64
+
65
+ # Flask stuff:
66
+ instance/
67
+ .webassets-cache
68
+
69
+ # Scrapy stuff:
70
+ .scrapy
71
+
72
+ # Sphinx documentation
73
+ docs/_build/
74
+
75
+ # PyBuilder
76
+ .pybuilder/
77
+ target/
78
+
79
+ # Jupyter Notebook
80
+ .ipynb_checkpoints
81
+
82
+ # IPython
83
+ profile_default/
84
+ ipython_config.py
85
+
86
+ # pyenv
87
+ # For a library or package, you might want to ignore these files since the code is
88
+ # intended to run in multiple environments; otherwise, check them in:
89
+ # .python-version
90
+
91
+ # pipenv
92
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
94
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
95
+ # install all needed dependencies.
96
+ # Pipfile.lock
97
+
98
+ # uv
99
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
100
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
101
+ # commonly ignored for libraries.
102
+ # uv.lock
103
+
104
+ # poetry
105
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
106
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
107
+ # commonly ignored for libraries.
108
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
109
+ # poetry.lock
110
+ # poetry.toml
111
+
112
+ # pdm
113
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
114
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
115
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
116
+ # pdm.lock
117
+ # pdm.toml
118
+ .pdm-python
119
+ .pdm-build/
120
+
121
+ # pixi
122
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
123
+ # pixi.lock
124
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
125
+ # in the .venv directory. It is recommended not to include this directory in version control.
126
+ .pixi/*
127
+ !.pixi/config.toml
128
+
129
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
130
+ __pypackages__/
131
+
132
+ # Celery stuff
133
+ celerybeat-schedule*
134
+ celerybeat.pid
135
+
136
+ # Redis
137
+ *.rdb
138
+ *.aof
139
+ *.pid
140
+
141
+ # RabbitMQ
142
+ mnesia/
143
+ rabbitmq/
144
+ rabbitmq-data/
145
+
146
+ # ActiveMQ
147
+ activemq-data/
148
+
149
+ # SageMath parsed files
150
+ *.sage.py
151
+
152
+ # Environments
153
+ .env
154
+ .envrc
155
+ .venv
156
+ env/
157
+ venv/
158
+ ENV/
159
+ env.bak/
160
+ venv.bak/
161
+
162
+ # Spyder project settings
163
+ .spyderproject
164
+ .spyproject
165
+
166
+ # Rope project settings
167
+ .ropeproject
168
+
169
+ # mkdocs/Zensical documentation
170
+ /site
171
+
172
+ # mypy
173
+ .mypy_cache/
174
+ .dmypy.json
175
+ dmypy.json
176
+
177
+ # Pyre type checker
178
+ .pyre/
179
+
180
+ # pytype static type analyzer
181
+ .pytype/
182
+
183
+ # Cython debug symbols
184
+ cython_debug/
185
+
186
+ # PyCharm
187
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
188
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
189
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
190
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
191
+ # .idea/
192
+
193
+ # Abstra
194
+ # Abstra is an AI-powered process automation framework.
195
+ # Ignore directories containing user credentials, local state, and settings.
196
+ # Learn more at https://abstra.io/docs
197
+ .abstra/
198
+
199
+ # Visual Studio Code
200
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore that
201
+ # can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
202
+ # and can be added to the global gitignore or merged into this file. However, if you prefer, you
203
+ # could uncomment the following to ignore the entire vscode folder
204
+ # .vscode/
205
+ # Temporary file for partial code execution
206
+ tempCodeRunnerFile.py
207
+
208
+ # Ruff stuff:
209
+ .ruff_cache/
210
+
211
+ # PyPI configuration file
212
+ .pypirc
213
+
214
+ # Marimo
215
+ marimo/_static/
216
+ marimo/_lsp/
217
+ __marimo__/
218
+
219
+ # Streamlit
220
+ .streamlit/secrets.toml
@@ -0,0 +1,5 @@
1
+ repos:
2
+ - repo: https://github.com/AndrewZakh/auto-version-hook
3
+ rev: v1.1.0
4
+ hooks:
5
+ - id: auto-version
@@ -0,0 +1,49 @@
1
+ ---
2
+ # jearless × auto-version-hook: the version lives in env.yml and rides the
3
+ # feature branch. The README invariant ("main moves only by merges") holds:
4
+ # main is protected, merge commits are skipped, the bump happens in the branch.
5
+
6
+ version:
7
+ file: env.yml
8
+ key: version
9
+ parse_regex: '^v(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)$'
10
+
11
+ # The package version mirrors the flow version (auto-version-hook v1.1.0):
12
+ # after every bump both move together; no v-prefix here (PEP 440 style).
13
+ version_files:
14
+ - path: pyproject.toml
15
+ regex: '^version = "(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)"$'
16
+
17
+ git:
18
+ remote: origin
19
+ fetch: true
20
+
21
+ branch_flow:
22
+ - name: main
23
+ regex: '^main$'
24
+ protected: true # a direct commit to main never bumps the version
25
+ policy: keep
26
+
27
+ - name: feature
28
+ regex: '^feature/' # feature/<project>/<id>-<slug> — the whole README taxonomy
29
+ target: main
30
+ policy: increment
31
+ bump: minor
32
+ reset_lower_parts: false
33
+
34
+ - name: fix
35
+ regex: '^fix/'
36
+ target: main
37
+ policy: increment
38
+ bump: patch
39
+ reset_lower_parts: false
40
+
41
+ - name: chore
42
+ regex: '^chore/'
43
+ target: main
44
+ policy: ignore
45
+
46
+ hooks:
47
+ auto_stage: true
48
+ on_error: fail
49
+ skip_merges: true # does not act on merge commits — the charter moves those
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+ ## v0.10.4 (2026-11-07)
3
+ ### Added
4
+ - tooling-10: add — duplicate guard: keyword-overlap warning and confirmation
5
+ # 1 off-taxonomy merge(s) skipped — the visible hole (README)
6
+
7
+ ## v0.9.4 (2026-11-07)
8
+ ### Added
9
+ - tooling-1: add — append a task to the registry
10
+ - tooling-2: take — claim a task with a single push
11
+ - tooling-3: sprint — a join of three sources
12
+ - tooling-4: changelog — a generator over merge commits
13
+ - tooling-5: doctor — the visible hole
14
+ - tooling-6: timeline — live and snapshot
15
+ - tooling-7: retro — plan versus actual
16
+ - tooling-9: release step — tag, changelog and timeline snapshot in one move
17
+ # 8 off-taxonomy merge(s) skipped — the visible hole (README)
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AndrewZakh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.5
2
+ Name: jearless
3
+ Version: 0.10.4
4
+ Summary: Fearless. Jearless. Task tracking and a changelog on bare git — for small teams.
5
+ Project-URL: Homepage, https://github.com/AndrewZakh/jearless
6
+ Project-URL: Repository, https://github.com/AndrewZakh/jearless
7
+ Project-URL: Changelog, https://github.com/AndrewZakh/jearless/blob/main/CHANGELOG.md
8
+ Project-URL: Issues, https://github.com/AndrewZakh/jearless/issues
9
+ Author: Andrei Zakhrebetkov
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: changelog,cli,git,issue-tracker,task-tracking
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Version Control :: Git
22
+ Requires-Python: >=3.11
23
+ Requires-Dist: ruamel-yaml>=0.18
24
+ Provides-Extra: dev
25
+ Requires-Dist: pre-commit>=3; extra == 'dev'
26
+ Requires-Dist: pytest>=8; extra == 'dev'
27
+ Requires-Dist: ruff>=0.6; extra == 'dev'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # jearless
31
+
32
+ **Fearless. Jearless.** Task tracking and a changelog on bare git — for small teams.
33
+
34
+ *Why the name: by ear — a faint echo of Jira; by sight — fearless; spelled with an error — and we track errors for a living; a jearless sprint — a fast run without fat.*
35
+
36
+ ## Install
37
+
38
+ From PyPI, straight into an isolated environment:
39
+
40
+ ```sh
41
+ pipx install jearless # the intended way for daily use
42
+ ```
43
+
44
+ For development, one command builds the venv with everything. There is
45
+ deliberately **no requirements.txt**: `pyproject.toml` is the single authored
46
+ source of dependencies, and a second list would drift.
47
+
48
+ ```sh
49
+ uv venv && uv pip install -e ".[dev]" # uv
50
+ python3 -m venv .venv && .venv/bin/pip install -e ".[dev]" # plain pip
51
+ ```
52
+
53
+ ## Quickstart
54
+
55
+ Inside your git repository:
56
+
57
+ ```sh
58
+ jear add door "paint it black" --points 2 # a task in the eternal registry
59
+ jear take door 1 # branch feature/door/1-paint-it-black, pushed
60
+ # ... work, then a true merge (--no-ff) ...
61
+ jear sprint # available / in_progress / done — derived from git
62
+ ```
63
+
64
+ No statuses are ever stored: the registry holds frozen facts, git holds
65
+ everything alive. See [The idea](#the-idea) for the full model.
66
+
67
+ **Status:** the design has stabilized; the mechanism is built around the eternal registry; the name is settled — jearless (2026-11-06). This file records the decisions and their rationale — it is not a specification. The implementation plan lives in [PLAN.md](PLAN.md).
68
+
69
+ ## The idea
70
+
71
+ Task tracking and a changelog on bare git. No trackers, no infrastructure.
72
+
73
+ 1. `tasks.yml` — the **eternal task registry**: subproject, id, summary, description, story_points. Tasks are added and never deleted; the file only grows. No live-state fields.
74
+ 2. `jear take door_design 16` — zero commits, zero YAML edits: verify the branch is absent on origin, create `feature/door_design/16-slug`, push. **Taking a task = pushing the branch.**
75
+ 3. Merging is a true merge (`--no-ff`): the branch name lands in history through the merge commit. No squash, no rebase merges.
76
+ 4. The changelog is not stored — it is generated at release time: merge commits between tags → the regex from the config → the line template. The line is the summary from the registry or the slug from the branch name.
77
+ 5. The branch prefix (`feature/`, `fix/`, `chore/`) → the changelog section.
78
+
79
+ ## The state model: three sources, zero stored statuses
80
+
81
+ | State | Condition |
82
+ |---|---|
83
+ | available | present in tasks.yml ∧ no branch on origin ∧ no merge commit |
84
+ | in_progress | a branch exists on origin ∧ no merge commit |
85
+ | done | a merge commit carrying the branch name exists in main (overrides an existing branch when the forge's auto-delete is off) |
86
+
87
+ The sprint and all analytics are joins on the key (subproject, number), computed on demand. The race for a task is arbitrated by git itself: a second push of a branch with the same name is rejected as non-fast-forward — "taken" arrives from the server.
88
+
89
+ ## Timeline and plan-versus-actual
90
+
91
+ - **The actual timeline** is a derived view: start = author date of the branch's first commit, end = committer date of the merge commit. The live variant is a command run on demand (fetch --prune, open branches end "now") and is **never committed**. The release snapshot is frozen by the tag and committed next to the changelog.
92
+ - **The Gantt (the plan)** is an authored artifact: drawn by hand or by script, committed as the team's contract. For the retrospective comparison, **bar ids = task ids** (subproject + number) — otherwise the plan cannot be joined with the facts.
93
+ - The hierarchy of truth about the start: the branch's first commit (exact, eternal) → merge-base (skewed by main's stagnation: the common commit's date ≠ the cut date) → forge events (the push time is visible, but the feed expires — on GitHub, within about 90 days). Push time does not exist in git's object model.
94
+ - Activity time metrics (hours, commit-clock deadlines) are deliberately out of scope: commit dates are client clocks, trivially spoofed, and they invite gaming.
95
+
96
+ ## Key decisions
97
+
98
+ - **The number is an identifier, not a position.** No renumbering, no reuse; the uniqueness key is the pair (subproject, number). Allocating the next number looks only at the registry — it never empties and never shrinks.
99
+ - **The eternal registry instead of deletion:** the old mechanics — "the task's deletion rides the branch's first commit" — is cancelled. Consequences: the task card (story_points included) lives forever, so analytics over done tasks lose nothing; `take` never mutates the YAML, so the yq round-trip problem disappears; registry conflicts become nearly impossible — only simultaneous additions at the same spot.
100
+ - **The branch name is the only key** linking the registry, the changelog and history. Slashes are the structural separators (`feature/door_design/16-slug`); hyphens live only inside slugs.
101
+ - **YAML as the registry format:** readable by yq, analytics over frozen facts. Live-state fields (status and friends) are forbidden: git knows the state, a stored copy drifts. Scripts only read the registry (`add` appending is the sole sanctioned exception).
102
+ - **The language of the registry, slugs and changelog is English** (the GitHub norm; decided 2026-11-06). Transliteration is only a fallback inside `take`, for summaries not written in latin script — a last resort for those who skipped their lessons, not the project's style.
103
+ - **main moves by merges only — no squash, no rebase merges, always `--no-ff`.** Direct human commits (add a task, fix the plan) are allowed; the ban concerns automatic writers and merge methods. Rebasing inside one's own unmerged branch is harmless — author dates survive it.
104
+ - **Nothing automatic writes to main.** Dropping a task = deleting the branch = the task is available again (it never left the registry).
105
+ - **"A separate life for the registry" is rejected** (a submodule, a database rendered into yml, a secrets manager): moving the storage does not solve the trigger problem — who flips `done` after the merge. The principle: **state for an event that cannot be observed without infrastructure is never stored — it is derived.**
106
+ - **Taking a task is a branch push: an occupancy registry, not a notification** — others see it at their own fetch.
107
+
108
+ ## Pros
109
+
110
+ - One database — git history plus the eternal registry of facts. Zero state files, zero hooks with write access, zero CI jobs pushing to main.
111
+ - Taking a task is one push, zero commits; undoing it is deleting the branch.
112
+ - The task card lives in the registry forever: velocity and any analytics over done tasks lose no data.
113
+ - The race for a task is resolved by the server, atomically, without locks or queues.
114
+ - Plan-versus-actual from two honest sources: the plan is authored and committed, the actual is derived from git, the comparison is the retrospective.
115
+ - Branch prefixes are the taxonomy: "Added / Fixed" sections come for free.
116
+ - Merge commits make a task an object of history: `--merges` between tags is the task journal; a merge commit's diff is the whole task at once.
117
+ - Version binding is free: whatever is merged between v1.2 and v1.3 lands under the v1.3 heading.
118
+ - The registry is versioned with the code, reviewed in merges, available offline.
119
+
120
+ ## Cons / risks
121
+
122
+ - **Squash and rebase merges silently lose the branch name** — the changelog line disappears without an error. On the forge these are the two enemy buttons; the repo needs "Create a merge commit" as the default. The legitimate way to clean up a task's history is **squash inside the branch before the true merge** ("squash inside, merge outside"): compress the WIP into one commit preserving the first commit's author date, then `--no-ff`. Verified by a live demo, 2026-11-06: the key lives in the merge commit, the timeline start in the preserved author date.
123
+ - **Fast-forward is the default** and the sneakiest source of lost merge commits: when main has not moved, git just moves the pointer. The "always --no-ff" rule is easy to forget.
124
+ - **The regex in the config rots first** (the very first draft failed to match its own example). A generator dry-run is mandatory.
125
+ - The regex must digest both merge message forms: "Merge branch ..." and "Merge pull request #N from owner/...".
126
+ - **YAML under hand editing:** block scalars for long descriptions are the most finicky spot; on-the-fly validation is mandatory.
127
+ - **Registry growth:** the file only increases. A measurement (2026-11-06, synthetic, ~270 bytes per task): a filtered query over 1M tasks (270 MB) takes 20 s with slow ruby/libyaml and 2 s with jq; a full minute arrives at ~3–30 million tasks — outside human scales. The real archivation trigger is ergonomics (scrolling the file, bloated diffs), not query speed.
128
+ - **available is a three-way join with negation** (in the registry ∧ no branch ∧ no merge commit): easy to get wrong — hence exactly one canonical implementation.
129
+ - **The start of a bar ≠ the birth of the branch:** merge-base is skewed by main's stagnation; the forge event feed expires; git does not store push time. The only eternal starting point is the branch's first commit.
130
+ - **Local origin/* refs are a cache, not the truth:** without `fetch --prune`, ghosts of deleted branches keep tasks "taken" forever.
131
+ - Only single-line changelog entries; multi-paragraph ones are a backup path of towncrier-style fragments.
132
+ - The insurance against someone else's squash/rebase is **a task trailer in the commit message** (`Task: door_design/16`; modern gits even have `git commit --trailer`): it rides in the object itself and survives a squash if it made it into the squash message. git notes and refs/replace were rejected as metadata channels: they do not travel with a default clone/fetch and forges do not show them. When a squash has already happened, the key cannot be attached retroactively (amend would rewrite main) — which is why the generator's dry-run must highlight unattributed commits loudly: the hole must be visible, not silent.
133
+ - The applicability boundary: the moment you need labels, assignees or task discussion — move to Issues, do not build a hybrid. Jira earns its keep for large teams and corporate scale; jearless is for the small and the swift.
134
+
135
+ ## Open questions
136
+
137
+ - The generator config: the regex + the line template + the prefix-to-section mapping; the dry-run format.
138
+ - How to hard-wire the fast-forward ban (an alias, a setting, a check inside `take`).
139
+ - Branches that miss the regex (quick fixes without a task) — a filter feature or a hole?
140
+ - Auto-deleting branches on the forge after a merge — the generator must not rely on the branch still existing.
141
+ - Archiving `tasks.yml`: the trigger is ergonomic discomfort, not performance. Rules for the future: cut by release tag (the same frozen moment as the changelog and the timeline snapshot), the file `archive_<tag>.yml`, move out done tasks only; available/in_progress are always computed over tasks.yml, identity for analytics is the union of the registry and the archives; it is a release-time step run by a human. The script itself is a registry task, to be taken when the trigger fires.
@@ -0,0 +1,44 @@
1
+ # Implementation plan
2
+
3
+ Date: 2026-11-06. The design lives in README.md (the project constitution — argued and proven). This file is how we build it. The name is settled: **jearless**. Package `jearless`, console command `jear` (alias `jearless`), config `.jearless.toml`. Slogan: "Fearless. Jearless. Bare git is enough — for small teams."
4
+
5
+ ## Technical decisions (and why)
6
+
7
+ - **Python 3.11+, installed via pipx.** One dependency — ruamel.yaml. Everything else is stdlib.
8
+ - **Git via subprocess.** Zero dependencies and full flag transparency. GitPython drags in gitdb and hides parameters — and our tool is precisely about the exact semantics of git; hiding it is out of the question.
9
+ - **Versioning via auto-version-hook** (pre-commit, pinned `v1.1.0`, config `.version-hook.yml`): the version bump rides the feature branch, main is protected, merge commits are untouched (`skip_merges`) — the "main moves only by merges" invariant holds. The flow version lives in `env.yml`; the package version in pyproject mirrors it automatically (`version_files`, hook v1.1.0).
10
+ - **CLI on argparse.** The tool preaches minimalism — let the framework count be zero.
11
+ - **ruamel.yaml (rt mode) — the only YAML parser.** Edits preserve comments (closes the round-trip risk from the README); reads use it too — the files are kilobytes, and the 2026-11-06 measurement showed performance is a non-issue at any scale.
12
+ - **Tests are repository factories in temporary directories** (the pattern of our /tmp demos) plus golden files for the generator. No git mocks — only real repositories with real merges.
13
+ - **The config is `.jearless.toml`:** the branch regex, the changelog line template, the prefix-to-section mapping.
14
+
15
+ ## The CLI surface (mapped to the README)
16
+
17
+ | Command | Mechanics | Phase |
18
+ |---|---|---|
19
+ | `add <project> "summary" [--points N] [--yes]` | append a task to the registry (comments survive); next id = max over registry ∧ archives ∧ branches; the duplicate guard warns on keyword overlap and asks `y/N` (non-TTY declines, `--yes` skips) | 1 |
20
+ | `take <project> <id>` | registry → slug; check origin (ls-remote — the truth, not the cache); branch `feature/<project>/<id>-<slug>`; push. The server arbitrates the race | 1 |
21
+ | `sprint` | fetch --prune; the join registry × origin branches × merge log; states available / in_progress / done | 1 |
22
+ | `changelog --from <tag> --to <tag> --dry-run` | merge commits → the config regex → sections by prefix; the summary from the registry (union with archives) | 2 |
23
+ | `doctor` | unattributed commits on main (neither merges nor registry edits) — the visible hole; branch naming violations; an invalid registry | 2 |
24
+ | `timeline --live \| --snapshot <tag>` | a mermaid gantt of the facts; live is never committed; the snapshot is a release artifact | 3 |
25
+ | `retro <plan.md>` | join the plan's bar ids (the gantt contract) with the actual timeline; planning misses | 3 |
26
+ | `archive <tag>` | move done tasks into `archive_<tag>.yml` — only on the ergonomics trigger | 4 |
27
+ | `release [--tag TAG]` | tag + changelog section + timeline snapshot in one human-run move; the tag defaults to the env.yml flow version; artifacts are committed and the annotated tag lands on them | — |
28
+
29
+ ## Phases and Definitions of Done
30
+
31
+ - **Phase 0. Skeleton:** pyproject, the CLI frame, the config schema, the git wrapper (subprocess), lint. DoD: `--help` works; empty states (no tags, no branches, an empty registry) do not crash commands.
32
+ - **Phase 1. The core loop:** add + take + sprint. DoD: a task is taken with a single push; the sprint is assembled from three sources; the race test — two takes from two clones against one remote, the second one gets "taken"; deleting a branch returns the task to available.
33
+ - **Phase 2. The journal:** changelog + doctor. DoD: golden tests on a factory repository with both merge message forms ("Merge branch …" and "Merge pull request …"); a squash commit is highlighted by doctor; dry-run is the default mode.
34
+ - **Phase 3. Time:** timeline live/snapshot + retro. DoD: the snapshot carries the author date of the branch's first commit and the committer date of the merge; a plan with task ids joins with the facts.
35
+ - **Phase 4. Deferred (on trigger):** archive, velocity analytics over the union of the registry and the archives.
36
+
37
+ ## What is deliberately absent (and that is a feature)
38
+
39
+ Statuses and assignees (derived, not stored), CI integrations, a web UI, daemons, hooks, notifications. The moment those are needed — time to leave for Issues / git-bug / Backlog.md, not to build them in here.
40
+
41
+ ## Open implementation questions
42
+
43
+ - `take` for a task missing from the registry: refuse, or an `--adhoc` flag — this would also settle the "branches off the regex" question.
44
+ - Detecting a fast-forward retroactively is impossible (it leaves no trace) — doctor catches only unattributed commits.
@@ -0,0 +1,112 @@
1
+ # jearless
2
+
3
+ **Fearless. Jearless.** Task tracking and a changelog on bare git — for small teams.
4
+
5
+ *Why the name: by ear — a faint echo of Jira; by sight — fearless; spelled with an error — and we track errors for a living; a jearless sprint — a fast run without fat.*
6
+
7
+ ## Install
8
+
9
+ From PyPI, straight into an isolated environment:
10
+
11
+ ```sh
12
+ pipx install jearless # the intended way for daily use
13
+ ```
14
+
15
+ For development, one command builds the venv with everything. There is
16
+ deliberately **no requirements.txt**: `pyproject.toml` is the single authored
17
+ source of dependencies, and a second list would drift.
18
+
19
+ ```sh
20
+ uv venv && uv pip install -e ".[dev]" # uv
21
+ python3 -m venv .venv && .venv/bin/pip install -e ".[dev]" # plain pip
22
+ ```
23
+
24
+ ## Quickstart
25
+
26
+ Inside your git repository:
27
+
28
+ ```sh
29
+ jear add door "paint it black" --points 2 # a task in the eternal registry
30
+ jear take door 1 # branch feature/door/1-paint-it-black, pushed
31
+ # ... work, then a true merge (--no-ff) ...
32
+ jear sprint # available / in_progress / done — derived from git
33
+ ```
34
+
35
+ No statuses are ever stored: the registry holds frozen facts, git holds
36
+ everything alive. See [The idea](#the-idea) for the full model.
37
+
38
+ **Status:** the design has stabilized; the mechanism is built around the eternal registry; the name is settled — jearless (2026-11-06). This file records the decisions and their rationale — it is not a specification. The implementation plan lives in [PLAN.md](PLAN.md).
39
+
40
+ ## The idea
41
+
42
+ Task tracking and a changelog on bare git. No trackers, no infrastructure.
43
+
44
+ 1. `tasks.yml` — the **eternal task registry**: subproject, id, summary, description, story_points. Tasks are added and never deleted; the file only grows. No live-state fields.
45
+ 2. `jear take door_design 16` — zero commits, zero YAML edits: verify the branch is absent on origin, create `feature/door_design/16-slug`, push. **Taking a task = pushing the branch.**
46
+ 3. Merging is a true merge (`--no-ff`): the branch name lands in history through the merge commit. No squash, no rebase merges.
47
+ 4. The changelog is not stored — it is generated at release time: merge commits between tags → the regex from the config → the line template. The line is the summary from the registry or the slug from the branch name.
48
+ 5. The branch prefix (`feature/`, `fix/`, `chore/`) → the changelog section.
49
+
50
+ ## The state model: three sources, zero stored statuses
51
+
52
+ | State | Condition |
53
+ |---|---|
54
+ | available | present in tasks.yml ∧ no branch on origin ∧ no merge commit |
55
+ | in_progress | a branch exists on origin ∧ no merge commit |
56
+ | done | a merge commit carrying the branch name exists in main (overrides an existing branch when the forge's auto-delete is off) |
57
+
58
+ The sprint and all analytics are joins on the key (subproject, number), computed on demand. The race for a task is arbitrated by git itself: a second push of a branch with the same name is rejected as non-fast-forward — "taken" arrives from the server.
59
+
60
+ ## Timeline and plan-versus-actual
61
+
62
+ - **The actual timeline** is a derived view: start = author date of the branch's first commit, end = committer date of the merge commit. The live variant is a command run on demand (fetch --prune, open branches end "now") and is **never committed**. The release snapshot is frozen by the tag and committed next to the changelog.
63
+ - **The Gantt (the plan)** is an authored artifact: drawn by hand or by script, committed as the team's contract. For the retrospective comparison, **bar ids = task ids** (subproject + number) — otherwise the plan cannot be joined with the facts.
64
+ - The hierarchy of truth about the start: the branch's first commit (exact, eternal) → merge-base (skewed by main's stagnation: the common commit's date ≠ the cut date) → forge events (the push time is visible, but the feed expires — on GitHub, within about 90 days). Push time does not exist in git's object model.
65
+ - Activity time metrics (hours, commit-clock deadlines) are deliberately out of scope: commit dates are client clocks, trivially spoofed, and they invite gaming.
66
+
67
+ ## Key decisions
68
+
69
+ - **The number is an identifier, not a position.** No renumbering, no reuse; the uniqueness key is the pair (subproject, number). Allocating the next number looks only at the registry — it never empties and never shrinks.
70
+ - **The eternal registry instead of deletion:** the old mechanics — "the task's deletion rides the branch's first commit" — is cancelled. Consequences: the task card (story_points included) lives forever, so analytics over done tasks lose nothing; `take` never mutates the YAML, so the yq round-trip problem disappears; registry conflicts become nearly impossible — only simultaneous additions at the same spot.
71
+ - **The branch name is the only key** linking the registry, the changelog and history. Slashes are the structural separators (`feature/door_design/16-slug`); hyphens live only inside slugs.
72
+ - **YAML as the registry format:** readable by yq, analytics over frozen facts. Live-state fields (status and friends) are forbidden: git knows the state, a stored copy drifts. Scripts only read the registry (`add` appending is the sole sanctioned exception).
73
+ - **The language of the registry, slugs and changelog is English** (the GitHub norm; decided 2026-11-06). Transliteration is only a fallback inside `take`, for summaries not written in latin script — a last resort for those who skipped their lessons, not the project's style.
74
+ - **main moves by merges only — no squash, no rebase merges, always `--no-ff`.** Direct human commits (add a task, fix the plan) are allowed; the ban concerns automatic writers and merge methods. Rebasing inside one's own unmerged branch is harmless — author dates survive it.
75
+ - **Nothing automatic writes to main.** Dropping a task = deleting the branch = the task is available again (it never left the registry).
76
+ - **"A separate life for the registry" is rejected** (a submodule, a database rendered into yml, a secrets manager): moving the storage does not solve the trigger problem — who flips `done` after the merge. The principle: **state for an event that cannot be observed without infrastructure is never stored — it is derived.**
77
+ - **Taking a task is a branch push: an occupancy registry, not a notification** — others see it at their own fetch.
78
+
79
+ ## Pros
80
+
81
+ - One database — git history plus the eternal registry of facts. Zero state files, zero hooks with write access, zero CI jobs pushing to main.
82
+ - Taking a task is one push, zero commits; undoing it is deleting the branch.
83
+ - The task card lives in the registry forever: velocity and any analytics over done tasks lose no data.
84
+ - The race for a task is resolved by the server, atomically, without locks or queues.
85
+ - Plan-versus-actual from two honest sources: the plan is authored and committed, the actual is derived from git, the comparison is the retrospective.
86
+ - Branch prefixes are the taxonomy: "Added / Fixed" sections come for free.
87
+ - Merge commits make a task an object of history: `--merges` between tags is the task journal; a merge commit's diff is the whole task at once.
88
+ - Version binding is free: whatever is merged between v1.2 and v1.3 lands under the v1.3 heading.
89
+ - The registry is versioned with the code, reviewed in merges, available offline.
90
+
91
+ ## Cons / risks
92
+
93
+ - **Squash and rebase merges silently lose the branch name** — the changelog line disappears without an error. On the forge these are the two enemy buttons; the repo needs "Create a merge commit" as the default. The legitimate way to clean up a task's history is **squash inside the branch before the true merge** ("squash inside, merge outside"): compress the WIP into one commit preserving the first commit's author date, then `--no-ff`. Verified by a live demo, 2026-11-06: the key lives in the merge commit, the timeline start in the preserved author date.
94
+ - **Fast-forward is the default** and the sneakiest source of lost merge commits: when main has not moved, git just moves the pointer. The "always --no-ff" rule is easy to forget.
95
+ - **The regex in the config rots first** (the very first draft failed to match its own example). A generator dry-run is mandatory.
96
+ - The regex must digest both merge message forms: "Merge branch ..." and "Merge pull request #N from owner/...".
97
+ - **YAML under hand editing:** block scalars for long descriptions are the most finicky spot; on-the-fly validation is mandatory.
98
+ - **Registry growth:** the file only increases. A measurement (2026-11-06, synthetic, ~270 bytes per task): a filtered query over 1M tasks (270 MB) takes 20 s with slow ruby/libyaml and 2 s with jq; a full minute arrives at ~3–30 million tasks — outside human scales. The real archivation trigger is ergonomics (scrolling the file, bloated diffs), not query speed.
99
+ - **available is a three-way join with negation** (in the registry ∧ no branch ∧ no merge commit): easy to get wrong — hence exactly one canonical implementation.
100
+ - **The start of a bar ≠ the birth of the branch:** merge-base is skewed by main's stagnation; the forge event feed expires; git does not store push time. The only eternal starting point is the branch's first commit.
101
+ - **Local origin/* refs are a cache, not the truth:** without `fetch --prune`, ghosts of deleted branches keep tasks "taken" forever.
102
+ - Only single-line changelog entries; multi-paragraph ones are a backup path of towncrier-style fragments.
103
+ - The insurance against someone else's squash/rebase is **a task trailer in the commit message** (`Task: door_design/16`; modern gits even have `git commit --trailer`): it rides in the object itself and survives a squash if it made it into the squash message. git notes and refs/replace were rejected as metadata channels: they do not travel with a default clone/fetch and forges do not show them. When a squash has already happened, the key cannot be attached retroactively (amend would rewrite main) — which is why the generator's dry-run must highlight unattributed commits loudly: the hole must be visible, not silent.
104
+ - The applicability boundary: the moment you need labels, assignees or task discussion — move to Issues, do not build a hybrid. Jira earns its keep for large teams and corporate scale; jearless is for the small and the swift.
105
+
106
+ ## Open questions
107
+
108
+ - The generator config: the regex + the line template + the prefix-to-section mapping; the dry-run format.
109
+ - How to hard-wire the fast-forward ban (an alias, a setting, a check inside `take`).
110
+ - Branches that miss the regex (quick fixes without a task) — a filter feature or a hole?
111
+ - Auto-deleting branches on the forge after a merge — the generator must not rely on the branch still existing.
112
+ - Archiving `tasks.yml`: the trigger is ergonomic discomfort, not performance. Rules for the future: cut by release tag (the same frozen moment as the changelog and the timeline snapshot), the file `archive_<tag>.yml`, move out done tasks only; available/in_progress are always computed over tasks.yml, identity for analytics is the union of the registry and the archives; it is a release-time step run by a human. The script itself is a registry task, to be taken when the trigger fires.
@@ -0,0 +1,4 @@
1
+ # Flow version (auto-version-hook dogfood): bumped automatically in branches,
2
+ # reaches main only by merge. The package version in pyproject.toml mirrors
3
+ # this one automatically (version_files, hook v1.1.0).
4
+ version: v0.10.4