kosokoso 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.
- kosokoso-0.1.0/LICENSE +13 -0
- kosokoso-0.1.0/PKG-INFO +66 -0
- kosokoso-0.1.0/README.md +51 -0
- kosokoso-0.1.0/kosokoso.egg-info/PKG-INFO +66 -0
- kosokoso-0.1.0/kosokoso.egg-info/SOURCES.txt +9 -0
- kosokoso-0.1.0/kosokoso.egg-info/dependency_links.txt +1 -0
- kosokoso-0.1.0/kosokoso.egg-info/entry_points.txt +2 -0
- kosokoso-0.1.0/kosokoso.egg-info/top_level.txt +1 -0
- kosokoso-0.1.0/kosokoso.py +154 -0
- kosokoso-0.1.0/pyproject.toml +28 -0
- kosokoso-0.1.0/setup.cfg +4 -0
kosokoso-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
2
|
+
Version 2, December 2004
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
|
5
|
+
|
|
6
|
+
Everyone is permitted to copy and distribute verbatim or modified
|
|
7
|
+
copies of this license document, and changing it is allowed as long
|
|
8
|
+
as the name is changed.
|
|
9
|
+
|
|
10
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
11
|
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
12
|
+
|
|
13
|
+
0. You just DO WHAT THE FUCK YOU WANT TO.
|
kosokoso-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kosokoso
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Create local AGENTS.md and CLAUDE.md aliases in Git repositories
|
|
5
|
+
License-Expression: WTFPL
|
|
6
|
+
Project-URL: Repository, https://github.com/uonr/kosokoso
|
|
7
|
+
Classifier: Environment :: Console
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# Kosokoso
|
|
17
|
+
|
|
18
|
+
Kosokoso (Japanese for “stealthily”) is a small script that creates `AGENTS.md` and `CLAUDE.md` links while preventing Git from tracking them.
|
|
19
|
+
|
|
20
|
+
For example, if a repository provides `CLAUDE.md` but not `AGENTS.md`, or vice versa, this script can quickly create a link for the missing file. If neither file exists, it uses `CONTRIBUTING.md` as the source for both links.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
Run it directly without installing anything:
|
|
25
|
+
|
|
26
|
+
```console
|
|
27
|
+
$ python3 kosokoso.py
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Install it as an isolated command with uv:
|
|
31
|
+
|
|
32
|
+
```console
|
|
33
|
+
$ uv tool install kosokoso
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
You can also run it without installing it permanently:
|
|
37
|
+
|
|
38
|
+
```console
|
|
39
|
+
$ uvx kosokoso
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
With Nix:
|
|
43
|
+
|
|
44
|
+
```console
|
|
45
|
+
$ nix run github:uonr/kosokoso
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
Run `kosokoso` from anywhere inside a Git repository:
|
|
51
|
+
|
|
52
|
+
```console
|
|
53
|
+
$ kosokoso
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
If you later want Git to track the generated links, remove their rules from the repository's local exclude file:
|
|
57
|
+
|
|
58
|
+
```console
|
|
59
|
+
$ kosokoso --track
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
This only removes the local exclude rules; it does not run `git add`.
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
[WTFPL](LICENSE)
|
kosokoso-0.1.0/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Kosokoso
|
|
2
|
+
|
|
3
|
+
Kosokoso (Japanese for “stealthily”) is a small script that creates `AGENTS.md` and `CLAUDE.md` links while preventing Git from tracking them.
|
|
4
|
+
|
|
5
|
+
For example, if a repository provides `CLAUDE.md` but not `AGENTS.md`, or vice versa, this script can quickly create a link for the missing file. If neither file exists, it uses `CONTRIBUTING.md` as the source for both links.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Run it directly without installing anything:
|
|
10
|
+
|
|
11
|
+
```console
|
|
12
|
+
$ python3 kosokoso.py
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Install it as an isolated command with uv:
|
|
16
|
+
|
|
17
|
+
```console
|
|
18
|
+
$ uv tool install kosokoso
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
You can also run it without installing it permanently:
|
|
22
|
+
|
|
23
|
+
```console
|
|
24
|
+
$ uvx kosokoso
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
With Nix:
|
|
28
|
+
|
|
29
|
+
```console
|
|
30
|
+
$ nix run github:uonr/kosokoso
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
Run `kosokoso` from anywhere inside a Git repository:
|
|
36
|
+
|
|
37
|
+
```console
|
|
38
|
+
$ kosokoso
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
If you later want Git to track the generated links, remove their rules from the repository's local exclude file:
|
|
42
|
+
|
|
43
|
+
```console
|
|
44
|
+
$ kosokoso --track
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This only removes the local exclude rules; it does not run `git add`.
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
[WTFPL](LICENSE)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kosokoso
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Create local AGENTS.md and CLAUDE.md aliases in Git repositories
|
|
5
|
+
License-Expression: WTFPL
|
|
6
|
+
Project-URL: Repository, https://github.com/uonr/kosokoso
|
|
7
|
+
Classifier: Environment :: Console
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# Kosokoso
|
|
17
|
+
|
|
18
|
+
Kosokoso (Japanese for “stealthily”) is a small script that creates `AGENTS.md` and `CLAUDE.md` links while preventing Git from tracking them.
|
|
19
|
+
|
|
20
|
+
For example, if a repository provides `CLAUDE.md` but not `AGENTS.md`, or vice versa, this script can quickly create a link for the missing file. If neither file exists, it uses `CONTRIBUTING.md` as the source for both links.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
Run it directly without installing anything:
|
|
25
|
+
|
|
26
|
+
```console
|
|
27
|
+
$ python3 kosokoso.py
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Install it as an isolated command with uv:
|
|
31
|
+
|
|
32
|
+
```console
|
|
33
|
+
$ uv tool install kosokoso
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
You can also run it without installing it permanently:
|
|
37
|
+
|
|
38
|
+
```console
|
|
39
|
+
$ uvx kosokoso
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
With Nix:
|
|
43
|
+
|
|
44
|
+
```console
|
|
45
|
+
$ nix run github:uonr/kosokoso
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
Run `kosokoso` from anywhere inside a Git repository:
|
|
51
|
+
|
|
52
|
+
```console
|
|
53
|
+
$ kosokoso
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
If you later want Git to track the generated links, remove their rules from the repository's local exclude file:
|
|
57
|
+
|
|
58
|
+
```console
|
|
59
|
+
$ kosokoso --track
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
This only removes the local exclude rules; it does not run `git add`.
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
[WTFPL](LICENSE)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
kosokoso
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Create local aliases for a Git repository's agent instruction file."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import os
|
|
8
|
+
import subprocess
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def git_path(*args: str) -> Path:
|
|
13
|
+
"""Return a path reported by Git, or exit with a useful error."""
|
|
14
|
+
try:
|
|
15
|
+
result = subprocess.run(
|
|
16
|
+
["git", "rev-parse", *args],
|
|
17
|
+
check=True,
|
|
18
|
+
stdout=subprocess.PIPE,
|
|
19
|
+
stderr=subprocess.PIPE,
|
|
20
|
+
text=True,
|
|
21
|
+
)
|
|
22
|
+
except FileNotFoundError:
|
|
23
|
+
raise SystemExit("error: git is not installed or is not on PATH") from None
|
|
24
|
+
except subprocess.CalledProcessError:
|
|
25
|
+
raise SystemExit("error: the current directory is not inside a Git repository") from None
|
|
26
|
+
|
|
27
|
+
return Path(result.stdout.strip()).resolve()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def create_link(link: Path, target: Path) -> None:
|
|
31
|
+
"""Create a relative symbolic link, falling back to a hard link."""
|
|
32
|
+
relative_target = os.path.relpath(target, start=link.parent)
|
|
33
|
+
try:
|
|
34
|
+
link.symlink_to(relative_target)
|
|
35
|
+
except (NotImplementedError, OSError) as symlink_error:
|
|
36
|
+
try:
|
|
37
|
+
os.link(target, link)
|
|
38
|
+
except OSError as hardlink_error:
|
|
39
|
+
raise SystemExit(
|
|
40
|
+
f"error: could not link {link.name} to {target.name}: "
|
|
41
|
+
f"symbolic link failed ({symlink_error}); "
|
|
42
|
+
f"hard link failed ({hardlink_error})"
|
|
43
|
+
) from None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def tracked_paths(root: Path, paths: list[Path]) -> list[str]:
|
|
47
|
+
"""Return the requested repository-relative paths found in Git's index."""
|
|
48
|
+
relative_paths = [path.relative_to(root).as_posix() for path in paths]
|
|
49
|
+
try:
|
|
50
|
+
result = subprocess.run(
|
|
51
|
+
["git", "ls-files", "--cached", "-z", "--", *relative_paths],
|
|
52
|
+
cwd=root,
|
|
53
|
+
check=True,
|
|
54
|
+
stdout=subprocess.PIPE,
|
|
55
|
+
stderr=subprocess.PIPE,
|
|
56
|
+
text=True,
|
|
57
|
+
)
|
|
58
|
+
except subprocess.CalledProcessError as error:
|
|
59
|
+
message = error.stderr.strip() or "git ls-files failed"
|
|
60
|
+
raise SystemExit(f"error: could not inspect Git's index: {message}") from None
|
|
61
|
+
|
|
62
|
+
return sorted(path for path in result.stdout.split("\0") if path)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def add_to_git_exclude(exclude_file: Path, names: list[str]) -> None:
|
|
66
|
+
"""Add root-anchored patterns for newly created links."""
|
|
67
|
+
exclude_file.parent.mkdir(parents=True, exist_ok=True)
|
|
68
|
+
|
|
69
|
+
existing_text = exclude_file.read_text() if exclude_file.exists() else ""
|
|
70
|
+
existing_lines = set(existing_text.splitlines())
|
|
71
|
+
additions = [f"/{name}" for name in names if f"/{name}" not in existing_lines]
|
|
72
|
+
if not additions:
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
with exclude_file.open("a") as file:
|
|
76
|
+
if existing_text and not existing_text.endswith("\n"):
|
|
77
|
+
file.write("\n")
|
|
78
|
+
file.write("\n".join(additions) + "\n")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def remove_from_git_exclude(exclude_file: Path, names: list[str]) -> None:
|
|
82
|
+
"""Remove root-anchored patterns previously added by this script."""
|
|
83
|
+
if not exclude_file.exists():
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
patterns = {f"/{name}" for name in names}
|
|
87
|
+
lines = exclude_file.read_text().splitlines(keepends=True)
|
|
88
|
+
kept_lines = [line for line in lines if line.rstrip("\r\n") not in patterns]
|
|
89
|
+
if kept_lines == lines:
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
exclude_file.write_text("".join(kept_lines))
|
|
93
|
+
for pattern in sorted(patterns):
|
|
94
|
+
if any(line.rstrip("\r\n") == pattern for line in lines):
|
|
95
|
+
print(f"removed {pattern} from {exclude_file}")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def parse_args() -> argparse.Namespace:
|
|
99
|
+
parser = argparse.ArgumentParser(
|
|
100
|
+
description="Create local aliases for a Git repository's agent instruction file."
|
|
101
|
+
)
|
|
102
|
+
parser.add_argument(
|
|
103
|
+
"--track",
|
|
104
|
+
action="store_true",
|
|
105
|
+
help="remove AGENTS.md and CLAUDE.md from the repository's local exclude file",
|
|
106
|
+
)
|
|
107
|
+
return parser.parse_args()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def main() -> None:
|
|
111
|
+
args = parse_args()
|
|
112
|
+
root = git_path("--show-toplevel")
|
|
113
|
+
exclude_file = git_path("--git-path", "info/exclude")
|
|
114
|
+
|
|
115
|
+
if args.track:
|
|
116
|
+
remove_from_git_exclude(exclude_file, ["AGENTS.md", "CLAUDE.md"])
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
agents = root / "AGENTS.md"
|
|
120
|
+
claude = root / "CLAUDE.md"
|
|
121
|
+
contributing = root / "CONTRIBUTING.md"
|
|
122
|
+
|
|
123
|
+
agents_exists = os.path.lexists(agents)
|
|
124
|
+
claude_exists = os.path.lexists(claude)
|
|
125
|
+
|
|
126
|
+
if agents_exists and claude_exists:
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
links: list[tuple[Path, Path]]
|
|
130
|
+
if agents_exists:
|
|
131
|
+
links = [(claude, agents)]
|
|
132
|
+
elif claude_exists:
|
|
133
|
+
links = [(agents, claude)]
|
|
134
|
+
elif os.path.lexists(contributing):
|
|
135
|
+
links = [(agents, contributing), (claude, contributing)]
|
|
136
|
+
else:
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
tracked = tracked_paths(root, [link for link, _ in links])
|
|
140
|
+
if tracked:
|
|
141
|
+
names = ", ".join(tracked)
|
|
142
|
+
raise SystemExit(
|
|
143
|
+
f"error: refusing to create links for paths already tracked by Git: {names}\n"
|
|
144
|
+
" .git/info/exclude cannot ignore tracked files"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
for link, target in links:
|
|
148
|
+
create_link(link, target)
|
|
149
|
+
print(f"created {link.name} -> {os.readlink(link) if link.is_symlink() else target.name}")
|
|
150
|
+
add_to_git_exclude(exclude_file, [link.name])
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
if __name__ == "__main__":
|
|
154
|
+
main()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kosokoso"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Create local AGENTS.md and CLAUDE.md aliases in Git repositories"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "WTFPL"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
dependencies = []
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Environment :: Console",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
kosokoso = "kosokoso:main"
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Repository = "https://github.com/uonr/kosokoso"
|
|
26
|
+
|
|
27
|
+
[tool.setuptools]
|
|
28
|
+
py-modules = ["kosokoso"]
|
kosokoso-0.1.0/setup.cfg
ADDED