taskulus 0.1.0__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.
- taskulus-0.1.0/PKG-INFO +20 -0
- taskulus-0.1.0/README.md +3 -0
- taskulus-0.1.0/pyproject.toml +46 -0
- taskulus-0.1.0/setup.cfg +4 -0
- taskulus-0.1.0/src/taskulus/__init__.py +7 -0
- taskulus-0.1.0/src/taskulus/cache.py +147 -0
- taskulus-0.1.0/src/taskulus/cli.py +563 -0
- taskulus-0.1.0/src/taskulus/config.py +36 -0
- taskulus-0.1.0/src/taskulus/config_loader.py +79 -0
- taskulus-0.1.0/src/taskulus/daemon.py +37 -0
- taskulus-0.1.0/src/taskulus/daemon_client.py +170 -0
- taskulus-0.1.0/src/taskulus/daemon_paths.py +31 -0
- taskulus-0.1.0/src/taskulus/daemon_protocol.py +86 -0
- taskulus-0.1.0/src/taskulus/daemon_server.py +220 -0
- taskulus-0.1.0/src/taskulus/dependencies.py +291 -0
- taskulus-0.1.0/src/taskulus/dependency_tree.py +218 -0
- taskulus-0.1.0/src/taskulus/doctor.py +48 -0
- taskulus-0.1.0/src/taskulus/file_io.py +53 -0
- taskulus-0.1.0/src/taskulus/hierarchy.py +58 -0
- taskulus-0.1.0/src/taskulus/ids.py +100 -0
- taskulus-0.1.0/src/taskulus/index.py +98 -0
- taskulus-0.1.0/src/taskulus/issue_close.py +37 -0
- taskulus-0.1.0/src/taskulus/issue_comment.py +55 -0
- taskulus-0.1.0/src/taskulus/issue_creation.py +137 -0
- taskulus-0.1.0/src/taskulus/issue_delete.py +28 -0
- taskulus-0.1.0/src/taskulus/issue_display.py +29 -0
- taskulus-0.1.0/src/taskulus/issue_files.py +47 -0
- taskulus-0.1.0/src/taskulus/issue_listing.py +209 -0
- taskulus-0.1.0/src/taskulus/issue_lookup.py +49 -0
- taskulus-0.1.0/src/taskulus/issue_transfer.py +81 -0
- taskulus-0.1.0/src/taskulus/issue_update.py +94 -0
- taskulus-0.1.0/src/taskulus/maintenance.py +227 -0
- taskulus-0.1.0/src/taskulus/migration.py +277 -0
- taskulus-0.1.0/src/taskulus/models.py +122 -0
- taskulus-0.1.0/src/taskulus/project.py +143 -0
- taskulus-0.1.0/src/taskulus/queries.py +94 -0
- taskulus-0.1.0/src/taskulus/users.py +20 -0
- taskulus-0.1.0/src/taskulus/wiki.py +129 -0
- taskulus-0.1.0/src/taskulus/workflows.py +90 -0
- taskulus-0.1.0/src/taskulus.egg-info/PKG-INFO +20 -0
- taskulus-0.1.0/src/taskulus.egg-info/SOURCES.txt +43 -0
- taskulus-0.1.0/src/taskulus.egg-info/dependency_links.txt +1 -0
- taskulus-0.1.0/src/taskulus.egg-info/entry_points.txt +2 -0
- taskulus-0.1.0/src/taskulus.egg-info/requires.txt +10 -0
- taskulus-0.1.0/src/taskulus.egg-info/top_level.txt +1 -0
taskulus-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: taskulus
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A git-backed project management system.
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: click
|
|
8
|
+
Requires-Dist: jinja2
|
|
9
|
+
Requires-Dist: pydantic
|
|
10
|
+
Requires-Dist: pyyaml
|
|
11
|
+
Requires-Dist: pydantic-settings
|
|
12
|
+
Requires-Dist: behave
|
|
13
|
+
Requires-Dist: coverage
|
|
14
|
+
Requires-Dist: ruff
|
|
15
|
+
Requires-Dist: black
|
|
16
|
+
Requires-Dist: sphinx
|
|
17
|
+
|
|
18
|
+
# Taskulus (Python)
|
|
19
|
+
|
|
20
|
+
This file exists for packaging metadata in editable installs. For full project documentation, see the repository README.md.
|
taskulus-0.1.0/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "taskulus"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A git-backed project management system."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"click",
|
|
13
|
+
"jinja2",
|
|
14
|
+
"pydantic",
|
|
15
|
+
"pyyaml",
|
|
16
|
+
"pydantic-settings",
|
|
17
|
+
"behave",
|
|
18
|
+
"coverage",
|
|
19
|
+
"ruff",
|
|
20
|
+
"black",
|
|
21
|
+
"sphinx",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
tsk = "taskulus.cli:cli"
|
|
26
|
+
|
|
27
|
+
[tool.setuptools]
|
|
28
|
+
package-dir = { "" = "src" }
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.packages.find]
|
|
31
|
+
where = ["src"]
|
|
32
|
+
|
|
33
|
+
[tool.black]
|
|
34
|
+
line-length = 88
|
|
35
|
+
target-version = ["py311"]
|
|
36
|
+
|
|
37
|
+
[tool.ruff]
|
|
38
|
+
line-length = 88
|
|
39
|
+
target-version = "py311"
|
|
40
|
+
|
|
41
|
+
[tool.semantic_release]
|
|
42
|
+
version_toml = ["pyproject.toml:project.version"]
|
|
43
|
+
tag_format = "v{version}"
|
|
44
|
+
build_command = "python -m build"
|
|
45
|
+
allow_zero_version = true
|
|
46
|
+
major_on_zero = false
|
taskulus-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Index cache utilities for Taskulus."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from taskulus.index import IssueIndex
|
|
13
|
+
from taskulus.models import IssueData
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class IndexCache:
|
|
18
|
+
"""Serialized index cache representation."""
|
|
19
|
+
|
|
20
|
+
version: int
|
|
21
|
+
built_at: datetime
|
|
22
|
+
file_mtimes: Dict[str, float]
|
|
23
|
+
issues: List[IssueData]
|
|
24
|
+
reverse_deps: Dict[str, List[str]]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _normalize_mtime(value: float) -> float:
|
|
28
|
+
"""Normalize file modification times for cache consistency.
|
|
29
|
+
|
|
30
|
+
:param value: Raw modification time value.
|
|
31
|
+
:type value: float
|
|
32
|
+
:return: Rounded modification time to microsecond precision.
|
|
33
|
+
:rtype: float
|
|
34
|
+
"""
|
|
35
|
+
return round(value, 6)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def collect_issue_file_mtimes(issues_directory: Path) -> Dict[str, float]:
|
|
39
|
+
"""Collect file modification times for issues.
|
|
40
|
+
|
|
41
|
+
:param issues_directory: Directory containing issue files.
|
|
42
|
+
:type issues_directory: Path
|
|
43
|
+
:return: Mapping of filename to mtime.
|
|
44
|
+
:rtype: Dict[str, float]
|
|
45
|
+
"""
|
|
46
|
+
mtimes: Dict[str, float] = {}
|
|
47
|
+
for entry in os.scandir(issues_directory):
|
|
48
|
+
if not entry.is_file() or not entry.name.endswith(".json"):
|
|
49
|
+
continue
|
|
50
|
+
mtimes[entry.name] = _normalize_mtime(entry.stat().st_mtime)
|
|
51
|
+
return mtimes
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def load_cache_if_valid(
|
|
55
|
+
cache_path: Path, issues_directory: Path
|
|
56
|
+
) -> Optional[IssueIndex]:
|
|
57
|
+
"""Load cached index if the cache is valid.
|
|
58
|
+
|
|
59
|
+
:param cache_path: Path to cache file.
|
|
60
|
+
:type cache_path: Path
|
|
61
|
+
:param issues_directory: Directory containing issue files.
|
|
62
|
+
:type issues_directory: Path
|
|
63
|
+
:return: IssueIndex if cache is valid, otherwise None.
|
|
64
|
+
:rtype: Optional[IssueIndex]
|
|
65
|
+
"""
|
|
66
|
+
if not cache_path.exists():
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
payload = json.loads(cache_path.read_text(encoding="utf-8"))
|
|
70
|
+
file_mtimes = payload.get("file_mtimes", {})
|
|
71
|
+
current_mtimes = collect_issue_file_mtimes(issues_directory)
|
|
72
|
+
if file_mtimes != current_mtimes:
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
issues = [IssueData.model_validate(item) for item in payload.get("issues", [])]
|
|
76
|
+
reverse_deps = payload.get("reverse_deps", {})
|
|
77
|
+
return build_index_from_cache(issues, reverse_deps)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def write_cache(
|
|
81
|
+
index: IssueIndex, cache_path: Path, file_mtimes: Dict[str, float]
|
|
82
|
+
) -> None:
|
|
83
|
+
"""Write an index cache file to disk.
|
|
84
|
+
|
|
85
|
+
:param index: Issue index to serialize.
|
|
86
|
+
:type index: IssueIndex
|
|
87
|
+
:param cache_path: Path to cache file.
|
|
88
|
+
:type cache_path: Path
|
|
89
|
+
:param file_mtimes: File modification time mapping.
|
|
90
|
+
:type file_mtimes: Dict[str, float]
|
|
91
|
+
"""
|
|
92
|
+
cache = IndexCache(
|
|
93
|
+
version=1,
|
|
94
|
+
built_at=datetime.now(timezone.utc),
|
|
95
|
+
file_mtimes=file_mtimes,
|
|
96
|
+
issues=list(index.by_id.values()),
|
|
97
|
+
reverse_deps={
|
|
98
|
+
target: [issue.identifier for issue in issues]
|
|
99
|
+
for target, issues in index.reverse_dependencies.items()
|
|
100
|
+
},
|
|
101
|
+
)
|
|
102
|
+
payload = {
|
|
103
|
+
"version": cache.version,
|
|
104
|
+
"built_at": cache.built_at.isoformat().replace("+00:00", "Z"),
|
|
105
|
+
"file_mtimes": cache.file_mtimes,
|
|
106
|
+
"issues": [
|
|
107
|
+
issue.model_dump(by_alias=True, mode="json") for issue in cache.issues
|
|
108
|
+
],
|
|
109
|
+
"reverse_deps": cache.reverse_deps,
|
|
110
|
+
}
|
|
111
|
+
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
|
112
|
+
cache_path.write_text(
|
|
113
|
+
json.dumps(payload, indent=2, sort_keys=False),
|
|
114
|
+
encoding="utf-8",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def build_index_from_cache(
|
|
119
|
+
issues: List[IssueData], reverse_deps: Dict[str, List[str]]
|
|
120
|
+
) -> IssueIndex:
|
|
121
|
+
"""Rebuild an IssueIndex from cached data.
|
|
122
|
+
|
|
123
|
+
:param issues: Issues from cache.
|
|
124
|
+
:type issues: List[IssueData]
|
|
125
|
+
:param reverse_deps: Reverse dependency mapping.
|
|
126
|
+
:type reverse_deps: Dict[str, List[str]]
|
|
127
|
+
:return: IssueIndex populated from cache.
|
|
128
|
+
:rtype: IssueIndex
|
|
129
|
+
"""
|
|
130
|
+
index = IssueIndex()
|
|
131
|
+
for issue in issues:
|
|
132
|
+
index.by_id[issue.identifier] = issue
|
|
133
|
+
index.by_status.setdefault(issue.status, []).append(issue)
|
|
134
|
+
index.by_type.setdefault(issue.issue_type, []).append(issue)
|
|
135
|
+
if issue.parent is not None:
|
|
136
|
+
index.by_parent.setdefault(issue.parent, []).append(issue)
|
|
137
|
+
for label in issue.labels:
|
|
138
|
+
index.by_label.setdefault(label, []).append(issue)
|
|
139
|
+
|
|
140
|
+
for target, issue_ids in reverse_deps.items():
|
|
141
|
+
index.reverse_dependencies[target] = [
|
|
142
|
+
index.by_id[identifier]
|
|
143
|
+
for identifier in issue_ids
|
|
144
|
+
if identifier in index.by_id
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
return index
|