From a7a7c3caba02a256edbf8d5ef3a753bae4c24c84 Mon Sep 17 00:00:00 2001 From: Julian Lechner Date: Thu, 24 Sep 2026 21:42:49 +0200 Subject: [PATCH] build(rules): generate skills from nested topics --- scripts/commands/build.ps1 | 21 +++++++++++++++++---- scripts/commands/build.sh | 23 +++++++++++++++++++++-- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/scripts/commands/build.ps1 b/scripts/commands/build.ps1 index c3b6691..eedf6c5 100644 --- a/scripts/commands/build.ps1 +++ b/scripts/commands/build.ps1 @@ -100,16 +100,29 @@ foreach ($shell in @('powershell','bash')) { $ruleSkills += [pscustomobject]@{ Name = "rules-$($ruleFile.BaseName)"; Body = $ruleFile.FullName; References = $null } } foreach ($ruleDirectory in Get-ChildItem $rulesRoot -Directory) { - $ruleBody = Join-Path $ruleDirectory.FullName 'index.md' - if (Test-Path -LiteralPath $ruleBody) { - $ruleSkills += [pscustomobject]@{ Name = "rules-$($ruleDirectory.Name)"; Body = $ruleBody; References = (Join-Path $ruleDirectory.FullName 'references') } + foreach ($ruleFile in Get-ChildItem $ruleDirectory.FullName -File -Filter '*.md' -Recurse) { + $relativeRulePath = $ruleFile.FullName.Substring($ruleDirectory.FullName.Length).TrimStart([char[]]@('\','/')) + if ($relativeRulePath -eq 'index.md') { + $ruleName = "rules-$($ruleDirectory.Name)" + $references = Join-Path $ruleDirectory.FullName 'references' + } else { + $ruleParts = $relativeRulePath.Substring(0, $relativeRulePath.Length - 3).Split([char[]]@('\','/')) + if ($ruleParts[0] -eq 'references') { $ruleParts = @($ruleParts | Select-Object -Skip 1) } + $ruleName = "rules-$($ruleDirectory.Name)-$($ruleParts -join '-')" + $references = $null + } + if ($ruleSkills.Name -contains $ruleName) { throw "Duplicate generated rule skill name: $ruleName" } + $ruleSkills += [pscustomobject]@{ Name = $ruleName; Body = $ruleFile.FullName; References = $references } } } foreach ($ruleSkill in $ruleSkills | Sort-Object Name) { $skillDir = Join-Path $output "claude-$shell/skills/rules/$($ruleSkill.Name)" New-Item $skillDir -ItemType Directory -Force | Out-Null $ruleContent = Get-Content $ruleSkill.Body -Raw - $skillBody = "---`r`nname: $($ruleSkill.Name)`r`ndescription: $(Quote-Toml 'Use when its matching rule condition in global instructions applies.')`r`n---`r`n`r`n$ruleContent" + $ruleTitle = (Get-Content $ruleSkill.Body | Where-Object { $_ -match '^#+\s+' } | Select-Object -First 1) -replace '^#+\s+', '' + if ([string]::IsNullOrWhiteSpace($ruleTitle)) { throw "Missing rule title in $($ruleSkill.Body)" } + $ruleDescription = "Use when the task concerns $ruleTitle." + $skillBody = "---`r`nname: $($ruleSkill.Name)`r`ndescription: $(Quote-Toml $ruleDescription)`r`n---`r`n`r`n$ruleContent" Set-Content (Join-Path $skillDir 'SKILL.md') $skillBody -Encoding UTF8 if ($ruleSkill.References -and (Test-Path -LiteralPath $ruleSkill.References)) { Copy-Directory $ruleSkill.References (Join-Path $skillDir 'references') diff --git a/scripts/commands/build.sh b/scripts/commands/build.sh index 67be2d0..1220013 100644 --- a/scripts/commands/build.sh +++ b/scripts/commands/build.sh @@ -69,20 +69,39 @@ copy_directory "$shared/hooks" "$output/codex-$shell/hooks" copy_directory "$shared/hooks" "$output/claude-$shell/hooks" copy_directory "$shared/statusline" "$output/claude-$shell/statusline" +declare -A rule_skill_names=() while IFS= read -r rule_path; do case "$rule_path" in general.md) continue ;; */index.md) skill_name="rules-${rule_path%/index.md}"; rule_body="$shared/rules/$rule_path"; references_dir="${rule_body%/index.md}/references" ;; + */*.md) + rule_directory=${rule_path%%/*} + rule_relative=${rule_path#*/} + rule_relative=${rule_relative%.md} + rule_relative=${rule_relative#references/} + rule_relative=${rule_relative//\//-} + skill_name="rules-$rule_directory-$rule_relative" + rule_body="$shared/rules/$rule_path" + references_dir='' + ;; *.md) skill_name="rules-${rule_path%.md}"; rule_body="$shared/rules/$rule_path"; references_dir='' ;; *) continue ;; esac + if [[ -n "${rule_skill_names[$skill_name]:-}" ]]; then + printf 'Duplicate generated rule skill name: %s\n' "$skill_name" >&2 + exit 1 + fi + rule_skill_names[$skill_name]=1 skill_dir="$output/claude-$shell/skills/rules/$skill_name" mkdir -p "$skill_dir" - { printf -- '---\nname: %s\ndescription: %s\n---\n\n' "$skill_name" "$(quote_toml 'Use when its matching rule condition in global instructions applies.')"; cat "$rule_body"; } > "$skill_dir/SKILL.md" + rule_title=$(sed -n 's/^#\{1,6\} *//p' "$rule_body" | head -n 1) + if [ -z "$rule_title" ]; then printf 'Missing rule title in %s\n' "$rule_body" >&2; exit 1; fi + rule_description="Use when the task concerns $rule_title." + { printf -- '---\nname: %s\ndescription: %s\n---\n\n' "$skill_name" "$(quote_toml "$rule_description")"; cat "$rule_body"; } > "$skill_dir/SKILL.md" if [ -n "$references_dir" ] && [ -d "$references_dir" ]; then copy_directory "$references_dir" "$skill_dir/references" fi -done < <(find "$shared/rules" -mindepth 1 -maxdepth 2 -type f -name '*.md' -printf '%P\n' | sort) +done < <(find "$shared/rules" -mindepth 1 -type f -name '*.md' -printf '%P\n' | sort) shared_template=$(<"$shared/global-instructions.md") general_content=$(<"$shared/rules/general.md")