codebase-cortex 0.1.0__tar.gz → 0.1.2__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 (55) hide show
  1. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/PKG-INFO +17 -1
  2. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/README.md +16 -0
  3. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/pyproject.toml +1 -1
  4. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/agents/doc_writer.py +5 -4
  5. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/agents/sprint_reporter.py +5 -6
  6. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/agents/task_creator.py +1 -1
  7. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/cli.py +2 -2
  8. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/notion/bootstrap.py +14 -15
  9. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/notion/page_cache.py +3 -3
  10. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/tests/test_bootstrap.py +5 -2
  11. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/tests/test_page_cache.py +3 -3
  12. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/.gitignore +0 -0
  13. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/LICENSE +0 -0
  14. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/docs/agents.md +0 -0
  15. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/docs/architecture.md +0 -0
  16. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/docs/cli-reference.md +0 -0
  17. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/docs/configuration.md +0 -0
  18. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/docs/contributing.md +0 -0
  19. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/docs/embeddings.md +0 -0
  20. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/docs/notion-integration.md +0 -0
  21. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/__init__.py +0 -0
  22. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/agents/__init__.py +0 -0
  23. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/agents/base.py +0 -0
  24. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/agents/code_analyzer.py +0 -0
  25. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/agents/semantic_finder.py +0 -0
  26. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/auth/__init__.py +0 -0
  27. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/auth/callback_server.py +0 -0
  28. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/auth/oauth.py +0 -0
  29. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/auth/token_store.py +0 -0
  30. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/config.py +0 -0
  31. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/embeddings/__init__.py +0 -0
  32. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/embeddings/clustering.py +0 -0
  33. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/embeddings/indexer.py +0 -0
  34. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/embeddings/store.py +0 -0
  35. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/git/__init__.py +0 -0
  36. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/git/diff_parser.py +0 -0
  37. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/git/github_client.py +0 -0
  38. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/graph.py +0 -0
  39. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/mcp_client.py +0 -0
  40. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/notion/__init__.py +0 -0
  41. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/state.py +0 -0
  42. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/utils/__init__.py +0 -0
  43. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/utils/json_parsing.py +0 -0
  44. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/utils/logging.py +0 -0
  45. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/utils/rate_limiter.py +0 -0
  46. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/src/codebase_cortex/utils/section_parser.py +0 -0
  47. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/tests/__init__.py +0 -0
  48. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/tests/conftest.py +0 -0
  49. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/tests/test_agents.py +0 -0
  50. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/tests/test_config.py +0 -0
  51. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/tests/test_diff_parser.py +0 -0
  52. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/tests/test_embeddings.py +0 -0
  53. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/tests/test_graph.py +0 -0
  54. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/tests/test_section_parser.py +0 -0
  55. {codebase_cortex-0.1.0 → codebase_cortex-0.1.2}/tests/test_state.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codebase-cortex
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: AI-powered documentation autopilot — commit code, docs update themselves. Five LangGraph agents analyze diffs, find related code via FAISS embeddings, and sync Notion pages through MCP.
5
5
  Project-URL: Homepage, https://github.com/sarupurisailalith/codebase-cortex
6
6
  Project-URL: Repository, https://github.com/sarupurisailalith/codebase-cortex
@@ -164,6 +164,22 @@ graph TD
164
164
  4. **TaskCreator** — Identifies undocumented areas and creates task pages in Notion
165
165
  5. **SprintReporter** — Synthesizes all activity into a weekly sprint summary
166
166
 
167
+ ## Notion Page Structure
168
+
169
+ When you run `cortex init`, Cortex creates a parent page in Notion **named after your repository directory**. All documentation pages are created as children of this parent:
170
+
171
+ ```
172
+ your-project/ (repo directory)
173
+ └── Notion:
174
+ 📄 your-project ← parent page (named after repo)
175
+ ├── 🏗️ Architecture Overview
176
+ ├── 📡 API Reference
177
+ ├── 📋 Sprint Log
178
+ └── ✅ Task Board
179
+ ```
180
+
181
+ Each repo gets its own parent page — if you use Cortex in multiple projects, they each get an independent page tree. To bring existing Notion pages under Cortex management, simply move them under the parent page in Notion and run `cortex scan` to discover them.
182
+
167
183
  ## Architecture
168
184
 
169
185
  For detailed architecture documentation, see [`docs/architecture.md`](docs/architecture.md).
@@ -118,6 +118,22 @@ graph TD
118
118
  4. **TaskCreator** — Identifies undocumented areas and creates task pages in Notion
119
119
  5. **SprintReporter** — Synthesizes all activity into a weekly sprint summary
120
120
 
121
+ ## Notion Page Structure
122
+
123
+ When you run `cortex init`, Cortex creates a parent page in Notion **named after your repository directory**. All documentation pages are created as children of this parent:
124
+
125
+ ```
126
+ your-project/ (repo directory)
127
+ └── Notion:
128
+ 📄 your-project ← parent page (named after repo)
129
+ ├── 🏗️ Architecture Overview
130
+ ├── 📡 API Reference
131
+ ├── 📋 Sprint Log
132
+ └── ✅ Task Board
133
+ ```
134
+
135
+ Each repo gets its own parent page — if you use Cortex in multiple projects, they each get an independent page tree. To bring existing Notion pages under Cortex management, simply move them under the parent page in Notion and run `cortex scan` to discover them.
136
+
121
137
  ## Architecture
122
138
 
123
139
  For detailed architecture documentation, see [`docs/architecture.md`](docs/architecture.md).
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codebase-cortex"
3
- version = "0.1.0"
3
+ version = "0.1.2"
4
4
  description = "AI-powered documentation autopilot — commit code, docs update themselves. Five LangGraph agents analyze diffs, find related code via FAISS embeddings, and sync Notion pages through MCP."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -150,7 +150,7 @@ class DocWriterAgent(BaseAgent):
150
150
  existing_content_section += f"\n### {title}\n```\n{truncated}\n```\n"
151
151
 
152
152
  # Build dynamic page list from cache
153
- doc_pages = cache.find_all_doc_pages()
153
+ doc_pages = cache.find_all_doc_pages(parent_title=settings.repo_path.name)
154
154
  page_list = "\n".join(f"- {p.title}" for p in doc_pages) if doc_pages else "- (no pages yet)"
155
155
 
156
156
  # Ask LLM to generate doc updates
@@ -240,8 +240,8 @@ Only include pages that genuinely need updating. Respond with ONLY the JSON arra
240
240
  logger = get_logger()
241
241
  existing: dict[str, str] = {}
242
242
 
243
- # Fetch all doc pages (skip infrastructure-only pages)
244
- doc_pages = cache.find_all_doc_pages()
243
+ # Fetch all doc pages (skip parent page)
244
+ doc_pages = cache.find_all_doc_pages(parent_title=settings.repo_path.name)
245
245
  # Limit to 10 pages to avoid excessive API calls
246
246
  pages_to_fetch = doc_pages[:10]
247
247
 
@@ -300,7 +300,8 @@ Only include pages that genuinely need updating. Respond with ONLY the JSON arra
300
300
  settings = Settings.from_env()
301
301
 
302
302
  # Get parent page for new pages
303
- parent_page = cache.find_by_title("Codebase Cortex")
303
+ parent_title = settings.repo_path.name
304
+ parent_page = cache.find_by_title(parent_title)
304
305
  parent_id = parent_page.page_id if parent_page else None
305
306
 
306
307
  try:
@@ -89,7 +89,7 @@ Write a complete sprint report in markdown."""
89
89
  cache = PageCache(cache_path=settings.page_cache_path)
90
90
 
91
91
  sprint_page = cache.find_by_title("Sprint Log")
92
- parent_page = cache.find_by_title("Codebase Cortex")
92
+ parent_page = cache.find_by_title(settings.repo_path.name)
93
93
  parent_id = (sprint_page or parent_page)
94
94
  parent_id = parent_id.page_id if parent_id else None
95
95
 
@@ -101,17 +101,16 @@ Write a complete sprint report in markdown."""
101
101
  content = f"# Sprint Report — Week of {week_label}\n\n{summary}"
102
102
 
103
103
  if sprint_page:
104
- # Append to existing Sprint Log using insert_content_after
104
+ # Replace Sprint Log content with latest report
105
105
  await session.call_tool(
106
106
  "notion-update-page",
107
107
  arguments={
108
108
  "page_id": sprint_page.page_id,
109
- "command": "insert_content_after",
110
- "selection_with_ellipsis": "---\n*Auto-gen...by Codebase Cortex*",
111
- "new_str": f"\n\n---\n\n{content}",
109
+ "command": "replace_content",
110
+ "new_str": content,
112
111
  },
113
112
  )
114
- logger.info(f"Appended to Sprint Log for week of {week_label}")
113
+ logger.info(f"Updated Sprint Log for week of {week_label}")
115
114
  else:
116
115
  # Create new sprint report page
117
116
  create_args: dict = {
@@ -91,7 +91,7 @@ Respond with a JSON array of tasks (title, description, priority). Return [] if
91
91
  cache = PageCache(cache_path=settings.page_cache_path)
92
92
 
93
93
  task_board = cache.find_by_title("Task Board")
94
- parent_page = cache.find_by_title("Codebase Cortex")
94
+ parent_page = cache.find_by_title(settings.repo_path.name)
95
95
  parent_id = (task_board or parent_page)
96
96
  parent_id = parent_id.page_id if parent_id else None
97
97
 
@@ -314,7 +314,7 @@ def run(once: bool, watch: bool, dry_run: bool, full: bool, verbose: bool) -> No
314
314
  if arch_page and arch_page.content_hash == "":
315
315
  # Pages exist but were never written with real content
316
316
  # Check if this looks like a first run after init
317
- doc_pages = cache.find_all_doc_pages()
317
+ doc_pages = cache.find_all_doc_pages(parent_title=settings.repo_path.name)
318
318
  all_empty = all(p.content_hash == "" for p in doc_pages)
319
319
  if all_empty:
320
320
  console.print("[cyan]First run detected — doing full codebase scan[/cyan]")
@@ -624,7 +624,7 @@ async def _run_prompt(
624
624
  from codebase_cortex.utils.section_parser import merge_sections, parse_sections
625
625
 
626
626
  cache = PageCache(cache_path=settings.page_cache_path)
627
- doc_pages = cache.find_all_doc_pages()
627
+ doc_pages = cache.find_all_doc_pages(parent_title=settings.repo_path.name)
628
628
 
629
629
  if not doc_pages:
630
630
  console.print("[red]No pages in cache. Run 'cortex run --once' first.[/red]")
@@ -10,7 +10,9 @@ from codebase_cortex.utils.logging import get_logger
10
10
 
11
11
  logger = get_logger()
12
12
 
13
- PARENT_PAGE_TITLE = "Codebase Cortex"
13
+ def get_parent_page_title(settings: Settings) -> str:
14
+ """Return the parent page title for a given repo (the repo directory name)."""
15
+ return settings.repo_path.name
14
16
 
15
17
 
16
18
  def normalize_page_id(raw_id: str) -> str:
@@ -119,7 +121,8 @@ async def discover_child_pages(settings: Settings) -> int:
119
121
 
120
122
  logger = get_logger()
121
123
  cache = PageCache(cache_path=settings.page_cache_path)
122
- parent_page = cache.find_by_title("Codebase Cortex")
124
+ parent_title = get_parent_page_title(settings)
125
+ parent_page = cache.find_by_title(parent_title)
123
126
  if not parent_page:
124
127
  return 0
125
128
 
@@ -187,7 +190,7 @@ async def discover_child_pages(settings: Settings) -> int:
187
190
  async def bootstrap_notion_pages(settings: Settings) -> list[dict]:
188
191
  """Create the starter Notion pages via MCP tools.
189
192
 
190
- Creates a parent "Codebase Cortex" page, then child pages under it.
193
+ Creates a parent page named after the repo directory, then child pages under it.
191
194
  Searches for existing pages first to avoid duplicates.
192
195
  Seeds the page cache with all created/found pages.
193
196
 
@@ -209,7 +212,7 @@ async def bootstrap_notion_pages(settings: Settings) -> list[dict]:
209
212
 
210
213
  async with notion_mcp_session(settings) as session:
211
214
  # Step 1: Search for existing parent page
212
- parent_id = await search_page_by_title(session, PARENT_PAGE_TITLE)
215
+ parent_id = await search_page_by_title(session, parent_title)
213
216
 
214
217
  # Step 2: Create parent page if not found
215
218
  if not parent_id:
@@ -232,7 +235,7 @@ async def bootstrap_notion_pages(settings: Settings) -> list[dict]:
232
235
  )
233
236
  parent_id = extract_page_id(result)
234
237
  if parent_id:
235
- cache.upsert(parent_id, PARENT_PAGE_TITLE)
238
+ cache.upsert(parent_id, parent_title)
236
239
  logger.info(f"Created parent page: {parent_title}")
237
240
  else:
238
241
  logger.error("Failed to extract parent page ID from response")
@@ -241,28 +244,24 @@ async def bootstrap_notion_pages(settings: Settings) -> list[dict]:
241
244
  logger.error(f"Failed to create parent page: {e}")
242
245
  return []
243
246
  else:
244
- cache.upsert(parent_id, PARENT_PAGE_TITLE)
245
- logger.info(f"Found existing parent page: {PARENT_PAGE_TITLE}")
247
+ cache.upsert(parent_id, parent_title)
248
+ logger.info(f"Found existing parent page: {parent_title}")
246
249
 
247
250
  # Step 3: Create child pages under parent
248
251
  for page_info in STARTER_PAGES:
249
252
  title = page_info["title"]
250
253
  display_title = f"{page_info['icon']} {title}"
251
254
 
252
- # Check cache first, then search Notion
255
+ # Only check cache don't search the workspace, as that could
256
+ # find pages under a different parent from a previous init.
257
+ # Users adopt existing pages by moving them under the parent
258
+ # and running `cortex scan`.
253
259
  cached = cache.find_by_title(title)
254
260
  if cached:
255
261
  pages.append({"title": title, "page_id": cached.page_id})
256
262
  logger.info(f"Already exists (cached): {display_title}")
257
263
  continue
258
264
 
259
- existing_id = await search_page_by_title(session, title)
260
- if existing_id:
261
- cache.upsert(existing_id, title)
262
- pages.append({"title": title, "page_id": existing_id})
263
- logger.info(f"Found existing: {display_title}")
264
- continue
265
-
266
265
  # Create new page under parent
267
266
  await rate_limiter.acquire()
268
267
  try:
@@ -99,9 +99,9 @@ class PageCache:
99
99
  return page
100
100
  return None
101
101
 
102
- def find_all_doc_pages(self) -> list[CachedPage]:
103
- """Return all cached pages except infrastructure pages."""
102
+ def find_all_doc_pages(self, parent_title: str | None = None) -> list[CachedPage]:
103
+ """Return all cached pages except the parent page."""
104
104
  return [
105
105
  p for p in self.pages.values()
106
- if p.title != "Codebase Cortex"
106
+ if parent_title is None or p.title != parent_title
107
107
  ]
@@ -41,6 +41,7 @@ async def test_discover_child_pages_no_parent(tmp_path):
41
41
 
42
42
  settings = MagicMock()
43
43
  settings.page_cache_path = cache_path
44
+ settings.repo_path.name = "my-repo"
44
45
 
45
46
  count = await discover_child_pages(settings)
46
47
  assert count == 0
@@ -53,11 +54,12 @@ async def test_discover_child_pages_finds_new(tmp_path):
53
54
 
54
55
  cache_path = tmp_path / "cache.json"
55
56
  cache = PageCache(cache_path=cache_path)
56
- cache.upsert("parent-id-0000-0000-000000000001", "Codebase Cortex")
57
+ cache.upsert("parent-id-0000-0000-000000000001", "my-repo")
57
58
 
58
59
  settings = MagicMock()
59
60
  settings.page_cache_path = cache_path
60
61
  settings.notion_token_path = tmp_path / "tokens.json"
62
+ settings.repo_path.name = "my-repo"
61
63
 
62
64
  # Mock the parent page response with a child page UUID in content
63
65
  child_id = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
@@ -114,7 +116,7 @@ async def test_discover_child_pages_skips_cached(tmp_path):
114
116
 
115
117
  cache_path = tmp_path / "cache.json"
116
118
  cache = PageCache(cache_path=cache_path)
117
- cache.upsert("parent-id-0000-0000-000000000001", "Codebase Cortex")
119
+ cache.upsert("parent-id-0000-0000-000000000001", "my-repo")
118
120
 
119
121
  child_id = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
120
122
  cache.upsert(child_id, "Already Cached Page")
@@ -122,6 +124,7 @@ async def test_discover_child_pages_skips_cached(tmp_path):
122
124
  settings = MagicMock()
123
125
  settings.page_cache_path = cache_path
124
126
  settings.notion_token_path = tmp_path / "tokens.json"
127
+ settings.repo_path.name = "my-repo"
125
128
 
126
129
  parent_response = MagicMock()
127
130
  parent_response.isError = False
@@ -36,13 +36,13 @@ def test_find_by_title_fuzzy_case(tmp_path: Path):
36
36
 
37
37
  def test_find_all_doc_pages(tmp_path: Path):
38
38
  cache = PageCache(cache_path=tmp_path / "cache.json")
39
- cache.upsert("parent", "Codebase Cortex")
39
+ cache.upsert("parent", "my-repo")
40
40
  cache.upsert("id1", "Architecture Overview")
41
41
  cache.upsert("id2", "API Reference")
42
42
 
43
- doc_pages = cache.find_all_doc_pages()
43
+ doc_pages = cache.find_all_doc_pages(parent_title="my-repo")
44
44
  titles = [p.title for p in doc_pages]
45
- assert "Codebase Cortex" not in titles
45
+ assert "my-repo" not in titles
46
46
  assert "Architecture Overview" in titles
47
47
  assert "API Reference" in titles
48
48
 
File without changes