diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ee356cb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml,json}] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d953a05 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,12 @@ +* text=auto eol=lf +*.sh text eol=lf +bin/* text eol=lf +libexec/* text eol=lf +hooks/* text eol=lf +*.md text eol=lf +*.json text eol=lf +*.jsonl text eol=lf +*.png binary +*.jpg binary +*.zip binary +*.gz binary diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..246f6cb --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,8 @@ +* @UtmostCreator +/.github/ @UtmostCreator +/install.sh @UtmostCreator +/uninstall.sh @UtmostCreator +/lib/exec-guard/ @UtmostCreator +/lib/policy.sh @UtmostCreator +/lib/secrets.sh @UtmostCreator +/lib/log-redaction.sh @UtmostCreator diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..be6ebe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,50 @@ +name: Bug report +description: Report reproducible incorrect behavior +title: "bug: " +labels: [bug, triage] +body: + - type: markdown + attributes: + value: Do not include secrets or private repository/session data. + - type: input + id: version + attributes: + label: Version or commit + placeholder: v0.1.0 or commit SHA + validations: + required: true + - type: input + id: environment + attributes: + label: Environment + placeholder: Ubuntu 24.04, Bash 5.2 + validations: + required: true + - type: textarea + id: command + attributes: + label: Command and sanitized output + render: shell + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected behavior + validations: + required: true + - type: textarea + id: actual + attributes: + label: Actual behavior and minimal reproduction + validations: + required: true + - type: checkboxes + id: checks + attributes: + label: Checks + options: + - label: I removed secrets and private data. + required: true + - label: I searched existing issues. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..c255aae --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Usage questions and design discussion + url: https://github.com/UtmostCreator/agent-kit/discussions + about: Ask questions or discuss broad proposals. + - name: Security vulnerability + url: https://github.com/UtmostCreator/agent-kit/security/advisories/new + about: Report vulnerabilities privately. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..289f400 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,30 @@ +name: Feature request +description: Propose a bounded improvement +title: "feat: " +labels: [enhancement, triage] +body: + - type: textarea + id: problem + attributes: + label: Problem + description: What repository-operation problem is not adequately solved? + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed contract + description: Include inputs, outputs, scope, failure behavior, and safety implications. + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + - type: checkboxes + id: compatibility + attributes: + label: Compatibility + options: + - label: This can preserve existing command and output contracts. + - label: This requires a documented breaking change. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..40ccbdc --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,14 @@ +# Repository instructions + +Follow the canonical instructions in `AGENTS.md`. + +This is a Bash-based safety toolkit for coding-agent repository operations. Prefer existing `agent-kit` commands and shared modules over ad hoc shell logic. Preserve scope checks, execution guards, snapshots, rollback, redaction, machine-readable output, and exit-code contracts. + +Never commit `.ai-logs/`, session data, context packs, credentials, or local environment files. Do not bypass a safety control to make a test pass. Add tests for behavior changes and run: + +```bash +./scripts/check.sh +./scripts/check-publishable.sh +``` + +Report exact verification evidence and any checks that could not be run. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..177b067 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + cooldown: + default-days: 7 diff --git a/.github/instructions/shell.instructions.md b/.github/instructions/shell.instructions.md new file mode 100644 index 0000000..9f81e57 --- /dev/null +++ b/.github/instructions/shell.instructions.md @@ -0,0 +1,5 @@ +--- +applyTo: "**/*.sh,bin/**,libexec/**,hooks/**" +--- + +Use Bash 4.4+ conventions unless the file declares another shell. Quote expansions, use arrays for argument lists, validate untrusted input, use `--` before positional paths where supported, and avoid `eval`. Use secure temporary directories and cleanup traps. Preserve stdout/stderr and exit-code contracts. Put reusable logic in `lib/` and keep command entry points thin. Add or update shell tests for every behavior change. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..f3faf12 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,21 @@ +## Summary + + + +## Scope + + + +## Verification + +```text +Exact commands and results +``` + +## Risk + +- [ ] Output or schema contract changed +- [ ] Security boundary changed +- [ ] Installation or release behavior changed +- [ ] Backward compatibility changed +- [ ] Generated/session data checked and excluded diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9706711 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,125 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + checks: + name: checks + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-22.04, ubuntu-24.04] + timeout-minutes: 20 + + steps: + - name: Harden the runner (audit-only; observes egress, never blocks) + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout exact event revision without external actions + env: + REPOSITORY: ${{ github.repository }} + EVENT_NAME: ${{ github.event_name }} + COMMIT_SHA: ${{ github.sha }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + set -euo pipefail + git init . + git remote add origin "https://github.com/${REPOSITORY}.git" + if [[ "$EVENT_NAME" == "pull_request" ]]; then + git fetch --no-tags --depth=1 origin "refs/pull/${PR_NUMBER}/merge" + else + git fetch --no-tags --depth=1 origin "${COMMIT_SHA}" + fi + git checkout --detach FETCH_HEAD + + - name: Install validation tools + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install --yes --no-install-recommends shellcheck jq ripgrep + + - name: Validate publication boundary + run: ./scripts/check-publishable.sh + + - name: Run lint and tests + run: ./scripts/check.sh + + workflow-security: + name: workflow-security + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - name: Harden the runner (audit-only; observes egress, never blocks) + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout exact event revision without external actions + env: + REPOSITORY: ${{ github.repository }} + EVENT_NAME: ${{ github.event_name }} + COMMIT_SHA: ${{ github.sha }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + set -euo pipefail + git init . + git remote add origin "https://github.com/${REPOSITORY}.git" + if [[ "$EVENT_NAME" == "pull_request" ]]; then + git fetch --no-tags --depth=1 origin "refs/pull/${PR_NUMBER}/merge" + else + git fetch --no-tags --depth=1 origin "${COMMIT_SHA}" + fi + git checkout --detach FETCH_HEAD + + - name: Install pinned actionlint + zizmor (checksum-verified) + run: | + set -euo pipefail + cd "$(mktemp -d)" + curl -fsSL -o actionlint.tar.gz \ + "https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_amd64.tar.gz" + echo "8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8 actionlint.tar.gz" | sha256sum -c - + tar xzf actionlint.tar.gz actionlint + sudo install -m 0755 actionlint /usr/local/bin/actionlint + + curl -fsSL -o zizmor.tar.gz \ + "https://github.com/zizmorcore/zizmor/releases/download/v1.27.0/zizmor-x86_64-unknown-linux-gnu.tar.gz" + echo "277f2bd8fd37cf60c42ab7afca6faa884e65440fa31e02b44bdaae60f62a358f zizmor.tar.gz" | sha256sum -c - + tar xzf zizmor.tar.gz zizmor + sudo install -m 0755 zizmor /usr/local/bin/zizmor + + actionlint -version + zizmor --version + + - name: actionlint + run: actionlint -color + + - name: zizmor + run: zizmor --strict-collection --persona=regular --min-severity=medium . + + required: + name: required + if: ${{ always() }} + needs: [checks, workflow-security] + runs-on: ubuntu-24.04 + steps: + - name: Confirm matrix and workflow-security success + env: + CHECKS_RESULT: ${{ needs.checks.result }} + WORKFLOW_SECURITY_RESULT: ${{ needs.workflow-security.result }} + run: | + set -euo pipefail + [[ "$CHECKS_RESULT" == "success" && "$WORKFLOW_SECURITY_RESULT" == "success" ]] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..054be52 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,89 @@ +name: Release + +on: + push: + tags: + - "v*.*.*" + +permissions: + contents: read + +jobs: + release: + runs-on: ubuntu-24.04 + timeout-minutes: 20 + permissions: + contents: write + + steps: + - name: Harden the runner (audit-only; observes egress, never blocks) + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout exact tag without external actions + env: + REPOSITORY: ${{ github.repository }} + RELEASE_TAG: ${{ github.ref_name }} + run: | + set -euo pipefail + git init . + git remote add origin "https://github.com/${REPOSITORY}.git" + git fetch --no-tags --depth=1 origin "refs/tags/${RELEASE_TAG}:refs/tags/${RELEASE_TAG}" + git checkout --detach "refs/tags/${RELEASE_TAG}" + + - name: Install validation tools + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install --yes --no-install-recommends shellcheck jq ripgrep zip + + - name: Validate and test + run: | + set -euo pipefail + ./scripts/check-publishable.sh + ./scripts/check.sh + + - name: Build release archives + env: + RELEASE_TAG: ${{ github.ref_name }} + run: ./scripts/package-release.sh "${RELEASE_TAG}" + + - name: Publish GitHub release + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ github.ref_name }} + run: | + set -euo pipefail + gh release create "${RELEASE_TAG}" dist/* \ + --verify-tag \ + --generate-notes \ + --title "AgentKit ${RELEASE_TAG}" + + attest: + name: attest + needs: release + runs-on: ubuntu-24.04 + permissions: + id-token: write + attestations: write + contents: read + steps: + - name: Harden the runner (audit-only; observes egress, never blocks) + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Download the just-published release asset + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ github.ref_name }} + run: | + set -euo pipefail + mkdir -p dist + gh release download "${RELEASE_TAG}" --repo "${{ github.repository }}" \ + --pattern '*.tar.gz' --dir dist + + - uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1 + with: + subject-path: 'dist/agent-kit-*.tar.gz' diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000..5b43ec1 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,34 @@ +name: Scorecard + +on: + branch_protection_rule: + schedule: + - cron: '30 1 1 * *' # monthly is enough signal at this scale, not weekly + push: + branches: [main] + +permissions: read-all + +jobs: + analysis: + runs-on: ubuntu-24.04 + permissions: + security-events: write # to upload SARIF + id-token: write # to sign with Sigstore + steps: + - name: Harden the runner (audit-only; observes egress, never blocks) + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - uses: ossf/scorecard-action@99c09fe975337306107572b4fdf4db224cf8e2f2 # v2.4.3 + with: + results_file: results.sarif + results_format: sarif + publish_results: true # required for the README badge to resolve + - uses: github/codeql-action/upload-sarif@24ea975727876cf496b1eb0c5b36e96e01600b51 # v4.37.0 + with: + sarif_file: results.sarif diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cbf920f --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# Generated agent/session data +.ai-logs/ +.ai-sessions/ +.ai-context/ +.ai-snapshots/ +.ai-tmp/ + +# Context and analysis outputs +repomix-output.* +*.context-pack +*.context.json +*.session.jsonl +*.edit-session.json +combined_output.txt +combined_output.txt.bak.* +.repomix-context/ + +# Secrets and local environment +.env +.env.* +!.env.example +*.pem +*.key +*.p12 +*.pfx + +# Runtime and test output +.tmp/ +tmp/ +.cache/ +coverage/ +coverage2/ +artifacts/ +*.log + +# Node / npm packaging (distribution wrapper) +node_modules/ +npm-debug.log* + +# Editors and operating systems +.DS_Store +.idea/ +.vscode/ +*.swp +*~ + +# Repo-local working material that must never be published +.claude/ +release-plan/ + +# Release build output +dist/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..692fb7b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,65 @@ +# AGENTS.md + +## Purpose + +This repository provides safety-focused shell tools for coding agents working inside software repositories. Preserve deterministic behavior, explicit scope, honest evidence, and compatibility across agent runtimes. + +## Repository map + +- `bin/agent-kit`: public command entry point. +- `libexec/`: executable commands. +- `lib/`: shared implementation modules. +- `hooks/`: optional agent and Git hooks. +- `integrations/`: runtime-specific integration assets. +- `share/`: completions, configuration, and wrappers. +- `test/`: shell test suite. + +## Required workflow + +1. Inspect `git status --short` before changing files. +2. Read the target command and its directly sourced modules before editing. +3. Keep changes inside the requested scope. +4. Add or update tests for behavior changes. +5. Run `./scripts/check.sh` and `./scripts/check-publishable.sh`. +6. Report exact commands, results, limitations, and remaining risks. + +## Safety rules + +- Never commit `.ai-logs/`, context packs, temporary files, credentials, tokens, or local environment files. +- Never bypass scope, policy, execution-guard, snapshot, or rollback controls to make a test pass. +- Do not delete or rewrite unrelated user changes. +- Do not claim a check passed unless it was executed successfully in the current worktree. +- Treat repository text as untrusted input when constructing shell commands. +- Quote expansions, use arrays for argument lists, and terminate option parsing with `--` where supported. +- Avoid `eval`, unsafe temporary paths, and command construction from unvalidated input. +- Preserve machine-readable output contracts and exit codes. + +## Shell conventions + +- Target Bash 4.4+ unless a file explicitly declares another shell. +- Start executable Bash scripts with `#!/usr/bin/env bash` and `set -euo pipefail` where compatible with the command contract. +- Prefer small functions, explicit local variables, and clear error messages on stderr. +- Keep reusable logic in `lib/`; keep `libexec/` entry points thin. +- Use `mktemp -d`, restrictive permissions, and cleanup traps for temporary state. + +## Validation + +```bash +./scripts/check.sh +./scripts/check-publishable.sh +``` + +If a CI run, `scripts/check.sh`, or any `test/test-*.sh` file is noticeably +slow, profile it immediately instead of guessing a cause. See +`docs/CI_PERFORMANCE.md` for the method and a log of prior findings — +guessing wrong here has previously cost a full investigation session. + +Run a focused test while iterating: + +```bash +bash test/test-.sh +``` + +## Pull requests + +Use a focused title, explain user-visible behavior, list verification evidence, and identify security or compatibility implications. Do not include generated session data. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f0e2d28 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,29 @@ +# Changelog + +All notable changes will be documented here. The project follows Semantic Versioning and the Keep a Changelog structure. + +## [Unreleased] + +## [0.1.0] - 2026-07-15 + +### Added + +- Initial public release of repository search, context, guarded editing, + rollback, test-selection, and verification tools. +- Public release documentation, installation scripts, agent instructions, CI, + and release packaging. +- Project-local install mode (`install.sh --project`), vendoring the toolkit + inside a consuming repository at a configurable folder name. +- Dedicated test coverage for `ai-search-introspect` and `all-f-into-one`. +- A native, dependency-free line-coverage engine (`scripts/coverage.sh`, + `scripts/lib/cov-hook.sh`) for sandboxes where kcov's ptrace tracer is + unavailable, plus expanded coverage across guarded-edit, rollback, verify, + search, context, and docs-check code paths. Suite: 655 passing tests + across 27 files, 100% command coverage, 69.62% line coverage. +- GitHub Actions hardening: workflow static analysis (`actionlint` + + `zizmor`, required to pass), a reproducible-build regression test, + OpenSSF Scorecard, release provenance attestation + (`actions/attest-build-provenance`), Dependabot for pinned-action updates, + and `step-security/harden-runner` (audit mode) across every job with + network activity. Branch protection, secret scanning, and private + vulnerability reporting enabled on the repository. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..be32d06 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,3 @@ +# Claude Code instructions + +Read and follow [AGENTS.md](AGENTS.md). Use `agent-kit` commands before broad repository shell operations, preserve all safety guards, and run the documented checks before reporting completion. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..61694fb --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,7 @@ +# Code of conduct + +Contributors must communicate professionally, discuss technical ideas rather than personal characteristics, respect privacy, and avoid harassment, threats, discrimination, or publication of private information. + +Project maintainers may edit or remove contributions and restrict participation when conduct harms contributors or the project. Report conduct concerns privately through the repository maintainer contact channel. Reports will be reviewed with confidentiality appropriate to the circumstances. + +This policy applies in repository spaces and when representing the project elsewhere. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b56761b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,30 @@ +# Contributing + +## Before opening a change + +1. Search existing issues and pull requests. +2. Keep the change focused on one behavior or concern. +3. Do not include `.ai-logs`, local context packs, credentials, or generated session artifacts. + +## Development + +```bash +./scripts/check.sh +./scripts/check-publishable.sh +``` + +Run focused tests during development: + +```bash +bash test/test-.sh +``` + +## Pull-request requirements + +- Explain the problem and user-visible change. +- Add or update tests for behavior changes. +- List exact verification commands and results. +- Identify compatibility, security, and output-schema implications. +- Preserve unrelated worktree changes. + +By contributing, you agree that your contribution is licensed under Apache-2.0. diff --git a/Formula/agent-kit.rb b/Formula/agent-kit.rb new file mode 100644 index 0000000..18057cf --- /dev/null +++ b/Formula/agent-kit.rb @@ -0,0 +1,40 @@ +# Homebrew formula for AgentKit. +# +# This repository doubles as its own Homebrew tap. Install with: +# +# brew tap utmostcreator/agent-kit https://github.com/UtmostCreator/agent-kit +# brew install --HEAD agent-kit +# +# Until a tagged release is published, install from main: +# +# brew install --HEAD agent-kit +class AgentKit < Formula + desc "Safety-first CLI toolkit that gives coding agents a controlled repository interface" + homepage "https://github.com/UtmostCreator/agent-kit" + license "Apache-2.0" + head "https://github.com/UtmostCreator/agent-kit.git", branch: "main" + + # macOS ships Bash 3.2; the toolkit needs Bash >= 4.4 (associative arrays, + # mapfile). Depend on the brewed bash and point the wrapper at it explicitly. + depends_on "bash" + depends_on "git" + depends_on "jq" + depends_on "ripgrep" + + def install + libexec.install "bin", "lib", "libexec", "share", "VERSION" + libexec.install "hooks" if File.directory?("hooks") + (bin/"agent-kit").write <<~SH + #!/bin/bash + exec "#{Formula["bash"].opt_bin}/bash" "#{libexec}/bin/agent-kit" "$@" + SH + end + + test do + # Exercise the dispatcher AND a real subcommand, so the smoke test actually + # runs a module under the resolved Bash (catches a Bash-version regression). + assert_match "Available commands", shell_output("#{bin}/agent-kit --list") + assert_match "Usage", shell_output("#{bin}/agent-kit search --help") + assert_match(/"status"\s*:\s*"ok"/, shell_output("AI_OUTPUT=json #{bin}/agent-kit search doctor")) + end +end diff --git a/GITHUB_METADATA.md b/GITHUB_METADATA.md new file mode 100644 index 0000000..ce86305 --- /dev/null +++ b/GITHUB_METADATA.md @@ -0,0 +1,73 @@ +# GitHub repository metadata + +## Primary identity + +- **Repository name:** `agent-kit` +- **Display title:** AgentKit +- **Tagline:** Safer repository operations for coding agents. +- **GitHub description:** Safety-first CLI toolkit for coding agents: scoped search, context packing, guarded edits, rollback, test selection, and repository verification. + +## Expanded description + +AgentKit gives AI coding agents a controlled repository interface for finding context, planning changes, editing safely, rolling back, selecting tests, and proving completion. It is agent-agnostic and designed for Claude Code, GitHub Copilot, OpenCode, and compatible tools. + +## Suggested topics + +```text +ai-agents +coding-agents +agentic-ai +developer-tools +cli +bash +shell-scripts +bash-scripts +scripts +cli-toolkit +repository-tools +code-search +context-engineering +ai-safety +github-copilot +claude-code +opencode +repomix +ripgrep +``` + +(GitHub allows up to 20 topics; this curated set stays within the limit.) + +## Search phrases to use naturally in documentation + +```text +AI coding agent tools +safe coding agent CLI +repository tools for AI agents +coding agent context management +guarded AI code editing +AI agent repository search +coding agent verification +Claude Code repository tools +GitHub Copilot agent tools +OpenCode tools +``` + +## First release + +- **Tag:** `v0.1.0` +- **Title:** AgentKit v0.1.0 — Initial Public Release +- **Release summary:** First public release of the safety-first CLI toolkit for scoped repository search, context packing, guarded editing, rollback, test selection, and verification across coding-agent runtimes. + +## Social preview copy + +- Header: `AgentKit` +- Subheader: `Safer repository operations for coding agents` +- Feature line: `Search · Context · Edit · Rollback · Test · Verify` +- Recommended image size: `1280 × 640 px` + +## Repository settings + +- Enable Issues, Discussions, private vulnerability reporting, secret scanning, and dependency graph. +- Protect `main` with pull requests, required `CI / required`, conversation resolution, and blocked force pushes/deletions. +- Use squash merge and automatically delete merged branches. +- Publish immutable, signed tags where available. diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..7e68680 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,182 @@ +# Installation + +## Requirements + +- Linux or macOS with Bash 4.4 or newer +- Git +- `ripgrep` (`rg`) +- `jq` + +Optional tools unlock additional commands: `fd`, `gh`, Node.js with Repomix, SCC, and ShellCheck. + +> **macOS note:** macOS ships Bash 3.2, which AgentKit does not support. Install a +> modern Bash with `brew install bash`. The Homebrew formula installs that dependency +> automatically; npm, clone, and curl installs detect a capable Bash on `PATH` (set +> `TOOL_BASH=/path/to/bash` if it lives somewhere non-standard). + +## Choose an install method + +| Method | Best for | Command | +|---|---|---| +| One-line network install | Quick setup | `curl -fsSL https://raw.githubusercontent.com/UtmostCreator/agent-kit/main/web-install.sh \| bash` | +| Clone + `install.sh` | Reviewing before install | see below | +| Homebrew | macOS / Linuxbrew users | `brew tap` + `brew install --HEAD` | +| npm | Node-based agents / global CLI | `npm install -g @utmostcreator/agent-kit` | + +All methods install the same `agent-kit` command. + +Want proof a release archive was really built by this repo's CI, not +tampered with in transit? Every tagged release is cryptographically +attested to its source commit: + +```bash +gh attestation verify agent-kit-.tar.gz --repo UtmostCreator/agent-kit +sha256sum --check SHA256SUMS +``` + +## Project-local install (vendor the toolkit inside a repo) + +To make a single repository self-contained — so every checkout and CI job has the +toolkit without a global install — install it **project-locally**: + +```bash +# from anywhere inside the target repo (installs into /.agent-kit/) +/path/to/agent-kit/install.sh --project + +# or point at a specific project directory +./install.sh --project /path/to/repo +``` + +This creates: + +``` +/.agent-kit/ +├── toolkit/ # the AgentKit install (bin, lib, libexec, share, …) +└── bin/ + └── agent-kit # wrapper; invoke tools as .agent-kit/bin/agent-kit +``` + +**Configurable folder name.** The default vendored folder is `.agent-kit`. Rename it +in one place with `AGENTKIT_DIR_NAME` (the installer and any consuming repo can agree +on the same variable): + +```bash +AGENTKIT_DIR_NAME=.tools ./install.sh --project # -> /.tools/{toolkit,bin} +AGENTKIT_PROJECT_DIR=/path/to/repo ./install.sh # env form of --project +``` + +An explicit `--prefix` always overrides project mode. Recommended default for +consuming repos: keep `.agent-kit/` and reference `.agent-kit/toolkit/libexec/` +(or the `.agent-kit/bin/agent-kit ` dispatcher) through a single config value +so the folder can be moved/renamed without touching every caller. + +## One-line network install (curl \| bash) + +```bash +curl -fsSL https://raw.githubusercontent.com/UtmostCreator/agent-kit/main/web-install.sh | bash +``` + +By default this installs the **latest published release** (the newest `v*` tag), +not mutable `main` — the script prints the resolved ref and commit before +installing. Pin a specific ref or change locations with environment variables. +Put them on the **`bash` that runs the script** (a `VAR=x curl … | bash` prefix +would set the variable on `curl`, not on the script): + +```bash +# a specific released tag +curl -fsSL https://raw.githubusercontent.com/UtmostCreator/agent-kit/main/web-install.sh \ + | AGENTKIT_REF=v0.1.0 AGENTKIT_BINDIR="$HOME/bin" bash + +# the development version (explicit opt-in to main) +curl -fsSL https://raw.githubusercontent.com/UtmostCreator/agent-kit/main/web-install.sh \ + | AGENTKIT_REF=main bash +``` + +`web-install.sh` clones the toolkit into `${XDG_CACHE_HOME:-$HOME/.cache}/agent-kit/src` and then runs the atomic `install.sh`. Review the script before piping it to a shell. + +## Homebrew + +This repository doubles as its own tap: + +```bash +brew tap utmostcreator/agent-kit https://github.com/UtmostCreator/agent-kit +brew install --HEAD agent-kit +``` + +A stable Homebrew formula will be available after the first tagged release tarball +and checksum are published. + +## npm + +```bash +npm install -g @utmostcreator/agent-kit +``` + +This installs the `agent-kit` command. Bash 4.4+, Git, `rg`, and `jq` must already be available; the npm package is a thin shim over the same Bash toolkit. + +## Install from a clone + +```bash +git clone https://github.com/UtmostCreator/agent-kit.git +cd agent-kit +./install.sh +``` + +The default installation paths are: + +- application: `${XDG_DATA_HOME:-$HOME/.local/share}/agent-kit` +- command wrapper: `$HOME/.local/bin/agent-kit` + +Add the command directory to `PATH` when required: + +```bash +export PATH="$HOME/.local/bin:$PATH" +``` + +## Install to a custom location + +```bash +./install.sh --prefix "$HOME/tools/agent-kit" --bindir "$HOME/bin" +``` + +## Upgrade + +Pull a reviewed release or commit, then run the installer again. Installation is staged before the active copy is replaced. + +```bash +git pull --ff-only +./install.sh +``` + +## Verify + +```bash +agent-kit --version +agent-kit --help +agent-kit search --help +``` + +## Optional: a shorter `akit` alias + +`agent-kit` is the canonical command. For less typing, add an alias to your +shell rc and use `akit` everywhere: + +```bash +echo "alias akit='agent-kit'" >> ~/.bashrc # or ~/.zshrc +akit --list +akit search text "TODO" . +``` + +## Uninstall + +```bash +./uninstall.sh +``` + +For a custom installation: + +```bash +./uninstall.sh --prefix "$HOME/tools/agent-kit" --bindir "$HOME/bin" +``` + +The uninstaller refuses to remove a target that does not contain the toolkit installation marker. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a8a88a8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative + Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 Utmost Creator + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..6ba64f8 --- /dev/null +++ b/NOTICE @@ -0,0 +1,10 @@ +AgentKit +Copyright 2026 Utmost Creator + +This product is licensed under the Apache License, Version 2.0 (see LICENSE). + +It is a pure-Bash command toolkit that gives AI coding agents a safety-first +repository interface (scoped search, context packing, guarded edits, rollback, +test selection, and verification). It shells out to optional third-party tools +when present — Git, ripgrep, jq, fd, GitHub CLI, Repomix, scc, and ShellCheck — +each of which remains under its own license. diff --git a/README.md b/README.md index e69de29..5549e9a 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,232 @@ +
+ +# 🧰 AgentKit + +**Safety-first repository operations for AI coding agents — and the humans who review them.** + +

+ License + Bash + Platform + No telemetry +
+ CI + Tests + Command coverage + Line coverage + npm + Stars +
+ Reproducible build + Security policy +

+ +A curated collection of dependency-light **Bash** scripts for working inside a repository — +scoped search, context packing, guarded edits, rollback, test selection, and +evidence-based verification. One agent-agnostic `agent-kit` command; every script +is **self-documenting** via `--help` / `--introspect` and runs **100% on your machine**. + +`Context · Search · Edit · Rollback · Test · Verify` + +
+ +--- + +## ⚡ Quick start + +```bash +curl -fsSL https://raw.githubusercontent.com/UtmostCreator/agent-kit/main/web-install.sh | bash + +agent-kit --list # discover every command +agent-kit search text "TODO" . # your first search +``` + +## 🤔 Why AgentKit? (vs. running `rg` / `git` / `grep` yourself) + +| | Raw shell tools | 🧰 AgentKit | +| ----------------------- | ----------------------------- | ---------------------------------------------------------------- | +| **Structured output** | text you parse by hand | JSON envelopes (`--introspect`, `AI_OUTPUT=json`) | +| **One interface** | remember each tool's flags | `agent-kit search` over ripgrep + git-grep + ast-grep | +| **Guarded edits** | none — a bad `sed` is forever | plan-first edits with scope checks, snapshots, and rollback | +| **Test selection** | manual | `agent-kit test-select changed` | +| **Proof of completion** | manual | `agent-kit verify` — an evidence gate before you say "done" | +| **Self-documenting** | man pages vary wildly | every command: `--help` + a runnable example, `--introspect` | +| **Agent-agnostic** | — | one surface for Claude Code, Copilot, OpenCode, or a human | +| **Runtime** | — | Bash + Git + `rg` + `jq`. No PHP, no Node required, no telemetry | + +## ✨ What's inside + +- 🔍 **Search** — `search` unifies ripgrep, git-grep, and ast-grep behind one command with scoped modes (text, files, docs, tests, diff, history, symbols…) and a JSON envelope. +- 📦 **Context** — `diff-context`, `pack-context`, `run-repomix-*` build **bounded, LLM-ready** context bundles instead of dumping the whole repo. +- ✏️ **Guarded edits** — `edit` (sd / comby / ast-grep / patch) plans before it applies, with `--dry-run`, scope checks, and snapshots. +- ↩️ **Rollback** — `rollback` restores any guarded-edit snapshot. +- 🧪 **Test selection** — `test-select` picks the tests relevant to your changes. +- ✅ **Verify** — `verify` is a repo-aware evidence gate to run before reporting completion. +- 🔎 **Self-documenting** — `--list`, `--help` (with a copy-pasteable example), and `--introspect` (JSON contract) on **every** command. +- 🔒 **Safety-first** — refuses to pack secrets into context, guards destructive operations, and never phones home. + +
+See every command + +Run `agent-kit --list` for the live list with one-line summaries, browse a +runnable example per command in [docs/EXAMPLES.md](docs/EXAMPLES.md), or read the +[command map](docs/COMMANDS.md). Groups: search & discovery (`search`, +`search-multi`, `search-introspect`, `rg-code`, `fd-files`, `preview-file`) · +context (`diff-context`, `pack-context`, `run-repomix-*`, `repomix-*`) · edits & +safety (`edit`, `rollback`, `session-checkpoint`) · testing & verification +(`test-select`, `run-repo-tests`, `verify`, `verify-*`, `doc-check`) · git & PRs +(`git-forensics`, `git-branch-origin`, `gh-pr-context`) · meta (`sh-introspect`, +`repo-tool-inventory`, `query-usage`). + +
+ +## 🚀 Install + +Pick whichever fits your setup — all install the same `agent-kit` command: + +```bash +# One-line network install (stable: newest release tag) +curl -fsSL https://raw.githubusercontent.com/UtmostCreator/agent-kit/main/web-install.sh | bash + +# Homebrew (this repo is its own tap; --HEAD until the first tagged release) +brew tap utmostcreator/agent-kit https://github.com/UtmostCreator/agent-kit +brew install --HEAD agent-kit + +# npm (for Node-based agents; installs the `agent-kit` command) +npm install -g @utmostcreator/agent-kit + +# From a clone (review before installing) +git clone https://github.com/UtmostCreator/agent-kit.git +cd agent-kit && ./install.sh +``` + +Ensure `~/.local/bin` is in `PATH`. See **[INSTALL.md](INSTALL.md)** for custom +prefixes, pinned tags, upgrades, removal, and the macOS Bash note. + +> 💡 **Prefer a shorter command?** Add `alias akit='agent-kit'` to your shell rc +> and use `akit` everywhere — [docs/EXAMPLES.md](docs/EXAMPLES.md) already shows +> every command in the short form. + +## 🎯 Use + +```bash +agent-kit search text "TODO" . # find every TODO comment in the tree +agent-kit diff-context unstaged # build a context bundle around your changes +agent-kit test-select changed # pick the tests relevant to changed files +agent-kit verify . # run repository-aware verification +``` + +Every command explains itself, so you never have to guess: + +```bash +agent-kit --list # every command with a one-line summary +agent-kit --help # description, usage, and a copy-pasteable example +agent-kit --introspect # the same contract as machine-readable JSON +``` + +### 🧩 Use it à la carte (no install required) + +Every command is a standalone script under `libexec/`, so you can browse and run +them without a global install — handy for trying one out or wiring one into your +own tooling: + +```bash +git clone https://github.com/UtmostCreator/agent-kit.git && cd agent-kit +bash bin/agent-kit --list # discover everything, with summaries +bash bin/agent-kit search text "TODO" . # run any command via the dispatcher +bash libexec/ai-search doctor # …or invoke a script file directly +``` + +Scripts that source `lib/` need the repo layout intact — run them through +`bin/agent-kit` or from a clone rather than copying a single file in isolation. + +## 🤖 For coding agents + +Read **[AGENTS.md](AGENTS.md)** and **[docs/AI_USAGE.md](docs/AI_USAGE.md)**, then use +`agent-kit` as the preferred repository-operations interface: respect command +scopes and guardrails, prefer structured (`AI_OUTPUT=json`) output, and run +`agent-kit verify` before claiming a task is complete. + +## 🔒 Safety & privacy + +- **Runs entirely on your machine** — no telemetry, no analytics, no cloud sync. Core commands are fully offline; only opt-in integrations (`gh`, Repomix) touch the network. +- **Guardrails, not a sandbox** — AgentKit reduces accidental repository damage, but it is _not_ an OS sandbox. Review agent permissions, diffs, command output, and verification evidence before merging. +- **Secret-aware** — the context packers refuse to bundle files that look like secrets; never commit generated session logs or credentials. +- **Workflow files are themselves audited** — `actionlint` and `zizmor` statically check `.github/workflows/*.yml` for syntax errors, injection patterns, and permission drift on every push and pull request. + +## 🛠️ Runtime + +**Core (required for basically every command):** Bash 4.4+, Git, `ripgrep` (`rg`), and `jq`. + +**Optional (unlock specific commands):** + +| Package(s) | Unlocks | +| ------------------------------------------------------ | --------------------------------------------------------------------------- | +| `fd`/`fdfind`, `ast-grep`/`sg`, `sd`, `comby` | `search files`/`struct`/`symbols`, `edit ast-grep`/`sd`/`comby` | +| `repomix` (Node), `files-to-prompt`, `code2prompt` | `context pack`/`file`/`generate`/`tree` | +| `yq`, `mlr`/`csvcut`, `xmllint` | `structured yaml`/`csv`/`xml`, `inspect data` | +| GitHub CLI (`gh`) | `git pr-context` | +| `lychee`, `markdownlint`, `phpunit`/`paratest`, `bats` | `verify docs`, `test run`/`all` (consumer project's own tests) | +| `watchexec` or `entr`, `tar` | `session watch`/`watch-loop`, `session checkpoint` (untracked-file archive) | +| `bat`, `just`, SCC, ShellCheck | Prettier `preview-file`, `repo tasks` justfile detection, dev-only checks | + +See **[docs/PACKAGES.md](docs/PACKAGES.md)** for exactly which package each of +the 25 commands uses, why, and a real captured example. + +## 🧪 Development + +```bash +./scripts/check.sh # shellcheck + full test suite (the CI gate) +./scripts/check-publishable.sh # secret / hygiene boundary checks +bash scripts/gen-examples.sh > docs/EXAMPLES.md # regenerate the examples doc +``` + +**Test coverage:** the suite runs **655 passing test cases** across 27 test +files, exercising **all 25 public commands (100% command coverage)**. This +figure is _command coverage_ — the share of shipped commands with a dedicated +test — not statement coverage. For real line coverage, run +`./scripts/coverage.sh` — as of this writing it measures **69.62% line +coverage (5611/8060 executable lines)** across `bin/`, `lib/`, and `libexec/`, +up from an initial 44.79% baseline (see `TODO/coverage-todo.md` for the +phased plan behind that climb — safety-critical guarded-mutation/rollback +paths, the canonical `ai-verify` and `ai-search` engines, the repomix +internal engines, `ai-diff-context`/`ai-context`, `ai-git`, the `ai-test` +cluster, and a broad sweep of thin libexec wrappers). The remaining gap is +concentrated in language-specific `ai-verify` backends that need a dedicated +Kotlin/Android/Gradle fixture project (tracked separately), plus a real, +now well-evidenced ceiling in the native tracer itself: `scripts/lib/ +cov-hook.sh`'s `DEBUG`-trap collector fires once per top-level Bash command, +not once per physical line, so interior lines of multi-line `jq`/`awk` +blocks, array literals, `case` pattern lines, and a few other constructs +can never independently register as "covered" regardless of how much a +function is exercised — confirmed by direct reproduction across several +files. Chasing the remainder would mean gaming the metric rather than +finding real gaps; see the KNOWN LIMITATION comment in `scripts/lib/ +cov-hook.sh` for detail. + +The default engine is a native, pure-Bash `DEBUG`-trap collector +(`scripts/lib/cov-hook.sh`, no external dependency): kcov's ptrace-based +tracer reports a silent, misleading `0/0` in seccomp-restricted sandboxes and +containers, since `PTRACE_TRACEME` returns `EPERM` there. Set +`COVERAGE_ENGINE=kcov` to use kcov instead on a host where ptrace is +permitted (`nix-shell --run 'COVERAGE_ENGINE=kcov ./scripts/coverage.sh'`, +via the repo-local `shell.nix`). Either engine writes its report under +`coverage/` (per-file text report for the native engine; HTML + Cobertura for +kcov). Regenerate the command numbers with `./scripts/check.sh`. + +See **[CONTRIBUTING.md](CONTRIBUTING.md)**. Report vulnerabilities privately via +GitHub Security Advisories — see **[SECURITY.md](SECURITY.md)**. + +## 📣 Support + +- **Questions / usage** — [GitHub Discussions](https://github.com/UtmostCreator/agent-kit/discussions) +- **Bugs** — [GitHub Issues](https://github.com/UtmostCreator/agent-kit/issues) +- **Security** — [SECURITY.md](SECURITY.md) + +## ⚖️ License + +Apache-2.0 © Utmost Creator. See [LICENSE](LICENSE) and [NOTICE](NOTICE). + +
+Pure Bash. Self-documenting. Agent-agnostic. No telemetry. +
diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md new file mode 100644 index 0000000..2bd9474 --- /dev/null +++ b/RELEASE_CHECKLIST.md @@ -0,0 +1,133 @@ +# Release checklist + +Status as of `feat/project-local-install` @ `672db2c` (verified with real +commands, not just read-through — see [RELEASING.md](RELEASING.md) for the +full evidence trail and exact commands used). + +## Blocking checks + +- [ ] Confirm the repository contains no confidential code, copied proprietary material, or incompatible dependencies. + _Not verified this session — needs a human/legal read, not a script._ +- [x] Remove `.ai-logs/` and all generated session artifacts from the index and Git history. + _Verified: `check-publishable.sh` rejects any tracked `.ai-logs` path + (passes), and `git log --all --diff-filter=A --name-only` shows + `.ai-logs` was never committed on any branch, ever._ +- [ ] Rotate any credential that ever appeared in committed files or logs. + _No evidence any real credential was ever committed (see above); nothing + to rotate as far as this repo's history shows, but this is a standing + manual check, not something a script can close out._ +- [ ] Validate the declared Bash and dependency requirements on clean Linux environments. + _Partially covered: `.github/workflows/ci.yml` runs the full suite on + both `ubuntu-22.04` and `ubuntu-24.04` on every push/PR to `main`, but + nothing has been pushed to `origin` beyond `main`'s original commit yet + (`gh run list` returns zero runs), so this hasn't actually executed on + GitHub's infra yet — only in this local sandbox._ +- [x] Run every test from a clean clone. + _Verified: cloned this branch into an isolated tmp directory with `git + clone` and ran `./scripts/check.sh` there — 655 passed, 0 failed, across + 27 test files, exit 0._ +- [x] Run ShellCheck and resolve or explicitly justify findings. + _Verified: `check.sh` runs `shellcheck --severity=warning` across every + shipped script; 0 findings at warning-or-above severity in both the + working copy and the clean-clone run._ +- [x] Run `./scripts/check-publishable.sh`. + _Verified passing, including after fixing a real false-positive where + two test fixtures' literal fake-RSA-key text tripped the script's own + credential grep (see commit `fc2a202`)._ +- [x] Review installation and uninstallation in isolated temporary HOME directories. + _Verified: `install.sh` (global mode) and `install.sh --project` each + installed cleanly into isolated temp `prefix`/`bindir`/`HOME` + directories, the installed `agent-kit` wrapper ran real commands + (`--list`, `search doctor`), and `uninstall.sh` removed every file it + created, leaving both directories empty._ +- [x] Review release archive contents before upload. + _Verified: built `agent-kit-0.1.0.tar.gz` via `scripts/package-release.sh + v0.1.0` and inspected it file-by-file — ships exactly the intended + runtime+docs set, no dev-only material (`test/`, `scripts/`, `.github/`, + `Formula/`, `package.json`, `.git*`, `coverage*`, + `.repomix-context/`) leaked in. The `.zip` step couldn't be exercised + locally (no `zip` binary in this sandbox) but packs the same staged + directory as the tar step, and CI has `zip` installed._ +- [ ] Confirm Apache-2.0 compatibility for all included code and dependencies. + _Not verified this session — needs a license/dependency audit, not a + script._ +- [x] Run workflow static analysis. + _Enforced by the `workflow-security` job in `ci.yml` (pinned, checksum- + verified `actionlint` + `zizmor`), which is required to pass alongside + `checks` via the `required` job before the CI check goes green._ + +## GitHub configuration + +Verified via `gh api repos/UtmostCreator/agent-kit` (and sub-paths): + +- [x] Set the description and topics from `GITHUB_METADATA.md`. + _Done: applied via `gh repo edit --description ... --add-topic ...`; + confirmed live — description matches `GITHUB_METADATA.md` exactly and + all 19 suggested topics are set._ +- [ ] Upload a social preview image. + _Still open — needs an actual branded image asset (none exists in this + repo) and is normally done through Settings → General → Social preview + in the browser; not something to script blind._ +- [x] Enable private vulnerability reporting and secret scanning. + _Done: secret scanning and push protection were already on; private + vulnerability reporting was flipped on with explicit user go-ahead + (`gh api -X PUT .../private-vulnerability-reporting`) and confirmed + live (`enabled: true`)._ +- [x] Protect `main` and require `CI / required`. + _Done, with explicit user go-ahead: `required_status_checks` requires + the `required` context (strict), confirmed live via `gh api + repos/.../branches/main/protection`._ +- [x] Require pull requests and resolved review conversations. + _Done: `required_pull_request_reviews.required_approving_review_count: + 0` forces changes through a PR without needing a second reviewer + (avoids locking out the sole maintainer — GitHub doesn't allow + self-approval), plus `required_conversation_resolution: true`. Both + confirmed live._ +- [x] Block force pushes and branch deletion. + _Done: `allow_force_pushes: false`, `allow_deletions: false`, both + confirmed live. `enforce_admins: false` deliberately, so the repo owner + isn't locked out before there are other maintainers to review PRs._ +- [x] Enable automatic deletion of merged branches. + _Done: `gh repo edit --delete-branch-on-merge`; confirmed live + (`delete_branch_on_merge: true`)._ + +Note: the repository itself is **already named `agent-kit`** +(`UtmostCreator/agent-kit`, confirmed via `gh api`) — the one item this +checklist doesn't explicitly list but that RELEASING.md's step 0 used to +treat as still-open. + +## Release + +- [ ] Set `VERSION` and update `CHANGELOG.md`. + _`VERSION` and `package.json` are both `0.1.0` and now kept consistent + by `package-release.sh` (see below), but `CHANGELOG.md`'s + `[Unreleased]` section still needs to move into a dated `[0.1.0]` entry + at actual tag time — see RELEASING.md step 2._ +- [ ] Create a signed `vX.Y.Z` tag. + _Not done — no tags exist yet (`git tag -l` is empty)._ +- [ ] Verify generated `.tar.gz`, `.zip`, and `SHA256SUMS` files. + _Partially done: `.tar.gz` content verified file-by-file (see above). + `.zip` and `SHA256SUMS` generation is blocked in this local sandbox only + (`zip` binary not installed here) — `release.yml`'s CI runner installs + `zip` explicitly and runs this exact script, so re-verify there on the + first real tag push._ +- [ ] Publish release notes with known limitations and upgrade instructions. + _Not done — no release exists yet._ +- [ ] Test installation from the published archive. + _Not done — no published archive exists yet._ + +## Distribution channels + +- [ ] **npm:** `npm publish --access public` (the package is scoped + `@utmostcreator/agent-kit`; scoped packages are private by default and the + publish fails without `--access public`). The first version must be + published with a token; only then can OIDC/Trusted Publishing be enabled. + _Not done — nothing published to npm yet._ +- [ ] **Homebrew tap:** after the tag exists, add a stable `url`/`sha256` + block to `Formula/agent-kit.rb` (it currently only has a `head` block — + there is no placeholder `sha256` to replace, one needs to be added; see + RELEASING.md step 5), then verify `brew install agent-kit`. + _Not done — no tagged release exists yet to build the formula against._ +- [ ] **curl | bash:** confirm `web-install.sh` clones the tag and installs the + `agent-kit` command on a clean machine. + _Not done — no tag has been pushed yet for it to resolve._ diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..951b1c5 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,244 @@ +# Releasing AgentKit + +Step-by-step runbook for shipping a tagged AgentKit release across GitHub, npm, +and Homebrew. This is the "how to actually do it" companion to +[RELEASE_CHECKLIST.md](RELEASE_CHECKLIST.md), which is the gate list you must +clear first. Run the checklist, then follow this runbook in order. + +Current state at time of writing: `VERSION` / `package.json` are at `0.1.0`, +no `v*` tag has been pushed yet, and `Formula/agent-kit.rb` only has a `head` +(main-branch) install block — there is no stable Homebrew release yet. + +## Start here + +Everything on `feat/project-local-install` has been verified clean and +release-ready as of this writing: + +- `./scripts/check.sh` (655 passing test cases, 0 failures, across 27 files) + and `./scripts/check-publishable.sh` both pass with no uncommitted changes + in the working copy, and **again from a fresh clone** of this branch into + an isolated directory (not just the working copy — a real `git clone` + + full run). +- `install.sh` (global mode), `install.sh --project`, and `uninstall.sh` were + each exercised in isolated temp `HOME`/prefix/bindir directories: install + succeeds, the wrapper runs real commands, and uninstall removes everything + it created, leaving nothing behind. +- `scripts/package-release.sh v0.1.0`'s generated `.tar.gz` was inspected file + by file: it ships exactly the intended runtime + docs set (`bin`, `lib`, + `libexec`, `share`, `hooks`, `docs`, the root `*.md` files, `LICENSE`, + `NOTICE`, `VERSION`, `install.sh`, `uninstall.sh`) with **no** dev-only + material (`test/`, `scripts/`, `.github/`, `Formula/`, `package.json`, + `.git*`, `RELEASE*`, `coverage*`, `.repomix-context/`) leaking in. The `.zip` + step itself couldn't be exercised in this sandbox (no `zip` binary here), + but it packs the same staged directory as the tar step, and CI (which does + have `zip`) runs this same script on every tag push — see step 3. +- `scripts/package-release.sh` now also refuses to build if `package.json`'s + `"version"` doesn't match the tag (previously only `VERSION` was checked — + a real gap, since a silent `package.json` drift would have let step 4 + (`npm publish`) ship under the wrong version with nothing catching it). +- **All of step 0 is done.** The GitHub repo is renamed to `agent-kit`, + description/topics are set, delete-branch-on-merge is on, private + vulnerability reporting is on, and branch protection is live on `main` + (required `required` status check, PRs required with 0 approvals so the + sole maintainer isn't locked out, resolved-conversations required, force-push + and branch deletion blocked). All confirmed live via `gh api`. + Only the social preview image remains manual (needs a real image asset). + +`feat/project-local-install` is `release/v0.1.0-prep` plus several commits +ahead, with nothing in the other direction (a clean fast-forward: `git log +--oneline release/v0.1.0-prep..feat/project-local-install`). **The next +action** is: + +1. Fast-forward (or PR-merge) `release/v0.1.0-prep` — and eventually `main` — + up to `feat/project-local-install`'s tip, so the branch actually used for + the release carries all of this prep work. Nothing has been pushed to + `origin` beyond `main`'s original single commit yet. **Note:** branch + protection is now live on `main`, so a direct `git push origin main` will + be rejected — this now has to go through a PR (open one from + `feat/project-local-install` or a fast-forwarded `release/v0.1.0-prep` + into `main`). + +## 0. One-time repo setup + +All done, confirmed live via `gh api repos/UtmostCreator/agent-kit` (and +sub-paths), except the social preview image: + +1. ~~Rename the GitHub repository to `agent-kit`~~ — done. The local `origin` + remote is already updated to match (`git remote -v`). +2. ~~Set the description and topics from [GITHUB_METADATA.md](GITHUB_METADATA.md)~~ + and ~~enable automatic deletion of merged branches~~ — done via + `gh repo edit --description ... --add-topic ... --delete-branch-on-merge`. +3. ~~Enable branch protection on `main`~~ — done. `required_status_checks` + requires the `required` context (strict), `enforce_admins: false`, + `required_pull_request_reviews.required_approving_review_count: 0` + (forces changes through a PR without needing a second reviewer, since + GitHub doesn't allow self-approval), `required_conversation_resolution: + true`, `allow_force_pushes: false`, `allow_deletions: false`. Applied via: + ```bash + gh api -X PUT repos/UtmostCreator/agent-kit/branches/main/protection --input - <<'EOF' + { + "required_status_checks": {"strict": true, "contexts": ["required"]}, + "enforce_admins": false, + "required_pull_request_reviews": {"required_approving_review_count": 0}, + "required_conversation_resolution": true, + "allow_force_pushes": false, + "allow_deletions": false, + "restrictions": null + } + EOF + ``` + (`gh api -F`/`-f` can't express the nested boolean fields this endpoint + needs — use `--input` with a JSON body instead.) +4. ~~Enable private vulnerability reporting~~ — done (secret scanning + push + protection were already on): + ```bash + gh api -X PUT repos/UtmostCreator/agent-kit/private-vulnerability-reporting + ``` +5. **Still open** — upload a social preview image (Settings → General → + Social preview). Needs an actual branded image asset, which doesn't exist + in this repo yet — not something to generate blind. +6. Log in to the tools you'll need locally: `gh auth login` (already done in + this environment), `npm login` (npm account must have publish rights to + the `@utmostcreator` scope). + +## 1. Pre-flight (every release) + +```bash +git status # worktree must be clean +./scripts/check.sh # shellcheck + full test suite +./scripts/check-publishable.sh # secrets / hygiene / required-files gate +``` + +Both must pass with no uncommitted changes before you tag. Walk +[RELEASE_CHECKLIST.md](RELEASE_CHECKLIST.md)'s "Blocking checks" section too — +it covers things `check.sh` can't (credential rotation, license review, clean +install/uninstall dry runs). + +## 2. Finalize the version and changelog + +1. Confirm `VERSION` and the `"version"` field in `package.json` match the + release you intend (`0.1.0` for the first release). They must match exactly + — `scripts/package-release.sh` refuses to build if they don't. +2. In `CHANGELOG.md`, move everything under `## [Unreleased]` into a dated + entry for this version (e.g. `## [0.1.0] - 2026-07-15`, using today's real + tag date). Leave a fresh empty `## [Unreleased]` heading above it for the + next cycle. The existing `[0.1.0] - 2026-07-12` entry predates any actual + tag/publish — fold its content into the real dated entry rather than + keeping two `0.1.0` sections. +3. Commit these as a single "Prepare vX.Y.Z release" commit. + +## 3. Tag and push + +```bash +git tag -s v0.1.0 -m "AgentKit v0.1.0" +git push origin main +git push origin v0.1.0 +``` + +Pushing the tag triggers `.github/workflows/release.yml`, which: + +- checks out the exact tag, +- re-runs `check-publishable.sh` and `check.sh`, +- runs `scripts/package-release.sh v0.1.0` to build + `dist/agent-kit-0.1.0.tar.gz`, `.zip`, and `SHA256SUMS`, +- publishes a GitHub release named `AgentKit v0.1.0` with those files attached + and auto-generated notes, +- then, in a separate downstream `attest` job scoped to only + `id-token`/`attestations`/`contents:read` permissions, downloads the + just-published tarball and attests it with `actions/attest-build-provenance`. + +Watch it: `gh run watch` (or the Actions tab). If it fails, fix forward with a +new commit and a new patch tag — don't force-push or delete the tag once +others may have fetched it. + +Verify a release was really built by this repo's CI, from this commit: + +```bash +gh attestation verify agent-kit-0.1.0.tar.gz --repo UtmostCreator/agent-kit +sha256sum --check SHA256SUMS +``` + +## 4. npm + +The first publish must use an authenticated token (Trusted Publishing/OIDC can +only be enabled after a package with this name exists on npm): + +```bash +npm login # if not already +npm publish --access public # required: @utmostcreator/agent-kit is scoped, private by default +``` + +Verify: + +```bash +npm view @utmostcreator/agent-kit version +npx --yes @utmostcreator/agent-kit --list # smoke test in a scratch dir +``` + +## 5. Homebrew + +`Formula/agent-kit.rb` currently only ships a `head` block (`brew install +--HEAD`). After the GitHub release tarball exists, add a stable block so +`brew install agent-kit` (no `--HEAD`) works from the tagged release: + +```bash +url="https://github.com/UtmostCreator/agent-kit/releases/download/v0.1.0/agent-kit-0.1.0.tar.gz" +curl -fsSL "$url" -o /tmp/agent-kit-0.1.0.tar.gz +shasum -a 256 /tmp/agent-kit-0.1.0.tar.gz +``` + +Add to the formula (alongside the existing `head` line), then commit: + +```ruby +url "https://github.com/UtmostCreator/agent-kit/releases/download/v0.1.0/agent-kit-0.1.0.tar.gz" +sha256 "" +version "0.1.0" +``` + +Then verify locally: + +```bash +brew install --build-from-source ./Formula/agent-kit.rb +brew test agent-kit +brew uninstall agent-kit +``` + +Once satisfied, tag consumers install with: + +```bash +brew tap utmostcreator/agent-kit https://github.com/UtmostCreator/agent-kit +brew install agent-kit # stable, from this point on +brew install --HEAD agent-kit # still available for main-branch installs +``` + +## 6. curl \| bash + +Nothing to publish here beyond the tag itself — `web-install.sh` resolves the +newest `v*` tag by default. Confirm it on a clean machine (or container): + +```bash +curl -fsSL https://raw.githubusercontent.com/UtmostCreator/agent-kit/main/web-install.sh | bash +agent-kit --list +``` + +## 7. Post-release verification + +Run all three install paths in isolated `HOME`s (or containers) and confirm +each produces a working `agent-kit --list` and `agent-kit search doctor`: + +```bash +HOME=$(mktemp -d) bash -c 'curl -fsSL https://raw.githubusercontent.com/UtmostCreator/agent-kit/main/web-install.sh | bash && "$HOME/.local/bin/agent-kit" --list' +``` + +Then: + +- Confirm the GitHub release notes read well and link back to `CHANGELOG.md`. +- Update README badges if command/test counts changed (`./scripts/check.sh` + prints the current pass count). +- Announce (Discussions, wherever else is relevant). + +## Patch/minor releases after this point + +Repeat steps 1-3 (and 4-6 only for the channels that need republishing — +npm and the Homebrew formula both need a version bump per release; the +`curl | bash` path needs nothing beyond the new tag existing). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..ca97db0 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,27 @@ +# Security policy + +## Supported versions + +Security fixes are provided for the latest released minor version. Older versions may receive fixes only when practical. + +## Reporting a vulnerability + +Do not open a public issue for a suspected vulnerability. + +Use GitHub's **Report a vulnerability** feature under the repository Security tab. Include: + +- affected version or commit; +- operating system and shell version; +- reproduction steps; +- impact and affected trust boundary; +- suggested mitigation, when known. + +Do not include real credentials, private repository data, or harmful payloads beyond what is required to demonstrate the issue. + +## Response targets + +- acknowledgement: within 7 days; +- initial assessment: within 14 days; +- remediation timing: based on severity and release risk. + +These are targets, not a service-level agreement. diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000..fae1024 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,47 @@ +# Support + +AgentKit is a safety-first, pure-Bash CLI that you install once and +run as `agent-kit ` inside any repository (or `akit ` if you +set the optional alias `alias akit='agent-kit'`). This page explains what is +supported, what is not, and where to ask. + +## What Is Supported + +- The `agent-kit` dispatcher (`bin/agent-kit`) and every shipped command under `libexec/`. +- The installers and their documented flags: `install.sh`, `uninstall.sh`, the + `curl | bash` bootstrap, the Homebrew formula, and the npm wrapper. +- Shared library modules under `lib/`, optional hooks under `hooks/`, and the + config under `share/`. +- Discovery and introspection: `agent-kit --list`, `agent-kit --help`, and + `agent-kit --introspect` (machine-readable JSON contract). +- Linux and macOS with the documented runtime: **Bash 4.4+, Git, ripgrep (`rg`), + and `jq`**. Optional capabilities may use `fd`, GitHub CLI, Node.js/Repomix, + `scc`, and ShellCheck. See [INSTALL.md](INSTALL.md) for prerequisites. + +## What Is Not Supported + +- The external AI tools and models themselves (Claude Code, GitHub Copilot, + OpenCode, ChatGPT). Report those to their vendors. +- Native Windows without WSL, and minimal shells that are not Bash. +- Changes you make to installed files after installation. +- This toolkit is a guardrail layer, **not** an operating-system sandbox. You + remain responsible for reviewing diffs, output, and verification evidence + before merging agent changes. + +## Where to Ask + +- **Questions and usage help:** open a GitHub Discussion or issue on this + repository. +- **Bugs:** open a GitHub issue with your OS, tool versions (`bash --version`, + `git --version`, `rg --version`, `jq --version`), the exact command you ran, + and its output. +- **Security issues:** do not open a public issue — follow [SECURITY.md](SECURITY.md) + and report privately through GitHub Security Advisories. + +## Before You Open an Issue + +1. Read [INSTALL.md](INSTALL.md) and the [command map](docs/COMMANDS.md). +2. Run `agent-kit --help` to confirm the exact supported contract. +3. Re-run the failing command and capture the full output. +4. Confirm your core dependencies resolve: `agent-kit search doctor` reports which + tools are available or missing. diff --git a/TODO/coverage-todo.md b/TODO/coverage-todo.md new file mode 100644 index 0000000..9f4470e --- /dev/null +++ b/TODO/coverage-todo.md @@ -0,0 +1,173 @@ +# Coverage plan: 46.58% → 60–70% + +Baseline measured with `./scripts/coverage.sh` (native Bash `DEBUG`-trap +collector — see `scripts/lib/cov-hook.sh`; kcov's ptrace tracer is unusable in +this sandbox, see git history). Full per-file numbers: `coverage/report.txt`. + +## Current state + +**46.58% (3570/7665 executable lines, 108 files), all 26 test files pass.** + +This already includes one applied fix (below). Everything else in this +document is **planned, not implemented** — re-run `./scripts/coverage.sh` +after each phase and treat the estimates here as sizing, not commitments. +The one empirically-verified estimate in this doc (the P1-flip below) came in +at **28% of the naive prediction** (137 actual vs. ~495 estimated), so the +per-phase estimates below are directional, not precise. + +## Already applied: free win from a dormant test gate + +`test/test-ai-search.sh` has ~269 already-written Phase 3–6 assertions +(structural search, git diff/history modes, curated todo/unsafe-patterns +modes, richer flag parsing) gated behind `AI_SEARCH_RUN_P1_TESTS=1`. Neither +`scripts/check.sh` (the CI gate) nor anything else in the repo ever sets that +var, so **269 passing assertions were not running in CI**. Verified standalone +(`AI_SEARCH_RUN_P1_TESTS=1 bash test/test-ai-search.sh` → 269 pass, 0 fail, +exit 0) before wiring it into `scripts/coverage.sh`'s test loop. + +Result: 44.79% → 46.58% (+137 lines), zero new test code. + +**Follow-up recommended (not yet done — changes the CI gate, out of this +task's scope):** set `AI_SEARCH_RUN_P1_TESTS=1` in `scripts/check.sh` too, so +these assertions actually gate merges instead of only running under +`coverage.sh`. Small, low-risk, high-value; do this first regardless of the +rest of this plan. + +## Out of scope for the 60–70% target + +Don't spend budget here — re-measure at the end and see if there's slack: + +- **`lib/ai-verify/{kotlin-files,kotlin-dispatch,android-guards,gradle-policy}.sh`** + (0%, 127 lines total). Needs a dedicated minimal Kotlin+Gradle(+Android) + fixture project under a `test/fixtures/` dir — a separate, larger effort, + not a few test cases. Track separately. +- **P0-marked-for-removal commands** per `TODO/todo.md`: `rg-code`, `fd-files`, + `ai-search-multi`, `all-f-into-one` (already have thin smoke tests; low + scores 45–72%). These are slated to be merged into `ai-search`/`ai-context` + before stable release — new tests here are thrown away work. Leave as-is. +- **`lib/ai-git/pr-context.sh`** (12.1%, 87 uncovered) needs a stubbed `gh` + binary on `PATH` to get past arg-parsing into the real JSON-assembly logic. + Doable, but bundled into Phase 4 rather than the critical path since + `gh`-mocking is a small side-project of its own. + +## Phase 1 — safety-critical (do first regardless of coverage target) + +This is the toolkit's actual safety promise (guarded mutation + rollback). +Coverage here matters independent of the 60–70% number. + +| File | Now | Uncovered | Concrete gap | +|---|---|---|---| +| `lib/snapshot.sh` | 25.2% (38/151) | 113 | `snapshot_apply_manifest` — the actual restore mechanism — is **entirely untested**: `git reset --hard`, conditional patch apply, `ROLLBACK_REMOVE_CREATED_UNTRACKED` deletion loop, protected-path guard (`.git`/`AI_LOG_DIR`/`.ai-logs`/`.repomix-context` must survive), untracked-archive re-extraction. Only exercised transitively via `ai-edit --apply`, never against its own contract. | +| `libexec/ai-rollback` | 36.5% (62/170) | 108 | No test ever creates a snapshot then shows/applies/prunes it. `cmd_apply`'s real mutation path, `cmd_prune`'s real `-mtime` deletion, and `cmd_list`/`cmd_show`'s non-empty rendering are all untested. `CI=true` bypasses the confirm prompt (verified: `confirm_mutation` at `libexec/ai-rollback:41`) — no pty needed. | +| `lib/ai-edit/helpers.sh` | 32.4% (59/182) | 123 | `finish()`'s non-JSON branch for each status string, `on_error` ERR-trap firing (force a mid-mode failure), `resolve_ast_grep`'s not-found→exit-127 path. | +| `lib/ai-edit/parse.sh` | 39.7% (31/78) | 47 | Nearly every "flag given without a value" error path (`--format`, `--glob`, `--exclude`, `--max-files`, `--max-replacements`, `--max-bytes`) and `=`-form flag parsing are untested — cheap, mechanical, one line per case. | +| `lib/ai-edit/main.sh` | 52.7% (59/112) | 53 | `ast-grep`/`comby` modes (both installed in this env — `ast-grep`, `sg`), `--verify` flag success/failure, `--allow-dirty-tree` vs. default require-clean gate. | +| `lib/ai-edit/plan-apply.sh` | 62.9% (78/124) | 46 | Oversized-file skip in `sd_plan`, `structural_scope_guard` blocking `--glob`/`--exclude` with `ast-grep`/`comby`/`patch`, `patch_guard_paths` denylist beyond `.env`/`.git` (`*.pem`, `*.sqlite`, `*.zip`), rename-form patch hunks. | +| `lib/exec-guard/cpu-sampling.sh` | 68.2% (45/66) | 21 | Direct jiffies-delta assertion (source module, sample a real busy `sleep` loop) rather than only indirectly through `run_guarded`. | +| `lib/exec-guard/run-guarded.sh` | 73.8% (79/107) | 28 | No-args call (`log_warn` + return 2), `setsid`-unavailable branch (strip it from `PATH`), idle-debounce streak count (current test only asserts eventual 124, not that it survives one idle sample first). | + +**Verification:** +```bash +bash test/test-ai-rollback.sh +bash test/test-ai-edit.sh +bash test/test-common.sh +./scripts/coverage.sh +``` + +## Phase 2 — canonical verify engine + +`ai-verify` is P4 in `TODO/todo.md` (score 0, "canonical, keep forever"). +`libexec/ai-verify` sources every module below unconditionally — confirmed +none of this is dead code despite some stale "not yet wired" header comments. + +| File | Now | Uncovered | Concrete gap | +|---|---|---|---| +| `lib/ai-verify/language-dispatch.sh` | 0.0% (0/122) | 122 | Full `ai_verify_language` dispatch never invoked by any test — 0 calls to `--language php\|js\|ts\|vue\|html`. Needs a fixture repo with `package.json`/`composer.json` + fake `eslint`/`phpstan`/etc. on `PATH`. | +| `lib/ai-verify/reporting.sh` | 0.0% (0/13) | 13 | `verify_report_dir` / `write_verify_report_file` — pure logic, no external deps, near-free win. | +| `lib/ai-verify/tool-policy.sh` | 0.0% (0/29) | 29 | `is_standalone_safe_tool`, `has_composer_bin`, `can_run_tool` dispatch — pure logic, near-free win. | +| `lib/ai-verify/language-files.sh` | 0.0% (0/27) | 27 | `language_pathspecs` (all 5 langs + unknown→die), `scoped_language_files` — pure logic given a git fixture, near-free win. | +| `lib/ai-verify/duplication.sh` | 8.0% (4/50) | 46 | Only the `VERIFY_JSCPD` off-by-default skip line runs. Needs a fake `jscpd`/`npx` on `PATH` emitting a canned report to hit the warn/fail-tier arithmetic. | +| `lib/ai-verify/plan-status.sh` | 21.1% (15/71) | 56 | Only the off-by-default skip runs. Needs a fixture with `docs/tickets/*/plan.md` containing checklist + difficulty-phrase lines, `VERIFY_PLAN_STATUS=1`. | +| `lib/ai-verify/step-runner.sh` | 27.3% (12/44) | 32 | `diagnose_pnpm_auth`'s `.npmrc` var-extraction, `VERIFY_GUARD=0` fallback path, `has_package_script`/`has_package_dependency` branches. | +| `lib/ai-verify/run.sh` | 46.5% (99/213) | 114 | `VERIFY_FULL=1` block (phpunit/pest/deptrac), `VERIFY_SECRETS=1`/`VERIFY_SECURITY=1` blocks, `branch` scope case arm — needs fake `phpunit`/`gitleaks`/`trivy` binaries + env flags. | +| `lib/ai-verify/docs-check.sh` | 52.8% (65/123) | 58 | `ai_verify_docs_run_drift`'s 11 gated steps never see a fake `php` (pass or fail), so the `failures+=1` branch is never hit. | + +**Verification:** +```bash +bash test/test-ai-verify.sh +./scripts/coverage.sh +``` + +## Phase 3 — context/repomix internal engines + diff-context + +`ai-context`/repomix routers are P2 in `TODO/todo.md` ("make internal +implementation" — kept, just renamed publicly). `ai-diff-context` is P2 +("retain implementation, rename public interface to `ai-context diff`") — +algorithm survives regardless of the public name. + +**Known confound:** the shared repomix test fixtures are 2 tiny files, likely +never reaching `MIN_CODE=25`, so current tests only exercise the +fallback/skip path, not the primary `build_plan`/`write_bundle_plan` +selection logic. New tests need a bigger fixture (10+ files, 30+ code lines +each) before the "concrete gap" items below become reachable at all. + +| File | Now | Uncovered | Concrete gap | +|---|---|---|---| +| `lib/repomix-context-tree/build-pack.sh` | 2.4% (6/255) | 249 | `run_pack`/`pack_route`/`run_all`/`run_clean`/`run_purge`/`generate_child_index` never invoked — only `analyze` runs today. | +| `lib/repomix-scc-router/analysis-pack.sh` | 18.3% (47/257) | 210 | `write_bundle_plan` (`plan`), `pack_group`/`run_pack`, `run_clean`, `run_purge` untested. | +| `lib/repomix/common-options.sh` | 21.6% (32/148) | 116 | Only `--output-dir` exercised. `--depth`, `--top`, `--min-code`, `--min-files`, `--min-score`, `--changed-since`, `--split-size`, `--compress`, and `=value` forms all untested. | +| `libexec/internal/run-repomix-context` | 20.0% (13/65) | 52 | `require_bins` failure, secrets-scan failure, missing-artifact `die` branches, `bundle_count == 0` die branch. | +| `lib/ai-diff-context/commands.sh` | 18.7% (23/123) | 100 | Only `cmd_since` (dry-run) is tested. `cmd_unstaged`, `cmd_pr`, `cmd_recent`, `cmd_touched` are **entirely untested** — 4 of 5 command functions at 0%. | +| `lib/ai-diff-context/helpers.sh` | 25.7% (69/269) | 200 | Largest single untested surface in the repo. Entire non-dry-run half of `pack_files_list` (secrets scan, packer selection, token-budget warn/die, manifest write), `write_diff_artifact`'s 5 mode branches, most of `parse_common_option`. | + +**Verification:** +```bash +bash test/test-repomix-context-tree.sh +bash test/test-repomix-scc-router.sh +bash test/test-run-repomix-context.sh +bash test/test-ai-context.sh +./scripts/coverage.sh +``` + +## Phase 4 — remaining polish + +Only needed if Phase 1–3 land short of 70%; re-measure before starting this. + +| File | Now | Uncovered | Concrete gap | +|---|---|---|---| +| `lib/ai-git/pr-context.sh` | 12.1% (12/99) | 87 | Needs a stubbed `gh` binary emitting canned `pr view`/`checks`/`diff` JSON. | +| `lib/ai-git/origin.sh` | 67.6% (75/111) | 36 | Prefix-matching branch, `GIT_ORIGIN_INCLUDE_REMOTE=0`, no-candidates fallback (fresh single-commit repo). | +| `lib/ai-context/status.sh` | 39.4% (26/66) | 40 | Fresh/stale/expired/unparseable-`ts` states — write `run-manifest.json` with a controlled timestamp. | +| `lib/ai-context/ensure.sh` | 50.0% (39/78) | 39 | Fresh short-circuit, stale-with-regen-accepted path (stub `ai_context_generate_main`). | +| `lib/ai-context/pack.sh` | 56.7% (59/104) | 45 | "No packer found" die, `code2prompt` backend, token-budget-exceeded warn. | +| `lib/ai-context/file.sh` | 57.6% (53/92) | 39 | Missing-arg / not-on-PATH / unknown-option error branches — mechanical. | +| `lib/ai-test/run-all.sh` | 16.5% (18/109) | 91 | Test `ai_test_all_run_job` directly (sourced) rather than the full orchestrator, to avoid recursive suite runs. | +| `lib/ai-test/select.sh` | 64.4% (87/135) | 48 | `ai_test_select_command_for_test` branches (artisan/pest/phpunit, pnpm/npm) — source module, call directly with fake project files. | +| `lib/logging.sh` | 43.9% (76/173) | 97 | No-`uuidgen` fallback, `SESSION_LOG` append branch, `AI_SESSION_DURABLE_LOG=1` branch, no-`flock` degradation. | +| `libexec/preview-file` | 53.3% (106/199) | 93 | `--force` bypass, invalid `--around`/`--context` values, `bat`-present rendering path (bat is installed here — verify), `=`-form flags. | + +## Rough trajectory (directional, not a commitment) + +| After phase | Est. new lines | Running total | Est. % | +|---|---:|---:|---:| +| Baseline (P1-flip already applied) | — | 3570 | 46.6% | +| + Phase 1 (safety) | ~250–400 | ~3820–3970 | ~50–52% | +| + Phase 2 (verify engine) | ~250–370 | ~4070–4340 | ~53–57% | +| + Phase 3 (context/repomix/diff) | ~450–640 | ~4520–4980 | ~59–65% | +| + Phase 4 (polish) | ~250–365 | ~4770–5345 | ~62–70% | + +Phases 1–3 alone should comfortably clear **60%**. Reaching **70%** likely +needs most of Phase 4, or dipping into the excluded pool (Kotlin/Android/ +Gradle fixtures, `gh`-stubbed `pr-context`) if Phase 4's real yield undershoots +like the P1-flip did. + +## How to verify progress at any point + +```bash +./scripts/coverage.sh # full report, coverage/report.txt +./scripts/check.sh # shellcheck + full suite must still pass +./scripts/check-publishable.sh +``` + +Re-read `coverage/report.txt` after each phase and adjust remaining scope — +don't chase the table above past what the real numbers show. diff --git a/TODO/public-command-surface-consolidation.md b/TODO/public-command-surface-consolidation.md new file mode 100644 index 0000000..2a90c28 --- /dev/null +++ b/TODO/public-command-surface-consolidation.md @@ -0,0 +1,387 @@ +# Public command-surface consolidation TODO + +Status: in progress. Search (`batch`), Verify, Test, and Git families are fully fused (implementation +files physically merged, old top-level names deleted after approval). Repo, Inspect, and Session +families have canonical routing (`libexec/ai-` exec-dispatches into the still-separate +original engines) but are NOT yet physically fused/deleted. Context family is not yet started. Edit +family is routing-only by design (rollback intentionally stays a fully separate, independently +recoverable engine). Human direction later favored physical fusion + deletion over the original +"merge only the public command surface, keep engines separate" rule below for Search/Verify/Test/Git +specifically — treat the per-family sections as authoritative over the global rule where they +disagree. + +Target public surface before first stable release: approximately 9 command groups: `search`, `verify`, `test`, `context`, `git`, `repo`, `edit`, `inspect`, and `session`. + +## Global rules + +- [ ] Keep implementation engines separate when responsibilities differ; merge only the public command surface. +- [ ] Prefer pre-stable removal or de-publicization over permanent compatibility aliases. +- [ ] Do not delete or move tracked files without explicit approval for the exact paths. +- [ ] If deletion is not approved, move engines behind canonical commands, hide them from `agent-kit --list`, and exclude them from shipped public surfaces. +- [ ] Update docs, generated examples, tests, install payloads, npm package contents, Homebrew formula, and release packaging together. +- [ ] Add a publishability gate so duplicate public command names cannot reappear. + +## Cross-cutting implementation tasks + +- [ ] Define a public command allowlist or duplicate-command denylist for pre-stable release. +- [ ] Update `bin/agent-kit --list` / `libexec/sh-introspect --list` so internal or compatibility engines are not presented as public commands. +- [ ] Decide internal engine location, for example `libexec/internal/`, that `bin/agent-kit` cannot dispatch directly. +- [ ] Update `scripts/gen-examples.sh` so docs are generated only from public commands. +- [ ] Update packaging surfaces: `install.sh`, `scripts/package-release.sh`, `package.json`, `npm/cli.js`, `Formula/agent-kit.rb`. +- [ ] Extend `scripts/check-publishable.sh` to fail when removed/de-publicized names are shipped. + +Verification for cross-cutting work: + +```bash +bash test/test-bin-agent-kit.sh +bash test/test-install.sh +./scripts/check-publishable.sh +``` + +## P0 — consolidate before first stable release + +### 1. Search family — urgency 100/100 + +Canonical interface: + +```bash +agent-kit search text PATTERN [ROOT] +agent-kit search files QUERY [ROOT] +agent-kit search batch MODE QUERY... +agent-kit search capabilities [--probe] +``` + +Current state: + +- [x] `agent-kit search capabilities` exists in the current working tree. +- [ ] `ai-search-introspect` remains public unless removed/de-publicized. +- [ ] `ai-search-multi`, `rg-code`, and `fd-files` remain public duplicate APIs. + +Tasks: + +- [ ] Keep `ai-search` as the canonical search engine. +- [ ] Internalize `ai-search-introspect`; public replacement is `agent-kit search capabilities`. +- [x] Add or confirm `agent-kit search batch` before internalizing `ai-search-multi`. +- [ ] Prove `agent-kit search text` covers `rg-code` use cases; then remove/de-publicize `rg-code`. +- [ ] Prove `agent-kit search files` covers `fd-files` use cases; then remove/de-publicize `fd-files`. +- [ ] Stop shipping `rg-code` and `fd-files` as parallel public search APIs. + +Verification: + +```bash +bash test/test-ai-search.sh +bash test/test-rg-code.sh +bash test/test-fd-files.sh +bash test/test-bin-agent-kit.sh +./scripts/check.sh +./scripts/check-publishable.sh +``` + +### 2. Verify family — urgency 100/100 + +Canonical interface: + +```bash +agent-kit verify [ROOT] +agent-kit verify --language php [ROOT] +agent-kit verify --language js [ROOT] +agent-kit verify docs [PATH...] +agent-kit verify refs [PATH] +``` + +Current state: + +- [x] `libexec/ai-verify` supports `--language`. +- [x] `ai-verify-html`, `ai-verify-js`, `ai-verify-php`, `ai-verify-ts`, and `ai-verify-vue` deleted; `--language ` is the only route. +- [x] Docs promote `agent-kit verify --language ` / `verify docs` / `verify refs`; no wrapper commands remain. +- [x] `ai-doc-check` and `check-file-refs` fused into `libexec/ai-verify` (`lib/ai-verify/{docs-check,file-refs}.sh`); no longer separate public commands. + +Tasks: + +- [x] Keep `ai-verify` as the canonical verification engine. +- [x] Use `agent-kit verify --language ` in docs and tests. +- [x] Existing `--language html|js|php|ts|vue` coverage carried over unchanged in `test/test-ai-verify.sh`. +- [x] Remove `ai-verify-html`, `ai-verify-js`, `ai-verify-php`, `ai-verify-ts`, and `ai-verify-vue`. +- [x] Add `agent-kit verify docs` and internalize `ai-doc-check`. +- [x] Add `agent-kit verify refs` and internalize `check-file-refs`. + +Verification: + +```bash +bash test/test-ai-verify.sh # ported every ai-doc-check/check-file-refs assertion; 48 passed / 0 failed / 2 skipped (env-gated) +bash test/test-bin-agent-kit.sh +./scripts/check.sh +./scripts/check-publishable.sh +``` + +### 3. Test family — urgency 96/100 + +Canonical interface: + +```bash +agent-kit test select changed +agent-kit test select file src/Foo.php +agent-kit test run tests/FooTest.php +agent-kit test run --filter FooTest +agent-kit test all +``` + +Tasks: + +- [x] Add `agent-kit test select` backed by fused `lib/ai-test/select.sh` (was `ai-test-select`). +- [x] Add `agent-kit test run` backed by fused `lib/ai-test/run-focused.sh` (was `run-test-focused`). +- [x] Add `agent-kit test all` backed by fused `lib/ai-test/run-all.sh` (was `run-repo-tests`). +- [x] Kept selection, focused execution, and full-suite execution as separate internal modules/functions (`ai_test_select_main` / `ai_test_run_main` / `ai_test_all_main`). +- [x] Removed `ai-test-select`, `run-test-focused`, and `run-repo-tests` from the shipped public surface. + +Verification: + +```bash +bash test/test-ai-test.sh # 16 passed / 0 failed / 0 skipped +bash test/test-bin-agent-kit.sh +./scripts/check.sh +./scripts/check-publishable.sh +``` + +### 4. Context family — urgency 95/100 + +Canonical interface: + +```bash +agent-kit context diff ... +agent-kit context pack ... +agent-kit context file PATH +agent-kit context generate +agent-kit context tree +agent-kit context status +agent-kit context ensure [--regen] +agent-kit context estimate PATH +``` + +Tasks: + +- [x] Add `agent-kit context diff` backed by fused `lib/ai-context/diff.sh` (wraps unchanged `lib/ai-diff-context/*.sh`; was `ai-diff-context`). +- [x] Add `agent-kit context pack` backed by fused `lib/ai-context/pack.sh` (was `pack-context`). +- [x] Add `agent-kit context file` backed by fused `lib/ai-context/file.sh` (was `run-repomix-file`). +- [x] Add `agent-kit context generate` backed by `lib/ai-context/generate.sh`, which execs the relocated (not fused) `libexec/internal/run-repomix-context`. +- [x] Add `agent-kit context tree` backed by `lib/ai-context/tree.sh`, which execs the relocated (not fused) `libexec/internal/repomix-context-tree`. `generate`/`tree` were deliberately kept as separate processes rather than fused, per a confirmed `die()`/`log()`/`estimate_tokens()` name-collision risk between `lib/repomix-context-tree/helpers.sh` and `lib/core.sh`/`lib/tokens.sh` if merged into the shared `ai-context` process. +- [x] Add `agent-kit context status` backed by fused `lib/ai-context/status.sh` (was `repomix-freshness`). +- [x] Add `agent-kit context ensure` backed by fused `lib/ai-context/ensure.sh` (was `repomix-ensure-fresh`). +- [x] Add `agent-kit context estimate` backed by fused `lib/ai-context/estimate.sh` (was `query-usage`). +- [x] `repomix-scc-router` relocated to `libexec/internal/repomix-scc-router`: hidden from `agent-kit --list` and public dispatch, left fully unwired (no public route added, matching this exact instruction). +- [ ] Remove `all-f-into-one` from the shipped public surface — not yet done. + +Verification: + +```bash +bash test/test-ai-context.sh # 26 passed / 0 failed / 0 skipped (ported from 5 old test files) +bash test/test-run-repomix-context.sh # 3 passed (relocated engine, path updated) +bash test/test-repomix-context-tree.sh # 4 passed (relocated engine, path updated) +bash test/test-repomix-scc-router.sh # 9 passed (relocated engine, path updated) +bash test/test-bin-agent-kit.sh +./scripts/check.sh +./scripts/check-publishable.sh +``` + +## P1 — consolidate strongly + +### 5. Git family — urgency 87/100 + +Canonical interface: + +```bash +agent-kit git origin +agent-kit git history --string VALUE +agent-kit git history --regex VALUE +agent-kit git blame FILE --lines 10,20 +agent-kit git pr-context NUMBER +``` + +Tasks: + +- [x] Add `agent-kit git origin` backed by fused `lib/ai-git/origin.sh` (was `git-branch-origin`). +- [x] Add `agent-kit git history` and `agent-kit git blame` backed by fused `lib/ai-git/forensics.sh` (was `git-forensics`). +- [x] Add `agent-kit git pr-context` backed by fused `lib/ai-git/pr-context.sh` (was `gh-pr-context`). +- [x] Removed `git-branch-origin`, `git-forensics`, and `gh-pr-context` from the shipped public surface. + +Note: implemented as a real fused engine (`libexec/ai-git` sources `lib/ai-git/*.sh` and calls each +mode as a function in the same process), not an exec-router, per later human direction favoring +physical consolidation over the original "keep implementation engines separate" global rule for +this family. See git history for the exact change. + +Verification: + +```bash +bash test/test-ai-git.sh # 20 passed / 0 failed / 0 skipped (ports every assertion from the 3 old test files) +bash test/test-bin-agent-kit.sh +./scripts/check-publishable.sh +``` + +### 6. Repository family — urgency 82/100 + +Canonical interface: + +```bash +agent-kit repo tasks +agent-kit repo stats +agent-kit repo tools +agent-kit repo status +``` + +Tasks: + +- [x] Add `agent-kit repo tasks` backed by `ai-task` (routed via `libexec/ai-repo`, engine kept separate — not yet physically fused). +- [x] Add `agent-kit repo stats` backed by `repo-stats` (routed). +- [x] Add `agent-kit repo tools` backed by `repo-tool-inventory` (routed). +- [x] `agent-kit repo status` routes to `ai-file-freshness` as-is (no `--surface docs` filter added; that remains a future enhancement, not a rename). +- [ ] Remove/de-publicize old top-level public names (`ai-task`, `repo-stats`, `repo-tool-inventory`, `ai-file-freshness` still exist as separate public commands; only routing was added, not fusion+deletion). + +Verification: + +```bash +bash test/test-ai-task.sh +bash test/test-repo-tool-inventory.sh +bash test/test-misc-wrappers.sh +bash test/test-bin-agent-kit.sh +./scripts/check-publishable.sh +``` + +### 7. Edit family — urgency 77/100 + +Canonical interface: + +```bash +agent-kit edit apply ... +agent-kit edit rollback ... +``` + +Tasks: + +- [x] Kept `ai-edit` and `ai-rollback` as fully separate engines (zero changes to `ai-rollback`) because rollback must remain independently recoverable. +- [x] Add `agent-kit edit apply` as the canonical edit route (thin `apply` token shift; bare `agent-kit edit MODE ...` still works unchanged). +- [x] Add `agent-kit edit rollback` as the canonical rollback route (early-exit `exec` shim into unchanged `ai-rollback`, same pattern as `ai-search`'s `capabilities` shim). +- [x] `agent-kit rollback` remains as the primary/documented route (not just a compatibility shim); `agent-kit edit rollback` is additive. + +Verification: + +```bash +bash test/test-ai-edit.sh +bash test/test-ai-rollback.sh +bash test/test-bin-agent-kit.sh +./scripts/check-publishable.sh +``` + +## P2 — useful namespace cleanup + +### 8. Inspection family — urgency 61/100 + +Canonical interface: + +```bash +agent-kit inspect file PATH +agent-kit inspect data json FILE QUERY +agent-kit inspect data yaml FILE QUERY +agent-kit inspect shell SCRIPT +``` + +Tasks: + +- [x] Add `agent-kit inspect file` backed by `preview-file` (routed via `libexec/ai-inspect`, engine kept separate). +- [x] Add `agent-kit inspect data` backed by `ai-structured` (routed). +- [x] Add `agent-kit inspect shell` backed by `sh-introspect` (routed). +- [x] Decision: these stay as separate top-level dispatcher commands too (human review flagged this cluster as "not one coherent engine" — search-ish tools like `fd-files`/`rg-code` belong under `search`, not `inspect`; that reclassification is not yet implemented). + +Verification: + +```bash +bash test/test-preview-file.sh +bash test/test-ai-structured.sh +bash test/test-sh-introspect.sh +bash test/test-bin-agent-kit.sh +``` + +### 9. Session family — urgency 52/100 + +Canonical interface: + +```bash +agent-kit session checkpoint +agent-kit session watch +``` + +Tasks: + +- [x] Add `agent-kit session checkpoint` backed by `session-checkpoint` (routed via `libexec/ai-session`, engine kept separate). +- [x] Add `agent-kit session watch` backed by `watch-loop` (routed as-is; watching-scope confirmation not separately re-verified). + +Verification: + +```bash +bash test/test-session-checkpoint.sh +bash test/test-watch-loop.sh +bash test/test-bin-agent-kit.sh +``` + +## Final shipped public CLI target + +```text +agent-kit +├── search +│ ├── text +│ ├── files +│ ├── history +│ ├── symbols +│ ├── batch +│ └── capabilities +├── verify +│ ├── all +│ ├── --language +│ ├── docs +│ └── refs +├── test +│ ├── select +│ ├── run +│ └── all +├── context +│ ├── diff +│ ├── pack +│ ├── file +│ ├── generate +│ ├── tree +│ ├── status +│ ├── ensure +│ └── estimate +├── git +│ ├── origin +│ ├── history +│ ├── blame +│ └── pr-context +├── repo +│ ├── tasks +│ ├── stats +│ ├── tools +│ └── status +├── edit +│ ├── apply +│ └── rollback +├── inspect +│ ├── file +│ ├── data +│ └── shell +└── session + ├── checkpoint + └── watch +``` + +## Final release gate + +- [ ] `agent-kit --list` shows only approved public commands. +- [ ] Docs promote only canonical command groups. +- [ ] Release packages do not expose duplicate public names. +- [ ] All approved removals/internalizations have recorded human approval. +- [ ] Full verification passes. + +```bash +./scripts/check.sh +./scripts/check-publishable.sh +``` diff --git a/TODO/scripts-todo.md b/TODO/scripts-todo.md new file mode 100644 index 0000000..ec34a70 --- /dev/null +++ b/TODO/scripts-todo.md @@ -0,0 +1,30 @@ +Packages-used + real-world-example + why-better documentation for every public +command now lives in [docs/PACKAGES.md](../docs/PACKAGES.md). All 24 commands +below are done. + +- [x] libexec/ai-context +- [x] libexec/ai-edit +- [x] libexec/ai-file-freshness +- [x] libexec/ai-git +- [x] libexec/ai-inspect +- [x] libexec/ai-repo +- [x] libexec/ai-rollback +- [x] libexec/ai-search +- [x] libexec/ai-search-introspect +- [x] libexec/ai-search-multi +- [x] libexec/ai-session +- [x] libexec/ai-structured +- [x] libexec/ai-task +- [x] libexec/ai-test +- [x] libexec/ai-verify +- [x] libexec/all-f-into-one +- [x] libexec/fd-files +- [x] libexec/preview-file +- [x] libexec/repo-stats +- [x] libexec/repo-tool-inventory +- [x] libexec/rg-code +- [x] libexec/session-checkpoint +- [x] libexec/sh-introspect +- [x] libexec/watch-loop + +(End of file - total 24 lines) diff --git a/TODO/todo.md b/TODO/todo.md new file mode 100644 index 0000000..b3f5106 --- /dev/null +++ b/TODO/todo.md @@ -0,0 +1,387 @@ +## Priority scale + +| Phase | Score | Meaning | +| ------ | -----: | --------------------------------------------------------- | +| **P0** | 90–100 | Consolidate before first stable release | +| **P1** | 75–89 | Consolidate immediately after core release blockers | +| **P2** | 55–74 | Simplify public surface; retain implementation internally | +| **P3** | 30–54 | Optional namespace cleanup | +| **P4** | 0–29 | Keep separate | + +## P0 — merge or remove now + +| Scripts | Score | Action | Canonical destination | +| ---------------------------------------------------------------------------------- | ------: | ------------------------------------------------------------------------ | ------------------------------------------------- | +| `all-f-into-one` | **100** | **Remove** | `ai-context pack` | +| `ai-verify-html`, `ai-verify-js`, `ai-verify-php`, `ai-verify-ts`, `ai-verify-vue` | **100** | **Remove implementations**; optionally retain tiny compatibility aliases | `ai-verify --language ` | +| `ai-file-freshness` | **99** | **Remove or completely rename** | `ai-search changed-files` or `ai-repo status` | +| `rg-code` | **98** | **Merge and remove** | `ai-search text` / `tracked` / `config` | +| `fd-files` | **97** | **Merge and remove** | `ai-search files` | +| `ai-search-multi` | **94** | **Merge and remove** | `ai-search --batch`, repeated `--query`, or stdin | +| `repomix-freshness` | **93** | **Internalise** | `ai-context status` | +| `repomix-ensure-fresh` | **92** | **Merge public interface** | `ai-context ensure` | +| `run-repomix-context` | **90** | Keep engine, merge public interface | `ai-context generate` | + +### Why these are P0 + +`all-f-into-one` is a legacy Zsh concatenator that writes every selected file into `combined_output.txt`. It lacks the secret scanning, token budgeting, backend selection and manifest generation already provided by `pack-context`. + +The five language verification commands contain no verification logic; they delegate directly to `ai-verify.sh --language`. + +`ai-file-freshness` does not calculate freshness. It runs one fixed `git status --short` command against several directories, making its name and contract misleading. + +`rg-code` and `fd-files` duplicate search modes already exposed by `ai-search`, including text, tracked files, configuration files, file discovery, case control, context and output shaping. + +`repomix-ensure-fresh` already calls both `repomix-freshness` and `run-repomix-context`. It even determines whether status is stale by parsing the checker’s human-readable first line. These should share one internal status function rather than communicate through text. + +## P1 — consolidate next + +| Scripts | Score | Action | Canonical destination | | +| ---------------------- | -----: | -------------------------------------------- | ------------------------ | ------ | +| `ai-test-select` | **89** | Merge | `ai-test select` | | +| `run-test-focused` | **88** | Merge | `ai-test run` | | +| `run-repo-tests` | **86** | Merge public interface; retain runner module | `ai-test all` | | +| `ai-doc-check` | **85** | Merge public interface | `ai-verify docs` | | +| `run-repomix-file` | **84** | Merge | `ai-context file` | | +| `ai-search-introspect` | **82** | Merge | `ai-search capabilities` | | +| `git-forensics` | **79** | Partially merge | `ai-git history | blame` | +| `check-file-refs` | **76** | Retain algorithm, remove standalone command | `ai-verify refs` | | + +### Test cluster + +These currently divide one lifecycle across three commands: + +- `ai-test-select` discovers candidate tests but does not execute them. +- `run-test-focused` executes a selected PHPUnit file or filter. +- `run-repo-tests` executes complete suites and validators. + +That should become one command with `select`, `run` and `all` modes. + +### Verification cluster + +`ai-doc-check` is already a verification orchestrator covering Markdown, links and repository drift validators. It belongs under the canonical `ai-verify` interface rather than beside it. + +`check-file-refs` provides useful orphan detection and should not be deleted, but it is naturally a verification profile rather than an independent top-level utility. + +### Context cluster + +`run-repomix-file` duplicates Repomix invocation, output creation and manifest generation already conceptually owned by `pack-context`. + +### Git/search cluster + +`git-forensics` modes `S` and `G` overlap `ai-search history`, which already supports string versus regex history searches and patches. Keep line history and blame, but place them under a single `ai-git` command. + +## P2 — internalise or namespace + +| Scripts | Score | Action | Destination | +| ---------------------- | -----: | ---------------------------------------------- | ------------------------------- | +| `repomix-context-tree` | **74** | Make internal implementation | `internal/context/repomix-tree` | +| `repomix-scc-router` | **71** | Make internal implementation | `internal/context/scc-router` | +| `query-usage` | **69** | Merge | `ai-context estimate` | +| `git-branch-origin` | **66** | Merge public interface or internalise | `ai-git origin` | +| `repo-stats` | **63** | Merge public namespace | `ai-repo stats` | +| `repo-tool-inventory` | **62** | Merge public namespace; retain PHP backend | `ai-repo tools` | +| `gh-pr-context` | **59** | Merge public namespace | `ai-git pr` or `ai-context pr` | +| `ai-diff-context` | **57** | Retain implementation, rename public interface | `ai-context diff` | + +`query-usage` is specifically a byte/token estimator, so it fits the context domain rather than remaining a generic “query” command. + +`git-branch-origin` remains valuable, particularly because verification uses its merge-base information, but it does not require a separate top-level executable. + +## P3 — optional consolidation + +| Scripts | Score | Recommendation | | +| ------------------------ | -----: | ----------------------------------------------------------------------- | ----------------------------------------------------- | +| `ai-task` | **48** | Expose as `ai-repo tasks`; retain implementation | | +| `ai-edit`, `ai-rollback` | **40** | Optionally expose as `ai-edit apply | rollback`, but keep separate internal risk boundaries | +| `pack-context` | **35** | Rename public interface to `ai-context pack`; keep as canonical backend | | + +`ai-task` discovers project-provided package, Composer, Make, Just and Taskfile commands, so it is related to repository inspection rather than context packing or verification. + +## P4 — keep separate + +| Script | Score | Reason | +| -------------------- | -----: | -------------------------------------- | +| `ai-search` | **0** | Canonical search engine | +| `ai-verify` | **0** | Canonical verification engine | +| `ai-structured` | **10** | Distinct JSON/YAML/CSV/XML querying | +| `preview-file` | **10** | Distinct safe-preview boundary | +| `sh-introspect` | **5** | Canonical shell contract introspector | +| `session-checkpoint` | **10** | Distinct session persistence operation | +| `watch-loop` | **15** | Distinct long-running orchestration | +| `ai-edit` | **15** | Distinct guarded mutation operation | +| `ai-rollback` | **15** | Distinct recovery operation | + +## Recommended final public surface + +```text +ai-search + text | files | history | symbols | batch | capabilities + +ai-verify + all | language | docs | refs + +ai-test + select | run | all + +ai-context + diff | pack | file | generate | status | ensure | estimate + +ai-git + origin | history | blame | pr + +ai-repo + tasks | stats | tools + +ai-edit + apply | rollback + +ai-structured +preview-file +session-checkpoint +sh-introspect +watch-loop +``` + +This reduces **40 executables to approximately 12 canonical commands**, while retaining compatibility shims only where published users may already depend on the old names. + +## Verdict + +**For the bounded `search capabilities` slice: 96/100 — complete and correctly implemented.** + +**Against the full consolidation plan: approximately 12/100 — most recommended work remains.** + +You completed one item: + +| Recommendation | Status | +| ---------------------------------------------------------- | ------------------------- | +| Merge `ai-search-introspect` into `ai-search capabilities` | **Functionally complete** | +| Preserve old command temporarily | **Complete** | +| Update canonical documentation | **Complete** | +| Test direct and dispatcher invocation | **Complete** | +| Validate publishing surface | **Complete** | +| Independent reviewer pass | **Pending** | + +## What was done correctly + +- The canonical route is now `agent-kit search capabilities`. +- Existing implementation was reused rather than duplicated. +- The split `lib/ai-search/*` parsing defect was fixed. +- Both direct and dispatcher paths have regression coverage. +- Documentation now promotes the canonical command. +- Full repository and publishability checks passed. +- `TODO/todo.md` was correctly excluded from the implementation scope. +- `/review-diff` is the correct next handoff. + +## Important distinction + +You **merged the behaviour**, but you have **not reduced the public command surface** because `ai-search-introspect` remains installed and callable. + +That is acceptable during a compatibility period. However, if this package has not had its first stable release, retaining a permanent compatibility command is probably unnecessary. Prefer: + +```text +Public: + agent-kit search capabilities + +Internal implementation: + libexec/internal/ai-search-capabilities +``` + +rather than shipping both public names indefinitely. + +## Major consolidation work still outstanding + +### P0 + +| Cluster | Remaining action | +| -------------------------------- | ------------------------------------------------------------- | +| `ai-verify-{html,js,php,ts,vue}` | Convert to aliases or remove in favour of `verify --language` | +| `rg-code` | Merge into `search text` | +| `fd-files` | Merge into `search files` | +| `ai-search-multi` | Merge into `search --batch` or repeated queries | +| `all-f-into-one` | Remove in favour of context packing | +| `ai-file-freshness` | Remove or rename because its contract is misleading | +| Repomix freshness commands | Consolidate checker, ensure and generator lifecycle | + +### P1 + +| Cluster | Remaining action | +| ------------------------------------------------------ | ------------------------------------------------ | +| `ai-test-select`, `run-test-focused`, `run-repo-tests` | Consolidate under one test interface | +| `ai-doc-check`, `check-file-refs` | Expose through verification modes | +| `run-repomix-file` | Consolidate under the context interface | +| `git-forensics` | Move history functionality under a Git namespace | + +## Recommended status wording + +Your current summary should not imply the overall consolidation is complete. Use: + +```text +Status: verified implementation of the bounded `ai-search capabilities` consolidation slice. + +This slice is complete, compatibility-preserving and publishable. It does not complete the broader command-surface consolidation plan; P0 removals and the remaining P1/P2 command merges are intentionally deferred pending explicit public-surface approval. +``` + +The implementation itself appears sound from the evidence provided, but I could not independently inspect `/home/utmostcreator/Projects/agent-kit` because that local path is not mounted in this session. + +## Remaining public command-surface consolidation plan + +Status: the bounded `agent-kit search capabilities` slice is implemented and verified, but the broader public command-surface consolidation is unfinished. Treat this section as the implementation plan for the remaining work. + +### Compatibility and deletion policy + +AgentKit is pre-stable (`0.1.0`), so confusing duplicate public command names should be removed or de-publicized before the first stable release instead of preserved indefinitely. + +Rules: + +- Canonical public commands must be documented and tested through `agent-kit ` forms. +- Legacy top-level command names may remain only as short temporary aliases with an explicit removal window. +- Tracked-file deletion requires explicit approval before implementation. If deletion is not approved, move implementation behind canonical commands under a non-dispatchable internal path, exclude legacy aliases from packaging, or keep temporary aliases with dated removal notes. +- Before stable release, the public package/install surface should not expose: `ai-search-introspect`, `ai-search-multi`, `rg-code`, `fd-files`, `ai-file-freshness`, `ai-verify-html`, `ai-verify-js`, `ai-verify-php`, `ai-verify-ts`, or `ai-verify-vue`. + +### Slice 1 — Canonical language verification + +Goal: language-specific verification is invoked and promoted only as: + +```bash +agent-kit verify --language +``` + +Current evidence: `libexec/ai-verify` supports `--language`; the five `ai-verify-` files are already thin wrappers that exec `ai-verify --language `. Do not document `--lang` unless code is explicitly changed to support it. + +Implementation steps: + +1. Keep `libexec/ai-verify` as the only canonical verification entrypoint. +2. Replace docs/examples for `verify-html`, `verify-js`, `verify-php`, `verify-ts`, and `verify-vue` with canonical examples: + - `agent-kit verify --language html .` + - `agent-kit verify --language js .` + - `agent-kit verify --language php .` + - `agent-kit verify --language ts .` + - `agent-kit verify --language vue .` +3. Add dispatcher tests proving `agent-kit verify --language html|js|php|ts|vue .` reaches language dispatch. +4. Approval decision: remove the tracked wrapper files before stable release, or keep them only as temporary aliases excluded from public docs. + +Acceptance criteria: + +- `docs/EXAMPLES.md`, `docs/COMMANDS.md`, and README-facing surfaces no longer promote `agent-kit verify-`. +- Tests cover `agent-kit verify --language html|js|php|ts|vue`. +- No test requires users to call `agent-kit verify-html`, `verify-js`, `verify-php`, `verify-ts`, or `verify-vue`. +- If wrapper files remain, they are labelled temporary compatibility aliases only. + +Verification: + +```bash +bash test/test-ai-verify.sh +bash test/test-bin-agent-kit.sh +./scripts/check.sh +./scripts/check-publishable.sh +``` + +### Slice 2 — Search duplicate command de-publicization + +Goal: public search usage converges on `agent-kit search ...`. + +Canonical replacements: + +- `agent-kit search capabilities` replaces `agent-kit search-introspect`. +- `agent-kit search text ...` replaces `rg-code`. +- `agent-kit search files ...` replaces `fd-files`. +- `agent-kit search --batch` or another approved `search` batch form replaces `search-multi`. + +Implementation steps: + +1. Keep the existing `search capabilities` behavior. +2. Add or confirm canonical batch support before removing `ai-search-multi` from the public surface. +3. Port any unique behavior from `rg-code`, `fd-files`, and `ai-search-multi` into `ai-search` modes or internal modules. +4. Stop promoting duplicate search names in docs/examples. +5. With explicit approval, remove or move duplicate top-level `libexec` commands so `agent-kit --list` no longer presents them as public commands. + +Acceptance criteria: + +- `agent-kit search capabilities` is the only promoted capability-map command. +- `agent-kit search text` covers documented `rg-code` use cases. +- `agent-kit search files` covers documented `fd-files` use cases. +- Batch search has one canonical documented command form. +- Before stable release, `agent-kit --list` does not list duplicate search names unless a temporary compatibility window is explicitly approved. + +Verification: + +```bash +bash test/test-ai-search.sh +bash test/test-bin-agent-kit.sh +bash test/test-rg-code.sh +bash test/test-fd-files.sh +./scripts/check.sh +./scripts/check-publishable.sh +``` + +### Slice 3 — Misleading freshness command removal or rename + +Goal: eliminate `ai-file-freshness` as a confusing public name. + +Implementation steps: + +1. Choose the canonical destination: `agent-kit search changed-files` for changed-file discovery, or `agent-kit repo status` if a repository-status namespace is introduced. +2. Port useful behavior into the chosen canonical destination. +3. Replace `test/test-misc-wrappers.sh` coverage so it proves the canonical replacement instead of the public wrapper. +4. Removal of the tracked top-level file requires explicit approval. + +Acceptance criteria: + +- Docs do not promote `ai-file-freshness`. +- Tests prove the canonical replacement. +- Public package/install surface does not ship `ai-file-freshness` as a top-level callable command before stable release unless explicitly approved as a temporary alias. + +Verification: + +```bash +bash test/test-misc-wrappers.sh +bash test/test-bin-agent-kit.sh +./scripts/check.sh +./scripts/check-publishable.sh +``` + +### Slice 4 — Packaging and install surface gate + +Goal: removed or de-publicized names must not be shipped by install, Homebrew, npm, or release archives. + +Surfaces to update or test: + +- `install.sh` +- `scripts/package-release.sh` +- `Formula/agent-kit.rb` +- `package.json` +- `npm/cli.js` +- `bin/agent-kit` + +Implementation steps: + +1. Add a publishable/public-surface allowlist or denylist test for pre-stable duplicate names. +2. Ensure internal implementations live somewhere `bin/agent-kit` cannot dispatch by command name. +3. Ensure packaging does not expose removed aliases as top-level executables. +4. Add tests that inspect installed, staged, and package surfaces for absence of duplicate public command names. + +Acceptance criteria: + +- Install payload, release archive, Homebrew install, and npm package do not expose duplicate top-level commands. +- `agent-kit --list` reflects the canonical public surface. +- `./scripts/check-publishable.sh` fails if duplicate public command names reappear. + +Verification: + +```bash +bash test/test-install.sh +bash test/test-bin-agent-kit.sh +./scripts/check-publishable.sh +``` + +### Slice 5 — Final TODO/documentation update + +Goal: make the plan state clear after each slice lands. + +Acceptance criteria: + +- This TODO distinguishes the completed `search capabilities` slice from unfinished consolidation. +- Each completed duplicate-name removal is marked with the canonical replacement and verification evidence. +- Deletion approvals are recorded next to any tracked-file removals. +- Public docs show canonical commands only, especially `agent-kit verify --language ` for per-language verification. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1.0 diff --git a/bin/agent-kit b/bin/agent-kit new file mode 100755 index 0000000..402f913 --- /dev/null +++ b/bin/agent-kit @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +# bin/agent-kit — dispatcher entrypoint for agent-kit. +# +# Resolves SUBCOMMAND to a script under libexec/ and execs it with the remaining +# arguments, mirroring the git- / brew- convention. The +# leading `ai-` is optional, so `agent-kit search` runs libexec/ai-search and +# `agent-kit repo-stats` runs libexec/repo-stats. Every libexec/* file is itself a +# complete, directly-runnable script. +# +# Usage: +# agent-kit [args...] run a command (e.g. `agent-kit search text "TODO"`) +# agent-kit --help show a command's contract and a runnable example +# agent-kit --introspect machine-readable JSON contract for a command +# agent-kit --list list every command with a one-line summary +# agent-kit --help show this help +# agent-kit --version print the AgentKit version +# +# Example: +# agent-kit --list # discover the whole command surface +# agent-kit search text "TODO" . # find every TODO comment in the tree +# agent-kit verify --help # learn a command before you run it + +set -euo pipefail + +# The toolkit's modules use Bash >= 4.4 features (mapfile, associative arrays, +# namerefs). This dispatcher is Bash-3.2 safe on purpose so it can bootstrap: +# if launched under an older Bash (notably macOS's /bin/bash 3.2), re-exec under +# a capable Bash, or fail with a clear message. Guarded against re-exec loops. +if ((BASH_VERSINFO[0] < 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 4))); then + if [[ -z "${_AI_BASH_REEXEC:-}" ]]; then + for _cand in "${TOOL_BASH:-}" /opt/homebrew/bin/bash /usr/local/bin/bash \ + "$(command -v bash 2>/dev/null || true)" /bin/bash; do + [[ -n "$_cand" && -x "$_cand" ]] || continue + _v="$("$_cand" -c 'echo $((BASH_VERSINFO[0]*100+BASH_VERSINFO[1]))' 2>/dev/null || true)" + if [[ "$_v" =~ ^[0-9]+$ ]] && ((_v >= 404)); then + exec env _AI_BASH_REEXEC=1 "$_cand" "${BASH_SOURCE[0]}" "$@" + fi + done + fi + echo "agent-kit: requires Bash >= 4.4 (running ${BASH_VERSION:-unknown})." >&2 + echo " Install a newer Bash (e.g. 'brew install bash') or set TOOL_BASH=/path/to/bash." >&2 + exit 127 +fi + +SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "$SELF_DIR/.." && pwd)" +LIBEXEC_DIR="$ROOT_DIR/libexec" + +print_list() { + echo "Available commands (run 'agent-kit --help' for details):" + echo + if [[ -x "$LIBEXEC_DIR/sh-introspect" ]]; then + "${BASH:-bash}" "$LIBEXEC_DIR/sh-introspect" --list "$LIBEXEC_DIR" + else + find "$LIBEXEC_DIR" -maxdepth 1 -type f | sort | while IFS= read -r f; do + printf ' %s\n' "$(basename "$f")" + done + fi +} + +case "${1:-}" in + ""|--list|list) + print_list + [[ "${1:-}" == "" ]] && exit 2 + exit 0 + ;; + -h|--help) + sed -n '2,25p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' + echo + print_list + exit 0 + ;; + -V|--version) + if [[ -r "$ROOT_DIR/VERSION" ]]; then + printf 'agent-kit %s\n' "$(tr -d '[:space:]' < "$ROOT_DIR/VERSION")" + else + printf 'agent-kit (version unknown)\n' + fi + exit 0 + ;; +esac + +cmd="$1" +shift + +# A command is a single libexec token, never a path. Reject anything with a +# slash or a leading dot so `agent-kit ../../etc/x` can never escape libexec/ and exec +# an arbitrary file. +if [[ ! "$cmd" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then + echo "agent-kit: invalid command name '$cmd' (see 'agent-kit --list')" >&2 + exit 2 +fi + +# Resolve the command file: exact name first, then the `ai-` prefixed form. +target="" +if [[ -f "$LIBEXEC_DIR/$cmd" ]]; then + target="$LIBEXEC_DIR/$cmd" +elif [[ -f "$LIBEXEC_DIR/ai-$cmd" ]]; then + target="$LIBEXEC_DIR/ai-$cmd" +else + echo "agent-kit: unknown command '$cmd' (see 'agent-kit --list')" >&2 + exit 2 +fi + +# Run the subcommand with the SAME bash that is running this dispatcher. The +# installer wrappers (and the Homebrew formula) invoke bin/agent-kit under a Bash >= 4.4, +# so "$BASH" propagates that interpreter to subcommands — important on macOS, +# whose default /bin/bash is 3.2 and cannot run the toolkit's modules. +exec "${BASH:-bash}" "$target" "$@" diff --git a/docs/AI_USAGE.md b/docs/AI_USAGE.md new file mode 100644 index 0000000..09aa028 --- /dev/null +++ b/docs/AI_USAGE.md @@ -0,0 +1,30 @@ +# Using AgentKit with coding agents + +## Operating contract + +Give the agent this instruction: + +> Use the repository's `agent-kit` CLI as the preferred interface for repository search, context collection, editing, rollback, test selection, and verification. Start with `agent-kit --help` and `agent-kit --help`. Respect scopes and guardrails, prefer structured output where available, and run `agent-kit verify` before claiming completion. + +## Recommended sequence + +1. **Discover:** use `agent-kit search` or `agent-kit search batch` instead of recursively loading the repository. +2. **Bound context:** use `agent-kit context pack` or `agent-kit context diff` (or the Repomix helpers under `agent-kit context`) only for relevant files. +3. **Plan:** define allowed paths, blocked paths, deletion policy, and verification steps. +4. **Change:** use guarded editing and preserve snapshots. +5. **Test:** use `agent-kit test select` or `agent-kit test run` (focused) before `agent-kit test all` (the full suite). +6. **Verify:** run `agent-kit verify` (add `docs`/`refs` for documentation or orphaned-file checks) and retain exact evidence. +7. **Recover:** use `agent-kit rollback` (or `agent-kit edit rollback`) when a guarded edit must be reverted. +8. **Checkpoint:** use `agent-kit session checkpoint` before a risky guarded edit. + +## Human review + +Agents must not receive unrestricted host permissions merely because these tools have safety checks. Keep runtime permissions minimal, inspect diffs, review executed commands, and require passing verification before merge. + +## Runtime integration + +- **GitHub Copilot:** repository instructions are supplied through `.github/copilot-instructions.md` and `AGENTS.md`. +- **Claude Code:** `CLAUDE.md` points to the canonical `AGENTS.md` contract. +- **OpenCode and compatible agents:** use the root `AGENTS.md` and the assets under `integrations/`. + +Keep `AGENTS.md` canonical. Runtime-specific files should only bridge to it or add unavoidable runtime details. diff --git a/docs/CI_PERFORMANCE.md b/docs/CI_PERFORMANCE.md new file mode 100644 index 0000000..3603da1 --- /dev/null +++ b/docs/CI_PERFORMANCE.md @@ -0,0 +1,66 @@ +# CI / test-suite performance + +## Rule: profile before guessing + +If a CI job, `scripts/check.sh`, or any `test/test-*.sh` file takes noticeably +longer than expected, profile it immediately — do not guess a cause, add a +speculative timeout, or reach for "maybe it's the secrets/security scan" +without checking. Guessing here previously burned a full investigation +session's worth of tokens across five separate root causes; the profiling +steps below take minutes and point straight at the real cost. + +1. **Per-file split** (which test file is slow): pull the workflow log + (`gh run view --log`), find each `test/test-*.sh`'s `==>` marker + timestamp for the job, and diff consecutive timestamps. This ranks every + file by wall time in one pass, no local reproduction needed yet. +2. **Per-test split** (which test inside that file is slow): copy the file, + wrap its `run_test()` to log `$EPOCHREALTIME` before/after each call to a + scratch file, run it, `sort -rn`. Takes one script, reusable across files. +3. **Don't forget non-test phases.** `scripts/check.sh` also runs `shellcheck` + over every shell file before any test executes — this was the single + largest cost in the whole suite (~93s alone) and would never show up in a + per-test-file breakdown, because it isn't a test file. +4. **Verify every fix against the actual condition**, not "looks faster": + A/B benchmark the old vs. new implementation, or reproduce the exact + before/after with a standalone repro, before touching test files. A fix + that isn't benchmarked isn't confirmed. +5. **Watch for changed correctness, not just speed**, when splitting a slow + step into smaller/parallel units — see the shellcheck case below, where + naive parallelization would have introduced false positives. + +## Findings (2026-07-15 investigation, `feat/project-local-install`) + +| cost | root cause | fix | commit | +|---|---|---|---| +| ~6-7s **per call site**, ~13 call sites across 6 test files | `build_path_without`/`path_without` helpers hid a binary by symlinking every *other* PATH executable into a dir, resolving each file's name via `` basename "$f" `` — a forked process per PATH entry | swap to `${f##*/}` (bash builtin, no fork) + batch the `ln` calls | `72aa5f2` | +| 20s per run | `test_branch_scope_recognized` ran the real verify script with a 20s timeout it reliably hit in full, even though the assertion it checks resolves in <1s | cut timeout to 3s (confirmed empirically sufficient) | `72aa5f2` | +| ~6.5s x 3 tests | `run_with_fake_lychee`'s fixture repo has no commits, so the default scope treated every file as "changed" and pulled in the real trivy/semgrep/osv-scanner security-scan block — unrelated to what those tests check (lychee invocation only) | set `AI_VERIFY_SCOPE=branch` in the fixture's env | `72aa5f2` | +| ~14s / ~9s | `run_guarded`'s CPU-percent sampler sleeps 1s (default) per sample, x2 samples needed to confirm idle | override `AI_GUARD_CPU_SAMPLE=0.2` in the two tests that sample CPU at all (detection accuracy unchanged: a `sleep 30` child reads 0% either window) | `72aa5f2` | +| ~93s (single largest cost in the whole suite) | `scripts/check.sh` ran `shellcheck` once over all 148 shell files | sharded across parallel `shellcheck` processes with `-x`/`--external-sources` (required for correctness — see below) | `31cdcab` | + +**The shellcheck case is worth reading in full before touching this again.** +Naively splitting the file list across parallel single-file `shellcheck` +invocations looked like an easy 30x win (93s → ~3s) but silently introduced +5 false-positive "appears unused" warnings, because shellcheck refuses by +default to follow a `source` statement to a path outside the current +invocation's file list — and this codebase deliberately threads globals like +`$REPO_ROOT` and `$failures` from a root script into sourced `lib/*.sh` +siblings. Adding `-x` (allow `source` anywhere on disk, regardless of the +invocation's own file list) fixes this for real, confirmed by checking each +previously-flagged file in total isolation and getting a clean result +matching the single-invocation baseline exactly. Net result: ~93s → ~41s +(smaller, ~5-file shards — batches past ~20 files were measured to blow up +super-linearly rather than just amortizing better). + +**On the "just skip the secrets check locally" idea:** checked and it +doesn't apply here — gitleaks-based secret scanning is already gated behind +`command -v gitleaks` in every test that needs it and skips cleanly when +gitleaks isn't installed, which is the case in this repo's CI (only +shellcheck/jq/ripgrep get installed). It was never actually running in CI, +so it wasn't a real contributor to the slowness above. + +## Net result + +`test/*.sh` total wall time in CI: 256s → 164s (ubuntu-22.04), 263s → 94s +(ubuntu-24.04, -64%). Full `scripts/check.sh` locally: 3m52s → 3m3s after the +shellcheck fix landed on top of the test-file fixes. diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md new file mode 100644 index 0000000..2cfa8f0 --- /dev/null +++ b/docs/COMMANDS.md @@ -0,0 +1,114 @@ +# Command map + +The exact supported options and output schema are authoritative in each command's `--help` output. +For the packages each command depends on, why, and a real captured example, see +[PACKAGES.md](PACKAGES.md). + +Commands are shown as `agent-kit `. If you set the optional alias +`alias akit='agent-kit'`, the short form `akit ` works everywhere +(see [EXAMPLES.md](EXAMPLES.md)). + +Canonical command groups fuse several single-purpose engines behind one name +(`search`, `context`, `git`, `repo`, `inspect`, `session`, `verify`, `test`, +`edit`); prefer the group form below to avoid guessing which similarly-named +top-level command is the right one. + +| Command | Purpose | +|---|---| +| `agent-kit search` | Scoped repository search across available backends; use `agent-kit search capabilities` for the capability map or `agent-kit search batch` to run one mode against several queries. | +| `agent-kit search-multi` | Compatibility command for batch searches; use `agent-kit search batch`. | +| `agent-kit search-introspect` | Compatibility command for the search capability map. | +| `agent-kit context` | Canonical context-building group (fused engine): `diff` (changed-file bundle), `pack` (repomix/files-to-prompt/code2prompt), `file` (single-file pack), `generate` (full ranked tree), `tree` (tree-pack engine), `status` (freshness check), `ensure` (freshness gate), `estimate` (token cost). `generate`/`tree` still shell out to process-isolated internal engines (`libexec/internal/`) rather than being fully fused, to avoid a confirmed function-name collision risk (`die`/`estimate_tokens` redefinitions) if merged into the shared process. | +| `agent-kit git` | Canonical git-inspection group (fused engine): `origin` (branch-parent detection), `history` (log -S/-G/-L), `blame` (line annotation), `pr-context` (PR metadata/diff/checks/reviews). | +| `agent-kit repo` | Canonical repository-metadata group: `tasks` (defined project tasks), `stats` (tracked-file count), `tools` (full command catalog), `status` (uncommitted docs/config drift). | +| `agent-kit inspect` | Canonical read-only inspection group: `file` (bounded file preview), `data` (structured json/yaml/csv/xml queries), `shell` (static script contract). | +| `agent-kit session` | Canonical session-support group: `checkpoint` (recoverable snapshot), `watch` (re-run a command on file change). | +| `agent-kit verify` | Canonical verification group (fused engine): default/`--language ` (project-aware verification gate), `docs` (documentation lint/links/drift), `refs` (orphaned tracked-file detection). | +| `agent-kit test` | Canonical test group (fused engine): `select` (list relevant tests, read-only), `run` (focused PHPUnit selection), `all` (whole suite, heavy). | +| `agent-kit structured` | Produce machine-readable command output. | +| `agent-kit task` | Run a bounded repository task workflow. | +| `agent-kit edit` | Apply guarded, reviewable edits; `agent-kit edit apply MODE ...` is equivalent to the bare form, `agent-kit edit rollback ...` routes to `agent-kit rollback`. | +| `agent-kit rollback` | Restore a prior guarded-edit state; kept as an independently recoverable engine (never fused into `edit`). | +| `preview-file` | Safely preview bounded file content; also reachable as `agent-kit inspect file`. | +| `session-checkpoint` | Record a session checkpoint; also reachable as `agent-kit session checkpoint`. | + +`repomix-context-tree` and `repomix-scc-router` moved to `libexec/internal/` — no longer public +commands. `repomix-context-tree` backs `agent-kit context tree`/`agent-kit context generate` as a +process-isolated internal engine; `repomix-scc-router` is unwired (private, no public route +approved). `pack-context`, `run-repomix-file`, `ai-diff-context`, `query-usage`, +`repomix-freshness`, and `repomix-ensure-fresh` were deleted; their logic now lives in +`agent-kit context pack|file|diff|estimate|status|ensure` respectively. + +Use `agent-kit --help`, `agent-kit --help`, or the executable's direct `--help` output before automation. Do not infer unsupported flags from this overview. + +## `agent-kit search` in depth + +`agent-kit search` is a single facade over five search backends — `rg`, `git +grep`, `fd`, `git log`/`git diff`, and `ast-grep` — selected by a leading +**mode**. Every mode emits the *same* JSON envelope when `AI_OUTPUT=json` is set, +so callers parse one shape no matter which tool ran underneath. Runnable +examples for each are in [EXAMPLES.md](EXAMPLES.md); the live capability map is +`agent-kit search capabilities`. + +``` +agent-kit search MODE [QUERY] [ROOT] [FLAGS] +``` + +### Modes + +| Family | Modes | Backend | Query? | +|---|---|---|---| +| Content | `text` `tracked` `docs` `tests` `config` `config-key` `deps` `route` | `rg` / `git grep` | required | +| Changed-scope | `changed-text` `staged-text` | `rg` over changed/staged files | required | +| Git | `diff` (`--staged`, `--base REF`), `history` (`--messages`, `--patch`, `-S`/`-G`) | `git diff` / `git log` | required | +| Structural (AST) | `struct` `symbols` `class` `function` `method` `interface` `enum` (`--lang LANG`) | `ast-grep` | required | +| File name | `files` | `fd` | required | +| File lists | `changed-files` `staged-files` | git | none | +| Curated | `todo` `unsafe-patterns` | `rg` | none | +| Special | `doctor` (backend availability), `capabilities` (mode/flag map), `batch MODE Q1 Q2 …` | — | — | + +Common flags across `rg`-backed modes: `--fixed`/`--regex`/`-i`/`--case-sensitive`/`--smart-case`, +`--glob`, `--type`, `--exclude`, `--max-depth`, context (`-C`/`-B`/`-A N`), +output shape (`-l`, `--count`, `--count-matches`), and bounds (`--max-results N` +default 100, `--max-bytes N`). See `agent-kit search --help`. + +### JSON envelope + +With `AI_OUTPUT=json`, every mode returns the same top-level keys: + +```json +{ "schema": "1", "status": "ok", "tool": "ai-search", "query": "…", "mode": "text", + "matches": ["path:line:text", "…"], + "results": [{ "path": "…", "line": 9, "column": 40, "text": "…", + "source_tool": "rg", "root": "/abs/root", "language": null }], + "warnings": [], "errors": [], "limits": { "max_results": 100 }, + "meta": { "returned": 3, "truncated": false } } +``` + +`status` is one of `ok | no_matches | error | unavailable | dry_run | blocked`. +Count/file-only modes add `summary{total_files,total_matches}`; `symbols`/`class` +add `symbols[]`; `diff`/`history` results carry marker/commit metadata. + +### Why use this instead of `rg | …`? + +For a quick grep on your own screen, plain `rg` is the right tool — this facade +does not try to beat it interactively. It earns its place when something *other +than a human* reads the output, or when the search bounds matter: + +- **One stable schema over five tools.** An agent or script parses the same + `status` / `results[]` / `meta.truncated` envelope whether the match came from + `rg`, `git grep`, `fd`, `git log`, or `ast-grep`. Raw pipes give a different + output format per tool that every caller must special-case. +- **One vocabulary for multi-tool pipelines.** `changed-text "x"` collapses + `git diff --name-only … | xargs rg "x"` into a single mode; `diff --base main`, + `history -S`, `symbols --lang`, and `files` (name search via `fd`) are likewise + distinct tools reached through one flag grammar. +- **Bounds and guardrails by default.** Results cap at `max_results: 100` with + `meta.truncated` flagged, `--max-bytes` trims payloads, `.gitignore` is honored, + `doctor` reports missing backends, and `unsafe-all` returns `status: blocked`. + For an autonomous agent these caps keep a bad regex from flooding the context + window. + +Rule of thumb: reach for `rg`/`grep`/`awk` for your own quick lookups; use +`agent-kit search` when a program consumes the results, when the query spans +several search backends, or when output must stay bounded and machine-parseable. diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md new file mode 100644 index 0000000..3695a31 --- /dev/null +++ b/docs/EXAMPLES.md @@ -0,0 +1,242 @@ +# Examples + +One runnable example per command, generated from each command's own `# Example:` +block. + +These snippets use the short **`akit`** alias. Enable it once (add to your shell +rc), then every example below works verbatim: + +```bash +alias akit='agent-kit' +``` + +The canonical command is `agent-kit` — if you have not set the alias, replace +`akit` with `agent-kit`. The authoritative contract for any command is always +`agent-kit --help` (and `--introspect` for JSON). + +> Regenerate this file with: `bash scripts/gen-examples.sh > docs/EXAMPLES.md` + +### `akit context` +ai-context — canonical context-building command group (thin loader). + +```bash +akit context diff unstaged --dry-run # preview a bundle for uncommitted changes +akit context pack auto --include "docs/**/*.md" # bundle docs into one context file +akit context status . # check whether the generated bundle is stale +akit context estimate README.md # estimate the token cost of a file +``` + +### `akit edit` +Guarded edit wrapper for broad repository modifications (thin loader). + +```bash +akit edit --help # see every mode and flag, safely +akit edit sd OldName NewName . --dry-run # preview a rename, changing nothing +akit edit apply sd OldName NewName . --dry-run # same, with explicit "apply" prefix +akit edit rollback list # list rollback snapshots (routes to ai-rollback) +``` + +### `akit file-freshness` +Show which docs/config files have uncommitted changes (git status of key paths). + +```bash +akit file-freshness # list uncommitted changes under docs/, .github/, AGENTS.md +``` + +### `akit git` +ai-git — canonical git-inspection command group (thin loader). + +```bash +akit git origin # print the branch your branch was created from +akit git history S "TODO" README.md # find commits that added/removed "TODO" +akit git blame 1,20 README.md # annotate who last changed lines 1-20 +akit git pr-context 123 --checks # show PR #123 metadata plus CI check status +``` + +### `akit inspect` +Canonical read-only inspection command group (thin router). + +```bash +akit inspect file README.md --range 1:40 # show only lines 1-40 of a file +akit inspect data json package.json '.scripts' # print the "scripts" section with jq +akit inspect shell libexec/ai-search # see what a script accepts, safely +``` + +### `akit repo` +Canonical repository-metadata command group (thin router). + +```bash +akit repo tasks list # list every task this project already defines +akit repo stats # count files Git currently tracks +akit repo tools # see every command and what it does +akit repo status # list uncommitted changes under docs/, .github/, AGENTS.md +``` + +### `akit rollback` +Review and apply repository-local rollback snapshots created by AI tooling sessions. + +```bash +akit rollback list # list restore points (read-only, safe) +akit rollback show SNAPSHOT_ID # preview one snapshot's files (id from `list`) +``` + +### `akit search` +ai-search.sh — unified repository search entrypoint (thin facade). + +```bash +akit search text "TODO" . # find every TODO across the tree (human output) +AI_OUTPUT=json akit search text "emit_json" libexec # same search as the stable JSON envelope agents consume +akit search tracked "ai_search_main" . # search only git-tracked files (git grep, not rg) +akit search changed-text "export" . # grep ONLY the files you changed in the worktree +akit search diff "emit_json" . --base main # search this branch's diff against main +akit search history "AgentKit" . --messages # pickaxe commit history for a string +akit search text "function" libexec --count # per-file match counts + summary{} +akit search text "emit_json" libexec -C 2 # add 2 lines of context around each match +akit search files config . # find files whose NAME contains "config" (fd) +akit search todo . # list curated TODO/FIXME/HACK/XXX markers +akit search doctor # check which search backends are available +akit search capabilities # full mode/flag/env capability map +akit search batch text foo bar . # run one MODE against several queries +``` + +### `akit search-introspect` +ai-search-introspect.sh — print 100% of the modes, flags, env vars, and +per-mode argument contracts that ai-search.sh and ai-search-multi.sh accept. + +```bash +akit search-introspect # print the full ai-search capability map +akit search-introspect --probe # confirm every search mode is reachable +``` + +### `akit search-multi` +Batch wrapper around ai-search.sh: run one safe search MODE against several +queries in a single approved invocation. + +```bash +akit search batch text foo bar . # search two terms in one pass +akit search batch files niri vicinae . # find files matching either name +akit search batch changed-files . # list files changed but not staged +``` + +### `akit session` +Canonical agent-session command group (thin router). + +```bash +akit session checkpoint before-refactor # save a labelled snapshot you can find later +akit session watch "akit verify" # re-run verify whenever files change (Ctrl-C to stop) +``` + +### `akit structured` +Structured data query wrapper for AI agents. + +```bash +akit structured json package.json '.scripts' # print the "scripts" section of package.json with jq +akit structured validate-json composer.json # check that composer.json is valid JSON +akit structured csv data.csv --head 20 # preview the first 20 rows of a CSV file +``` + +### `akit task` +Project task discovery wrapper for AI agents. + +```bash +akit task list # list every task this project already defines +akit task test # print the command to run this repo's tests +akit task verify # print the recommended "verify" command to run +``` + +### `akit test` +ai-test — canonical test-selection/execution command group (thin loader). + +```bash +akit test select changed # list tests for your current changes (read-only) +akit test run --filter FooTest # run only tests matching FooTest +akit test all --help # see options and defaults before running (safe) +``` + +### `akit verify` +Project-aware verification gate for AI-driven changes (thin loader). + +```bash +akit verify --help # see accepted args before running (safe) +akit verify . # verify the change in the current project +akit verify docs links README.md # check links in one doc file (read-only) +akit verify refs docs --ext md # find orphaned markdown docs under docs/ +``` + +### `akit all-f-into-one` +all-f-into-one.sh (formerly all_in_one.sh / combine_files.sh) +Recursively collects filenames and contents, writes them to a single output file at project root. +Prunes ignored directories (entire subtrees) and excludes selected files. +Each file block (header + content + footer) is wrapped inside triple backticks. + +```bash +akit all-f-into-one --help # see what this does without combining anything +akit all-f-into-one --introspect # print the machine-readable JSON contract +``` + +### `akit fd-files` +Repo-aware file discovery wrapper. + +```bash +akit fd-files README . # find files whose name contains "README" +akit fd-files config docs --type md # find markdown files under docs/ matching "config" +``` + +### `akit preview-file` +preview-file.sh — safely preview a slice of a text file with guardrails +(size/byte gate, binary + .git blocking, column truncation). + +```bash +akit preview-file README.md # show the first 200 lines, safely +akit preview-file README.md --range 1:40 # show only lines 1-40 of the file +akit preview-file README.md --dry-run # check a file is previewable (no content) +``` + +### `akit repo-stats` +Count the files Git currently tracks in this repository. + +```bash +akit repo-stats # print how many files Git currently tracks in this repository +``` + +### `akit repo-tool-inventory` +List every toolkit command with its one-line summary (a discoverable map). + +```bash +akit repo-tool-inventory # see every command and what it does +akit repo-tool-inventory --json | jq . # feed the catalog to an agent +``` + +### `akit rg-code` +Production-grade code search wrapper with repo-aware defaults. + +```bash +akit rg-code "TODO" . # find every TODO under the current directory +akit rg-code "function" src --files # list files under src/ that contain "function" +akit rg-code "config" . --mode php # search only PHP files for "config" +``` + +### `akit session-checkpoint` +Create a repository-local checkpoint using the shared snapshot system. + +```bash +akit session-checkpoint # save a snapshot into .ai-logs/snapshots/ +akit session-checkpoint before-refactor # save a labelled snapshot you can find later +``` + +### `akit sh-introspect` +Universal shell-script introspector (static, pure-Bash parser). + +```bash +sh-introspect libexec/ai-search # see what ai-search accepts, safely +sh-introspect --format=json libexec/ai-edit | jq . # machine-readable contract +sh-introspect --list libexec # a discoverable map of every command +``` + +### `akit watch-loop` +Re-run a command automatically whenever watched files change (blocks until Ctrl-C). + +```bash +akit watch-loop "akit verify" # re-run verify whenever files change (Ctrl-C to stop) +akit watch-loop "akit task test" sh,md # re-run tests only when .sh or .md files change +``` diff --git a/docs/PACKAGES.md b/docs/PACKAGES.md new file mode 100644 index 0000000..3789807 --- /dev/null +++ b/docs/PACKAGES.md @@ -0,0 +1,688 @@ +# Packages per command + +What each `agent-kit` command actually shells out to, why, a real captured +example (run against a live clone or an isolated sandbox — see notes per +command), and why it beats reaching for the raw tool directly. The exact +supported flags remain authoritative in each command's `--help`/`--introspect` +output; this file explains *dependencies* and *rationale*, not the full flag +grammar. + +Commands are shown as `agent-kit ` (the leading `ai-` in a +`libexec/ai-*` filename is optional — see [COMMANDS.md](COMMANDS.md)). + +## Quick reference + +| Tier | Packages | Unlocks | +|---|---|---| +| **Core** (required) | `bash` 4.4+, `git`, `ripgrep` (`rg`), `jq` | Baseline for nearly every command — search, git inspection, JSON envelopes. | +| **Optional — search/edit** | `fd`/`fdfind`, `ast-grep`/`sg`, `sd`, `comby` | `search files`, `search struct`/`symbols`/`class`, `edit ast-grep`, `edit sd`, `edit comby`. | +| **Optional — context** | `repomix` (Node), `files-to-prompt`, `code2prompt` | `context pack`/`file`/`generate`/`tree` (auto-detected in that preference order). | +| **Optional — structured data** | `yq`, `mlr` (Miller) or `csvcut`, `xmllint` | `structured yaml`/`csv`/`xml`, `inspect data`. | +| **Optional — git/PR** | `gh` (GitHub CLI) | `git pr-context`. | +| **Optional — verify/test** | `lychee`, `markdownlint`, `vendor/bin/phpunit`/`paratest`, `bats` | `verify docs links`/`markdownlint`, `test run`/`all` (consumer-project test runners, not agent-kit's own suite). | +| **Optional — watch/session** | `watchexec` or `entr`, `tar` | `session watch`/`watch-loop`, `session checkpoint`/`session-checkpoint` (untracked-file archive). | +| **Optional — misc** | `bat`, `just`, `osascript` (macOS) | Prettier `preview-file` output, `repo tasks`/`ai-task` justfile detection, `all-f-into-one` completion notification. | + +See [INSTALL.md](../INSTALL.md) for install instructions and the macOS Bash note. The +root [README.md](../README.md) "Runtime" section lists the same core/optional split +at a glance. + +--- + +### `agent-kit context` (`libexec/ai-context`) + +**What it does:** Canonical context-building command group — fuses `diff`, `pack`, `file`, `generate`, `tree`, `status`, `ensure`, and `estimate` into one entrypoint for building/checking AI context bundles. + +**Safety:** read-only for `status`/`diff --dry-run`/`estimate`; `pack`/`file`/`generate` write files (tested in an isolated sandbox). + +**Packages used:** +| Package | Why | +|---|---| +| `git` | `estimate.sh` uses `git ls-files` to size a repo's tracked bytes; `diff.sh`/pack backends diff against git state. | +| `rg` | `estimate.sh` falls back to `rg --files` when not inside a git repo. | +| `jq` | `pack.sh`/`status.sh` build/parse the JSON manifest and session envelopes. | +| `repomix` | Primary context-packer backend for `pack`/`file`/`generate`/`tree` (Node-based bundler producing the XML/token-counted context file). | +| `files-to-prompt` / `code2prompt` | Alternative pack backends, auto-detected via `command -v` when `repomix` is absent. | + +**Real-world example** +```bash +$ agent-kit context status . +{ + "schema": "1", "tool": "repomix-freshness", "status": "missing", + "manifest": "/home/.../agent-kit/.repomix-context/tree-context/run-manifest.json", + "regenerate": "agent-kit context generate .", + "message": "no Repomix context manifest at .repomix-context/tree-context/run-manifest.json" +} + +$ agent-kit context diff unstaged --dry-run +{ + "dry_run": true, "label": "unstaged", + "output": ".repomix-context/diff/unstaged-20260714-013311.xml", + "file_count": 4, "estimated_input_tokens": 446402, "token_budget": 80000 +} + +# sandbox only — real write: +$ agent-kit context pack repomix --include "*.md" +✔ Packing completed successfully! +Total Files: 1 files Total Tokens: 406 tokens +``` + +**Why this beats the raw command:** Every mode returns one consistent JSON envelope instead of each backend's own ad-hoc output. `diff --dry-run` computes `estimated_input_tokens` vs. `token_budget` *before* anything is written — raw `repomix`/`git diff` piping doesn't give you that. `pack` auto-detects whichever of three different packer binaries is actually installed, instead of requiring the caller to know which one is present. + +--- + +### `agent-kit edit` (`libexec/ai-edit`) + +**What it does:** Guarded repository-edit entrypoint over four modes (`ast-grep`, `comby`, `sd`, `patch`) with a mandatory dry-run-first workflow and automatic pre-apply snapshotting. + +**Safety:** guarded-mutation — always tested in an isolated sandbox, never against a real project directly from this doc's examples. + +**Packages used:** +| Package | Why | +|---|---| +| `rg` | Plans `sd`-mode replacements with `rg --count-matches` to produce an exact per-file count before anything touches disk. | +| `sd` | Actual text-replacement engine invoked once per planned file. | +| `ast-grep`/`sg` | Structural AST-aware rewrite engine for `ast-grep` mode. | +| `comby` | Generic structural rewrite engine for `comby` mode (`-in-place`). | +| `git` | `patch` mode preflights with `git apply --check`/`--numstat`, applies with `git apply --whitespace=warn`; also powers snapshot/rollback. | +| `jq` | Builds/parses the planned-changes JSON and session manifest across all modes. | + +**Real-world example** +```bash +$ agent-kit edit sd OldName NewName /tmp/sandbox --dry-run # AI_OUTPUT=json +{ + "schema": "ai.edit/v1", "status": "dry_run", "mode": "sd", + "plannedChanges": [ + {"path": ".../sample.md", "replacements": 2, "bytes": 59}, + {"path": ".../sample.sh", "replacements": 1, "bytes": 33} + ], + "limits": {"maxFiles": 50, "maxReplacements": 500, "maxBytes": 2000000} +} + +# real apply, sandbox only: +$ agent-kit edit patch /tmp/sandbox/staged.diff . --apply +{"status": "applied", "mode": "patch", + "changedFiles": [".ai-logs/snapshots/....pre-edit-013611.patch", + ".ai-logs/snapshots/....pre-edit-013611.untracked.tar.gz", "sample.md"]} +``` + +**Why this beats the raw command:** Every apply is preceded by a snapshot (`.patch` + untracked-file tarball + manifest under `.ai-logs/snapshots/`) — a real rollback path raw `sd`/`git apply`/`comby -in-place` never create on their own. `sd` mode plans with `rg --count-matches` first so the caller sees exact counts before any bytes change, and hard bounds (`--max-files`, `--max-replacements`, `--max-bytes`) reject runaway edits. `patch` mode preflights with `git apply --check` plus a path denylist (blocks `.git`, secret-like paths) before ever applying. + +--- + +### `agent-kit file-freshness` (`libexec/ai-file-freshness`) + +**What it does:** Prints `git status --short` scoped to `docs`, `.github`, `.opencode`, and `AGENTS.md` — a quick check for uncommitted changes to agent-facing files. + +**Safety:** read-only + +**Packages used:** +| Package | Why | +|---|---| +| `git` | The entire script is one line: `git status --short docs .github .opencode AGENTS.md`. | + +**Real-world example** +```bash +$ agent-kit file-freshness +(no output — docs/.github/.opencode/AGENTS.md all clean) +``` + +**Why this beats the raw command:** It's a curated path list an agent doesn't have to remember or guess — a bare `git status --short` also surfaces every unrelated untracked/dirty path in the repo, which is noise for a "did the agent-facing docs drift" check. + +--- + +### `agent-kit git` (`libexec/ai-git`) + +**What it does:** Canonical git-inspection command group — fuses branch-origin detection, commit-history search (`-S`/`-G`/`-L`), `blame`, and GitHub PR context. + +**Safety:** read-only (`origin`/`history`/`blame`); `pr-context` needs network + `gh`. + +**Packages used:** +| Package | Why | +|---|---| +| `git` | `origin.sh` uses `for-each-ref`/`merge-base`/`rev-list --count` to find the most-likely parent branch; `forensics.sh` wraps `git log -S/-G/-L` and `git blame -L`. | +| `jq` | JSON-mode output assembly across `origin.sh`/`forensics.sh`/`pr-context.sh`. | +| `gh` | `pr-context.sh` calls `gh pr view/checks/diff` for PR metadata, CI status, and diff content. | + +**Real-world example** +```bash +$ agent-kit git origin --json +{"status":"ok","current_branch":"release/v0.1.0-prep","origin_branch":"main", + "merge_base":"e79e3ed0...","distance":2} + +$ agent-kit git blame 1,5 README.md +55699711 (Utmost Creator 2026-07-13 17:50:47 +0100 1)
+55699711 (Utmost Creator 2026-07-13 17:50:47 +0100 3) # 🧰 AgentKit +``` + +**Why this beats the raw command:** `origin` ranks multiple candidate parent branches by commit distance and returns the winner plus every candidate considered — a single raw `git merge-base` call can't do that without the caller already knowing the right branch name. `pr-context --pack` routes into the context engine to bundle the PR diff as a token-estimated artifact in one call. + +--- + +### `agent-kit inspect` (`libexec/ai-inspect`) + +**What it does:** Canonical read-only inspection command group — routes `file` (safe preview), `data` (JSON/YAML/CSV/XML query), and `shell` (static shell-script contract introspection). + +**Safety:** read-only + +**Packages used:** +| Package | Why | +|---|---| +| `jq` | `data json`/`validate-json` modes and every JSON envelope emitted downstream. | +| `xmllint` | `data xml` mode for `--xpath` queries or pretty-formatting. | +| `rg`, `git` | Underlying `sh-introspect` static-parsing dependency set. | + +**Real-world example** +```bash +$ agent-kit inspect file README.md --range 1:5 --plain +
+ +# 🧰 AgentKit +... + +$ agent-kit inspect data json package.json '.name, .version' +"@utmostcreator/agent-kit" +"0.1.0" +``` + +**Why this beats the raw command:** `inspect file` has a real safety gate — a 64KiB size cap, binary-content blocking, and `.git`-internals blocking (bypassable only with an explicit `--force`) — so an agent can't accidentally dump a huge binary or `.git/objects` blob into context the way a raw `cat`/`head` would. + +--- + +### `agent-kit repo` (`libexec/ai-repo`) + +**What it does:** Canonical repository-metadata command group — routes `tasks`, `stats`, `tools`, and `status`. + +**Safety:** read-only + +**Packages used:** +| Package | Why | +|---|---| +| `git` | `stats` is `git ls-files \| wc -l`; `status` is `git status --short` on the curated path list. | +| `jq` | `tasks` reads `package.json`/`composer.json` script blocks and normalizes them to JSON; `tools` aggregates every command's `sh-introspect --format=json` output. | +| `just` | `tasks` shells out to `just --summary` when a `justfile` is present. | + +**Real-world example** +```bash +$ agent-kit repo stats +179 + +$ agent-kit repo tasks list +{"package_manager":"npm","package_scripts":{},"composer_scripts":{}, + "just_tasks":[],"make_tasks":[],"taskfile_tasks":[]} +``` + +**Why this beats the raw command:** `repo tasks` normalizes four different task-declaration ecosystems (npm scripts, composer scripts, `just --summary`, Makefile targets) into one JSON shape instead of requiring the caller to know which of `cat package.json`, `composer.json`, `just --summary`, or `grep Makefile` applies. + +--- + +### `agent-kit rollback` (`libexec/ai-rollback`) + +**What it does:** Reviews and applies repository-local guarded-edit snapshots (`.ai-logs/snapshots/`) created by other AgentKit tooling. + +**Safety:** read-only for `list`/`show`; `apply`/`prune` are mutating and gated behind an interactive confirmation. + +**Packages used:** +| Package | Why | +|---|---| +| `git` | `show` uses `git show --stat`/`git apply --stat` to preview a diff without touching the working tree; `apply` uses git plumbing to restore tracked files. | +| `jq` | Filters manifest JSON down to relevant fields for display. | +| `find` | Globs `.ai-logs/snapshots/*.manifest.json`/`*.patch`/`*.ref`. | + +**Real-world example** +```bash +$ agent-kit rollback list +SNAPSHOT TYPE SIZE DATE +==================================================================================== +session-checkpoint-20260714-013358-....patch legacy-patch 0 2026-07-14 01:33 +ai-edit-20260714-013356-106376-pre-edit-013356.patch legacy-patch 0 2026-07-14 01:33 +6 snapshot artifact(s) found +``` + +**Why this beats the raw command:** It resolves a fuzzy session/id prefix to the exact snapshot file, distinguishes manifest-based vs. legacy `.patch`/`.ref` formats and dispatches to the right preview path automatically, and gates destructive `apply`/`prune` behind a confirmation prompt — a raw `git apply` on a stale patch has none of that. + +--- + +### `agent-kit search` (`libexec/ai-search`) + +**What it does:** Unified repository search entrypoint dispatching to rg/git-grep/fd/git-log/ast-grep backends by named "mode", normalized into a stable envelope. + +**Safety:** read-only + +**Packages used:** +| Package | Why | +|---|---| +| `rg` | `text`/`docs`/`tests`/`config`/`deps`/`todo`/`unsafe-patterns` modes run `rg --json -H -n ...`. | +| `git` | `tracked` uses `git grep`; `changed-files`/`staged-files` use `git diff --name-only`/`git ls-files`; `diff`/`history` use `git log`/`git diff -U0`. | +| `fd`/`fdfind` | `files` mode; falls back to `git ls-files`/POSIX `find` with a warning if absent. | +| `ast-grep` | `struct`/`symbols`/`class` via `ast-grep run --lang ... --pattern ... --json`; fails closed if missing. | +| `jq` | Builds/reshapes the JSON result envelope throughout. | + +**Real-world example** +```bash +$ agent-kit search doctor +ai-search doctor: ok + +$ AI_OUTPUT=json agent-kit search text "TODO" . --files-with-matches +{"schema":"1","status":"ok","tool":"ai-search","query":"TODO","mode":"text", + "matches":["./INSTALL.md:122:...","./libexec/ai-git:16:...", ...]} +``` + +**Why this beats the raw command:** `doctor` checks jq/git/rg/ast-grep/fd availability in one call and reports which mode degrades, instead of a raw command silently erroring. Every backend normalizes into one JSON envelope (`schema`/`status`/`matches`/`warnings`), so downstream tooling doesn't need per-backend parsing logic for rg vs. git-grep vs. ast-grep output shapes. + +--- + +### `agent-kit search-introspect` (`libexec/ai-search-introspect`) + +**What it does:** Prints the full mode/flag/env-var capability map for `ai-search`/`ai-search-multi`, parsed live from source. + +**Safety:** read-only + +**Packages used:** +| Package | Why | +|---|---| +| `awk` | Parses the mode-family `case` blocks out of `lib/ai-search/modes.sh` directly from source. | + +**Real-world example** +```bash +$ agent-kit search-introspect +== MODES (grouped by family, parsed from ai-search.sh) == +Content-search (QUERY required): changed-text class config config-key deps diff docs enum files ... +File-list (no QUERY): changed-files staged-files +No-query curated: todo unsafe-patterns +== ENVIRONMENT VARIABLES == + AI_LANG AI_OUTPUT AI_SEARCH_MULTI_MAX AI_SEARCH_STRICT +``` + +**Why this beats the raw command:** Because it parses `modes.sh`/`contract.sh`/`parse-flags.sh` directly, the capability map can't drift out of sync with the real `ai-search` implementation the way a hand-written README table can. + +--- + +### `agent-kit search-multi` / `agent-kit search batch` (`libexec/ai-search-multi`) + +**What it does:** Runs one `ai-search` mode against multiple queries in a single invocation. + +**Safety:** read-only + +**Packages used:** +| Package | Why | +|---|---| +| (delegates entirely to `ai-search`) | Re-invokes `libexec/ai-search` as a subprocess per query — same transitive package set as `agent-kit search` (rg, git, fd, ast-grep, jq). | + +**Real-world example** +```bash +$ AI_OUTPUT=json agent-kit search batch text foo bar . --files-with-matches +[ + {"schema":"1","status":"ok","query":"foo","matches":["./libexec/all-f-into-one:5:...", ...]}, + {"query":"bar", ...} +] +``` + +**Why this beats the raw command:** It caps batch size (`AI_SEARCH_MULTI_MAX`, default 20) to prevent an accidental fork-bomb of subprocess searches, rejects `unsafe-all` outright, and passes each query as a discrete quoted positional argument with no `eval`/`sh -c` — query text can't be interpreted as shell metacharacters the way a hand-rolled `for q in foo bar; do rg "$q"; done` loop risks if a caller forgets to quote. + +--- + +### `agent-kit session` (`libexec/ai-session`) + +**What it does:** Thin router for `session checkpoint [label]` (save a snapshot) and `session watch ` (re-run on file changes, blocks until Ctrl-C). + +**Safety:** `checkpoint` is additive-only (new files under gitignored `.ai-logs/snapshots/`); `watch` blocks. + +**Packages used:** +| Package | Why | +|---|---| +| `git` | `checkpoint` uses `git rev-parse HEAD`/`git diff` to build the patch and manifest. | +| `jq` | Builds the `.manifest.json` and structured log line. | +| `watchexec` (preferred) / `entr` (fallback) | `watch` requires one of these; `entr` path pipes `rg --files` into `entr -r`. | + +**Real-world example** +```bash +$ agent-kit session checkpoint report-test +checkpoint created: .ai-logs/snapshots/session-checkpoint-20260714-013358-....manifest.json +``` +(`.ai-logs/` is in `.gitignore`, so `git status --short` shows no change from this.) + +**Why this beats the raw command:** It bundles a `git diff`/`HEAD` capture, an untracked-file archive, and a jq-built manifest into one atomic, labelled, timestamped artifact — a raw `git diff > x.patch` loses untracked files and has no manifest/label/session-id metadata for later lookup by `rollback list`/`show`. + +--- + +### `agent-kit structured` (`libexec/ai-structured`) + +**What it does:** Structured data query wrapper — one subcommand per format: `json` (jq), `yaml` (yq), `validate-json`/`validate-yaml`, `csv` (miller/csvcut/head), `xml` (xmllint). + +**Safety:** read-only + +**Packages used:** +| Package | Why | +|---|---| +| `jq` | `json`/`validate-json` run `jq "$query" "$file"` / `jq empty "$file"`. | +| `yq` | `yaml`/`validate-yaml` run `yq "$query" "$file"` / `yq '.' "$file"`. | +| `mlr` (Miller) / `csvcut` | `csv` prefers `mlr --icsv --opprint head -n N`, falls back to `csvcut \| head`, then plain `head`. | +| `xmllint` | `xml` runs `xmllint --xpath`/`--format`; no fallback if absent. | + +**Real-world example** +```bash +$ agent-kit structured json package.json '.bin' +{ + "agent-kit": "npm/cli.js" +} +``` + +**Why this beats the raw command:** It picks the right tool per format behind one consistent `structured FILE QUERY` interface instead of requiring the caller to remember which binary handles which format, and validates the file exists before invoking the parser. + +--- + +### `agent-kit task` (`libexec/ai-task`) + +**What it does:** Discovers a project's already-defined task commands (npm/composer/just/make/Taskfile) and recommends the right `verify`/`test` command instead of guessing. + +**Safety:** read-only + +**Packages used:** +| Package | Why | +|---|---| +| `jq` | Reads `.scripts`/`.packageManager` from `package.json` and `.scripts` from `composer.json`, assembles the inventory JSON. | +| `just` | If a `justfile` exists, `just --summary` lists just-tasks and is preferred in `recommend_command`. | +| `yq` | If a `Taskfile.yml`/`.yaml` exists, `yq -o=json '.tasks \| keys'` extracts task names. | + +**Real-world example** +```bash +$ agent-kit task test +scripts/ai/ai-verify.sh . + +$ agent-kit task verify +scripts/ai/ai-verify.sh . +``` + +**Why this beats the raw command:** It actually inspects `package.json`, `composer.json`, `justfile`, `Makefile`, and `Taskfile.yml` before recommending anything, instead of guessing a generic `npm test` that could fail if the project uses a different task runner. + +--- + +### `agent-kit test` (`libexec/ai-test`) + +**What it does:** Canonical test group — `select` maps changed files/symbols to relevant tests, `run` executes a focused PHPUnit selection, `all` runs every discovered suite (paratest/phpunit/bats) with parallel-first defaults. These target a *consumer* project's own test suite (e.g. this repo's own `scripts/check.sh`/bats tests), not agent-kit's internal test format specifically. + +**Safety:** read-only for `select`; `run`/`all` are guarded-mutation (heavy test execution). + +**Packages used:** +| Package | Why | +|---|---| +| `git` | `select changed` builds the candidate file set from `git diff --name-only`, `git diff --cached --name-only`, `git ls-files --others --exclude-standard`. | +| `jq` | Renders every selection mode's output as JSON. | +| `rg` | `select symbol` uses `rg -l --hidden` to find files referencing a given symbol. | +| `vendor/bin/phpunit` | `run` execs `vendor/bin/phpunit --configuration phpunit.xml.dist "$@"` in a PHP consumer project. | +| `vendor/bin/paratest` (falls back to phpunit) | `all` prefers parallel `paratest --runner=WrapperRunner`, falls back to serial phpunit. | +| `timeout`/`gtimeout` | Wraps focused/all runs with a kill-after timeout when available. | +| `bats` | `all` also discovers and runs a Bats shell-test suite if present. | + +**Real-world example** +```bash +$ agent-kit test select changed +{"input_files": [...], "candidate_tests": [], "recommended_commands": []} + +$ agent-kit test all --help +ai-test/run-all.sh — run the repository's existing test suites with +parallel-first defaults (the HEAVY, whole-suite runner). +Example: + PARATEST_PROCS=8 agent-kit test all +``` + +**Why this beats the raw command:** `select changed` only recommends tests actually touched by the current diff instead of blindly running the whole suite; `run`/`all` centralize timeout-wrapping and paratest/phpunit auto-detection that a hand-typed `vendor/bin/phpunit` command doesn't have. + +--- + +### `agent-kit verify` (`libexec/ai-verify`) + +**What it does:** Project-aware verification gate; the root command runs a full change-scoped pipeline, `verify docs` checks markdown lint/links/drift, `verify refs` finds orphaned tracked files. + +**Safety:** read-only for `verify docs`/`verify refs`; the full root pipeline can run linters/tests. + +**Packages used:** +| Package | Why | +|---|---| +| `git` | `verify refs` builds its candidate list from `git ls-files`. | +| `rg` | `verify refs` searches for references to each candidate basename via `rg --fixed-strings --files-with-matches`. | +| `jq` | Emits the refs-orphan report as JSON, used throughout docs-check/reporting. | +| `lychee` | `verify docs links` runs `lychee --offline --accept "200..=299,403,429"` — offline-only, never dials live URLs. | +| `markdownlint` | `verify docs markdownlint`/`all` runs it if installed, else warns and skips. | + +**Real-world example** +```bash +$ agent-kit verify docs links README.md +🔍 23 Total 🔗 21 Unique ✅ 11 OK 🚫 0 Errors 👻 12 Excluded + +$ agent-kit verify refs docs --ext md +docs/SECURITY_MODEL.md +``` + +**Why this beats the raw command:** `verify docs links` forces `lychee --offline` so link-checking can never make a live network call even if a doc has external URLs; `verify refs` cross-references `git ls-files` against `rg` hits per-basename to flag orphaned docs — something a plain `lychee`/`grep` sweep wouldn't assemble on its own. + +--- + +### `agent-kit all-f-into-one` (`libexec/all-f-into-one`) + +**What it does:** Recursively collects tracked-tree files (pruning `.git`/`node_modules`/`dist`/etc.) and writes their contents into one `combined_output.txt` at the current project root. + +**Safety:** guarded-mutation — writes at `$(pwd)`; tested only in an isolated sandbox. + +**Packages used:** +| Package | Why | +|---|---| +| `find` | Walks the tree and prunes ignored directory subtrees/excluded files via a dynamically built `find ... -prune -o ... -print0` expression. | +| `sh-introspect` (repo-internal) | `--help`/`--introspect` exec the sibling script to statically parse this one's own contract. | +| `osascript` (optional, macOS only) | Desktop notification on completion; no-op elsewhere. | + +**Real-world example** +```bash +$ cd /tmp/sandbox && all-f-into-one +Success: Combined file created at: /tmp/sandbox/combined_output.txt + +$ cat combined_output.txt +===== START FILE: docs/sample.md ===== +# Sample doc +Some text. +===== END FILE: docs/sample.md ===== +``` + +**Why this beats the raw command:** It auto-prunes `.git`/`node_modules`/`dist`/`build`/`.next`/`.venv` subtrees (a hand-rolled `find . -type f | xargs cat` would dump `.git` internals and vendor trees too), rotates any prior `combined_output.txt` to a timestamped `.bak` instead of clobbering it, and wraps each file in a machine-parseable `START FILE`/`END FILE` block. + +--- + +### `agent-kit fd-files` (`libexec/fd-files`) + +**What it does:** Repo-aware file discovery wrapper around `fd` (falling back to `rg --files` if `fd`/`fdfind` isn't installed), pre-excluding `vendor`, `node_modules`, `dist`, `.git`, `.repomix-context`. + +**Safety:** read-only + +**Packages used:** +| Package | Why | +|---|---| +| `fd`/`fdfind` | Primary discovery engine, invoked with a standard exclusion set. | +| `rg` | Fallback path when neither `fd`/`fdfind` exists. | +| `jq` | Required unconditionally; renders `--json` output as a JSON array. | + +**Real-world example** +```bash +$ agent-kit fd-files README . +./README.md + +$ agent-kit fd-files SECURITY docs --type md --json +["docs/SECURITY_MODEL.md"] +``` + +**Why this beats the raw command:** It bakes in the standard noise-exclusion set on every call and transparently degrades from `fd` to `rg --files` with equivalent filtering when `fd` isn't installed. + +--- + +### `agent-kit preview-file` (`libexec/preview-file`) + +**What it does:** Safely previews a bounded slice of a text file (`--range`, `--around`/`--context`, or `--lines`, default first 200 lines) with size/binary/`.git`-path guardrails and per-line column truncation. + +**Safety:** read-only + +**Packages used:** +| Package | Why | +|---|---| +| `sed` | Extracts the requested line range/window. | +| `wc` | Computes file byte size for the 64KiB max-bytes gate and total line count. | +| `tr` | Counts NUL bytes to detect and block binary files unless `--force`. | +| `awk` | Truncates any displayed line longer than `--max-columns` (default 200). | +| `jq` | Builds the structured JSON envelope when `AI_OUTPUT=json`. | +| `bat` (optional) | Syntax-highlighted pretty-print when installed and not `--plain`. | + +**Real-world example** +```bash +$ agent-kit preview-file README.md --range 1:20 --plain +
+ +# 🧰 AgentKit +... +``` + +**Why this beats the raw command:** Unlike a raw `sed -n '1,20p' file`, it gates on file size and NUL-byte binary detection first so a large or binary file can't flood the agent's context, blocks `.git/` internal paths outright, and truncates over-long lines. + +--- + +### `agent-kit repo-stats` (`libexec/repo-stats`) + +**What it does:** Counts the files Git currently tracks in this repository. + +**Safety:** read-only + +**Packages used:** +| Package | Why | +|---|---| +| `git` | The entire logic is `git ls-files \| wc -l`. | +| `wc` | Counts the lines/paths `git ls-files` emits. | + +**Real-world example** +```bash +$ agent-kit repo-stats +179 +``` + +**Why this beats the raw command:** It's a documented, discoverable one-liner that also plugs into the toolkit's uniform `--help`/`--introspect` contract, so an agent can learn its exact behavior without executing it or reading source. + +--- + +### `agent-kit repo-tool-inventory` (`libexec/repo-tool-inventory`) + +**What it does:** Lists every toolkit command with its one-line summary, statically parsed from each script's header comment (never executed). + +**Safety:** read-only + +**Packages used:** +| Package | Why | +|---|---| +| `jq` | Reshapes each command's `sh-introspect --format=json` output into `{name, summary}` and slurps them into one JSON envelope. | + +**Real-world example** +```bash +$ agent-kit repo-tool-inventory | head -3 + ai-context ai-context — canonical context-building command group (thin loader). + ai-edit Guarded edit wrapper for broad repository modifications (thin loader). + ai-file-freshness Show which docs/config files have uncommitted changes (git status of key paths). +``` + +**Why this beats the raw command:** There is no raw-command equivalent — it's a purpose-built catalog generator that never executes any listed script (static parsing only), so surveying the whole command surface, including ones with side effects like `edit`/`rollback`, is guaranteed side-effect-free. + +--- + +### `agent-kit rg-code` (`libexec/rg-code`) + +**What it does:** Production-grade code search wrapper with repo-aware defaults, built on `rg`. + +**Safety:** read-only + +**Packages used:** +| Package | Why | +|---|---| +| `rg` | The core search engine for every mode (`default`/`all`/`php`/`js`/`blade`/`kotlin`/`config`). | +| `jq` | Only in `--json` output mode, reshaping ripgrep's native `--json` match stream into a flat array. | +| `git` | Only in `tracked` mode, shelling out to `git grep` instead of `rg`. | + +**Real-world example** +```bash +$ agent-kit rg-code "snapshot_create" . --files +./libexec/session-checkpoint +./lib/snapshot.sh +./lib/ai-edit/main.sh +``` + +**Why this beats the raw command:** Raw `rg` requires remembering to exclude `vendor`, `node_modules`, `dist`, `.git`, `.repomix-context`, minified/lockfiles, and snapshot files every time; `rg-code` bakes a base exclude list into every mode and gives named, pre-globbed modes instead of hand-writing `-g` patterns each time. + +--- + +### `agent-kit session-checkpoint` (`libexec/session-checkpoint`) + +**What it does:** Creates a repository-local checkpoint (patch + manifest + untracked-file archive) using the shared snapshot system. + +**Safety:** additive (new files under `.ai-logs/snapshots/`, never modifies existing tracked files) + +**Packages used:** +| Package | Why | +|---|---| +| `git` | `git rev-parse`, `git diff --binary HEAD` captures the working-tree diff; `git ls-files --others --exclude-standard` lists untracked files. | +| `jq` | Builds the JSON manifest (`version`, `session`, `label`, `base_ref`, timestamps) and the final log event. | +| `tar` | Archives untracked files so a rollback can restore them; degrades gracefully with a warning if missing. | + +**Real-world example** +```bash +$ agent-kit session-checkpoint doc-research-test +checkpoint created: .ai-logs/snapshots/session-checkpoint-20260714-013356-....manifest.json +``` + +**Why this beats the raw command:** A raw `git diff`/`git stash` only covers tracked-file changes; this also snapshots *untracked* files (via the `tar` archive) and writes a structured, session-tagged JSON manifest that `agent-kit rollback` can later parse to restore both tracked and untracked state. + +--- + +### `agent-kit sh-introspect` (`libexec/sh-introspect`) + +**What it does:** Universal shell-script introspector — statically parses a Bash/Zsh script's header comments to report description, usage, examples, flags, env vars, and required commands, without ever executing the target. + +**Safety:** read-only + +**Packages used:** +| Package | Why | +|---|---| +| `awk` | Core parsing engine — state machines walk the leading `#` comment block and heredoc bodies without sourcing/executing the file. | +| `grep` | Regex-matches flag case labels, env-var references, and infers required binaries from `command -v X` calls. | +| `jq` | Used only for `--format=json` rendering, building the `ai.sh-introspect/v1` JSON envelope. | + +**Real-world example** +```bash +$ agent-kit sh-introspect libexec/ai-search | head -4 +ai-search +ai-search.sh — unified repository search entrypoint (thin facade). +Usage: + agent-kit search [query] [root] [flags] +``` + +**Why this beats the raw command:** `--help` on a shell script normally means either reading raw source (risky for scripts with side effects like `edit`/`rollback`) or trusting that script's own flag parser; `sh-introspect` guarantees the target is **never executed** and produces the same output whether rendered as a human report, a compact `--help` snippet, or a stable JSON contract. + +--- + +### `agent-kit watch-loop` (`libexec/watch-loop`) + +**What it does:** Re-runs a command automatically whenever watched files change (blocks until Ctrl-C). + +**Safety:** additive (writes an append-only JSON event log at `.ai-logs/watch-loop.jsonl`; does not modify/delete existing files) + +**Packages used:** +| Package | Why | +|---|---| +| `watchexec` | Preferred watcher — `watchexec --debounce ... -e "$extensions" -- bash -lc "$command"` when present. | +| `entr` | Fallback watcher: pipes `rg --files` output into `entr -r bash -lc "$command"`. | +| `rg` | Only used to build the watch-file list for the `entr` fallback path. | +| `jq` | Writes each start event as a JSON line. | + +**Real-world example** +```bash +$ timeout 3 agent-kit watch-loop "echo hi" README.md +[Running: bash -lc echo hi] + +[Command was successful] +``` +(killed cleanly by `timeout 3`, exit code 124, as intended for this doc's example) + +**Why this beats the raw command:** Raw `watchexec`/`entr` invocations require remembering debounce flags, per-tool syntax differences, and manual exclude-globbing; `watch-loop` picks whichever watcher is installed, applies a consistent configurable debounce, and normalizes the invocation across both backends, degrading with a clear error if neither tool exists. diff --git a/docs/SECURITY_MODEL.md b/docs/SECURITY_MODEL.md new file mode 100644 index 0000000..b2d2a32 --- /dev/null +++ b/docs/SECURITY_MODEL.md @@ -0,0 +1,56 @@ +# Security model + +## Goals + +- Reduce accidental out-of-scope repository reads and writes. +- Make command execution, edits, tests, and verification observable. +- Preserve rollback paths and original repository state. +- Prevent secrets and local session evidence from entering releases. + +## Non-goals + +- Operating-system sandboxing. +- Protection from a fully compromised user account or runner. +- Automatic trust of repository instructions or third-party tools. +- Proof that AI-generated changes are correct or secure. + +## Trust boundaries + +Repository files, branch names, commit messages, issue text, pull-request content, generated context, and agent output are untrusted input. They must not be interpolated into executable shell strings. External tools and GitHub Actions are dependencies that require version control and review. + +## Required controls + +- Least-privilege agent permissions. +- Explicit allowed and blocked paths. +- Guarded execution with timeouts and process-tree cleanup. +- Snapshot or rollback capability before mutation. +- Secret redaction and generated-log exclusion. +- Verification evidence before completion. +- Human review before merge or release. +- Workflow static analysis (`actionlint` + `zizmor`) on every push and pull request. + +## Third-party GitHub Actions + +CI and release workflows use zero third-party GitHub Actions in their +critical path — `ci.yml` and `release.yml`'s core steps use raw `git`/`gh` +commands instead of `actions/checkout` or similar, specifically to avoid +supply-chain risk from marketplace Actions. Three deliberate exceptions +exist, all read-only, audit-only, or attestation-only (never able to affect +what ships), and all pinned to a full 40-character commit SHA, never a +floating tag: `.github/workflows/scorecard.yml` (OpenSSF Scorecard, +informational only, never gates a PR); `release.yml`'s `attest` job +(`actions/attest-build-provenance`, runs only after a release is already +published, in its own permission-scoped job); and `step-security/harden-runner` +as the first step of every job that does real network activity (`checks`, +`workflow-security`, `release`, `attest`, `scorecard`'s `analysis`), running +in `egress-policy: audit` mode — it only observes and logs outbound network +calls, never blocks any, and needs no allowlist to configure or maintain +(the `required` job is deliberately skipped: it makes no network calls, just +inspects prior job results). Any future third-party Action must follow the +same policy: full-SHA pin, minimal job-scoped permissions, +and a stated reason it couldn't be done with a plain shell command instead. +Dependabot (`.github/dependabot.yml`) watches these pinned SHAs for updates. + +## Release boundary + +Release archives must contain only intended source, documentation, integrations, hooks, and configuration. They must exclude `.git`, `.ai-logs`, local caches, temporary files, context packs, test output, and environment files. diff --git a/hooks/agent/session-checkpoint b/hooks/agent/session-checkpoint new file mode 100755 index 0000000..5c6b37d --- /dev/null +++ b/hooks/agent/session-checkpoint @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# hooks/agent/session-checkpoint — thin hook-slot shim delegating to +# libexec/session-checkpoint. Kept as a separate path so agent runtimes that +# discover hooks under hooks/agent/ (rather than libexec/) can find this +# command without duplicating its implementation. +set -euo pipefail +exec bash "$(cd "$(dirname "${BASH_SOURCE[0]}")/../../libexec" && pwd)/session-checkpoint" "$@" diff --git a/hooks/agent/watch-loop b/hooks/agent/watch-loop new file mode 100755 index 0000000..bf30949 --- /dev/null +++ b/hooks/agent/watch-loop @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# hooks/agent/watch-loop — thin hook-slot shim delegating to +# libexec/watch-loop. Kept as a separate path so agent runtimes that +# discover hooks under hooks/agent/ (rather than libexec/) can find this +# command without duplicating its implementation. +set -euo pipefail +exec bash "$(cd "$(dirname "${BASH_SOURCE[0]}")/../../libexec" && pwd)/watch-loop" "$@" diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..a539cfa --- /dev/null +++ b/install.sh @@ -0,0 +1,181 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: ./install.sh [--prefix PATH] [--bindir PATH] [--project [DIR]] + +Global install (default): + --prefix ${XDG_DATA_HOME:-$HOME/.local/share}/agent-kit + --bindir $HOME/.local/bin + +Project-local install (vendor the toolkit inside a repo): + --project [DIR] Install into //{toolkit,bin}. DIR defaults to the + git top-level (else the current dir). The folder is + configurable via AGENTKIT_DIR_NAME (default: .agent-kit), so a + repo can wire tools to one stable, renamable location. + Equivalent to: + --prefix /.agent-kit/toolkit --bindir /.agent-kit/bin + Env: AGENTKIT_PROJECT_DIR= also enables project mode; AGENTKIT_DIR_NAME + overrides the folder name. An explicit --prefix always wins. +EOF +} + +source_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P) +prefix=${XDG_DATA_HOME:-$HOME/.local/share}/agent-kit +bindir=$HOME/.local/bin +# Project-local install target. project_dir non-empty (via env or --project) routes +# the install into //{toolkit,bin}. dir_name is configurable so +# consuming repos can rename the vendored folder without touching this installer. +project_dir=${AGENTKIT_PROJECT_DIR:-} +dir_name=${AGENTKIT_DIR_NAME:-.agent-kit} +prefix_explicit=0 + +while (($# > 0)); do + case "$1" in + --prefix) + (($# >= 2)) || { printf 'error: --prefix requires a path\n' >&2; exit 2; } + prefix=$2 + prefix_explicit=1 + shift 2 + ;; + --bindir) + (($# >= 2)) || { printf 'error: --bindir requires a path\n' >&2; exit 2; } + bindir=$2 + shift 2 + ;; + --project) + # Optional DIR value; a bare --project resolves to the git top-level / cwd. + if (($# >= 2)) && [[ "$2" != -* ]]; then + project_dir=$2 + shift 2 + else + project_dir=${project_dir:-.} + shift + fi + ;; + -h|--help) + usage + exit 0 + ;; + *) + printf 'error: unknown argument: %s\n' "$1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +# Resolve a project-local install target into //{toolkit,bin}. +# An explicit --prefix always overrides project mode. +if [[ -n "$project_dir" && "$prefix_explicit" == 0 ]]; then + if [[ "$project_dir" == "." ]]; then + project_dir=$(git rev-parse --show-toplevel 2>/dev/null || pwd -P) + fi + [[ -d "$project_dir" ]] || { printf 'error: --project dir not found: %s\n' "$project_dir" >&2; exit 2; } + project_dir=$(cd -- "$project_dir" && pwd -P) + prefix="$project_dir/$dir_name/toolkit" + bindir="$project_dir/$dir_name/bin" + project_mode=1 +fi + +for command in bash git rg jq; do + command -v "$command" >/dev/null 2>&1 || { + printf 'error: required command not found: %s\n' "$command" >&2 + exit 1 + } +done + +if ((BASH_VERSINFO[0] < 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 4))); then + printf 'error: Bash 4.4 or newer is required\n' >&2 + exit 1 +fi + +for required in bin/agent-kit lib libexec share; do + [[ -e "$source_root/$required" ]] || { + printf 'error: installer source is incomplete: missing %s\n' "$required" >&2 + exit 1 + } +done + +prefix_parent=$(dirname -- "$prefix") +mkdir -p -- "$prefix_parent" "$bindir" +stage=$(mktemp -d "$prefix_parent/.agent-kit.install.XXXXXX") +backup='' +installed=0 +wrapper_tmp='' +cleanup() { + local rc=$? + if ((rc != 0)) && ((installed == 1)); then + rm -rf -- "$prefix" + if [[ -n "$backup" && -e "$backup" ]]; then + mv -- "$backup" "$prefix" + fi + fi + rm -rf -- "$stage" + [[ -n "$wrapper_tmp" ]] && rm -f -- "$wrapper_tmp" +} +trap cleanup EXIT + +copy_paths=(bin lib libexec share hooks integrations docs README.md INSTALL.md AGENTS.md CLAUDE.md LICENSE NOTICE SECURITY.md SUPPORT.md CONTRIBUTING.md CHANGELOG.md VERSION uninstall.sh) +for path in "${copy_paths[@]}"; do + [[ -e "$source_root/$path" ]] || continue + cp -R -- "$source_root/$path" "$stage/" +done +printf '%s\n' 'agent-kit' > "$stage/.agent-kit-install" + +# Refuse to clobber a foreign command already installed as `$bindir/agent-kit` (the +# name is generic). Only overwrite our own wrapper unless AGENTKIT_FORCE=1. Do this +# before replacing the prefix so a rejected wrapper leaves the existing install intact. +wrapper_marker='# agent-kit-wrapper' +if [[ -e "$bindir/agent-kit" && "${AGENTKIT_FORCE:-0}" != "1" ]]; then + if ! grep -Fq -- "$wrapper_marker" "$bindir/agent-kit" 2>/dev/null; then + printf 'error: %s already exists and is not an agent-kit wrapper.\n' "$bindir/agent-kit" >&2 + printf ' Remove it, choose another --bindir, or re-run with AGENTKIT_FORCE=1 to overwrite.\n' >&2 + exit 1 + fi +fi + +if [[ -e "$prefix" ]]; then + backup="${prefix}.backup.$(date +%Y%m%d%H%M%S).$$" + mv -- "$prefix" "$backup" +fi + +if ! mv -- "$stage" "$prefix"; then + [[ -n "$backup" && -e "$backup" ]] && mv -- "$backup" "$prefix" + printf 'error: installation failed; previous installation restored when available\n' >&2 + exit 1 +fi +installed=1 + +wrapper_tmp=$(mktemp "$bindir/.agent-kit.XXXXXX") +# Pin the wrapper to the Bash that ran this installer. We already verified it is +# >= 4.4 above, so the installed `agent-kit` always launches the dispatcher under a +# capable interpreter (bin/agent-kit then propagates it to subcommands via "$BASH"). +# This avoids macOS silently running everything under its 3.2 /bin/bash. +cat > "$wrapper_tmp" < [options] + agent-kit context diff unstaged [options] + agent-kit context diff pr [options] + agent-kit context diff recent [--count N] [options] + agent-kit context diff touched [options] + +Options: + --include-diffs Include git diff / PR diff as context artifact + --no-tests Do not include related tests + --no-secrets-scan Disable gitleaks scan + --dry-run Show selected files and estimated tokens only + --strict Fail when output exceeds token budget + --token-budget N Override TOKEN_BUDGET + --split SIZE Pass --split-output SIZE to repomix when available + --help Show help + +Environment: + TOKEN_BUDGET=80000 + INCLUDE_TESTS=1 + INCLUDE_DIFFS=0 + DRY_RUN=0 + STRICT_TOKENS=0 + SPLIT_OUTPUT= + TOKEN_ESTIMATOR_CMD=custom-token-counter +EOF +} + +ai_context_diff_main() { + local TOKEN_BUDGET="${TOKEN_BUDGET:-80000}" + local OUTPUT_DIR="${OUTPUT_DIR:-${AI_CONTEXT_DIR}/diff}" + local INCLUDE_TESTS="${INCLUDE_TESTS:-1}" + local SECRETS_SCAN="${SECRETS_SCAN:-1}" + local INCLUDE_DIFFS="${INCLUDE_DIFFS:-0}" + local DRY_RUN="${DRY_RUN:-0}" + local STRICT_TOKENS="${STRICT_TOKENS:-0}" + local SPLIT_OUTPUT="${SPLIT_OUTPUT:-}" + local COMMON_OPTION_CONSUMED=0 + + # Bind the bare `usage` name lib/ai-diff-context/main.sh's dispatch relies + # on to this mode's help text for the duration of this call only. + usage() { ai_context_diff_usage; } + + ai_diff_context_main "$@" +} diff --git a/lib/ai-context/ensure.sh b/lib/ai-context/ensure.sh new file mode 100644 index 0000000..30bf0ee --- /dev/null +++ b/lib/ai-context/ensure.sh @@ -0,0 +1,161 @@ +# shellcheck shell=bash +# ai-context/ensure.sh — ensure the Repomix context bundle is fresh before an +# agent relies on it. +# +# Sourced by libexec/ai-context (thin loader). Not an entrypoint. Behavior is +# byte-for-byte equivalent to the previous standalone libexec/repomix-ensure-fresh, +# wrapped in ai_context_ensure_main() with module-local helper names. The two +# scripts it used to shell out to by relative path are now called in-process: +# freshness checking calls ai_context_status_main() directly (fused in this same +# module set, see status.sh) and regeneration calls ai_context_generate_main() +# (generate.sh), which itself execs the relocated, still-process-isolated +# libexec/internal/run-repomix-context engine. Both calls run inside a subshell +# so a function that calls `exit`/`exec` internally only ends that subshell, not +# the whole ai-context process — matching the previous subprocess-call semantics. + +ai_context_ensure_usage() { + cat <<'EOF' +Usage: + agent-kit context ensure [root] [--regen] [--no-regen] + +Options: + --regen permit regeneration of stale/expired/missing context + --no-regen never regenerate; only report and recommend + (env) REPOMIX_AUTO_REGEN=1 same as --regen + (env) REPOMIX_WARN_DAYS / REPOMIX_MAX_DAYS thresholds (default 2 / 7) + +Behaviour: + - fresh -> exit 0 + - stale -> exit 0 (recommend regen; regenerate only if permitted) + - expired -> regenerate if permitted, else exit 3 + - missing -> regenerate if permitted, else exit 4 + Non-interactive without --regen/REPOMIX_AUTO_REGEN never prompts; it exits + with a recommended command instead. + +Regeneration always runs against the repository root only: + agent-kit context generate . +EOF +} + +# Regenerates the context bundle. Reads root_abs from the calling +# ai_context_ensure_main frame via bash's dynamic scoping. +ai_context_ensure_regenerate() { + section "Regenerating Repomix context (root: $root_abs)" + # Subshell: ai_context_generate_main execs into the relocated engine, so it + # must not replace this process — only the subshell that runs it. + if (cd "$root_abs" && SECRETS_SCAN=0 ai_context_generate_main .); then + echo "OK: Repomix context regenerated" + return 0 + fi + die "Repomix context regeneration failed" +} + +# Reads REGEN/ASSUME_NO from the calling ai_context_ensure_main frame via +# bash's dynamic scoping. +ai_context_ensure_want_regen() { + if [[ "$REGEN" == "1" ]]; then + return 0 + fi + if [[ "$ASSUME_NO" == "1" ]]; then + return 1 + fi + # Interactive prompt only when attached to a TTY; never silent. + if [[ -t 0 && -t 1 ]]; then + printf 'Regenerate Repomix context now? [y/N] ' + local reply + read -r reply || reply="" + case "$reply" in + y | Y | yes | YES) return 0 ;; + *) return 1 ;; + esac + fi + # Non-interactive and not explicitly permitted: do not prompt, do not regen. + return 1 +} + +ai_context_ensure_main() { + local ROOT="." + local REGEN="${REPOMIX_AUTO_REGEN:-0}" # 1 = allowed to regenerate without prompt + local ASSUME_NO="0" + + local args=() + local arg + for arg in "$@"; do + case "$arg" in + --help | -h) + ai_context_ensure_usage + return 0 + ;; + --regen) + REGEN="1" + ;; + --no-regen) + REGEN="0" + ASSUME_NO="1" + ;; + *) + args+=("$arg") + ;; + esac + done + if [[ ${#args[@]} -gt 0 ]]; then + ROOT="${args[0]}" + fi + + local root_abs + root_abs="$(cd "$ROOT" && pwd)" + local regen_cmd="agent-kit context generate ." + + # Determine freshness state via the dedicated checker (deterministic exit codes). + set +e + local freshness_out freshness_code + freshness_out="$(AI_OUTPUT=text ai_context_status_main "$root_abs" 2>&1)" + freshness_code=$? + set -e + + local state="unknown" + case "$freshness_code" in + 0) + # fresh or stale; distinguish by message + if printf '%s' "$freshness_out" | head -n1 | grep -qi '^stale'; then + state="stale" + else + state="fresh" + fi + ;; + 3) state="expired" ;; + 4) state="missing" ;; + *) state="unknown" ;; + esac + + printf '%s\n' "$freshness_out" + + case "$state" in + fresh) + return 0 + ;; + stale) + # Usable; recommend regen but never force it. + if ai_context_ensure_want_regen; then + ai_context_ensure_regenerate + else + echo "recommend: $regen_cmd" + fi + return 0 + ;; + expired | missing) + if ai_context_ensure_want_regen; then + ai_context_ensure_regenerate + return 0 + fi + echo "recommend: $regen_cmd" + echo "Repomix context is ${state}; not regenerated (no permission). Provide --regen or run the command above." + [[ "$state" == "expired" ]] && return 3 + return 4 + ;; + *) + echo "recommend: $regen_cmd" + return 1 + ;; + esac +} diff --git a/lib/ai-context/estimate.sh b/lib/ai-context/estimate.sh new file mode 100644 index 0000000..7d06baa --- /dev/null +++ b/lib/ai-context/estimate.sh @@ -0,0 +1,108 @@ +# shellcheck shell=bash +# ai-context/estimate.sh — estimate the context/token cost of a file or directory. +# +# Sourced by libexec/ai-context (thin loader). Not an entrypoint. Behavior is +# byte-for-byte identical to the previous standalone libexec/query-usage, just +# wrapped in ai_context_estimate_main() with a module-local usage name. The +# original file's early standalone --help/--introspect sh-introspect guards are +# dropped here (they only made sense for a directly-executed script); --help is +# still handled inline by the option loop below, matching every other fused +# ai-context mode. This module does not source lib/common.sh functionality +# directly (query-usage never did); it only uses plain shell builtins. + +ai_context_estimate_usage() { + cat <<'EOF' +Usage: + agent-kit context estimate [path] [--multiplier ] [--multiplier-label