重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
npx skills add https://github.com/autumnsgrove/groveengine --skill museum-documentation文档即待客之道。代码即精心策展的收藏。
将技术系统转化为引人入胜的导览之旅的艺术。博物馆式文档将作者定位为向导,在深入实现细节之前,陪伴读者一起探索“为什么”的问题。
这是 Grove 优雅的文档风格——旨在为任何经验水平的 Wanderer 理解 Grove 赖以构建的技术、模式和决策。
不适用于:
文档应发挥待客之道的作用。
当读者进入一个代码库或系统时,他们是访客,不熟悉其架构和设计决策。博物馆式文档将作者定位为向导而非参考编译器,在深入实现之前,陪伴读者一起探索“为什么”的问题。
🌲 欢迎 🌲
╭─────────────────────╮
│ │
│ ╭─────────────╮ │
│ │ 展品 A │ │
│ │ │ │
│ │ 登录如何 │ │
│ │ 工作 │ │
│ │ │ │
│ ╰─────────────╯ │
│ │ │
│ ═════╪═════ │
│ │ │
│ ╭─────────────╮ │
│ │ 展品 B │ │
│ ╰─────────────╯ │
│ │
╰─────────────────────╯
漫步其中。慢慢欣赏。
每个展品都在讲述一个故事。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
博物馆不会递给你一本目录然后祝你好运。它会引导你完成一次精心策划的体验,将背景置于复杂性之前,将故事置于规格说明之前。
通过建立背景来开始展品。在展示代码或架构之前,告诉读者:
避免:
TokenRefreshMap 按用户 ID 存储活动的刷新操作。
应写为:
当某人的登录状态过期时,我们需要在不让他们登出的情况下刷新其凭据。这个映射表协调该过程——防止在打开多个标签页时出现重复的刷新尝试。
代码展示的是“是什么”。文档解释的是“为什么”。
与其陈述一个 Map 存储了令牌刷新操作,不如描述它解决的协调问题。将抽象概念与它创造的体验联系起来。
将技术概念与熟悉的体验联系起来:
| 技术概念 | 博物馆隐喻 |
|---|---|
| 数据库 | 一个带有有序抽屉的文件柜 |
| 缓存 | 门边的快速查找架 |
| 中间件 | 你经过的安检点 |
| 队列 | 请求排队等候的队伍 |
| 工作者 | 在后台处理任务的得力助手 |
| Webhook | 当某事发生时响起的门铃 |
呈现实际的代码片段,然后清晰地解释其意义:
const pending = this.refreshInProgress.get(userId);
if (pending) return pending;
如果已经有人在为此人进行刷新,我们就等待那个操作完成,而不是启动另一个。一个厨房,一个厨师。
博物馆展品遵循一致的构成:
# 身份验证展品
> 登录发生之处。信任开始之地。
立即为读者定位:
## 您正在看什么
这是登录发生的地方。当某人证明他们拥有一个电子邮件地址时,这段代码决定他们可以访问什么以及该访问持续多久。
您将看到 OAuth 流程、会话管理和令牌刷新逻辑。没什么可怕的——只是精心编排的舞蹈。
使用展厅、部分或章节来组织旅程:
## 导览
### 展厅 1:前门
访客如何到达并证明其身份。
### 展厅 2:记忆室
我们如何记住谁已登录。
### 展厅 3:续期办公室
会话如何在不中断工作的情况下保持新鲜。
突出可迁移的经验教训:
## 值得借鉴的模式
**“已在处理中”检查。** 在开始一项昂贵的操作之前,检查它是否已经在运行。简单,但容易忘记。
**集中式错误处理。** 所有身份验证失败都流经一个函数。一个地方修复,一个地方记录。
提供诚实的反思:
## 经验教训
我们最初尝试了无状态 JWT。他们说更简单。他们承诺可扩展。但登出是不可能的——令牌无法撤销。
基于会话的身份验证更古老,但它有效。有时,平淡无奇才是对的。
链接到相关展品:
## 继续您的导览
- **[数据库展品](./database.md)** — 会话存储之处
- **[API 展品](./api.md)** — 受保护路由如何检查访问权限
- **[安全展品](./security.md)** — 速率限制和保护
---
_— Autumn, 2026年1月_
或者一句诗意的单行语:
---
_信任是通过一次次的已验证请求建立起来的。_
展示过程和关系:
访客 Grove Google
│ │ │
│ "让我登录" │ │
│ ───────────────────────>│ │
│ │ "这是谁?" │
│ │ ────────────────────────>│
│ │ │
│ │ "是 autumn@..." │
│ │ <────────────────────────│
│ │ │
│ "欢迎回来" │ │
│ <───────────────────────│ │
│ │ │
| 文件 | 功能 | 重要性 |
| --------------- | -------------------- | -------------- |
| `auth.ts` | 处理 OAuth 流程 | 前门 |
| `session.ts` | 管理登录状态 | 记忆 |
| `middleware.ts` | 检查每个请求 | 保镖 |
永远不要孤立地展示代码。总是在之前或之后解释:
// 每个请求都经过这个检查点
export async function authGuard(request: Request): Promise<Response | null> {
const session = await getSession(request);
if (!session) {
return redirect("/login");
}
return null; // 继续访问页面
}
如果你没有登录,就去登录页面。如果你已登录,就继续。简单的检查点逻辑。
通用风格(避免):
本模块提供了健壮的功能,以无缝的方式处理身份验证流程,利用了行业标准的 OAuth 协议。
博物馆风格(使用):
这是登录发生的地方。当某人证明他们拥有一个电子邮件地址时,这段代码决定他们可以访问什么。
要记录整个代码库,创建一个中心的 MUSEUM.md 作为入口:
# 欢迎来到 Grove 博物馆
> 关于这片森林如何生长的导览。
## 展馆
- **[架构展馆](./docs/exhibits/architecture.md)** — 整体概览
- **[身份验证展品](./docs/exhibits/auth.md)** — 信任与身份
- **[数据库展厅](./docs/exhibits/database.md)** — 数据存放之处
针对单个复杂功能或目录:
# 编辑器展品
> 文字变成文章的地方。
这个目录包含了驱动写作体验的一切...
针对多部分系统,使用展厅:
## 展厅 1:数据层
文章如何存储和检索。
## 展厅 2:API 端点
访客敲响的门。
## 展厅 3:编辑器组件
浏览器中魔法发生的地方。
## 展厅 4:安全考量
确保一切安全。
在最终确定任何博物馆式文档之前:
@autumnsgrove/lattice/ui 中的 GroveTerm、GroveSwap 或 GroveText,而不是硬编码术语。新访客默认看到标准术语;Grove 模式用户看到自然主题的词汇。在 markdown 内容中使用 [[term]] 语法,通过 rehype-groveterm 插件进行自动转换。# 会话展品
> Grove 如何记住你是谁。
## 您正在看什么
当你登录时,Grove 需要记住你。不是永远——只是在你访问期间。这个展品展示了这种记忆是如何工作的。
你将看到 cookie、数据库表,以及每次页面加载时发生的“你是谁?”的精心编排的舞蹈。
---
## 展厅 1:Cookie 罐
会话从一个 cookie 开始。不是巧克力豆那种——是一小段你的浏览器保存的文本。
```typescript
const SESSION_COOKIE = "grove_session";
const SESSION_DURATION = 7 * 24 * 60 * 60 * 1000; // 7 天
七天。足够方便,也足够短以保持安全。
当你登录时,我们生成一个随机 ID 并将其存储在这个 cookie 中。ID 本身没有意义——它只是一个用来查找你的密钥。
实际的会话数据存储在数据库中:
| 列名 | 存储内容 |
|---|---|
id | 来自 cookie 的随机密钥 |
user_id | 此会话属于谁 |
created_at | 你何时登录 |
expires_at | 此会话何时结束 |
每次你加载页面时,我们都会在这个表中查找你 cookie 的 ID。如果我们找到了(并且它没有过期),你就仍然处于登录状态。
会话会过期。但每周都登录很烦人。
所以我们进行静默刷新:当你的会话生命期过半时,我们延长它。你永远不会注意到。你只是在使用期间保持登录状态。
if (session.expiresAt < Date.now() + SESSION_DURATION / 2) {
await extendSession(session.id);
}
活跃的访客保持登录。被遗忘的会话过期。
“过半刷新”模式。 不要等到过期才延长会话。在访客活跃时延长它们。
随机 ID 优于顺序 ID。 会话 ID 应该是不可预测的。切勿将自增数字用于安全令牌。
我们曾经将会话数据存储在 cookie 本身中(JWT)。我们认为更简单。但后来我们无法撤销会话——如果某人的令牌泄露,我们无法使其失效。
基于数据库的会话是更古老的技术。有时,更古老意味着更明智。
— Autumn, 2026年1月
博物馆式文档尊重读者的时间和智力,同时承认代码是值得精心管理的知识。
阅读这些展品的 Wanderer 应该离开时不仅理解 Grove 做什么,而且理解它为什么那样做。他们应该感觉像是被欢迎进入了工作坊,而不是被递了一本手册。
每个代码库都有故事。博物馆式文档讲述它们。
每周安装量
48
代码仓库
[autumnsgrove/groveengine](https://github.com/autumnsgrove/groveengine "autumnsgrove/groveengine")
GitHub 星标数
2
首次出现
2026年2月5日
安全审计
[Gen Agent Trust HubPass](/autumnsgrove/groveengine/museum-documentation/security/agent-trust-hub)[SocketPass](/autumnsgrove/groveengine/museum-documentation/security/socket)[SnykPass](/autumnsgrove/groveengine/museum-documentation/security/snyk)
安装于
codex48
gemini-cli48
opencode48
cline47
github-copilot47
kimi-cli47
Documentation as hospitality. Code as curated collection.
The art of transforming technical systems into welcoming guided tours. Museum documentation positions the writer as a guide walking alongside readers through "why" questions before diving into implementation specifics.
This is Grove's elegant documentation style—meant for Wanderers of any experience level who want to understand the technologies, patterns, and decisions that make Grove what it is.
Not for:
Documentation should function as hospitality.
When readers enter a codebase or system, they're visitors unfamiliar with its architecture and design decisions. Museum-style documentation positions the writer as a guide rather than a reference compiler, walking alongside readers through "why" questions before diving into implementation.
🌲 Welcome 🌲
╭─────────────────────╮
│ │
│ ╭─────────────╮ │
│ │ EXHIBIT A │ │
│ │ │ │
│ │ How Login │ │
│ │ Works │ │
│ │ │ │
│ ╰─────────────╯ │
│ │ │
│ ═════╪═════ │
│ │ │
│ ╭─────────────╮ │
│ │ EXHIBIT B │ │
│ ╰─────────────╯ │
│ │
╰─────────────────────╯
Walk through. Take your time.
Every exhibit tells a story.
A museum doesn't hand you a catalog and wish you luck. It guides you through a curated experience, placing context before complexity, stories before specifications.
Start exhibits by establishing context. Before showing code or architecture, tell readers:
Instead of:
The TokenRefreshMap stores active refresh operations keyed by user ID.
Write:
When someone's login expires, we need to refresh their credentials without logging them out. This map coordinates that process—preventing duplicate refresh attempts when multiple tabs are open.
Code shows what. Documentation explains why.
Rather than stating that a Map stores token refresh operations, describe the coordination problem it solves. Connect the abstraction to the experience it creates.
Connect technical concepts to familiar experiences:
| Technical Concept | Museum Metaphor |
|---|---|
| Database | A filing cabinet with organized drawers |
| Cache | A quick-lookup shelf by the door |
| Middleware | A security checkpoint you pass through |
| Queue | A line where requests wait their turn |
| Worker | A helpful assistant handling tasks in the background |
| Webhook | A doorbell that rings when something happens |
Present actual code snippets followed by clear explanation of their significance:
const pending = this.refreshInProgress.get(userId);
if (pending) return pending;
If a refresh is already happening for this person, we wait for that one instead of starting another. One kitchen, one cook.
Museum exhibits follow consistent anatomy:
# The Authentication Exhibit
> Where login happens. Where trust begins.
Orient the reader immediately:
## What You're Looking At
This is where login happens. When someone proves they own an email
address, this code decides what they can access and how long that
access lasts.
You'll see OAuth flows, session management, and token refresh logic.
Nothing scary—just careful choreography.
Use galleries, parts, or sections to organize the journey:
## The Tour
### Gallery 1: The Front Door
How visitors arrive and prove who they are.
### Gallery 2: The Memory Room
How we remember who's logged in.
### Gallery 3: The Renewal Office
How sessions stay fresh without interrupting work.
Highlight transferable lessons:
## Patterns Worth Stealing
**The "already in progress" check.** Before starting an expensive
operation, check if it's already running. Simple, but easy to forget.
**Centralized error handling.** All auth failures flow through one
function. One place to fix, one place to log.
Offer honest reflection:
## Lessons Learned
We tried stateless JWTs first. Simpler, they said. Scalable, they
promised. But logout was impossible—tokens couldn't be revoked.
Session-based auth is older, but it works. Sometimes boring is right.
Link to related exhibits:
## Continue Your Tour
- **[The Database Exhibit](./database.md)** — Where sessions are stored
- **[The API Exhibit](./api.md)** — How protected routes check access
- **[The Security Exhibit](./security.md)** — Rate limiting and protection
---
_— Autumn, January 2026_
Or a poetic one-liner:
---
_Trust is built one verified request at a time._
Show processes and relationships:
Visitor Grove Google
│ │ │
│ "Log me in" │ │
│ ───────────────────────>│ │
│ │ "Who is this?" │
│ │ ────────────────────────>│
│ │ │
│ │ "It's autumn@..." │
│ │ <────────────────────────│
│ │ │
│ "Welcome back" │ │
│ <───────────────────────│ │
│ │ │
| File | What It Does | Why It Matters |
| --------------- | -------------------- | -------------- |
| `auth.ts` | Handles OAuth flow | The front door |
| `session.ts` | Manages login state | The memory |
| `middleware.ts` | Checks every request | The bouncer |
Never show code in isolation. Always explain before or after:
// Every request passes through this checkpoint
export async function authGuard(request: Request): Promise<Response | null> {
const session = await getSession(request);
if (!session) {
return redirect("/login");
}
return null; // Continue to the page
}
If you're not logged in, you go to login. If you are, you continue. Simple checkpoint logic.
Generic (avoid):
This module provides robust functionality for handling authentication flows in a seamless manner, leveraging industry-standard OAuth protocols.
Museum style (use):
This is where login happens. When someone proves they own an email address, this code decides what they can access.
For documenting an entire codebase, create a central MUSEUM.md that serves as the entrance:
# Welcome to the Grove Museum
> A guided tour of how this forest grows.
## The Wings
- **[The Architecture Wing](./docs/exhibits/architecture.md)** — The big picture
- **[The Authentication Exhibit](./docs/exhibits/auth.md)** — Trust and identity
- **[The Database Galleries](./docs/exhibits/database.md)** — Where data lives
For a single complex feature or directory:
# The Editor Exhibit
> Where words become posts.
This directory contains everything that powers the writing experience...
For multi-part systems, use galleries:
## Gallery 1: Data Layer
How posts are stored and retrieved.
## Gallery 2: API Endpoints
The doors visitors knock on.
## Gallery 3: The Editor Component
Where the magic happens in the browser.
## Gallery 4: Security Considerations
Keeping things safe.
Before finalizing any museum documentation:
GroveTerm, GroveSwap, or GroveText from @autumnsgrove/lattice/ui instead of hardcoding terms. New visitors see standard terms by default; Grove Mode users see the nature-themed vocabulary. Use [[term]] syntax in markdown content for auto-transformation via the rehype-groveterm plugin.# The Session Exhibit
> How Grove remembers who you are.
## What You're Looking At
When you log in, Grove needs to remember you. Not forever—just long
enough for your visit. This exhibit shows how that memory works.
You'll see cookies, database tables, and the careful dance of
"who are you?" that happens with every page load.
---
## Gallery 1: The Cookie Jar
A session starts with a cookie. Not the chocolate chip kind—a small
piece of text your browser holds onto.
```typescript
const SESSION_COOKIE = "grove_session";
const SESSION_DURATION = 7 * 24 * 60 * 60 * 1000; // 7 days
```
Seven days. Long enough to be convenient, short enough to stay secure.
When you log in, we generate a random ID and store it in this cookie. The ID itself means nothing—it's just a key to look you up.
The actual session data lives in the database:
| Column | What It Holds |
|---|---|
id | The random key from the cookie |
user_id | Who this session belongs to |
created_at | When you logged in |
expires_at | When this session ends |
Every time you load a page, we look up your cookie's ID in this table. If we find it (and it hasn't expired), you're still logged in.
Sessions expire. But logging in every week is annoying.
So we do a quiet refresh: when your session is more than halfway through its life, we extend it. You never notice. You just stay logged in while you're active.
if (session.expiresAt < Date.now() + SESSION_DURATION / 2) {
await extendSession(session.id);
}
Active visitors stay. Abandoned sessions expire.
The "halfway refresh" pattern. Don't wait until expiration to extend sessions. Extend them while the visitor is active.
Random IDs over sequential. Session IDs should be unpredictable. Never use auto-incrementing numbers for security tokens.
We used to store session data in the cookie itself (JWTs). Simpler, we thought. But then we couldn't revoke sessions—if someone's token leaked, we had no way to invalidate it.
Database sessions are older technology. Sometimes older is wiser.
— Autumn, January 2026
---
## The Underlying Purpose
Museum documentation respects readers' time and intelligence while acknowledging that code is knowledge deserving careful stewardship.
Wanderers who read these exhibits should leave understanding not just *what* Grove does, but *why* it does it that way. They should feel like they've been welcomed into the workshop, not handed a manual.
*Every codebase has stories. Museum documentation tells them.*
Weekly Installs
48
Repository
GitHub Stars
2
First Seen
Feb 5, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex48
gemini-cli48
opencode48
cline47
github-copilot47
kimi-cli47
API文档生成与管理指南:OpenAPI规范、Swagger使用教程与团队协作
11,700 周安装