refactor(rules): move loading map to global instructions
This commit is contained in:
1 parent
a50575dbec
commit
aeec4d351c
14 files changed
+113
-192
No files matched your search
@@ -1,21 +0,0 @@
|
||||
rule_file skill_name trigger
|
||||
angular rules-angular Angular component, template, or RxJS/signal work
|
||||
csharp.md rules-csharp C# or .NET implementation, review, or refactoring work
|
||||
decision-rule.md rules-decision-rule unresolved requirements, ambiguous behavior, or a meaningful technical choice
|
||||
definition-of-done.md rules-definition-of-done validating that a task is complete or ready for review
|
||||
git.md rules-git Git operations such as commits, branches, or merges
|
||||
microsoft.md rules-microsoft Microsoft 365, Azure DevOps, or Teams operations
|
||||
orchestration.md rules-orchestration delegation, subagent work, or long-running task state
|
||||
refactoring.md rules-refactoring refactoring existing code without changing behavior
|
||||
security.md rules-security any software development task: implementing, modifying, debugging, reviewing, testing, or configuring code, scripts, hooks, or infrastructure
|
||||
security-auth.md rules-security-auth authentication, authorization, sessions, tokens, permissions, or tenant-boundary work
|
||||
security-web.md rules-security-web web, browser, frontend, cookie, redirect, or XSS/CSRF work
|
||||
security-api.md rules-security-api HTTP API, request handling, serialization, or endpoint work
|
||||
security-data.md rules-security-data database, query, persistence, sensitive-data, or multi-tenant work
|
||||
security-files.md rules-security-files file, upload, archive, path, process, IPC, or deserialization work
|
||||
security-network.md rules-security-network network, URL fetch, proxy, outbound request, TLS, or SSRF work
|
||||
security-crypto.md rules-security-crypto cryptography, secrets, credentials, key, or token work
|
||||
security-supply-chain.md rules-security-supply-chain dependency, package, plugin, build, deployment, or CI supply-chain work
|
||||
typescript.md rules-typescript TypeScript or modern ECMAScript implementation work
|
||||
ui-ux rules-ui-ux UI or UX design and interaction decisions
|
||||
wpf rules-wpf WPF, XAML, or MVVM desktop UI work
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
Codex receives a generated global `AGENTS.md`, agent TOML files, skills, rule files, and a `config.toml` adapter. It keeps workspace sandboxing, automatic approval review, `agents.enabled = true`, `agents.max_concurrent_threads_per_session = 5`, and `agents.max_depth = 1`. Claude Code receives a generated global `CLAUDE.md`, agent Markdown files, skills, and `settings.json` with `auto` permissions, explicit `Agent` permission, and five-operation concurrency. Installation resolves user paths at runtime; the repository contains no machine-specific paths.
|
||||
|
||||
Both clients embed `shared/rules/general.md` in their global instructions. Codex loads applicable focused rules from `rules/`; Claude generates them as skills from the shared `adapters/rule-skills.tsv` manifest. Angular, WPF, and UI/UX rules have small entry points with references loaded by topic. `security.md` is the baseline for programming tasks; focused security rules cover authentication, web, API, data, files, network, cryptography, and supply-chain work.
|
||||
Both clients embed `shared/rules/general.md` and the rule-loading conditions from `shared/global-instructions.md`. Codex loads applicable focused rules from `rules/`; Claude generates a matching skill for each focused rule file or rule directory. Angular, WPF, and UI/UX rules have small entry points with references loaded by topic. `security.md` is the baseline for programming tasks; focused security rules cover authentication, web, API, data, files, network, cryptography, and supply-chain work.
|
||||
|
||||
The Codex adapter sets `agents.max_depth = 1` and does not enable `features.multi_agent_v2`. Shared instructions also prohibit subagents from creating further subagents. Agent roles are optional, not stages that every task must run.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ Shared definitions are the source of truth. Rebuild after every semantic change.
|
||||
|
||||
## Add a rule
|
||||
|
||||
Create a focused Markdown file in `shared/rules/`. For a rule that applies only to a language, framework, tool, or change area, add one row to `adapters/rule-skills.tsv`. The build uses that manifest to generate both Claude's rule skill and Codex's rule-loading condition. Put always-applicable guidance in `general.md`, which the build embeds for both clients; a new rule file is not automatically embedded.
|
||||
Create a focused Markdown file in `shared/rules/` and add its loading condition to `shared/global-instructions.md`. Claude rule skills are generated from the rule filenames; Codex loads the referenced rule files directly. Put always-applicable guidance in `general.md`, which the build embeds for both clients; a new rule file is not automatically embedded.
|
||||
|
||||
## Add a skill
|
||||
|
||||
|
||||
+4
-4
@@ -14,10 +14,10 @@ The current structure is organized by purpose:
|
||||
|
||||
The files intentionally define only a small baseline. Detailed personal workflows and standards can be added later without changing the structure.
|
||||
|
||||
Claude's generated package additionally has `skills/rules/`: one skill per technology- or situation-specific rule in
|
||||
`shared/rules/` (driven by the shared `adapters/rule-skills.tsv` manifest), so the full rule text loads only when Claude invokes the
|
||||
matching skill instead of sitting permanently in context. This category does not exist for Codex, which keeps loading
|
||||
applicable focused rules as plain files instead. Both clients embed the general rules in their global instructions.
|
||||
Claude's generated package additionally has `skills/rules/`: one skill per focused rule file or directory in
|
||||
`shared/rules/`. The rule-to-skill loading conditions live in `shared/global-instructions.md`, so full rule text loads
|
||||
only when Claude invokes a matching skill instead of sitting permanently in context. Codex reads the matching rule
|
||||
files directly. Both clients embed the general rules in their global instructions.
|
||||
Split Angular, WPF, and UI/UX rules load supporting references only for the topic being worked on. See [Configuration](configuration.md).
|
||||
|
||||
Skills guide a task when needed; their availability does not require a full review, build, or test suite after every
|
||||
|
||||
+19
-54
@@ -5,7 +5,6 @@ $root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||
$shared = Join-Path $root 'shared'
|
||||
$output = Join-Path $root 'generated'
|
||||
$pluginManifest = Join-Path $root 'adapters/plugins.tsv'
|
||||
$ruleSkillManifest = Join-Path $root 'adapters/rule-skills.tsv'
|
||||
$capabilityManifest = Join-Path $root 'adapters/claude/capabilities.tsv'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $pluginManifest)) { throw "Missing plugin manifest: $pluginManifest" }
|
||||
@@ -20,31 +19,12 @@ foreach ($pluginEntry in $pluginEntries) {
|
||||
$pluginNames += $pluginEntry.name
|
||||
}
|
||||
|
||||
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 = @()
|
||||
foreach ($entry in $ruleSkillEntries) {
|
||||
foreach ($field in @('rule_file','skill_name','trigger')) {
|
||||
if ([string]::IsNullOrWhiteSpace($entry.$field)) { throw "Missing $field in rule-skill manifest row for $($entry.rule_file)." }
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $shared "rules/$($entry.rule_file)"))) { throw "Rule-skill manifest references a missing rule file: $($entry.rule_file)" }
|
||||
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
|
||||
@@ -104,22 +84,6 @@ foreach ($agentDir in $sourceAgentDirs) {
|
||||
$agentNames += $name
|
||||
}
|
||||
|
||||
# Codex: general.md is embedded directly into AGENTS.md, the same way Claude embeds
|
||||
# it into CLAUDE.md, instead of only being referenced by path — guaranteed present
|
||||
# either way, and safe even if a subagent's AGENTS.md inheritance is not guaranteed.
|
||||
$codexRuleLoading = @'
|
||||
General rules are embedded below.
|
||||
|
||||
When programming, always load and apply `rules/security.md`. This includes implementing, modifying, debugging, reviewing, testing, and configuring software, scripts, hooks, infrastructure, and integrations.
|
||||
|
||||
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:
|
||||
|
||||
__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
|
||||
New-Item (Join-Path $output "claude-$shell") -ItemType Directory -Force | Out-Null
|
||||
@@ -130,36 +94,37 @@ foreach ($shell in @('powershell','bash')) {
|
||||
Copy-Directory (Join-Path $shared 'hooks') (Join-Path $output "claude-$shell/hooks")
|
||||
Copy-Directory (Join-Path $shared 'statusline') (Join-Path $output "claude-$shell/statusline")
|
||||
|
||||
$claudeRulesDir = Join-Path $output "claude-$shell/rules"
|
||||
New-Item $claudeRulesDir -ItemType Directory -Force | Out-Null
|
||||
|
||||
$claudeRuleSkillLines = @()
|
||||
foreach ($entry in $ruleSkillEntries) {
|
||||
$ruleSourcePath = Join-Path $shared "rules/$($entry.rule_file)"
|
||||
$isDirectory = Test-Path -LiteralPath $ruleSourcePath -PathType Container
|
||||
$ruleBodyPath = if ($isDirectory) { Join-Path $ruleSourcePath 'index.md' } else { $ruleSourcePath }
|
||||
$skillDir = Join-Path $output "claude-$shell/skills/rules/$($entry.skill_name)"
|
||||
$rulesRoot = Join-Path $shared 'rules'
|
||||
$ruleSkills = @()
|
||||
foreach ($ruleFile in Get-ChildItem $rulesRoot -File -Filter '*.md' | Where-Object Name -ne 'general.md') {
|
||||
$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 ($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 $ruleBodyPath -Raw
|
||||
$skillBody = "---`r`nname: $($entry.skill_name)`r`ndescription: $(Quote-Toml "Use for $($entry.trigger).")`r`n---`r`n`r`n$ruleContent"
|
||||
$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"
|
||||
Set-Content (Join-Path $skillDir 'SKILL.md') $skillBody -Encoding UTF8
|
||||
if ($isDirectory) {
|
||||
$referencesSource = Join-Path $ruleSourcePath 'references'
|
||||
if (Test-Path -LiteralPath $referencesSource) { Copy-Directory $referencesSource (Join-Path $skillDir 'references') }
|
||||
if ($ruleSkill.References -and (Test-Path -LiteralPath $ruleSkill.References)) {
|
||||
Copy-Directory $ruleSkill.References (Join-Path $skillDir 'references')
|
||||
}
|
||||
$claudeRuleSkillLines += "- $($entry.skill_name) for $($entry.trigger)."
|
||||
}
|
||||
$claudeRuleLoading = "General rules are embedded below.`r`n`r`nDetect the languages, frameworks, tools, and change areas from the repository and the requested work. Invoke every matching rule skill before editing. Invoke all matching rule skills when multiple technologies apply.`r`n`r`nInvoke rule skills when their subject applies:`r`n`r`n" + ($claudeRuleSkillLines -join "`r`n")
|
||||
|
||||
$sharedTemplate = Get-Content (Join-Path $shared 'global-instructions.md') -Raw
|
||||
$generalContent = Get-Content (Join-Path $shared 'rules/general.md') -Raw
|
||||
$credentialHelperExtension = if ($shell -eq 'powershell') { 'ps1' } else { 'sh' }
|
||||
|
||||
$agentsContent = $sharedTemplate.Replace('__RULE_LOADING__', $codexRuleLoading).Replace('__CLIENT__', 'codex').Replace('__SHELL__', $credentialHelperExtension)
|
||||
$agentsContent = $sharedTemplate.Replace('__CLIENT__', 'codex').Replace('__SHELL__', $credentialHelperExtension)
|
||||
$agentsContent = $agentsContent.TrimEnd() + "`r`n`r`n---`r`n`r`n$generalContent"
|
||||
Set-Content (Join-Path $output "codex-$shell/AGENTS.md") $agentsContent -Encoding UTF8
|
||||
|
||||
$claudeContent = $sharedTemplate.Replace('__RULE_LOADING__', $claudeRuleLoading).Replace('__CLIENT__', 'claude').Replace('__SHELL__', $credentialHelperExtension)
|
||||
$claudeContent = $sharedTemplate.Replace('__CLIENT__', 'claude').Replace('__SHELL__', $credentialHelperExtension)
|
||||
$claudeContent = $claudeContent.TrimEnd() + "`r`n`r`n---`r`n`r`n$generalContent"
|
||||
Set-Content (Join-Path $output "claude-$shell/CLAUDE.md") $claudeContent -Encoding UTF8
|
||||
|
||||
|
||||
+13
-82
@@ -12,50 +12,9 @@ root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
|
||||
shared="$root/shared"
|
||||
output="$root/generated"
|
||||
plugin_manifest="$root/adapters/plugins.tsv"
|
||||
rule_skill_manifest="$root/adapters/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
|
||||
plugin_count=0
|
||||
while IFS=$'\t' read -r name _ claude_plugin _ codex_plugin; do
|
||||
[ "$name" != name ] || continue
|
||||
[ -n "$name" ] || continue
|
||||
plugin_count=$((plugin_count + 1))
|
||||
[ -n "$claude_plugin" ] || { printf 'Missing claude_plugin for plugin %s.\n' "$name" >&2; exit 1; }
|
||||
[ -n "$codex_plugin" ] || { printf 'Missing codex_plugin for plugin %s.\n' "$name" >&2; exit 1; }
|
||||
[ -z "${plugin_seen[$name]+x}" ] || { printf 'Duplicate plugin name in manifest: %s\n' "$name" >&2; exit 1; }
|
||||
plugin_seen[$name]=1
|
||||
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=()
|
||||
rule_skill_triggers=()
|
||||
while IFS=$'\t' read -r rule_file skill_name trigger; do
|
||||
trigger=${trigger%$'\r'}
|
||||
[ "$rule_file" != rule_file ] || continue
|
||||
[ -n "$rule_file" ] || continue
|
||||
[ -n "$skill_name" ] || { printf 'Missing skill_name in rule-skill manifest row for %s.\n' "$rule_file" >&2; exit 1; }
|
||||
[ -n "$trigger" ] || { printf 'Missing trigger in rule-skill manifest row for %s.\n' "$rule_file" >&2; exit 1; }
|
||||
[ -f "$shared/rules/$rule_file" ] || [ -d "$shared/rules/$rule_file" ] || { printf 'Rule-skill manifest references a missing rule file: %s\n' "$rule_file" >&2; exit 1; }
|
||||
[ -z "${rule_skill_seen[$skill_name]+x}" ] || { printf 'Duplicate rule-skill name in manifest: %s\n' "$skill_name" >&2; exit 1; }
|
||||
rule_skill_seen[$skill_name]=1
|
||||
rule_skill_files+=("$rule_file")
|
||||
rule_skill_names+=("$skill_name")
|
||||
rule_skill_triggers+=("$trigger")
|
||||
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
|
||||
@@ -101,20 +60,6 @@ for agent_dir in "${agent_dirs[@]}"; do
|
||||
agent_seen[$name]=1
|
||||
done
|
||||
|
||||
# Codex: general.md is embedded directly into AGENTS.md, the same way Claude embeds
|
||||
# it into CLAUDE.md, instead of only being referenced by path — guaranteed present
|
||||
# either way, and safe even if a subagent's AGENTS.md inheritance is not guaranteed.
|
||||
read -r -d '' codex_rule_loading <<'BLOCK' || true
|
||||
General rules are embedded below.
|
||||
|
||||
When programming, always load and apply `rules/security.md`. This includes implementing, modifying, debugging, reviewing, testing, and configuring software, scripts, hooks, infrastructure, and integrations.
|
||||
|
||||
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:
|
||||
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"
|
||||
copy_directory "$shared/skills" "$output/codex-$shell/skills"
|
||||
@@ -124,45 +69,31 @@ copy_directory "$shared/hooks" "$output/codex-$shell/hooks"
|
||||
copy_directory "$shared/hooks" "$output/claude-$shell/hooks"
|
||||
copy_directory "$shared/statusline" "$output/claude-$shell/statusline"
|
||||
|
||||
mkdir -p "$output/claude-$shell/rules"
|
||||
|
||||
claude_rule_loading_list=""
|
||||
for i in "${!rule_skill_names[@]}"; do
|
||||
rule_file=${rule_skill_files[$i]}
|
||||
skill_name=${rule_skill_names[$i]}
|
||||
trigger=${rule_skill_triggers[$i]}
|
||||
rule_source="$shared/rules/$rule_file"
|
||||
rule_body="$rule_source"
|
||||
[ -d "$rule_source" ] && rule_body="$rule_source/index.md"
|
||||
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) skill_name="rules-${rule_path%.md}"; rule_body="$shared/rules/$rule_path"; references_dir='' ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
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 for $trigger.")"; cat "$rule_body"; } > "$skill_dir/SKILL.md"
|
||||
if [ -d "$rule_source/references" ]; then
|
||||
copy_directory "$rule_source/references" "$skill_dir/references"
|
||||
{ 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"
|
||||
if [ -n "$references_dir" ] && [ -d "$references_dir" ]; then
|
||||
copy_directory "$references_dir" "$skill_dir/references"
|
||||
fi
|
||||
claude_rule_loading_list="${claude_rule_loading_list}- ${skill_name} for ${trigger}.
|
||||
"
|
||||
done
|
||||
claude_rule_loading="General rules are embedded below.
|
||||
|
||||
Detect the languages, frameworks, tools, and change areas from the repository and the requested work. Invoke every matching rule skill before editing. Invoke all matching rule skills when multiple technologies apply.
|
||||
|
||||
Invoke rule skills when their subject applies:
|
||||
|
||||
${claude_rule_loading_list%$'\n'}"
|
||||
done < <(find "$shared/rules" -mindepth 1 -maxdepth 2 -type f -name '*.md' -printf '%P\n' | sort)
|
||||
|
||||
shared_template=$(<"$shared/global-instructions.md")
|
||||
general_content=$(<"$shared/rules/general.md")
|
||||
credential_helper_extension='sh'
|
||||
[ "$shell" != powershell ] || credential_helper_extension='ps1'
|
||||
|
||||
agents_content=${shared_template//__RULE_LOADING__/$codex_rule_loading}
|
||||
agents_content=${agents_content//__CLIENT__/codex}
|
||||
agents_content=${shared_template//__CLIENT__/codex}
|
||||
agents_content=${agents_content//__SHELL__/$credential_helper_extension}
|
||||
printf '%s\n\n---\n\n%s\n' "${agents_content%$'\n'}" "$general_content" > "$output/codex-$shell/AGENTS.md"
|
||||
|
||||
claude_content=${shared_template//__RULE_LOADING__/$claude_rule_loading}
|
||||
claude_content=${claude_content//__CLIENT__/claude}
|
||||
claude_content=${shared_template//__CLIENT__/claude}
|
||||
claude_content=${claude_content//__SHELL__/$credential_helper_extension}
|
||||
printf '%s\n\n---\n\n%s\n' "${claude_content%$'\n'}" "$general_content" > "$output/claude-$shell/CLAUDE.md"
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ if (Get-Command jq -ErrorAction SilentlyContinue) { Result 'PASS' 'jq available
|
||||
|
||||
$sourceAgentCount = @(Get-ChildItem (Join-Path $root 'shared/agents') -Directory -ErrorAction SilentlyContinue).Count
|
||||
$sourceSkillCount = @(Get-ChildItem (Join-Path $root 'shared/skills') -Filter 'SKILL.md' -Recurse -ErrorAction SilentlyContinue).Count
|
||||
$ruleSkillManifestPath = Join-Path $root 'adapters/rule-skills.tsv'
|
||||
$ruleSkillCount = if (Test-Path -LiteralPath $ruleSkillManifestPath) { @(Import-Csv -LiteralPath $ruleSkillManifestPath -Delimiter ([char]9)).Count } else { 0 }
|
||||
$rulesRoot = Join-Path $root 'shared/rules'
|
||||
$ruleSkillCount = @((Get-ChildItem $rulesRoot -File -Filter '*.md' | Where-Object Name -ne 'general.md')).Count + @((Get-ChildItem $rulesRoot -Directory | Where-Object { Test-Path (Join-Path $_.FullName 'index.md') })).Count
|
||||
|
||||
foreach ($shell in @('powershell','bash')) {
|
||||
if (Test-Path (Join-Path $root "generated/codex-$shell/AGENTS.md")) { Result 'PASS' "generated output present ($shell)" } else { Result 'FAIL' "generated output missing ($shell); run build" }
|
||||
|
||||
@@ -33,9 +33,8 @@ command -v jq >/dev/null && result PASS 'jq available for Claude Bash status lin
|
||||
|
||||
source_agent_count=$(find "$root/shared/agents" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l)
|
||||
source_skill_count=$(find "$root/shared/skills" -name SKILL.md 2>/dev/null | wc -l)
|
||||
rule_skill_manifest="$root/adapters/rule-skills.tsv"
|
||||
rule_skill_count=0
|
||||
[ -f "$rule_skill_manifest" ] && rule_skill_count=$(($(wc -l < "$rule_skill_manifest") - 1))
|
||||
rule_skill_count=$(find "$root/shared/rules" -mindepth 1 -maxdepth 1 -type f -name '*.md' ! -name general.md | wc -l)
|
||||
rule_skill_count=$((rule_skill_count + $(find "$root/shared/rules" -mindepth 2 -maxdepth 2 -type f -name index.md | wc -l)))
|
||||
|
||||
for shell in powershell bash; do
|
||||
[ -f "$root/generated/codex-$shell/AGENTS.md" ] && result PASS "generated output present ($shell)" || result FAIL "generated output missing ($shell); run build"
|
||||
|
||||
@@ -12,12 +12,11 @@ function Tokens([int64]$bytes) { return [int][Math]::Ceiling($bytes / 4.0) }
|
||||
$claude = (Get-Item (Join-Path $generated 'claude-bash/CLAUDE.md')).Length
|
||||
$codex = (Get-Item (Join-Path $generated 'codex-bash/AGENTS.md')).Length
|
||||
$skills = Measure-Tree (Join-Path $generated 'claude-bash/skills')
|
||||
$triggers = (Get-Item (Join-Path $root 'adapters/rule-skills.tsv')).Length
|
||||
$lazyItem = Get-ChildItem (Join-Path $root 'shared/rules') -File -Recurse | Sort-Object Length -Descending | Select-Object -First 1
|
||||
$lazy = if ($lazyItem) { [int64]$lazyItem.Length } else { 0 }
|
||||
$baseline = Join-Path $root 'adapters/prompt-budget-baseline.json'
|
||||
$old = if (Test-Path $baseline) { Get-Content $baseline -Raw | ConvertFrom-Json } else { $null }
|
||||
$rows = @([pscustomobject]@{Name='Claude permanent context';Bytes=$claude;Tokens=(Tokens $claude)},[pscustomobject]@{Name='Codex permanent context';Bytes=$codex;Tokens=(Tokens $codex)},[pscustomobject]@{Name='Claude skill files (lazy)';Bytes=$skills;Tokens=(Tokens $skills)},[pscustomobject]@{Name='Rule trigger metadata';Bytes=$triggers;Tokens=(Tokens $triggers)},[pscustomobject]@{Name='Largest lazy rule';Bytes=$lazy;Tokens=(Tokens $lazy)})
|
||||
$rows = @([pscustomobject]@{Name='Claude permanent context';Bytes=$claude;Tokens=(Tokens $claude)},[pscustomobject]@{Name='Codex permanent context';Bytes=$codex;Tokens=(Tokens $codex)},[pscustomobject]@{Name='Claude skill files (lazy)';Bytes=$skills;Tokens=(Tokens $skills)},[pscustomobject]@{Name='Largest lazy rule';Bytes=$lazy;Tokens=(Tokens $lazy)})
|
||||
$fail = $false
|
||||
$baselineValues = @{}
|
||||
if ($old) {
|
||||
|
||||
@@ -8,7 +8,7 @@ generated="$root/generated"
|
||||
bytes() { wc -c < "$1" | tr -d ' '; }
|
||||
tree_bytes() { find "$1" -type f -printf '%s\n' 2>/dev/null | awk '{s+=$1} END {print s+0}'; }
|
||||
tokens() { awk -v b="$1" 'BEGIN { printf "%d", (b + 3) / 4 }'; }
|
||||
claude=$(bytes "$generated/claude-bash/CLAUDE.md"); codex=$(bytes "$generated/codex-bash/AGENTS.md"); skills=$(tree_bytes "$generated/claude-bash/skills"); triggers=$(bytes "$root/adapters/rule-skills.tsv"); lazy=$(find "$root/shared/rules" -type f -printf '%s %p\n' | sort -nr | awk 'NR==1 {print $1+0}'); fail=false
|
||||
claude=$(bytes "$generated/claude-bash/CLAUDE.md"); codex=$(bytes "$generated/codex-bash/AGENTS.md"); skills=$(tree_bytes "$generated/claude-bash/skills"); lazy=$(find "$root/shared/rules" -type f -printf '%s %p\n' | sort -nr | awk 'NR==1 {print $1+0}'); fail=false
|
||||
claude_baseline=0; codex_baseline=0; claude_absolute=0; codex_absolute=0
|
||||
if [ -f "$root/adapters/prompt-budget-baseline.tsv" ]; then
|
||||
while IFS=$'\t' read -r name old absolute; do
|
||||
@@ -20,7 +20,7 @@ if [ -f "$root/adapters/prompt-budget-baseline.tsv" ]; then
|
||||
if awk -v n="$current" -v a="$absolute" 'BEGIN { exit !(a > 0 && n > a) }'; then printf 'Prompt budget absolute limit failed: %s %s bytes (absolute limit %s).\n' "$name" "$current" "$absolute" >&2; fail=true; fi
|
||||
done < "$root/adapters/prompt-budget-baseline.tsv"
|
||||
fi
|
||||
if [ "$summary" = true ]; then printf 'Prompt Budget\nClaude permanent context %8s bytes baseline %s relative 115%% absolute %s\nCodex permanent context %8s bytes baseline %s relative 115%% absolute %s\nClaude skill files (lazy) %8s bytes %6s tokens\nRule trigger metadata %8s bytes %6s tokens\nLargest lazy rule %8s bytes %6s tokens\n' "$claude" "$claude_baseline" "$claude_absolute" "$codex" "$codex_baseline" "$codex_absolute" "$skills" "$(tokens "$skills")" "$triggers" "$(tokens "$triggers")" "$lazy" "$(tokens "$lazy")"; else printf 'Prompt budget: Claude %s bytes, Codex %s bytes, skills %s bytes, triggers %s bytes, largest lazy rule %s bytes\n' "$claude" "$codex" "$skills" "$triggers" "$lazy"; fi
|
||||
if [ "$summary" = true ]; then printf 'Prompt Budget\nClaude permanent context %8s bytes baseline %s relative 115%% absolute %s\nCodex permanent context %8s bytes baseline %s relative 115%% absolute %s\nClaude skill files (lazy) %8s bytes %6s tokens\nLargest lazy rule %8s bytes %6s tokens\n' "$claude" "$claude_baseline" "$claude_absolute" "$codex" "$codex_baseline" "$codex_absolute" "$skills" "$(tokens "$skills")" "$lazy" "$(tokens "$lazy")"; else printf 'Prompt budget: Claude %s bytes, Codex %s bytes, skills %s bytes, largest lazy rule %s bytes\n' "$claude" "$codex" "$skills" "$lazy"; fi
|
||||
python3 "$root/scripts/lib/prompt-inventory.py" --home "${HOME:?HOME is required}"
|
||||
$fail && { printf 'Prompt budget: permanent context increased by more than 15 percent.\n' >&2; exit 1; }
|
||||
exit 0
|
||||
@@ -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/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 = @('AI-Instructions.md','shared/global-instructions.md','adapters/plugins.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('=')
|
||||
|
||||
@@ -4,19 +4,25 @@ $ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||
$sourceAgentCount = @(Get-ChildItem (Join-Path $root 'shared/agents') -Directory).Count
|
||||
$sourceSkillCount = @(Get-ChildItem (Join-Path $root 'shared/skills') -Filter 'SKILL.md' -Recurse).Count
|
||||
$ruleSkillCount = @(Import-Csv -LiteralPath (Join-Path $root 'adapters/rule-skills.tsv') -Delimiter ([char]9)).Count
|
||||
$rulesRoot = Join-Path $root 'shared/rules'
|
||||
$ruleSources = @()
|
||||
foreach ($ruleFile in Get-ChildItem $rulesRoot -File -Filter '*.md' | Where-Object Name -ne 'general.md') {
|
||||
$ruleSources += [pscustomobject]@{ Path = $ruleFile.Name; Skill = "rules-$($ruleFile.BaseName)" }
|
||||
}
|
||||
foreach ($ruleDirectory in Get-ChildItem $rulesRoot -Directory) {
|
||||
if (Test-Path (Join-Path $ruleDirectory.FullName 'index.md')) { $ruleSources += [pscustomobject]@{ Path = "$($ruleDirectory.Name)/index.md"; Skill = "rules-$($ruleDirectory.Name)" } }
|
||||
}
|
||||
$ruleSkillCount = $ruleSources.Count
|
||||
$globalInstructions = Get-Content (Join-Path $root 'shared/global-instructions.md') -Raw
|
||||
foreach ($ruleSource in $ruleSources) {
|
||||
$ruleInstruction = [char]96 + "rules/$($ruleSource.Path)" + [char]96 + ' / ' + [char]96 + $ruleSource.Skill + [char]96
|
||||
if (-not $globalInstructions.Contains($ruleInstruction)) { throw "Global instructions are missing rule loading entry: $($ruleSource.Path)" }
|
||||
}
|
||||
foreach ($shell in @('powershell','bash')) {
|
||||
foreach ($client in @('codex','claude')) {
|
||||
$package = Join-Path $root "generated/$client-$shell"
|
||||
$file = if ($client -eq 'codex') { 'config.toml' } else { 'settings.json' }
|
||||
$content = Get-Content -LiteralPath (Join-Path $package $file) -Raw
|
||||
if ($client -eq 'codex') {
|
||||
foreach ($entry in @(Import-Csv (Join-Path $root 'adapters/rule-skills.tsv') -Delimiter ([char]9))) {
|
||||
$rulePath = $entry.rule_file
|
||||
if (Test-Path (Join-Path $root "shared/rules/$($entry.rule_file)") -PathType Container) { $rulePath = "$($entry.rule_file)/index.md" }
|
||||
if ((Get-Content (Join-Path $package 'AGENTS.md') -Raw) -notmatch [regex]::Escape("- rules/$rulePath for $($entry.trigger).")) { throw "Codex rule loading is missing $($entry.rule_file) in $package" }
|
||||
}
|
||||
}
|
||||
if ($client -eq 'claude') { $settings = $content | ConvertFrom-Json }
|
||||
if (@(Get-ChildItem "$package/agents" -File).Count -ne $sourceAgentCount) { throw "Missing agents in $package" }
|
||||
$expectedReviewerShellTool = if ($shell -eq 'powershell') { 'PowerShell' } else { 'Bash' }
|
||||
@@ -45,6 +51,13 @@ foreach ($shell in @('powershell','bash')) {
|
||||
}
|
||||
$docFile = if ($client -eq 'codex') { 'AGENTS.md' } else { 'CLAUDE.md' }
|
||||
$docContent = Get-Content -LiteralPath (Join-Path $package $docFile) -Raw
|
||||
if ($docContent -notmatch [regex]::Escape('rules/security.md')) { throw "$package is missing common rule loading instructions." }
|
||||
if ($client -eq 'claude') {
|
||||
foreach ($ruleSource in $ruleSources) {
|
||||
$skillPath = Join-Path $package "skills/rules/$($ruleSource.Skill)/SKILL.md"
|
||||
if (-not (Test-Path -LiteralPath $skillPath)) { throw "$package is missing generated skill $($ruleSource.Skill)." }
|
||||
}
|
||||
}
|
||||
if ($docContent -notmatch [regex]::Escape('Use the `mcporter` CLI for MCP services by default.')) { throw "$package must set the MCPorter default" }
|
||||
if ($docContent -notmatch [regex]::Escape('Use a native MCP connection when MCPorter is not active, or when the user explicitly requests it or selects a native MCP plugin in this configuration.')) { throw "$package must allow the selected native MCP route" }
|
||||
if (([regex]::Matches($docContent, [regex]::Escape('Apply instructions in this order'))).Count -ne 1) { throw "$client-$shell must embed the priority rule exactly once" }
|
||||
|
||||
@@ -10,22 +10,23 @@ done
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
|
||||
source_agent_count=$(find "$root/shared/agents" -mindepth 1 -maxdepth 1 -type d | wc -l)
|
||||
source_skill_count=$(find "$root/shared/skills" -name SKILL.md | wc -l)
|
||||
rule_skill_count=$(($(wc -l < "$root/adapters/rule-skills.tsv") - 1))
|
||||
mapfile -t rule_paths < <(find "$root/shared/rules" -mindepth 1 -maxdepth 2 -type f -name '*.md' ! -path '*/references/*' ! -name general.md -printf '%P\n' | sort)
|
||||
rule_skill_count=${#rule_paths[@]}
|
||||
global_instructions=$(<"$root/shared/global-instructions.md")
|
||||
for rule_path in "${rule_paths[@]}"; do
|
||||
case "$rule_path" in
|
||||
*/index.md) skill_name="rules-${rule_path%/index.md}" ;;
|
||||
*.md) skill_name="rules-${rule_path%.md}" ;;
|
||||
esac
|
||||
expected_rule="\`rules/$rule_path\` / \`$skill_name\`"
|
||||
printf '%s' "$global_instructions" | grep -Fq -- "$expected_rule"
|
||||
done
|
||||
for shell in powershell bash; do
|
||||
for client in codex claude; do
|
||||
package="$root/generated/$client-$shell"
|
||||
file=config.toml
|
||||
[ "$client" != claude ] || file=settings.json
|
||||
test -f "$package/$file"
|
||||
if [ "$client" = codex ]; then
|
||||
while IFS=$'\t' read -r rule_file _ trigger; do
|
||||
[ "$rule_file" = rule_file ] && continue
|
||||
[ -n "$rule_file" ] || continue
|
||||
rule_path=$rule_file
|
||||
[ -d "$root/shared/rules/$rule_file" ] && rule_path="$rule_file/index.md"
|
||||
grep -Fq -- "- rules/$rule_path for $trigger." "$package/AGENTS.md"
|
||||
done < "$root/adapters/rule-skills.tsv"
|
||||
fi
|
||||
test "$(find "$package/agents" -type f | wc -l)" -eq "$source_agent_count"
|
||||
expected_reviewer_shell_tool=Bash
|
||||
[ "$shell" != powershell ] || expected_reviewer_shell_tool=PowerShell
|
||||
@@ -61,6 +62,16 @@ for shell in powershell bash; do
|
||||
doc_file=AGENTS.md
|
||||
[ "$client" != claude ] || doc_file=CLAUDE.md
|
||||
doc_content=$(cat "$package/$doc_file")
|
||||
printf '%s' "$doc_content" | grep -Fq 'rules/security.md'
|
||||
if [ "$client" = claude ]; then
|
||||
for rule_path in "${rule_paths[@]}"; do
|
||||
case "$rule_path" in
|
||||
*/index.md) skill_name="rules-${rule_path%/index.md}" ;;
|
||||
*.md) skill_name="rules-${rule_path%.md}" ;;
|
||||
esac
|
||||
test -f "$package/skills/rules/$skill_name/SKILL.md"
|
||||
done
|
||||
fi
|
||||
printf '%s' "$doc_content" | grep -Fq 'Use the `mcporter` CLI for MCP services by default.'
|
||||
printf '%s' "$doc_content" | grep -Fq 'Use a native MCP connection when MCPorter is not active, or when the user explicitly requests it or selects a native MCP plugin in this configuration.'
|
||||
occurrences=$(printf '%s' "$doc_content" | grep -o 'Apply instructions in this order' | wc -l)
|
||||
|
||||
@@ -6,7 +6,31 @@ Before applying generated instructions, read `~/.my-ai-configuration/instruction
|
||||
|
||||
For a tool credential, invoke `~/.my-ai-configuration/bin/with-credential.__SHELL__` with the client, credential key, and child command. It exposes the credential only to that child process. Never print credentials or place them in commands.
|
||||
|
||||
__RULE_LOADING__
|
||||
When programming, always apply the security baseline: `rules/security.md` / `rules-security` (Codex file / Claude skill). This applies to implementing, modifying, debugging, reviewing, testing, and configuring software, scripts, hooks, infrastructure, and integrations.
|
||||
|
||||
Detect languages, frameworks, tools, and change areas from the repository and requested work. Apply every matching rule before editing. For Codex, read the listed rule file. For Claude, invoke the matching `rules-*` skill. Apply all matching rules when multiple areas apply.
|
||||
|
||||
Load or invoke rules for these areas:
|
||||
|
||||
- Angular component, template, or RxJS/signal work: `rules/angular/index.md` / `rules-angular`.
|
||||
- C# or .NET implementation, review, or refactoring: `rules/csharp.md` / `rules-csharp`.
|
||||
- Unresolved requirements, ambiguous behavior, or meaningful technical choices: `rules/decision-rule.md` / `rules-decision-rule`.
|
||||
- Validating completion or review readiness: `rules/definition-of-done.md` / `rules-definition-of-done`.
|
||||
- Git operations such as commits, branches, or merges: `rules/git.md` / `rules-git`.
|
||||
- Microsoft 365, Azure DevOps, or Teams operations: `rules/microsoft.md` / `rules-microsoft`.
|
||||
- Delegation, subagents, or long-running task state: `rules/orchestration.md` / `rules-orchestration`.
|
||||
- Refactoring without behavior changes: `rules/refactoring.md` / `rules-refactoring`.
|
||||
- Authentication, authorization, sessions, tokens, permissions, or tenant boundaries: `rules/security-auth.md` / `rules-security-auth`.
|
||||
- HTTP APIs, request handling, serialization, or endpoints: `rules/security-api.md` / `rules-security-api`.
|
||||
- Browser, frontend, cookies, redirects, XSS, or CSRF: `rules/security-web.md` / `rules-security-web`.
|
||||
- Databases, queries, persistence, sensitive data, or tenant boundaries: `rules/security-data.md` / `rules-security-data`.
|
||||
- Files, uploads, archives, paths, processes, IPC, or deserialization: `rules/security-files.md` / `rules-security-files`.
|
||||
- Network access, URLs, proxies, outbound requests, TLS, or SSRF: `rules/security-network.md` / `rules-security-network`.
|
||||
- Cryptography, secrets, credentials, keys, or tokens: `rules/security-crypto.md` / `rules-security-crypto`.
|
||||
- Dependencies, packages, plugins, builds, deployments, or CI supply chains: `rules/security-supply-chain.md` / `rules-security-supply-chain`.
|
||||
- TypeScript or modern ECMAScript implementation: `rules/typescript.md` / `rules-typescript`.
|
||||
- UI or UX design and interaction: `rules/ui-ux/index.md` / `rules-ui-ux`.
|
||||
- WPF, XAML, or MVVM desktop UI: `rules/wpf/index.md` / `rules-wpf`.
|
||||
|
||||
Use the `mcporter` CLI for MCP services by default. Use a native MCP connection when MCPorter is not active, or when the user explicitly requests it or selects a native MCP plugin in this configuration. If the requested route is unavailable, report the limitation instead of silently switching routes.
|
||||
|
||||
|
||||
Reference in new issue
Block a user