encore-ai 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- encore_ai-0.1.0/PKG-INFO +7 -0
- encore_ai-0.1.0/README.md +71 -0
- encore_ai-0.1.0/pyproject.toml +22 -0
- encore_ai-0.1.0/setup.cfg +4 -0
- encore_ai-0.1.0/src/encore/__init__.py +3 -0
- encore_ai-0.1.0/src/encore/cli.py +93 -0
- encore_ai-0.1.0/src/encore/encore_schema_v0.1.json +136 -0
- encore_ai-0.1.0/src/encore/schema.py +39 -0
- encore_ai-0.1.0/src/encore/storage.py +173 -0
- encore_ai-0.1.0/src/encore_ai.egg-info/PKG-INFO +7 -0
- encore_ai-0.1.0/src/encore_ai.egg-info/SOURCES.txt +13 -0
- encore_ai-0.1.0/src/encore_ai.egg-info/dependency_links.txt +1 -0
- encore_ai-0.1.0/src/encore_ai.egg-info/entry_points.txt +2 -0
- encore_ai-0.1.0/src/encore_ai.egg-info/requires.txt +2 -0
- encore_ai-0.1.0/src/encore_ai.egg-info/top_level.txt +1 -0
encore_ai-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Encore — 让每一次 AI 对话,都成为你的数字资产
|
|
2
|
+
|
|
3
|
+
## 痛点:被遗忘的"数字黄金"
|
|
4
|
+
|
|
5
|
+
每天,我们都在与 ChatGPT、Claude、Cursor 进行着极具价值的对话。我们排查了深度的代码 Bug,探讨了绝妙的产品架构,学习了晦涩的技术概念。
|
|
6
|
+
|
|
7
|
+
但是,当你关掉对话框的那一刻,这些知识就随之消散了。下一次遇到同样的报错,你依然需要重新提问;那些 AI 建议你"下一步该做的事",转头就被遗忘。
|
|
8
|
+
|
|
9
|
+
**对话是碎片的,但知识不应该是。**
|
|
10
|
+
|
|
11
|
+
## 什么是 Encore?
|
|
12
|
+
|
|
13
|
+
Encore 不是另一款笔记软件,而是一个**意图驱动的 AI 知识路由器**。
|
|
14
|
+
|
|
15
|
+
它寄生在你已有的 AI 对话环境中(Claude Code、IDE、浏览器)。它能听懂你与 AI 的对话,自动剥离废话,提取核心价值,并将其转化为结构化的文档和待办事项,精准投递到你现有的工作流中。
|
|
16
|
+
|
|
17
|
+
**你只管和 AI 解决问题,Encore 负责帮你记住一切。**
|
|
18
|
+
|
|
19
|
+
## 核心特性
|
|
20
|
+
|
|
21
|
+
### 零摩擦捕获
|
|
22
|
+
|
|
23
|
+
"最好的工具,是让你感觉不到它的存在。"
|
|
24
|
+
|
|
25
|
+
Encore 拒绝让你在多个软件间复制粘贴。通过内嵌于 Claude Code 的 Skill、IDE 插件和浏览器扩展,Encore 直接驻留在你的对话发生地。一个指令或一次点击,剩下的工作全部在后台静默完成。
|
|
26
|
+
|
|
27
|
+
### 动态意图解析
|
|
28
|
+
|
|
29
|
+
Encore 不做简单的"聊天记录导出"。它拥有自己的思考中枢,能精准识别你对话的真实意图,并动态应用不同的沉淀模板:
|
|
30
|
+
|
|
31
|
+
| 模式 | 自动提取 |
|
|
32
|
+
|------|---------|
|
|
33
|
+
| 🐛 排错模式 | 核心报错 → 踩坑记录 → 正确解决流程 → 最终代码 |
|
|
34
|
+
| 📚 学习模式 | 核心概念 → 费曼技巧总结 → 拓展阅读 |
|
|
35
|
+
| 💡 灵感模式 | 核心 Idea → 优劣势分析 → 落地步骤 |
|
|
36
|
+
|
|
37
|
+
### 智能资产分发
|
|
38
|
+
|
|
39
|
+
Encore 遵循"让信息去该去的地方"原则,无缝对接你现有的生产力工具:
|
|
40
|
+
|
|
41
|
+
- 复盘文档与知识点 → 自动写入 Notion / Obsidian
|
|
42
|
+
- 高频工具函数 → 自动剥离并存入 GitHub Gist
|
|
43
|
+
- 下一步行动指南 → 自动转化为待办任务,推送到 Todoist
|
|
44
|
+
|
|
45
|
+
### 记忆回响
|
|
46
|
+
|
|
47
|
+
"存下来的知识,能在需要时自动跳出来,才叫外脑。"
|
|
48
|
+
|
|
49
|
+
当你再次遇到类似的报错时,Encore 会被唤醒并提醒你:"上个月你遇到过类似问题,当时的复盘文档在这里,要不要先看看?"
|
|
50
|
+
|
|
51
|
+
### AI 上下文接力
|
|
52
|
+
|
|
53
|
+
"跨 AI 对话,不丢失上下文。"
|
|
54
|
+
|
|
55
|
+
每个归档笔记都会自动生成一份 `context_digest`——2000 字以内的结构化摘要。下一个 AI 读完就能直接接手,无需重复解释背景。**50 轮对话的核心信息,压缩到一张卡片。**
|
|
56
|
+
|
|
57
|
+
## 产品形态
|
|
58
|
+
|
|
59
|
+
Encore 采用 **"前端寄生,后端独立"** 的架构:
|
|
60
|
+
|
|
61
|
+
- **Encore for Claude Code** — 基于 Claude Code Skill 打造,终端环境下的一键知识归档
|
|
62
|
+
- **Encore Browser Extension** — 嵌入 ChatGPT / Claude Web 端,一键沉淀网页对话
|
|
63
|
+
- **Encore Workflow** — 基于 LangGraph 的异步处理引擎,负责意图识别与数据分发
|
|
64
|
+
|
|
65
|
+
## 愿景
|
|
66
|
+
|
|
67
|
+
Encore 的终极目标,是为你打造一个真正懂你的**认知外脑**。
|
|
68
|
+
|
|
69
|
+
随着时间的推移,Encore 沉淀的不仅是文档,更是你个人的思考路径、踩坑经验和成长轨迹。让 AI 不仅是你解决当下问题的工具,更是你积累终身数字资产的合伙人。
|
|
70
|
+
|
|
71
|
+
**Encore — 念念不忘,必有回响。**
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "encore-ai"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "AI对话知识归档工具 — 让每一次AI对话都成为你的数字资产"
|
|
5
|
+
requires-python = ">=3.10"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"click>=8.0",
|
|
8
|
+
"pyyaml>=6.0",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
[project.scripts]
|
|
12
|
+
encore = "encore.cli:main"
|
|
13
|
+
|
|
14
|
+
[build-system]
|
|
15
|
+
requires = ["setuptools>=68.0"]
|
|
16
|
+
build-backend = "setuptools.build_meta"
|
|
17
|
+
|
|
18
|
+
[tool.setuptools.packages.find]
|
|
19
|
+
where = ["src"]
|
|
20
|
+
|
|
21
|
+
[tool.setuptools.package-data]
|
|
22
|
+
encore = ["encore_schema_v0.1.json"]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Encore CLI — AI 对话知识归档命令行工具"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import click
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
|
|
7
|
+
from .storage import save, list_notes, search
|
|
8
|
+
from .schema import validate
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.group()
|
|
12
|
+
def main():
|
|
13
|
+
"""Encore — AI 对话知识归档工具"""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@main.command()
|
|
18
|
+
@click.argument("data", required=False)
|
|
19
|
+
@click.option("--file", "-f", "json_file", type=click.Path(exists=True), help="从 JSON 文件读取")
|
|
20
|
+
def save_cmd(data: str | None, json_file: str | None):
|
|
21
|
+
"""保存一条结构化笔记。传入 JSON 字符串,或通过 --file 指定 JSON 文件。
|
|
22
|
+
|
|
23
|
+
\b
|
|
24
|
+
示例:
|
|
25
|
+
encore save '{"title":"测试","intent":"learning","context_digest":"...","payload":{...}}'
|
|
26
|
+
encore save --file note.json
|
|
27
|
+
"""
|
|
28
|
+
if json_file:
|
|
29
|
+
with open(json_file, "r", encoding="utf-8") as f:
|
|
30
|
+
data = f.read()
|
|
31
|
+
|
|
32
|
+
if not data:
|
|
33
|
+
click.echo("错误: 请提供 JSON 数据或使用 --file 指定文件", err=True)
|
|
34
|
+
raise SystemExit(1)
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
note = json.loads(data)
|
|
38
|
+
except json.JSONDecodeError as e:
|
|
39
|
+
click.echo(f"JSON 解析错误: {e}", err=True)
|
|
40
|
+
raise SystemExit(1)
|
|
41
|
+
|
|
42
|
+
# Fill defaults
|
|
43
|
+
if "created_at" not in note:
|
|
44
|
+
note["created_at"] = datetime.now(timezone.utc).isoformat()
|
|
45
|
+
if "status" not in note:
|
|
46
|
+
note["status"] = "resolved"
|
|
47
|
+
if "source_environment" not in note:
|
|
48
|
+
note["source_environment"] = "claude_code"
|
|
49
|
+
|
|
50
|
+
# Validate
|
|
51
|
+
errors = validate(note)
|
|
52
|
+
if errors:
|
|
53
|
+
for e in errors:
|
|
54
|
+
click.echo(f"校验错误: {e}", err=True)
|
|
55
|
+
raise SystemExit(1)
|
|
56
|
+
|
|
57
|
+
# Save
|
|
58
|
+
filepath = save(note)
|
|
59
|
+
click.echo(f"✅ 已归档: {filepath}")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@main.command()
|
|
63
|
+
@click.option("--intent", "-i", type=click.Choice(["bug_fix", "learning", "idea"]), help="按意图过滤")
|
|
64
|
+
def list_cmd(intent: str | None):
|
|
65
|
+
"""列出所有已归档笔记"""
|
|
66
|
+
notes = list_notes(intent)
|
|
67
|
+
if not notes:
|
|
68
|
+
click.echo("暂无归档笔记。")
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
click.echo(f"共 {len(notes)} 条笔记:\n")
|
|
72
|
+
for n in notes:
|
|
73
|
+
intent_icon = {"bug_fix": "🐛", "learning": "📚", "idea": "💡"}.get(n.get("intent", ""), "📝")
|
|
74
|
+
title = n.get("title", "未命名")
|
|
75
|
+
click.echo(f" {intent_icon} {title}")
|
|
76
|
+
click.echo(f" 文件: {n['_file']} | {n.get('created_at', '')}")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@main.command()
|
|
80
|
+
@click.argument("keyword")
|
|
81
|
+
def search_cmd(keyword: str):
|
|
82
|
+
"""按关键词搜索笔记(匹配标题和标签)"""
|
|
83
|
+
results = search(keyword)
|
|
84
|
+
if not results:
|
|
85
|
+
click.echo(f"未找到与 '{keyword}' 相关的笔记。")
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
click.echo(f"找到 {len(results)} 条相关笔记:\n")
|
|
89
|
+
for n in results:
|
|
90
|
+
intent_icon = {"bug_fix": "🐛", "learning": "📚", "idea": "💡"}.get(n.get("intent", ""), "📝")
|
|
91
|
+
title = n.get("title", "未命名")
|
|
92
|
+
click.echo(f" {intent_icon} {title}")
|
|
93
|
+
click.echo(f" 文件: {n['_file']} | 标签: {', '.join(n.get('tags', []))}")
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/WWBQ/Encore/encore_note_v0.1.json",
|
|
4
|
+
"title": "Encore Memory Standard v0.1",
|
|
5
|
+
"description": "一条归档笔记的数据契约。同时服务于人类回顾和 AI 上下文传递。",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["encore_version", "intent", "title", "context_digest", "created_at"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"encore_version": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"const": "0.1",
|
|
12
|
+
"description": "协议版本号"
|
|
13
|
+
},
|
|
14
|
+
"intent": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"enum": ["bug_fix", "learning", "idea"],
|
|
17
|
+
"description": "对话意图分类。bug_fix=排错 | learning=学习 | idea=灵感"
|
|
18
|
+
},
|
|
19
|
+
"title": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"maxLength": 120,
|
|
22
|
+
"description": "一句话标题,人类可读"
|
|
23
|
+
},
|
|
24
|
+
"source_environment": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"enum": ["claude_code", "chatgpt_web", "cursor", "windsurf", "other"],
|
|
27
|
+
"default": "claude_code"
|
|
28
|
+
},
|
|
29
|
+
"created_at": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"format": "date-time"
|
|
32
|
+
},
|
|
33
|
+
"status": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"enum": ["resolved", "in_progress", "open"],
|
|
36
|
+
"default": "resolved",
|
|
37
|
+
"description": "问题是否已解决"
|
|
38
|
+
},
|
|
39
|
+
"context_digest": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"maxLength": 2000,
|
|
42
|
+
"description": "给下一个 AI 看的压缩摘要。包含:做了什么、为什么这样做、关键决策、遗留问题。等价于 50 轮原始对话的核心信息量。"
|
|
43
|
+
},
|
|
44
|
+
"key_decision": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"maxLength": 500,
|
|
47
|
+
"description": "本次对话中做出的核心决策"
|
|
48
|
+
},
|
|
49
|
+
"open_questions": {
|
|
50
|
+
"type": "array",
|
|
51
|
+
"items": { "type": "string" },
|
|
52
|
+
"description": "未解决的遗留问题"
|
|
53
|
+
},
|
|
54
|
+
"payload": {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"description": "按意图类型填充的结构化详情",
|
|
57
|
+
"oneOf": [
|
|
58
|
+
{
|
|
59
|
+
"$ref": "#/$defs/bug_fix_payload"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"$ref": "#/$defs/learning_payload"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"$ref": "#/$defs/idea_payload"
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
"tags": {
|
|
70
|
+
"type": "array",
|
|
71
|
+
"items": { "type": "string" },
|
|
72
|
+
"description": "分类标签,用于检索"
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
"$defs": {
|
|
77
|
+
"bug_fix_payload": {
|
|
78
|
+
"type": "object",
|
|
79
|
+
"required": ["symptom", "root_cause", "solution"],
|
|
80
|
+
"properties": {
|
|
81
|
+
"symptom": { "type": "string", "description": "报错现象/症状描述" },
|
|
82
|
+
"root_cause": { "type": "string", "description": "根因分析" },
|
|
83
|
+
"failed_attempts": {
|
|
84
|
+
"type": "array",
|
|
85
|
+
"items": { "type": "string" },
|
|
86
|
+
"description": "试过但失败的方法"
|
|
87
|
+
},
|
|
88
|
+
"solution_code": { "type": "string", "description": "最终可运行的代码" },
|
|
89
|
+
"solution_summary": { "type": "string", "description": "解决方案的文字总结" },
|
|
90
|
+
"action_items": {
|
|
91
|
+
"type": "array",
|
|
92
|
+
"items": { "type": "string" },
|
|
93
|
+
"description": "后续待办事项"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"learning_payload": {
|
|
98
|
+
"type": "object",
|
|
99
|
+
"required": ["core_concept", "feynman_summary"],
|
|
100
|
+
"properties": {
|
|
101
|
+
"core_concept": { "type": "string", "description": "核心概念名称" },
|
|
102
|
+
"feynman_summary": { "type": "string", "description": "用费曼技巧一句话解释" },
|
|
103
|
+
"detailed_explanation": { "type": "string", "description": "详细解释" },
|
|
104
|
+
"related_concepts": {
|
|
105
|
+
"type": "array",
|
|
106
|
+
"items": { "type": "string" },
|
|
107
|
+
"description": "关联概念"
|
|
108
|
+
},
|
|
109
|
+
"references": {
|
|
110
|
+
"type": "array",
|
|
111
|
+
"items": { "type": "string" },
|
|
112
|
+
"description": "拓展阅读链接"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"idea_payload": {
|
|
117
|
+
"type": "object",
|
|
118
|
+
"required": ["core_idea", "pros_cons"],
|
|
119
|
+
"properties": {
|
|
120
|
+
"core_idea": { "type": "string", "description": "核心创意" },
|
|
121
|
+
"pros_cons": {
|
|
122
|
+
"type": "object",
|
|
123
|
+
"properties": {
|
|
124
|
+
"pros": { "type": "array", "items": { "type": "string" } },
|
|
125
|
+
"cons": { "type": "array", "items": { "type": "string" } }
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
"action_steps": {
|
|
129
|
+
"type": "array",
|
|
130
|
+
"items": { "type": "string" },
|
|
131
|
+
"description": "落地步骤拆解"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Schema 加载与基础校验"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
SCHEMA_PATH = Path(__file__).parent / "encore_schema_v0.1.json"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def load_schema() -> dict:
|
|
10
|
+
"""加载数据 Schema 定义"""
|
|
11
|
+
with open(SCHEMA_PATH, "r", encoding="utf-8") as f:
|
|
12
|
+
return json.load(f)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def validate(note: dict) -> list[str]:
|
|
16
|
+
"""校验必填字段和枚举值,返回错误列表"""
|
|
17
|
+
errors = []
|
|
18
|
+
schema = load_schema()
|
|
19
|
+
|
|
20
|
+
# Required fields
|
|
21
|
+
for field in schema.get("required", []):
|
|
22
|
+
if field not in note or note[field] is None:
|
|
23
|
+
errors.append(f"缺少必填字段: {field}")
|
|
24
|
+
|
|
25
|
+
# Intent enum
|
|
26
|
+
intent = note.get("intent", "")
|
|
27
|
+
intent_schema = schema["properties"]["intent"]
|
|
28
|
+
valid_intents = intent_schema.get("enum", [])
|
|
29
|
+
if intent not in valid_intents:
|
|
30
|
+
errors.append(f"无效意图 '{intent}',允许值: {valid_intents}")
|
|
31
|
+
|
|
32
|
+
# Status enum
|
|
33
|
+
status = note.get("status", "resolved")
|
|
34
|
+
status_schema = schema["properties"]["status"]
|
|
35
|
+
valid_statuses = status_schema.get("enum", [])
|
|
36
|
+
if status not in valid_statuses:
|
|
37
|
+
errors.append(f"无效状态 '{status}',允许值: {valid_statuses}")
|
|
38
|
+
|
|
39
|
+
return errors
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""笔记存储:本地 Markdown + YAML frontmatter"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import json
|
|
6
|
+
import yaml
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
NOTES_DIR = Path.home() / ".encore" / "notes"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _ensure_dir() -> Path:
|
|
14
|
+
NOTES_DIR.mkdir(parents=True, exist_ok=True)
|
|
15
|
+
return NOTES_DIR
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _slugify(title: str) -> str:
|
|
19
|
+
"""中英文标题转为文件名 safe slug"""
|
|
20
|
+
slug = re.sub(r"[^\w一-鿿-]", "-", title.strip())
|
|
21
|
+
slug = re.sub(r"-{2,}", "-", slug)
|
|
22
|
+
return slug.strip("-")[:80] or "untitled"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def save(note: dict) -> Path:
|
|
26
|
+
"""保存一条笔记,返回文件路径"""
|
|
27
|
+
_ensure_dir()
|
|
28
|
+
|
|
29
|
+
title = note.get("title", "未命名")
|
|
30
|
+
ts = note.get("created_at", datetime.now().astimezone().isoformat())
|
|
31
|
+
try:
|
|
32
|
+
dt = datetime.fromisoformat(ts)
|
|
33
|
+
except ValueError:
|
|
34
|
+
dt = datetime.now()
|
|
35
|
+
timestamp = dt.strftime("%Y-%m-%d-%H%M%S")
|
|
36
|
+
filename = f"{timestamp}--{_slugify(title)}.md"
|
|
37
|
+
filepath = NOTES_DIR / filename
|
|
38
|
+
|
|
39
|
+
# Build YAML frontmatter
|
|
40
|
+
frontmatter = {
|
|
41
|
+
"title": note.get("title", ""),
|
|
42
|
+
"intent": note.get("intent", ""),
|
|
43
|
+
"status": note.get("status", "resolved"),
|
|
44
|
+
"tags": note.get("tags", []),
|
|
45
|
+
"created_at": note.get("created_at", dt.isoformat()),
|
|
46
|
+
"source": note.get("source_environment", "claude_code"),
|
|
47
|
+
}
|
|
48
|
+
if note.get("key_decision"):
|
|
49
|
+
frontmatter["key_decision"] = note["key_decision"]
|
|
50
|
+
if note.get("open_questions"):
|
|
51
|
+
frontmatter["open_questions"] = note["open_questions"]
|
|
52
|
+
|
|
53
|
+
# Build Markdown body
|
|
54
|
+
body = _build_body(note)
|
|
55
|
+
|
|
56
|
+
# Write file
|
|
57
|
+
with open(filepath, "w", encoding="utf-8") as f:
|
|
58
|
+
f.write("---\n")
|
|
59
|
+
yaml.dump(frontmatter, f, allow_unicode=True, default_flow_style=False, sort_keys=False)
|
|
60
|
+
f.write("---\n\n")
|
|
61
|
+
f.write(body)
|
|
62
|
+
|
|
63
|
+
return filepath
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _build_body(note: dict) -> str:
|
|
67
|
+
"""按意图类型生成人类可读正文"""
|
|
68
|
+
intent = note.get("intent", "")
|
|
69
|
+
payload = note.get("payload", {})
|
|
70
|
+
|
|
71
|
+
blocks = []
|
|
72
|
+
|
|
73
|
+
if intent == "bug_fix":
|
|
74
|
+
if payload.get("symptom"):
|
|
75
|
+
blocks.append(f"## 症状\n\n{payload['symptom']}\n")
|
|
76
|
+
if payload.get("root_cause"):
|
|
77
|
+
blocks.append(f"## 根因\n\n{payload['root_cause']}\n")
|
|
78
|
+
failed = payload.get("failed_attempts", [])
|
|
79
|
+
if failed:
|
|
80
|
+
lines = "\n".join(f"- {a}" for a in failed)
|
|
81
|
+
blocks.append(f"## 失败尝试\n\n{lines}\n")
|
|
82
|
+
if payload.get("solution_summary"):
|
|
83
|
+
blocks.append(f"## 解决方案\n\n{payload['solution_summary']}\n")
|
|
84
|
+
if payload.get("solution_code"):
|
|
85
|
+
blocks.append(f"## 最终代码\n\n```\n{payload['solution_code']}\n```\n")
|
|
86
|
+
actions = payload.get("action_items", [])
|
|
87
|
+
if actions:
|
|
88
|
+
lines = "\n".join(f"- [ ] {a}" for a in actions)
|
|
89
|
+
blocks.append(f"## 后续行动\n\n{lines}\n")
|
|
90
|
+
|
|
91
|
+
elif intent == "learning":
|
|
92
|
+
if payload.get("core_concept"):
|
|
93
|
+
blocks.append(f"## 核心概念\n\n{payload['core_concept']}\n")
|
|
94
|
+
if payload.get("feynman_summary"):
|
|
95
|
+
blocks.append(f"## 一句话理解\n\n{payload['feynman_summary']}\n")
|
|
96
|
+
if payload.get("detailed_explanation"):
|
|
97
|
+
blocks.append(f"## 详细解释\n\n{payload['detailed_explanation']}\n")
|
|
98
|
+
related = payload.get("related_concepts", [])
|
|
99
|
+
if related:
|
|
100
|
+
lines = "\n".join(f"- {c}" for c in related)
|
|
101
|
+
blocks.append(f"## 关联概念\n\n{lines}\n")
|
|
102
|
+
refs = payload.get("references", [])
|
|
103
|
+
if refs:
|
|
104
|
+
lines = "\n".join(f"- {r}" for r in refs)
|
|
105
|
+
blocks.append(f"## 拓展阅读\n\n{lines}\n")
|
|
106
|
+
|
|
107
|
+
elif intent == "idea":
|
|
108
|
+
if payload.get("core_idea"):
|
|
109
|
+
blocks.append(f"## 核心创意\n\n{payload['core_idea']}\n")
|
|
110
|
+
pc = payload.get("pros_cons", {})
|
|
111
|
+
if pc:
|
|
112
|
+
if pc.get("pros"):
|
|
113
|
+
lines = "\n".join(f"- {p}" for p in pc["pros"])
|
|
114
|
+
blocks.append(f"## 优势\n\n{lines}\n")
|
|
115
|
+
if pc.get("cons"):
|
|
116
|
+
lines = "\n".join(f"- {c}" for c in pc["cons"])
|
|
117
|
+
blocks.append(f"## 劣势\n\n{lines}\n")
|
|
118
|
+
steps = payload.get("action_steps", [])
|
|
119
|
+
if steps:
|
|
120
|
+
lines = "\n".join(f"- [ ] {s}" for s in steps)
|
|
121
|
+
blocks.append(f"## 落地步骤\n\n{lines}\n")
|
|
122
|
+
|
|
123
|
+
# Append context_digest
|
|
124
|
+
if note.get("context_digest"):
|
|
125
|
+
blocks.append("---\n")
|
|
126
|
+
blocks.append("## AI 上下文摘要 (context_digest)\n\n")
|
|
127
|
+
blocks.append(f"{note['context_digest']}\n")
|
|
128
|
+
|
|
129
|
+
return "\n".join(blocks)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def list_notes(intent_filter: str = None) -> list[dict]:
|
|
133
|
+
"""列出所有笔记摘要"""
|
|
134
|
+
_ensure_dir()
|
|
135
|
+
notes = []
|
|
136
|
+
for fp in sorted(NOTES_DIR.glob("*.md"), reverse=True):
|
|
137
|
+
meta = _read_frontmatter(fp)
|
|
138
|
+
if meta:
|
|
139
|
+
if intent_filter and meta.get("intent") != intent_filter:
|
|
140
|
+
continue
|
|
141
|
+
meta["_file"] = fp.name
|
|
142
|
+
notes.append(meta)
|
|
143
|
+
return notes
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def search(keyword: str) -> list[dict]:
|
|
147
|
+
"""基于标题和标签的简单搜索"""
|
|
148
|
+
_ensure_dir()
|
|
149
|
+
results = []
|
|
150
|
+
kw = keyword.lower()
|
|
151
|
+
for fp in sorted(NOTES_DIR.glob("*.md"), reverse=True):
|
|
152
|
+
meta = _read_frontmatter(fp)
|
|
153
|
+
if not meta:
|
|
154
|
+
continue
|
|
155
|
+
title = meta.get("title", "").lower()
|
|
156
|
+
tags = " ".join(meta.get("tags", [])).lower()
|
|
157
|
+
if kw in title or kw in tags:
|
|
158
|
+
meta["_file"] = fp.name
|
|
159
|
+
results.append(meta)
|
|
160
|
+
return results
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _read_frontmatter(filepath: Path) -> dict | None:
|
|
164
|
+
"""读取笔记的 YAML frontmatter"""
|
|
165
|
+
try:
|
|
166
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
167
|
+
content = f.read()
|
|
168
|
+
if not content.startswith("---"):
|
|
169
|
+
return None
|
|
170
|
+
_, fm, _ = content.split("---", 2)
|
|
171
|
+
return yaml.safe_load(fm)
|
|
172
|
+
except Exception:
|
|
173
|
+
return None
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/encore/__init__.py
|
|
4
|
+
src/encore/cli.py
|
|
5
|
+
src/encore/encore_schema_v0.1.json
|
|
6
|
+
src/encore/schema.py
|
|
7
|
+
src/encore/storage.py
|
|
8
|
+
src/encore_ai.egg-info/PKG-INFO
|
|
9
|
+
src/encore_ai.egg-info/SOURCES.txt
|
|
10
|
+
src/encore_ai.egg-info/dependency_links.txt
|
|
11
|
+
src/encore_ai.egg-info/entry_points.txt
|
|
12
|
+
src/encore_ai.egg-info/requires.txt
|
|
13
|
+
src/encore_ai.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
encore
|