feat(agents): generate capabilities and rule loading

This commit is contained in:
Julian lechner committed 2026-09-17 08:31:58 +02:00
1 parent 3fad860256
commit ea2c80817b
4 files changed
+46 -43

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
role tools
architect Read,Search,WebFetch
researcher Read,Search,WebFetch
reviewer Read,Diff,Search
verifier Read,Shell
implementer Read,Write,Shell,Tests
1 role tools
2 architect Read,Search,WebFetch
3 researcher Read,Search,WebFetch
4 reviewer Read,Diff,Search
5 verifier Read,Shell
6 implementer Read,Write,Shell,Tests
+20 -20
View File
@@ -6,6 +6,7 @@ $shared = Join-Path $root 'shared'
$output = Join-Path $root 'generated' $output = Join-Path $root 'generated'
$pluginManifest = Join-Path $root 'adapters/plugins.tsv' $pluginManifest = Join-Path $root 'adapters/plugins.tsv'
$ruleSkillManifest = Join-Path $root 'adapters/claude/rule-skills.tsv' $ruleSkillManifest = Join-Path $root 'adapters/claude/rule-skills.tsv'
$capabilityManifest = Join-Path $root 'adapters/claude/capabilities.tsv'
if (-not (Test-Path -LiteralPath $pluginManifest)) { throw "Missing plugin manifest: $pluginManifest" } if (-not (Test-Path -LiteralPath $pluginManifest)) { throw "Missing plugin manifest: $pluginManifest" }
$pluginEntries = @(Import-Csv -LiteralPath $pluginManifest -Delimiter ([char]9)) $pluginEntries = @(Import-Csv -LiteralPath $pluginManifest -Delimiter ([char]9))
@@ -20,6 +21,7 @@ foreach ($pluginEntry in $pluginEntries) {
} }
if (-not (Test-Path -LiteralPath $ruleSkillManifest)) { throw "Missing rule-skill manifest: $ruleSkillManifest" } if (-not (Test-Path -LiteralPath $ruleSkillManifest)) { throw "Missing rule-skill manifest: $ruleSkillManifest" }
if (-not (Test-Path -LiteralPath $capabilityManifest)) { throw "Missing capability manifest: $capabilityManifest" }
$ruleSkillEntries = @(Import-Csv -LiteralPath $ruleSkillManifest -Delimiter ([char]9) | Sort-Object skill_name) $ruleSkillEntries = @(Import-Csv -LiteralPath $ruleSkillManifest -Delimiter ([char]9) | Sort-Object skill_name)
if ($ruleSkillEntries.Count -eq 0) { throw 'Rule-skill manifest must define at least one entry.' } if ($ruleSkillEntries.Count -eq 0) { throw 'Rule-skill manifest must define at least one entry.' }
$ruleSkillNames = @() $ruleSkillNames = @()
@@ -31,6 +33,18 @@ foreach ($entry in $ruleSkillEntries) {
if ($ruleSkillNames -contains $entry.skill_name) { throw "Duplicate rule-skill name in manifest: $($entry.skill_name)" } if ($ruleSkillNames -contains $entry.skill_name) { throw "Duplicate rule-skill name in manifest: $($entry.skill_name)" }
$ruleSkillNames += $entry.skill_name $ruleSkillNames += $entry.skill_name
} }
$agentTools = @{}
foreach ($entry in @(Import-Csv -LiteralPath $capabilityManifest -Delimiter ([char]9))) {
if ([string]::IsNullOrWhiteSpace($entry.role) -or [string]::IsNullOrWhiteSpace($entry.tools)) { throw 'Invalid capability manifest row.' }
$agentTools[$entry.role] = $entry.tools
}
$codexRuleLoadingLines = @()
foreach ($entry in $ruleSkillEntries) {
$rulePath = $entry.rule_file
if (Test-Path -LiteralPath (Join-Path $shared "rules/$($entry.rule_file)") -PathType Container) { $rulePath = "$($entry.rule_file)/index.md" }
$codexRuleLoadingLines += "- rules/$rulePath for $($entry.trigger)."
}
$codexRuleLoadingList = $codexRuleLoadingLines -join "`r`n"
Remove-Item $output -Recurse -Force -ErrorAction SilentlyContinue Remove-Item $output -Recurse -Force -ErrorAction SilentlyContinue
New-Item $output -ItemType Directory -Force | Out-Null New-Item $output -ItemType Directory -Force | Out-Null
@@ -102,26 +116,9 @@ Detect the languages, frameworks, tools, and change areas from the repository an
Load rule files when their subject applies: Load rule files when their subject applies:
- rules/security-auth.md for authentication, authorization, sessions, tokens, permissions, or tenant boundaries. __CODEX_RULE_LOADING__
- rules/security-web.md for browser, frontend, cookie, redirect, XSS, or CSRF work.
- rules/security-api.md for HTTP APIs, request handling, serialization, or endpoints.
- rules/security-data.md for databases, persistence, sensitive data, or multi-tenancy.
- rules/security-files.md for files, uploads, archives, paths, processes, IPC, or deserialization.
- rules/security-network.md for network access, URLs, TLS, proxies, or SSRF.
- rules/security-crypto.md for cryptography, secrets, credentials, keys, or tokens.
- rules/security-supply-chain.md for dependencies, packages, plugins, builds, deployments, or CI.
- rules/angular/index.md for Angular work.
- rules/typescript.md for TypeScript work.
- rules/csharp.md for C# or .NET work.
- rules/wpf/index.md for WPF work.
- rules/ui-ux/index.md for UI or UX decisions.
- rules/microsoft.md for Microsoft 365, Azure DevOps, or Teams work.
- rules/orchestration.md for delegation, subagent work, or long-running task state.
- rules/git.md for Git operations.
- rules/refactoring.md for refactoring work.
- rules/definition-of-done.md when validating completion.
- rules/decision-rule.md when requirements are unresolved, behavior is ambiguous, or a meaningful technical choice remains.
'@ '@
$codexRuleLoading = $codexRuleLoading.Replace('__CODEX_RULE_LOADING__', $codexRuleLoadingList)
foreach ($shell in @('powershell','bash')) { foreach ($shell in @('powershell','bash')) {
New-Item (Join-Path $output "codex-$shell") -ItemType Directory -Force | Out-Null New-Item (Join-Path $output "codex-$shell") -ItemType Directory -Force | Out-Null
@@ -180,7 +177,10 @@ foreach ($shell in @('powershell','bash')) {
if ($client -eq 'codex') { if ($client -eq 'codex') {
Set-Content (Join-Path $agentsOutput "$name.toml") "name = $(Quote-Toml $name)`r`ndescription = $(Quote-Toml $description)`r`ndeveloper_instructions = $(Quote-Toml $instructions)" -Encoding UTF8 Set-Content (Join-Path $agentsOutput "$name.toml") "name = $(Quote-Toml $name)`r`ndescription = $(Quote-Toml $description)`r`ndeveloper_instructions = $(Quote-Toml $instructions)" -Encoding UTF8
} else { } else {
Set-Content (Join-Path $agentsOutput "$name.md") "---`r`nname: $name`r`ndescription: $description`r`n---`r`n`r`n$instructions" -Encoding UTF8 $tools = $agentTools[$name]
$frontmatter = "---`r`nname: $name`r`ndescription: $description`r`n"
if ($tools) { $frontmatter += "tools: $tools`r`n" }
Set-Content (Join-Path $agentsOutput "$name.md") ($frontmatter + "---`r`n`r`n$instructions") -Encoding UTF8
} }
} }
} }
+19 -22
View File
@@ -13,6 +13,7 @@ shared="$root/shared"
output="$root/generated" output="$root/generated"
plugin_manifest="$root/adapters/plugins.tsv" plugin_manifest="$root/adapters/plugins.tsv"
rule_skill_manifest="$root/adapters/claude/rule-skills.tsv" rule_skill_manifest="$root/adapters/claude/rule-skills.tsv"
capability_manifest="$root/adapters/claude/capabilities.tsv"
[ -f "$plugin_manifest" ] || { printf 'Missing plugin manifest: %s\n' "$plugin_manifest" >&2; exit 1; } [ -f "$plugin_manifest" ] || { printf 'Missing plugin manifest: %s\n' "$plugin_manifest" >&2; exit 1; }
declare -A plugin_seen declare -A plugin_seen
@@ -29,6 +30,7 @@ done < "$plugin_manifest"
[ "$plugin_count" -gt 0 ] || { printf 'Plugin manifest must define at least one plugin.\n' >&2; exit 1; } [ "$plugin_count" -gt 0 ] || { printf 'Plugin manifest must define at least one plugin.\n' >&2; exit 1; }
[ -f "$rule_skill_manifest" ] || { printf 'Missing rule-skill manifest: %s\n' "$rule_skill_manifest" >&2; exit 1; } [ -f "$rule_skill_manifest" ] || { printf 'Missing rule-skill manifest: %s\n' "$rule_skill_manifest" >&2; exit 1; }
[ -f "$capability_manifest" ] || { printf 'Missing capability manifest: %s\n' "$capability_manifest" >&2; exit 1; }
declare -A rule_skill_seen declare -A rule_skill_seen
rule_skill_files=() rule_skill_files=()
rule_skill_names=() rule_skill_names=()
@@ -45,8 +47,21 @@ while IFS=$'\t' read -r rule_file skill_name trigger; do
rule_skill_files+=("$rule_file") rule_skill_files+=("$rule_file")
rule_skill_names+=("$skill_name") rule_skill_names+=("$skill_name")
rule_skill_triggers+=("$trigger") rule_skill_triggers+=("$trigger")
done < "$rule_skill_manifest" done < <(tail -n +2 "$rule_skill_manifest" | sort -t$'\t' -k2,2)
[ "${#rule_skill_names[@]}" -gt 0 ] || { printf 'Rule-skill manifest must define at least one entry.\n' >&2; exit 1; } [ "${#rule_skill_names[@]}" -gt 0 ] || { printf 'Rule-skill manifest must define at least one entry.\n' >&2; exit 1; }
codex_rule_loading_list=""
for i in "${!rule_skill_names[@]}"; do
rule_file=${rule_skill_files[$i]}
rule_path="$rule_file"
[ -d "$shared/rules/$rule_file" ] && rule_path="$rule_file/index.md"
codex_rule_loading_list="${codex_rule_loading_list}- rules/${rule_path} for ${rule_skill_triggers[$i]}."$'\n'
done
declare -A agent_tools
while IFS=$'\t' read -r role tools; do
[ "$role" != role ] || continue
[ -n "$role" ] && [ -n "$tools" ] || { printf 'Invalid capability manifest row.\n' >&2; exit 1; }
agent_tools[$role]="$tools"
done < "$capability_manifest"
copy_directory() { copy_directory() {
local source=$1 destination=$2 local source=$1 destination=$2
@@ -97,27 +112,8 @@ When programming, always load and apply `rules/security.md`. This includes imple
Detect the languages, frameworks, tools, and change areas from the repository and the requested work. Load every applicable rule file before editing. Load all matching files when multiple technologies apply. Detect the languages, frameworks, tools, and change areas from the repository and the requested work. Load every applicable rule file before editing. Load all matching files when multiple technologies apply.
Load rule files when their subject applies: Load rule files when their subject applies:
- rules/security-auth.md for authentication, authorization, sessions, tokens, permissions, or tenant boundaries.
- rules/security-web.md for browser, frontend, cookie, redirect, XSS, or CSRF work.
- rules/security-api.md for HTTP APIs, request handling, serialization, or endpoints.
- rules/security-data.md for databases, persistence, sensitive data, or multi-tenancy.
- rules/security-files.md for files, uploads, archives, paths, processes, IPC, or deserialization.
- rules/security-network.md for network access, URLs, TLS, proxies, or SSRF.
- rules/security-crypto.md for cryptography, secrets, credentials, keys, or tokens.
- rules/security-supply-chain.md for dependencies, packages, plugins, builds, deployments, or CI.
- rules/angular/index.md for Angular work.
- rules/typescript.md for TypeScript work.
- rules/csharp.md for C# or .NET work.
- rules/wpf/index.md for WPF work.
- rules/ui-ux/index.md for UI or UX decisions.
- rules/microsoft.md for Microsoft 365, Azure DevOps, or Teams work.
- rules/orchestration.md for delegation, subagent work, or long-running task state.
- rules/git.md for Git operations.
- rules/refactoring.md for refactoring work.
- rules/definition-of-done.md when validating completion.
- rules/decision-rule.md when requirements are unresolved, behavior is ambiguous, or a meaningful technical choice remains.
BLOCK BLOCK
codex_rule_loading="${codex_rule_loading}"$'\n\n'"${codex_rule_loading_list%$'\n'}"
for shell in powershell bash; do for shell in powershell bash; do
mkdir -p "$output/codex-$shell" "$output/claude-$shell" mkdir -p "$output/codex-$shell" "$output/claude-$shell"
@@ -178,7 +174,8 @@ for client in codex claude; do
if [ "$client" = codex ]; then if [ "$client" = codex ]; then
printf 'name = %s\ndescription = %s\ndeveloper_instructions = %s\n' "$(quote_toml "$name")" "$(quote_toml "$description")" "$(quote_toml "$instructions")" > "$agents_output/$name.toml" printf 'name = %s\ndescription = %s\ndeveloper_instructions = %s\n' "$(quote_toml "$name")" "$(quote_toml "$description")" "$(quote_toml "$instructions")" > "$agents_output/$name.toml"
else else
printf '%s\nname: %s\ndescription: %s\n%s\n\n%s\n' '---' "$name" "$description" '---' "$instructions" > "$agents_output/$name.md" tools=${agent_tools[$name]:-}
if [ -n "$tools" ]; then printf '%s\nname: %s\ndescription: %s\ntools: %s\n%s\n\n%s\n' '---' "$name" "$description" "$tools" '---' "$instructions" > "$agents_output/$name.md"; else printf '%s\nname: %s\ndescription: %s\n%s\n\n%s\n' '---' "$name" "$description" '---' "$instructions" > "$agents_output/$name.md"; fi
fi fi
done done
done done
+1 -1
View File
@@ -6,7 +6,7 @@ $cacheDir = Join-Path $root '.ai-session'
$cachePath = Join-Path $cacheDir 'repository-context.json' $cachePath = Join-Path $cacheDir 'repository-context.json'
$safeRoot = $root.Replace('\','/') $safeRoot = $root.Replace('\','/')
$head = (& git -c "safe.directory=$safeRoot" -C $root rev-parse HEAD 2>$null).Trim() $head = (& git -c "safe.directory=$safeRoot" -C $root rev-parse HEAD 2>$null).Trim()
$inputs = @('AI-Instructions.md','adapters/plugins.tsv','adapters/claude/rule-skills.tsv','adapters/codex/config/config.toml','adapters/claude/config/settings.json') $inputs = @('AI-Instructions.md','adapters/plugins.tsv','adapters/claude/rule-skills.tsv','adapters/claude/capabilities.tsv','adapters/prompt-budget-baseline.json','adapters/prompt-budget-baseline.tsv','adapters/codex/config/config.toml','adapters/claude/config/settings.json')
$inputs += @(Get-ChildItem $root -File -Recurse | Where-Object { $_.FullName -notmatch '\\(generated|\.git|node_modules|bin|obj|vendor|\.ai-session)\\' -and $_.Extension -in @('.sln','.csproj','.fsproj','.ts','.tsx','.json','.toml','.yaml','.yml','.cs','.fs','.py','.sh','.ps1') } | ForEach-Object { $_.FullName.Substring($root.Length + 1) } | Sort-Object) $inputs += @(Get-ChildItem $root -File -Recurse | Where-Object { $_.FullName -notmatch '\\(generated|\.git|node_modules|bin|obj|vendor|\.ai-session)\\' -and $_.Extension -in @('.sln','.csproj','.fsproj','.ts','.tsx','.json','.toml','.yaml','.yml','.cs','.fs','.py','.sh','.ps1') } | ForEach-Object { $_.FullName.Substring($root.Length + 1) } | Sort-Object)
$keyText = $head + "`n" + (($inputs | Sort-Object -Unique | ForEach-Object { $_ + ':' + (Get-FileHash (Join-Path $root $_) -Algorithm SHA256).Hash }) -join "`n") $keyText = $head + "`n" + (($inputs | Sort-Object -Unique | ForEach-Object { $_ + ':' + (Get-FileHash (Join-Path $root $_) -Algorithm SHA256).Hash }) -join "`n")
$key = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($keyText)).TrimEnd('=') $key = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($keyText)).TrimEnd('=')