Compare commits

..
14 Commits
Author SHA1 Message Date
Julian lechnerandClaude Opus 5.5 37d3475b92 docs(scripts): list client smoke test
CI / CI configuration security checks (push) Successful in 5s
CI / Capabilities and shell quality (push) Successful in 14s
CI / Python tests and compilation (push) Successful in 18s
CI / Build and inspect generated packages (push) Successful in 11s
CI / PowerShell tests on Ubuntu (push) Successful in 50s
CI / Installed client configuration smoke tests (push) Successful in 48s
CI / Bash integration and installation guards (push) Successful in 58s
CI / Qwen retrieval integration (push) Successful in 14m55s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:23:24 +02:00
Julian lechnerandClaude Opus 5.5 cd2f4fa93e test(clients): add behavioral client smoke tests
Install a generated package into a temporary home with foreign settings, then
ask the real Claude and Codex CLIs which skills, agents, and hooks they
discover. No model request completes. The Claude skill check currently fails
because Claude does not discover nested package skills.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:23:23 +02:00
Julian lechnerandClaude Opus 5.5 e2394ca1d1 fix(install): keep foreign Claude hooks in mixed groups
Remove only setup-owned hook entries from a hook group and keep foreign
entries with their group options, such as matcher.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:23:23 +02:00
Julian lechnerandClaude Opus 5.5 857720bcfd fix(install): exchange install option content through UTF-8 files
Windows PowerShell pipes re-encode text by console code page: under code page
65001 a BOM broke the config.toml merge, and otherwise non-ASCII characters
became question marks. Pass managed and merged content through files, and
decode piped input as utf-8-sig.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:23:14 +02:00
Julian lechnerandClaude Opus 5.5 bed90fee2d docs(rules): align technology comment rules
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:22:50 +02:00
Julian lechnerandClaude Opus 5.5 996233c663 docs(rules): unify documentation update rule
Update directly affected documentation and comments; add other documentation
only for a concrete need. The implementer and definition of done now defer to
the general rule instead of requiring explicit approval.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:22:50 +02:00
Julian lechnerandClaude Opus 5.5 f63770efbf docs(config): describe sandbox network and Context7 permissions
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:22:42 +02:00
Julian lechnerandClaude Opus 5.5 3067d82e1a feat(claude): allow Context7 domain in enabled sandbox
Where the Claude sandbox is enabled, permit network access to
mcp.context7.com only, and validate that exact shape.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:22:42 +02:00
Julian lechnerandClaude Opus 5.5 d5c29cb1b6 feat(claude): allow Context7 calls through MCPorter
Add a shell-specific permission for `mcporter call context7.*` so Claude can
query Context7 through MCPorter without a permission prompt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:22:35 +02:00
Julian lechnerandClaude Opus 5.5 f76e13fc18 feat(codex): allow network access in workspace sandbox
Enable sandbox_workspace_write.network_access so MCPorter calls such as
Context7 run without an approval round trip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:22:07 +02:00
Julian lechnerandClaude Opus 5.5 736b1d1d9e docs(skills): note Superpowers ownership of general workflows
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:22:07 +02:00
Julian lechnerandClaude Opus 5.5 e237d07441 refactor(skills): remove workflows covered by Superpowers
Drop the debugging, implementation-planner, code-review, and
regression-verification skills so Superpowers owns these triggers without
competing descriptions. Focused domain skills remain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:21:59 +02:00
Julian lechnerandClaude Opus 5.5 b4aa484f1a docs(config): document unmanaged TOML table retention
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:21:49 +02:00
Julian lechnerandClaude Opus 5.5 1a6cc4d46f fix(config): preserve unmanaged Codex TOML tables
Keep foreign tables such as mcp_servers and model_providers during config
synchronization instead of dropping them; only setup-owned keys are replaced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:21:41 +02:00
28 changed files with 293 additions and 130 deletions

No files matched your search

+1 -1
View File
@@ -218,7 +218,7 @@ backed up and retained with a warning. Backups are stored under `backups/<timest
Codex skill backups use `.agents/.ai-config-skill-backups/` instead, so Codex does not discover backup copies as skills.
Existing `.agents/skills/backups/` content is moved there on the next install, update, or uninstall.
Codex `config.toml` synchronization merges setup-owned keys while retaining other root keys, extra fields in managed
tables, plugin and marketplace tables, and unowned array tables. Other standalone tables may be dropped; the previous
tables, plugin and marketplace tables, other unmanaged tables, and unowned array tables. The previous
file is backed up when content changes. Claude `settings.json` synchronization retains unrelated settings and hooks.
Dry runs still rebuild `generated/`, but do not modify installation destinations.
+1 -3
View File
@@ -1,9 +1,7 @@
{
"permissions": {
"defaultMode": "auto",
"allow": [
"Agent"
],
"allow": __CLAUDE_ALLOW_RULES__,
"deny": __CLAUDE_DENY_RULES__
},
"sandbox": __CLAUDE_SANDBOX__,
+3
View File
@@ -2,6 +2,9 @@ approval_policy = "on-request"
approvals_reviewer = "auto_review"
sandbox_mode = "workspace-write"
[sandbox_workspace_write]
network_access = true
[windows]
sandbox = "unelevated"
+2 -2
View File
@@ -1,6 +1,6 @@
# Configuration
Codex receives a generated global `AGENTS.md`, agent TOML files, skills, rule files, and a `config.toml` adapter. It sets 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 a five-operation concurrency default. Installation resolves user paths at runtime; the repository contains no machine-specific paths.
Codex receives a generated global `AGENTS.md`, agent TOML files, skills, rule files, and a `config.toml` adapter. It sets workspace sandboxing with network access for shell commands (so MCPorter calls such as Context7 work without approval), 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` and `mcporter call context7.*` shell permissions, `mcp.context7.com` as the only allowed sandbox domain where the Claude sandbox is enabled, and a five-operation concurrency default. Installation resolves user paths at runtime; the repository contains no machine-specific paths.
Both clients embed `shared/rules/general.md` and the rule-loading conditions from `shared/global-instructions.md`. Rules for languages and frameworks live in their own directories, with an `index.md` entry point and focused Markdown rules under `references/`. Security has its own directory with a baseline entry point and focused rules for authentication, web, APIs, data, files, network, cryptography, and supply chains. Git, Microsoft, and cross-cutting rules remain directly under `shared/rules/`. Codex receives the full directory tree; Claude generates a skill for each rule and copies supporting references alongside the index skill.
@@ -37,4 +37,4 @@ The agent chooses the smallest sufficient checks based on behavior, affected cal
Delegation and long-running state use the shared orchestration rule, loaded on demand as a Codex rule or Claude skill. The coordinator owns decisions and routes concise evidence-based handoffs. Review and execution verification have separate responsibilities; see [Agents](agents.md).
Rebuild packages after changes to shared definitions or adapters. Generated defaults reach a user's configuration only through installation or update. Updates merge Codex's setup-owned root keys and table fields while retaining other root keys, extra fields in managed tables, plugin and marketplace tables, and unowned array tables. Other standalone tables may be dropped. Claude settings retain unrelated keys and hooks. See [the README](../README.md) for overwrite and backup behavior.
Rebuild packages after changes to shared definitions or adapters. Generated defaults reach a user's configuration only through installation or update. Updates merge Codex's setup-owned root keys and table fields while retaining other root keys, extra fields in managed tables, plugin and marketplace tables, other unmanaged tables such as `mcp_servers` and `model_providers`, and unowned array tables. Claude settings retain unrelated keys and hooks. See [the README](../README.md) for overwrite and backup behavior.
+1 -1
View File
@@ -10,7 +10,7 @@ Selecting Context7 with MCPorter registers the server in MCPorter. Selecting Con
`update.ps1`/`update.sh` lets you revise the saved extension selection. Checking an extension installs it if missing or updates it if owned; existing foreign installations are preserved. Deselection removes only extensions that this setup installed and recorded as owned. Pre-existing or manually installed plugins and skills are retained. Extensions removed from the manifest are also removed when recorded as owned; unrelated installations are retained. Update refuses to run if any selected destination lacks an installation manifest (`Not installed, use the install script.`).
Codex's local `plugins` and `marketplaces` tables survive configuration synchronization, along with other root keys, extra fields in managed tables, and unowned array tables. Other standalone tables may be dropped; changed files are backed up. Cached plugin files alone are not treated as proof of installation: selection uses the client CLI's installed list. If an older update removed the registrations, inspect the backed-up `config.toml` under `~/.codex/backups/` and reselect the affected plugins on the next update.
Codex's local `plugins` and `marketplaces` tables survive configuration synchronization, along with other root keys, extra fields in managed tables, other unmanaged tables, and unowned array tables; changed files are backed up. Cached plugin files alone are not treated as proof of installation: selection uses the client CLI's installed list. If an older update removed the registrations, inspect the backed-up `config.toml` under `~/.codex/backups/` and reselect the affected plugins on the next update.
Codex receives Caveman through its native plugin adapter. Humanizer, Impeccable, and Anthropic Frontend Design are downloaded as skill payloads from the GitHub sources in the manifest, without running upstream installers. All three appear in the extension selector for Codex. Ownership is recorded in `~/.my-ai-configuration/extensions.json`. Skill files are tracked by hash; modified or foreign files are retained with a warning. Existing installations from before ownership tracking are not automatically adopted.
+1
View File
@@ -50,6 +50,7 @@ Gitea Actions runs the repository checks from `.gitea/workflows/ci.yml`.
| `tests/test-build-validation.ps1`, `tests/test-build-validation.sh` | Validate build failures and generated output. |
| `tests/test-capabilities.ps1` | Guard PowerShell capability detection. |
| `tests/test-install-guards.ps1`, `tests/test-install-guards.sh` | Validate install and update guard conditions. |
| `tests/test-client-smoke.py` | Install a package into a temporary home with foreign settings and ask the installed Claude and Codex CLIs which skills, agents, and hooks they discover, without completing a model request. Skips a client whose CLI is unavailable. |
| `tests/test-install-options.py` | Validate interactive option handling and generated manifest changes. |
| `tests/test-managed-extensions.py` | Validate plugin, skill, and CLI ownership handling. |
| `tests/test-managed-install.ps1`, `tests/test-managed-install.sh` | Validate managed-file synchronization and backups. |
+5 -4
View File
@@ -4,17 +4,18 @@ Skills are reusable workflows. Rules are persistent constraints and agents are s
The current structure is organized by purpose:
- `debugging/`
- `planning/`: implementation planning, impact analysis, and work-item definition
- `planning/`: impact analysis and work-item definition
- `research/`: technical and repository research
- `reviews/`: code, security, performance, WPF, UI/UX, business, API, and EF Core reviews
- `reviews/`: security, performance, WPF, UI/UX, business, API, and EF Core reviews
- `implementation/`: SQL, unit-test, and integration-test writing
- `verification/`: facts, work-item, and regression verification
- `verification/`: facts and work-item verification
- `git/`: pull request reviews
- `content-strategy/`, `copy-editing/`, `copywriting/`: planning, editing, and writing text
- `image/`, `site-architecture/`: visuals and website structure
- `marketing-psychology/`, `offers/`, `product-marketing/`, `cro/`: audience understanding, value framing, and conversion
General planning, debugging, code review, and completion verification workflows come from Superpowers, so the catalog does not duplicate their triggers. The remaining skills cover focused domains Superpowers does not.
Each skill defines a focused workflow with the investigation, decision points, and output expected for that task. Keep guidance specific to the skill; shared repository constraints belong in the rules.
The single catalog in `shared/skills/` includes development skills and focused skills for writing, images, content and site structure, positioning, psychology, and conversion. Builds include every retained skill for both clients. Updating a managed installation backs up and removes unchanged setup-owned skills deleted from the catalog; local changes are backed up and retained.
+3 -2
View File
@@ -11,7 +11,7 @@ from pathlib import Path
CLAUDE_TOP_LEVEL = {"permissions", "sandbox", "env", "statusLine", "hooks"}
CLAUDE_PERMISSION_KEYS = {"defaultMode", "allow", "deny"}
CLAUDE_SANDBOX_KEYS = {"enabled", "allowUnsandboxedCommands", "failIfUnavailable"}
CLAUDE_SANDBOX_KEYS = {"enabled", "allowUnsandboxedCommands", "failIfUnavailable", "network"}
CLAUDE_STATUS_LINE_KEYS = {"type", "command"}
CLAUDE_HOOK_KEYS = {"type", "command", "timeout"}
CLAUDE_PERMISSION_MODES = {"acceptEdits", "auto", "bypassPermissions", "default", "dontAsk", "plan"}
@@ -40,7 +40,8 @@ def validate_claude(path: Path) -> None:
if not isinstance(permissions.get(key), list) or not all(isinstance(item, str) for item in permissions[key]):
fail(f"permissions.{key} must be an array of strings")
sandbox = require_keys(settings.get("sandbox"), CLAUDE_SANDBOX_KEYS, "sandbox")
if sandbox != {"enabled": False} and sandbox != {"enabled": True, "allowUnsandboxedCommands": False, "failIfUnavailable": True}:
if sandbox != {"enabled": False} and sandbox != {"enabled": True, "allowUnsandboxedCommands": False, "failIfUnavailable": True,
"network": {"allowedDomains": ["mcp.context7.com"]}}:
fail("sandbox must be disabled or enabled with fail-closed settings")
env = settings.get("env")
if not isinstance(env, dict) or not all(isinstance(key, str) and isinstance(value, str) for key, value in env.items()):
+4 -2
View File
@@ -9,7 +9,7 @@ if ($sandboxSupported) {
$kernel = (& uname -r 2>$null | Out-String).Trim()
if ($kernel -match '(?i)microsoft') { $sandboxSupported = $kernel -match '(?i)microsoft-standard-WSL2' }
}
$claudeSandbox = if ($sandboxSupported) { '{"enabled": true, "allowUnsandboxedCommands": false, "failIfUnavailable": true}' } else { '{"enabled": false}' }
$claudeSandbox = if ($sandboxSupported) { '{"enabled": true, "allowUnsandboxedCommands": false, "failIfUnavailable": true, "network": {"allowedDomains": ["mcp.context7.com"]}}' } else { '{"enabled": false}' }
$pluginManifest = Join-Path $root 'adapters/plugins.tsv'
$capabilityManifest = Join-Path $root 'adapters/claude/capabilities.tsv'
@@ -185,9 +185,11 @@ foreach ($shell in @('powershell','bash')) {
@('Bash(git reset --hard*)','Bash(git clean*)','Bash(rm -rf*)')
}
$denyRulesJson = ConvertTo-Json -InputObject $denyRules -Compress
$shellTool = if ($shell -eq 'powershell') { 'PowerShell' } else { 'Bash' }
$allowRulesJson = ConvertTo-Json -InputObject @('Agent', "$shellTool(mcporter call context7.*)") -Compress
$compactScript = if ($shell -eq 'powershell') { 'Record-Compact.ps1' } else { 'record-compact.sh' }
$pointerScript = if ($shell -eq 'powershell') { 'Show-SessionStatePointer.ps1' } else { 'show-session-state-pointer.sh' }
$content = $content.Replace('__HOOK_COMMAND__', $command).Replace('__POWERSHELL_HOOK_COMMAND__', $command).Replace('__HOOK_SCRIPT__', $script).Replace('__POWERSHELL_HOOK_SCRIPT__', $script).Replace('__COMPACT_SCRIPT__', $compactScript).Replace('__SESSION_POINTER_SCRIPT__', $pointerScript).Replace('__STATUSLINE_COMMAND__', $command).Replace('__STATUSLINE_SCRIPT__', $statusLineScript).Replace('__CLAUDE_SANDBOX__', $claudeSandbox).Replace('__CLAUDE_DENY_RULES__', $denyRulesJson)
$content = $content.Replace('__HOOK_COMMAND__', $command).Replace('__POWERSHELL_HOOK_COMMAND__', $command).Replace('__HOOK_SCRIPT__', $script).Replace('__POWERSHELL_HOOK_SCRIPT__', $script).Replace('__COMPACT_SCRIPT__', $compactScript).Replace('__SESSION_POINTER_SCRIPT__', $pointerScript).Replace('__STATUSLINE_COMMAND__', $command).Replace('__STATUSLINE_SCRIPT__', $statusLineScript).Replace('__CLAUDE_SANDBOX__', $claudeSandbox).Replace('__CLAUDE_DENY_RULES__', $denyRulesJson).Replace('__CLAUDE_ALLOW_RULES__', $allowRulesJson)
Set-Content -LiteralPath $path -Value $content -Encoding UTF8
}
+4 -1
View File
@@ -24,7 +24,7 @@ case "$platform" in
;;
esac
sandbox='{"enabled": false}'
[ "$sandbox_supported" != true ] || sandbox='{"enabled": true, "allowUnsandboxedCommands": false, "failIfUnavailable": true}'
[ "$sandbox_supported" != true ] || sandbox='{"enabled": true, "allowUnsandboxedCommands": false, "failIfUnavailable": true, "network": {"allowedDomains": ["mcp.context7.com"]}}'
plugin_manifest="$root/adapters/plugins.tsv"
capability_manifest="$root/adapters/claude/capabilities.tsv"
@@ -174,8 +174,10 @@ for client in codex claude; do
content=$(<"$path")
if [ "$shell" = powershell ]; then
deny_rules='["PowerShell(git reset --hard*)","PowerShell(git clean*)","PowerShell(Remove-Item *-Recurse*)"]'
allow_rules='["Agent","PowerShell(mcporter call context7.*)"]'
else
deny_rules='["Bash(git reset --hard*)","Bash(git clean*)","Bash(rm -rf*)"]'
allow_rules='["Agent","Bash(mcporter call context7.*)"]'
fi
content=${content//__HOOK_COMMAND__/$command}
content=${content//__POWERSHELL_HOOK_COMMAND__/$command}
@@ -187,6 +189,7 @@ for client in codex claude; do
content=${content//__STATUSLINE_SCRIPT__/$statusline_script}
content=${content//__CLAUDE_SANDBOX__/$sandbox}
content=${content//__CLAUDE_DENY_RULES__/$deny_rules}
content=${content//__CLAUDE_ALLOW_RULES__/$allow_rules}
printf '%s\n' "$content" > "$path"
done
+24 -15
View File
@@ -73,18 +73,23 @@ def merge_json(current, managed):
groups = value.get(event, [])
old_groups = hooks.get(event, [])
if isinstance(groups, list) and isinstance(old_groups, list):
retained = [group for group in old_groups if not any(
re.search(r'(?:flashbang|statusline|record-compact|session-state-pointer)\.(?:ps1|sh)',
str(hook.get('command', '')) + str(hook.get('command_windows', '')), re.I)
for hook in group.get('hooks', []) if isinstance(hook, dict)
)]
retained = []
for group in old_groups:
if not isinstance(group, dict) or not isinstance(group.get('hooks'), list):
retained.append(group)
continue
foreign = [hook for hook in group['hooks'] if not (isinstance(hook, dict) and re.search(
r'(?:flashbang|statusline|record-compact|session-state-pointer)\.(?:ps1|sh)',
str(hook.get('command', '')) + str(hook.get('command_windows', '')), re.I))]
if foreign:
retained.append(dict(group, hooks=foreign))
merged = groups + [group for group in retained if group not in groups]
if merged:
hooks[event] = merged
else:
hooks.pop(event, None)
else:
hooks[event] = value
hooks[event] = groups
result[key] = hooks
elif isinstance(value, dict) and isinstance(result.get(key), dict):
result[key] = merge_json(result[key], value)
@@ -129,11 +134,9 @@ def merge_toml(current_text, managed_text, statusline_enabled=True):
owned_hook = any(re.search(r'(?:flashbang|statusline|record-compact|session-state-pointer)\.(?:ps1|sh)', line, re.I) for line in block)
if plugin_table and header in managed_headers:
raise ValueError('Cannot merge conflicting managed plugin tables.')
if (plugin_table and header not in managed_headers) or (array_table and not owned_hook):
if (header not in managed_headers and not array_table) or (array_table and not owned_hook):
extras.extend(block)
continue
if header not in managed_headers:
continue
if array_table:
continue
generated_block = next(lines for name, lines in managed_sections if name == header)
@@ -175,18 +178,24 @@ if __name__ == '__main__':
parser.add_argument('--current', type=Path)
parser.add_argument('--flashbang', choices=('true', 'false'))
parser.add_argument('--statusline', choices=('true', 'false'), default='true')
parser.add_argument('--managed', type=Path)
parser.add_argument('--output', type=Path)
args = parser.parse_args()
try:
sys.stdout.reconfigure(encoding='utf-8')
if args.action == 'filter':
print(filter_options(args.path, args.flashbang == 'true', args.statusline == 'true'), end='')
result = filter_options(args.path, args.flashbang == 'true', args.statusline == 'true')
else:
managed = args.managed.read_text(encoding='utf-8-sig') if args.managed else sys.stdin.buffer.read().decode('utf-8-sig')
current = args.current.read_text(encoding='utf-8-sig')
if args.action == 'merge-json':
current = json.loads(args.current.read_text(encoding='utf-8-sig'))
managed = json.load(sys.stdin)
print(json.dumps(merge_json(current, managed), indent=2, ensure_ascii=False) + '\n', end='')
result = json.dumps(merge_json(json.loads(current), json.loads(managed)), indent=2, ensure_ascii=False) + '\n'
else:
print(merge_toml(args.current.read_text(encoding='utf-8-sig'), sys.stdin.read(), args.statusline == 'true'), end='')
result = merge_toml(current, managed, args.statusline == 'true')
if args.output:
args.output.write_text(result, encoding='utf-8', newline='')
else:
sys.stdout.reconfigure(encoding='utf-8')
print(result, end='')
except (OSError, ValueError, KeyError) as error:
print(f'Install options: {error}', file=sys.stderr)
sys.exit(1)
+23 -6
View File
@@ -86,6 +86,26 @@ function Write-ManagedManifest {
[System.IO.File]::WriteAllText($ManifestPath, $content, (New-Object System.Text.UTF8Encoding($false)))
}
function Invoke-InstallOptions {
param([Parameter(Mandatory=$true)][string[]]$Arguments, [byte[]]$Managed, [Parameter(Mandatory=$true)][string]$ErrorMessage)
# Exchange content through UTF-8 files: Windows PowerShell pipes re-encode text by console code page.
$outputPath = [System.IO.Path]::GetTempFileName()
$managedPath = $null
try {
if ($null -ne $Managed) {
$managedPath = [System.IO.Path]::GetTempFileName()
[System.IO.File]::WriteAllBytes($managedPath, $Managed)
$Arguments += @('--managed', $managedPath)
}
& python (Join-Path $PSScriptRoot 'install-options.py') @Arguments --output $outputPath
if ($LASTEXITCODE -ne 0) { throw $ErrorMessage }
return [System.IO.File]::ReadAllText($outputPath, (New-Object System.Text.UTF8Encoding($false)))
} finally {
Remove-Item -LiteralPath $outputPath -Force -ErrorAction SilentlyContinue
if ($managedPath) { Remove-Item -LiteralPath $managedPath -Force -ErrorAction SilentlyContinue }
}
}
function Sync-ManagedDestination {
<#
.SYNOPSIS
@@ -135,8 +155,7 @@ function Sync-ManagedDestination {
if ($filterOptions) {
$flashbangOption = if ($FlashbangEnabled) { 'true' } else { 'false' }
$statusLineOption = if ($StatusLineEnabled) { 'true' } else { 'false' }
$rawContent = (& python (Join-Path $PSScriptRoot 'install-options.py') filter --path $_.FullName --flashbang $flashbangOption --statusline $statusLineOption | Out-String)
if ($LASTEXITCODE -ne 0) { throw 'Could not configure install options.' }
$rawContent = Invoke-InstallOptions -Arguments @('filter', '--path', $_.FullName, '--flashbang', $flashbangOption, '--statusline', $statusLineOption) -ErrorMessage 'Could not configure install options.'
}
$needsSubstitution = $filterOptions -or $rawContent.Contains('__AI_CONFIG_ROOT__') -or $rawContent.Contains('__HOOK_COMMAND__') -or $rawContent.Contains('__POWERSHELL_HOOK_COMMAND__') -or $rawContent.Contains('__POWERSHELL_COMMAND__') -or $rawContent.Contains('__CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY__')
if ($needsSubstitution) {
@@ -147,13 +166,11 @@ function Sync-ManagedDestination {
}
if ($relative -eq 'config.toml' -and (Test-Path -LiteralPath $target)) {
$statusLineFlag = if ($StatusLineEnabled) { 'true' } else { 'false' }
$content = [System.Text.Encoding]::UTF8.GetString($newBytes) | & python (Join-Path $PSScriptRoot 'install-options.py') merge-toml --current $target --statusline $statusLineFlag | Out-String
if ($LASTEXITCODE -ne 0) { throw 'Could not merge managed config.toml keys.' }
$content = Invoke-InstallOptions -Arguments @('merge-toml', '--current', $target, '--statusline', $statusLineFlag) -Managed $newBytes -ErrorMessage 'Could not merge managed config.toml keys.'
$newBytes = [System.Text.Encoding]::UTF8.GetBytes($content)
}
if ($relative -eq 'settings.json' -and (Test-Path -LiteralPath $target)) {
$finalContent = [System.Text.Encoding]::UTF8.GetString($newBytes) | & python (Join-Path $PSScriptRoot 'install-options.py') merge-json --current $target | Out-String
if ($LASTEXITCODE -ne 0) { throw 'Could not merge managed settings.json keys.' }
$finalContent = Invoke-InstallOptions -Arguments @('merge-json', '--current', $target) -Managed $newBytes -ErrorMessage 'Could not merge managed settings.json keys.'
$newBytes = [System.Text.Encoding]::UTF8.GetBytes($finalContent)
}
$newHash = Get-Sha256HashOfBytes $newBytes
+165
View File
@@ -0,0 +1,165 @@
"""Behavioral client smoke tests against an isolated installation.
Each test installs a generated package into a temporary home that already holds
foreign settings, then asks the real client CLI what it discovered. Claude runs
with an invalid API key and is stopped at its init event; Codex is queried
through `codex app-server`. No model request completes.
"""
import json
import os
from pathlib import Path
import re
import shutil
import subprocess
import sys
import tempfile
import unittest
ROOT = Path(__file__).resolve().parent.parent.parent
SHELL = 'powershell' if os.name == 'nt' else 'bash'
MARKER_SCRIPT = "import sys; open(sys.argv[1], 'w').write('ok')"
def frontmatter_names(directory, pattern):
names = set()
for path in directory.rglob(pattern):
match = re.search(r'(?m)^name\s*[:=]\s*"?([^"\r\n]+)"?\s*$', path.read_text(encoding='utf-8-sig'))
if match:
names.add(match.group(1).strip())
return names
def marker_command(marker):
return f'"{sys.executable}" -c "{MARKER_SCRIPT}" "{marker}"'
def clean_env(**values):
env = {key: value for key, value in os.environ.items()
if not key.startswith(('BUN_INSPECT', 'CLAUDE', 'ANTHROPIC', 'CODEX', 'OPENAI'))}
env.update(values)
return env
def install(client, home):
if SHELL == 'powershell':
script = ('$ErrorActionPreference = "Stop"; '
f'. "{ROOT}/scripts/lib/manifest.ps1"; . "{ROOT}/scripts/lib/install-targets.ps1"; '
'$command = "powershell -NoProfile -ExecutionPolicy Bypass -File"; '
f'foreach ($item in (Get-InstallTargets -Generated "{ROOT}/generated" -HomePath "{home}" -Shell PowerShell -Client {client})) {{ '
'Sync-ManagedDestination -Source $item.Source -Destination $item.Destination -Stamp smoke '
'-AiConfigRoot $item.Destination.Replace("\\","/") -ShellCommand $command -PowerShellCommand $command -Summary | Out-Null }')
args = ['powershell', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', script]
else:
script = (f'set -e; . "{ROOT}/scripts/lib/manifest.sh"; . "{ROOT}/scripts/lib/install-targets.sh"; '
f'get_install_targets "{ROOT}/generated" "{home}" bash {client.lower()} | '
'while IFS="|" read -r source destination; do '
'sync_managed_destination "$source" "$destination" smoke "$destination" bash "pwsh -NoProfile -File" false true >/dev/null; done')
args = ['bash', '-c', script]
result = subprocess.run(args, capture_output=True, text=True)
if result.returncode:
raise RuntimeError(f"install failed: {result.stdout}{result.stderr}")
class ClientSmokeTests(unittest.TestCase):
def setUp(self):
self.base = Path(tempfile.mkdtemp(prefix='ai-config-client-smoke-'))
self.home = self.base / 'home'
self.work = self.base / 'work'
self.work.mkdir(parents=True)
subprocess.run(['git', 'init', '-q'], cwd=self.work, check=True)
self.marker = self.base / 'foreign-hook.txt'
def tearDown(self):
shutil.rmtree(self.base, ignore_errors=True)
@unittest.skipUnless(shutil.which('claude'), 'Claude CLI unavailable')
def test_claude_discovers_package_and_keeps_foreign_settings(self):
package = ROOT / f'generated/claude-{SHELL}'
config = self.home / '.claude'
config.mkdir(parents=True)
foreign_hook = {'hooks': [{'type': 'command', 'command': marker_command(self.marker), 'timeout': 10}]}
(config / 'settings.json').write_text(json.dumps({'foreignSetting': 'keep', 'hooks': {'SessionStart': [foreign_hook]}}))
install('Claude', self.home)
process = subprocess.Popen([shutil.which('claude'), '-p', 'smoke', '--output-format', 'stream-json', '--verbose', '--max-turns', '1'],
cwd=self.work, env=clean_env(CLAUDE_CONFIG_DIR=str(config), ANTHROPIC_API_KEY='sk-ant-invalid-smoke'),
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, encoding='utf-8')
events = []
try:
for line in process.stdout:
events.append(json.loads(line))
if events[-1].get('subtype') == 'init':
break
finally:
process.kill()
for stream in (process.stdin, process.stdout):
if stream:
stream.close()
process.wait(timeout=30)
init = next((event for event in events if event.get('subtype') == 'init'), None)
self.assertIsNotNone(init, 'Claude emitted no init event')
with self.subTest('skills'):
self.assertEqual(sorted(frontmatter_names(package / 'skills', 'SKILL.md') - set(init['skills'])), [])
with self.subTest('agents'):
self.assertEqual(sorted(frontmatter_names(package / 'agents', '*.md') - set(init['agents'])), [])
with self.subTest('hooks'):
failed = [(event.get('hook_name'), event.get('exit_code'), event.get('stderr', '')[:200])
for event in events if event.get('subtype') == 'hook_response' and event.get('exit_code') != 0]
self.assertEqual(failed, [])
self.assertTrue(self.marker.exists(), 'foreign SessionStart hook did not run')
with self.subTest('foreign settings'):
self.assertEqual(json.loads((config / 'settings.json').read_text(encoding='utf-8-sig')).get('foreignSetting'), 'keep')
@unittest.skipUnless(shutil.which('codex'), 'Codex CLI unavailable')
def test_codex_discovers_package_and_keeps_foreign_settings(self):
package = ROOT / f'generated/codex-{SHELL}'
codex_home = self.home / '.codex'
codex_home.mkdir(parents=True)
command = json.dumps(marker_command(self.marker))
(codex_home / 'config.toml').write_text(
'[mcp_servers.smoke_demo]\ncommand = "smoke-demo"\nenabled = false\n\n'
f'[[hooks.SessionStart]]\n[[hooks.SessionStart.hooks]]\ntype = "command"\ncommand = {command}\ntimeout = 10\n')
install('Codex', self.home)
process = subprocess.Popen([shutil.which('codex'), 'app-server'], cwd=self.work,
env=clean_env(CODEX_HOME=str(codex_home), OPENAI_API_KEY='sk-invalid-smoke'),
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, encoding='utf-8')
def call(request_id, method, params):
process.stdin.write(json.dumps({'jsonrpc': '2.0', 'id': request_id, 'method': method, 'params': params}) + '\n')
process.stdin.flush()
while True:
message = json.loads(process.stdout.readline())
if message.get('id') == request_id:
self.assertNotIn('error', message, method)
return message['result']
try:
call(1, 'initialize', {'clientInfo': {'name': 'ai-config-smoke', 'version': '1'}})
process.stdin.write(json.dumps({'jsonrpc': '2.0', 'method': 'initialized'}) + '\n')
# Codex resolves ~/.agents/skills from the real Windows profile, so register the isolated root explicitly.
skills_root = self.home / '.agents/skills'
call(2, 'skills/extraRoots/set', {'extraRoots': [str(skills_root)]})
skills = call(3, 'skills/list', {'cwds': [str(self.work)], 'forceReload': True})['data'][0]['skills']
hooks = call(4, 'hooks/list', {'cwds': [str(self.work)]})['data'][0]['hooks']
config = call(5, 'config/read', {})['config']
finally:
process.kill()
for stream in (process.stdin, process.stdout):
if stream:
stream.close()
process.wait(timeout=30)
isolated = {skill['name'] for skill in skills if Path(skill['path']).resolve().is_relative_to(skills_root.resolve())}
self.assertEqual(sorted(frontmatter_names(package / 'skills', 'SKILL.md') - isolated), [], 'skills not discovered')
events = {(hook['eventName'], hook['enabled']) for hook in hooks if hook.get('source') == 'user'}
self.assertIn(('stop', True), events, 'managed Stop hook not registered')
self.assertIn(('sessionStart', True), events, 'foreign SessionStart hook lost')
self.assertEqual(config['mcp_servers']['smoke_demo']['command'], 'smoke-demo')
self.assertTrue((codex_home / 'agents').is_dir() and frontmatter_names(codex_home / 'agents', '*.toml') == frontmatter_names(package / 'agents', '*.toml'))
if __name__ == '__main__':
unittest.main()
+42
View File
@@ -4,6 +4,7 @@ import os
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
import tomllib
import unittest
@@ -31,6 +32,47 @@ class InstallOptionsTests(unittest.TestCase):
self.assertIn(plugin_state, result)
def test_merge_toml_preserves_unmanaged_tables(self):
foreign = ('[mcp_servers.demo]\ncommand = "demo"\n\n[mcp_servers.demo.env]\nTOKEN_NAME = "x"\n\n'
'[model_providers.custom]\nname = "Custom"\nbase_url = "https://example.invalid"\n')
current = "model = 'old'\n\n[tui]\nstatus_line = ['a']\ntheme = 'dark'\n\n" + foreign
managed = "model = 'new'\n\n[tui]\nstatus_line = ['b']\n"
result = tomllib.loads(OPTIONS.merge_toml(current, managed))
self.assertEqual(result['model'], 'new')
self.assertEqual(result['tui'], {'status_line': ['b'], 'theme': 'dark'})
self.assertEqual(result['mcp_servers'], {'demo': {'command': 'demo', 'env': {'TOKEN_NAME': 'x'}}})
self.assertEqual(result['model_providers'], {'custom': {'name': 'Custom', 'base_url': 'https://example.invalid'}})
def test_merge_json_removes_only_owned_hooks_from_mixed_group(self):
owned = {'type': 'command', 'command': 'bash "/old/hooks/flashbang.sh"', 'timeout': 8}
foreign = {'type': 'command', 'command': 'notify-done', 'timeout': 3}
current = {'hooks': {'Stop': [{'matcher': 'custom', 'hooks': [owned, foreign]}],
'PreCompact': [{'hooks': [{'type': 'command', 'command': 'bash "/old/record-compact.sh"'}]}]}}
managed_stop = {'hooks': [{'type': 'command', 'command': 'bash "/new/hooks/flashbang.sh"', 'timeout': 8}]}
managed = {'hooks': {'Stop': [managed_stop], 'PreCompact': []}}
hooks = OPTIONS.merge_json(current, managed)['hooks']
self.assertEqual(hooks['Stop'], [managed_stop, {'matcher': 'custom', 'hooks': [foreign]}])
self.assertNotIn('PreCompact', hooks)
def test_merge_cli_accepts_bom_input_and_preserves_non_ascii(self):
with tempfile.TemporaryDirectory() as directory:
current = Path(directory) / 'config.toml'
current.write_text('# Größe\n[mcp_servers.demo]\ncommand = "ä"\n', encoding='utf-8')
output = Path(directory) / 'out.toml'
script = str(ROOT / 'scripts/lib/install-options.py')
managed = 'model = "new"\n'.encode('utf-8')
piped = subprocess.run([sys.executable, script, 'merge-toml', '--current', str(current), '--output', str(output)], input=managed)
self.assertEqual(piped.returncode, 0)
self.assertEqual(tomllib.loads(output.read_text(encoding='utf-8'))['mcp_servers']['demo']['command'], 'ä')
managed_path = Path(directory) / 'managed.toml'
managed_path.write_bytes(managed)
subprocess.run([sys.executable, script, 'merge-toml', '--current', str(current), '--managed', str(managed_path), '--output', str(output)], check=True)
self.assertIn('# Größe', output.read_text(encoding='utf-8'))
def test_filter_preserves_other_claude_hooks_and_status(self):
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / 'settings.json'
+1 -1
View File
@@ -63,7 +63,7 @@ try {
$null = Sync-ManagedDestination -Source $source -Destination $destination -Stamp 'plugins'
$mergedConfig = [System.IO.File]::ReadAllText($configPath)
if (-not ($mergedConfig -replace "`r`n", "`n").Contains(($pluginState -replace "`r`n", "`n"))) { throw 'Updating config.toml must preserve local marketplace and plugin tables, including disabled plugins.' }
if (-not $mergedConfig.Contains("model = 'old'") -or -not $mergedConfig.Contains("model_reasoning_effort = 'high'") -or -not $mergedConfig.Contains("approvals_reviewer = 'auto_review'") -or $mergedConfig.Contains("approvals_reviewer = 'user'") -or $mergedConfig.Contains('[other]')) { throw 'Updating config.toml must preserve local model settings and enforce automatic review.' }
if (-not $mergedConfig.Contains("model = 'old'") -or -not $mergedConfig.Contains("model_reasoning_effort = 'high'") -or -not $mergedConfig.Contains("approvals_reviewer = 'auto_review'") -or $mergedConfig.Contains("approvals_reviewer = 'user'") -or -not $mergedConfig.Contains('[other]')) { throw 'Updating config.toml must preserve local settings and tables and enforce automatic review.' }
if ($mergedConfig.IndexOf("model = 'old'") -gt $mergedConfig.IndexOf('[tui]') -or $mergedConfig.IndexOf("model_reasoning_effort = 'high'") -gt $mergedConfig.IndexOf('[tui]')) { throw 'Model settings must remain in the TOML root table.' }
if ([System.IO.File]::ReadAllText((Join-Path $destination 'backups/plugins/config.toml')) -ne $originalConfig) { throw 'Original plugin configuration must be backed up.' }
$null = Sync-ManagedDestination -Source $source -Destination $destination -Stamp 'plugins-repeat'
+1 -1
View File
@@ -74,7 +74,7 @@ merged_config=$(cat "$destination/config.toml")
merged_config_normalized=${merged_config//$'\r'/}
plugin_state_normalized=${plugin_state//$'\r'/}
[[ "$merged_config_normalized" == *"$plugin_state_normalized"* ]] || { printf 'Updating config.toml must preserve local marketplace and plugin tables, including disabled plugins.\n' >&2; exit 1; }
[[ "$merged_config" == *"model = 'old'"* && "$merged_config" == *"model_reasoning_effort = 'high'"* && "$merged_config" == *"approvals_reviewer = 'auto_review'"* && "$merged_config" != *"approvals_reviewer = 'user'"* && "$merged_config" != *'[other]'* ]] || { printf 'Updating config.toml must preserve local model settings and enforce automatic review.\n' >&2; exit 1; }
[[ "$merged_config" == *"model = 'old'"* && "$merged_config" == *"model_reasoning_effort = 'high'"* && "$merged_config" == *"approvals_reviewer = 'auto_review'"* && "$merged_config" != *"approvals_reviewer = 'user'"* && "$merged_config" == *'[other]'* ]] || { printf 'Updating config.toml must preserve local model settings and enforce automatic review.\n' >&2; exit 1; }
[ "$(printf '%s\n' "$merged_config" | grep -n "model = 'old'" | cut -d: -f1)" -lt "$(printf '%s\n' "$merged_config" | grep -n '^\[tui\]$' | cut -d: -f1)" ] || { printf 'Model must remain in TOML root table.\n' >&2; exit 1; }
[ "$(printf '%s\n' "$merged_config" | grep -n "model_reasoning_effort = 'high'" | cut -d: -f1)" -lt "$(printf '%s\n' "$merged_config" | grep -n '^\[tui\]$' | cut -d: -f1)" ] || { printf 'Model effort must remain in TOML root table.\n' >&2; exit 1; }
[ "$(cat "$destination/backups/plugins/config.toml")" = "$original_config" ] || { printf 'Original plugin configuration must be backed up.\n' >&2; exit 1; }
+2 -2
View File
@@ -48,7 +48,7 @@ foreach ($shell in @('powershell','bash')) {
if ($client -eq 'claude' -and (Get-Content (Join-Path $package 'agents/reviewer.md') -Raw) -notmatch "(?m)^tools: Read,Grep,Glob,$expectedReviewerShellTool\r?`$") { throw "Reviewer capability profile is missing in $package" }
$expectedSkillCount = if ($client -eq 'claude') { $sourceSkillCount + $ruleSkillCount } else { $sourceSkillCount }
if (@(Get-ChildItem "$package/skills" -Filter SKILL.md -Recurse).Count -ne $expectedSkillCount) { throw "Missing skills in $package" }
if (-not (Test-Path "$package/skills/debugging/SKILL.md") -or -not (Test-Path "$package/skills/copywriting/SKILL.md") -or -not (Test-Path "$package/skills/marketing-psychology/SKILL.md") -or -not (Test-Path "$package/skills/image/SKILL.md") -or (Test-Path "$package/skills/marketing-plan/SKILL.md") -or (Test-Path "$package/skills/social/SKILL.md")) { throw "Incorrect skill catalog in $package" }
if ((Test-Path "$package/skills/debugging/SKILL.md") -or -not (Test-Path "$package/skills/reviews/security-review/SKILL.md") -or -not (Test-Path "$package/skills/copywriting/SKILL.md") -or -not (Test-Path "$package/skills/marketing-psychology/SKILL.md") -or -not (Test-Path "$package/skills/image/SKILL.md") -or (Test-Path "$package/skills/marketing-plan/SKILL.md") -or (Test-Path "$package/skills/social/SKILL.md")) { throw "Incorrect skill catalog in $package" }
$expected = if ($shell -eq 'powershell') { '__POWERSHELL_COMMAND__ .*flashbang.ps1' } else { 'bash .*flashbang.sh' }
if ($content -notmatch $expected -or $content -match '__HOOK_|__POWERSHELL_HOOK_') { throw "Incorrect shell command in $package" }
if ($shell -eq 'powershell' -and $content -match 'WindowStyle\s+Hidden') { throw "PowerShell hook hides the terminal in $package" }
@@ -62,7 +62,7 @@ foreach ($shell in @('powershell','bash')) {
throw "Claude finish hook is incorrect in $package"
} elseif ($content -notmatch '"defaultMode"\s*:\s*"auto"') {
throw "Claude auto permission mode is missing in $package"
} elseif ($sandboxSupported -and ($settings.sandbox.enabled -ne $true -or $settings.sandbox.allowUnsandboxedCommands -ne $false -or $settings.sandbox.failIfUnavailable -ne $true -or @($settings.sandbox.PSObject.Properties).Count -ne 3)) {
} elseif ($sandboxSupported -and ($settings.sandbox.enabled -ne $true -or $settings.sandbox.allowUnsandboxedCommands -ne $false -or $settings.sandbox.failIfUnavailable -ne $true -or (@($settings.sandbox.network.allowedDomains) -join ',') -ne 'mcp.context7.com' -or @($settings.sandbox.PSObject.Properties).Count -ne 4)) {
throw "Claude strict sandbox is missing in $package"
} elseif (-not $sandboxSupported -and ($settings.sandbox.enabled -ne $false -or @($settings.sandbox.PSObject.Properties).Count -ne 1)) {
throw "Claude sandbox must be disabled in $package"
+3 -2
View File
@@ -55,7 +55,8 @@ for shell in powershell bash; do
expected_skill_count=$source_skill_count
[ "$client" != claude ] || expected_skill_count=$((source_skill_count + rule_skill_count))
test "$(find "$package/skills" -name SKILL.md | wc -l)" -eq "$expected_skill_count"
test -f "$package/skills/debugging/SKILL.md"
test ! -e "$package/skills/debugging/SKILL.md"
test -f "$package/skills/reviews/security-review/SKILL.md"
test -f "$package/skills/copywriting/SKILL.md"
test -f "$package/skills/marketing-psychology/SKILL.md"
test -f "$package/skills/image/SKILL.md"
@@ -79,7 +80,7 @@ for shell in powershell bash; do
! grep -q 'flashbang-if-input' "$package/$file"
! grep -q '"async"[[:space:]]*:[[:space:]]*true' "$package/$file"
grep -q '"defaultMode"[[:space:]]*:[[:space:]]*"auto"' "$package/$file"
python3 -c 'import json,sys; settings=json.load(open(sys.argv[1], encoding="utf-8-sig")); expected={"enabled": True, "allowUnsandboxedCommands": False, "failIfUnavailable": True} if sys.argv[2] == "true" else {"enabled": False}; assert settings["sandbox"] == expected' "$package/$file" "$sandbox_supported"
python3 -c 'import json,sys; settings=json.load(open(sys.argv[1], encoding="utf-8-sig")); expected={"enabled": True, "allowUnsandboxedCommands": False, "failIfUnavailable": True, "network": {"allowedDomains": ["mcp.context7.com"]}} if sys.argv[2] == "true" else {"enabled": False}; assert settings["sandbox"] == expected' "$package/$file" "$sandbox_supported"
statusline_extension='sh'
[ "$shell" != powershell ] || statusline_extension=ps1
test -f "$package/statusline/statusline.$statusline_extension"
+1 -1
View File
@@ -1,5 +1,5 @@
# Implementer
Understand requirements and existing code, implement the smallest complete solution, follow applicable rules, and choose proportionate verification. Inspect your changes to the degree needed for their risk; a separate or full review is not automatic. Preserve unrelated user work. Do not add comments or documentation without explicit user approval.
Understand requirements and existing code, implement the smallest complete solution, follow applicable rules, and choose proportionate verification. Inspect your changes to the degree needed for their risk; a separate or full review is not automatic. Preserve unrelated user work.
Stay within assigned ownership, start at supplied references, and inspect additional context only when needed. Return changes, evidence, and unresolved risks using the shared orchestration handoff protocol; the parent owns shared task state.
@@ -38,7 +38,7 @@
* Remove dead code.
* Remove unused imports and dependencies.
* Remove commented-out code instead of keeping it in source files.
* Do not add comments unless the user explicitly requests them.
* Add comments only for non-obvious decisions or public contracts.
* Treat Angular, TypeScript and lint warnings seriously.
* Do not suppress diagnostics without a concrete reason.
* Prefer correctness, security and maintainability over cleverness.
+1 -1
View File
@@ -15,7 +15,7 @@ A task is only considered done when all applicable requirements below are fulfil
* No temporary code, debug output, commented-out code, placeholders, or TODOs remain unless explicitly intended.
* Error handling and edge cases are handled appropriately.
* The implementation is readable, maintainable, and production-ready.
* Documentation and comments follow `general.md`'s Documentation and Comments rules (not changed automatically, none created without explicit instruction).
* Directly affected documentation and comments are updated; other documentation or comments are added only for a concrete need.
* Any delegated research, review, or verification is complete and its relevant findings are resolved or reported.
## Work Items
+2 -2
View File
@@ -4,10 +4,10 @@ Apply instructions in this order: direct user instructions; security and data-lo
Preserve security, data, compatibility, and unrelated user work. Ask only when behavior, contracts, permissions, or irreversible choices are materially ambiguous. Otherwise choose the smallest reversible change.
Use one applicable workflow per task. Treat skills and plugins as guidance subordinate to these rules. Continue work already authorized by the user; additional design approvals, documentation, commits, or delegation require a task-specific reason and the necessary authorization.
Use one applicable workflow per task. Treat skills and plugins as guidance subordinate to these rules. Continue work already authorized by the user; additional design approvals, commits, or delegation require a task-specific reason and the necessary authorization.
Start repository discovery with direct reads and `rg` for known paths, symbols, exact strings, localized changes, configuration edits, and routine checks. Use the installed semantic retrieval CLI only when terminology is unknown, the question spans multiple components, or direct search did not locate the relevant flow: `python ~/.my-ai-configuration/semantic-retrieval/server.py search --root . --data-dir ~/.my-ai-configuration/semantic-retrieval/data --model-cache ~/.my-ai-configuration/semantic-retrieval/model-cache --query "..." --top-k 5`. Each uncached search uses Qwen3-Embedding-0.6B for candidate retrieval and Qwen3-Reranker-0.6B for final ranking; reuse the cache for repeated unchanged queries and verify results against current files. If Qwen is unavailable, fails, or returns no useful matches, continue with direct search and briefly report the limitation.
Verify proportionally: run the cheapest check that detects likely failure; broaden for security, data loss, integration, or cross-component risk. Never claim unrun checks passed. Report failures and material gaps.
Do not create documentation or explanatory comments without a clear need. Update existing documentation or comments only when directly affected by an API, configuration, or behavior change and necessary to keep them accurate. Write repository content and user-facing output in English.
Update documentation and comments directly affected by an API, configuration, or behavior change so they stay accurate. Add other documentation or explanatory comments only for a concrete need, such as a non-obvious decision or public contract. Write repository content and user-facing output in English.
@@ -9,4 +9,4 @@ Apply these rules when changing TypeScript syntax, control flow, or everyday cod
* Use strict equality. Handle `null` and `undefined` intentionally and do not use `||` when `0`, `false`, or an empty string is a valid value.
* Use optional chaining and nullish coalescing where they clarify behavior; avoid mixing nullish conventions without a project reason.
* Keep naming descriptive and consistent. Avoid vague names and abbreviations unless they are established domain terms.
* Remove dead code and unused imports. Do not add comments unless the user asks or a project rule requires them.
* Remove dead code and unused imports. Add comments only for non-obvious decisions or public contracts.
@@ -44,7 +44,7 @@
* Use descriptive names.
* Remove dead code.
* Remove commented-out code.
* Do not add comments unless the user explicitly requests them.
* Add comments only for non-obvious decisions or public contracts.
* Treat compiler, analyzer and binding warnings seriously.
* Do not suppress warnings without a concrete reason.
* Prefer correctness, responsiveness, security and maintainability over cleverness.
-23
View File
@@ -1,23 +0,0 @@
---
name: debugging
description: Use when code, tests, commands, integrations, or user-visible behavior fail unexpectedly; reproduce the failure, trace the cause, and verify a focused fix.
---
# Debugging
Use when a command, test, integration, or user-visible behavior fails or behaves unexpectedly.
## Workflow
1. State expected and observed behavior. Capture the exact error, input, environment, and reproduction steps that matter.
2. Reproduce the failure with the smallest relevant command or scenario. If it cannot be reproduced, inspect logs, recent changes, callers, configuration, and environment differences before changing code.
3. Trace the failing path through its callers and dependencies. Compare a failing case with a successful one and identify the earliest point where behavior diverges.
4. Form a root-cause explanation supported by evidence. If several causes remain plausible, gather the cheapest evidence that distinguishes them.
5. Fix the cause at the narrowest shared boundary that covers affected callers. Preserve unrelated behavior and keep error handling intact.
6. Run the smallest check that would fail if the defect returned, then add broader regression checks when the change affects connected paths or data.
Do not hide the failure with retries, broad exception handling, suppressed diagnostics, or caller-specific guards unless evidence shows that is the actual cause.
## Report
Summarize the cause, evidence, change, checks run, and any remaining uncertainty. Separate environment failures from product failures. Never claim a check passed if it did not run.
@@ -1,19 +0,0 @@
---
name: implementation-planner
description: Use when requirements are understood but implementation needs multiple dependent steps, affected areas, risk handling, or coordinated verification. Produce a plan without making the changes.
---
# Implementation Planner
Use when an understood change has multiple dependent steps or meaningful coordination risk.
## Workflow
1. Restate the intended outcome and acceptance conditions without expanding the request.
2. Identify the files, components, data contracts, or workflows likely to change. Confirm the repository's existing patterns before proposing new structure.
3. Order work by dependency. Keep steps small enough to review and verify independently where possible.
4. Call out decisions that affect compatibility, data, permissions, or external systems. Resolve routine reversible choices using repository conventions.
5. Pair each substantial step with the cheapest useful verification.
6. Keep the plan concise and omit speculative scaffolding, unrelated cleanup, and work that can safely wait.
Return an actionable sequence with affected areas, risks, and checks. This skill plans only; do not implement the change.
@@ -1,20 +0,0 @@
---
name: code-review
description: Use for a general review of a code diff when looking for concrete defects, regressions, compatibility issues, and maintainability risks. For focused API, security, performance, UI, WPF, or EF Core reviews, use the matching review skill.
---
# Code Review
Review the requested diff independently for defects and material regression risk.
## Workflow
1. Read the diff and the surrounding code, then identify the behavior and contracts the change intends to affect.
2. Trace callers and data flow through relevant shared helpers. Check normal, boundary, and failure paths rather than reviewing changed lines in isolation.
3. Look for incorrect behavior, compatibility breaks, unsafe input or data handling, resource leaks, concurrency problems, and avoidable complexity where relevant.
4. Check whether the change has focused verification and whether stated results match the evidence. Do not run broad checks by default.
5. Ignore style preferences unless they make the code error-prone or conflict with a repository rule.
## Findings
List only actionable issues, ordered by severity. Include file and line, condition, concrete impact, and the smallest practical fix. Do not modify code unless explicitly asked. If no defect is found, say so and note important unverified risks.
@@ -1,18 +0,0 @@
---
name: regression-verification
description: Use after a code or configuration change when adjacent existing behavior may have regressed; select and run focused checks, then report results and gaps.
---
# Regression Verification
Use after a change when existing behavior may have been affected.
## Workflow
1. List the user-visible behaviors and contracts adjacent to the change, including sibling callers that share modified logic.
2. Select existing tests or short scenarios that exercise those behaviors and their important boundaries.
3. Run checks at the narrowest useful level. Expand only when shared components, persisted data, security boundaries, or integration paths justify it.
4. Compare failures with the baseline and distinguish regressions from pre-existing or environment-specific failures.
5. Check generated output or compatibility artifacts when the source change feeds them.
Report behaviors checked, exact checks run, results, and any gaps. Never imply that unrun tests passed.