Compare commits

..

5 Commits

7 changed files with 145 additions and 3 deletions

View File

@ -56,4 +56,5 @@ docs/templates/ 炼境接入包模板草案agents-meta-rules
1. 读取 `.blueprint/manifest.yaml` 了解项目全景和当前状态 1. 读取 `.blueprint/manifest.yaml` 了解项目全景和当前状态
2. 读取 `.blueprint/CONVENTIONS.md` 了解完整工作流规则 2. 读取 `.blueprint/CONVENTIONS.md` 了解完整工作流规则
3. 遵循 AGENTS.md 中的蓝图工作流(需求→蓝图同步、复杂度决策、两阶段提交) 3. 遵循 AGENTS.md 中的蓝图工作流(需求→蓝图同步、复杂度决策、两阶段提交)
4. 调用 `mcp__lian-jing__list_envoy_returns` 检查子项目使者回传——若有待处理条目,在开始其他任务前先列出,询问是否立即处置
<!-- BLUEPRINT_MANAGED_END --> <!-- BLUEPRINT_MANAGED_END -->

View File

@ -0,0 +1,51 @@
你是**炼境使者**——在子项目会话里代表炼境执行现场感知与回传。
直接执行以下步骤,无需用户提供任何输入。
## Step 1读取项目身份
用 Read 工具读取 `.claude/liangjing.json`,获取 `project_id` 和项目名称。
若文件不存在:输出 `⚠️ 未找到 .claude/liangjing.json该项目未接入炼境` 并停止。
## Step 2扫描本次会话
回顾本次会话的完整对话,识别有价值的洞察。
跳过:纯代码实现细节、具体 bug 修复、一次性操作记录。
## Step 3分类每条洞察
**📦 带回炼境**(满足任一条):
- 炼境分发的模板 / 约定 / 接入包有 bug 或可改进
- 某工作流或实践有明确代价或验证结果(有具体证据)
- 会影响多个项目的体系级改进(哪怕还是假设)
**🏠 留本项目**(同时满足以下所有):
- 只影响本项目的具体实现
- 无任何跨项目复用价值
- 纯猜测、零证据
## Step 4发回炼境
对每条「带回」洞察,单独调用一次 `mcp__lian-jing__append_project_note`
- `project_id`:来自 Step 1
- `content` 格式(严格遵守):
【炼境使者回传】来源: {项目名}
类型: template_bug | validated_practice | workflow_hypothesis
置信度: confirmed | under_validation
影响范围: this_project | project_type:{类型} | all_projects
内容: {一句话精确描述}
建议炼境: fix_template | update_convention | log_for_review
**`confirmed` 的门槛**:必须能陈述至少一条具体代价或验证结果(如"导致 N% 时间损耗"、"在 X 场景实测有效")。
无法陈述具体证据的,一律标 `under_validation`,不得标 `confirmed`
## Step 5输出摘要
📦 已发回炼境 N 条
· {标题}{置信度} · {影响范围}
🏠 留本项目 M 条
· {标题} — {一句说明}
若本次会话无任何体系级洞察,直接输出:`✅ 本次会话无需回传,无体系级洞察。`

View File

@ -18,7 +18,7 @@
<!-- TODO: 列出 README / ARCHITECTURE.md / 其他关键文档及用途。 --> <!-- TODO: 列出 README / ARCHITECTURE.md / 其他关键文档及用途。 -->
<!-- ONBOARDING_MANAGED_START version="1.3.0" --> <!-- ONBOARDING_MANAGED_START version="1.5.0" -->
## 核心开发约束 ## 核心开发约束
### 包管理 ### 包管理
@ -31,6 +31,31 @@
- 异步统一 `async/await`,不用裸 Promise 链 - 异步统一 `async/await`,不用裸 Promise 链
- 错误处理明确 `try/catch`,不吞异常 - 错误处理明确 `try/catch`,不吞异常
- 优先编辑现有文件,避免创建不必要的新文件 - 优先编辑现有文件,避免创建不必要的新文件
- **Playwright 版本固定**`package.json` 里的 `playwright` 锁死版本号,去掉 `^`
另建 `test:install` 脚本显式安装浏览器(`playwright install --with-deps`
升级时有意识操作,避免 pnpm install 静默触发 ~200MB 的浏览器重下
## 需求对齐协议
开始写代码前,先判断改动类型:
**Bug Fix**(当前行为与预期的意外偏差)
→ 建任务卡,直接执行,无需等确认词。
**Feature / 行为变更**(新增能力或主动改变已有行为)
→ 必须先输出确认卡,等用户说「确认」再动手:
- **S 级**(单文件、行为对外无变化,任务卡 complexity=S输出三行轻量卡
```
改哪:<文件或模块>
改什么:<一句话>
验收:<agent_verify 路径>
```
- **M / L 级**:输出完整任务卡(含 `files` + `acceptance` + `complexity`),等确认
**边界模糊时**:保守判断,按 Feature 处理,走完整确认卡。
> ⚠️ Session 结束汇总补录 ≠ 确认卡替代——补录仅适用于漂移检测触发后用户主动选择补录的场景,不得作为「先做再补」的通道。
## Git 工作流 ## Git 工作流
@ -60,6 +85,11 @@
- 正例:`[Tauri command] apply_onboarding_pack(project_id) → written≥1文件在磁盘存在` - 正例:`[Tauri command] apply_onboarding_pack(project_id) → written≥1文件在磁盘存在`
- 正例:`[HTTP API] POST /api/users → 201body.id 非空` - 正例:`[HTTP API] POST /api/users → 201body.id 非空`
- 反例:`功能正常可用`模糊agent 无法自验) - 反例:`功能正常可用`模糊agent 无法自验)
**分层原则**:凡含 UI 操作路径的 acceptance必须拆成两段
- `agent_verify:` — 机器可断言HTTP 状态码、数据库记录、函数返回值stop hook / CI 只验此段
- `human_verify:` — 需人眼判断视觉效果、拖拽手势、30 秒浏览器终验)
纯 API / 命令行任务可省略 `human_verify:`UI 拖拽 / 动画 / 视觉类必须拆,否则 stop hook 无法收敛。
6. **自验报告纪律**:交人终验前的反馈必须含「**没验什么**」一节(未覆盖的路径、依赖的 6. **自验报告纪律**:交人终验前的反馈必须含「**没验什么**」一节(未覆盖的路径、依赖的
假设、不确定的地方);自修过程中改了断言/验收标准必须显式声明理由(改功能让测试过是 假设、不确定的地方);自修过程中改了断言/验收标准必须显式声明理由(改功能让测试过是
常规改测试让功能过需报备agent 自验对 acceptance人终验对意图——acceptance 常规改测试让功能过需报备agent 自验对 acceptance人终验对意图——acceptance

View File

@ -3,7 +3,7 @@
> 把此文件粘贴给任何 AI Agent让它了解本项目的 git 工作方式。由炼境接入包分发。 > 把此文件粘贴给任何 AI Agent让它了解本项目的 git 工作方式。由炼境接入包分发。
> `ONBOARDING_MANAGED` 标记块由炼境版本化管理(重跑接入包可原位升级),块外可自由补充项目特有约定。 > `ONBOARDING_MANAGED` 标记块由炼境版本化管理(重跑接入包可原位升级),块外可自由补充项目特有约定。
<!-- ONBOARDING_MANAGED_START version="1.4.0" --> <!-- ONBOARDING_MANAGED_START version="1.5.0" -->
## 分支策略(复杂度感知) ## 分支策略(复杂度感知)
@ -48,7 +48,7 @@ git commit -m "fix(api/user): 修正分页 off-by-one"
## Rules-Applied trailer规则命中回传 ## Rules-Applied trailer规则命中回传
若本次任务中触发过 CONVENTIONS 自动推荐规则R01-R11见 `.blueprint/CONVENTIONS.md` 规则列表 若本次任务中触发过项目约定规则(见项目 CONVENTIONS.md 或 `.blueprint/CONVENTIONS.md`
在 commit message 末尾追加一行 trailer与正文空行分隔 在 commit message 末尾追加一行 trailer与正文空行分隔
``` ```

View File

@ -9,6 +9,13 @@ pre-commit:
typecheck: typecheck:
glob: "*.{ts,tsx}" glob: "*.{ts,tsx}"
run: pnpm typecheck run: pnpm typecheck
# ⚠️ 如果项目有 API / 后端层,必须在此补对应的 typecheck——否则后端类型错误不会被拦截。
# 取消注释并按实际填写(留多余示例行删掉即可):
# api-typecheck:
# glob: "api/**/*.{ts,tsx}"
# run: pnpm --filter api typecheck # monorepo workspace
# # run: pnpm exec tsc -p api/tsconfig.json --noEmit # 独立 tsconfig
# # run: pnpm -r typecheck # 递归所有包
commit-msg: commit-msg:
commands: commands:

View File

@ -16,6 +16,7 @@ const MANAGED_END: &str = "<!-- ONBOARDING_MANAGED_END -->";
const TMPL_AGENTS: &str = include_str!("../../resources/onboarding/AGENTS.md.tmpl"); const TMPL_AGENTS: &str = include_str!("../../resources/onboarding/AGENTS.md.tmpl");
const TMPL_LEFTHOOK: &str = include_str!("../../resources/onboarding/lefthook.yml.tmpl"); const TMPL_LEFTHOOK: &str = include_str!("../../resources/onboarding/lefthook.yml.tmpl");
const TMPL_GIT_WORKFLOW: &str = include_str!("../../resources/onboarding/git-workflow.md.tmpl"); const TMPL_GIT_WORKFLOW: &str = include_str!("../../resources/onboarding/git-workflow.md.tmpl");
const TMPL_ENVOY: &str = include_str!("../../resources/onboarding/.claude/commands/envoy.md");
/// 单个模板的分发结果。 /// 单个模板的分发结果。
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
@ -145,6 +146,7 @@ pub fn dispatch_templates(project_path: &str) -> Vec<TemplateResult> {
dispatch_versioned(root, "AGENTS.md", TMPL_AGENTS), dispatch_versioned(root, "AGENTS.md", TMPL_AGENTS),
dispatch_one(root, "lefthook.yml", TMPL_LEFTHOOK), dispatch_one(root, "lefthook.yml", TMPL_LEFTHOOK),
dispatch_versioned(root, "docs/ai-context/git-workflow.md", TMPL_GIT_WORKFLOW), dispatch_versioned(root, "docs/ai-context/git-workflow.md", TMPL_GIT_WORKFLOW),
dispatch_one(root, ".claude/commands/envoy.md", TMPL_ENVOY),
] ]
} }

View File

@ -57,6 +57,14 @@ pub fn tools_list_result() -> Value {
"required": ["project_id"] "required": ["project_id"]
} }
}, },
{
"name": "list_envoy_returns",
"description": "列出所有子项目通过「炼境使者」发回炼境的待处理洞察,按项目分组展示。炼境会话启动时调用,用于检查是否有需要处置的治理反馈(模板 bug、验证有效的实践、体系改进假设。无需任何参数。",
"inputSchema": {
"type": "object",
"properties": {}
}
},
{ {
"name": "append_project_note", "name": "append_project_note",
"description": "将 Claude 的分析结论或复盘记录写入项目笔记,供未来会话和用户在炼境中查阅。完成 M/L 复杂度任务后必须调用,写入结构化复盘笔记。", "description": "将 Claude 的分析结论或复盘记录写入项目笔记,供未来会话和用户在炼境中查阅。完成 M/L 复杂度任务后必须调用,写入结构化复盘笔记。",
@ -370,6 +378,9 @@ pub async fn tools_call(params: Option<&Value>) -> Value {
let pid = args.get("project_id").and_then(|v| v.as_str()).unwrap_or(""); let pid = args.get("project_id").and_then(|v| v.as_str()).unwrap_or("");
get_project_mentor_context(pid) get_project_mentor_context(pid)
} }
"list_envoy_returns" => {
list_envoy_returns()
}
"append_project_note" => { "append_project_note" => {
let pid = args.get("project_id").and_then(|v| v.as_str()).unwrap_or(""); let pid = args.get("project_id").and_then(|v| v.as_str()).unwrap_or("");
let content = args.get("content").and_then(|v| v.as_str()).unwrap_or(""); let content = args.get("content").and_then(|v| v.as_str()).unwrap_or("");
@ -887,6 +898,46 @@ fn get_project_mentor_context(project_id: &str) -> Result<String, String> {
Ok(ctx.mentor_markdown) Ok(ctx.mentor_markdown)
} }
fn list_envoy_returns() -> Result<String, String> {
let conn = db::pool().get().map_err(|e| e.to_string())?;
let mut stmt = conn
.prepare(
"SELECT pn.id, pp.name, pn.content, pn.created_at
FROM project_notes pn
JOIN project_workspaces pw ON pn.project_id = pw.id
JOIN project_profiles pp ON pw.profile_id = pp.id
WHERE pn.content LIKE '使%'
ORDER BY pn.created_at DESC
LIMIT 50",
)
.map_err(|e| e.to_string())?;
let rows: Vec<(i64, String, String, String)> = stmt
.query_map([], |row| {
Ok((
row.get::<_, i64>(0)?,
row.get::<_, String>(1)?,
row.get::<_, String>(2)?,
row.get::<_, String>(3)?,
))
})
.map_err(|e| e.to_string())?
.filter_map(|r| r.ok())
.collect();
if rows.is_empty() {
return Ok("📭 暂无待处理的炼境使者回传".to_string());
}
let mut out = format!("📬 炼境使者回传(共 {} 条)\n\n", rows.len());
for (_id, project_name, content, created_at) in &rows {
let date = &created_at[..created_at.len().min(10)];
out.push_str(&format!("---\n**来源**: {} | **时间**: {}\n{}\n\n", project_name, date, content));
}
out.push_str("---\n💡 处置参考fix_template → 改接入包模板文件update_convention → 改 CONVENTIONS.mdlog_for_review → 记录待观察,暂不动模板");
Ok(out)
}
fn append_project_note(project_id: &str, content: &str) -> Result<String, String> { fn append_project_note(project_id: &str, content: &str) -> Result<String, String> {
if content.trim().is_empty() { if content.trim().is_empty() {
return Err("笔记内容不能为空".to_string()); return Err("笔记内容不能为空".to_string());