diff --git a/src-tauri/resources/onboarding/.claude/hooks/blueprint-gate.cjs b/src-tauri/resources/onboarding/.claude/hooks/blueprint-gate.cjs new file mode 100644 index 0000000..cf379f7 --- /dev/null +++ b/src-tauri/resources/onboarding/.claude/hooks/blueprint-gate.cjs @@ -0,0 +1,31 @@ +#!/usr/bin/env node +// 蓝图门控:pre-commit 检查未关闭的 🔵 任务卡,warn only(exit 0,不阻断提交)。 + +const fs = require('fs'); +const path = require('path'); + +const modulesDir = path.join(process.cwd(), '.blueprint', 'modules'); + +if (!fs.existsSync(modulesDir)) { + process.exit(0); +} + +const unclosed = []; +for (const file of fs.readdirSync(modulesDir).filter(f => f.endsWith('.md'))) { + const content = fs.readFileSync(path.join(modulesDir, file), 'utf8'); + for (const line of content.split('\n')) { + if (line.startsWith('### 🔵')) { + unclosed.push({ file, title: line.replace(/^###\s*🔵\s*/, '').trim() }); + } + } +} + +if (unclosed.length > 0) { + process.stderr.write('\n⚠️ 蓝图门控:发现未关闭任务卡\n'); + for (const { file, title } of unclosed) { + process.stderr.write(` 🔵 ${title}(${file})\n`); + } + process.stderr.write('\n 已完成 → 改为 ✅;仍进行中 → 属预期,可忽略本提示。\n\n'); +} + +process.exit(0); diff --git a/src-tauri/resources/onboarding/AGENTS.md.tmpl b/src-tauri/resources/onboarding/AGENTS.md.tmpl index 1b29911..1c8c10f 100644 --- a/src-tauri/resources/onboarding/AGENTS.md.tmpl +++ b/src-tauri/resources/onboarding/AGENTS.md.tmpl @@ -18,7 +18,7 @@ - + ## 核心开发约束 ### 包管理 @@ -55,6 +55,8 @@ **边界模糊时**:保守判断,按 Feature 处理,走完整确认卡。 +**提交时**:`feat:` 提交须在 commit message 末尾加 `Feature-Confirmed: true` trailer(lefthook commit-msg 强制检查)。这是确认卡已输出的机器层存证。 + > ⚠️ Session 结束汇总补录 ≠ 确认卡替代——补录仅适用于漂移检测触发后用户主动选择补录的场景,不得作为「先做再补」的通道。 ## Git 工作流 diff --git a/src-tauri/resources/onboarding/lefthook.yml.tmpl b/src-tauri/resources/onboarding/lefthook.yml.tmpl index b292980..bbe11a8 100644 --- a/src-tauri/resources/onboarding/lefthook.yml.tmpl +++ b/src-tauri/resources/onboarding/lefthook.yml.tmpl @@ -17,8 +17,14 @@ pre-commit: # run: pnpm --filter api typecheck # monorepo workspace # # run: pnpm exec tsc -p api/tsconfig.json --noEmit # 独立 tsconfig # # run: pnpm -r typecheck # 递归所有包 + blueprint-gate: + run: node .claude/hooks/blueprint-gate.cjs || true commit-msg: commands: conventional: run: "head -1 .git/COMMIT_EDITMSG | grep -qE '^(feat|fix|docs|chore|refactor|ci|perf|style|test|build|revert)(\\(.+\\))?!?: .+' || exit 1" + feature-confirmed: + run: | + head -1 .git/COMMIT_EDITMSG | grep -qE '^feat' || exit 0 + grep -q 'Feature-Confirmed: true' .git/COMMIT_EDITMSG || { printf '\n⚠️ feat: 提交须含 Feature-Confirmed: true trailer\n 在 commit message 末尾加一行:Feature-Confirmed: true\n (确认卡已与用户对齐,方可提交)\n\n' >&2; exit 1; } diff --git a/src-tauri/src/commands/onboarding.rs b/src-tauri/src/commands/onboarding.rs index b15540e..c115e4d 100644 --- a/src-tauri/src/commands/onboarding.rs +++ b/src-tauri/src/commands/onboarding.rs @@ -17,6 +17,7 @@ const TMPL_AGENTS: &str = include_str!("../../resources/onboarding/AGENTS.md.tmp 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_ENVOY: &str = include_str!("../../resources/onboarding/.claude/commands/envoy.md"); +const TMPL_BLUEPRINT_GATE: &str = include_str!("../../resources/onboarding/.claude/hooks/blueprint-gate.cjs"); /// 单个模板的分发结果。 #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] @@ -147,6 +148,7 @@ pub fn dispatch_templates(project_path: &str) -> Vec { dispatch_one(root, "lefthook.yml", TMPL_LEFTHOOK), dispatch_versioned(root, "docs/ai-context/git-workflow.md", TMPL_GIT_WORKFLOW), dispatch_one(root, ".claude/commands/envoy.md", TMPL_ENVOY), + dispatch_one(root, ".claude/hooks/blueprint-gate.cjs", TMPL_BLUEPRINT_GATE), ] }