1390 lines
59 KiB
HTML
1390 lines
59 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Graphify-rs 深度分析报告</title>
|
||
<style>
|
||
:root {
|
||
--color-primary: #1677FF;
|
||
--color-primary-hover: #4096FF;
|
||
--color-primary-active: #0958D9;
|
||
--color-primary-bg: rgba(22, 119, 255, 0.1);
|
||
--color-success: #52C41A;
|
||
--color-warning: #FAAD14;
|
||
--color-error: #FF4D4F;
|
||
|
||
--bg: #0B0F1A;
|
||
--bg-container: #111827;
|
||
--bg-elevated: #1A2235;
|
||
--border: #1E2A3A;
|
||
--border-light: #151D2E;
|
||
--text-primary: #E8ECF1;
|
||
--text-secondary: #8892A4;
|
||
--text-tertiary: #5A6478;
|
||
|
||
--radius-sm: 6px;
|
||
--radius-md: 8px;
|
||
|
||
--shadow-sm: 0 1px 2px rgba(0,0,0,0.15);
|
||
--shadow-md: 0 2px 4px rgba(0,0,0,0.2), 0 4px 12px -2px rgba(0,0,0,0.2);
|
||
|
||
--sidebar-w: 260px;
|
||
--header-h: 64px;
|
||
|
||
--gradient1: linear-gradient(135deg, #1677FF, #52C41A);
|
||
--gradient2: linear-gradient(135deg, #722ED1, #EB2F96);
|
||
--gradient3: linear-gradient(135deg, #FAAD14, #FF4D4F);
|
||
}
|
||
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Segoe UI', 'Noto Sans', sans-serif;
|
||
background: var(--bg);
|
||
color: var(--text-primary);
|
||
line-height: 1.57;
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* Scrollbar */
|
||
::-webkit-scrollbar { width: 6px; height: 6px; }
|
||
::-webkit-scrollbar-track { background: transparent; }
|
||
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
||
::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); }
|
||
|
||
/* Header */
|
||
.header {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: var(--header-h);
|
||
background: rgba(11, 15, 26, 0.85);
|
||
backdrop-filter: blur(12px);
|
||
border-bottom: 1px solid var(--border);
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 1.5rem;
|
||
z-index: 100;
|
||
}
|
||
.header h1 {
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
background: var(--gradient1);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
}
|
||
.header .badges {
|
||
margin-left: auto;
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
}
|
||
.header .badge {
|
||
padding: 0.2rem 0.6rem;
|
||
border-radius: 999px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
border: 1px solid var(--border);
|
||
background: var(--bg-container);
|
||
color: var(--text-secondary);
|
||
}
|
||
.header .badge.hot { border-color: var(--color-error); color: var(--color-error); }
|
||
.header .badge.green { border-color: var(--color-success); color: var(--color-success); }
|
||
.header .badge.purple { border-color: #722ED1; color: #722ED1; }
|
||
|
||
/* Sidebar */
|
||
.sidebar {
|
||
position: fixed;
|
||
top: var(--header-h);
|
||
left: 0;
|
||
bottom: 0;
|
||
width: var(--sidebar-w);
|
||
background: var(--bg-container);
|
||
border-right: 1px solid var(--border);
|
||
overflow-y: auto;
|
||
padding: 1.25rem 0;
|
||
z-index: 90;
|
||
}
|
||
.sidebar .toc-label {
|
||
font-size: 12px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.1em;
|
||
color: var(--text-secondary);
|
||
padding: 0 1.25rem;
|
||
margin-bottom: 0.75rem;
|
||
}
|
||
.sidebar nav ol {
|
||
list-style: none;
|
||
counter-reset: toc;
|
||
padding: 0;
|
||
}
|
||
.sidebar nav li {
|
||
counter-increment: toc;
|
||
padding: 0;
|
||
}
|
||
.sidebar nav a {
|
||
display: block;
|
||
padding: 0.45rem 1.25rem;
|
||
color: var(--text-secondary);
|
||
text-decoration: none;
|
||
font-size: 14px;
|
||
line-height: 1.4;
|
||
border-left: 2px solid transparent;
|
||
transition: all 0.15s;
|
||
}
|
||
.sidebar nav a::before {
|
||
content: counter(toc, decimal-leading-zero) ".";
|
||
color: var(--color-primary);
|
||
font-weight: 600;
|
||
font-size: 12px;
|
||
margin-right: 0.5rem;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.sidebar nav a:hover {
|
||
color: var(--text-primary);
|
||
background: var(--color-primary-bg);
|
||
}
|
||
.sidebar nav a.active {
|
||
color: var(--color-primary);
|
||
border-left-color: var(--color-primary);
|
||
background: rgba(22, 119, 255, 0.08);
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* Main content */
|
||
.main {
|
||
margin-left: var(--sidebar-w);
|
||
margin-top: var(--header-h);
|
||
padding: 2rem 2.5rem 4rem;
|
||
max-width: calc(960px + var(--sidebar-w));
|
||
min-height: calc(100vh - var(--header-h));
|
||
}
|
||
|
||
/* Section content panels */
|
||
.section {
|
||
margin-bottom: 3.5rem;
|
||
scroll-margin-top: calc(var(--header-h) + 1rem);
|
||
}
|
||
.section h2 {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
margin-bottom: 1.25rem;
|
||
padding-bottom: 0.5rem;
|
||
border-bottom: 1px solid var(--border);
|
||
color: var(--text-primary);
|
||
}
|
||
.section h3 {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
margin: 1.5rem 0 0.75rem;
|
||
color: var(--color-primary);
|
||
}
|
||
.section h4 {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
margin: 1.25rem 0 0.5rem;
|
||
color: var(--color-warning);
|
||
}
|
||
.section p { margin-bottom: 1rem; }
|
||
.section ul, .section ol { margin: 0.5rem 0 1rem 1.5rem; }
|
||
.section li { margin-bottom: 0.25rem; }
|
||
.section strong { color: var(--text-primary); }
|
||
|
||
/* Cards */
|
||
.card-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||
gap: 1rem;
|
||
margin: 1rem 0;
|
||
}
|
||
.card {
|
||
background: var(--bg-container);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
padding: 1.25rem;
|
||
transition: border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
|
||
}
|
||
.card:hover {
|
||
border-color: var(--color-primary);
|
||
transform: translateY(-2px);
|
||
box-shadow: var(--shadow-md);
|
||
}
|
||
.card .card-title {
|
||
font-weight: 700;
|
||
font-size: 14px;
|
||
margin-bottom: 0.5rem;
|
||
color: var(--color-primary);
|
||
}
|
||
.card .card-desc {
|
||
font-size: 14px;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
/* Tables */
|
||
.table-wrap {
|
||
overflow-x: auto;
|
||
margin: 1rem 0;
|
||
border-radius: var(--radius-md);
|
||
border: 1px solid var(--border);
|
||
}
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 14px;
|
||
}
|
||
thead { background: var(--bg-container); }
|
||
th {
|
||
padding: 0.75rem 1rem;
|
||
text-align: left;
|
||
font-weight: 600;
|
||
color: var(--color-primary);
|
||
border-bottom: 1px solid var(--border);
|
||
white-space: nowrap;
|
||
}
|
||
td {
|
||
padding: 0.65rem 1rem;
|
||
border-bottom: 1px solid var(--border);
|
||
color: var(--text-secondary);
|
||
}
|
||
tr:last-child td { border-bottom: none; }
|
||
tr:hover td { background: var(--color-primary-bg); }
|
||
td code {
|
||
background: var(--bg-elevated);
|
||
padding: 0.15rem 0.4rem;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
color: #722ED1;
|
||
font-family: 'SF Mono', 'Fira Code', monospace;
|
||
}
|
||
|
||
/* Code */
|
||
pre {
|
||
background: var(--bg-elevated);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
padding: 1.25rem 1.5rem;
|
||
overflow-x: auto;
|
||
margin: 1rem 0;
|
||
font-size: 14px;
|
||
line-height: 1.6;
|
||
}
|
||
code {
|
||
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
|
||
}
|
||
p code, li code {
|
||
background: var(--color-primary-bg);
|
||
padding: 0.15rem 0.4rem;
|
||
border-radius: 4px;
|
||
font-size: 0.85em;
|
||
color: #722ED1;
|
||
}
|
||
pre code { color: var(--text-secondary); background: none; padding: 0; border-radius: 0; }
|
||
|
||
/* Syntax highlight */
|
||
.comment { color: #6a737d; }
|
||
.string { color: #a5d6ff; }
|
||
.keyword { color: #ff7b72; }
|
||
.builtin { color: #79c0ff; }
|
||
.func { color: #d2a8ff; }
|
||
.number { color: #79c0ff; }
|
||
|
||
/* Blockquote */
|
||
blockquote {
|
||
border-left: 3px solid var(--color-primary);
|
||
background: var(--color-primary-bg);
|
||
padding: 1rem 1.25rem;
|
||
margin: 1rem 0;
|
||
border-radius: 0 var(--radius-md) var(--radius-md) 0;
|
||
color: var(--text-secondary);
|
||
font-style: italic;
|
||
}
|
||
|
||
/* Diagram */
|
||
.diagram {
|
||
background: var(--bg-container);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
padding: 1.5rem;
|
||
margin: 1.25rem 0;
|
||
font-family: 'SF Mono', 'Fira Code', monospace;
|
||
font-size: 14px;
|
||
line-height: 1.8;
|
||
color: var(--text-secondary);
|
||
text-align: center;
|
||
overflow-x: auto;
|
||
}
|
||
.diagram .label { color: var(--color-primary); font-weight: 600; }
|
||
.diagram .arrow { color: var(--color-success); }
|
||
.diagram .node { color: #722ED1; }
|
||
|
||
/* Metrics */
|
||
.metrics {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||
gap: 1rem;
|
||
margin: 1.25rem 0;
|
||
}
|
||
.metric {
|
||
background: var(--bg-container);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
padding: 1.25rem;
|
||
text-align: center;
|
||
}
|
||
.metric .value {
|
||
font-size: 2rem;
|
||
font-weight: 800;
|
||
background: var(--gradient1);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
}
|
||
.metric .value.green {
|
||
background: var(--gradient1);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
}
|
||
.metric .value.purple {
|
||
background: var(--gradient2);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
}
|
||
.metric .label {
|
||
font-size: 12px;
|
||
color: var(--text-secondary);
|
||
margin-top: 0.25rem;
|
||
}
|
||
|
||
/* Alert */
|
||
.alert {
|
||
border-radius: var(--radius-md);
|
||
padding: 1rem 1.25rem;
|
||
margin: 1rem 0;
|
||
font-size: 14px;
|
||
}
|
||
.alert.info {
|
||
background: var(--color-primary-bg);
|
||
border: 1px solid rgba(22, 119, 255, 0.3);
|
||
color: var(--color-primary);
|
||
}
|
||
.alert.warn {
|
||
background: rgba(250, 173, 20, 0.08);
|
||
border: 1px solid rgba(250, 173, 20, 0.3);
|
||
color: var(--color-warning);
|
||
}
|
||
.alert.success {
|
||
background: rgba(82, 196, 26, 0.08);
|
||
border: 1px solid rgba(82, 196, 26, 0.3);
|
||
color: var(--color-success);
|
||
}
|
||
.alert.error {
|
||
background: rgba(255, 77, 79, 0.08);
|
||
border: 1px solid rgba(255, 77, 79, 0.3);
|
||
color: var(--color-error);
|
||
}
|
||
|
||
/* Step list */
|
||
.steps { counter-reset: step; list-style: none; margin-left: 0; }
|
||
.steps li {
|
||
counter-increment: step;
|
||
padding: 0.75rem 0 0.75rem 3rem;
|
||
position: relative;
|
||
border-left: 1px solid var(--border);
|
||
margin-left: 1rem;
|
||
}
|
||
.steps li::before {
|
||
content: counter(step);
|
||
position: absolute;
|
||
left: -0.75rem;
|
||
top: 0.65rem;
|
||
width: 1.5rem;
|
||
height: 1.5rem;
|
||
background: var(--color-primary);
|
||
color: var(--bg);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: 700;
|
||
font-size: 12px;
|
||
}
|
||
.steps li:last-child { border-left-color: transparent; }
|
||
|
||
/* Footer */
|
||
.footer {
|
||
text-align: center;
|
||
padding: 2rem 1.5rem;
|
||
border-top: 1px solid var(--border);
|
||
color: var(--text-tertiary);
|
||
font-size: 14px;
|
||
}
|
||
.footer a {
|
||
color: var(--color-primary);
|
||
text-decoration: none;
|
||
}
|
||
.footer a:hover { text-decoration: underline; }
|
||
|
||
/* Link */
|
||
a { color: var(--color-primary); }
|
||
|
||
/* Verdict / rec-box style gradient backgrounds */
|
||
.verdict, .rec-box {
|
||
background: linear-gradient(135deg, rgba(22, 119, 255, 0.08), rgba(114, 46, 209, 0.08));
|
||
}
|
||
|
||
/* Responsive */
|
||
@media (max-width: 992px) {
|
||
:root { --sidebar-w: 220px; }
|
||
.main { padding: 1.5rem 1.25rem 3rem; }
|
||
.header .badge { display: none; }
|
||
}
|
||
@media (max-width: 768px) {
|
||
.sidebar { display: none; }
|
||
.main { margin-left: 0; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- Header -->
|
||
<header class="header">
|
||
<h1>Graphify-rs 深度分析报告</h1>
|
||
<div class="badges">
|
||
<span class="badge hot">22K+ Stars</span>
|
||
<span class="badge green">71x Token 节省</span>
|
||
<span class="badge purple">25 种语言</span>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Sidebar -->
|
||
<aside class="sidebar">
|
||
<div class="toc-label">目录</div>
|
||
<nav>
|
||
<ol>
|
||
<li><a href="#overview">Graphify 是什么?</a></li>
|
||
<li><a href="#architecture">架构与技术栈</a></li>
|
||
<li><a href="#install">全功能安装指南</a></li>
|
||
<li><a href="#cli">CLI 命令全览</a></li>
|
||
<li><a href="#claude">与 Claude Code 配合最佳实践</a></li>
|
||
<li><a href="#new-project">新建项目最佳实践</a></li>
|
||
<li><a href="#second-dev">二次开发最佳实践</a></li>
|
||
<li><a href="#bug-fix">Bug 修正最佳实践</a></li>
|
||
<li><a href="#vscode">VSCode 最佳实践</a></li>
|
||
<li><a href="#ogham-intro">Ogham MCP 是什么?</a></li>
|
||
<li><a href="#ogham-install">Ogham MCP 安装与配置</a></li>
|
||
<li><a href="#ogham-tools">Ogham 核心工具详解</a></li>
|
||
<li><a href="#ogham-hooks">Ogham Hooks:记忆跨压缩</a></li>
|
||
<li><a href="#ogham-combo">Graphify + Ogham MCP 组合实战</a></li>
|
||
<li><a href="#config">完整配置清单</a></li>
|
||
<li><a href="#summary">总结:何时该用?</a></li>
|
||
</ol>
|
||
</nav>
|
||
</aside>
|
||
|
||
<!-- Main content -->
|
||
<main class="main">
|
||
|
||
<!-- Section 1: Overview -->
|
||
<div class="section" id="overview">
|
||
<h2>一、Graphify 是什么?</h2>
|
||
<p><strong>Graphify</strong> 是一个开源知识图谱工具,专为 AI 编程助手(Claude Code、Cursor、Gemini CLI 等)设计。它能将代码库、文档、论文、图片转化为 <strong>可查询的结构化知识图谱</strong>。</p>
|
||
|
||
<div class="metrics">
|
||
<div class="metric">
|
||
<div class="value green">71x</div>
|
||
<div class="label">Token 节省比</div>
|
||
</div>
|
||
<div class="metric">
|
||
<div class="value purple">22K+</div>
|
||
<div class="label">GitHub Stars</div>
|
||
</div>
|
||
<div class="metric">
|
||
<div class="value">25</div>
|
||
<div class="label">编程语言支持</div>
|
||
</div>
|
||
<div class="metric">
|
||
<div class="value green">48h</div>
|
||
<div class="label">从概念到产品</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card-grid">
|
||
<div class="card">
|
||
<div class="card-title">解决上下文膨胀</div>
|
||
<div class="card-desc">避免 AI 每次对话都扫描整个代码库,一次性建图后反复查询</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-title">解决 AI 失忆</div>
|
||
<div class="card-desc">图谱持久化到磁盘,跨会话保留项目理解,换台电脑也能继续</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-title">本地运行</div>
|
||
<div class="card-desc">代码不离开本机,tree-sitter AST 解析,零数据外泄风险</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-title">多模态处理</div>
|
||
<div class="card-desc">不仅能解析代码,还能处理 PDF、图片、手写笔记等多种文件类型</div>
|
||
</div>
|
||
</div>
|
||
|
||
<p><strong>灵感来源:</strong>Andrej Karpathy 分享的个人知识库工作流,开源社区在 48 小时内将其工程化落地。Rust 重写版由 TtTRz 维护,主打高性能生产环境使用。</p>
|
||
|
||
<p><strong>Rust 仓库地址:</strong><a href="https://github.com/TtTRz/graphify-rs">github.com/TtTRz/graphify-rs</a> | <strong>Crates.io:</strong><a href="https://crates.io/crates/graphify-rs">crates.io/crates/graphify-rs</a></p>
|
||
</div>
|
||
|
||
<!-- Section 2: Architecture -->
|
||
<div class="section" id="architecture">
|
||
<h2>二、架构与技术栈</h2>
|
||
|
||
<h3>2.1 Rust Crate 架构</h3>
|
||
<div class="diagram">
|
||
<span class="label">graphify-rs</span> (CLI) ──<span class="arrow">──┬──</span>── <span class="node">graphify-build</span> (构建/去重/社区检测)
|
||
│
|
||
├── <span class="node">graphify-core</span> (核心数据结构)
|
||
│
|
||
├── <span class="node">graphify-extract</span> (AST 提取)
|
||
│
|
||
└── <span class="node">graphify-watch</span> (文件监听)
|
||
</div>
|
||
|
||
<h3>2.2 双轨策略(Dual-Track)</h3>
|
||
|
||
<div class="card-grid">
|
||
<div class="card" style="border-color: rgba(63,185,80,0.3);">
|
||
<div class="card-title" style="color: var(--color-success);">确定性轨道</div>
|
||
<div class="card-desc">
|
||
<code>tree-sitter</code> AST 解 → 提取类/函数/继承/导入关系 → 精准、零幻觉
|
||
</div>
|
||
</div>
|
||
<div class="card" style="border-color: rgba(188,140,255,0.3);">
|
||
<div class="card-title" style="color: #722ED1;">概率性轨道</div>
|
||
<div class="card-desc">
|
||
LLM 语义摘要 → 社区检测 → God Node 识别 → 理解"为什么这样设计"
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="diagram">
|
||
<span class="label">代码文件</span> ──<span class="arrow">──→</span> <span class="node">确定性解析</span> (AST) ──<span class="arrow">──┐
|
||
├──→ <span class="label">知识图谱</span> (节点 + 边 + 社区)
|
||
<span class="label">文档/图片</span> ──<span class="arrow">──→</span> <span class="node">概率性解析</span> (LLM) ──<span class="arrow">──┘
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 3: Install -->
|
||
<div class="section" id="install">
|
||
<h2>三、全功能安装指南</h2>
|
||
|
||
<h3>3.1 安装 Rust 版(推荐生产使用)</h3>
|
||
|
||
<pre><code><span class="comment"># 1. 安装 Rust 工具链(如未安装)</span>
|
||
<span class="builtin">curl</span> <span class="keyword">--proto</span> <span class="string">'=https'</span> <span class="keyword">--tlsv1.2</span> <span class="keyword">-sSf</span> https://sh.rustup.rs | <span class="builtin">sh</span>
|
||
|
||
<span class="comment"># 2. 安装 graphify-rs</span>
|
||
<span class="builtin">cargo</span> install graphify-rs
|
||
|
||
<span class="comment"># 3. 验证安装</span>
|
||
<span class="builtin">graphify-rs</span> <span class="keyword">--help</span></code></pre>
|
||
|
||
<h3>3.2 系统要求</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>依赖</th><th>说明</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>运行时</td><td>Rust 编译后二进制(无运行时依赖)</td></tr>
|
||
<tr><td>tree-sitter</td><td>静态链接,无需额外安装</td></tr>
|
||
<tr><td>平台</td><td>Linux / macOS / Windows</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 4: CLI -->
|
||
<div class="section" id="cli">
|
||
<h2>四、CLI 命令全览</h2>
|
||
|
||
<h3>4.1 Rust 版(graphify-rs)</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>命令</th><th>功能</th><th>示例</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><code>init</code></td><td>创建配置文件</td><td><code>graphify-rs init</code></td></tr>
|
||
<tr><td><code>build</code></td><td>构建知识图谱</td><td><code>graphify-rs build --path ./src</code></td></tr>
|
||
<tr><td><code>update</code></td><td>增量更新已有图谱</td><td><code>graphify-rs update</code></td></tr>
|
||
<tr><td><code>watch</code></td><td>监听文件变化自动更新</td><td><code>graphify-rs watch</code></td></tr>
|
||
<tr><td><code>query</code></td><td>交互式查询图谱</td><td><code>graphify-rs query</code></td></tr>
|
||
<tr><td><code>path</code></td><td>查找两节点间路径</td><td><code>graphify-rs path "A" "B"</code></td></tr>
|
||
<tr><td><code>explain</code></td><td>解释某个节点</td><td><code>graphify-rs explain "AuthService"</code></td></tr>
|
||
<tr><td><code>wiki</code></td><td>生成 Wiki 文档</td><td><code>graphify-rs wiki</code></td></tr>
|
||
<tr><td><code>svg</code></td><td>导出 SVG 可视化</td><td><code>graphify-rs svg --output graph.svg</code></td></tr>
|
||
<tr><td><code>graphml</code></td><td>导出 GraphML 格式</td><td><code>graphify-rs graphml --output g.graphml</code></td></tr>
|
||
<tr><td><code>neo4j</code></td><td>导入 Neo4j 数据库</td><td><code>graphify-rs neo4j --url bolt://localhost:7687</code></td></tr>
|
||
<tr><td><code>mcp</code></td><td>启动 MCP Server</td><td><code>graphify-rs mcp</code></td></tr>
|
||
<tr><td><code>add</code></td><td>手动添加文件</td><td><code>graphify-rs add src/new_module.py</code></td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 5: Claude Code -->
|
||
<div class="section" id="claude">
|
||
<h2>五、与 Claude Code 配合最佳实践</h2>
|
||
|
||
<h3>5.1 配置为 MCP Server(推荐,功能最全)</h3>
|
||
|
||
<p><strong>项目级别配置</strong>(<code>.mcp.json</code>):</p>
|
||
<pre><code>{
|
||
<span class="string">"mcpServers"</span>: {
|
||
<span class="string">"graphify"</span>: {
|
||
<span class="string">"command"</span>: <span class="string">"graphify-rs"</span>,
|
||
<span class="string">"args"</span>: [<span class="string">"mcp"</span>, <span class="string">"--graph"</span>, <span class="string">"graphify"</span>]
|
||
}
|
||
}
|
||
}</code></pre>
|
||
|
||
<p><strong>全局配置</strong>(<code>~/.claude/settings.json</code>):</p>
|
||
<pre><code>{
|
||
<span class="string">"mcpServers"</span>: {
|
||
<span class="string">"graphify"</span>: {
|
||
<span class="string">"command"</span>: <span class="string">"graphify-rs"</span>,
|
||
<span class="string">"args"</span>: [<span class="string">"mcp"</span>]
|
||
}
|
||
}
|
||
}</code></pre>
|
||
|
||
<h3>5.2 典型工作流</h3>
|
||
<ol class="steps">
|
||
<li><code>graphify-rs init</code> — 初始化配置</li>
|
||
<li><code>graphify-rs build</code> — 首次构建图谱</li>
|
||
<li>在 Claude Code 中使用 <code>/graphify</code> 查询,或通过 MCP 工具交互</li>
|
||
<li><code>graphify-rs watch</code> — 后台监听文件变化</li>
|
||
<li>提交 <code>graphify-out/</code> 到 Git — 持久化图谱,团队共享</li>
|
||
</ol>
|
||
|
||
<h3>5.3 Token 节省策略</h3>
|
||
<div class="diagram">
|
||
<span class="label" style="color:var(--color-error);">不使用 Graphify</span>:
|
||
每次 Claude Code 会话 ──<span class="arrow">──→</span> 扫描整个代码库 ──<span class="arrow">──→</span> <span style="color:var(--color-error);">~500K tokens</span>
|
||
|
||
<span class="label" style="color:var(--color-success);">使用 Graphify</span>:
|
||
首次构建(一次性) ──<span class="arrow">──→</span> ~500K tokens
|
||
后续每次会话查询节点 ──<span class="arrow">──→</span> <span style="color:var(--color-success);">~7K tokens</span>
|
||
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
<span style="color:var(--color-success);font-weight:700;">节省比: ~71x</span>
|
||
</div>
|
||
|
||
<h3>5.4 与 CLAUDE.md 配合</h3>
|
||
<p>在项目根目录 <code>CLAUDE.md</code> 中添加:</p>
|
||
<pre><code><span class="comment">## 知识图谱</span>
|
||
本项目使用 Graphify 构建知识图谱,图谱位于 <span class="string">graphify-out/</span>。
|
||
新任务请先通过 graphify MCP 查询相关模块,再开始编码。
|
||
修改代码后运行 <span class="builtin">graphify-rs</span> update 更新图谱。</code></pre>
|
||
</div>
|
||
|
||
<!-- Section 6: New Project -->
|
||
<div class="section" id="new-project">
|
||
<h2>六、新建项目最佳实践</h2>
|
||
|
||
<h3>6.1 项目初始化流程</h3>
|
||
<pre><code><span class="comment"># 1. 创建项目</span>
|
||
<span class="builtin">mkdir</span> my-project && <span class="builtin">cd</span> my-project
|
||
<span class="builtin">git</span> init
|
||
|
||
<span class="comment"># 2. 初始化 Graphify</span>
|
||
<span class="builtin">graphify-rs</span> init
|
||
|
||
<span class="comment"># 3. 编辑 graphify.toml(按需配置语言和排除项)</span>
|
||
|
||
<span class="comment"># 4. 首次构建图谱</span>
|
||
<span class="builtin">graphify-rs</span> build
|
||
|
||
<span class="comment"># 5. 在 Claude Code 中验证</span>
|
||
<span class="builtin">claude</span>
|
||
<span class="comment">> /graphify .</span></code></pre>
|
||
|
||
<h3>6.2 <code>graphify.toml</code> 配置示例</h3>
|
||
<pre><code>[<span class="builtin">project</span>]
|
||
<span class="string">name</span> = <span class="string">"my-project"</span>
|
||
<span class="string">languages</span> = [<span class="string">"typescript"</span>, <span class="string">"python"</span>, <span class="string">"rust"</span>]
|
||
|
||
[<span class="builtin">exclude</span>]
|
||
<span class="string">dirs</span> = [<span class="string">"node_modules"</span>, <span class="string">"dist"</span>, <span class="string">".git"</span>, <span class="string">"venv"</span>]
|
||
<span class="string">files</span> = [<span class="string">"*.test.*"</span>, <span class="string">"*.spec.*"</span>]
|
||
|
||
[<span class="builtin">build</span>]
|
||
<span class="string">semantic</span> = <span class="keyword">true</span> <span class="comment"># 启用 LLM 语义分析</span>
|
||
<span class="string">community_detection</span> = <span class="keyword">true</span> <span class="comment"># 社区检测</span>
|
||
<span class="string">max_files</span> = <span class="number">5000</span> <span class="comment"># 最大文件数</span>
|
||
<span class="string">max_words</span> = <span class="number">100000</span> <span class="comment"># 最大词数</span></code></pre>
|
||
|
||
<h3>6.3 Git 集成</h3>
|
||
<pre><code><span class="comment"># .gitignore 配置</span>
|
||
<span class="comment"># graphify-out/ # 图谱输出(建议提交用于团队共享)</span>
|
||
<span class="comment"># .graphify-cache/ # 缓存(可以忽略)</span></code></pre>
|
||
|
||
<div class="alert info">
|
||
<strong>建议:</strong>将 <code>graphify-out/</code> 提交到仓库,让团队成员克隆后直接获得项目上下文,无需重新构建。
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 7: Second Dev -->
|
||
<div class="section" id="second-dev">
|
||
<h2>七、二次开发最佳实践</h2>
|
||
|
||
<h3>7.1 接手已有项目的标准流程</h3>
|
||
<ol class="steps">
|
||
<li><code>git clone <repo> && cd <repo></code> — 克隆项目</li>
|
||
<li><code>graphify-rs build</code> — 立即构建图谱,获得项目全景</li>
|
||
<li><code>graphify-rs query</code> — 查找 God Node(核心模块)、模块间依赖关系</li>
|
||
<li>在 Claude Code 中 <code>/graphify 查询 "认证模块的入口是什么"</code></li>
|
||
<li>基于图谱理解,有针对性地开始修改</li>
|
||
</ol>
|
||
|
||
<h3>7.2 增量更新策略</h3>
|
||
<pre><code><span class="comment"># 方式1: 文件监听模式(开发中持续更新)</span>
|
||
<span class="builtin">graphify-rs</span> watch &
|
||
|
||
<span class="comment"># 方式2: 手动增量更新(每次大改后)</span>
|
||
<span class="builtin">graphify-rs</span> update
|
||
|
||
<span class="comment"># 方式3: 添加单个新文件</span>
|
||
<span class="builtin">graphify-rs</span> add src/new_module.py</code></pre>
|
||
|
||
<h3>7.3 导出与可视化</h3>
|
||
<pre><code><span class="comment"># 导出 SVG 在浏览器中查看</span>
|
||
<span class="builtin">graphify-rs</span> svg <span class="keyword">--output</span> graph.svg
|
||
|
||
<span class="comment"># 导出 GraphML 导入 Neo4j 进行深度分析</span>
|
||
<span class="builtin">graphify-rs</span> graphml <span class="keyword">--output</span> graph.graphml
|
||
|
||
<span class="comment"># 直接导入 Neo4j 数据库</span>
|
||
<span class="builtin">graphify-rs</span> neo4j <span class="keyword">--url</span> bolt://localhost:7687</code></pre>
|
||
|
||
<h3>7.4 利用图谱理解模块边界</h3>
|
||
<p>在修改某个模块前,先用 <code>graphify-rs path</code> 了解它的上下游依赖,确保修改不会破坏模块边界:</p>
|
||
<pre><code><span class="comment"># 查看 UserController 到 DatabaseService 的调用链</span>
|
||
<span class="builtin">graphify-rs</span> path <span class="string">"UserController"</span> <span class="string">"DatabaseService"</span>
|
||
|
||
<span class="comment"># 解释 AuthService 的职责</span>
|
||
<span class="builtin">graphify-rs</span> explain <span class="string">"AuthService"</span></code></pre>
|
||
</div>
|
||
|
||
<!-- Section 8: Bug Fix -->
|
||
<div class="section" id="bug-fix">
|
||
<h2>八、Bug 修正最佳实践</h2>
|
||
|
||
<h3>8.1 利用图谱定位 BUG</h3>
|
||
<pre><code><span class="comment"># 1. 查询 BUG 相关模块</span>
|
||
<span class="builtin">graphify-rs</span> query <span class="string">"error handling middleware"</span>
|
||
|
||
<span class="comment"># 2. 查找完整调用链</span>
|
||
<span class="builtin">graphify-rs</span> path <span class="string">"UserController"</span> <span class="string">"DatabaseService"</span>
|
||
|
||
<span class="comment"># 3. 解释疑似问题节点</span>
|
||
<span class="builtin">graphify-rs</span> explain <span class="string">"AuthService"</span></code></pre>
|
||
|
||
<h3>8.2 Claude Code 中的 BUG 修复流程</h3>
|
||
<ol class="steps">
|
||
<li><code>/graphify</code> 查询 "最近的错误处理相关变更"</li>
|
||
<li>利用 MCP 工具获取模块上下文和依赖关系</li>
|
||
<li>理解依赖关系后再修改,避免引入回归</li>
|
||
<li><code>graphify-rs update</code> 更新图谱</li>
|
||
<li>验证修改没有破坏模块边界</li>
|
||
</ol>
|
||
|
||
<h3>8.3 已知问题与解决方案</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>问题</th><th>Issue</th><th>解决方案</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>大仓库文件数上限</td><td>#162</td><td>配置 <code>max_files</code> / <code>max_words</code></td></tr>
|
||
<tr><td>只读模式 Skill 失败</td><td>#195</td><td>确保 Claude Code 有文件读取权限</td></tr>
|
||
<tr><td><code>--wiki</code> 参数缺失</td><td>#177</td><td>已修复,更新到最新版</td></tr>
|
||
<tr><td><code>to_wiki()</code> 未调用</td><td>#354</td><td>待修复,可手动调用</td></tr>
|
||
<tr><td>请求原生 Claude Plugin</td><td>#146</td><td>社区提案中,关注进展</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 9: VSCode -->
|
||
<div class="section" id="vscode">
|
||
<h2>九、VSCode 最佳实践</h2>
|
||
|
||
<h3>9.1 配合 GitHub Copilot Chat</h3>
|
||
<p>通过 MCP Server 配置,Copilot Chat 可以直接使用 graphify-rs 的图谱查询能力。</p>
|
||
|
||
<h3>9.2 推荐 VSCode 扩展组合</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>扩展</th><th>用途</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><strong>GitHub Copilot / Copilot Chat</strong></td><td>AI 编程助手</td></tr>
|
||
<tr><td><strong>Graphviz Preview</strong></td><td>查看 Graphify 导出的 DOT / SVG 图</td></tr>
|
||
<tr><td><strong>Neo4j for VS Code</strong></td><td>如果图谱导入 Neo4j,直接在 IDE 中查询</td></tr>
|
||
<tr><td><strong>Markdown Preview</strong></td><td>查看 Graphify 生成的 Wiki 文档</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>9.3 VSCode 工作流</h3>
|
||
<div class="diagram">
|
||
<span class="label">VSCode 编辑代码</span>
|
||
│
|
||
<span class="arrow">↓</span>
|
||
<span class="node">Graphify watch</span> 自动检测变更
|
||
│
|
||
<span class="arrow">↓</span>
|
||
<span class="label">Copilot Chat</span> 查询图谱上下文
|
||
│
|
||
<span class="arrow">↓</span>
|
||
基于图谱理解 → <span class="label">精准修改</span>
|
||
│
|
||
<span class="arrow">↓</span>
|
||
<span class="node">Graphviz Preview</span> 实时查看图谱变化
|
||
</div>
|
||
|
||
<h3>9.4 VSCode Settings 配置</h3>
|
||
<p>在项目 <code>.vscode/settings.json</code> 中:</p>
|
||
<pre><code>{
|
||
<span class="comment">// 避免 Graphify 输出触发 VSCode 文件监听风暴</span>
|
||
<span class="string">"files.watcherExclude"</span>: {
|
||
<span class="string">"**/graphify-out/**"</span>: <span class="keyword">true</span>
|
||
},
|
||
<span class="comment">// 确保 Copilot Agent 模式启用</span>
|
||
<span class="string">"github.copilot.chat.agent.enabled"</span>: <span class="keyword">true</span>
|
||
}</code></pre>
|
||
</div>
|
||
|
||
<!-- Section 10: Ogham Intro -->
|
||
<div class="section" id="ogham-intro">
|
||
<h2>十、Ogham MCP 是什么?</h2>
|
||
|
||
<p><strong>Ogham</strong>(发音 "OH-um")是一个开源的、持久化的、可搜索的 <strong>共享记忆 MCP Server</strong>,专为 AI 编程代理(Claude Code、Cursor、Codex CLI 等)设计。它基于 Model Context Protocol (MCP),通过 PostgreSQL + pgvector 实现语义搜索级别的记忆存储与召回。</p>
|
||
|
||
<div class="metrics">
|
||
<div class="metric">
|
||
<div class="value green">97.2%</div>
|
||
<div class="label">Recall@10 (LongMemEval)</div>
|
||
</div>
|
||
<div class="metric">
|
||
<div class="value purple">MIT</div>
|
||
<div class="label">开源许可证</div>
|
||
</div>
|
||
<div class="metric">
|
||
<div class="value">Go</div>
|
||
<div class="label">单二进制,零依赖</div>
|
||
</div>
|
||
<div class="metric">
|
||
<div class="value green">跨客户端</div>
|
||
<div class="label">Claude / Cursor / Codex</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h3>10.1 核心能力</h3>
|
||
<div class="card-grid">
|
||
<div class="card" style="border-color: rgba(63,185,80,0.3);">
|
||
<div class="card-title" style="color: var(--color-success);">持久记忆</div>
|
||
<div class="card-desc">记忆存储在 PostgreSQL 中,跨会话、跨客户端、跨项目不丢失。关闭 Claude Code 再打开,记忆依然存在。</div>
|
||
</div>
|
||
<div class="card" style="border-color: rgba(88,166,255,0.3);">
|
||
<div class="card-title" style="color: var(--color-primary);">混合搜索</div>
|
||
<div class="card-desc">向量相似度 + 关键词匹配,通过 Reciprocal Rank Fusion 合并结果。一次 SQL 查询即可完成语义搜索。</div>
|
||
</div>
|
||
<div class="card" style="border-color: rgba(188,140,255,0.3);">
|
||
<div class="card-title" style="color: #722ED1;">Profile 隔离</div>
|
||
<div class="card-desc">按项目/工作/个人分区记忆。切换 profile 后,不同上下文的记忆互不干扰。</div>
|
||
</div>
|
||
<div class="card" style="border-color: rgba(240,136,62,0.3);">
|
||
<div class="card-title" style="color: var(--color-warning);">无需 LLM 存储</div>
|
||
<div class="card-desc">存储记忆只需要 embedding 模型,不需要调用 LLM。成本低、速度快、可离线。</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h3>10.2 为什么需要 Ogham?</h3>
|
||
<p>Claude Code 存在两个固有问题:</p>
|
||
<ul>
|
||
<li><strong>上下文压缩(Compaction):</strong>当对话超过上下文窗口时,Claude 会自动压缩对话历史。压缩后,之前的决策、关键细节、架构讨论都会丢失。</li>
|
||
<li><strong>AI 失忆(Amnesia):</strong>每次新会话启动,Claude 对项目的理解从零开始。即使你在昨天的会话中讨论了重要的架构决策,今天也不会记得。</li>
|
||
</ul>
|
||
|
||
<div class="diagram">
|
||
<span style="color:var(--color-error);">没有 Ogham</span>:
|
||
会话1: 讨论架构决策 ──<span class="arrow">──→</span> 上下文压缩 ──<span class="arrow">──→</span> <span style="color:var(--color-error);">决策丢失</span>
|
||
会话2: 重新开始 ──<span class="arrow">──→</span> 重新扫描代码库 ──<span class="arrow">──→</span> <span style="color:var(--color-error);">重复消耗 Token</span>
|
||
|
||
<span style="color:var(--color-success);">有 Ogham</span>:
|
||
会话1: 讨论架构决策 ──<span class="arrow">──→</span> <span class="node">inscribe</span> 保存 ──<span class="arrow">──→</span> <span style="color:var(--color-success);">持久记忆</span>
|
||
会话2: <span class="node">recall</span> 召回 ──<span class="arrow">──→</span> 注入上下文 ──<span class="arrow">──→</span> <span style="color:var(--color-success);">从上次继续</span>
|
||
</div>
|
||
|
||
<p><strong>仓库地址:</strong><a href="https://github.com/ogham-mcp/ogham-mcp">github.com/ogham-mcp/ogham-mcp</a> | <strong>官网:</strong><a href="https://ogham-mcp.dev/">ogham-mcp.dev</a></p>
|
||
</div>
|
||
|
||
<!-- Section 10b: Ogham Installation -->
|
||
<div class="section" id="ogham-install">
|
||
<h2>十一、Ogham MCP 安装与配置</h2>
|
||
|
||
<h3>11.1 前置条件</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>依赖</th><th>说明</th><th>可选方案</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>PostgreSQL + pgvector</td><td>持久化存储 + 向量搜索</td><td>自托管(免费)或 Supabase 云服务</td></tr>
|
||
<tr><td>Embedding 提供商</td><td>生成文本向量</td><td>OpenAI / Ollama 本地 / Cloudflare Workers AI</td></tr>
|
||
<tr><td>Ogham CLI</td><td>单二进制 Go 程序</td><td>macOS / Linux / Windows</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>11.2 安装步骤</h3>
|
||
<ol class="steps">
|
||
<li><strong>下载 Ogham CLI</strong><br>前往 <a href="https://ogham-mcp.dev/download/">ogham-mcp.dev/download</a> 下载对应平台的二进制文件,或使用包管理器安装。</li>
|
||
<li><strong>准备数据库</strong><br>使用 Docker 速启动 PostgreSQL + pgvector:<pre><code><span class="builtin">docker</span> run <span class="keyword">-d</span> <span class="keyword">--name</span> ogham-postgres \\
|
||
<span class="keyword">-e</span> POSTGRES_PASSWORD=ogham_secret \\
|
||
<span class="keyword">-p</span> 5432:5432 \\
|
||
pgvector/pgvector:pg16</code></pre>或使用 <a href="https://supabase.com/">Supabase</a> 免费托管实例。</li>
|
||
<li><strong>运行初始化向导</strong><br><pre><code><span class="comment"># 使用 uv 运行(推荐,自动管理依赖)</span>
|
||
<span class="builtin">uvx</span> <span class="keyword">--from</span> ogham-mcp ogham init
|
||
|
||
<span class="comment"># 或直接下载的二进制</span>
|
||
<span class="builtin">./ogham</span> init</code></pre>
|
||
初始化向导会引导你完成:
|
||
<ul>
|
||
<li>数据库连接字符串配置</li>
|
||
<li>Embedding 提供商选择</li>
|
||
<li>Schema 迁移(自动创建表结构)</li>
|
||
<li><strong>自动写入 MCP 客户端配置</strong>(Claude Code、Cursor 等)</li>
|
||
</ul>
|
||
</li>
|
||
<li><strong>验证安装</strong><br><pre><code><span class="comment"># 测试数据库连接</span>
|
||
<span class="builtin">uvx</span> <span class="keyword">--from</span> ogham-mcp ogham <span class="keyword">doctor</span></code></pre></li>
|
||
</ol>
|
||
|
||
<h3>11.3 自动配置的 MCP 连接</h3>
|
||
<p>初始化向导会自动执行 <code>claude mcp add</code>,将以下配置写入 <code>~/.claude/settings.json</code>:</p>
|
||
<pre><code>{
|
||
<span class="string">"mcpServers"</span>: {
|
||
<span class="string">"ogham"</span>: {
|
||
<span class="string">"command"</span>: <span class="string">"ogham"</span>,
|
||
<span class="string">"args"</span>: [<span class="string">"serve"</span>],
|
||
<span class="string">"env"</span>: {
|
||
<span class="string">"DATABASE_URL"</span>: <span class="string">"postgresql://..."</span>,
|
||
<span class="string">"EMBEDDING_PROVIDER"</span>: <span class="string">"openai"</span>
|
||
}
|
||
}
|
||
}
|
||
}</code></pre>
|
||
|
||
<h3>11.4 手动配置(项目级别)</h3>
|
||
<p>在项目根目录创建 <code>.mcp.json</code>:</p>
|
||
<pre><code>{
|
||
<span class="string">"mcpServers"</span>: {
|
||
<span class="string">"ogham"</span>: {
|
||
<span class="command</span>: <span class="string">"ogham"</span>,
|
||
<span class="string">"args"</span>: [<span class="string">"serve"</span>, <span class="string">"--profile"</span>, <span class="string">"my-project"</span>],
|
||
<span class="string">"env"</span>: {
|
||
<span class="string">"DATABASE_URL"</span>: <span class="string">"postgresql://user:pass@localhost:5432/ogham"</span>,
|
||
<span class="string">"EMBEDDING_PROVIDER"</span>: <span class="string">"openai"</span>,
|
||
<span class="string">"OPENAI_API_KEY"</span>: <span class="string">"sk-..."</span>
|
||
}
|
||
}
|
||
}
|
||
}</code></pre>
|
||
|
||
<div class="alert info">
|
||
<strong>Profile 建议:</strong>每个项目使用独立的 <code>--profile</code>,避免不同项目的记忆互相污染。
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 10c: Ogham Tools -->
|
||
<div class="section" id="ogham-tools">
|
||
<h2>十二、Ogham 核心工具详解</h2>
|
||
|
||
<p>Ogham MCP 暴露给 AI 代理的工具有三个,构成了完整的记忆生命周期:</p>
|
||
|
||
<h3>12.1 <code>inscribe</code> — 写入记忆</h3>
|
||
<p>在会话压缩前,Claude 会调用 <code>inscribe</code> 将关键上下文保存到数据库。典型保存内容包括:</p>
|
||
<ul>
|
||
<li>架构决策及原因(为什么选 A 不选 B)</li>
|
||
<li>项目约定和编码规范</li>
|
||
<li>已知的技术债务和 TODO</li>
|
||
<li>用户偏好和工作流习惯</li>
|
||
<li>模块职责和边界定义</li>
|
||
</ul>
|
||
<pre><code><span class="comment"># Claude 在压缩前自动调用,也可以手动触发</span>
|
||
<span class="comment"># 示例:保存一个重要决策</span>
|
||
<span class="comment">→ inscribe("认证使用 JWT 而非 session,因为 API 需要无状态")</span></code></pre>
|
||
|
||
<h3>12.2 <code>recall</code> — 召回记忆</h3>
|
||
<p>在新会话开始时,Claude 自动调用 <code>recall</code> 搜索与当前项目相关的记忆并注入上下文:</p>
|
||
<ul>
|
||
<li>语义搜索:理解"认证"相关的所有历史讨论</li>
|
||
<li>关键词匹配:精确查找特定术语</li>
|
||
<li>混合排序:RRF 融合两种结果,召回率最优</li>
|
||
</ul>
|
||
<pre><code><span class="comment"># 会话开始时自动触发</span>
|
||
<span class="comment">→ recall("我正在修改认证模块")</span>
|
||
<span class="comment"># 返回: "2026-04-20: 认证使用 JWT,原因是 API 需要无状态"</span>
|
||
<span class="comment"># 返回: "2026-04-22: 用户偏好使用 HS256 算法"</span></code></pre>
|
||
|
||
<h3>12.3 <code>search</code> — 主动搜索</h3>
|
||
<p>在任何时候都可以主动搜索记忆库:</p>
|
||
<pre><code><span class="comment"># 搜索所有关于数据库的决策</span>
|
||
<span class="comment">→ search("database decisions")</span>
|
||
|
||
<span class="comment"># 搜索特定时间段的讨论</span>
|
||
<span class="comment">→ search("migration plan")</span></code></pre>
|
||
|
||
<h3>12.4 工具对比</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>工具</th><th>触发时机</th><th>目的</th><th>类比</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><code>inscribe</code></td><td>压缩前 / 重要决策后</td><td>保存上下文</td><td>写日记</td></tr>
|
||
<tr><td><code>recall</code></td><td>会话开始 / 压缩后</td><td>自动召回相关记忆</td><td>回忆</td></tr>
|
||
<tr><td><code>search</code></td><td>任何时候主动查询</td><td>精确搜索特定内容</td><td>翻笔记本</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 10d: Ogham Hooks -->
|
||
<div class="section" id="ogham-hooks">
|
||
<h2>十三、Ogham Hooks:记忆跨压缩存活</h2>
|
||
|
||
<p>Ogham v0.6.0 引入了 <strong>生命周期 Hooks</strong> 机制,让 Claude Code 在特定事件(如上下文压缩)发生时自动调用 Ogham 工具,确保记忆不丢失。</p>
|
||
|
||
<h3>13.1 安装 Hooks</h3>
|
||
<pre><code><span class="builtin">uvx</span> ogham-mcp hooks install</code></pre>
|
||
<p>这会将 hooks 配置写入 <code>~/.claude/settings.json</code>,无需手动编辑。</p>
|
||
|
||
<h3>13.2 四个内置 Hooks</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>Hook</th><th>触发时机</th><th>执行动作</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><code>pre-compact</code></td><td>上下文压缩前</td><td>调用 <code>inscribe</code> 保存当前关键上下文</td></tr>
|
||
<tr><td><code>post-compact</code></td><td>上下文压缩后</td><td>调用 <code>recall</code> 重新注入被压缩掉的记忆</td></tr>
|
||
<tr><td><code>session-start</code></td><td>新会话开始</td><td>调用 <code>recall</code> 加载项目历史记忆</td></tr>
|
||
<tr><td><code>session-end</code></td><td>会话结束</td><td>调用 <code>inscribe</code> 保存本次会话的重要信息</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>13.3 Hooks 工作流</h3>
|
||
<div class="diagram">
|
||
<span class="label">会话开始</span> ──<span class="arrow">──→</span> <span class="node">session-start hook</span>
|
||
<span class="arrow">↓</span>
|
||
<span class="node">recall</span> 加载历史记忆
|
||
<span class="arrow">↓</span>
|
||
<span class="label">正常对话 / 编码</span>
|
||
<span class="arrow">↓</span>
|
||
上下文即将达到上限 ──<span class="arrow">──→</span> <span class="node">pre-compact hook</span>
|
||
<span class="arrow">↓</span>
|
||
<span class="node">inscribe</span> 保存关键信息
|
||
<span class="arrow">↓</span>
|
||
<span class="label">上下文压缩发生</span>
|
||
<span class="arrow">↓</span>
|
||
<span class="node">post-compact hook</span>
|
||
<span class="arrow">↓</span>
|
||
<span class="node">recall</span> 重新注入记忆
|
||
<span class="arrow">↓</span>
|
||
<span class="label">继续编码(记忆完整)</span>
|
||
<span class="arrow">↓</span>
|
||
<span class="label">会话结束</span> ──<span class="arrow">──→</span> <span class="node">session-end hook</span>
|
||
<span class="arrow">↓</span>
|
||
<span class="node">inscribe</span> 保存会话摘要
|
||
</div>
|
||
|
||
<div class="alert success">
|
||
<strong>效果:</strong>即使对话经历了多次压缩,Claude 依然能"记住"项目关键信息。在 LongMemEval 基准测试(500 道题,ICLR 2025)上达到 <strong>97.2% Recall@10</strong>。
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 10e: Graphify + Ogham Combo -->
|
||
<div class="section" id="ogham-combo">
|
||
<h2>十四、Graphify + Ogham MCP 组合实战</h2>
|
||
|
||
<p><strong>Graphify</strong> 解决 <strong>项目理解</strong>(代码结构、模块关系、架构决策),<strong>Ogham MCP</strong> 解决 <strong>上下文管理</strong>(记忆持久化、跨压缩存活、智能召回)。两者组合实现 1 + 1 > 2 的效果:</p>
|
||
|
||
<div class="diagram">
|
||
<span class="node">Graphify</span> <span class="node">Ogham MCP</span>
|
||
┌───────────────┐ ┌───────────────┐
|
||
│ AST 解析代码 │ │ 会话记忆存储 │
|
||
│ 知识图谱构建 │ │ 语义搜索召回 │
|
||
│ 模块依赖分析 │ │ 跨压缩不丢失 │
|
||
│ 社区检测 │ │ Profile 隔离 │
|
||
└───────┬───────┘ └───────┬───────┘
|
||
│ │
|
||
│ <span class="arrow">Graphify 图谱输出</span> │ <span class="arrow">Ogham 记忆输出</span>
|
||
└──────────<span class="label">┬────────────┘</span>
|
||
<span class="arrow">↓</span>
|
||
<span class="label" style="font-size:1rem;">Claude Code 完整上下文</span>
|
||
<span class="arrow">↓</span>
|
||
<span class="string" style="font-size:1rem;">精准编码 · 零重复 · 不遗忘</span>
|
||
</div>
|
||
|
||
<h3>14.1 组合安装(一次性)</h3>
|
||
<pre><code><span class="comment"># 1. 安装 Graphify</span>
|
||
<span class="builtin">cargo</span> install graphify-rs
|
||
|
||
<span class="comment"># 2. 安装 Ogham + 初始化</span>
|
||
<span class="builtin">uvx</span> <span class="keyword">--from</span> ogham-mcp ogham init
|
||
|
||
<span class="comment"># 3. 安装 Ogham Hooks</span>
|
||
<span class="builtin">uvx</span> ogham-mcp hooks install
|
||
|
||
<span class="comment"># 4. 验证两个 MCP Server 都已注册</span>
|
||
<span class="builtin">cat</span> ~/.claude/settings.json | <span class="builtin">grep</span> <span class="keyword">-E</span> <span class="string">"graphify|ogham"</span></code></pre>
|
||
|
||
<h3>14.2 组合工作流</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>阶段</th><th>Graphify 做什么</th><th>Ogham 做什么</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><strong>首次接手项目</strong></td><td><code>build</code> 构建图谱,理解结构</td><td><code>recall</code> 加载项目历史决策</td></tr>
|
||
<tr><td><strong>开发新功能</strong></td><td><code>path</code> 查找模块依赖链</td><td><code>recall</code> 搜索相关历史讨论</td></tr>
|
||
<tr><td><strong>架构决策</strong></td><td><code>explain</code> 理解现有模块职责</td><td><code>inscribe</code> 保存新决策及原因</td></tr>
|
||
<tr><td><strong>修复 BUG</strong></td><td><code>query</code> 定位问题模块</td><td><code>search</code> 搜索已知类似问题</td></tr>
|
||
<tr><td><strong>上下文压缩后</strong></td><td>图谱不变,随时可查</td><td><code>recall</code> 自动恢复记忆</td></tr>
|
||
<tr><td><strong>跨天继续工作</strong></td><td>图谱持久化,无需重建</td><td><code>recall</code> 自动加载昨天讨论</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>14.3 完整 <code>~/.claude/settings.json</code>(双工具配置)</h3>
|
||
<pre><code>{
|
||
<span class="string">"mcpServers"</span>: {
|
||
<span class="string">"graphify"</span>: {
|
||
<span class="string">"command"</span>: <span class="string">"graphify-rs"</span>,
|
||
<span class="string">"args"</span>: [<span class="string">"mcp"</span>]
|
||
},
|
||
<span class="string">"ogham"</span>: {
|
||
<span class="string">"command"</span>: <span class="string">"ogham"</span>,
|
||
<span class="string">"args"</span>: [<span class="string">"serve"</span>],
|
||
<span class="string">"env"</span>: {
|
||
<span class="string">"DATABASE_URL"</span>: <span class="string">"postgresql://user:pass@localhost:5432/ogham"</span>,
|
||
<span class="string">"EMBEDDING_PROVIDER"</span>: <span class="string">"openai"</span>
|
||
}
|
||
}
|
||
}
|
||
}</code></pre>
|
||
|
||
<h3>14.4 组合 CLAUDE.md 模板</h3>
|
||
<pre><code><span class="comment">## 项目工具</span>
|
||
<span class="comment">### Graphify 知识图谱</span>
|
||
<span class="comment">- 图谱位置: graphify-out/</span>
|
||
<span class="comment">- 新任务先通过 graphify MCP 查询相关模块</span>
|
||
<span class="comment">- 修改代码后运行 graphify-rs update</span>
|
||
|
||
<span class="comment">### Ogham 持久记忆</span>
|
||
<span class="comment">- Profile: my-project</span>
|
||
<span class="comment">- 重要决策后主动 inscribe 保存</span>
|
||
<span class="comment">- 遇到类似问题先 search 历史记忆</span>
|
||
<span class="comment">- Hooks 已安装,压缩前后自动记忆</span></code></pre>
|
||
|
||
<h3>14.5 实际使用场景示例</h3>
|
||
|
||
<h4>场景 A:接手一个 2 年历史的遗留项目</h4>
|
||
<pre><code><span class="comment"># 第 1 步:Graphify 构建图谱</span>
|
||
<span class="builtin">graphify-rs</span> build
|
||
|
||
<span class="comment"># 第 2 步:通过图谱识别核心模块</span>
|
||
<span class="builtin">graphify-rs</span> query
|
||
|
||
<span class="comment"># 第 3 步:Ogham 自动召回历史记忆</span>
|
||
<span class="comment"># (如果有前人留下的 inscribe 记录)</span>
|
||
|
||
<span class="comment"># 第 4 步:针对性查询 + 开始修改</span>
|
||
<span class="builtin">graphify-rs</span> path <span class="string">"PaymentService"</span> <span class="string">"OrderController"</span>
|
||
|
||
<span class="comment"># 第 5 步:理解后 inscribe 保存自己的发现</span>
|
||
<span class="comment"># (Claude 自动调用,无需手动)</span></code></pre>
|
||
|
||
<h4>场景 B:长会话中的架构重构</h4>
|
||
<pre><code><span class="comment"># 上午:讨论方案,Claude 记住决策</span>
|
||
<span class="comment"># 上下文压缩 → Ogham pre-compact hook 保存决策</span>
|
||
<span class="comment"># 下午:开始实施,post-compact hook 恢复记忆</span>
|
||
<span class="comment"># 遇到分岐 → search 搜索上午讨论的结论</span>
|
||
<span class="comment"># Graphify path 确认重构影响范围</span>
|
||
<span class="comment"># 晚上:会话结束 → session-end hook 保存全天总结</span>
|
||
<span class="comment"># 第二天:session-start hook 自动加载昨天总结</span></code></pre>
|
||
|
||
<div class="alert success">
|
||
<strong>组合效果:</strong>Graphify 让 Claude "看懂代码",Ogham 让 Claude "记住讨论"。两者结合,Claude Code 从"每次从零开始"变为"带着项目全部上下文工作"。
|
||
</div>
|
||
|
||
<div class="alert info">
|
||
<strong>参考阅读:</strong><a href="https://levelup.gitconnected.com/supercharging-claude-code-dx-solving-context-bloat-and-ai-amnesia-with-graphify-and-ogham-mcp-6f8d5b414081">Solving Context Bloat and AI Amnesia with Graphify and Ogham MCP</a> |
|
||
<a href="https://ogham-mcp.dev/blog/hooks/">Give your AI agent a memory that survives compaction</a>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 15: Config -->
|
||
<div class="section" id="config">
|
||
<h2>十五、完整配置清单</h2>
|
||
|
||
<h3>15.1 全局配置(<code>~/.claude/settings.json</code>)</h3>
|
||
<pre><code>{
|
||
<span class="string">"mcpServers"</span>: {
|
||
<span class="string">"graphify"</span>: {
|
||
<span class="string">"command"</span>: <span class="string">"graphify-rs"</span>,
|
||
<span class="string">"args"</span>: [<span class="string">"mcp"</span>]
|
||
},
|
||
<span class="string">"ogham"</span>: {
|
||
<span class="string">"command"</span>: <span class="string">"ogham"</span>,
|
||
<span class="string">"args"</span>: [<span class="string">"serve"</span>],
|
||
<span class="string">"env"</span>: {
|
||
<span class="string">"DATABASE_URL"</span>: <span class="string">"postgresql://user:pass@localhost:5432/ogham"</span>,
|
||
<span class="string">"EMBEDDING_PROVIDER"</span>: <span class="string">"openai"</span>
|
||
}
|
||
}
|
||
}
|
||
}</code></pre>
|
||
|
||
<h3>15.2 项目配置(<code>graphify.toml</code>)</h3>
|
||
<pre><code>[<span class="builtin">project</span>]
|
||
<span class="string">name</span> = <span class="string">"my-project"</span>
|
||
<span class="string">languages</span> = [<span class="string">"typescript"</span>, <span class="string">"python"</span>]
|
||
|
||
[<span class="builtin">exclude</span>]
|
||
<span class="string">dirs</span> = [<span class="string">"node_modules"</span>, <span class="string">"dist"</span>, <span class="string">".git"</span>, <span class="string">"venv"</span>, <span class="string">"graphify-out"</span>]
|
||
<span class="string">files</span> = [<span class="string">"*.test.*"</span>, <span class="string">"*.spec.*"</span>, <span class="string">"*.min.js"</span>]
|
||
|
||
[<span class="builtin">build</span>]
|
||
<span class="string">semantic</span> = <span class="keyword">true</span>
|
||
<span class="string">community_detection</span> = <span class="keyword">true</span>
|
||
<span class="string">god_node_detection</span> = <span class="keyword">true</span>
|
||
<span class="string">max_files</span> = <span class="number">5000</span>
|
||
<span class="string">max_words</span> = <span class="number">100000</span></code></pre>
|
||
|
||
<h3>15.3 项目 CLAUDE.md 提示</h3>
|
||
<pre><code><span class="comment">## Graphify 知识图谱</span>
|
||
<span class="comment">- 图谱位置: graphify-out/</span>
|
||
<span class="comment">- 查询方式: 通过 graphify MCP 工具</span>
|
||
<span class="comment">- 新任务先查询相关模块,再开始编码</span>
|
||
<span class="comment">- 修改后运行 graphify-rs update 更新图谱</span>
|
||
|
||
<span class="comment">## Ogham 持久记忆</span>
|
||
<span class="comment">- Profile: my-project</span>
|
||
<span class="comment">- 重要决策后自动 inscribe,压缩后自动 recall</span>
|
||
<span class="comment">- Hooks 已安装,无需手动管理记忆</span></code></pre>
|
||
|
||
<h3>15.4 VSCode 配置(<code>.vscode/settings.json</code>)</h3>
|
||
<pre><code>{
|
||
<span class="string">"files.watcherExclude"</span>: {
|
||
<span class="string">"**/graphify-out/**"</span>: <span class="keyword">true</span>
|
||
},
|
||
<span class="string">"github.copilot.chat.agent.enabled"</span>: <span class="keyword">true</span>
|
||
}</code></pre>
|
||
|
||
<h3>15.5 Git 忽略(<code>.gitignore</code>)</h3>
|
||
<pre><code><span class="comment"># Graphify 缓存(可忽略)</span>
|
||
.graphify-cache/
|
||
|
||
<span class="comment"># Graphify 输出(建议提交用于团队共享,按需调整)</span>
|
||
<span class="comment"># graphify-out/</span></code></pre>
|
||
</div>
|
||
|
||
<!-- Section 16: Summary -->
|
||
<div class="section" id="summary">
|
||
<h2>十六、总结:何时该用?</h2>
|
||
|
||
<h3>16.1 Graphify 适用场景</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>场景</th><th>推荐程度</th><th>理由</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>大项目(>100 文件)</td><td><span style="color:var(--color-success);font-weight:700;">强烈推荐</span></td><td>节省 70x Token,快速理解结构</td></tr>
|
||
<tr><td>多语言混合项目</td><td><span style="color:var(--color-success);font-weight:700;">强烈推荐</span></td><td>25 种语言统一分析</td></tr>
|
||
<tr><td>二开 / 接手他人代码</td><td><span style="color:var(--color-success);font-weight:700;">强烈推荐</span></td><td>God Node 识别、社区检测、路径询</td></tr>
|
||
<tr><td>长期维护的项目</td><td><span style="color:var(--color-primary);font-weight:700;">推荐</span></td><td>图谱持久化,跨会话复用理解</td></tr>
|
||
<tr><td>中等项目(20-100 文件)</td><td><span style="color:var(--color-warning);font-weight:700;">可选</span></td><td>有一定收益,但不如大项目显著</td></tr>
|
||
<tr><td>小项目(<20 文件)</td><td><span style="color:var(--text-secondary);">不推荐</span></td><td>Token 节省收益不明显</td></tr>
|
||
<tr><td>纯脚本 / 单文件</td><td><span style="color:var(--text-secondary);">不需要</span></td><td>知识图谱无意义</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>16.2 Ogham MCP 适用场景</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>场景</th><th>推荐程度</th><th>理由</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>长会话(经常触发压缩)</td><td><span style="color:var(--color-success);font-weight:700;">强烈推荐</span></td><td>记忆跨压缩不丢失</td></tr>
|
||
<tr><td>跨天/跨周继续工作</td><td><span style="color:var(--color-success);font-weight:700;">强烈推荐</span></td><td>自动召回历史讨论</td></tr>
|
||
<tr><td>多人协作同一项目</td><td><span style="color:var(--color-success);font-weight:700;">强烈推荐</span></td><td>共享记忆池,知识沉淀</td></tr>
|
||
<tr><td>架构决策频繁的项目</td><td><span style="color:var(--color-primary);font-weight:700;">推荐</span></td><td>记录决策原因,避免重复讨论</td></tr>
|
||
<tr><td>一次性小任务</td><td><span style="color:var(--text-secondary);">不需要</span></td><td>无需持久记忆</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>16.3 组合使用推荐</h3>
|
||
<div class="card-grid">
|
||
<div class="card" style="border-color: rgba(63,185,80,0.4);">
|
||
<div class="card-title" style="color: var(--color-success);">两者都用</div>
|
||
<div class="card-desc">大项目 + 长期维护 + 多人协作 → Graphify 理解代码 + Ogham 记住讨论</div>
|
||
</div>
|
||
<div class="card" style="border-color: rgba(88,166,255,0.4);">
|
||
<div class="card-title" style="color: var(--color-primary);">只用 Graphify</div>
|
||
<div class="card-desc">接手项目快速理解结构 → 代码图谱足够,无需持久记忆</div>
|
||
</div>
|
||
<div class="card" style="border-color: rgba(188,140,255,0.4);">
|
||
<div class="card-title" style="color: #722ED1;">只用 Ogham</div>
|
||
<div class="card-desc">长期项目 + 频繁压缩 → 记忆不丢失比图谱更重要</div>
|
||
</div>
|
||
</div>
|
||
|
||
<blockquote>
|
||
<strong>一句话总结:</strong>Graphify 让 Claude Code "看懂代码",Ogham 让 Claude Code "记住讨论"。两者结合,Claude 从"每次都从零开始"变为"带着项目全部上下文工作"——这才是 AI 编程助手的完全体形态。
|
||
</blockquote>
|
||
</div>
|
||
|
||
</main>
|
||
|
||
<!-- Footer -->
|
||
<div class="footer">
|
||
<p>Graphify-rs 深度分析报告 · 2026-04-28</p>
|
||
<p style="margin-top:0.5rem;">
|
||
<a href="https://github.com/TtTRz/graphify-rs">GitHub (Rust)</a> ·
|
||
<a href="https://crates.io/crates/graphify-rs">Crates.io</a> ·
|
||
<a href="https://github.com/ogham-mcp/ogham-mcp">Ogham MCP</a> ·
|
||
<a href="https://levelup.gitconnected.com/supercharging-claude-code-dx-solving-context-bloat-and-ai-amnesia-with-graphify-and-ogham-mcp-6f8d5b414081">进阶指南</a>
|
||
</p>
|
||
</div>
|
||
|
||
<script>
|
||
// Sidebar active link tracking
|
||
const links = document.querySelectorAll('.sidebar nav a');
|
||
const sections = document.querySelectorAll('.section');
|
||
|
||
function setActive() {
|
||
let current = '';
|
||
sections.forEach(s => {
|
||
if (s.getBoundingClientRect().top < 120) current = s.id;
|
||
});
|
||
links.forEach(a => {
|
||
a.classList.toggle('active', a.getAttribute('href') === '#' + current);
|
||
});
|
||
}
|
||
|
||
links.forEach(a => {
|
||
a.addEventListener('click', e => {
|
||
e.preventDefault();
|
||
const target = document.querySelector(a.getAttribute('href'));
|
||
if (target) target.scrollIntoView();
|
||
});
|
||
});
|
||
|
||
window.addEventListener('scroll', setActive, { passive: true });
|
||
setActive();
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|