<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sailalith Sarupuri</title>
    <description>The latest articles on DEV Community by Sailalith Sarupuri (@sarupurisailalith).</description>
    <link>https://dev.to/sarupurisailalith</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F595785%2F578e56f5-a01b-43cd-9b21-cb29e2145bf2.png</url>
      <title>DEV Community: Sailalith Sarupuri</title>
      <link>https://dev.to/sarupurisailalith</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sarupurisailalith"/>
    <language>en</language>
    <item>
      <title>I Built a Tool That Updates Your Docs Every Time You Commit Code</title>
      <dc:creator>Sailalith Sarupuri</dc:creator>
      <pubDate>Wed, 11 Mar 2026 09:19:01 +0000</pubDate>
      <link>https://dev.to/sarupurisailalith/i-built-a-tool-that-updates-your-docs-every-time-you-commit-code-4me8</link>
      <guid>https://dev.to/sarupurisailalith/i-built-a-tool-that-updates-your-docs-every-time-you-commit-code-4me8</guid>
      <description>&lt;p&gt;I recently built &lt;a href="https://github.com/sarupurisailalith/codebase-cortex" rel="noopener noreferrer"&gt;Codebase Cortex&lt;/a&gt; for the &lt;a href="https://dev.to/sarupurisailalith/commit-code-update-docs-building-an-ai-pipeline-with-notion-mcp-36he"&gt;Notion MCP Challenge&lt;/a&gt; — an AI pipeline that watches your git commits and updates your Notion docs automatically. Five agents, semantic search, section-level merging. It worked.&lt;/p&gt;

&lt;p&gt;But it bothered me that the first thing you needed was a Notion workspace. I wanted automated documentation without any platform dependency — just markdown files in my repo, version-controlled and diffable.&lt;/p&gt;

&lt;p&gt;So I rebuilt the whole thing. Version 0.2 is local-first — your docs are plain markdown files in &lt;code&gt;docs/&lt;/code&gt;. No cloud accounts, no API keys for a docs platform, no internet connection needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;p&gt;You commit code. Cortex analyzes the diff, finds related documentation via semantic search, and updates only the sections that changed. Unchanged sections stay byte-for-byte identical.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Git Commit → CodeAnalyzer → SemanticFinder → SectionRouter → DocWriter
→ DocValidator → TOCGenerator → TaskCreator → SprintReporter → docs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nine agents. Each one does one thing. The pipeline runs in the background after every commit if you install the git hook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show Me
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;codebase-cortex

&lt;span class="nb"&gt;cd &lt;/span&gt;your-project
cortex init          &lt;span class="c"&gt;# Pick your LLM, configure&lt;/span&gt;
cortex run &lt;span class="nt"&gt;--once&lt;/span&gt;    &lt;span class="c"&gt;# Docs appear in docs/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First run scans the whole codebase. After that, only diffs.&lt;/p&gt;

&lt;h3&gt;
  
  
  What you get
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;docs/
├── architecture-overview.md    # Auto-generated, section-level updates
├── api-reference.md            # Tracks your actual code, not last month's code
├── INDEX.md                    # Auto-generated table of contents
└── .cortex-meta.json           # Tracks which sections you edited by hand
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Features That Matter
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Any LLM.&lt;/strong&gt; Cortex uses &lt;a href="https://docs.litellm.ai/" rel="noopener noreferrer"&gt;LiteLLM&lt;/a&gt; — 100+ providers. Cloud or local:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Cloud&lt;/span&gt;
&lt;span class="nv"&gt;LLM_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gemini/gemini-2.5-flash-lite
&lt;span class="nv"&gt;LLM_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;anthropic/claude-sonnet-4-20250514

&lt;span class="c"&gt;# Local (no API key needed)&lt;/span&gt;
&lt;span class="nv"&gt;LLM_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ollama/llama3
&lt;span class="nv"&gt;LLM_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;hosted_vllm/my-model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Your edits are protected.&lt;/strong&gt; Manually edit a section and Cortex will never overwrite it. It tracks section hashes and skips anything you've touched.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review workflow.&lt;/strong&gt; New docs get a draft banner. Use &lt;code&gt;cortex accept&lt;/code&gt; after reviewing. Or switch to propose mode — changes stage for review before landing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cortex config &lt;span class="nb"&gt;set &lt;/span&gt;DOC_OUTPUT_MODE propose
cortex run &lt;span class="nt"&gt;--once&lt;/span&gt;     &lt;span class="c"&gt;# Stages to .cortex/proposed/&lt;/span&gt;
cortex diff           &lt;span class="c"&gt;# Review&lt;/span&gt;
cortex apply          &lt;span class="c"&gt;# Ship it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;CI/CD.&lt;/strong&gt; Run in GitHub Actions or GitLab CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# In your CI pipeline&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cortex ci --on-pr&lt;/span&gt;              &lt;span class="c1"&gt;# PR impact analysis (JSON output)&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cortex ci --on-merge --auto-apply&lt;/span&gt;  &lt;span class="c1"&gt;# Auto-update on merge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Semantic search, not filename matching.&lt;/strong&gt; When you change &lt;code&gt;auth/login.py&lt;/code&gt;, Cortex finds related docs in your API reference, architecture overview, and getting started guide — via FAISS embeddings, not string matching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Natural language control.&lt;/strong&gt; For when you want to direct updates yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cortex prompt &lt;span class="s2"&gt;"Add error handling examples to the API docs"&lt;/span&gt;
cortex prompt &lt;span class="s2"&gt;"Expand the authentication section"&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Architecture"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Full Command Set
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cortex init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Setup wizard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cortex run --once [--full]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run the pipeline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cortex accept&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clear draft banners&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cortex prompt "..."&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Natural language doc updates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cortex check&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Find stale docs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cortex diff / apply / discard&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Review workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cortex sync --target notion&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Push to Notion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cortex ci --on-pr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CI/CD mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cortex embed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rebuild search index&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cortex map&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Visualize codebase clusters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cortex config show&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;View/update settings&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Want to Contribute?
&lt;/h2&gt;

&lt;p&gt;The repo is open source (MIT), and there are several ways to get involved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Try it on your project&lt;/strong&gt; and &lt;a href="https://github.com/sarupurisailalith/codebase-cortex/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; with what breaks or what's missing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a new pipeline node&lt;/strong&gt; — the LangGraph architecture makes it straightforward to add agents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improve chunking&lt;/strong&gt; — TreeSitter support for more languages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build a new backend&lt;/strong&gt; — the &lt;code&gt;DocBackend&lt;/code&gt; protocol is pluggable (Confluence? GitHub Wiki?)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://github.com/sarupurisailalith/codebase-cortex/blob/main/docs/contributing.md" rel="noopener noreferrer"&gt;contributing guide&lt;/a&gt; has the full setup.&lt;/p&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;codebase-cortex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/sarupurisailalith/codebase-cortex" rel="noopener noreferrer"&gt;sarupurisailalith/codebase-cortex&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;PyPI:&lt;/strong&gt; &lt;a href="https://pypi.org/project/codebase-cortex/" rel="noopener noreferrer"&gt;codebase-cortex&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://github.com/sarupurisailalith/codebase-cortex/tree/main/docs" rel="noopener noreferrer"&gt;Full documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've dealt with stale docs on a project, give it a shot and let me know how it goes.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>documentation</category>
    </item>
    <item>
      <title>Commit Code, Update Docs: Building an AI Pipeline With Notion MCP</title>
      <dc:creator>Sailalith Sarupuri</dc:creator>
      <pubDate>Mon, 09 Mar 2026 05:54:30 +0000</pubDate>
      <link>https://dev.to/sarupurisailalith/commit-code-update-docs-building-an-ai-pipeline-with-notion-mcp-36he</link>
      <guid>https://dev.to/sarupurisailalith/commit-code-update-docs-building-an-ai-pipeline-with-notion-mcp-36he</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/notion-2026-03-04"&gt;Notion MCP Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Codebase Cortex&lt;/strong&gt; is a multi-agent AI system that automatically keeps engineering documentation in sync with code. You commit code — your Notion docs update themselves.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not just use Claude Code / Cursor / Copilot for docs?
&lt;/h3&gt;

&lt;p&gt;Coding AI tools are great at writing documentation — when you prompt them. The problem is you have to keep prompting. Every commit, you need to remember to ask, check if the right pages were updated, and verify nothing was overwritten. It's still a manual process — just with better writing.&lt;/p&gt;

&lt;p&gt;Cortex is different because it's &lt;strong&gt;autonomous&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Coding AI Tools&lt;/th&gt;
&lt;th&gt;Codebase Cortex&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trigger&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You prompt it manually&lt;/td&gt;
&lt;td&gt;Runs automatically on git commit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scope&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Updates what you point it to&lt;/td&gt;
&lt;td&gt;Finds related docs via semantic search across the entire codebase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Consistency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Different output each time&lt;/td&gt;
&lt;td&gt;Deterministic section-level merge — unchanged sections stay identical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Forgets between sessions&lt;/td&gt;
&lt;td&gt;FAISS index + page cache persist across runs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-page&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You manage which pages to update&lt;/td&gt;
&lt;td&gt;Agents discover which pages need updating&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tracking&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No audit trail&lt;/td&gt;
&lt;td&gt;Sprint reports, task creation, content hashes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Workflow&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Interactive (needs your attention)&lt;/td&gt;
&lt;td&gt;Background process (runs after commit, logs results)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Think of it like the difference between manually running a linter versus having it in your CI pipeline. Both do the same job — only one actually happens consistently.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Agents
&lt;/h3&gt;

&lt;p&gt;The system uses five specialized LangGraph agents that work in sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;CodeAnalyzer&lt;/strong&gt; — Reads git diffs and produces a structured analysis of what changed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SemanticFinder&lt;/strong&gt; — Searches a FAISS vector index to find semantically related code across the entire codebase&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DocWriter&lt;/strong&gt; — Fetches current Notion pages via MCP, generates section-level updates, and merges them deterministically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TaskCreator&lt;/strong&gt; — Identifies undocumented areas and creates task pages in Notion&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SprintReporter&lt;/strong&gt; — Generates weekly sprint summaries and appends them to a Sprint Log page&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automatic doc sync&lt;/strong&gt; — Post-commit git hook triggers the pipeline with zero human effort&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Section-level updates&lt;/strong&gt; — Only changed sections are rewritten; unchanged sections are preserved exactly as-is&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic search&lt;/strong&gt; — FAISS embeddings (all-MiniLM-L6-v2, 384-dim) find related code, not just filename matches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Natural language prompts&lt;/strong&gt; — &lt;code&gt;cortex prompt "Make the API docs more detailed"&lt;/code&gt; for directed updates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-page intelligence&lt;/strong&gt; — Agents understand relationships across all doc pages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirmation flow&lt;/strong&gt; — Shows planned changes and asks for approval before writing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pluggable LLMs&lt;/strong&gt; — Works with Google Gemini, Anthropic Claude, or any OpenRouter model&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Published on PyPI&lt;/strong&gt; — &lt;code&gt;pip install codebase-cortex&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Pipeline
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Git Commit
    ↓
CodeAnalyzer (parse diff, LLM analysis)
    ↓
SemanticFinder (embed analysis, FAISS search for top-10 related chunks)
    ↓
DocWriter (fetch Notion pages via MCP → LLM generates section_updates → deterministic merge → write via MCP)
    ↓
TaskCreator (LLM identifies gaps → create task pages in Notion via MCP)
    ↓
SprintReporter (LLM generates summary → append to Sprint Log via MCP)
    ↓
Notion Workspace (updated docs, new tasks, sprint report)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Section-Level Merging (the core innovation)
&lt;/h3&gt;

&lt;p&gt;Instead of rewriting entire pages (which causes "LLM drift"), DocWriter:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetches the existing page from Notion via &lt;code&gt;notion-fetch&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Parses it into sections split by markdown headings&lt;/li&gt;
&lt;li&gt;Asks the LLM to return ONLY the sections that changed&lt;/li&gt;
&lt;li&gt;Merges the changes into the existing structure deterministically&lt;/li&gt;
&lt;li&gt;Writes the full merged content back via &lt;code&gt;notion-update-page&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This means a page with 10 sections where 1 section needs updating gets exactly 1 section changed. The other 9 sections are byte-for-byte identical to before.&lt;/p&gt;

&lt;h2&gt;
  
  
  Video Demo
&lt;/h2&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/yIHAjPXFIFU"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install and try it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install the challenge submission version (v0.1.4 — Notion MCP as the core backend)&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;codebase-cortex&lt;span class="o"&gt;==&lt;/span&gt;0.1.4

&lt;span class="nb"&gt;cd &lt;/span&gt;your-project
cortex init        &lt;span class="c"&gt;# Interactive setup (LLM, Notion OAuth, starter pages)&lt;/span&gt;
cortex run &lt;span class="nt"&gt;--once&lt;/span&gt;  &lt;span class="c"&gt;# Run the full pipeline&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note for judges:&lt;/strong&gt; This submission uses &lt;strong&gt;v0.1.4&lt;/strong&gt;, where Notion MCP is the central and only documentation backend — every read and write goes through MCP. A newer version (v0.2+) adds local-first markdown output as an alternative, but v0.1.4 is the version built specifically around Notion MCP as described in this post.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Project structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;codebase-cortex/
├── src/codebase_cortex/
│   ├── cli.py              # Click CLI (init, run, status, prompt, scan, embed)
│   ├── config.py            # Settings, get_llm() factory
│   ├── state.py             # CortexState TypedDict (shared pipeline state)
│   ├── graph.py             # LangGraph StateGraph with conditional routing
│   ├── mcp_client.py        # Notion MCP connection (Streamable HTTP + OAuth)
│   ├── agents/
│   │   ├── base.py          # BaseAgent ABC with LLM invocation
│   │   ├── code_analyzer.py # Git diff / full codebase analysis
│   │   ├── semantic_finder.py # FAISS similarity search
│   │   ├── doc_writer.py    # Section-level Notion page updates
│   │   ├── task_creator.py  # Task page creation
│   │   └── sprint_reporter.py # Sprint summary generation
│   ├── auth/                # OAuth 2.0 + PKCE for Notion
│   ├── embeddings/          # sentence-transformers, FAISS, HDBSCAN
│   ├── git/                 # Diff parsing, GitHub client
│   ├── notion/              # Page bootstrap and cache
│   └── utils/               # Rate limiter, section parser, JSON parsing
├── tests/                   # 74 tests
├── docs/                    # Full documentation with mermaid diagrams
└── pyproject.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key technical decisions
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LangGraph StateGraph&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Conditional routing — skip agents when no changes detected, skip sprint report when nothing to report&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;FAISS IndexFlatL2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Exact nearest-neighbor search, no training required, sub-millisecond for medium codebases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Section-level merging&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deterministic merge prevents LLM drift; unchanged sections preserved exactly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;sentence-transformers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;all-MiniLM-L6-v2 is fast, CPU-friendly, 384-dim — good balance of quality and speed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HDBSCAN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Auto-determines cluster count, handles noise — better than k-means for code topology&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dual rate limiter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Async token bucket (180 req/min general + 30 req/min search) matches Notion's limits exactly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OAuth 2.0 + PKCE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Browser-based auth with dynamic client registration — zero manual token setup&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How I Used Notion MCP
&lt;/h2&gt;

&lt;p&gt;Codebase Cortex uses Notion MCP as its &lt;strong&gt;primary interface to Notion&lt;/strong&gt; — every read and write goes through MCP. The system connects via &lt;strong&gt;Streamable HTTP&lt;/strong&gt; to &lt;code&gt;https://mcp.notion.com/mcp&lt;/code&gt; with OAuth bearer token authentication.&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP tools used and how
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;notion-fetch&lt;/code&gt;&lt;/strong&gt; — Read page content&lt;/p&gt;

&lt;p&gt;Used by DocWriter to fetch current page content before generating updates. Also used during init to detect page renames and sync titles back to the local cache.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notion-fetch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;page_id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;strip_notion_metadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;notion-update-page&lt;/code&gt;&lt;/strong&gt; — Write page updates&lt;/p&gt;

&lt;p&gt;Used by DocWriter to write section-merged content, and by SprintReporter to append sprint summaries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Replace entire page content (after local section merge)
&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notion-update-page&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;page_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;page_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;command&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace_content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new_str&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;merged_content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Append to existing page (sprint reports)
&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notion-update-page&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;page_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;page_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;command&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;insert_content_after&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new_str&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sprint_report&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;notion-create-pages&lt;/code&gt;&lt;/strong&gt; — Create new pages&lt;/p&gt;

&lt;p&gt;Used by DocWriter for new documentation pages, TaskCreator for task pages, and the bootstrap process for starter pages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notion-create-pages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API Reference&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;# API Reference&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;page_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;parent_id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;notion-search&lt;/code&gt;&lt;/strong&gt; — Search workspace&lt;/p&gt;

&lt;p&gt;Used by the &lt;code&gt;cortex scan&lt;/code&gt; command to discover existing pages and by the page discovery process to find new child pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Notion MCP unlocks
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Zero-setup Notion access&lt;/strong&gt; — OAuth 2.0 with PKCE means users just click "Allow" in their browser. No manual integration creation, no API token copying, no Notion developer portal.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI-native page interaction&lt;/strong&gt; — MCP returns content in markdown format, which is exactly what LLMs work best with. No HTML parsing, no block-level API complexity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Section-level updates&lt;/strong&gt; — The &lt;code&gt;replace_content&lt;/code&gt; command lets us write full merged content after doing section-level merging locally. This gives us deterministic control over what changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stateless connection&lt;/strong&gt; — Each pipeline run opens a fresh MCP session with auto-refreshed tokens. No persistent connection to manage, no websocket heartbeats.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rate-limit friendly&lt;/strong&gt; — The dual token bucket (180/min general, 30/min search) ensures we never hit Notion's limits, even when updating multiple pages in a single pipeline run.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Handling Notion MCP quirks
&lt;/h3&gt;

&lt;p&gt;One non-obvious challenge: the &lt;code&gt;notion-fetch&lt;/code&gt; tool returns page content with &lt;strong&gt;literal &lt;code&gt;\n&lt;/code&gt;&lt;/strong&gt; (backslash + n) instead of real newline characters. This broke section parsing until we added an unescape step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_unescape_notion_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Convert literal &lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;n and &lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;t from Notion MCP to real characters.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;next_char&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;next_char&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;next_char&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is applied before any content parsing, making the MCP encoding transparent to the rest of the system.&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP integration architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cortex run --once
    ↓
[Agents run, produce doc_updates]
    ↓
DocWriter opens MCP session (OAuth token auto-refresh)
    ↓
For each page to update:
    → rate_limiter.acquire()
    → notion-fetch (read current content)
    → _unescape_notion_text() + strip_notion_metadata()
    → parse_sections() + LLM generates section_updates
    → merge_sections() (deterministic)
    → rate_limiter.acquire()
    → notion-update-page (replace_content)
    → page_cache.upsert() (update content hash)
    ↓
MCP session closes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every MCP call goes through an async rate limiter and a &lt;code&gt;LoggingSession&lt;/code&gt; wrapper that logs tool names, arguments, and response previews when verbose mode is enabled.&lt;/p&gt;

</description>
      <category>notionchallenge</category>
      <category>ai</category>
      <category>devchallenge</category>
      <category>mcp</category>
    </item>
  </channel>
</rss>
