Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
023d1610ba | ||
|
|
145169dea8 | ||
|
|
117cdb33ab | ||
|
|
61ed36560f | ||
|
|
ef084f6fef | ||
|
|
c37ffe4c66 | ||
|
|
11c2b6dfcb |
No files matched your search
+181
-27
@@ -1,4 +1,4 @@
|
||||
name: CI
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -6,40 +6,194 @@ on:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '23 4 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
env:
|
||||
PYTHONUNBUFFERED: '1'
|
||||
PIP_DISABLE_PIP_VERSION_CHECK: '1'
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
capabilities:
|
||||
name: Capabilities and shell quality
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
- name: Bash syntax
|
||||
shell: bash
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Install shell tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install --yes --no-install-recommends shellcheck jq
|
||||
- name: Check capabilities
|
||||
run: bash scripts/commands/check-capabilities.sh
|
||||
- name: Validate Bash syntax
|
||||
run: git ls-files -z '*.sh' | xargs -0 -r -n1 bash -n
|
||||
- name: PowerShell checks when runtime exists
|
||||
shell: bash
|
||||
- name: Lint Bash
|
||||
run: shellcheck --severity=warning $(git ls-files '*.sh')
|
||||
|
||||
python:
|
||||
name: Python tests and compilation
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- name: Compile Python sources
|
||||
run: python -m compileall -q scripts shared
|
||||
- name: Run Python tests
|
||||
run: |
|
||||
if ! command -v pwsh >/dev/null 2>&1; then
|
||||
echo '::warning::PowerShell unavailable; PowerShell checks skipped.'
|
||||
exit 0
|
||||
fi
|
||||
for file in $(git ls-files '*.ps1'); do
|
||||
pwsh -NoLogo -NoProfile -NonInteractive -Command "\$tokens=\$null; \$errors=\$null; [System.Management.Automation.Language.Parser]::ParseFile((Resolve-Path '$file'),[ref]\$tokens,[ref]\$errors)|Out-Null; if(\$errors.Count){\$errors|%{Write-Error \"$file: \$_\"}; exit 1}"
|
||||
set -euo pipefail
|
||||
for file in scripts/tests/test-*.py; do
|
||||
echo "==> $file"
|
||||
python "$file"
|
||||
done
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: bash scripts/build.sh --summary
|
||||
- name: Bash install dry run
|
||||
shell: bash
|
||||
run: bash scripts/install.sh --summary --dry-run --client both --shell bash
|
||||
- name: Bash update dry run
|
||||
shell: bash
|
||||
run: bash scripts/update.sh --summary --dry-run --client both --shell bash
|
||||
- name: PowerShell install and update dry runs
|
||||
- name: Validate generated configuration
|
||||
run: python scripts/checks/validate-config.py generated/codex-powershell/config.toml generated/claude-powershell/settings.json
|
||||
|
||||
powershell-on-ubuntu:
|
||||
name: PowerShell tests on Ubuntu
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Check PowerShell runtime
|
||||
shell: bash
|
||||
run: |
|
||||
if ! command -v pwsh >/dev/null 2>&1; then
|
||||
echo '::warning::PowerShell unavailable; PowerShell dry runs skipped.'
|
||||
if command -v pwsh >/dev/null 2>&1; then
|
||||
echo 'POWERSHELL_AVAILABLE=true' >> "$GITHUB_ENV"
|
||||
exit 0
|
||||
fi
|
||||
pwsh -NoLogo -NoProfile -NonInteractive -File ./scripts/install.ps1 -Summary -DryRun -Client Both -Shell PowerShell
|
||||
pwsh -NoLogo -NoProfile -NonInteractive -File ./scripts/update.ps1 -Summary -DryRun -Client Both -Shell PowerShell
|
||||
if sudo apt-get update && sudo apt-get install --yes --no-install-recommends powershell; then
|
||||
echo 'POWERSHELL_AVAILABLE=true' >> "$GITHUB_ENV"
|
||||
else
|
||||
echo 'POWERSHELL_AVAILABLE=false' >> "$GITHUB_ENV"
|
||||
echo '::warning::PowerShell is unavailable; PowerShell checks skipped for this runner.'
|
||||
fi
|
||||
- name: Run PowerShell tests
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "${POWERSHELL_AVAILABLE:-false}" != true ]; then exit 0; fi
|
||||
pwsh -NoLogo -NoProfile -NonInteractive -File ./scripts/commands/check-capabilities.ps1
|
||||
for file in scripts/tests/test-*.ps1; do
|
||||
echo "==> $file"
|
||||
pwsh -NoLogo -NoProfile -NonInteractive -File "$file" -Summary
|
||||
done
|
||||
|
||||
build:
|
||||
name: Build and inspect generated packages
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
needs: [capabilities, python]
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Build packages
|
||||
env:
|
||||
REQUIRE_CODEX_SCHEMA: 'false'
|
||||
run: bash scripts/commands/build.sh --summary
|
||||
- name: Run build validation
|
||||
run: bash scripts/tests/test-build-validation.sh --summary
|
||||
- name: Check generated files are reproducible
|
||||
run: |
|
||||
before=$(find generated -type f -print0 | sort -z | xargs -0 sha256sum)
|
||||
bash scripts/commands/build.sh --summary
|
||||
after=$(find generated -type f -print0 | sort -z | xargs -0 sha256sum)
|
||||
test "$before" = "$after"
|
||||
|
||||
shell-integration:
|
||||
name: Bash integration and installation guards
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 25
|
||||
needs: [build]
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
set -euo pipefail
|
||||
bash scripts/tests/test-install-guards.sh --summary
|
||||
bash scripts/tests/test-plugin-selection.sh --summary
|
||||
bash scripts/tests/test-quick-update.sh --summary
|
||||
bash scripts/tests/test-managed-install.sh --summary
|
||||
bash scripts/tests/test-shell-packages.sh --summary
|
||||
- name: Run doctor and prompt budget checks
|
||||
run: |
|
||||
bash scripts/commands/doctor.sh --summary
|
||||
bash scripts/commands/prompt-budget.sh --summary
|
||||
- name: Verify dry-run installation safety
|
||||
run: |
|
||||
bash scripts/commands/install.sh --summary --dry-run --client both --shell bash
|
||||
bash scripts/commands/update.sh --summary --dry-run --client both --shell bash
|
||||
|
||||
client-smoke:
|
||||
name: Installed client configuration smoke tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
needs: [build]
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v22.16.0
|
||||
with:
|
||||
node-version: '24'
|
||||
- name: Install current client CLIs
|
||||
run: npm install --global @openai/codex @anthropic-ai/claude-code
|
||||
- name: Validate current Codex and Claude settings
|
||||
run: |
|
||||
set -euo pipefail
|
||||
schema_home=$(mktemp -d)
|
||||
cp generated/codex-powershell/config.toml "$schema_home/config.toml"
|
||||
CODEX_HOME="$schema_home" codex --strict-config --help >/dev/null
|
||||
claude --settings generated/claude-powershell/settings.json --help >/dev/null
|
||||
|
||||
retrieval:
|
||||
name: Qwen retrieval integration
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
needs: [python]
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: '3.12'
|
||||
cache: pip
|
||||
cache-dependency-path: shared/retrieval/requirements.txt
|
||||
- name: Install retrieval dependencies
|
||||
run: python -m pip install -r shared/retrieval/requirements.txt
|
||||
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: .cache/huggingface
|
||||
key: qwen-retrieval-${{ runner.os }}-${{ hashFiles('shared/retrieval/requirements.txt') }}
|
||||
- name: Run retrieval tests with real models
|
||||
env:
|
||||
QWEN_TEST_MODEL_CACHE: ${{ github.workspace }}/.cache/huggingface
|
||||
run: python scripts/tests/test-retrieval-server.py
|
||||
|
||||
security:
|
||||
name: CI configuration security checks
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Reject unsafe workflow settings
|
||||
run: |
|
||||
set -euo pipefail
|
||||
! grep -RInE 'runs-on:[[:space:]]*windows|pull_request_target|permissions:[[:space:]]*write-all|actions/checkout@v|actions/setup-python@v|curl[[:space:]].*\|[[:space:]]*(bash|sh)|wget[[:space:]].*\|[[:space:]]*(bash|sh)' .github/workflows
|
||||
test "$(grep -Roh 'runs-on: ubuntu-latest' .github/workflows | wc -l)" -ge 1
|
||||
test "$(grep -Roh 'uses: actions/[^@]*@[0-9a-f]\{40\}' .github/workflows | wc -l)" -ge 1
|
||||
- name: Check for tracked secrets
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if git ls-files | grep -E '(^|/)(\.env($|\.)|.*\.(pem|key))$'; then
|
||||
echo 'Potential secret file or private key tracked' >&2
|
||||
exit 1
|
||||
fi
|
||||
if git ls-files -z | xargs -0 -r grep -IlE 'BEGIN (RSA|OPENSSH|EC) PRIVATE KEY'; then
|
||||
echo 'Potential private key material tracked' >&2
|
||||
exit 1
|
||||
fi
|
||||
+38
-18
@@ -31,7 +31,7 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install --yes --no-install-recommends shellcheck jq
|
||||
- name: Check capabilities
|
||||
run: bash scripts/check-capabilities.sh
|
||||
run: bash scripts/commands/check-capabilities.sh
|
||||
- name: Validate Bash syntax
|
||||
run: git ls-files -z '*.sh' | xargs -0 -r -n1 bash -n
|
||||
- name: Lint Bash
|
||||
@@ -51,12 +51,12 @@ jobs:
|
||||
- name: Run Python tests
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for file in scripts/test-*.py; do
|
||||
for file in scripts/tests/test-*.py; do
|
||||
echo "==> $file"
|
||||
python "$file"
|
||||
done
|
||||
- name: Validate generated configuration
|
||||
run: python scripts/validate-config.py generated/codex-powershell/config.toml generated/claude-powershell/settings.json
|
||||
run: python scripts/checks/validate-config.py generated/codex-powershell/config.toml generated/claude-powershell/settings.json
|
||||
|
||||
powershell-on-ubuntu:
|
||||
name: PowerShell tests on Ubuntu
|
||||
@@ -81,8 +81,8 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "${POWERSHELL_AVAILABLE:-false}" != true ]; then exit 0; fi
|
||||
pwsh -NoLogo -NoProfile -NonInteractive -File ./scripts/check-capabilities.ps1
|
||||
for file in scripts/test-*.ps1; do
|
||||
pwsh -NoLogo -NoProfile -NonInteractive -File ./scripts/commands/check-capabilities.ps1
|
||||
for file in scripts/tests/test-*.ps1; do
|
||||
echo "==> $file"
|
||||
pwsh -NoLogo -NoProfile -NonInteractive -File "$file" -Summary
|
||||
done
|
||||
@@ -97,13 +97,13 @@ jobs:
|
||||
- name: Build packages
|
||||
env:
|
||||
REQUIRE_CODEX_SCHEMA: 'false'
|
||||
run: bash scripts/build.sh --summary
|
||||
run: bash scripts/commands/build.sh --summary
|
||||
- name: Run build validation
|
||||
run: bash scripts/test-build-validation.sh --summary
|
||||
run: bash scripts/tests/test-build-validation.sh --summary
|
||||
- name: Check generated files are reproducible
|
||||
run: |
|
||||
before=$(find generated -type f -print0 | sort -z | xargs -0 sha256sum)
|
||||
bash scripts/build.sh --summary
|
||||
bash scripts/commands/build.sh --summary
|
||||
after=$(find generated -type f -print0 | sort -z | xargs -0 sha256sum)
|
||||
test "$before" = "$after"
|
||||
|
||||
@@ -117,19 +117,39 @@ jobs:
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
set -euo pipefail
|
||||
bash scripts/test-install-guards.sh --summary
|
||||
bash scripts/test-plugin-selection.sh --summary
|
||||
bash scripts/test-quick-update.sh --summary
|
||||
bash scripts/test-managed-install.sh --summary
|
||||
bash scripts/test-shell-packages.sh --summary
|
||||
bash scripts/tests/test-install-guards.sh --summary
|
||||
bash scripts/tests/test-plugin-selection.sh --summary
|
||||
bash scripts/tests/test-quick-update.sh --summary
|
||||
bash scripts/tests/test-managed-install.sh --summary
|
||||
bash scripts/tests/test-shell-packages.sh --summary
|
||||
- name: Run doctor and prompt budget checks
|
||||
run: |
|
||||
bash scripts/doctor.sh --summary
|
||||
bash scripts/prompt-budget.sh --summary
|
||||
bash scripts/commands/doctor.sh --summary
|
||||
bash scripts/commands/prompt-budget.sh --summary
|
||||
- name: Verify dry-run installation safety
|
||||
run: |
|
||||
bash scripts/install.sh --summary --dry-run --client both --shell bash
|
||||
bash scripts/update.sh --summary --dry-run --client both --shell bash
|
||||
bash scripts/commands/install.sh --summary --dry-run --client both --shell bash
|
||||
bash scripts/commands/update.sh --summary --dry-run --client both --shell bash
|
||||
|
||||
client-smoke:
|
||||
name: Installed client configuration smoke tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
needs: [build]
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v22.16.0
|
||||
with:
|
||||
node-version: '24'
|
||||
- name: Install current client CLIs
|
||||
run: npm install --global @openai/codex @anthropic-ai/claude-code
|
||||
- name: Validate current Codex and Claude settings
|
||||
run: |
|
||||
set -euo pipefail
|
||||
schema_home=$(mktemp -d)
|
||||
cp generated/codex-powershell/config.toml "$schema_home/config.toml"
|
||||
CODEX_HOME="$schema_home" codex --strict-config --help >/dev/null
|
||||
claude --settings generated/claude-powershell/settings.json --help >/dev/null
|
||||
|
||||
retrieval:
|
||||
name: Qwen retrieval integration
|
||||
@@ -152,7 +172,7 @@ jobs:
|
||||
- name: Run retrieval tests with real models
|
||||
env:
|
||||
QWEN_TEST_MODEL_CACHE: ${{ github.workspace }}/.cache/huggingface
|
||||
run: python scripts/test-retrieval-server.py
|
||||
run: python scripts/tests/test-retrieval-server.py
|
||||
|
||||
security:
|
||||
name: CI configuration security checks
|
||||
|
||||
+3
-3
@@ -36,8 +36,8 @@ Write all repository content, generated instructions, scripts, prompts, document
|
||||
## Workflow
|
||||
|
||||
- Choose the smallest sufficient verification based on the change's behavior, affected consumers, risk, and remaining uncertainty. Inspection may suffice for low-risk changes; full builds, test suites, and independent reviews are not automatic.
|
||||
- Run `scripts/build.ps1 -Summary` to regenerate packages when shared definitions, adapters, or generation logic change. This generates configuration packages; it does not require building applications that use them.
|
||||
- Run `scripts/doctor.ps1 -Summary` when installation, client configuration, hooks, or environment changes need its checks. Changes to instruction wording alone do not require it.
|
||||
- Run `scripts/commands/build.ps1 -Summary` to regenerate packages when shared definitions, adapters, or generation logic change. This generates configuration packages; it does not require building applications that use them.
|
||||
- Run `scripts/commands/doctor.ps1 -Summary` when installation, client configuration, hooks, or environment changes need its checks. Changes to instruction wording alone do not require it.
|
||||
- Run focused script tests when script behavior changes. Broaden or repeat checks only when failures, affected dependencies, or unresolved risks justify it.
|
||||
- Preserve backward compatibility where practical.
|
||||
- Keep changes focused on the requested configuration behavior.
|
||||
@@ -52,7 +52,7 @@ Write all repository content, generated instructions, scripts, prompts, document
|
||||
## Installation and security
|
||||
|
||||
- Do not install global configuration automatically.
|
||||
- Perform global installation only after explicit user instruction, using `scripts/install.ps1` so replaced managed files are backed up.
|
||||
- Perform global installation only after explicit user instruction, using `scripts/commands/install.ps1` so replaced managed files are backed up.
|
||||
- Do not add project-specific rules, secrets, credentials, authentication state, or generated runtime state.
|
||||
|
||||
## Version control
|
||||
|
||||
@@ -18,27 +18,27 @@ Check the selected shell's minimum version and required Python and `jq` tools be
|
||||
PowerShell:
|
||||
|
||||
```powershell
|
||||
.\scripts\check-capabilities.ps1
|
||||
.\scripts\commands\check-capabilities.ps1
|
||||
```
|
||||
|
||||
Bash:
|
||||
|
||||
```bash
|
||||
./scripts/check-capabilities.sh
|
||||
./scripts/commands/check-capabilities.sh
|
||||
```
|
||||
|
||||
## Quickstart
|
||||
|
||||
With the [requirements](#requirements) installed, clone the repository, build it, then install it.
|
||||
Stop if any command fails. Installation asks for the shell, client, CLI update, Flashbang, and extension choices.
|
||||
Stop if any command fails. Installation asks for the shell, client, CLI update, Flashbang, Qwen retrieval, and extension choices.
|
||||
|
||||
PowerShell:
|
||||
|
||||
```powershell
|
||||
git clone https://git.lechner-systems.at/fraujulian/MyAiConfiguration.git
|
||||
cd MyAiConfiguration
|
||||
.\scripts\build.ps1 -Summary
|
||||
.\scripts\install.ps1 -Summary
|
||||
.\scripts\commands\build.ps1 -Summary
|
||||
.\scripts\commands\install.ps1 -Summary
|
||||
```
|
||||
|
||||
Bash:
|
||||
@@ -46,55 +46,40 @@ Bash:
|
||||
```bash
|
||||
git clone https://git.lechner-systems.at/fraujulian/MyAiConfiguration.git
|
||||
cd MyAiConfiguration
|
||||
./scripts/build.sh --summary
|
||||
./scripts/install.sh --summary
|
||||
./scripts/commands/build.sh --summary
|
||||
./scripts/commands/install.sh --summary
|
||||
```
|
||||
|
||||
For an already-installed configuration, use Quickupdate instead.
|
||||
|
||||
## Quickupdate
|
||||
|
||||
Run these commands from the repository root. Quick reuses the shell, client, CLI update, Flashbang, and extension choices from the last
|
||||
Run these commands from the repository root. Quick reuses the shell, client, CLI update, Flashbang, retrieval, and extension choices from the last
|
||||
successful installation or update without opening selection menus. Stop if any command fails.
|
||||
|
||||
PowerShell:
|
||||
|
||||
```powershell
|
||||
git pull --ff-only
|
||||
.\scripts\build.ps1 -Summary
|
||||
.\scripts\update.ps1 -Quick -Summary
|
||||
.\scripts\commands\build.ps1 -Summary
|
||||
.\scripts\commands\update.ps1 -Quick -Summary
|
||||
```
|
||||
|
||||
Bash:
|
||||
|
||||
```bash
|
||||
git pull --ff-only
|
||||
./scripts/build.sh --summary
|
||||
./scripts/update.sh --quick --summary
|
||||
./scripts/commands/build.sh --summary
|
||||
./scripts/commands/update.sh --quick --summary
|
||||
```
|
||||
|
||||
If no selection has been saved yet, or it predates the CLI update and Flashbang options, Quick stops with a hint. Run `scripts/update.ps1` or `./scripts/update.sh` once
|
||||
If no selection has been saved yet, or it predates a required option, Quick stops with a hint. Run `scripts/commands/update.ps1` or `./scripts/commands/update.sh` once
|
||||
without Quick to save your choices. They are stored in `~/.my-ai-configuration/selection.json`; dry runs and failed
|
||||
runs do not overwrite them. To change the selection later, run a normal update again. Quick does not automatically
|
||||
select newly added plugins.
|
||||
|
||||
Install and update also rebuild internally; the explicit build above lets you check the packages before installation.
|
||||
|
||||
## Documentation
|
||||
|
||||
| Document | Contents |
|
||||
| --- | --- |
|
||||
| [Architecture](docs/architecture.md) | Shared sources, adapters, generated packages, and installation. |
|
||||
| [Configuration](docs/configuration.md) | Client settings, permission defaults, status displays, and verification. |
|
||||
| [Plugins](docs/plugins.md) | Plugin sources, selection, installation, updates, and removal. |
|
||||
| [Hooks](docs/hooks.md) | Registered events, session-state helpers, notifications, and optional utilities. |
|
||||
| [Agents](docs/agents.md) | Agent roles and when delegation is useful. |
|
||||
| [Skills](docs/skills.md) | Skill categories and conditional rule loading. |
|
||||
| [Codex adapter](adapters/codex/README.md) | Codex-specific configuration files. |
|
||||
| [Claude Code adapter](adapters/claude/README.md) | Claude-specific configuration files. |
|
||||
| [Hook package](shared/hooks/README.md) | Overview shipped with the hook scripts. |
|
||||
| [Repository instructions](AI-Instructions.md) | Rules for maintaining this repository. |
|
||||
|
||||
## Features
|
||||
|
||||
- One shared source of truth for Codex and Claude Code.
|
||||
@@ -113,10 +98,22 @@ Install and update also rebuild internally; the explicit build above lets you ch
|
||||
never installed.
|
||||
- User-level plugin installation for Ponytail, i-have-adhd, Superpowers, Context7, Caveman, QMD, Humanizer, Impeccable, and
|
||||
Anthropic Frontend Design, with an interactive extension toggle during both install and update. Only extensions
|
||||
integrations installed and recorded by this setup can be removed through deselection.
|
||||
installed and recorded by this setup can be removed through deselection.
|
||||
- Optional local semantic retrieval for both clients using Qwen3-Embedding-0.6B embeddings and Qwen3-Reranker-0.6B
|
||||
reranking, disabled by default and removable through the install/update choice.
|
||||
|
||||
## Requirements
|
||||
|
||||
- PowerShell 5.1 or newer; PowerShell 7 is supported as well.
|
||||
- Bash 4.3 or newer, with standard utilities such as `awk`, `sed`, and `sha256sum`.
|
||||
- Python 3.11 or newer for configuration validation and task-state hooks/helpers (`python` for PowerShell scripts, `python3` for Bash scripts).
|
||||
- `jq` for Claude's Bash status line; without it, that status line produces no output.
|
||||
- Python `venv` and `pip` when the optional semantic retrieval layer is enabled.
|
||||
- Node.js 22 or newer and `npm` when QMD or MCPorter is selected.
|
||||
|
||||
The Bash scripts do not require PowerShell. The PowerShell scripts remain compatible with PowerShell 5.1;
|
||||
PowerShell 7 can also run them.
|
||||
|
||||
## Architecture
|
||||
|
||||
```text
|
||||
@@ -128,7 +125,7 @@ shared definitions -> client adapters -> generated packages -> user installation
|
||||
| `shared/` | Client-independent rules, agents, skills, hooks, status lines, and global instructions. |
|
||||
| `adapters/` | Codex and Claude Code formats, settings, templates, and the plugin manifest. |
|
||||
| `generated/` | Reproducible PowerShell and Bash packages. This directory is ignored by Git. |
|
||||
| `scripts/` | Build, installation, update, diagnosis, and shell validation entry points. |
|
||||
| [`scripts/`](docs/scripts.md) | Build, installation, update, diagnosis, and validation scripts. |
|
||||
| `docs/` | Focused documentation about the configuration design. |
|
||||
|
||||
The build creates:
|
||||
@@ -141,37 +138,42 @@ generated/
|
||||
`-- claude-bash/
|
||||
```
|
||||
|
||||
## Requirements
|
||||
## Documentation
|
||||
|
||||
- PowerShell 5.1 or newer; PowerShell 7 is supported as well.
|
||||
- Bash 4.3 or newer, with standard utilities such as `awk`, `sed`, and `sha256sum`.
|
||||
- Python 3.11 or newer for configuration validation and task-state hooks/helpers (`python` for PowerShell scripts, `python3` for Bash scripts).
|
||||
- `jq` for Claude's Bash status line; without it, that status line produces no output.
|
||||
- Python `venv` and `pip` when the optional semantic retrieval layer is enabled.
|
||||
- Node.js 22 or newer and `npm` when QMD is selected.
|
||||
| Document | Contents |
|
||||
| --- | --- |
|
||||
| [Architecture](docs/architecture.md) | Shared sources, adapters, generated packages, and installation. |
|
||||
| [Scripts](docs/scripts.md) | Commands, checks, tests, and internal script layout. |
|
||||
| [Configuration](docs/configuration.md) | Client settings, permission defaults, status displays, and verification. |
|
||||
| [Extending the configuration](docs/extending-configuration.md) | Add shared rules, skills, and agents. |
|
||||
| [Plugins](docs/plugins.md) | Plugin sources, selection, installation, updates, and removal. |
|
||||
| [Hooks](docs/hooks.md) | Registered events, session-state helpers, notifications, and optional utilities. |
|
||||
| [Agents](docs/agents.md) | Agent roles and when delegation is useful. |
|
||||
| [Skills](docs/skills.md) | Skill categories and conditional rule loading. |
|
||||
| [Codex adapter](adapters/codex/README.md) | Codex-specific configuration files. |
|
||||
| [Claude Code adapter](adapters/claude/README.md) | Claude-specific configuration files. |
|
||||
| [Hook package](shared/hooks/README.md) | Overview shipped with the hook scripts. |
|
||||
| [Repository instructions](AI-Instructions.md) | Rules for maintaining this repository. |
|
||||
|
||||
The Bash scripts do not require PowerShell. The PowerShell scripts remain compatible with PowerShell 5.1;
|
||||
PowerShell 7 can also run them.
|
||||
|
||||
## Build
|
||||
## Build Script
|
||||
|
||||
Run the matching command from the repository root:
|
||||
|
||||
PowerShell:
|
||||
```powershell
|
||||
.\scripts\build.ps1
|
||||
.\scripts\commands\build.ps1
|
||||
```
|
||||
|
||||
Bash:
|
||||
```bash
|
||||
./scripts/build.sh
|
||||
./scripts/commands/build.sh
|
||||
```
|
||||
|
||||
Rebuild when shared definitions, adapters, or generation logic change. Choose other verification according to the
|
||||
change's risk; documentation-only edits do not require a build. Building writes reproducible packages below
|
||||
`generated/` and may use temporary validation files; it does not install global user configuration.
|
||||
|
||||
## Installation
|
||||
## Installation Script
|
||||
|
||||
The installer asks for the target shell and client, unless they are passed as parameters, then refuses to continue
|
||||
if any selected destination is already installed (`Already installed, use the update script.`). Selecting PowerShell or
|
||||
@@ -182,24 +184,24 @@ Preview an installation without changing user files, backups, or plugins:
|
||||
|
||||
PowerShell:
|
||||
```powershell
|
||||
.\scripts\install.ps1 -DryRun
|
||||
.\scripts\commands\install.ps1 -DryRun
|
||||
```
|
||||
|
||||
Bash:
|
||||
```bash
|
||||
./scripts/install.sh --dry-run
|
||||
./scripts/commands/install.sh --dry-run
|
||||
```
|
||||
|
||||
Install the selected configuration interactively:
|
||||
|
||||
PowerShell:
|
||||
```powershell
|
||||
.\scripts\install.ps1
|
||||
.\scripts\commands\install.ps1
|
||||
```
|
||||
|
||||
Bash:
|
||||
```bash
|
||||
./scripts/install.sh
|
||||
./scripts/commands/install.sh
|
||||
```
|
||||
|
||||
Omitting either target parameter opens its selection prompt. There is no automatic shell default.
|
||||
@@ -230,7 +232,7 @@ not (for example, installed through a native installer or another package manage
|
||||
in place with a warning, and only a fresh `npm install -g` is run alongside it. Codex is skipped with a warning while
|
||||
`codex.exe` is running. A dry run never asks this question and never previews it, since the choice is not persisted.
|
||||
|
||||
## Update
|
||||
## Update Script
|
||||
|
||||
Once a destination is installed, use the update script (not the installer) to refresh its files and plugins. It refuses
|
||||
to continue if any selected destination is not installed yet (`Not installed, use the install script.`), also skipped
|
||||
@@ -238,20 +240,20 @@ for a dry run.
|
||||
|
||||
PowerShell:
|
||||
```powershell
|
||||
.\scripts\update.ps1
|
||||
.\scripts\commands\update.ps1
|
||||
```
|
||||
|
||||
Bash:
|
||||
```bash
|
||||
./scripts/update.sh
|
||||
./scripts/commands/update.sh
|
||||
```
|
||||
|
||||
It accepts the same `-DryRun`/`--dry-run`, `-Client`/`--client`, and `-Shell`/`--shell` parameters as the
|
||||
installer, re-syncs every managed file, and lets you revise the CLI update, Flashbang, and extension choices.
|
||||
installer, re-syncs every managed file, and lets you revise the CLI update, Flashbang, retrieval, and extension choices.
|
||||
Selected extensions are installed or updated. Deselection removes only extensions recorded as installed by this
|
||||
setup; pre-existing or manually installed extensions are retained. Quickupdate reuses the saved choices.
|
||||
|
||||
## Uninstall
|
||||
## Uninstall Script
|
||||
|
||||
Remove everything this setup previously installed for the selected client(s): managed files tracked in each
|
||||
destination's manifest, extensions and skills recorded in the extension ledger, an enabled semantic retrieval setup,
|
||||
@@ -259,12 +261,12 @@ and the saved Quickupdate selection (once no destination remains installed for a
|
||||
|
||||
PowerShell:
|
||||
```powershell
|
||||
.\scripts\uninstall.ps1
|
||||
.\scripts\commands\uninstall.ps1
|
||||
```
|
||||
|
||||
Bash:
|
||||
```bash
|
||||
./scripts/uninstall.sh
|
||||
./scripts/commands/uninstall.sh
|
||||
```
|
||||
|
||||
Omitting `-Client`/`--client` opens the same selection prompt as install and update. Uninstall refuses to continue
|
||||
@@ -287,84 +289,11 @@ is only removed once uninstalling leaves no client installed at all, so uninstal
|
||||
Uninstall does not remove the CLI tools themselves (`codex`, `claude`), any plugin marketplace registrations they
|
||||
keep outside this setup's ledger, or files this setup never installed.
|
||||
|
||||
## Doctor
|
||||
## Configuration overview
|
||||
|
||||
Check client availability, generated packages, source directories, hooks, and installed managed files:
|
||||
Codex and Claude Code receive the same shared rules, skills, agents, hooks, and status data. Codex loads applicable rules from generated files, while Claude Code exposes technology- and situation-specific rules as generated skills.
|
||||
|
||||
PowerShell:
|
||||
```powershell
|
||||
.\scripts\doctor.ps1
|
||||
```
|
||||
|
||||
Bash:
|
||||
```bash
|
||||
./scripts/doctor.sh
|
||||
```
|
||||
|
||||
Use `-Summary` (PowerShell) or `--summary` (Bash) with build, doctor, install, update, and test scripts for compact output.
|
||||
Omit the flag for detailed output. Summary mode retains warnings and failure status, but can suppress captured
|
||||
plugin-command output on failure. Set `AI_CONFIG_VERBOSE=1` when full plugin failure output is needed.
|
||||
|
||||
## Configuration
|
||||
|
||||
Codex receives global instructions, rules, skills, agent TOML files, hooks, and `config.toml`. Its defaults keep writes
|
||||
workspace-scoped and use automatic review for eligible escalation requests. Every rule ships as a plain file under
|
||||
`rules/`, and `AGENTS.md` tells Codex to load the matching one by path.
|
||||
|
||||
Claude Code receives the equivalent global instructions, agents, hooks, and `settings.json`. General rules are embedded
|
||||
once in `CLAUDE.md` (and in Codex's `AGENTS.md`); every technology- or
|
||||
situation-specific rule (`adapters/rule-skills.tsv`) is generated as a skill under `skills/rules/` instead, so
|
||||
only its name and description are permanently visible and the full rule text loads only when Claude invokes it. Its
|
||||
generated settings use the supported automatic permission mode where available.
|
||||
|
||||
Both clients receive the same logical agent roles:
|
||||
|
||||
- Architect
|
||||
- Implementer
|
||||
- Researcher
|
||||
- Reviewer
|
||||
- Verifier
|
||||
|
||||
Agent use is proportional to the task. Small changes can stay with the implementer, while additional roles are available
|
||||
when research, design, independent review, or focused verification adds value.
|
||||
|
||||
Verification is also proportional: the agent selects checks based on changed behavior, affected callers, risk,
|
||||
and uncertainty. A small change may need only focused inspection; builds, tests, and separate reviews are not
|
||||
automatic. Explicit user and project requirements still apply.
|
||||
|
||||
Both status displays include model, effort, repository, branch, context-window size, context used, and tokens used.
|
||||
Codex shows its review mode. Claude also shows its review mode in two colored lines, with context usage
|
||||
turning yellow at 60% and red at 85%. See
|
||||
[Configuration](docs/configuration.md) for details and limitations.
|
||||
|
||||
## Extending the Configuration
|
||||
|
||||
### 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.
|
||||
|
||||
### Add a skill
|
||||
|
||||
Create `shared/skills/<category>/<skill>/SKILL.md` with valid YAML frontmatter and a concise workflow. Reference shared
|
||||
rules instead of repeating persistent standards.
|
||||
|
||||
### Add an agent
|
||||
|
||||
Create `shared/agents/<agent>/agent.yml` and `instructions.md`. Keep semantic behavior in these shared files; the build
|
||||
renders the Codex and Claude Code formats.
|
||||
|
||||
Rebuild after every semantic change.
|
||||
|
||||
## Security
|
||||
|
||||
- Never store API keys, OAuth tokens, credentials, authentication state, or sensitive logs in this repository.
|
||||
- Review generated output and use the installer's dry-run before installation.
|
||||
- Global installation happens only through an explicit installer invocation.
|
||||
- Plugins execute with permissions granted by their client; review their upstream sources and trust prompts.
|
||||
- Codex keeps workspace sandboxing enabled and requires escalation outside expected development boundaries.
|
||||
The default configuration keeps writes workspace-scoped and enables automatic review for eligible escalation requests. Both clients include the same logical agent roles and compact status displays. See [Configuration](docs/configuration.md) for settings, permissions, and limitations.
|
||||
|
||||
## Support
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
},
|
||||
"sandbox": {
|
||||
"enabled": true,
|
||||
"allowUnsandboxedCommands": false
|
||||
"allowUnsandboxedCommands": false,
|
||||
"failIfUnavailable": true
|
||||
},
|
||||
"env": {
|
||||
"CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY": "__CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY__"
|
||||
|
||||
@@ -5,6 +5,7 @@ Superpowers - superpowers@claude-plugins-official openai-curated-remote superpow
|
||||
Context7 upstash/context7 context7@context7-marketplace upstash/context7 context7@context7-marketplace plugin - -
|
||||
Caveman JuliusBrussee/caveman caveman@caveman thinkhome-org/caveman-codex caveman@thinkhome-caveman plugin - -
|
||||
QMD tobi/qmd qmd@qmd - - qmd @tobilu/qmd qmd
|
||||
MCPorter - mcporter - mcporter cli mcporter -
|
||||
Humanizer blader/humanizer humanizer@humanizer - - skill blader/humanizer humanizer
|
||||
Impeccable pbakaus/impeccable impeccable@impeccable pbakaus/impeccable impeccable skill pbakaus/impeccable impeccable
|
||||
Anthropic Frontend Design anthropics/claude-plugins-official frontend-design@claude-plugins-official - - skill anthropics/skills frontend-design
|
||||
|
+1
-1
@@ -22,7 +22,7 @@ The shared [orchestration rule](../shared/rules/orchestration.md) defines the `R
|
||||
|
||||
For long-running work, the main agent maintains one compact state containing `goal`, `decisions`, `changed`, `verified`, `open`, and `risks`. An existing task-state facility is preferred; the file fallback is `.ai-session/state.json` in the current workspace. Subagents may read it when needed, but send updates to the parent. Keep unrelated concurrent tasks in separate checkouts, and keep runtime state out of commits.
|
||||
|
||||
The optional `scripts/session-state.ps1` and `scripts/session-state.sh` helpers update this workspace file. Omitted fields are preserved. Their `-Workspace`/`--workspace` option selects the workspace explicitly; the default is the current working directory. Both accept goal, decisions, changed files, completed verification, open items, and risks. `-Pointer`/`--pointer` only prints the existing file's location.
|
||||
The optional `scripts/commands/session-state.ps1` and `scripts/commands/session-state.sh` helpers update this workspace file. Omitted fields are preserved. Their `-Workspace`/`--workspace` option selects the workspace explicitly; the default is the current working directory. Both accept goal, decisions, changed files, completed verification, open items, and risks. `-Pointer`/`--pointer` only prints the existing file's location.
|
||||
|
||||
Starting a different goal requires `-Reset -Goal ...` or `--reset --goal ...`, which clears the previous task's decisions and verification. Updates are atomic; legacy field names are migrated when reading existing state.
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# Extending the configuration
|
||||
|
||||
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.
|
||||
|
||||
## Add a skill
|
||||
|
||||
Create `shared/skills/<category>/<skill>/SKILL.md` with valid YAML frontmatter and a concise workflow. Reference shared rules instead of repeating persistent standards.
|
||||
|
||||
## Add an agent
|
||||
|
||||
Create `shared/agents/<agent>/agent.yml` and `instructions.md`. Keep semantic behavior in these shared files; the build renders the Codex and Claude Code formats.
|
||||
@@ -0,0 +1,66 @@
|
||||
# Scripts
|
||||
|
||||
Run scripts from the repository root. PowerShell and Bash commands have equivalent behavior unless noted otherwise.
|
||||
|
||||
## Layout
|
||||
|
||||
| Directory | Purpose |
|
||||
| --- | --- |
|
||||
| `scripts/commands/` | User-facing build, installation, maintenance, and local utility commands. |
|
||||
| `scripts/checks/` | Standalone configuration validation. |
|
||||
| `scripts/tests/` | Repository verification scripts. |
|
||||
| `scripts/lib/` | Internal helpers used by commands; do not invoke them directly. |
|
||||
|
||||
## Commands
|
||||
|
||||
| Script | Purpose |
|
||||
| --- | --- |
|
||||
| `commands/build.ps1`, `commands/build.sh` | Generate the four client packages in `generated/`. |
|
||||
| `commands/check-capabilities.ps1`, `commands/check-capabilities.sh` | Check required tools and versions. |
|
||||
| `commands/install.ps1`, `commands/install.sh` | Install a selected client configuration. |
|
||||
| `commands/update.ps1`, `commands/update.sh` | Update an existing installation; Quick reuses saved choices. |
|
||||
| `commands/uninstall.ps1`, `commands/uninstall.sh` | Remove setup-owned configuration and extensions. |
|
||||
| `commands/doctor.ps1`, `commands/doctor.sh` | Check generated output and installed configuration. |
|
||||
| `commands/prompt-budget.ps1`, `commands/prompt-budget.sh` | Check generated instruction sizes against their budget. |
|
||||
| `commands/repository-context.ps1`, `commands/repository-context.sh` | Refresh the local repository-context cache. |
|
||||
| `commands/session-state.ps1`, `commands/session-state.sh` | Read or update the local agent session state. |
|
||||
| `commands/telemetry.ps1`, `commands/telemetry.sh` | Append a local telemetry event. |
|
||||
|
||||
Qwen retrieval uses `shared/retrieval/benchmark.py`; run it through `scripts/lib/semantic-retrieval.py benchmark --root . --home <home-directory>`.
|
||||
|
||||
## Checks and tests
|
||||
|
||||
| Script | Purpose |
|
||||
| --- | --- |
|
||||
| `checks/validate-config.py` | Validate generated Codex TOML and Claude JSON. |
|
||||
| `tests/test-build-validation.ps1`, `tests/test-build-validation.sh` | Validate build failures and generated output. |
|
||||
| `tests/test-install-guards.ps1`, `tests/test-install-guards.sh` | Validate install and update guard conditions. |
|
||||
| `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. |
|
||||
| `tests/test-plugin-selection.ps1`, `tests/test-plugin-selection.sh` | Validate plugin selection. |
|
||||
| `tests/test-prompt-inventory.py` | Validate prompt inventory collection. |
|
||||
| `tests/test-quick-update.py` | Validate saved Quickupdate choices. |
|
||||
| `tests/test-quick-update.ps1`, `tests/test-quick-update.sh` | Run the Quickupdate test through the relevant shell. |
|
||||
| `tests/test-retrieval-server.py` | Validate retrieval indexing, ranking, and optional real-model execution. |
|
||||
| `tests/test-semantic-retrieval.py` | Validate retrieval installation and removal. |
|
||||
| `tests/test-session-state.py` | Validate session-state helpers. |
|
||||
| `tests/test-shell-packages.ps1`, `tests/test-shell-packages.sh` | Validate generated packages and shell entry points. |
|
||||
| `tests/test-telemetry.py` | Validate telemetry output. |
|
||||
|
||||
## Internal helpers
|
||||
|
||||
| Script | Purpose |
|
||||
| --- | --- |
|
||||
| `lib/install-options.ps1`, `lib/install-options.sh` | Read installation options and calculate client-specific choices. |
|
||||
| `lib/install-options.py` | Filter configuration templates according to installation options. |
|
||||
| `lib/install-targets.ps1`, `lib/install-targets.sh` | Resolve configuration targets for the selected client and shell. |
|
||||
| `lib/manifest.ps1`, `lib/manifest.sh` | Synchronize managed files and their ownership manifest. |
|
||||
| `lib/plugins.ps1`, `lib/plugins.sh` | Select and install configured plugins. |
|
||||
| `lib/managed-extensions.py` | Reconcile CLI extensions and MCPorter-managed plugins. |
|
||||
| `lib/prompt-inventory.py` | Collect prompt files for the build. |
|
||||
| `lib/selection-state.ps1`, `lib/selection-state.sh` | Persist choices used by Quickupdate. |
|
||||
| `lib/selection-state.py` | Read and write Quickupdate state. |
|
||||
| `lib/semantic-retrieval.ps1`, `lib/semantic-retrieval.sh` | Invoke semantic retrieval from the installation flow. |
|
||||
| `lib/semantic-retrieval.py` | Install, index, benchmark, and remove the local retrieval service. |
|
||||
| `shared/retrieval/benchmark.py` | Measure Qwen embedding and reranking speed for the Auto recommendation. |
|
||||
@@ -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"}
|
||||
CLAUDE_SANDBOX_KEYS = {"enabled", "allowUnsandboxedCommands", "failIfUnavailable"}
|
||||
CLAUDE_STATUS_LINE_KEYS = {"type", "command"}
|
||||
CLAUDE_HOOK_KEYS = {"type", "command", "timeout"}
|
||||
CLAUDE_PERMISSION_MODES = {"acceptEdits", "auto", "bypassPermissions", "default", "dontAsk", "plan"}
|
||||
@@ -40,8 +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.get("enabled") is not True or sandbox.get("allowUnsandboxedCommands") is not False:
|
||||
fail("sandbox must be enabled without unsandboxed command retries")
|
||||
if sandbox.get("enabled") is not True or sandbox.get("allowUnsandboxedCommands") is not False or sandbox.get("failIfUnavailable") is not True:
|
||||
fail("sandbox must be enabled, fail closed, and disallow unsandboxed command retries")
|
||||
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()):
|
||||
fail("env must be an object of string values")
|
||||
@@ -1,7 +1,7 @@
|
||||
[CmdletBinding()]
|
||||
param([switch]$Summary)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path $PSScriptRoot -Parent
|
||||
$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'
|
||||
@@ -202,7 +202,7 @@ foreach ($shell in @('powershell','bash')) {
|
||||
|
||||
$tomlPath = Join-Path $output "codex-$shell/config.toml"
|
||||
$settingsPath = Join-Path $output "claude-$shell/settings.json"
|
||||
& python (Join-Path $root 'scripts/validate-config.py') $tomlPath $settingsPath
|
||||
& python (Join-Path $root 'scripts/checks/validate-config.py') $tomlPath $settingsPath
|
||||
if ($LASTEXITCODE -ne 0) { throw "Generated configuration validation failed for $shell." }
|
||||
Test-CodexSchema $tomlPath
|
||||
}
|
||||
Executable → Regular
+3
-3
@@ -8,7 +8,7 @@ while [ $# -gt 0 ]; do
|
||||
esac
|
||||
done
|
||||
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
|
||||
shared="$root/shared"
|
||||
output="$root/generated"
|
||||
plugin_manifest="$root/adapters/plugins.tsv"
|
||||
@@ -85,7 +85,7 @@ quote_toml() {
|
||||
printf '"%s"' "$value"
|
||||
}
|
||||
|
||||
session_output=$(bash "$shared/hooks/scripts/test-session-config.sh" "$root") || { printf '%s\n' "$session_output" >&2; exit 1; }
|
||||
session_output=$(bash "$shared/hooks/scripts/tests/test-session-config.sh" "$root") || { printf '%s\n' "$session_output" >&2; exit 1; }
|
||||
if [ "$summary" = true ]; then
|
||||
printf '%s\n' "$session_output" | sed '/^PASS session configuration$/d'
|
||||
else
|
||||
@@ -213,7 +213,7 @@ done
|
||||
|
||||
toml_path="$output/codex-$shell/config.toml"
|
||||
settings_path="$output/claude-$shell/settings.json"
|
||||
python3 "$root/scripts/validate-config.py" "$toml_path" "$settings_path"
|
||||
python3 "$root/scripts/checks/validate-config.py" "$toml_path" "$settings_path"
|
||||
if command -v codex >/dev/null; then
|
||||
schema_home=$(mktemp -d)
|
||||
cp "$toml_path" "$schema_home/config.toml"
|
||||
File renamed without changes.
Executable → Regular
File renamed without changes.
@@ -1,8 +1,8 @@
|
||||
[CmdletBinding()]
|
||||
param([switch]$Summary)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path $PSScriptRoot -Parent
|
||||
. (Join-Path $PSScriptRoot 'lib/manifest.ps1')
|
||||
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||
. (Join-Path $root 'scripts/lib/manifest.ps1')
|
||||
$homePath = [Environment]::GetFolderPath('UserProfile')
|
||||
$script:fail = $false
|
||||
$script:checkCount = 0
|
||||
Executable → Regular
+1
-1
@@ -9,7 +9,7 @@ while [ $# -gt 0 ]; do
|
||||
esac
|
||||
done
|
||||
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
|
||||
. "$root/scripts/lib/manifest.sh"
|
||||
home_path=${HOME:?HOME is required}
|
||||
failed=false
|
||||
@@ -6,13 +6,13 @@ param(
|
||||
[switch]$Summary
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path $PSScriptRoot -Parent
|
||||
. (Join-Path $PSScriptRoot 'lib/plugins.ps1')
|
||||
. (Join-Path $PSScriptRoot 'lib/manifest.ps1')
|
||||
. (Join-Path $PSScriptRoot 'lib/install-targets.ps1')
|
||||
. (Join-Path $PSScriptRoot 'lib/selection-state.ps1')
|
||||
. (Join-Path $PSScriptRoot 'lib/install-options.ps1')
|
||||
. (Join-Path $PSScriptRoot 'lib/semantic-retrieval.ps1')
|
||||
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||
. (Join-Path $root 'scripts/lib/plugins.ps1')
|
||||
. (Join-Path $root 'scripts/lib/manifest.ps1')
|
||||
. (Join-Path $root 'scripts/lib/install-targets.ps1')
|
||||
. (Join-Path $root 'scripts/lib/selection-state.ps1')
|
||||
. (Join-Path $root 'scripts/lib/install-options.ps1')
|
||||
. (Join-Path $root 'scripts/lib/semantic-retrieval.ps1')
|
||||
|
||||
$Shell = Read-InstallShell -Shell $Shell
|
||||
$shell = $Shell.ToLowerInvariant()
|
||||
@@ -26,7 +26,7 @@ if (-not $DryRun -and (Test-AnyManifestPresent (Get-InstallDestinations -HomePat
|
||||
$options = Read-InstallOptions -HomePath $homePath -RepositoryRoot $root -Client $Client -DryRun:$DryRun
|
||||
$reinstallViaNpm = Read-CliReinstallOption -Client $Client -DryRun:$DryRun
|
||||
|
||||
$buildScript = Join-Path $PSScriptRoot 'build.ps1'
|
||||
$buildScript = Join-Path $root 'scripts/commands/build.ps1'
|
||||
& $buildScript -Summary:$Summary
|
||||
if (-not $?) { throw 'Build failed. Installation was not started.' }
|
||||
$generated = Join-Path $root 'generated'
|
||||
Executable → Regular
+2
-2
@@ -15,7 +15,7 @@ while [ $# -gt 0 ]; do
|
||||
esac
|
||||
done
|
||||
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
|
||||
. "$root/scripts/lib/plugins.sh"
|
||||
. "$root/scripts/lib/manifest.sh"
|
||||
. "$root/scripts/lib/install-targets.sh"
|
||||
@@ -40,7 +40,7 @@ reinstall_via_npm=$(read_cli_reinstall_option "$client" "$dry_run")
|
||||
|
||||
build_args=()
|
||||
[ "$summary" = false ] || build_args+=(--summary)
|
||||
bash "$root/scripts/build.sh" "${build_args[@]}"
|
||||
bash "$root/scripts/commands/build.sh" "${build_args[@]}"
|
||||
generated="$root/generated"
|
||||
[ -d "$generated" ] || { printf 'Generated output is missing after a successful build.\n' >&2; exit 1; }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[CmdletBinding()]
|
||||
param([switch]$Summary)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path $PSScriptRoot -Parent
|
||||
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||
$generated = Join-Path $root 'generated'
|
||||
if (-not (Test-Path (Join-Path $generated 'claude-bash/CLAUDE.md'))) { throw 'Generated output is missing; run build first.' }
|
||||
function Measure-Tree([string]$path) {
|
||||
@@ -30,7 +30,7 @@ if ($old) {
|
||||
}
|
||||
}
|
||||
if ($Summary) { Write-Output 'Prompt Budget'; foreach ($row in $rows | Select-Object -First 2) { $limits = $baselineValues[$row.Name]; $base = if($limits){$limits[0]}else{'-'}; $absolute = if($limits){$limits[1]}else{'-'}; Write-Output ('{0,-28} {1,8} bytes baseline {2} relative 115% absolute {3}' -f $row.Name,$row.Bytes,$base,$absolute) }; $rows | Select-Object -Skip 2 | ForEach-Object { Write-Output ('{0,-28} {1,8} bytes {2,6} tokens' -f $_.Name,$_.Bytes,$_.Tokens) } } else { $rows | ForEach-Object { Write-Output ('{0}: {1} bytes / {2} tokens' -f $_.Name,$_.Bytes,$_.Tokens) } }
|
||||
& python (Join-Path $PSScriptRoot 'lib/prompt-inventory.py') --home ([Environment]::GetFolderPath('UserProfile'))
|
||||
& python (Join-Path $root 'scripts/lib/prompt-inventory.py') --home ([Environment]::GetFolderPath('UserProfile'))
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Prompt inventory failed.' }
|
||||
if ($fail) { exit 1 }
|
||||
exit 0
|
||||
Executable → Regular
+1
-1
@@ -2,7 +2,7 @@
|
||||
set -euo pipefail
|
||||
summary=false
|
||||
for arg in "$@"; do case "$arg" in --summary) summary=true;; *) printf 'Unknown argument: %s\n' "$arg" >&2; exit 1;; esac; done
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
|
||||
generated="$root/generated"
|
||||
[ -f "$generated/claude-bash/CLAUDE.md" ] || { printf 'Generated output is missing; run build first.\n' >&2; exit 1; }
|
||||
bytes() { wc -c < "$1" | tr -d ' '; }
|
||||
@@ -1,7 +1,7 @@
|
||||
[CmdletBinding()]
|
||||
param([switch]$Force,[switch]$Summary)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path $PSScriptRoot -Parent
|
||||
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||
$cacheDir = Join-Path $root '.ai-session'
|
||||
$cachePath = Join-Path $cacheDir 'repository-context.json'
|
||||
$safeRoot = $root.Replace('\','/')
|
||||
Executable → Regular
+1
-1
@@ -2,7 +2,7 @@
|
||||
set -euo pipefail
|
||||
force=false; summary=false
|
||||
for arg in "$@"; do case "$arg" in --force) force=true;; --summary) summary=true;; *) printf 'Unknown argument: %s\n' "$arg" >&2; exit 1;; esac; done
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd); cache_dir="$root/.ai-session"; cache_path="$cache_dir/repository-context.json"
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd); cache_dir="$root/.ai-session"; cache_path="$cache_dir/repository-context.json"
|
||||
head=$(git -c "safe.directory=$root" -C "$root" rev-parse HEAD 2>/dev/null || true); key_input=$head
|
||||
while IFS= read -r file; do key_input+="\n$file:$(sha256sum "$file" | awk '{print $1}')"; done < <(find "$root" -type f \( -name AI-Instructions.md -o -path '*/adapters/*' \) ! -path '*/generated/*' ! -path '*/.git/*' ! -path '*/.ai-session/*' | sort)
|
||||
key=$(printf '%s' "$key_input" | sha256sum | awk '{print $1}')
|
||||
@@ -12,7 +12,7 @@ param(
|
||||
[switch]$Reset
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$helper = Join-Path (Split-Path $PSScriptRoot -Parent) 'shared/hooks/scripts/session-state.py'
|
||||
$helper = Join-Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) 'shared/hooks/scripts/session-state.py'
|
||||
$values = @{ workspace = $Workspace }
|
||||
$fields = @{ Goal = 'goal'; Decisions = 'decisions'; ChangedFiles = 'changed'; Verified = 'verified'; Pending = 'open'; Risks = 'risks' }
|
||||
foreach ($parameter in $fields.Keys) {
|
||||
Executable → Regular
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
|
||||
arguments=()
|
||||
if [ "${AI_TASK_GOAL+x}" = x ]; then arguments+=(--goal "$AI_TASK_GOAL"); fi
|
||||
python3 "$root/shared/hooks/scripts/session-state.py" "${arguments[@]}" "$@"
|
||||
@@ -2,7 +2,7 @@
|
||||
param([ValidateSet('record','summary','rotate')][string]$Action='record',[string]$Event,[string]$Agent,[string]$Role,[Nullable[int]]$DurationMs,[Nullable[int]]$ToolCalls,[string]$CommandClass,[Nullable[int64]]$Bytes,[Nullable[int]]$Count,[Nullable[int]]$HandoffBytes,[Nullable[int]]$FilesRead,[Nullable[int]]$FilesChanged,[string]$Outcome,[Nullable[int]]$InputTokens,[Nullable[int]]$CachedTokens,[Nullable[int]]$OutputTokens,[Nullable[int]]$ReasoningTokens)
|
||||
$ErrorActionPreference = 'Stop'; if ($env:AI_CONFIG_TELEMETRY -eq '0') { exit 0 }
|
||||
try {
|
||||
$root = Split-Path $PSScriptRoot -Parent; $dir = Join-Path $root '.ai-session'; $path = Join-Path $dir 'telemetry.jsonl'; New-Item $dir -ItemType Directory -Force | Out-Null
|
||||
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent; $dir = Join-Path $root '.ai-session'; $path = Join-Path $dir 'telemetry.jsonl'; New-Item $dir -ItemType Directory -Force | Out-Null
|
||||
if ($Action -eq 'rotate') { if (Test-Path $path) { Move-Item $path ($path + '.1') -Force }; exit 0 }
|
||||
if ($Action -eq 'summary') { if (-not (Test-Path $path)) { Write-Output 'Telemetry: no events'; exit 0 }; $events=@(Get-Content $path | ForEach-Object { try { $_ | ConvertFrom-Json } catch {} }); Write-Output ('Telemetry: {0} events' -f $events.Count); foreach($group in @($events | Where-Object role | Group-Object role | Sort-Object Name)){ $inputs=@($group.Group|Where-Object tokenUsage|ForEach-Object {$_.tokenUsage.input}|Where-Object {$_ -is [int] -or $_ -is [long]}|Sort-Object); $median=if($inputs.Count){$inputs[[int][Math]::Floor($inputs.Count/2)]}else{'-'}; Write-Output ('{0}: runs {1} findings {2} median input {3}' -f $group.Name,$group.Count,@($group.Group|Where-Object outcome -eq findings).Count,$median)}; exit 0 }
|
||||
if ([string]::IsNullOrWhiteSpace($Event)) { throw 'Event is required.' }
|
||||
Executable → Regular
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
[ "${AI_CONFIG_TELEMETRY:-1}" = 0 ] && exit 0
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd); dir="$root/.ai-session"; path="$dir/telemetry.jsonl"; mkdir -p "$dir"
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd); dir="$root/.ai-session"; path="$dir/telemetry.jsonl"; mkdir -p "$dir"
|
||||
action=${1:-record}; shift || true
|
||||
case "$action" in
|
||||
rotate) [ -f "$path" ] && mv -f "$path" "$path.1"; exit 0;;
|
||||
@@ -6,11 +6,11 @@ param(
|
||||
[switch]$Summary
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path $PSScriptRoot -Parent
|
||||
. (Join-Path $PSScriptRoot 'lib/plugins.ps1')
|
||||
. (Join-Path $PSScriptRoot 'lib/manifest.ps1')
|
||||
. (Join-Path $PSScriptRoot 'lib/install-targets.ps1')
|
||||
. (Join-Path $PSScriptRoot 'lib/semantic-retrieval.ps1')
|
||||
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||
. (Join-Path $root 'scripts/lib/plugins.ps1')
|
||||
. (Join-Path $root 'scripts/lib/manifest.ps1')
|
||||
. (Join-Path $root 'scripts/lib/install-targets.ps1')
|
||||
. (Join-Path $root 'scripts/lib/semantic-retrieval.ps1')
|
||||
|
||||
$Client = Read-InstallClient -Client $Client
|
||||
$homePath = [Environment]::GetFolderPath('UserProfile')
|
||||
@@ -15,7 +15,7 @@ while [ $# -gt 0 ]; do
|
||||
esac
|
||||
done
|
||||
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
|
||||
. "$root/scripts/lib/plugins.sh"
|
||||
. "$root/scripts/lib/manifest.sh"
|
||||
. "$root/scripts/lib/install-targets.sh"
|
||||
@@ -7,13 +7,13 @@ param(
|
||||
[switch]$Summary
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path $PSScriptRoot -Parent
|
||||
. (Join-Path $PSScriptRoot 'lib/plugins.ps1')
|
||||
. (Join-Path $PSScriptRoot 'lib/manifest.ps1')
|
||||
. (Join-Path $PSScriptRoot 'lib/install-targets.ps1')
|
||||
. (Join-Path $PSScriptRoot 'lib/selection-state.ps1')
|
||||
. (Join-Path $PSScriptRoot 'lib/install-options.ps1')
|
||||
. (Join-Path $PSScriptRoot 'lib/semantic-retrieval.ps1')
|
||||
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||
. (Join-Path $root 'scripts/lib/plugins.ps1')
|
||||
. (Join-Path $root 'scripts/lib/manifest.ps1')
|
||||
. (Join-Path $root 'scripts/lib/install-targets.ps1')
|
||||
. (Join-Path $root 'scripts/lib/selection-state.ps1')
|
||||
. (Join-Path $root 'scripts/lib/install-options.ps1')
|
||||
. (Join-Path $root 'scripts/lib/semantic-retrieval.ps1')
|
||||
|
||||
$homePath = [Environment]::GetFolderPath('UserProfile')
|
||||
if ($Quick) {
|
||||
@@ -32,7 +32,7 @@ if (-not $DryRun -and -not (Test-AllManifestsPresent (Get-InstallDestinations -H
|
||||
|
||||
$options = if ($Quick) { $saved } else { Read-InstallOptions -HomePath $homePath -RepositoryRoot $root -Client $Client -DryRun:$DryRun }
|
||||
|
||||
$buildScript = Join-Path $PSScriptRoot 'build.ps1'
|
||||
$buildScript = Join-Path $root 'scripts/commands/build.ps1'
|
||||
& $buildScript -Summary:$Summary
|
||||
if (-not $?) { throw 'Build failed. Update was not started.' }
|
||||
$generated = Join-Path $root 'generated'
|
||||
Executable → Regular
+2
-2
@@ -17,7 +17,7 @@ while [ $# -gt 0 ]; do
|
||||
esac
|
||||
done
|
||||
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
|
||||
. "$root/scripts/lib/plugins.sh"
|
||||
. "$root/scripts/lib/manifest.sh"
|
||||
. "$root/scripts/lib/install-targets.sh"
|
||||
@@ -45,7 +45,7 @@ if [ "$quick" = false ]; then read_install_options; fi
|
||||
|
||||
build_args=()
|
||||
[ "$summary" = false ] || build_args+=(--summary)
|
||||
bash "$root/scripts/build.sh" "${build_args[@]}"
|
||||
bash "$root/scripts/commands/build.sh" "${build_args[@]}"
|
||||
generated="$root/generated"
|
||||
[ -d "$generated" ] || { printf 'Generated output is missing after a successful build.\n' >&2; exit 1; }
|
||||
|
||||
@@ -24,7 +24,23 @@ function Read-InstallOptions {
|
||||
return @{
|
||||
update_agents = Read-InstallBoolean -Prompt "Update selected agent CLIs ($Client)?" -Default $defaults.update_agents
|
||||
flashbang = Read-InstallBoolean -Prompt 'Enable the Flashbang notification hook?' -Default $defaults.flashbang
|
||||
semantic_retrieval = Read-InstallBoolean -Prompt 'Enable Qwen3 embedding and reranking semantic retrieval?' -Default $defaults.semantic_retrieval
|
||||
semantic_retrieval = Read-SemanticRetrievalOption -Default $defaults.semantic_retrieval -RepositoryRoot $RepositoryRoot -HomePath $HomePath
|
||||
}
|
||||
}
|
||||
|
||||
function Read-SemanticRetrievalOption {
|
||||
param([bool]$Default,[string]$RepositoryRoot,[string]$HomePath)
|
||||
$hint = if ($Default) { 'Y/n/a' } else { 'y/N/a' }
|
||||
while ($true) {
|
||||
Write-Host "Enable Qwen3 embedding and reranking semantic retrieval? [$hint] " -NoNewline
|
||||
$answer = [Console]::ReadLine()
|
||||
if ($null -eq $answer) { throw 'Input ended before semantic retrieval was confirmed.' }
|
||||
switch ($answer.Trim().ToLowerInvariant()) {
|
||||
'' { return $Default }
|
||||
{ $_ -in @('y', 'yes') } { return $true }
|
||||
{ $_ -in @('n', 'no') } { return $false }
|
||||
{ $_ -in @('a', 'auto') } { return Test-SemanticRetrievalDevice -RepositoryRoot $RepositoryRoot -HomePath $HomePath }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,28 @@ read_install_options() {
|
||||
[ "$dry_run" = false ] || return 0
|
||||
update_agents=$(read_install_boolean "Update selected agent CLIs ($client)?" "$update_agents") || return
|
||||
flashbang=$(read_install_boolean 'Enable the Flashbang notification hook?' "$flashbang") || return
|
||||
semantic_retrieval=$(read_install_boolean 'Enable Qwen3 embedding and reranking semantic retrieval?' "$semantic_retrieval") || return
|
||||
semantic_retrieval=$(read_semantic_retrieval_option "$semantic_retrieval") || return
|
||||
}
|
||||
|
||||
read_semantic_retrieval_option() {
|
||||
local default=$1 hint=y/N/a answer
|
||||
[ "$default" = false ] || hint=Y/n/a
|
||||
while :; do
|
||||
printf 'Enable Qwen3 embedding and reranking semantic retrieval? [%s] ' "$hint" >&2
|
||||
IFS= read -r answer || { printf 'Input ended before semantic retrieval was confirmed.\n' >&2; return 1; }
|
||||
answer=${answer%$'\r'}
|
||||
case "$answer" in
|
||||
'') printf '%s\n' "$default"; return ;;
|
||||
y|Y|yes|Yes|YES) printf 'true\n'; return ;;
|
||||
n|N|no|No|NO) printf 'false\n'; return ;;
|
||||
a|A|auto|Auto|AUTO)
|
||||
if test_semantic_retrieval_device "$root" "$home_path"; then printf 'true\n'; return; fi
|
||||
status=$?
|
||||
[ "$status" -eq 1 ] && { printf 'false\n'; return; }
|
||||
return "$status"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
global_npm_package() {
|
||||
|
||||
@@ -166,7 +166,7 @@ class Manager:
|
||||
raise ValueError('Invalid extension ownership ledger.')
|
||||
identities = set()
|
||||
for resource in self.state['resources']:
|
||||
if not isinstance(resource, dict) or resource.get('client') not in ('codex', 'claude') or resource.get('kind') not in ('plugin', 'skill', 'qmd'):
|
||||
if not isinstance(resource, dict) or resource.get('client') not in ('codex', 'claude') or resource.get('kind') not in ('plugin', 'skill', 'qmd', 'cli'):
|
||||
raise ValueError('Invalid extension ownership record.')
|
||||
if not isinstance(resource.get('name'), str) or not resource['name']:
|
||||
raise ValueError('Invalid extension ownership name.')
|
||||
@@ -192,8 +192,10 @@ class Manager:
|
||||
if not relative.startswith(directory + '/') or not re.fullmatch(r'[a-f0-9]{64}', value):
|
||||
raise ValueError('Invalid owned skill file record.')
|
||||
safe_path(self.home, relative)
|
||||
elif resource.get('package') != '@tobilu/qmd' or resource['client'] != 'codex':
|
||||
elif resource['kind'] == 'qmd' and (resource.get('package') != '@tobilu/qmd' or resource['client'] != 'codex'):
|
||||
raise ValueError('Invalid owned QMD record.')
|
||||
elif resource['kind'] == 'cli' and not re.fullmatch(r'[a-z0-9@/_.-]+', resource.get('package', '')):
|
||||
raise ValueError('Invalid owned CLI record.')
|
||||
|
||||
def save(self):
|
||||
if not self.dry_run:
|
||||
@@ -227,41 +229,19 @@ class Manager:
|
||||
self.command(arguments)
|
||||
self.cache[client].pop(selector, None)
|
||||
elif record['kind'] == 'skill':
|
||||
empty_candidates = set()
|
||||
for relative, expected in list(record['files'].items()):
|
||||
path = safe_path(self.home, relative)
|
||||
if path.exists():
|
||||
if not path.is_file() or digest(path) != expected:
|
||||
print(f'WARN Preserving modified skill file: {relative}')
|
||||
continue
|
||||
path.unlink()
|
||||
parent = path.parent
|
||||
directory = safe_path(self.home, record['directory'])
|
||||
while parent != directory.parent:
|
||||
empty_candidates.add(parent)
|
||||
parent = parent.parent
|
||||
del record['files'][relative]
|
||||
self.save()
|
||||
directory = safe_path(self.home, record['directory'])
|
||||
if directory.exists():
|
||||
for path in sorted(empty_candidates, key=lambda p: len(p.parts), reverse=True):
|
||||
if path.is_dir() and not path.is_symlink():
|
||||
try:
|
||||
path.rmdir()
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
directory.rmdir()
|
||||
except OSError:
|
||||
pass
|
||||
if record['files']:
|
||||
return
|
||||
shutil.rmtree(directory)
|
||||
else:
|
||||
self.command([record['client'], 'mcp', 'remove', 'qmd'] + (['--scope', 'user'] if record['client'] == 'claude' else []))
|
||||
package = record['package']
|
||||
self.command(['npm', 'uninstall', '--global', package])
|
||||
self.state['resources'].remove(record)
|
||||
self.save()
|
||||
|
||||
def ensure(self, client, entry):
|
||||
if entry['codex_method'] == 'cli':
|
||||
self.ensure_cli(client, entry)
|
||||
return
|
||||
if entry['codex_method'] == 'qmd':
|
||||
self.ensure_qmd(client, entry)
|
||||
return
|
||||
@@ -326,6 +306,18 @@ class Manager:
|
||||
self.save()
|
||||
self.cache[client][selector] = {'scope': 'user'}
|
||||
|
||||
def ensure_cli(self, client, entry):
|
||||
package = entry['codex_source']
|
||||
if not re.fullmatch(r'[a-z0-9@/_.-]+', package):
|
||||
raise ValueError('Invalid CLI package.')
|
||||
record = next((r for r in self.state['resources'] if r['client'] == client and r['name'] == entry['name']), None)
|
||||
if record is not None:
|
||||
return
|
||||
self.command(['npm', 'install', '--global', package])
|
||||
if not self.dry_run:
|
||||
self.state['resources'].append(dict(client=client, name=entry['name'], kind='cli', package=package))
|
||||
self.save()
|
||||
|
||||
def ensure_qmd(self, client, entry):
|
||||
package = entry['codex_source']
|
||||
if package != '@tobilu/qmd':
|
||||
@@ -340,7 +332,6 @@ class Manager:
|
||||
record = next((r for r in self.state['resources'] if r['client'] == client and r['name'] == entry['name']), None)
|
||||
if record is not None:
|
||||
return
|
||||
self.command(['codex', 'mcp', 'add', 'qmd', '--', 'qmd', 'mcp'])
|
||||
self.state['resources'].append(dict(client='codex', name=entry['name'], kind='qmd', package=package))
|
||||
self.save()
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ function Select-ConfiguredPlugins {
|
||||
$idField = if ($referenceClient -eq 'Claude') { 'id' } else { 'pluginId' }
|
||||
$checked = @($toggleable | ForEach-Object {
|
||||
if ($Mode -eq 'Install') { $true; return }
|
||||
if ($_.codex_method -eq 'cli') { return $null -ne (Get-Command $_.codex_source -ErrorAction SilentlyContinue) }
|
||||
$selector = if ($referenceClient -eq 'Claude') { $_.claude_plugin } else { $_.codex_plugin }
|
||||
if ($referenceClient -eq 'Codex' -and $_.codex_method -eq 'qmd') {
|
||||
$null -ne (Get-Command qmd -ErrorAction SilentlyContinue)
|
||||
|
||||
@@ -110,6 +110,10 @@ select_configured_plugins() {
|
||||
checked+=(true)
|
||||
continue
|
||||
fi
|
||||
if [ "$(plugin_field "$root" "$name" codex_method)" = cli ]; then
|
||||
if command -v "$(plugin_field "$root" "$name" codex_source)" >/dev/null 2>&1; then checked+=(true); else checked+=(false); fi
|
||||
continue
|
||||
fi
|
||||
if [ "$reference_client" = claude ]; then selector=$(plugin_field "$root" "$name" claude_plugin); else selector=$(plugin_field "$root" "$name" codex_plugin); fi
|
||||
if [ "$reference_client" = codex ] && [ "$(plugin_field "$root" "$name" codex_method)" = qmd ]; then
|
||||
if command -v qmd >/dev/null 2>&1; then checked+=(true); else checked+=(false); fi
|
||||
|
||||
@@ -8,3 +8,11 @@ function Sync-SemanticRetrieval {
|
||||
& python @arguments
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Semantic retrieval reconciliation failed.' }
|
||||
}
|
||||
|
||||
function Test-SemanticRetrievalDevice {
|
||||
param([Parameter(Mandatory=$true)][string]$RepositoryRoot,[Parameter(Mandatory=$true)][string]$HomePath,[switch]$Summary)
|
||||
& python (Join-Path $PSScriptRoot 'semantic-retrieval.py') benchmark --root $RepositoryRoot --home $HomePath $(if ($Summary) { '--summary' }) | Write-Host
|
||||
if ($LASTEXITCODE -eq 0) { return $true }
|
||||
if ($LASTEXITCODE -eq 2) { return $false }
|
||||
throw 'Semantic retrieval benchmark failed.'
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
FILES = ('server.py', 'requirements.txt')
|
||||
FILES = ('server.py', 'benchmark.py', 'requirements.txt')
|
||||
CUDA_INDEX = 'https://download.pytorch.org/whl/cu126'
|
||||
|
||||
|
||||
@@ -106,17 +106,55 @@ def sync(root, home, clients, enabled, dry_run, update, summary=False):
|
||||
save_state(state_path, state, False)
|
||||
|
||||
|
||||
def benchmark(root, home, dry_run, summary=False):
|
||||
base = home / '.my-ai-configuration'
|
||||
target = base / 'semantic-retrieval'
|
||||
state_path = base / 'semantic-retrieval.json'
|
||||
if dry_run:
|
||||
if not summary:
|
||||
print('DRYRUN benchmark semantic retrieval: Qwen3-Embedding-0.6B + Qwen3-Reranker-0.6B')
|
||||
return True
|
||||
state = load_state(state_path)
|
||||
source = root / 'shared' / 'retrieval'
|
||||
if target.exists() and not state['files']:
|
||||
if not all((target / name).is_file() and digest(target / name) == digest(source / name) for name in FILES):
|
||||
raise ValueError('Semantic retrieval directory exists without setup ownership; refusing to overwrite it.')
|
||||
target.mkdir(parents=True, exist_ok=True)
|
||||
for name in FILES:
|
||||
shutil.copy2(source / name, target / name)
|
||||
state['files'][name] = digest(target / name)
|
||||
python = ensure_runtime(target, False)
|
||||
result = subprocess.run([str(python), str(target / 'benchmark.py'), '--model-cache', str(target / 'model-cache')], check=True,
|
||||
stdout=subprocess.PIPE, text=True, timeout=300)
|
||||
outcome = json.loads(result.stdout)
|
||||
if not isinstance(outcome, dict) or not isinstance(outcome.get('recommended'), bool):
|
||||
raise ValueError('Semantic retrieval benchmark returned an invalid result.')
|
||||
if outcome['recommended']:
|
||||
save_state(state_path, state, False)
|
||||
else:
|
||||
shutil.rmtree(target)
|
||||
state_path.unlink(missing_ok=True)
|
||||
if not summary:
|
||||
print('Semantic retrieval benchmark: ' + ('recommended' if outcome['recommended'] else 'not recommended') + f" (embedding {outcome['embeddingSeconds']}s, reranking {outcome['rerankingSeconds']}s)")
|
||||
return outcome['recommended']
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('action', choices=('sync',))
|
||||
parser.add_argument('action', choices=('sync', 'benchmark'))
|
||||
parser.add_argument('--root', required=True, type=Path)
|
||||
parser.add_argument('--home', required=True, type=Path)
|
||||
parser.add_argument('--client', choices=('codex', 'claude', 'both'), required=True)
|
||||
parser.add_argument('--enabled', choices=('true', 'false'), required=True)
|
||||
parser.add_argument('--client', choices=('codex', 'claude', 'both'))
|
||||
parser.add_argument('--enabled', choices=('true', 'false'))
|
||||
parser.add_argument('--dry-run', action='store_true')
|
||||
parser.add_argument('--update', action='store_true')
|
||||
parser.add_argument('--summary', action='store_true')
|
||||
args = parser.parse_args()
|
||||
if args.action == 'benchmark':
|
||||
recommended = benchmark(args.root.resolve(), args.home.resolve(), args.dry_run, args.summary)
|
||||
raise SystemExit(0 if recommended else 2)
|
||||
if args.client is None or args.enabled is None:
|
||||
parser.error('sync requires --client and --enabled')
|
||||
clients = ('codex', 'claude') if args.client == 'both' else (args.client,)
|
||||
sync(args.root.resolve(), args.home.resolve(), clients, args.enabled == 'true', args.dry_run, args.update, args.summary)
|
||||
print('SEMANTIC RETRIEVAL: reconciliation complete' + (' (dry run)' if args.dry_run else ''))
|
||||
|
||||
@@ -9,3 +9,12 @@ sync_semantic_retrieval() {
|
||||
[ "$summary" != true ] || arguments+=(--summary)
|
||||
python3 "${arguments[@]}"
|
||||
}
|
||||
|
||||
test_semantic_retrieval_device() {
|
||||
local root=$1 home_path=$2 summary=${3:-false}
|
||||
local arguments=("$root/scripts/lib/semantic-retrieval.py" benchmark --root "$root" --home "$home_path")
|
||||
[ "$summary" != true ] || arguments+=(--summary)
|
||||
if python3 "${arguments[@]}" >&2; then return 0; fi
|
||||
[ "$?" -eq 2 ] && return 1
|
||||
return 2
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
[CmdletBinding()]
|
||||
param([switch]$Summary)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path $PSScriptRoot -Parent
|
||||
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||
$work = Join-Path ([System.IO.Path]::GetTempPath()) ("ai-config-build-validation-" + [Guid]::NewGuid())
|
||||
New-Item $work -ItemType Directory -Force | Out-Null
|
||||
|
||||
@@ -18,14 +18,14 @@ function Test-BuildFails([string]$Label, [scriptblock]$Mutate) {
|
||||
$copy = New-RepoCopy
|
||||
& $Mutate $copy
|
||||
$failed = $false
|
||||
try { & (Join-Path $copy 'scripts/build.ps1') 2>&1 | Out-Null }
|
||||
try { & (Join-Path $copy 'scripts/commands/build.ps1') 2>&1 | Out-Null }
|
||||
catch { $failed = $true }
|
||||
Remove-Item $copy -Recurse -Force -ErrorAction SilentlyContinue
|
||||
if (-not $failed) { throw "Expected build to fail for case '$Label' but it succeeded." }
|
||||
}
|
||||
|
||||
$baseline = New-RepoCopy
|
||||
& (Join-Path $baseline 'scripts/build.ps1') -Summary | Out-Null
|
||||
& (Join-Path $baseline 'scripts/commands/build.ps1') -Summary | Out-Null
|
||||
|
||||
Test-BuildFails 'invalid Claude settings.json' {
|
||||
param($copy)
|
||||
Executable → Regular
+3
-3
@@ -8,7 +8,7 @@ while [ $# -gt 0 ]; do
|
||||
esac
|
||||
done
|
||||
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
|
||||
work=$(mktemp -d)
|
||||
cleanup() { rm -rf -- "$work"; }
|
||||
trap cleanup EXIT
|
||||
@@ -24,7 +24,7 @@ test_build_fails() {
|
||||
local label=$1 mutate_fn=$2 copy
|
||||
copy=$(new_repo_copy)
|
||||
"$mutate_fn" "$copy"
|
||||
if bash "$copy/scripts/build.sh" >/dev/null 2>&1; then
|
||||
if bash "$copy/scripts/commands/build.sh" >/dev/null 2>&1; then
|
||||
printf 'Expected build to fail for case "%s" but it succeeded.\n' "$label" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -32,7 +32,7 @@ test_build_fails() {
|
||||
}
|
||||
|
||||
baseline=$(new_repo_copy)
|
||||
bash "$baseline/scripts/build.sh" --summary >/dev/null
|
||||
bash "$baseline/scripts/commands/build.sh" --summary >/dev/null
|
||||
|
||||
mutate_invalid_json() {
|
||||
printf '} this is not json {' >> "$1/adapters/claude/config/settings.json"
|
||||
@@ -1,9 +1,9 @@
|
||||
[CmdletBinding()]
|
||||
param([switch]$Summary)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path $PSScriptRoot -Parent
|
||||
. (Join-Path $PSScriptRoot 'lib/manifest.ps1')
|
||||
. (Join-Path $PSScriptRoot 'lib/install-targets.ps1')
|
||||
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||
. (Join-Path $root 'scripts/lib/manifest.ps1')
|
||||
. (Join-Path $root 'scripts/lib/install-targets.ps1')
|
||||
|
||||
$work = Join-Path ([System.IO.Path]::GetTempPath()) ("ai-config-install-guard-test-" + [Guid]::NewGuid())
|
||||
try {
|
||||
Executable → Regular
+1
-1
@@ -8,7 +8,7 @@ while [ $# -gt 0 ]; do
|
||||
esac
|
||||
done
|
||||
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
|
||||
. "$root/scripts/lib/manifest.sh"
|
||||
. "$root/scripts/lib/install-targets.sh"
|
||||
|
||||
@@ -9,7 +9,7 @@ import tomllib
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
ROOT = Path(__file__).resolve().parent.parent.parent
|
||||
SPEC = importlib.util.spec_from_file_location('install_options', ROOT / 'scripts/lib/install-options.py')
|
||||
OPTIONS = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(OPTIONS)
|
||||
@@ -172,19 +172,19 @@ class ManagedExtensionTests(unittest.TestCase):
|
||||
self.manager(dry_run=True, summary=True).sync([plugin(), skill()], ['codex'], {'Example', 'Humanizer'})
|
||||
self.assertEqual('EXTENSIONS: reconciliation complete (dry run)\n', output.getvalue())
|
||||
|
||||
def test_qmd_installs_once_and_registers_codex_mcp(self):
|
||||
def test_qmd_installs_once_as_a_cli_without_registering_mcp(self):
|
||||
entry = dict(name='QMD', codex_method='qmd', codex_source='@tobilu/qmd', codex_skill='qmd', codex_plugin='-', codex_marketplace='-', claude_plugin='qmd@qmd', claude_marketplace='tobi/qmd')
|
||||
self.manager().sync([entry], ['codex'], {'QMD'})
|
||||
self.assertEqual([['npm', 'install', '--global', '@tobilu/qmd'], ['codex', 'mcp', 'add', 'qmd', '--', 'qmd', 'mcp']], self.commands)
|
||||
self.assertEqual([['npm', 'install', '--global', '@tobilu/qmd']], self.commands)
|
||||
self.manager().sync([], ['codex'], set())
|
||||
self.assertEqual(['codex', 'mcp', 'remove', 'qmd'], self.commands[-1])
|
||||
self.assertEqual(['npm', 'uninstall', '--global', '@tobilu/qmd'], self.commands[-1])
|
||||
|
||||
def test_qmd_installs_once_for_both_clients(self):
|
||||
entry = dict(name='QMD', codex_method='qmd', codex_source='@tobilu/qmd', codex_skill='qmd', codex_plugin='-', codex_marketplace='-', claude_plugin='qmd@qmd', claude_marketplace='tobi/qmd')
|
||||
self.manager().sync([entry], ['claude', 'codex'], {'QMD'})
|
||||
self.assertEqual(1, self.commands.count(['npm', 'install', '--global', '@tobilu/qmd']))
|
||||
self.assertIn(['claude', 'plugin', 'install', 'qmd@qmd', '--scope', 'user'], self.commands)
|
||||
self.assertIn(['codex', 'mcp', 'add', 'qmd', '--', 'qmd', 'mcp'], self.commands)
|
||||
self.assertNotIn(['codex', 'mcp', 'add', 'qmd', '--', 'qmd', 'mcp'], self.commands)
|
||||
|
||||
def test_foreign_empty_directory_survives_skill_removal(self):
|
||||
self.manager().sync([skill()], ['codex'], {'Humanizer'})
|
||||
@@ -1,9 +1,9 @@
|
||||
[CmdletBinding()]
|
||||
param([switch]$Summary)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path $PSScriptRoot -Parent
|
||||
. (Join-Path $PSScriptRoot 'lib/manifest.ps1')
|
||||
. (Join-Path $PSScriptRoot 'lib/install-targets.ps1')
|
||||
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||
. (Join-Path $root 'scripts/lib/manifest.ps1')
|
||||
. (Join-Path $root 'scripts/lib/install-targets.ps1')
|
||||
|
||||
$work = Join-Path ([System.IO.Path]::GetTempPath()) ("ai-config-manifest-test-" + [Guid]::NewGuid())
|
||||
New-Item $work -ItemType Directory -Force | Out-Null
|
||||
Executable → Regular
+1
-1
@@ -8,7 +8,7 @@ while [ $# -gt 0 ]; do
|
||||
esac
|
||||
done
|
||||
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
|
||||
. "$root/scripts/lib/manifest.sh"
|
||||
. "$root/scripts/lib/install-targets.sh"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
[CmdletBinding()]
|
||||
param([switch]$Summary)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||
function Write-Host { param($Object) if (-not $Summary) { Microsoft.PowerShell.Utility\Write-Host $Object } }
|
||||
. (Join-Path $PSScriptRoot 'lib/plugins.ps1')
|
||||
. (Join-Path $root 'scripts/lib/plugins.ps1')
|
||||
|
||||
$script:answers = [System.Collections.Generic.Queue[string]]::new()
|
||||
$script:answers.Enqueue('1')
|
||||
@@ -43,22 +44,22 @@ $null = Get-InstalledPlugins -Client Codex -HomePath 'C:/temporary profile'
|
||||
if ($observedCodexHome -ne (Join-Path 'C:/temporary profile' '.codex') -or $observedProfile -ne 'C:/temporary profile') { throw 'Plugin inspection must use the requested user home.' }
|
||||
if ($env:CODEX_HOME -ne $previousCodexHome -or $env:USERPROFILE -ne $previousProfile) { throw 'Plugin inspection must restore the process environment.' }
|
||||
$script:answers.Enqueue('done')
|
||||
$selection = Select-ConfiguredPlugins -RepositoryRoot (Split-Path $PSScriptRoot -Parent) -Client Codex -Mode Update
|
||||
$selection = Select-ConfiguredPlugins -RepositoryRoot (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -Client Codex -Mode Update
|
||||
if (@($selection.Selected | Where-Object { $_.codex_method -eq 'plugin' }).Count -ne 5) { throw 'All installed Codex plugins must start checked when updating.' }
|
||||
$script:answers.Enqueue('4')
|
||||
$script:answers.Enqueue('done')
|
||||
$selection = Select-ConfiguredPlugins -RepositoryRoot (Split-Path $PSScriptRoot -Parent) -Client Codex -Mode Update
|
||||
$selection = Select-ConfiguredPlugins -RepositoryRoot (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -Client Codex -Mode Update
|
||||
if (@($selection.Deselected | Where-Object { $_.name -eq 'Context7' }).Count -ne 1) { throw 'Only the explicitly unchecked installed plugin must be deselected.' }
|
||||
$script:managedArguments = @()
|
||||
function python { $script:managedArguments = @($args); $global:LASTEXITCODE = 0 }
|
||||
$entry = [pscustomobject]@{ name = 'Test'; codex_method = 'plugin'; codex_marketplace = '-'; codex_plugin = 'test@market' }
|
||||
Sync-ConfiguredPlugins -RepositoryRoot (Split-Path $PSScriptRoot -Parent) -Client Codex -HomePath 'C:/temporary home' -Update -Summary -Entries @($entry)
|
||||
Sync-ConfiguredPlugins -RepositoryRoot (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -Client Codex -HomePath 'C:/temporary home' -Update -Summary -Entries @($entry)
|
||||
if ($managedArguments -notcontains 'sync' -or $managedArguments -notcontains '--update' -or $managedArguments -notcontains 'Test' -or $managedArguments -notcontains 'C:/temporary home') { throw 'Managed reconciliation must receive selection, home, and update mode.' }
|
||||
Sync-ConfiguredPlugins -RepositoryRoot (Split-Path $PSScriptRoot -Parent) -Client Codex -Entries @()
|
||||
Sync-ConfiguredPlugins -RepositoryRoot (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -Client Codex -Entries @()
|
||||
if ($managedArguments[-1] -ne '--selected') { throw 'Empty selection must reach reconciliation for managed cleanup.' }
|
||||
function Get-InstalledPlugins { throw 'Empty selection must not query installed plugins.' }
|
||||
function Invoke-PluginCommand { throw 'Empty selection must not run plugin commands.' }
|
||||
Install-ConfiguredPlugins -RepositoryRoot (Split-Path $PSScriptRoot -Parent) -Client Both -Entries @()
|
||||
Install-ConfiguredPlugins -RepositoryRoot (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -Client Both -Entries @()
|
||||
Uninstall-DeselectedPlugins -Client Both -Entries @()
|
||||
|
||||
if ($Summary) { Write-Output 'Tests: PASS | plugin selection' } else { Write-Output 'PASS plugin toggle selection: toggle and confirm' }
|
||||
Executable → Regular
+1
-1
@@ -8,7 +8,7 @@ while [ $# -gt 0 ]; do
|
||||
esac
|
||||
done
|
||||
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
|
||||
. "$root/scripts/lib/plugins.sh"
|
||||
|
||||
# shellcheck disable=SC2034
|
||||
File renamed without changes.
File renamed without changes.
@@ -8,7 +8,7 @@ import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
ROOT = Path(__file__).resolve().parent.parent.parent
|
||||
HELPER = ROOT / 'scripts/lib/selection-state.py'
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ class SelectionTests(unittest.TestCase):
|
||||
env=environment, capture_output=True, text=True, timeout=10)
|
||||
self.assertEqual(save.returncode, 0, save.stdout + save.stderr)
|
||||
original = self.path.read_bytes()
|
||||
source = (ROOT / 'scripts/update.ps1').read_text(encoding='utf-8-sig')
|
||||
source = (ROOT / 'scripts/commands/update.ps1').read_text(encoding='utf-8-sig')
|
||||
source = source.replace("[Environment]::GetFolderPath('UserProfile')", '$env:TEST_SELECTION_HOME')
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.tmp.ps1', dir=ROOT / 'scripts',
|
||||
encoding='utf-8', delete=False) as stream:
|
||||
@@ -148,7 +148,7 @@ class SelectionTests(unittest.TestCase):
|
||||
self.assertEqual(save.returncode, 0, save.stdout + save.stderr)
|
||||
original = self.path.read_bytes()
|
||||
environment = dict(os.environ, HOME=self.home.as_posix())
|
||||
result = subprocess.run([shell, str(ROOT / 'scripts/update.sh'), '--quick', '--dry-run'],
|
||||
result = subprocess.run([shell, str(ROOT / 'scripts/commands/update.sh'), '--quick', '--dry-run'],
|
||||
env=environment, stdin=subprocess.DEVNULL, capture_output=True, text=True, timeout=90)
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
self.assertNotIn('Select plugins', result.stdout)
|
||||
Executable → Regular
+2
-2
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
case "${1:-}" in ''|--summary) ;; *) printf 'Unknown argument: %s\n' "$1" >&2; exit 1 ;; esac
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
python3 "$root/scripts/test-quick-update.py"
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
|
||||
python3 "$root/scripts/tests/test-quick-update.py"
|
||||
printf 'Tests: PASS | quick update selection\n'
|
||||
@@ -11,7 +11,7 @@ import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
ROOT = Path(__file__).resolve().parent.parent.parent
|
||||
SPEC = importlib.util.spec_from_file_location('retrieval_server', ROOT / 'shared/retrieval/server.py')
|
||||
MODULE = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(MODULE)
|
||||
@@ -9,7 +9,7 @@ import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
ROOT = Path(__file__).resolve().parent.parent.parent
|
||||
SPEC = importlib.util.spec_from_file_location('semantic_retrieval', ROOT / 'scripts/lib/semantic-retrieval.py')
|
||||
MODULE = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(MODULE)
|
||||
@@ -8,7 +8,7 @@ import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
ROOT = Path(__file__).resolve().parent.parent.parent
|
||||
SHELLS = [
|
||||
('powershell', shutil.which('powershell') or shutil.which('pwsh')),
|
||||
('bash', str(Path(os.environ.get('ProgramFiles', 'C:/Program Files')) / 'Git/bin/bash.exe')
|
||||
@@ -88,16 +88,16 @@ class SessionStateHookTests(unittest.TestCase):
|
||||
if shell == 'powershell':
|
||||
prefix = [executable, '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command']
|
||||
commands = [
|
||||
'& ./scripts/session-state.ps1 -Workspace $env:TASK_WORKSPACE -Goal $env:TASK_GOAL -Decisions @("keep", "line two") -Pending @("check")',
|
||||
'& ./scripts/session-state.ps1 -Workspace $env:TASK_WORKSPACE -Verified @("passed")',
|
||||
'& ./scripts/session-state.ps1 -Workspace $env:TASK_WORKSPACE -Pointer',
|
||||
'& ./scripts/commands/session-state.ps1 -Workspace $env:TASK_WORKSPACE -Goal $env:TASK_GOAL -Decisions @("keep", "line two") -Pending @("check")',
|
||||
'& ./scripts/commands/session-state.ps1 -Workspace $env:TASK_WORKSPACE -Verified @("passed")',
|
||||
'& ./scripts/commands/session-state.ps1 -Workspace $env:TASK_WORKSPACE -Pointer',
|
||||
]
|
||||
else:
|
||||
prefix = [executable, '-c']
|
||||
commands = [
|
||||
'bash ./scripts/session-state.sh --workspace "$TASK_WORKSPACE" --goal "$TASK_GOAL" --decisions keep "line two" --open check',
|
||||
'bash ./scripts/session-state.sh --workspace "$TASK_WORKSPACE" --verified passed',
|
||||
'bash ./scripts/session-state.sh --workspace "$TASK_WORKSPACE" --pointer',
|
||||
'bash ./scripts/commands/session-state.sh --workspace "$TASK_WORKSPACE" --goal "$TASK_GOAL" --decisions keep "line two" --open check',
|
||||
'bash ./scripts/commands/session-state.sh --workspace "$TASK_WORKSPACE" --verified passed',
|
||||
'bash ./scripts/commands/session-state.sh --workspace "$TASK_WORKSPACE" --pointer',
|
||||
]
|
||||
for command in commands:
|
||||
result = subprocess.run(prefix + [command], cwd=repository, capture_output=True,
|
||||
@@ -1,13 +1,13 @@
|
||||
[CmdletBinding()]
|
||||
param([switch]$Summary)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path $PSScriptRoot -Parent
|
||||
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||
$powerShellHook = Get-Content -LiteralPath (Join-Path $root 'shared/hooks/flashbang.ps1') -Raw
|
||||
$bashHook = Get-Content -LiteralPath (Join-Path $root 'shared/hooks/flashbang.sh') -Raw
|
||||
if ($powerShellHook -notmatch '\$HoldMs\s*=\s*250' -or $powerShellHook -notmatch '\$FadeMs\s*=\s*250' -or ([regex]::Matches($bashHook, 'default=250').Count -lt 2)) { throw 'Flashbang defaults must total 500 milliseconds on PowerShell and Bash.' }
|
||||
$statusInput = '{"model":{"display_name":"Test Model"},"effort":{"level":"high"},"workspace":{"current_dir":"' + $root.Replace('\','\\') + '","repo":{"name":"TestRepo"}},"context_window":{"context_window_size":200000,"used_percentage":8.5,"total_input_tokens":15500,"total_output_tokens":1200}}'
|
||||
$branch = & git -C $root branch --show-current
|
||||
$buildSummaryOutput = & (Join-Path $root 'scripts/build.ps1') -Summary
|
||||
$buildSummaryOutput = & (Join-Path $root 'scripts/commands/build.ps1') -Summary
|
||||
if (@($buildSummaryOutput | Where-Object { $_ -eq 'Build: PASS | 4 packages' }).Count -ne 1) { throw "build.ps1 -Summary must still print the PASS line: $($buildSummaryOutput -join '; ')" }
|
||||
$statusOutput = ($statusInput | & (Join-Path $root 'shared/statusline/statusline.ps1')) -join "`n"
|
||||
$plainStatusOutput = [regex]::Replace($statusOutput, [char]27 + '\[[0-9;]*m', '')
|
||||
@@ -48,7 +48,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 (-not $settings.sandbox.enabled -or $settings.sandbox.allowUnsandboxedCommands -ne $false) {
|
||||
} elseif (-not $settings.sandbox.enabled -or $settings.sandbox.allowUnsandboxedCommands -ne $false -or $settings.sandbox.failIfUnavailable -ne $true) {
|
||||
throw "Claude strict sandbox is missing in $package"
|
||||
} elseif (-not (Test-Path -LiteralPath (Join-Path $package "statusline/statusline.$(if ($shell -eq 'powershell') { 'ps1' } else { 'sh' })")) -or $settings.statusLine.command -notmatch "statusline\.$(if ($shell -eq 'powershell') { 'ps1' } else { 'sh' })") {
|
||||
throw "Claude status line is incorrect in $package"
|
||||
@@ -70,17 +70,17 @@ foreach ($shellSelection in @('1','2')) {
|
||||
$script:answers.Enqueue($shellSelection)
|
||||
$script:answers.Enqueue($clientSelection)
|
||||
function Read-Host { param($Prompt) $answers.Dequeue() }
|
||||
$output = & "$PSScriptRoot/install.ps1" -DryRun 6>$null
|
||||
$output = & (Join-Path $root 'scripts/commands/install.ps1') -DryRun 6>$null
|
||||
if ($output -notcontains 'PASS install dry-run') { throw 'Dry-run did not finish.' }
|
||||
$shell = if ($shellSelection -eq '1') { 'powershell' } else { 'bash' }
|
||||
if (($output -join "`n") -notmatch "-$shell") { throw 'Incorrect selected package.' }
|
||||
}
|
||||
}
|
||||
$doctorSummaryOutput = & (Join-Path $root 'scripts/doctor.ps1') -Summary
|
||||
$doctorSummaryOutput = & (Join-Path $root 'scripts/commands/doctor.ps1') -Summary
|
||||
if (@($doctorSummaryOutput | Where-Object { $_ -match '^PASS ' }).Count -ne 0) { throw 'Doctor summary mode must not print individual PASS lines.' }
|
||||
if (@($doctorSummaryOutput | Where-Object { $_ -match '^Doctor: PASS \| \d+ checks$' }).Count -ne 1) { throw "Doctor summary mode must print one 'Doctor: PASS | N checks' line: $($doctorSummaryOutput -join '; ')" }
|
||||
foreach ($entryPoint in @('install','update')) {
|
||||
$preview = @(& "$PSScriptRoot/$entryPoint.ps1" -Summary -DryRun -Client Both -Shell PowerShell)
|
||||
$preview = @(& (Join-Path $root "scripts/commands/$entryPoint.ps1") -Summary -DryRun -Client Both -Shell PowerShell)
|
||||
if ($preview -match '^(SOURCE|CREATE|UPDATE|UNCHANGED|BACKUP|DRYRUN|PASS) ') { throw "$entryPoint summary leaked per-item output." }
|
||||
if (@($preview | Where-Object { $_ -match ('^' + $entryPoint + ': PASS') }).Count -ne 1) { throw "$entryPoint summary is missing." }
|
||||
}
|
||||
Executable → Regular
+6
-6
@@ -7,12 +7,12 @@ while [ $# -gt 0 ]; do
|
||||
*) printf 'Unknown argument: %s\n' "$1" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
|
||||
grep -Eq '\$HoldMs[[:space:]]*=[[:space:]]*250' "$root/shared/hooks/flashbang.ps1"
|
||||
grep -Eq '\$FadeMs[[:space:]]*=[[:space:]]*250' "$root/shared/hooks/flashbang.ps1"
|
||||
[ "$(grep -c 'default=250' "$root/shared/hooks/flashbang.sh")" -ge 2 ]
|
||||
branch=$(git -C "$root" branch --show-current)
|
||||
build_summary_output=$(bash "$root/scripts/build.sh" --summary)
|
||||
build_summary_output=$(bash "$root/scripts/commands/build.sh" --summary)
|
||||
if ! printf '%s\n' "$build_summary_output" | grep -qx 'Build: PASS | 4 packages'; then
|
||||
printf 'build.sh --summary must still print the PASS line: %s\n' "$build_summary_output" >&2
|
||||
exit 1
|
||||
@@ -65,7 +65,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")); assert settings["sandbox"] == {"enabled": True, "allowUnsandboxedCommands": False}' "$package/$file"
|
||||
python3 -c 'import json,sys; settings=json.load(open(sys.argv[1], encoding="utf-8-sig")); assert settings["sandbox"] == {"enabled": True, "allowUnsandboxedCommands": False, "failIfUnavailable": True}' "$package/$file"
|
||||
statusline_extension='sh'
|
||||
[ "$shell" != powershell ] || statusline_extension=ps1
|
||||
test -f "$package/statusline/statusline.$statusline_extension"
|
||||
@@ -100,7 +100,7 @@ for shell in powershell bash; do
|
||||
done
|
||||
for shell_selection in 1 2; do
|
||||
for client_selection in 1 2 3; do
|
||||
output=$(printf '%s\n%s\n' "$shell_selection" "$client_selection" | bash "$root/scripts/install.sh" --dry-run 2>&1) || { printf '%s\n' "$output" >&2; exit 1; }
|
||||
output=$(printf '%s\n%s\n' "$shell_selection" "$client_selection" | bash "$root/scripts/commands/install.sh" --dry-run 2>&1) || { printf '%s\n' "$output" >&2; exit 1; }
|
||||
printf '%s\n' "$output" | grep -E 'WARN|FAIL|WARNING|ERROR' || true
|
||||
[[ "$output" == *'PASS install dry-run'* ]]
|
||||
shell=powershell
|
||||
@@ -108,7 +108,7 @@ for shell_selection in 1 2; do
|
||||
[[ "$output" == *"-$shell"* ]]
|
||||
done
|
||||
done
|
||||
doctor_summary_output=$(bash "$root/scripts/doctor.sh" --summary)
|
||||
doctor_summary_output=$(bash "$root/scripts/commands/doctor.sh" --summary)
|
||||
if printf '%s\n' "$doctor_summary_output" | grep -q '^PASS '; then
|
||||
printf 'Doctor summary mode must not print individual PASS lines.\n' >&2
|
||||
exit 1
|
||||
@@ -118,7 +118,7 @@ if ! printf '%s\n' "$doctor_summary_output" | grep -Eq '^Doctor: PASS \| [0-9]+
|
||||
exit 1
|
||||
fi
|
||||
for entry_point in install update; do
|
||||
preview=$(bash "$root/scripts/$entry_point.sh" --summary --dry-run --client both --shell bash)
|
||||
preview=$(bash "$root/scripts/commands/$entry_point.sh" --summary --dry-run --client both --shell bash)
|
||||
if printf '%s\n' "$preview" | grep -Eq '^(SOURCE|CREATE|UPDATE|UNCHANGED|BACKUP|DRYRUN|PASS) '; then
|
||||
printf '%s summary leaked per-item output.\n' "$entry_point" >&2
|
||||
exit 1
|
||||
@@ -7,7 +7,7 @@ import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
ROOT = Path(__file__).resolve().parent.parent.parent
|
||||
|
||||
|
||||
class TelemetryTests(unittest.TestCase):
|
||||
@@ -17,9 +17,9 @@ class TelemetryTests(unittest.TestCase):
|
||||
self.skipTest('Bash unavailable')
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
script = root / 'scripts/telemetry.sh'
|
||||
script = root / 'scripts/commands/telemetry.sh'
|
||||
script.parent.mkdir()
|
||||
shutil.copy(ROOT / 'scripts/telemetry.sh', script)
|
||||
shutil.copy(ROOT / 'scripts/commands/telemetry.sh', script)
|
||||
env = dict(os.environ, AI_CONFIG_TELEMETRY='1')
|
||||
result = subprocess.run([bash, str(script), 'record', 'subagent-stop', '--role', 'reviewer', '--filesRead', '3', '--outcome', 'findings'], env=env, capture_output=True, text=True)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
@@ -1,5 +1,5 @@
|
||||
param([string]$RepositoryRoot = (Split-Path $PSScriptRoot -Parent | Split-Path -Parent | Split-Path -Parent))
|
||||
$build = Join-Path $RepositoryRoot 'scripts/build.ps1'
|
||||
$build = Join-Path $RepositoryRoot 'scripts/commands/build.ps1'
|
||||
if (-not (Test-Path $build)) { Write-Error 'Build script is missing.'; exit 1 }
|
||||
$shellCommand = if ([Environment]::OSVersion.Platform -eq [PlatformID]::Win32NT) { 'powershell' } else { 'pwsh' }
|
||||
& $shellCommand -NoProfile -ExecutionPolicy Bypass -File $build
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
set -euo pipefail
|
||||
|
||||
root=${1:?Repository root is required}
|
||||
bash "$root/scripts/build.sh"
|
||||
bash "$root/scripts/commands/build.sh"
|
||||
printf 'PASS post-change verification\n'
|
||||
@@ -0,0 +1,56 @@
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import statistics
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
def elapsed(call):
|
||||
started = time.monotonic()
|
||||
call()
|
||||
return time.monotonic() - started
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--model-cache', required=True, type=Path)
|
||||
parser.add_argument('--max-seconds', type=float, default=float(os.environ.get('QWEN_BENCHMARK_MAX_SECONDS', '8')))
|
||||
args = parser.parse_args()
|
||||
if args.max_seconds <= 0:
|
||||
raise ValueError('--max-seconds must be greater than zero.')
|
||||
os.environ['HF_HOME'] = str(args.model_cache)
|
||||
from server import shared_encoder, shared_reranker
|
||||
|
||||
encoder = shared_encoder()
|
||||
reranker = shared_reranker()
|
||||
query = 'Find the implementation that updates the selected client configuration.'
|
||||
documents = ['Path: scripts/commands/install.py\n\n' + query] * 50
|
||||
print('Benchmark: warming up embedding model...', file=sys.stderr, flush=True)
|
||||
encoder.encode([query], prompt_name='query')
|
||||
print('Benchmark: probing reranker speed...', file=sys.stderr, flush=True)
|
||||
probe = elapsed(lambda: reranker.predict([(query, documents[0])], show_progress_bar=False))
|
||||
if probe * len(documents) > args.max_seconds:
|
||||
print(json.dumps({
|
||||
'embeddingSeconds': None,
|
||||
'rerankingSeconds': round(probe, 3),
|
||||
'maxSeconds': args.max_seconds,
|
||||
'recommended': False,
|
||||
}))
|
||||
return
|
||||
print('Benchmark: measuring full retrieval batch...', file=sys.stderr, flush=True)
|
||||
reranker.predict([(query, document) for document in documents], show_progress_bar=False)
|
||||
embedding = statistics.median(elapsed(lambda: encoder.encode([query], prompt_name='query')) for _ in range(3))
|
||||
reranking = statistics.median(elapsed(lambda: reranker.predict([(query, document) for document in documents], show_progress_bar=False)) for _ in range(3))
|
||||
result = {
|
||||
'embeddingSeconds': round(embedding, 3),
|
||||
'rerankingSeconds': round(reranking, 3),
|
||||
'maxSeconds': args.max_seconds,
|
||||
'recommended': embedding <= args.max_seconds and reranking <= args.max_seconds,
|
||||
}
|
||||
print(json.dumps(result))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user