Merge pull request #99 from Miyabi-G-K/feature/issue-96-theta-skill-update

feat(dream): θ6 auto-update SKILL.md from rejections
This commit is contained in:
林 駿甫 (Shunsuke Hayashi) 2026-04-10 10:16:25 +09:00 committed by GitHub
commit b058632481
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -144,9 +144,68 @@ where
)?; )?;
} }
// θ6: Auto-update SKILL.md with drift corrections from gate rejections
if !report.patterns.gate_rejections.is_empty() {
update_skill_md_from_patterns(report, repo_root)?;
}
Ok(()) Ok(())
} }
/// Append a "Common Rejection Patterns" section to SKILL.md if gate rejections are detected.
fn update_skill_md_from_patterns(report: &DreamReport, repo_root: &Path) -> Result<()> {
let skill_path = repo_root.join("skills/polaris-ops/SKILL.md");
if !skill_path.exists() {
return Ok(());
}
let existing = fs::read_to_string(&skill_path)?;
let marker = "## よくある拒否パターン(自動生成)";
if existing.contains(marker) {
// Already has auto-generated section — remove it for refresh
let before = existing.split(marker).next().unwrap_or(&existing);
let mut content = before.trim_end().to_string();
content.push_str("\n\n");
content.push_str(&build_rejection_section(&report.patterns.gate_rejections));
content.push('\n');
let tmp = skill_path.with_extension("md.tmp");
fs::write(&tmp, &content)?;
fs::rename(&tmp, &skill_path)?;
} else {
let mut content = existing;
if !content.ends_with('\n') {
content.push('\n');
}
content.push('\n');
content.push_str(&build_rejection_section(&report.patterns.gate_rejections));
content.push('\n');
let tmp = skill_path.with_extension("md.tmp");
fs::write(&tmp, &content)?;
fs::rename(&tmp, &skill_path)?;
}
Ok(())
}
fn build_rejection_section(gate_rejections: &HashMap<String, usize>) -> String {
let mut section = String::from("## よくある拒否パターン(自動生成)\n\n");
section.push_str("| GATE | 回数 | 対処法 |\n|------|------|--------|\n");
let mut entries: Vec<_> = gate_rejections.iter().collect();
entries.sort_by(|a, b| b.1.cmp(a.1));
for (gate, count) in entries {
let remedy = match gate.as_str() {
"GATE 0" | "gate_0" => "Issue を先に作成する",
"GATE 2" | "gate_2" => "依存タスクを完了してから実行",
"GATE 3" | "gate_3" => "impact --approve で承認を付ける",
"GATE 4" | "gate_4" => "assign でロック獲得してから編集",
"GATE 5" | "gate_5" => "ブランチ名を feature/issue-N-slug 形式に",
_ => "手順を確認して条件を満たす",
};
section.push_str(&format!("| {gate} | {count} | {remedy} |\n"));
}
section
}
pub fn obsidian_export(learning: &Learning, vault_path: Option<&Path>) -> Result<PathBuf> { pub fn obsidian_export(learning: &Learning, vault_path: Option<&Path>) -> Result<PathBuf> {
let root = vault_path let root = vault_path
.map(Path::to_path_buf) .map(Path::to_path_buf)