kodebrain 0.1.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.
kodebrain/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
kodebrain/cli.py ADDED
@@ -0,0 +1,116 @@
1
+ """
2
+ kodebrain CLI — install agent instructions and git hooks.
3
+
4
+ Usage:
5
+ kodebrain install [path] [--platform all|claude|cursor|copilot|windsurf|cline]
6
+ kodebrain uninstall [path]
7
+ kodebrain hook install [path]
8
+ kodebrain hook uninstall [path]
9
+ kodebrain hook status [path]
10
+ """
11
+
12
+ from __future__ import annotations
13
+ import argparse
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ from kodebrain.install import PLATFORMS, install, uninstall
18
+ from kodebrain import hook as _hook
19
+
20
+
21
+ ALL_PLATFORMS = list(PLATFORMS.keys())
22
+
23
+
24
+ def cmd_install(args: argparse.Namespace) -> int:
25
+ root = Path(args.path).resolve()
26
+ platforms = ALL_PLATFORMS if args.platform == "all" else [args.platform]
27
+
28
+ try:
29
+ written = install(root, platforms)
30
+ except RuntimeError as e:
31
+ print(f"error: {e}", file=sys.stderr)
32
+ return 1
33
+
34
+ print(f"Kode Brain installed in {root.name}/")
35
+ for f in written:
36
+ label = next(
37
+ (PLATFORMS[p]["label"] for p in PLATFORMS if PLATFORMS[p]["file"] == f),
38
+ f,
39
+ )
40
+ print(f" ✓ {f} ({label})")
41
+ return 0
42
+
43
+
44
+ def cmd_uninstall(args: argparse.Namespace) -> int:
45
+ root = Path(args.path).resolve()
46
+ changed = uninstall(root)
47
+
48
+ if not changed:
49
+ print("No Kode Brain blocks found — nothing to remove.")
50
+ return 0
51
+
52
+ print(f"Kode Brain removed from {root.name}/")
53
+ for f in changed:
54
+ print(f" ✓ {f}")
55
+ return 0
56
+
57
+
58
+ def cmd_hook(args: argparse.Namespace) -> int:
59
+ root = Path(args.path).resolve()
60
+
61
+ if not (root / ".git").is_dir():
62
+ print(f"error: {root} is not a git repository.", file=sys.stderr)
63
+ return 1
64
+
65
+ if args.hook_cmd == "install":
66
+ path = _hook.install(root)
67
+ print(f"Hook installed: {path}")
68
+ elif args.hook_cmd == "uninstall":
69
+ removed = _hook.uninstall(root)
70
+ print("Hook removed." if removed else "Hook not installed — nothing to remove.")
71
+ elif args.hook_cmd == "status":
72
+ installed = _hook.status(root)
73
+ print(f"Hook {'installed' if installed else 'not installed'}.")
74
+ return 0
75
+
76
+
77
+ def main() -> None:
78
+ parser = argparse.ArgumentParser(
79
+ prog="kodebrain",
80
+ description="Kode Brain — install agent instructions across AI platforms.",
81
+ )
82
+ sub = parser.add_subparsers(dest="command", required=True)
83
+
84
+ # install
85
+ p_install = sub.add_parser("install", help="Write KB instruction blocks to platform config files.")
86
+ p_install.add_argument("path", nargs="?", default=".", help="Project root (default: current directory)")
87
+ p_install.add_argument(
88
+ "--platform",
89
+ choices=["all"] + ALL_PLATFORMS,
90
+ default="all",
91
+ help="Target platform (default: all)",
92
+ )
93
+
94
+ # uninstall
95
+ p_uninstall = sub.add_parser("uninstall", help="Remove all KB instruction blocks.")
96
+ p_uninstall.add_argument("path", nargs="?", default=".", help="Project root (default: current directory)")
97
+
98
+ # hook
99
+ p_hook = sub.add_parser("hook", help="Manage the git post-commit hook.")
100
+ hook_sub = p_hook.add_subparsers(dest="hook_cmd", required=True)
101
+ for hcmd in ("install", "uninstall", "status"):
102
+ hp = hook_sub.add_parser(hcmd)
103
+ hp.add_argument("path", nargs="?", default=".", help="Project root (default: current directory)")
104
+
105
+ args = parser.parse_args()
106
+
107
+ if args.command == "install":
108
+ sys.exit(cmd_install(args))
109
+ elif args.command == "uninstall":
110
+ sys.exit(cmd_uninstall(args))
111
+ elif args.command == "hook":
112
+ sys.exit(cmd_hook(args))
113
+
114
+
115
+ if __name__ == "__main__":
116
+ main()
kodebrain/hook.py ADDED
@@ -0,0 +1,90 @@
1
+ """
2
+ Git post-commit hook that marks KB-tracked source files as dirty
3
+ after each commit, so the next /kodebrain scan knows what changed.
4
+
5
+ The hook does NOT run the LLM — it only updates file-hashes.json
6
+ so scan can detect drift deterministically.
7
+ """
8
+
9
+ from __future__ import annotations
10
+ import os
11
+ import re
12
+ import stat
13
+ import subprocess
14
+ from pathlib import Path
15
+
16
+ HOOK_START = "# kodebrain:start"
17
+ HOOK_END = "# kodebrain:end"
18
+ HOOK_FILE = ".git/hooks/post-commit"
19
+ _HOOK_RE = re.compile(
20
+ r"\n?" + re.escape(HOOK_START) + r".*?" + re.escape(HOOK_END) + r"\n?",
21
+ re.DOTALL,
22
+ )
23
+
24
+ _HOOK_BODY = """\
25
+ # kodebrain:start
26
+ # Kode Brain — mark changed source files as dirty in file-hashes.json
27
+ # Run /kodebrain scan in Claude Code to refresh KB pages after significant changes.
28
+ _KB_HASHES=$(ls docs/brain/projects/*/graph/file-hashes.json 2>/dev/null | head -1)
29
+ if [ -n "$_KB_HASHES" ]; then
30
+ _CHANGED=$(git diff --name-only HEAD~1 2>/dev/null | grep -E '\\.(ts|tsx|js|jsx|py|go|rs|java|rb|swift|kt)$' | head -30)
31
+ if [ -n "$_CHANGED" ]; then
32
+ python3 -c "
33
+ import json, hashlib, sys
34
+ from pathlib import Path
35
+ hashes_path = Path('$_KB_HASHES')
36
+ if not hashes_path.exists():
37
+ sys.exit(0)
38
+ hashes = json.loads(hashes_path.read_text())
39
+ changed = [f for f in '''$_CHANGED'''.strip().split() if f]
40
+ for f in changed:
41
+ p = Path(f)
42
+ if p.exists():
43
+ hashes[f] = hashlib.sha256(p.read_bytes()).hexdigest()
44
+ elif f in hashes:
45
+ del hashes[f]
46
+ hashes_path.write_text(json.dumps(hashes, indent=2))
47
+ " 2>/dev/null || true
48
+ fi
49
+ fi
50
+ # kodebrain:end"""
51
+
52
+
53
+ def install(root: Path) -> str:
54
+ hook_path = root / HOOK_FILE
55
+ hook_path.parent.mkdir(parents=True, exist_ok=True)
56
+
57
+ existing = hook_path.read_text(encoding="utf-8") if hook_path.exists() else ""
58
+
59
+ if HOOK_START in existing:
60
+ new_content = _HOOK_RE.sub("", existing).rstrip("\n") + "\n\n" + _HOOK_BODY + "\n"
61
+ elif existing.strip():
62
+ new_content = existing.rstrip("\n") + "\n\n" + _HOOK_BODY + "\n"
63
+ else:
64
+ new_content = "#!/bin/sh\n\n" + _HOOK_BODY + "\n"
65
+
66
+ hook_path.write_text(new_content, encoding="utf-8")
67
+ hook_path.chmod(hook_path.stat().st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)
68
+ return HOOK_FILE
69
+
70
+
71
+ def uninstall(root: Path) -> bool:
72
+ hook_path = root / HOOK_FILE
73
+ if not hook_path.exists():
74
+ return False
75
+ original = hook_path.read_text(encoding="utf-8")
76
+ if HOOK_START not in original:
77
+ return False
78
+ cleaned = _HOOK_RE.sub("", original).rstrip("\n")
79
+ if cleaned and cleaned.strip() != "#!/bin/sh":
80
+ hook_path.write_text(cleaned + "\n", encoding="utf-8")
81
+ else:
82
+ hook_path.unlink()
83
+ return True
84
+
85
+
86
+ def status(root: Path) -> bool:
87
+ hook_path = root / HOOK_FILE
88
+ if not hook_path.exists():
89
+ return False
90
+ return HOOK_START in hook_path.read_text(encoding="utf-8")
kodebrain/install.py ADDED
@@ -0,0 +1,179 @@
1
+ """
2
+ Write/remove Kode Brain agent instruction blocks in platform config files.
3
+
4
+ Each platform gets a tagged block:
5
+ <!-- kodebrain:start -->
6
+ ...instructions...
7
+ <!-- kodebrain:end -->
8
+
9
+ The block is idempotent — install twice, update safely. Uninstall removes it cleanly.
10
+ """
11
+
12
+ from __future__ import annotations
13
+ import re
14
+ from pathlib import Path
15
+
16
+ BLOCK_START = "<!-- kodebrain:start -->"
17
+ BLOCK_END = "<!-- kodebrain:end -->"
18
+ _BLOCK_RE = re.compile(
19
+ r"\n?" + re.escape(BLOCK_START) + r".*?" + re.escape(BLOCK_END) + r"\n?",
20
+ re.DOTALL,
21
+ )
22
+
23
+ # ---------------------------------------------------------------------------
24
+ # Platform definitions
25
+ # ---------------------------------------------------------------------------
26
+
27
+ PLATFORMS = {
28
+ "claude": {
29
+ "file": "CLAUDE.md",
30
+ "label": "Claude Code",
31
+ "create_if_missing": True,
32
+ },
33
+ "cursor": {
34
+ "file": ".cursor/rules/kodebrain.mdc",
35
+ "label": "Cursor",
36
+ "create_if_missing": True,
37
+ },
38
+ "copilot": {
39
+ "file": ".github/copilot-instructions.md",
40
+ "label": "GitHub Copilot",
41
+ "create_if_missing": True,
42
+ },
43
+ "windsurf": {
44
+ "file": ".windsurfrules",
45
+ "label": "Windsurf",
46
+ "create_if_missing": True,
47
+ },
48
+ "cline": {
49
+ "file": ".clinerules",
50
+ "label": "Cline",
51
+ "create_if_missing": True,
52
+ },
53
+ }
54
+
55
+ # ---------------------------------------------------------------------------
56
+ # Block content
57
+ # ---------------------------------------------------------------------------
58
+
59
+ def _claude_block(name: str) -> str:
60
+ return f"""{BLOCK_START}
61
+ ## Kode Brain — Knowledge Base
62
+
63
+ This project has a structured knowledge map at `docs/brain/projects/{name}/`.
64
+
65
+ **Session start:** Run `/kodebrain reading-pack "<task>"` before touching any code.
66
+ It returns the relevant domain pages, source file hints, and active warnings — 3–25× cheaper than navigating source files cold.
67
+
68
+ **After editing source files:** Run `/kodebrain update --files <f1> <f2>` to keep the KB current.
69
+ Subsequent queries return fresh data, not pre-edit state.
70
+
71
+ **For questions:** Run `/kodebrain query "<question>"` instead of reading raw source files.
72
+
73
+ **KB-first rule:** Use KB pages as primary source of truth.
74
+ Read source files directly only when making a targeted edit or when a node is `confidence: stale`.
75
+
76
+ KB location: `docs/brain/projects/{name}/`
77
+ Graph view: Open `docs/brain/` as an Obsidian vault.
78
+ {BLOCK_END}"""
79
+
80
+
81
+ def _generic_block(name: str) -> str:
82
+ return f"""{BLOCK_START}
83
+ ## Kode Brain — Knowledge Base
84
+
85
+ This project has a structured knowledge map at `docs/brain/projects/{name}/`.
86
+
87
+ **Before starting any task:**
88
+ 1. Read `docs/brain/projects/{name}/{name}.md` — project hub with all domains.
89
+ 2. Read the domain hub(s) for areas you'll touch: `docs/brain/projects/{name}/domains/<domain>/<domain>.md`.
90
+ 3. Check `docs/brain/projects/{name}/reports/reading-packs/` for a pre-built context pack matching your task.
91
+
92
+ **KB-first rule:** Use KB pages as primary source of truth.
93
+ Read source files directly only for targeted edits or when a page shows `confidence: stale`.
94
+
95
+ **After editing source files:**
96
+ The KB may drift from source. Check `docs/brain/projects/{name}/graph/file-hashes.json`
97
+ to identify which files changed. Run `/kodebrain scan` in Claude Code to refresh the KB.
98
+
99
+ KB location: `docs/brain/projects/{name}/`
100
+ {BLOCK_END}"""
101
+
102
+
103
+ def _make_block(platform: str, name: str) -> str:
104
+ if platform == "claude":
105
+ return _claude_block(name)
106
+ return _generic_block(name)
107
+
108
+
109
+ # ---------------------------------------------------------------------------
110
+ # KB discovery
111
+ # ---------------------------------------------------------------------------
112
+
113
+ def find_kb_name(root: Path) -> str | None:
114
+ """Return the project name by scanning docs/brain/projects/."""
115
+ projects_dir = root / "docs" / "brain" / "projects"
116
+ if not projects_dir.is_dir():
117
+ return None
118
+ for child in projects_dir.iterdir():
119
+ if child.is_dir() and (child / "graph" / "nodes.json").exists():
120
+ return child.name
121
+ return None
122
+
123
+
124
+ # ---------------------------------------------------------------------------
125
+ # Install / uninstall
126
+ # ---------------------------------------------------------------------------
127
+
128
+ def install(root: Path, platforms: list[str]) -> list[str]:
129
+ """Write KB instruction blocks. Returns list of files written."""
130
+ name = find_kb_name(root)
131
+ if name is None:
132
+ raise RuntimeError(
133
+ "No Kode Brain KB found in docs/brain/projects/. "
134
+ "Run /kodebrain init first."
135
+ )
136
+
137
+ written: list[str] = []
138
+ for platform in platforms:
139
+ cfg = PLATFORMS[platform]
140
+ target = root / cfg["file"]
141
+
142
+ target.parent.mkdir(parents=True, exist_ok=True)
143
+
144
+ existing = target.read_text(encoding="utf-8") if target.exists() else ""
145
+ block = _make_block(platform, name)
146
+
147
+ if BLOCK_START in existing:
148
+ # Replace existing block
149
+ new_content = _BLOCK_RE.sub("", existing).rstrip("\n") + "\n\n" + block + "\n"
150
+ elif existing.strip():
151
+ # Append to existing file
152
+ new_content = existing.rstrip("\n") + "\n\n" + block + "\n"
153
+ else:
154
+ # New file
155
+ new_content = block + "\n"
156
+
157
+ target.write_text(new_content, encoding="utf-8")
158
+ written.append(str(target.relative_to(root)))
159
+
160
+ return written
161
+
162
+
163
+ def uninstall(root: Path) -> list[str]:
164
+ """Remove KB blocks from all platform files. Returns list of files changed."""
165
+ changed: list[str] = []
166
+ for cfg in PLATFORMS.values():
167
+ target = root / cfg["file"]
168
+ if not target.exists():
169
+ continue
170
+ original = target.read_text(encoding="utf-8")
171
+ if BLOCK_START not in original:
172
+ continue
173
+ cleaned = _BLOCK_RE.sub("", original).rstrip("\n")
174
+ if cleaned:
175
+ target.write_text(cleaned + "\n", encoding="utf-8")
176
+ else:
177
+ target.unlink()
178
+ changed.append(str(target.relative_to(root)))
179
+ return changed
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.4
2
+ Name: kodebrain
3
+ Version: 0.1.0
4
+ Summary: Living knowledge map for codebases — install agent instructions across AI platforms
5
+ Project-URL: Homepage, https://github.com/mekku/kb-builder
6
+ License: MIT
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+
10
+ # kb-builder
11
+
12
+ **Kode Brain** — a living knowledge system for evolving software projects.
13
+
14
+ Converts an imperfect, growing codebase into a structured, searchable knowledge map of domains, capabilities, concepts, flows, runtime behavior, dependencies, legacy areas, migration states, and source evidence — so humans and AI agents can understand and modify the system without rediscovering everything from scratch.
15
+
16
+ ## Design Documents
17
+
18
+ | Document | Purpose |
19
+ |---|---|
20
+ | [Taxonomy](docs/design/taxonomy.md) | Finalized node types, edge types, status labels, confidence labels |
21
+ | [Skills](docs/design/skills.md) | Skill API contracts — inputs, outputs, guarantees |
22
+ | [Agents](docs/design/agents.md) | Agent role boundaries — responsibilities, allowed skills, forbidden actions |
23
+ | [Workflows](docs/design/workflows.md) | Core workflow sequence diagrams |
24
+ | [Open Decisions](docs/design/open-decisions.md) | Unresolved architectural decisions |
25
+
26
+ ## Schemas
27
+
28
+ | File | Purpose |
29
+ |---|---|
30
+ | [schema/node.schema.json](schema/node.schema.json) | JSON Schema for KnowledgeNode |
31
+ | [schema/edge.schema.json](schema/edge.schema.json) | JSON Schema for KnowledgeEdge |
32
+ | [schema/knowledge-base.schema.json](schema/knowledge-base.schema.json) | Top-level graph container schema |
33
+
34
+ ## Design Order
35
+
36
+ Per the Kode Brain spec (§20.7):
37
+
38
+ ```
39
+ Taxonomy → Workflow → Skills → Agents → Plugin/CLI
40
+ ```
41
+
42
+ This repository is currently at the **design phase**. Implementation comes after the design is validated.
43
+
44
+ ## Commands
45
+
46
+ ```
47
+ /kodebrain init [path] Scan project, scaffold docs/kodebrain/
48
+ /kodebrain scan [path] Re-scan and update knowledge graph
49
+ /kodebrain query "<task or symptom>" Query the KB by task description
50
+ /kodebrain reading-pack "<task>" Generate + save a context pack
51
+ /kodebrain detect-legacy [--domain slug] Flag suspected dead code
52
+ /kodebrain review [--page path] Review KB pages for stale claims
53
+ /kodebrain update [--diff] [--files ...] Update KB from changed files
54
+ ```
55
+
56
+ ## Installation
57
+
58
+ The skill must be installed into Claude Code's skills directory:
59
+
60
+ ```bash
61
+ # From the repo root
62
+ ln -s "$(pwd)" ~/.claude/skills/kodebrain
63
+ ```
64
+
65
+ After symlinking, `SKILL.md` is discoverable by Claude Code and `/kodebrain` becomes available in any Claude Code session.
@@ -0,0 +1,8 @@
1
+ kodebrain/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
2
+ kodebrain/cli.py,sha256=WGuvRWbT6ZSSXGYeMrBUEklXMinyxkm-qU0rgz5aDHw,3569
3
+ kodebrain/hook.py,sha256=SydLKkho0tyUi13_RJt-OL4xCcEALwu4o37GD-pVG24,2854
4
+ kodebrain/install.py,sha256=gyqODHXmubCQEjYPm0jFIASM6xyD6Bpomx7uJqWNQVM,6097
5
+ kodebrain-0.1.0.dist-info/METADATA,sha256=NniPdUODjsSO_ROz2D4Qg35-7Td2xlMyQ5uYO15XAkU,2618
6
+ kodebrain-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
7
+ kodebrain-0.1.0.dist-info/entry_points.txt,sha256=ANdDDnzUVgeAX5QIxThWzGL9_8wyYFJSGAY93UXaWiE,49
8
+ kodebrain-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ kodebrain = kodebrain.cli:main