AI Workspace Hub
Vault template
First committed by Benboerba620
Published in Benboerba620/ai-workspace-hub. Publisher page
83 GitHub stars
What it is
All-in-one AI 研究工作台:把 Codex / Claude Code / Cursor 变成有长期记忆的研究系统。wiki 摄入、研究闭环、快速筛选、假设追踪、播客、日报六大能力,零 API key 可跑基座。
Summary from the brain’s metadata
All-in-one AI 研究工作台:把 Codex / Claude Code / Cursor 变成有长期记忆的研究系统。wiki 摄入、研究闭环、快速筛选、假设追踪、播客、日报六大能力,零 API key 可跑基座。 Turn Codex, Claude Code, Cursor, and Cline into a persistent research workspace.
How it works
Memory model: Markdown vault
AutoGineer overview, from the brain’s own files
Memory model: Markdown vault. Folders: .github (agent config, 1 file); docs (docs, 2 files); examples (examples, 22 files); inbox (raw sources, 3 files); tests (tests, 18 files); tools (scripts, 39 files); wiki (wiki, 15 files). The agent reads hypothesis/, wiki/explorations/.
| Folder | Role | Files |
|---|---|---|
| .github | agent-config | 1 |
| docs | docs | 2 |
| examples | examples | 22 |
| inbox | raw-sources | 3 |
| tests | tests | 18 |
| tools | scripts | 39 |
| wiki | wiki | 15 |
| config | folder | 2 |
| evidence | folder | 1 |
| hypothesis | folder | 1 |
| output | folder | 7 |
| portfolio | folder | 2 |
| system | folder | 52 |
| workspace | folder | 11 |
The agent reads
- hypothesis/ evidence: AGENTS.md
- wiki/explorations/ evidence: AGENTS.md
Requirements
Runtimes: Python
Works with: Claude Code, OpenAI Codex, GitHub Copilot, Cursor, Kiro, OpenCode, Amp, Cline, JetBrains Junie, Devin (ex-Windsurf)
Set it up
Clone the repository at the commit AutoGineer scanned:
git clone https://github.com/Benboerba620/ai-workspace-hub.git && cd ai-workspace-hub && git checkout e01a41a06886c6d59f714e6c133004d241ea91acClaude Code reads CLAUDE.md from this folder automatically.
cd ai-workspace-hub && claude
AutoGineer scanned these files at commit e01a41a.
First committed by Benboerba620. Published in Benboerba620/ai-workspace-hub. Licence: MIT License.
Usage
AutoGineer’s guidance for this type of brain
Copy the vault into a folder, open it in your notes app, and point your agent at its instruction file: the agent reads and writes the notes as its memory.
Documentation by Benboerba620
<div align="center">
AI Workspace Hub
Turn Codex, Claude Code, Cursor, and Cline into a persistent research workspace.
把 AI 编程助手变成一套能长期记忆、持续研究、自动沉淀的工作台。
Image: CI Image: Python 3.10+ Image: GitHub stars Image: License
30 秒上手 · 六大能力 · 免费数据源 · 第一周怎么用 · 故障排查
Image: 完整流程演示:一句话安装 → Core Mode READY → 首次股票研究 → 研究偏好 → 主题假设 → 日报跟踪
<sub>演示动图按安装协议与 <a href="START-HERE.md">首次股票研究流程</a>脚本化生成(<a href="docs/gen_demo_gif.py">生成脚本</a>),非实录。</sub>
</div>
最近更新(2026-09-30):当前 Agent 可直接完成材料摘要与标签;日报显示数据来源、日期和缺失状态;安装附带免费数据源指南,daily-watch 与 podcast 均已内置。
对话很聪明,但工作流没有记忆;今天做完,明天又从头解释。
AI Workspace Hub 解决这个问题。六大能力开箱即用——核心工作流不需要 API key。
📖 想先了解设计思路?看作者的公众号长文:《从0构建 AI 协作系统(一):从最小可运行的 MVP 开始》
⚡ 30 秒上手
把下面这句话发给你的 AI agent(Codex / Claude Code / Cursor / Cline):
帮我按这个协议安装 AI Workspace Hub:
https://raw.githubusercontent.com/Benboerba620/ai-workspace-hub/main/INSTALL-FOR-AI.md
抓不到协议全文就先 git clone 本仓库,再读其中的 INSTALL-FOR-AI.md 逐字执行。Agent 会问你 4 个问题(路径、用途、现有 wiki、是否使用 Obsidian),然后自动创建完整工作区。装好后,先用样例确认安装:
把 inbox/first-note.md 整理进 personal wiki。预期:agent 读 AGENTS.md → 按 wiki/_schema.md 整理 → 写入 wiki/sources/ → 在 active-context.md 记录进度。
不需要 API key,不需要联网,不需要写任何代码。
样例通过后,开始第一份真实股票研究:
研究一家公司,同时帮我建立第一版研究偏好。
公司是:____;我现在关注它是因为:____。这次会同时产出研究报告、研究偏好 v0.1、可验证假设和后续跟踪指标。以后研究其他公司会复用并继续校准这套方法,而不是每次从空白开始。
<details> <summary>还没有 AI agent?先花 2 分钟装一个</summary>
任选其一,都有免费或试用档:
Claude Code(推荐):先装 Node.js,然后终端运行
npm install -g @anthropic-ai/claude-code,在任意目录输入claude登录 Claude 账号。Codex CLI:
npm install -g @openai/codex,输入codex登录 ChatGPT 账号。Cursor:去 cursor.com 下载安装,打开一个文件夹后用内置对话。
装好后回到上面,把那句话发给它就行。
</details>
<details> <summary>更习惯手动 clone?</summary>
git clone https://github.com/Benboerba620/ai-workspace-hub.git my-ai-workspace
cd my-ai-workspace用 AI agent 打开这个目录,直接试跑 inbox/first-note.md → wiki。
验证安装状态:
python3 system/scripts/check_workspace.py # Windows: python system/scripts/check_workspace.pyCore Mode 显示 READY 即可开始使用。
</details>
📈 股票研究主流程
首次研究引导 → 研究偏好 v0.1 → 研究结论 → 可验证假设
→ 跟踪指标 → daily-watch 日报 → 独立证据 → 假设复盘 → 长期知识START-HERE.md 是安装后的用户首页。信息摄入只是输入环节;系统的主线是让研究结论进入假设,并通过日报持续验证。
每份研究、假设和证据都有稳定 ID;当前状态只保存在 Markdown frontmatter。需要用户确认的 wiki 沉淀、假设调整、股票池新增和研究偏好变化会进入持久待确认队列。直接说:
看看我的研究系统现在有什么需要处理。Agent 会汇总进行中的研究、待复盘证据、到期假设和待确认动作,并给出下一步。
🧩 六大能力
| 能力 | 你可以怎么说 | 写入哪里 | API key | |
|---|---|---|---|---|
| 📚 | wiki | "把这篇文章整理进知识库" | wiki/ | 推荐(自动标签) |
| 🔬 | research | "帮我研究一下某公司 / 某行业" | output/research/ | 不需要 |
| 🔍 | screen | "帮我筛选 AI 产业链股票" | output/screen/ | 可选 |
| 📊 | daily-watch | "生成今天的盯盘日报" | output/daily-watch/ + evidence/ | 可选 |
| 🧪 | hypothesis | "把这个投资假设建档并追踪" | hypothesis/ | 不需要 |
| 🎙️ | podcast | "扫一下这几个播客并写进 wiki" | wiki/sources/ + output/pod2wiki/ | 当前 Agent 无需独立 Key;后台摘要需 Key |
daily-watch 和 podcast 已经内置在 Hub 中,不需要再单独安装。 后台自动标签、独立运行的播客摘要和部分行情源需要按需填写 API key;交互整理可由当前 Agent 完成。
零 key 可用:wiki 可由当前 Agent 摄入和补齐有依据的标签,不调用独立模型 API;research、hypothesis、screen(websearch 模式)、daily-watch(报告骨架 + 美股降级源)照常可用。
默认建议配置:先按 免费数据源接入指南 选择市场——美股可零 Key 使用 Nasdaq,A 股可从 Tushare 的基础日线档起步,yfinance 可作为个人研究备用;再按需配置 LLM、FMP 或 Longbridge Skill。
🔄 工作流程
flowchart LR
IN["inbox / web"] --> TAG["统一自动标签"]
TAG --> KB["wiki 知识库"]
KB --> R["research R*"]
R --> H["hypothesis H*"]
H --> W["唯一执行股票池"]
W --> D["daily-watch 日报"]
D --> E["evidence E-*"]
E -->|"用户确认后复盘"| H
R --> Q["review-queue Q-*"]
H --> Q
Q -->|"确认后执行"| KB
S["workspace status"] -.-> R
S -.-> H
S -.-> E
S -.-> Q核心不是"文件夹长什么样",而是:agent 进入目录后知道先读什么、做研究时先查本地 wiki、输出时事实与推测分开、证据和判断不混写、待确认动作不会随对话消失、明天继续时能从断点接上。
wiki 变大后也不会每次全量加载。每次研究先运行 Research Preflight:按 rule -> pattern -> exploration 加载相关可复用知识,同时扫描 entities / concepts / sources,只返回少量命中页面、命中理由和过期/冲突提示,再决定哪些问题需要联网。扫描不调用大模型,并生成可检查的回执。知识按 source -> exploration -> pattern -> rule 逐级提炼;每次晋级、降级或退役都保留用户确认、复审日期和失效条件。
材料进入 wiki 时按同一标签契约补齐 domain / ticker / concepts / related / entity_salience / tags。交互模式由当前 Agent 处理,后台模式使用统一打标器;历史补标默认只预览,已有人工字段不会被覆盖。
用真实案例理解 Wiki 晋级
产能扩张周期见顶案例 把一条真实研究判断拆成 4 个 Source、2 个已提炼 Exploration 和 1 张 draft Pattern:光伏与锂电是产生该 Pattern 的母案例,因此不能再次冒充独立确认,primary_confirmations 保持为 0。日常研究默认不会加载这张草稿卡;复盘模式可以找到它,未来经过两次新的独立事前验证并由用户确认后,才允许变成 active Pattern。累计三次独立确认后,再讨论是否形成可执行 Rule。
案例目录可以独立运行 summary 和 load,仓库测试同时守住“母案例不计确认、draft 不进入日常加载、满足门槛后才可晋级”三条边界。
系统不在某个模型里,而在这套文件协议里。谁读懂这套协议,谁就接上你的工作流。
⚙️ Core Mode / Enhanced Mode
| 模式 | API key | 能做什么 | 适合 |
|---|---|---|---|
| Core | 不需要 | wiki 摄入(无脚本自动标签)、研究草稿、快速筛选、假设建档、断点续传 | 临时未配置 API 时 |
| Enhanced | 按市场选填 | wiki 自动标签、播客摘要、行情日报、A 股 / 多市场数据、自动监控 | 默认日常使用 |
推荐路径:安装后运行 check_workspace.py → 按 DATA-SOURCES.md 选择免费或付费数据源 → 只填写实际需要的 key。暂时没有 key 时仍可先跑 Core Mode,美股基础行情还可走 Nasdaq 无 Key 源。
📅 第一周怎么用
| 天数 | 动作 | 目标 |
|---|---|---|
| Day 1 | 用当前 Agent 跑通 inbox/first-note.md → wiki;后台自动化再配置 Key | 确认摘要、自动标签和分类都能工作 |
| Day 2 | 放入 3-5 条真实材料 | 建立第一批知识库 |
| Day 3 | 让 agent 研究一个公司 / 行业 | 生成第一篇结构化报告 |
| Day 4 | check_workspace.py,按免费数据源指南选择 Nasdaq / Tushare / yfinance 等 | 增强行情能力 |
| Day 5 | 做一次主题筛选 | 形成候选池 |
| Day 6 | 扫一次播客 / 博客 | 建立外部信息流 |
| Day 7 | 运行结构体检,删掉没用规则 | 防止系统变胖 |
适合谁?
适合:投资研究员、个人投资者、内容创作者、AI power user、Markdown / Obsidian 用户、想做长期项目而不是一次性对话的人。
不适合:想要 GUI 应用、自动交易系统,或不愿意用 Markdown 管理知识的人。
<details> <summary><b>📂 目录结构</b></summary>
ai-workspace-hub/
├── AGENTS.md # Codex 入口路由
├── CLAUDE.md # Claude Code 入口路由
├── INSTALL-FOR-AI.md # 交给 AI agent 的安装协议
├── SMOKE-TEST.md # 冒烟测试
├── ARCHITECTURE.md # 架构说明
├── TROUBLESHOOTING.md # 故障排查
├── workspace/ # 配置、断点、看板与归档
│ ├── monitoring/ # 用户阅读的监控看板
│ └── archive/ # 退出主流程的历史材料
├── inbox/ # 临时输入材料
├── wiki/ # personal wiki(exploration / pattern / rule)
├── output/ # 研究、筛选、播客和日报输出
│ └── daily-watch/ # 日报归档
├── hypothesis/ # 投资假设和复盘
├── evidence/ # 独立证据账本
├── portfolio/ # 交易记录
├── config/ # 用户配置,不入 git
├── tools/ # podcast / daily-watch 工具
└── system/ # skills / integrations / scripts / templates</details>
<details> <summary><b>🗄️ 数据源与边界</b></summary>
| 名称 | 类型 | 用途 | 是否内置 |
|---|---|---|---|
| Nasdaq | 无 key 降级源 | 美股基础行情 | 是 |
| Finnhub | 免费账户 / 付费 | 美股行情备选;免费端点和额度以官方账户为准 | 是,需 key |
| EODHD | 免费小额度 / 付费 | 港股、韩国、芬兰等最近 EOD 备用;免费账户当前 20 次/日,不是实时价 | 是,需 key |
| yfinance | 无 Key | 多市场个人研究备用;非官方 Yahoo SDK,需额外安装 | 是,需开启 |
| tushare | 积分 / 单独权限 | A 股行情;当前 120 积分档可取非复权日线 | 是,需 token |
| FMP | 免费 Basic / 付费 | 免费档适合 EOD/profile 测试;全球覆盖和高级数据取决于套餐 | 是,需 key |
| Longbridge Skill | 外部 Agent 扩展 | 多市场查询、筛选、研究 | 否,独立安装授权 |
| websearch | Agent 能力 | 补充新闻、资料、公司信息 | 取决于 agent |
重要边界:本项目不做自动交易,不承诺数据源永远免费,不把 AI 输出包装成投资建议。缺少 API key 时优雅降级,不会让用户误以为工作区坏了。
</details>
<details> <summary><b>💻 常用命令</b></summary>
python3 system/scripts/check_workspace.py # 总检查
python3 system/scripts/workspace_status.py # 当前研究、证据和待办
python3 system/scripts/research_preflight.py --root . --context "你的研究问题和关键词" --research-id R-... --research-file output/research/报告.md --record # 扫描 Wiki 并更新研究报告
python3 system/scripts/knowledge_lifecycle.py --root . summary # 知识状态、到期复审和晋级候选
python3 system/scripts/knowledge_lifecycle.py --root . rebuild-index --apply # 重建三个短索引
python3 system/scripts/review_queue.py list # 查看持久待确认队列
python3 system/scripts/wiki_tagger.py --root . backfill wiki # 预览历史 wiki 补标
python3 -m unittest discover -s tests -v # 运行测试
python3 tools/daily-watch/scripts/check_setup.py --init # 初始化日报配置
python3 tools/podcast/scripts/fetch_podcasts.py --help # 播客工具帮助Python 工具需要 3.10+。Windows 用户用 python 代替 python3,或 py -3 指定版本。
</details>
<details> <summary><b>❓ 新手常见问题</b></summary>
Q:我不会写代码,能用这个项目吗?
可以。Core Mode 只需要把文本放进 inbox/,然后用自然语言告诉 AI agent 做什么。
Q:一定要用 Codex / Claude Code 吗? 不一定。任何能读写文件的 AI agent 都可以。Codex 和 Claude Code 效果最好,Cursor 和 Cline 也可以。
Q:安装 Hub 后,还要单独安装 daily-watchlist 或 pod2wiki 吗?
不需要。两套工具及其配置模板已经包含在 tools/daily-watch/ 和 tools/podcast/;你只需要按用途安装可选依赖,并按 免费数据源接入指南 选择是否配置 key。
Q:wiki 越来越大怎么办?
用 Obsidian 打开 wiki/ 目录做可视化管理,或让 AI agent 帮你整理归档。
Q:API key 会不会泄露?
config/ 已被 .gitignore 排除,不会被 git 提交。
Q:自动标签会把什么发给大模型?
会把当前 wiki 页面的 frontmatter 和正文发送给你在 config/pod2wiki.env 选择的模型服务商,只用于返回结构化标签。敏感材料应先确认所选服务商的数据政策,或跳过自动标签。
Q:可以和 Obsidian 一起用吗?
可以。安装时选择 Obsidian(或在已有 vault 中安装),Hub 会增加 reading-hub.md 和 reading-hub.base,用 9 个动态视图汇总待读内容;可直接在表格中填写 已读 / 精读 / 跳过,并优先按资料自身日期而不是本机文件时间排序。默认不安装,也不影响非 Obsidian 用户。需要 Obsidian 1.9+ 的 Bases 功能。
更多问题见 TROUBLESHOOTING.md。
</details>
Roadmap
更多真实研究工作流样例
更强的 screen 预设
更完善的 daily-watch 降级策略
证据来源质量分级和跨来源事件去重
知识索引的自动健康检查与过期提示
Contributing
欢迎提 issue / PR,尤其是新 agent 适配、研究报告模板、数据源接入、真实使用摩擦点、README / 教程 / 安装流程改进。
Star History
如果这个项目对你有帮助,欢迎点一下 ⭐ Star。它会帮助更多需要"长期 AI 工作流"的人发现这个项目,也会让我知道哪些方向值得继续做。
Disclaimer
AI Workspace Hub 是个人研究与知识管理工具,不构成投资建议。所有市场数据、公司信息、财务数字、新闻和监管信息都应以原始来源为准。
<!-- 文件说明:项目首页、价值介绍和快速上手。 -->
为什么已经在 Agent 中使用,还可能需要模型 Key?
交互整理可以由当前 Agent 完成:播客工具使用 --agent-handoff 准备原文与待处理队列,Agent 阅读原文后完成摘要和标签,不要求另配模型 Key。脚本在后台独立生成摘要才需要模型 API。两种方式的步骤与完成标准见 Podcast 流程。
system/scripts/knowledge_lifecycle.py
Shown as plain text.
#!/usr/bin/env python3
"""Manage, summarize, and selectively load reusable workspace knowledge."""
from __future__ import annotations
import argparse
import hashlib
import json
import re
import sys
from collections import Counter
from datetime import date, datetime, timezone
from pathlib import Path
from typing import Any
try:
import yaml
except ImportError: # Core Mode remains dependency-free.
yaml = None
for _stream in (sys.stdout, sys.stderr):
if hasattr(_stream, "reconfigure"):
_stream.reconfigure(encoding="utf-8", errors="replace")
TYPE_PATHS = {
"exploration": ("wiki/explorations",),
# workspace/patterns remains readable for workspaces created during the preview period.
"pattern": ("wiki/patterns", "workspace/patterns"),
"rule": ("wiki/rules",),
}
INDEX_PATHS = {
"exploration": "wiki/explorations/_index.md",
"pattern": "wiki/patterns/_index.md",
"rule": "wiki/rules/_index.md",
}
ID_PREFIXES = {"exploration": "EXP-", "pattern": "PAT-", "rule": "RULE-"}
STATUSES = {
"exploration": {
"tentative",
"validated",
"weakened",
"invalidated",
"promoted",
"archived",
},
"pattern": {"draft", "active", "weakened", "promoted", "retired", "archived"},
"rule": {"candidate", "active", "weakened", "deprecated", "archived"},
}
DEFAULT_LOAD_STATUSES = {
"exploration": {"tentative", "validated", "promoted"},
"pattern": {"active", "promoted"},
"rule": {"active"},
}
TRANSITIONS = {
"exploration": {
"tentative": {"validated", "invalidated", "archived"},
"validated": {"weakened", "promoted", "archived"},
"weakened": {"validated", "invalidated", "archived"},
"invalidated": {"tentative", "archived"},
"promoted": {"weakened", "archived"},
"archived": {"tentative"},
},
"pattern": {
"draft": {"active", "retired", "archived"},
"active": {"weakened", "promoted", "retired", "archived"},
"weakened": {"active", "retired", "archived"},
"promoted": {"weakened", "retired", "archived"},
"retired": {"draft", "archived"},
"archived": {"draft"},
},
"rule": {
"candidate": {"active", "archived"},
"active": {"weakened", "deprecated", "archived"},
"weakened": {"active", "deprecated", "archived"},
"deprecated": {"candidate", "archived"},
"archived": {"candidate"},
},
}
LIST_FIELDS = (
"domain",
"ticker",
"concepts",
"tags",
"recall_signals",
"decision_scenarios",
)
def find_workspace_root(start: Path) -> Path:
current = start.resolve()
if current.is_file():
current = current.parent
for candidate in (current, *current.parents):
if (candidate / "workspace" / "workspace-config.md").is_file():
return candidate
return current
def relative(path: Path, root: Path) -> str:
return str(path.relative_to(root)).replace("\\", "/")
def read_page(path: Path) -> tuple[dict[str, Any], str, str | None]:
try:
content = path.read_text(encoding="utf-8-sig")
except (OSError, UnicodeError) as exc:
return {}, "", str(exc)
match = re.match(r"^---\s*\n(.*?)\n---\s*(?:\n|$)", content, re.DOTALL)
if not match:
return {}, content, "missing frontmatter"
raw = match.group(1)
if yaml is not None:
try:
payload = yaml.safe_load(raw) or {}
except yaml.YAMLError as exc:
return {}, content, f"invalid YAML: {exc}"
if not isinstance(payload, dict):
return {}, content, "frontmatter is not a mapping"
return payload, content, None
values: dict[str, Any] = {}
current_key: str | None = None
for line in raw.splitlines():
field = re.match(r"^([A-Za-z_][\w-]*):\s*(.*?)\s*$", line)
if field:
key = field.group(1)
value = field.group(2).strip().strip('"\'')
current_key = key if not value else None
if value.startswith("[") and value.endswith("]"):
values[key] = [
item.strip().strip('"\'')
for item in value[1:-1].split(",")
if item.strip()
]
else:
values[key] = value or None
continue
item = re.match(r"^\s*-\s+(.+?)\s*$", line)
if item and current_key:
values.setdefault(current_key, [])
if isinstance(values[current_key], list):
values[current_key].append(item.group(1).strip('"\''))
return values, content, None
def as_list(value: Any) -> list[str]:
if value is None:
return []
if isinstance(value, list):
return [str(item).strip() for item in value if str(item).strip()]
return [str(value).strip()] if str(value).strip() else []
def as_int(value: Any) -> int:
try:
return int(value)
except (TypeError, ValueError):
return 0
def iter_pages(root: Path, selected_types: set[str] | None = None) -> list[dict[str, Any]]:
pages: list[dict[str, Any]] = []
wanted = selected_types or set(TYPE_PATHS)
for knowledge_type in TYPE_PATHS:
if knowledge_type not in wanted:
continue
for folder in TYPE_PATHS[knowledge_type]:
base = root / folder
if not base.is_dir():
continue
for path in sorted(base.glob("*.md")):
if path.name.startswith("_") or path.name.lower() == "readme.md":
continue
meta, content, error = read_page(path)
pages.append(
{
"type": knowledge_type,
"path": path,
"relative_path": relative(path, root),
"meta": meta,
"content": content,
"error": error,
}
)
return pages
def is_overdue(value: Any, today: date | None = None) -> bool:
if not value:
return False
try:
return date.fromisoformat(str(value)[:10]) < (today or date.today())
except ValueError:
return False
def promotion_candidate(page: dict[str, Any]) -> str | None:
meta = page["meta"]
knowledge_type = page["type"]
status = str(meta.get("status") or "")
if knowledge_type == "exploration" and status == "validated":
return "可复盘是否提炼为 pattern"
if (
knowledge_type == "pattern"
and status == "active"
and as_int(meta.get("primary_confirmations")) >= 3
):
return "已满足 rule 的案例数量门槛"
if (
knowledge_type == "rule"
and status == "candidate"
and as_int(meta.get("independent_confirmations")) >= 3
):
return "可确认是否启用为 active rule"
return None
def summarize(root: Path) -> dict[str, Any]:
pages = iter_pages(root)
counts: dict[str, dict[str, int]] = {}
overdue: list[dict[str, str]] = []
candidates: list[dict[str, str]] = []
calls = Counter()
for page in pages:
knowledge_type = page["type"]
status = str(page["meta"].get("status") or "missing")
counts.setdefault(knowledge_type, {})[status] = (
counts.setdefault(knowledge_type, {}).get(status, 0) + 1
)
item_id = str(page["meta"].get("id") or page["path"].stem)
if is_overdue(page["meta"].get("review_due")):
overdue.append({"id": item_id, "type": knowledge_type, "path": page["relative_path"]})
candidate = promotion_candidate(page)
if candidate:
candidates.append(
{"id": item_id, "type": knowledge_type, "reason": candidate, "path": page["relative_path"]}
)
usage_path = root / "workspace" / "knowledge-usage.jsonl"
if usage_path.is_file():
for line in usage_path.read_text(encoding="utf-8-sig").splitlines():
try:
event = json.loads(line)
except json.JSONDecodeError:
continue
if event.get("event") == "load":
calls.update(str(item) for item in event.get("knowledge_ids", []))
return {
"counts": counts,
"overdue": overdue,
"promotion_candidates": candidates,
"most_loaded": [{"id": key, "count": count} for key, count in calls.most_common(10)],
}
def compact(value: Any, limit: int = 3) -> str:
values = as_list(value)
return "、".join(values[:limit]) + ("…" if len(values) > limit else "")
def markdown_link(page: dict[str, Any], index_path: str) -> str:
index_parent = Path(index_path).parent
target = Path(page["relative_path"])
try:
link = target.relative_to(index_parent)
except ValueError:
link = Path("../..") / target
return str(link).replace("\\", "/")
def render_index(root: Path, knowledge_type: str) -> str:
pages = iter_pages(root, {knowledge_type})
index_path = INDEX_PATHS[knowledge_type]
title = {"exploration": "Exploration", "pattern": "Pattern", "rule": "Rule"}[knowledge_type]
intro = (
f"> 自动生成的 {title} 路由摘要。先匹配召回信号和决策场景,再按链接加载全文;"
"不要在本文件手工维护证据。\n"
)
headers = {
"exploration": "| ID | Exploration | 状态 | 一句话结论 | 召回信号 | 复审 |\n|---|---|---|---|---|---|",
"pattern": "| ID | Pattern | 状态 | 一句话结构 | 召回信号 | 失效信号 | 复审 |\n|---|---|---|---|---|---|---|",
"rule": "| ID | Rule | 状态 | 适用范围 | 规则摘要 | 失效信号 | 复审 |\n|---|---|---|---|---|---|---|",
}
rows: list[str] = []
for page in sorted(pages, key=lambda item: str(item["meta"].get("id") or item["path"].stem)):
meta = page["meta"]
item_id = str(meta.get("id") or page["path"].stem)
name = str(meta.get("title") or page["path"].stem).replace("|", "/")
linked = f"[{name}]({markdown_link(page, index_path)})"
status = str(meta.get("status") or "missing")
summary = str(meta.get("summary") or "").replace("|", "/")
review_due = str(meta.get("review_due") or "-")
if knowledge_type == "exploration":
rows.append(
f"| {item_id} | {linked} | {status} | {summary} | {compact(meta.get('recall_signals'))} | {review_due} |"
)
elif knowledge_type == "pattern":
rows.append(
f"| {item_id} | {linked} | {status} | {summary} | {compact(meta.get('recall_signals'))} | "
f"{compact(meta.get('failure_signals'))} | {review_due} |"
)
else:
rows.append(
f"| {item_id} | {linked} | {status} | {str(meta.get('scope') or '').replace('|', '/')} | "
f"{summary} | {compact(meta.get('invalidation_signals'))} | {review_due} |"
)
if not rows:
rows.append("| - | 暂无条目 | - | - | - | - |" + ("" if knowledge_type == "exploration" else " - |"))
return f"# {title} 索引\n\n{intro}\n{headers[knowledge_type]}\n" + "\n".join(rows) + "\n"
def rebuild_indexes(root: Path, apply: bool) -> list[dict[str, Any]]:
results: list[dict[str, Any]] = []
for knowledge_type, relpath in INDEX_PATHS.items():
path = root / relpath
content = render_index(root, knowledge_type)
old = path.read_text(encoding="utf-8-sig") if path.is_file() else None
changed = old != content
if apply and changed:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
results.append({"type": knowledge_type, "path": relpath, "changed": changed, "applied": apply and changed})
return results
def normalized(value: str) -> str:
return re.sub(r"[\s_\-/]+", "", value.lower())
def field_matches(meta: dict[str, Any], field: str, wanted: list[str]) -> bool:
if not wanted:
return True
actual = [normalized(item) for item in as_list(meta.get(field))]
return any(
normalized(query) == item or normalized(query) in item or item in normalized(query)
for query in wanted
for item in actual
if item
)
def score_page(page: dict[str, Any], context: str) -> tuple[int, list[str]]:
meta = page["meta"]
context_norm = normalized(context)
score = 0
reasons: list[str] = []
weighted = (
("recall_signals", 8, "召回信号"),
("decision_scenarios", 6, "决策场景"),
("ticker", 6, "标的"),
("concepts", 4, "概念"),
("domain", 3, "领域"),
("tags", 2, "标签"),
)
for field, weight, label in weighted:
hits = []
for value in as_list(meta.get(field)):
value_norm = normalized(value)
if value_norm and (value_norm in context_norm or context_norm in value_norm):
hits.append(value)
if hits:
score += weight * len(hits)
reasons.append(f"{label}: {compact(hits, 2)}")
title_summary = normalized(f"{meta.get('title') or ''} {meta.get('summary') or ''}")
tokens = [token for token in re.split(r"[\s,,。;;::/]+", context.lower()) if len(token) >= 2]
weak_hits = [token for token in tokens if normalized(token) in title_summary][:3]
if weak_hits:
score += len(weak_hits)
reasons.append("标题/摘要: " + "、".join(weak_hits))
return score, reasons
def load_knowledge(
root: Path,
*,
context: str,
selected_types: set[str],
limit: int,
include_review: bool,
all_active: bool,
filters: dict[str, list[str]],
) -> list[dict[str, Any]]:
matches: list[dict[str, Any]] = []
for page in iter_pages(root, selected_types):
meta = page["meta"]
status = str(meta.get("status") or "")
if not include_review and status not in DEFAULT_LOAD_STATUSES[page["type"]]:
continue
if any(not field_matches(meta, field, wanted) for field, wanted in filters.items()):
continue
score, reasons = score_page(page, context) if context else (0, [])
if not all_active and score <= 0 and not any(filters.values()):
continue
matches.append(
{
"id": str(meta.get("id") or page["path"].stem),
"type": page["type"],
"title": str(meta.get("title") or page["path"].stem),
"status": status,
"summary": str(meta.get("summary") or ""),
"scope": str(meta.get("scope") or ""),
"review_due": str(meta.get("review_due") or ""),
"failure_signals": as_list(meta.get("failure_signals") or meta.get("invalidation_signals")),
"path": page["relative_path"],
"score": score,
"matched_by": reasons or (["显式筛选"] if any(filters.values()) else ["全部生效知识"]),
"content": page["content"],
}
)
type_order = {"rule": 0, "pattern": 1, "exploration": 2}
matches.sort(key=lambda item: (type_order[item["type"]], -item["score"], item["id"]))
return matches[:limit]
def record_load(root: Path, context: str, matches: list[dict[str, Any]], research_id: str) -> str:
now = datetime.now(timezone.utc)
digest = hashlib.sha256(
(now.isoformat() + context + "|".join(item["id"] for item in matches)).encode("utf-8")
).hexdigest()[:8]
invocation_id = f"KU-{now.strftime('%Y%m%dT%H%M%SZ')}-{digest}"
event = {
"event": "load",
"id": invocation_id,
"at": now.isoformat(),
"research_id": research_id or None,
"context": context[:500],
"knowledge_ids": [item["id"] for item in matches],
}
path = root / "workspace" / "knowledge-usage.jsonl"
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("a", encoding="utf-8", newline="\n") as handle:
handle.write(json.dumps(event, ensure_ascii=False) + "\n")
return invocation_id
def transition_errors(page: dict[str, Any], target: str) -> list[str]:
meta = page["meta"]
knowledge_type = page["type"]
current = str(meta.get("status") or "")
errors: list[str] = []
if target not in TRANSITIONS.get(knowledge_type, {}).get(current, set()):
errors.append(f"不允许 {knowledge_type} 从 `{current}` 迁移到 `{target}`")
return errors
if target in {"validated", "active", "promoted"} and not meta.get("review_due"):
errors.append("进入可调用或已晋级状态前必须设置 review_due")
if knowledge_type == "exploration" and target == "validated" and len(as_list(meta.get("based_on"))) < 2:
errors.append("exploration 至少关联两个独立来源才能 validated")
if knowledge_type == "exploration" and target == "promoted" and not meta.get("promoted_to"):
errors.append("exploration 晋级前必须填写 promoted_to")
if knowledge_type == "pattern" and target == "active":
if len(as_list(meta.get("source_explorations"))) < 2:
errors.append("pattern 至少需要两个独立 exploration")
if as_int(meta.get("primary_confirmations")) < 2:
errors.append("pattern 至少需要两次 primary confirmation 才能 active")
if knowledge_type == "pattern" and target == "promoted":
if as_int(meta.get("primary_confirmations")) < 3:
errors.append("pattern 至少需要三次 primary confirmation 才能晋级 rule")
if not meta.get("promoted_to"):
errors.append("pattern 晋级前必须填写 promoted_to")
if knowledge_type == "rule" and target == "active":
if not as_list(meta.get("source_patterns")):
errors.append("rule 必须关联 source_patterns")
if as_int(meta.get("independent_confirmations")) < 3:
errors.append("rule 至少需要三个独立案例确认")
if not meta.get("scope"):
errors.append("rule 必须限定 scope")
if not as_list(meta.get("invalidation_signals")):
errors.append("rule 必须定义 invalidation_signals")
return errors
def replace_frontmatter_scalar(content: str, field: str, value: str) -> str:
match = re.match(r"^(---\s*\n)(.*?)(\n---\s*(?:\n|$))", content, re.DOTALL)
if not match:
raise ValueError("missing frontmatter")
raw = match.group(2)
line = f"{field}: {value}"
pattern = re.compile(rf"(?m)^{re.escape(field)}:\s*.*$")
raw = pattern.sub(line, raw, count=1) if pattern.search(raw) else raw.rstrip() + "\n" + line
return match.group(1) + raw + match.group(3) + content[match.end() :]
def apply_transition(path: Path, target: str, reason: str, evidence: list[str]) -> None:
meta, content, error = read_page(path)
if error:
raise ValueError(error)
current = str(meta.get("status") or "")
today = date.today().isoformat()
content = replace_frontmatter_scalar(content, "status", target)
content = replace_frontmatter_scalar(content, "updated_at", today)
content = replace_frontmatter_scalar(content, "last_reviewed_at", today)
details = f"- {today}: `{current}` -> `{target}`;原因:{reason.strip()}"
if evidence:
details += ";依据:" + "、".join(evidence)
if "\n## 生命周期记录\n" in content:
content = content.rstrip() + "\n" + details + "\n"
else:
content = content.rstrip() + "\n\n## 生命周期记录\n\n" + details + "\n"
path.write_text(content, encoding="utf-8")
def queue_transition(
root: Path,
page: dict[str, Any],
target: str,
reason: str,
evidence: list[str],
) -> tuple[dict[str, str], bool]:
script_dir = Path(__file__).resolve().parent
if str(script_dir) not in sys.path:
sys.path.insert(0, str(script_dir))
from review_queue import add_item, queue_path # noqa: PLC0415
source = str(page.get("relative_path") or page["path"].relative_to(root)).replace("\\", "/")
if evidence:
source += ";依据:" + "、".join(evidence)
action = f"知识状态 {page['meta'].get('status')} -> {target}:{reason}"
return add_item(
queue_path(root),
item_type=f"knowledge-{page['type']}",
object_id=str(page["meta"].get("id") or page["path"].stem),
action=action,
source=source,
)
def find_page(root: Path, requested: Path) -> dict[str, Any] | None:
path = requested if requested.is_absolute() else root / requested
resolved = path.resolve()
for page in iter_pages(root):
if page["path"].resolve() == resolved:
return page
return None
def render_load(matches: list[dict[str, Any]], full: bool) -> str:
lines = [f"知识加载结果:{len(matches)} 条"]
for item in matches:
lines.extend(
[
"",
f"- [{item['type']}] {item['id']} {item['title']} ({item['status']})",
f" 路径:{item['path']}",
f" 摘要:{item['summary'] or '-'}",
f" 命中:{';'.join(item['matched_by'])}",
f" 失效信号:{'、'.join(item['failure_signals']) or '-'}",
]
)
if full:
lines.extend(["", item["content"].rstrip()])
return "\n".join(lines)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--root", type=Path, default=Path.cwd())
subparsers = parser.add_subparsers(dest="command", required=True)
summary_parser = subparsers.add_parser("summary", help="汇总生命周期状态")
summary_parser.add_argument("--json", action="store_true")
index_parser = subparsers.add_parser("rebuild-index", help="重建三个短索引")
index_parser.add_argument("--apply", action="store_true")
load_parser = subparsers.add_parser("load", help="按研究场景选择性加载知识")
load_parser.add_argument("--context", default="")
load_parser.add_argument("--types", nargs="+", choices=sorted(TYPE_PATHS), default=sorted(TYPE_PATHS))
load_parser.add_argument("--domain", action="append", default=[])
load_parser.add_argument("--ticker", action="append", default=[])
load_parser.add_argument("--scenario", action="append", default=[])
load_parser.add_argument("--signal", action="append", default=[])
load_parser.add_argument("--limit", type=int, default=8)
load_parser.add_argument("--include-review", action="store_true")
load_parser.add_argument("--all-active", action="store_true")
load_parser.add_argument("--full", action="store_true")
load_parser.add_argument("--json", action="store_true")
load_parser.add_argument("--record", action="store_true")
load_parser.add_argument("--research-id", default="")
transition_parser = subparsers.add_parser("transition", help="预览或执行合法状态迁移")
transition_parser.add_argument("path", type=Path)
transition_parser.add_argument("--to", required=True)
transition_parser.add_argument("--reason", required=True)
transition_parser.add_argument("--evidence", action="append", default=[])
transition_parser.add_argument("--queue", action="store_true", help="追加到待确认队列,不改变知识状态")
transition_parser.add_argument("--apply", action="store_true")
transition_parser.add_argument("--confirmed", action="store_true")
args = parser.parse_args()
root = find_workspace_root(args.root)
if args.command == "summary":
result = summarize(root)
if args.json:
print(json.dumps(result, ensure_ascii=False, indent=2))
else:
print("知识生命周期摘要")
for knowledge_type in TYPE_PATHS:
values = result["counts"].get(knowledge_type, {})
print(f"- {knowledge_type}: " + (", ".join(f"{key}={value}" for key, value in sorted(values.items())) or "0"))
print(f"- 到期复审:{len(result['overdue'])}")
print(f"- 晋级候选:{len(result['promotion_candidates'])}")
return 0
if args.command == "rebuild-index":
result = rebuild_indexes(root, args.apply)
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0
if args.command == "load":
if not args.context and not args.all_active and not any((args.domain, args.ticker, args.scenario, args.signal)):
print("ERROR: 请提供 --context、结构化筛选,或显式使用 --all-active", file=sys.stderr)
return 2
matches = load_knowledge(
root,
context=args.context,
selected_types=set(args.types),
limit=max(1, args.limit),
include_review=args.include_review,
all_active=args.all_active,
filters={
"domain": args.domain,
"ticker": args.ticker,
"decision_scenarios": args.scenario,
"recall_signals": args.signal,
},
)
invocation_id = record_load(root, args.context, matches, args.research_id) if args.record else None
if args.json:
payload = {"invocation_id": invocation_id, "matches": matches}
if not args.full:
for item in payload["matches"]:
item.pop("content", None)
print(json.dumps(payload, ensure_ascii=False, indent=2))
else:
print(render_load(matches, args.full))
if invocation_id:
print(f"\n调用记录:{invocation_id}")
return 0
page = find_page(root, args.path)
if page is None:
print(f"ERROR: 不是受支持的知识卡:{args.path}", file=sys.stderr)
return 2
if args.to not in STATUSES[page["type"]]:
print(f"ERROR: `{args.to}` 不是 {page['type']} 的合法状态", file=sys.stderr)
return 2
errors = transition_errors(page, args.to)
if errors:
for error in errors:
print(f"ERROR: {error}", file=sys.stderr)
return 2
preview = {
"path": page["relative_path"],
"id": page["meta"].get("id"),
"from": page["meta"].get("status"),
"to": args.to,
"reason": args.reason,
"evidence": args.evidence,
"applied": False,
}
if args.queue:
item, created = queue_transition(root, page, args.to, args.reason, args.evidence)
preview["queued"] = {"created": created, "item": item}
if args.apply:
if not args.confirmed:
print("ERROR: 状态变更必须同时提供 --confirmed,表示用户已确认", file=sys.stderr)
return 2
apply_transition(page["path"], args.to, args.reason, args.evidence)
rebuild_indexes(root, True)
preview["applied"] = True
print(json.dumps(preview, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
193 files · 8 not served here
- .gitattributes
- .github/workflows/ci.yml
- .gitignore
- AGENTS.md
- ARCHITECTURE.md
- ATTRIBUTION.md
- CHANGELOG.md
- CLAUDE.md
- DATA-SOURCES.md (not served here)
- INSTALL-FOR-AI.md
- LICENSE
- README.md
- SMOKE-TEST.md
- START-HERE.md
- TROUBLESHOOTING.md
- config/.gitkeep
- config/README.md
- docs/demo.gif (not served here)
- docs/gen_demo_gif.py
- evidence/README.md
- examples/README.md
- examples/knowledge-lifecycle-cycle-top/README.md
- examples/knowledge-lifecycle-cycle-top/wiki/explorations/lithium-cycle-review.md
- examples/knowledge-lifecycle-cycle-top/wiki/explorations/solar-cycle-review.md
- examples/knowledge-lifecycle-cycle-top/wiki/patterns/capacity-expansion-cycle-top.md
- examples/knowledge-lifecycle-cycle-top/wiki/sources/lithium-capacity-expansion.md
- examples/knowledge-lifecycle-cycle-top/wiki/sources/lithium-participant-pressure.md
- examples/knowledge-lifecycle-cycle-top/wiki/sources/solar-equipment-orders.md
- examples/knowledge-lifecycle-cycle-top/wiki/sources/solar-leader-slowdown.md
- examples/knowledge-lifecycle-cycle-top/workspace/workspace-config.md
- examples/smoke-test/output/first-ingest/2026-06-05-smoke-test.md
- examples/smoke-test/output/pdf-ingest/2026-06-05-smoke-test.md
- examples/smoke-test/output/wiki-read-priority/2026-06-05-answer-from-wiki.md
- examples/smoke-test/wiki/concepts/ai-workspace-file-protocol.md
- examples/smoke-test/wiki/concepts/pdf-to-md-ingest.md
- examples/smoke-test/wiki/explorations/2026-06-05-first-ingest-smoke-test.md
- examples/smoke-test/wiki/explorations/2026-06-05-pdf-ingest-smoke-test.md
- examples/smoke-test/wiki/raw/2026-06-05-first-note.md
- examples/smoke-test/wiki/raw/2026-06-05-sample-ai-workspace.md
- examples/smoke-test/wiki/raw/2026-06-05-sample-ai-workspace.pdf (not served here)
- examples/smoke-test/wiki/sources/2026-06-05-first-note.md
- examples/smoke-test/wiki/sources/2026-06-05-sample-ai-workspace.md
- hypothesis/README.md
- inbox/README.md
- inbox/first-note.md
- inbox/sample-ai-workspace.pdf (not served here)
- output/.gitkeep
- output/README.md
- output/daily-watch/README.md
- output/pod2wiki/.gitkeep
- output/research/.gitkeep
- output/research/preflight/.gitkeep
- output/screen/.gitkeep
- portfolio/README.md
- portfolio/journal/.gitkeep
- requirements-pdf.txt
- requirements.lock
- requirements.txt
- system/README.md
- system/integrations/_template.md
- system/integrations/daily-watchlist.md
- system/integrations/hypothesis-tracker.md
- system/integrations/object-model.md
- system/integrations/personal-wiki.md
- system/integrations/pod2wiki.md
- system/interfaces/README.md
- system/lib/__init__.py
- system/lib/llm_client.py
- system/managed-files.json
- system/scripts/check_workspace.py
- system/scripts/install_workspace.py
- system/scripts/knowledge_lifecycle.py
- system/scripts/pdf_to_md.py
- system/scripts/prune_active_context.py (not served here)
- system/scripts/research_preflight.py
- system/scripts/review_queue.py
- system/scripts/upgrade_workspace.py
- system/scripts/wiki_tagger.py
- system/scripts/workspace_doctor.py
- system/scripts/workspace_status.py
- system/skills/_template.md
- system/skills/daily-watch-ht.md
- system/skills/daily-watch-import.md
- system/skills/daily-watch.md
- system/skills/first-ingest.md
- system/skills/first-research.md
- system/skills/hypothesis-review.md
- system/skills/knowledge-lifecycle.md
- system/skills/pdf-ingest.md
- system/skills/podcast.md
- system/skills/post-install-cleanup.md
- system/skills/research-closeout.md
- system/skills/research.md
- system/skills/screen.md
- system/skills/structure-health.md
- system/skills/upgrade-workspace.md
- system/skills/workspace-doctor.md
- system/skills/workspace-status.md
- system/templates/AGENTS.md
- system/templates/CLAUDE.md
- system/templates/active-context.md
- system/templates/evidence-entry.md
- system/templates/friction-log.md
- system/templates/interfaces-README.md
- system/templates/reading-hub.base
- system/templates/reading-hub.md
- system/templates/research-profile.md
- system/templates/research-report.md
- system/templates/review-queue.md
- system/templates/workspace-config.md
- tests/README.md
- tests/_test_paths.py
- tests/conftest.py
- tests/test_agent_handoff.py
- tests/test_daily_watch_fixes.py
- tests/test_data_coverage.py
- tests/test_first_research_e2e.py
- tests/test_install_workspace.py
- tests/test_knowledge_lifecycle.py
- tests/test_llm_config.py
- tests/test_no_key_degradation.py
- tests/test_podcast_fixes.py
- tests/test_prune_active_context.py
- tests/test_repository_contract.py
- tests/test_research_preflight.py
- tests/test_wiki_tagger.py (not served here)
- tests/test_workspace_paths.py
- tests/test_workspace_state.py
- tools/README.md
- tools/__init__.py
- tools/daily-watch/README.md
- tools/daily-watch/__init__.py
- tools/daily-watch/config-examples/daily-watchlist.env.example
- tools/daily-watch/config-examples/daily-watchlist.example.yaml
- tools/daily-watch/config-examples/daily-watchlist.watchlist.empty.md
- tools/daily-watch/config-examples/daily-watchlist.watchlist.example.md
- tools/daily-watch/config-examples/hypothesis-tracker.example.yaml
- tools/daily-watch/config-examples/hypothesis-tracker.rules.example.md
- tools/daily-watch/pyproject.toml
- tools/daily-watch/requirements-tushare.txt
- tools/daily-watch/requirements.txt
- tools/daily-watch/scripts/__init__.py
- tools/daily-watch/scripts/check_setup.py (not served here)
- tools/daily-watch/scripts/fetch_macro_data.py
- tools/daily-watch/scripts/fetch_market_data.py (not served here)
- tools/daily-watch/scripts/generate_daily_report.py
- tools/daily-watch/scripts/sync_hypothesis.py
- tools/daily-watch/scripts/trade_stats.py
- tools/daily-watch/scripts/workspace_paths.py
- tools/daily-watch/templates/daily-watchlist-report-template.md
- tools/daily-watch/templates/hypothesis-tracker-hypothesis-template.md
- tools/daily-watch/templates/hypothesis-tracker-journal-template.md
- tools/daily-watch/templates/hypothesis-tracker-report-template.md
- tools/podcast/.env.example
- tools/podcast/README.md
- tools/podcast/__init__.py
- tools/podcast/examples/config.ai-investing.yaml
- tools/podcast/pyproject.toml
- tools/podcast/requirements-transcribe.txt
- tools/podcast/requirements.txt
- tools/podcast/scripts/__init__.py
- tools/podcast/scripts/fetch_podcasts.py
- tools/podcast/scripts/llm_client.py
- tools/podcast/scripts/podcast_batch_summarize.py
- tools/podcast/scripts/podcast_feed_registry.py
- tools/podcast/scripts/podcast_rss_transcribe.py
- tools/podcast/scripts/proxy_config.py
- wiki/README.md
- wiki/_schema.md
- wiki/concepts/.gitkeep
- wiki/entities/.gitkeep
- wiki/explorations/.gitkeep
- wiki/explorations/_index.md
- wiki/explorations/_template.md
- wiki/false-beliefs.md
- wiki/patterns/_index.md
- wiki/patterns/_template.md
- wiki/raw/.gitkeep
- wiki/rules.md
- wiki/rules/_index.md
- wiki/rules/_template.md
- wiki/sources/.gitkeep
- workspace/README.md
- workspace/archive/README.md
- workspace/meta/active-context.md
- workspace/meta/friction-log.md
- workspace/monitoring/README.md
- workspace/monitoring/watchlist.md
- workspace/patterns/_index.md
- workspace/patterns/_template.md
- workspace/research-profile.md
- workspace/review-queue.md
- workspace/workspace-config.md
Advantages
- Local-first: the memory stays in files or a database on your own machine (a rule-derived fact from the brain’s files, evidence: README.md)
- Plain markdown memory that you can read and edit yourself (a rule-derived fact from the brain’s files, evidence: wiki)
- No API key is named in its files (a rule-derived fact from the brain’s files, evidence: README.md)
- Open licence (MIT) (a rule-derived fact from the brain’s files)
Limitations
- No ZIP download: install it from its source (a rule-derived fact from the brain’s files)
Security
Risk level: Low risk
This brain runs
Agent instructions that run or fetch something (24)
AGENTS.md:工具代码在 `tools/` 目录下。首次使用时 agent 检查依赖,缺什么用 `python3 -m pip install ...` 安装;如果环境只有 `python`,再替换成 `python -m pip ...`。AGENTS.md:用户说“看看现在该做什么”时,运行 `python system/scripts/workspace_status.py`(macOS/Linux 可用 `python3`)。AGENTS.md:python3 system/scripts/prune_active_context.py # dry-run,先看会剪什么AGENTS.md:python3 system/scripts/prune_active_context.py --apply # 确认后执行AGENTS.md:装好上手后一次性瘦身:`system/skills/post-install-cleanup.md`(清安装脚手架 + 精简必读文件)。AGENTS.md:active-context 三道闸门:`python3 system/scripts/prune_active_context.py --apply`。CLAUDE.md:工具代码在 `tools/` 目录下。首次使用时 agent 检查依赖,缺什么用 `python3 -m pip install ...` 安装;如果环境只有 `python`,再替换成 `python -m pip ...`。CLAUDE.md:用户说“看看现在该做什么”时,运行 `python system/scripts/workspace_status.py`(macOS/Linux 可用 `python3`)。CLAUDE.md:python3 system/scripts/prune_active_context.py # dry-run,先看会剪什么CLAUDE.md:python3 system/scripts/prune_active_context.py --apply # 确认后执行CLAUDE.md:装好上手后一次性瘦身:`system/skills/post-install-cleanup.md`(清安装脚手架 + 精简必读文件)。CLAUDE.md:active-context 三道闸门:`python3 system/scripts/prune_active_context.py --apply`。system/templates/AGENTS.md:工具代码在 `tools/` 目录下。首次使用时 agent 检查依赖,缺什么用 `python3 -m pip install ...` 安装;如果环境只有 `python`,再替换成 `python -m pip ...`。system/templates/AGENTS.md:用户说“看看现在该做什么”时,运行 `python system/scripts/workspace_status.py`(macOS/Linux 可用 `python3`)。system/templates/AGENTS.md:python3 system/scripts/prune_active_context.py # dry-run,先看会剪什么system/templates/AGENTS.md:python3 system/scripts/prune_active_context.py --apply # 确认后执行system/templates/AGENTS.md:装好上手后一次性瘦身:`system/skills/post-install-cleanup.md`(清安装脚手架 + 精简必读文件)。system/templates/AGENTS.md:active-context 三道闸门:`python3 system/scripts/prune_active_context.py --apply`。system/templates/CLAUDE.md:工具代码在 `tools/` 目录下。首次使用时 agent 检查依赖,缺什么用 `python3 -m pip install ...` 安装;如果环境只有 `python`,再替换成 `python -m pip ...`。system/templates/CLAUDE.md:用户说“看看现在该做什么”时,运行 `python system/scripts/workspace_status.py`(macOS/Linux 可用 `python3`)。system/templates/CLAUDE.md:python3 system/scripts/prune_active_context.py # dry-run,先看会剪什么system/templates/CLAUDE.md:python3 system/scripts/prune_active_context.py --apply # 确认后执行system/templates/CLAUDE.md:装好上手后一次性瘦身:`system/skills/post-install-cleanup.md`(清安装脚手架 + 精简必读文件)。system/templates/CLAUDE.md:active-context 三道闸门:`python3 system/scripts/prune_active_context.py --apply`。
- Low risk: network-instructions (AGENTS.md:52)
- Low risk: network-instructions (CLAUDE.md:52)
- Low risk: scripts-present (system/scripts/prune_active_context.py)
- Low risk: network-instructions (system/templates/AGENTS.md:52)
- Low risk: network-instructions (system/templates/CLAUDE.md:52)
Attribution and licence
- First committed by
Benboerba620
- Publisher
- Benbor (Community)
- Source
- Benboerba620/ai-workspace-hub · commit e01a41a
- Licence
- MIT License (files served here)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
- Contributors
- Benboerba620
AutoGineer is not affiliated with or endorsed by Benboerba620.
Install from source
8 of its files are not served here, so there is no ZIP. Benboerba620/ai-workspace-hub at e01a41a