From ea2c80817b22200565c1df6665c3191cca20f1cd Mon Sep 17 00:00:00 2001 From: Julian lechner Date: Thu, 17 Sep 2026 08:31:58 +0200 Subject: [PATCH] feat(agents): generate capabilities and rule loading --- adapters/claude/capabilities.tsv | 6 +++++ scripts/build.ps1 | 40 +++++++++++++++---------------- scripts/build.sh | 41 +++++++++++++++----------------- scripts/repository-context.ps1 | 2 +- 4 files changed, 46 insertions(+), 43 deletions(-) create mode 100644 adapters/claude/capabilities.tsv diff --git a/adapters/claude/capabilities.tsv b/adapters/claude/capabilities.tsv new file mode 100644 index 0000000..73c491f --- /dev/null +++ b/adapters/claude/capabilities.tsv @@ -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 diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 883a818..5865ba4 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -6,6 +6,7 @@ $shared = Join-Path $root 'shared' $output = Join-Path $root 'generated' $pluginManifest = Join-Path $root 'adapters/plugins.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" } $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 $capabilityManifest)) { throw "Missing capability manifest: $capabilityManifest" } $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.' } $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)" } $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 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: -- 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. +__CODEX_RULE_LOADING__ '@ +$codexRuleLoading = $codexRuleLoading.Replace('__CODEX_RULE_LOADING__', $codexRuleLoadingList) foreach ($shell in @('powershell','bash')) { 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') { 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 { - 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 } } } diff --git a/scripts/build.sh b/scripts/build.sh index f70fcf4..e6e59a4 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -13,6 +13,7 @@ shared="$root/shared" output="$root/generated" plugin_manifest="$root/adapters/plugins.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; } 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; } [ -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 rule_skill_files=() 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_names+=("$skill_name") 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; } +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() { 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. 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 +codex_rule_loading="${codex_rule_loading}"$'\n\n'"${codex_rule_loading_list%$'\n'}" for shell in powershell bash; do mkdir -p "$output/codex-$shell" "$output/claude-$shell" @@ -178,7 +174,8 @@ for client in codex claude; do 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" 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 done done diff --git a/scripts/repository-context.ps1 b/scripts/repository-context.ps1 index fa60fbe..18c6be4 100644 --- a/scripts/repository-context.ps1 +++ b/scripts/repository-context.ps1 @@ -6,7 +6,7 @@ $cacheDir = Join-Path $root '.ai-session' $cachePath = Join-Path $cacheDir 'repository-context.json' $safeRoot = $root.Replace('\','/') $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) $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('=')