Skip to content
This repository was archived by the owner on Aug 15, 2026. It is now read-only.

Prepare AgentKit v0.1.0 - #1

Closed
UtmostCreator wants to merge 62 commits into
mainfrom
feat/project-local-install
Closed

Prepare AgentKit v0.1.0#1
UtmostCreator wants to merge 62 commits into
mainfrom
feat/project-local-install

Conversation

@UtmostCreator

Copy link
Copy Markdown
Owner

Summary

Full release-prep pass for AgentKit's first public release:

  • Project-local install mode (install.sh --project)
  • Coverage/test infrastructure: 655 passing tests across 27 files, 100% command coverage, 69.62% line coverage, native DEBUG-trap line-coverage engine
  • Release packaging: reproducible-build regression test, package.json/VERSION consistency check in package-release.sh
  • GitHub Actions security gating: workflow static analysis (actionlint + zizmor, required), OpenSSF Scorecard, release provenance attestation, Dependabot, step-security/harden-runner (audit mode)
  • GitHub repo configuration: branch protection, secret scanning, private vulnerability reporting, description/topics
  • CHANGELOG finalized for v0.1.0

Test plan

  • ./scripts/check.sh passes locally (655 tests, 0 failures)
  • ./scripts/check-publishable.sh passes locally
  • Install/uninstall verified in isolated temp directories (global and --project modes)
  • Release tarball contents verified file-by-file, no dev-only material
  • CI passes on GitHub's actual infrastructure (this is the first real run)

🤖 Generated with Claude Code

Utmost Creator and others added 30 commits July 13, 2026 20:57
Establish the releasable tree for the `agent-kit` CLI (dispatcher bin/agent-kit
→ libexec/*, shared lib/*): pure-Bash sh-introspect powering every
--help/--introspect, self-documenting commands with runnable examples, and four
install channels (install.sh, web-install.sh curl|bash, Homebrew Formula, npm
@utmostcreator/agent-kit) hardened for macOS Bash 3.2, path spaces, and
clobber/traversal safety. Adds --version, docs/EXAMPLES.md (akit alias),
governance files, CI/release workflows, and the test suite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AtARY1YQfKg1fcMMpbTueM
…o thin loaders

- Add libexec/ai-context, ai-git, ai-inspect, ai-repo, ai-session, ai-test as
  thin dispatch loaders over new lib/ai-context/*, lib/ai-git/*, lib/ai-test/*,
  lib/ai-verify/* engines.
- Relocate repomix-context-tree, repomix-scc-router, run-repomix-context to
  libexec/internal/ (process-isolated; no longer public commands) to avoid a
  confirmed function-name collision risk when fused into the shared process.
- Remove superseded single-purpose scripts and tests now folded into the
  fused command groups (ai-diff-context, ai-doc-check, ai-test-select,
  ai-verify-html/js/php/ts/vue, check-file-refs, gh-pr-context,
  git-branch-origin, git-forensics, pack-context, query-usage,
  repomix-ensure-fresh, repomix-freshness, run-repo-tests, run-test-focused,
  run-repomix-file, and their test-*.sh counterparts).
- Add test-ai-context.sh, test-ai-git.sh, test-ai-test.sh for the new groups;
  update test-ai-edit.sh, test-ai-search.sh, test-ai-verify.sh,
  test-bin-agent-kit.sh, test-repomix-context-tree.sh,
  test-repomix-scc-router.sh, test-run-repomix-context.sh for the new layout.
- Fix relocated-engine relative lib paths (../lib/ -> ../../lib/).
- Update docs/AI_USAGE.md, docs/COMMANDS.md, docs/EXAMPLES.md for the fused
  command surface.
- Add TODO/scripts-todo.md, TODO/todo.md,
  TODO/public-command-surface-consolidation.md tracking the consolidation
  follow-up work.

Verified: scripts/check.sh (shellcheck + full suite) and
scripts/check-publishable.sh both pass with all new files staged/tracked.
- Add docs/PACKAGES.md documenting all 24 public commands: which external
  packages each one shells out to (with the exact code path that calls them),
  why, a real captured command output (run against a live clone or an
  isolated /tmp sandbox for any mutating command such as `edit`, `rollback
  apply`, `all-f-into-one`), and why it beats reaching for the raw tool
  directly.
- Update README.md's Runtime section with a Core/Optional package table
  broken down by which commands each optional package unlocks, linking to
  docs/PACKAGES.md for the full per-command breakdown.
- Link docs/COMMANDS.md to docs/PACKAGES.md.
- Mark all 24 items in TODO/scripts-todo.md as done, pointing to
  docs/PACKAGES.md as where the work landed.

Verified: `agent-kit verify docs links` on the four changed docs (0 broken
links) and `scripts/check.sh` (shellcheck + full test suite) both pass.
…hot/exec-guard

Adds direct test coverage for the toolkit's actual undo mechanism
(snapshot_apply_manifest restore path, ai-rollback list/show/apply/prune
against a real snapshot) plus guarded-mutation edge cases in ai-edit
(parse errors, non-JSON finish() branches, on_error trap, ast-grep/comby
dispatch, dirty-tree gating) and exec-guard's cpu-sampling/idle-debounce
branches. Part of the coverage-improvement plan in TODO/coverage-todo.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByzR9hviBYUPqKNBZdHpt4
kcov's ptrace-based tracer fails silently (reports 0/0) in seccomp-
restricted sandboxes, since PTRACE_TRACEME returns EPERM there.
scripts/coverage.sh now defaults to a pure-Bash DEBUG-trap collector
(scripts/lib/cov-hook.sh + cov-lines.awk) that needs no ptrace, with
COVERAGE_ENGINE=kcov kept as an opt-in for unrestricted hosts. Also
enables test-ai-search.sh's dormant AI_SEARCH_RUN_P1_TESTS assertions
during coverage runs (269 already-passing tests that nothing else in
the repo was exercising).

TODO/coverage-todo.md records the baseline (46.58%) and a phased plan
to reach 60-70%, prioritized against this repo's own P0-P4 command
consolidation scores in TODO/todo.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByzR9hviBYUPqKNBZdHpt4
…files/language-dispatch

Covers lib/ai-verify/reporting.sh (verify_report_dir, write_verify_report_file),
tool-policy.sh (is_standalone_safe_tool, has_composer_bin, can_run_tool's full
case-arm dispatch), language-files.sh (language_pathspecs for all 5 languages
plus the unknown-language die path, scoped_language_files merge/dedupe), and
language-dispatch.sh (ai_verify_language's full --language php/js/ts/vue/html
dispatch via fake pnpm/composer-bin fixtures, plus AI_VERIFY_MODE=suggest's
never-fails eslint --fix-dry-run path). Part of TODO/coverage-todo.md Phase 2.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…alysis-pack, common-options, run-repomix-context)

Also fixes a stale .sh suffix on ROUTER_SCRIPT in lib/repomix-context-tree/main.sh that broke analyze/pack/all dispatch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…text status/ensure/pack/file

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nner/run/docs-check

Covers lib/ai-verify/duplication.sh (jscpd warn/fail/under-threshold tiers,
JSCPD_PATHS override, npx-unavailable skip via a fake jscpd), plan-status.sh
(checklist counts, difficulty-phrase detection scoped to checklist lines only,
plus full-run failure/warn behavior), step-runner.sh (diagnose_pnpm_auth's
.npmrc token-var detection, VERIFY_GUARD=0 fallback to run_with_timeout vs.
run_guarded's guard.start event, has_package_script/has_package_dependency
branches), run.sh (branch-scope case arm, VERIFY_FULL=1 phpunit/pest,
VERIFY_SECRETS=1 gitleaks, VERIFY_SECURITY=1 trivy/semgrep/osv-scanner,
check_composer_unused advisory path), and docs-check.sh (ai_verify_docs_run_drift's
gated php validate-*.php steps, both pass and fail). Part of TODO/coverage-todo.md
Phase 2.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
functrace (required to trace DEBUG into called functions) also makes
Bash inherit RETURN traps into called functions, with no way to opt
into DEBUG-only inheritance. Code that sets a scoped `trap '...'
RETURN` for its own cleanup (e.g. check_jscpd in
lib/ai-verify/duplication.sh) has that trap fire early when a nested
function call returns, deleting its own working directory before it
checks it -- confirmed via direct instrumentation, not a source bug.
Only affects scripts/coverage.sh runs; scripts/check.sh (the real CI
gate) never uses this hook.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByzR9hviBYUPqKNBZdHpt4
Reflects the real line-coverage result after the Phase 1-3 test
additions in TODO/coverage-todo.md (44.79% -> 62.38%, within the
60-70% target range), and points at the plan doc for what's left.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByzR9hviBYUPqKNBZdHpt4
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…canner

New `agent-kit refactor-scan [complexity|nloc|all] [folder]` command. Ranks
files by scc cyclomatic complexity (flag > 15) and per-function metrics via
lizard: NLOC (> 40), parameter count (> 5), and CCN (> 15). All thresholds,
the scan folder (defaults to git toplevel), file-format/language filters, the
scc report format, and extra ignore dirs are configurable. Reuses the shared
source-exclude list plus dot-directory skipping. Human output is CSV/table;
AI output is a JSON envelope. Complexity and NLOC passes run in parallel in
`all` mode. 41 isolated tests (mktemp fixtures only, never the real repo).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X5yYQzG16mdi4sFjLfepvt
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Also fixes a real JSON-contract gap found while testing: the invalid
--around/--context error branches always wrote a plain stderr message
and never respected AI_OUTPUT=json, unlike every other error path in
the file (missing file, .git block, invalid --lines/--range, binary,
max-bytes, unknown flag). Brought them in line with the existing
emit_json pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…curated/files/ast/git, doctor, modes, results-context, output-json, contract, parse-flags, dispatch)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…inder

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nt-kit

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Utmost Creator and others added 18 commits July 15, 2026 01:04
fallback path, non-first --help, and --type= equals-form

BUG FOUND (fixed, trivial + in-scope): the rg --files fallback path (used
when neither fd nor fdfind is on PATH) excluded vendor/node_modules/dist/
.git/.repomix-context via `-g '!name/**'` globs. Confirmed via direct rg
invocation that this glob form silently excludes nothing when the search
root is passed as an absolute path outside the cwd (rg anchors it to the
root's own path components in that case) -- e.g. `rg --files -g
'!vendor/**' /abs/root` still lists /abs/root/vendor/.... Switched to the
bare `!name` form already used successfully by rg-code.sh's BASE_EXCLUDES,
which excludes by directory name regardless of root path shape. This
fallback path was previously untested (fd is installed on this host, so
run_discovery always took the fd_bin branch), which is how the bug went
unnoticed.

Also add: --type=X equals-form flag, and the non-first-argument --help
case (common.sh's universal --help guard intercepts a literal "$1 ==
--help" before this script's own identical early check ever runs, making
that early check dead code -- but the later flag-parser's --help|-h case
arm is reachable once --help follows the query/root).

40/80 -> 61/80 lines. The remaining 20 are: 2 dead-code lines (the early
--help check shadowed by common.sh's guard), 5 case-arm pattern lines and
12 multi-line array-literal interior lines (both structurally uncoverable
by the DEBUG-trap tracer), and 1 line attached to a `while ... done < <(cmd)`
redirect that the trap attributes elsewhere despite the loop body itself
being fully covered.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
sd_plan's rg-error path, and patch_plan's max-files guard

sd_apply() was entirely untested on any host without the real `sd` binary
(this dev host included -- the existing sd-apply test is gated on
`command -v sd`). Install a minimal `sd FROM TO FILE` stand-in (sed-based
literal substitution) ahead of PATH so require_bins/`command -v sd` finds
it, proving sd_apply's read-loop actually invokes it once per planned
file. Also cover sd_plan's rc>=2 rg-error branch (an unbalanced character
class as the pattern forces rg's real "invalid regex" exit 2, distinct
from the already-tested rc==1 no-match path), and patch_plan's
max-files-exceeded guard (previously only sd mode's equivalent guard was
tested).

84/124 -> 93/124 lines. The remaining 31 are structurally uncoverable:
case-arm pattern lines, multi-line jq/awk command-block interiors, an
array literal, and four `done < <(...)`/`done <<<...` lines whose trailing
redirect the DEBUG trap does not attribute there even though the loop
body itself is fully covered. One more (line 60, sd_plan's file_count==0
&& !skipped_for_bytes return-1 branch) looks like defensive dead code:
reaching it needs rg to report count-matches for a path that then fails
`[[ -f "$path" ]]`, which does not happen with real rg output (rg only
lists files it actually read), and the rc==1 no-match case already
returns earlier.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
die branch

Force the underlying repomix-context-tree invocation to genuinely fail
(forward an extra --style that overrides the hardcoded --style xml --
common-options.sh's parser keeps the LAST occurrence of a flag -- so the
tree build dies on an unsupported style), reaching this script's own
`if ! bash "$TREE_SCRIPT" ...; then die ...` branch through a real
invocation of the real script at its real path.

This is the only one of the five die branches in this file that CAN
register with the coverage tracer: the other four (missing index/plan/
manifest/bundles-dir, bundle_count==0) are only reachable by stubbing out
repomix-context-tree, which the existing tests do safely by copying this
script into a throwaway fakeroot tree next to a symlinked lib/ and a
stub sibling script -- but the native DEBUG-trap tracer only credits
hits from files under the real repo root, so those five branches'
functional coverage (all five pass and assert the correct die message)
does not show up in ./scripts/coverage.sh's line count. This is a
distinct, harness-level limitation from the documented DEBUG-trap
multi-line-block issue: not a tracer artifact on the file's own lines,
but a path-prefix filter rejecting a necessarily-copied executable.

31/65 -> 32/65 lines. Also note two other blocks that remain structurally
uncoverable regardless: 74-84 (a multi-line `if ! bash ... \` continuation)
and 109-124 (a multi-line `jq -n \` manifest-building command), plus 5
lines (17-18, 51-53) that are dead code -- this script's own early
`--help`/`-h` handling is shadowed by common.sh's universal --help guard,
which checks the identical "$1 == --help" condition first and always
intercepts before this script's own check can run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
empty-list returns, real markdownlint/lychee invocation, and every
remaining drift step

Un-gate the existing markdownlint-mode test (its own assertion already
tolerates markdownlint being absent, but the `if command -v markdownlint`
wrapper around run_test meant the "markdownlint)" mode-dispatch arm was
never reached at all on a host without markdownlint installed -- this one
included). Add: explicit path arguments to resolve_paths (only the
default-glob branch was previously exercised), --check and docs' own
--help handling, the empty-resolved-path-list early return in both
run_markdownlint and run_links, a real markdownlint invocation via a
stand-in binary (markdownlint is not installed here either), the "lychee
not installed" warning via PATH-hiding the real lychee, is_excluded_path's
true branch through the real absolute-path dispatch (the existing
`bash -c 'source lib/ai-verify/docs-check.sh; ...'`-style test sources it
via a relative path, whose hits the coverage tracer's repo-root-prefix
filter silently drops), and every one of ai_verify_docs_run_drift's
previously-untested gated tools/ai/validate-*.php steps (only two of
eleven had guard-file fixtures before).

76/123 -> 100/123 lines. The remaining 23 fall into three groups: 9 are
case-arm pattern lines (structurally uncoverable -- the DEBUG trap only
fires on the body command, never the pattern match itself); 9 (the manual
glob-detection/expansion fallback in resolve_paths) are dead code under
real usage, because ai_verify_docs_main unconditionally sets
`shopt -s nullglob globstar` before resolve_paths runs, so bash's own
unquoted-variable pathname expansion in `for pattern in $DOC_PATHS`
already expands every glob before the loop body ever sees a raw pattern
string -- confirmed directly (`for p in $glob; do echo "[$p]"; done`
prints pre-expanded paths, never the literal glob); and 5 more (the
mode-dispatch case's --help/-h arm and its unknown-mode catch-all) are
also dead code, since ai_verify_docs_main's mode variable is already
fully constrained by an earlier case statement (which itself
early-returns on --help) before the second case can ever run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
coverage tests

Two of the legacy `changed` positional-count tests defaulted root to ".",
i.e. this actual repo checkout, and asserted status=="ok" -- which
depends on whether the checkout currently has any uncommitted TRACKED
file changes, real git state outside the test's control (observed
flipping to "no_matches" between runs as the working tree was cleaned up
by intervening commits in this same session). Accept ok|no_matches
instead; the tests' coverage purpose (exercising the 0-positional and
1-positional-non-directory branches with root staying default ".") is
unaffected either way.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Conclusion of the multi-round coverage effort in TODO/coverage-todo.md
(44.79% -> 69.62%). Documents the now well-evidenced structural ceiling
in the native DEBUG-trap tracer: it fires once per top-level command,
not once per physical line, so interior lines of multi-line jq/awk
blocks, array literals, and case-arm patterns can never independently
register regardless of test coverage. Confirmed via direct reproduction
across multiple files (lib/ai-search/results-rg.sh, lib/ai-edit/helpers.sh,
lib/logging.sh, lib/repomix-scc-router/analysis-pack.sh, and the final
8-file sweep). Further chasing would game the metric, not find real gaps.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByzR9hviBYUPqKNBZdHpt4
…der name

Global install stays the default. New `--project [DIR]` (and AGENTKIT_PROJECT_DIR)
vendors the toolkit inside a repo at <DIR>/<name>/{toolkit,bin}, where DIR defaults
to the git top-level (else cwd). The folder <name> is configurable via
AGENTKIT_DIR_NAME (default `.agent-kit`) so a consuming repo can rename/move the
vendored location from one variable. An explicit --prefix still overrides.

This makes per-project setup one command and matches the `.agent-kit/` convention
that consuming repos wire their tool shims to.

- install.sh: --project parsing, project-target resolution, project-aware summary.
- INSTALL.md: document project-local install + AGENTKIT_DIR_NAME.

Verified: --project installs to <repo>/.agent-kit/{toolkit,bin}, wrapper runs
(`agent-kit 0.1.0`), and AGENTKIT_DIR_NAME=.tools relocates the folder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P4sftxwvjdkzzEsuj2dqtt
…unbook, and rebrand cleanup

Adds test/test-ai-search-introspect.sh and test/test-all-f-into-one.sh (655
passing tests across 27 files, still 100% command coverage), fixes a leftover
"Agent Repo Toolkit" name in NOTICE, ignores generated .repomix-context/ and
coverage2/ output, and adds RELEASING.md as a step-by-step runbook for the
GitHub/npm/Homebrew release covering the pending agent-kit repo rename.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByzR9hviBYUPqKNBZdHpt4
…omix-context.sh

Both files embedded a literal "-----BEGIN RSA PRIVATE KEY-----" banner as a
synthetic fixture to test the gitleaks-based secrets scan. That same literal
string tripped scripts/check-publishable.sh's own credential grep over
tracked *source* files, making the release/CI gate fail. Build the banner
from two concatenated halves at runtime instead: the fixture file written to
disk still contains the real banner (so gitleaks still catches it), but the
.sh source no longer contains the contiguous match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByzR9hviBYUPqKNBZdHpt4
…e RELEASING.md with verified evidence

RELEASING.md claimed package-release.sh already refused to build on a
package.json/VERSION mismatch; it didn't (only checked VERSION against the
tag), which would let npm publish ship silently under the wrong version.
Add that check for real. Also update RELEASING.md's "Start here" section
with actual evidence gathered this session: a full clean-clone test run
(655/655 passing), verified install/uninstall/tarball contents, and the
current GitHub repo config state (already renamed to agent-kit; description,
topics, branch protection, and private vulnerability reporting still open).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByzR9hviBYUPqKNBZdHpt4
…ification pass

Ran the checklist for real instead of leaving it aspirational: clean-clone
test run (655/655), install/uninstall in isolated temp dirs, tarball content
audit, .ai-logs history check, and a live gh api query against the GitHub
repo's actual settings (rename done; description/topics, branch protection,
auto-delete-on-merge, and private vulnerability reporting still open).
Checked items link back to how they were verified so the state doesn't
silently go stale.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByzR9hviBYUPqKNBZdHpt4
…ings

Add a "line coverage: 69.62%" badge alongside "command coverage: 100%" so
the header can't be misread as full statement coverage — the detailed
paragraph already explained this distinction but the badges didn't.

Complete the release-prep GitHub settings that don't require a second
explicit go-ahead: description + topics from GITHUB_METADATA.md, and
delete-branch-on-merge, applied live via `gh repo edit` and confirmed via
`gh api`. Private vulnerability reporting and branch protection on main were
correctly blocked by the auto-mode permission classifier as security/
governance settings needing named consent, not just a general "do release
prep" instruction — RELEASING.md and RELEASE_CHECKLIST.md now document the
exact proposed config and commands for both, pending that go-ahead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByzR9hviBYUPqKNBZdHpt4
Private vulnerability reporting and branch protection on main (required
status check, PR-required with 0 approvals so the sole maintainer isn't
locked out, resolved-conversations required, force-push/deletion blocked)
were applied after explicit user go-ahead. All of RELEASING.md's step 0
one-time repo setup is now done except the social preview image, which
needs a real image asset that doesn't exist yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByzR9hviBYUPqKNBZdHpt4
Adds a workflow-security job to ci.yml (pinned, checksum-verified
actionlint + zizmor), fixes the required job to actually gate on it (not
just add it to needs:), adds a reproducible-build regression test, and
documents both in docs/SECURITY_MODEL.md, RELEASE_CHECKLIST.md, and
README.md's Safety & privacy section.

Sections C, D, E (OpenSSF Scorecard, release provenance attestation,
Dependabot) are deliberately not included — each needs a separate explicit
go-ahead per the plan, since they're the one exception to this repo's
zero-third-party-actions policy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByzR9hviBYUPqKNBZdHpt4
Adds .github/workflows/scorecard.yml (OpenSSF Scorecard, non-blocking,
informational only) and a downstream `attest` job in release.yml
(actions/attest-build-provenance, isolated in its own permission-scoped job
so id-token/attestations access doesn't leak into the release job). Both
are the one deliberate exception to this repo's zero-third-party-actions
policy, pinned to full commit SHAs verified live via `gh api` immediately
before implementation (actions/checkout and codeql-action/upload-sarif
pins were previously placeholders in the plan; scorecard-action and
attest-build-provenance pins were unchanged since the plan was written).

Adds .github/dependabot.yml to keep those pinned SHAs from rotting, a
"Third-party GitHub Actions" section in docs/SECURITY_MODEL.md documenting
the exception and pin policy, and gh attestation verify instructions in
RELEASING.md and INSTALL.md.

Deliberately not yet added: the Scorecard README badge and the cumulative
"Supply chain" Safety & privacy paragraph, both gated on an actual first
green run — nothing has been pushed to origin beyond main's initial commit
yet, so neither workflow has run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByzR9hviBYUPqKNBZdHpt4
Both are static shields.io badges backed by real, already-existing
evidence — no new third-party trust surface: reproducible-build is proven
by test_tarball_is_reproducible (added this session), security-policy links
to the existing SECURITY.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByzR9hviBYUPqKNBZdHpt4
… exception)

Adds harden-runner as the first step of every job with real network activity
(ci.yml's checks and workflow-security, release.yml's release and attest,
scorecard.yml's analysis), pinned to a live-verified full SHA (v2.20.0).
Deliberately egress-policy: audit only — it observes and logs outbound
network calls but never blocks any, needs no StepSecurity account, and has
no endpoint allowlist to discover or maintain, per the explicit condition
this addition requires no further action. ci.yml's required job is
deliberately skipped: it makes no network calls, so there's nothing to
audit.

docs/SECURITY_MODEL.md's "Third-party GitHub Actions" section now documents
all three exceptions (Scorecard, attestation, harden-runner).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByzR9hviBYUPqKNBZdHpt4
Move [Unreleased] content into a dated [0.1.0] entry and summarize the full
scope of the release: core toolkit, project-local install, coverage/test
infrastructure, and the GitHub Actions security-gating work (actionlint,
zizmor, reproducible-build test, OpenSSF Scorecard, release attestation,
Dependabot, harden-runner, branch protection).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByzR9hviBYUPqKNBZdHpt4
@gitguardian

gitguardian Bot commented Jul 15, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 2 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
34855528 Triggered RSA Private Key 06bea46 test/test-ai-context.sh View secret
34855527 Triggered RSA Private Key 5e64f96 test/test-run-repomix-context.sh View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

Utmost Creator and others added 9 commits July 15, 2026 14:24
zizmor was never actually run locally before this branch was pushed (only
YAML-syntax-validated via yq) — these are genuine findings from the first
execution on real GitHub Actions infrastructure, not false positives:

- excessive-permissions (high): release.yml's workflow-level `permissions:
  contents: write` applied to every job by default, including ones that
  don't need it. Moved to job-level on `release` only; workflow default is
  now `contents: read`. The `attest` job's own explicit permissions block
  was already correctly scoped and is unaffected.
- dependabot-cooldown (medium): dependabot.yml had no cooldown period before
  proposing a version bump. Added `cooldown: default-days: 7`.

Verified locally this time: downloaded the exact pinned actionlint v1.7.12
and zizmor v1.27.0 binaries and ran them directly against the real
.github/workflows/*.yml and dependabot.yml — both now report zero findings
at medium+ severity.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByzR9hviBYUPqKNBZdHpt4
…ndency

Root causes of the 9 test-ai-context.sh failures on real ubuntu-22.04 CI
(never surfaced in the local Nix sandbox, which ships jq 1.8.x):

- jq < 1.7 (e.g. jq 1.6, Ubuntu 22.04's apt package) treats "label" as a
  reserved grammar keyword and rejects `$label` as a --arg/variable name
  with a compile error. pack_files_list's dry-run and manifest jq calls,
  snapshot_create's manifest jq call, run_guarded's guard.* log_json calls,
  and session-checkpoint's log_json call all did `--arg label "$label"` /
  `$label`, silently producing no/degraded output under set -e on any
  jq < 1.7 host. Fixed by renaming the jq-side variable to $label_arg
  everywhere (the JSON key itself stays "label"). Confirmed against the
  real jq 1.6 binary before and after the fix.

- cmd_touched (`diff touched`) hard-required both `fd` and `rg` via
  require_bins, even though fd is documented in README as an optional,
  feature-gated tool, not core. CI only installs ripgrep (not fd), so
  `diff touched` died immediately, and collect_related_tests' fd-only
  PHP-naming-convention branch had nothing to find. cmd_touched now only
  hard-requires rg and soft-degrades (log_warn) when fd is absent, matching
  collect_related_tests' existing convention.

Also gated 4 test-ai-context.sh cases that assumed an installed context
packer (repomix/files-to-prompt/code2prompt) or fd, behind the same
skip_test convention already used for their sibling tests, since this
repo's CI workflow never installs those optional tools.

Verified with the actual CI toolchain reproduced locally: real jq 1.6,
real ripgrep, no fd/repomix/files-to-prompt/code2prompt/gitleaks — all 9
previously-failing tests now pass or correctly skip. Full local suite
(scripts/check.sh) still passes with the normal, fully-equipped sandbox.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The origin field/JSON/override tests ran against the ambient repo checkout
instead of an isolated fixture, so they passed on a normal clone (which sets
up origin/* refs) but failed under CI's no-external-actions checkout, which
only fetches into a detached FETCH_HEAD with no local branches or origin/*
refs at all. Give them their own fixture repo, like the other origin tests
already have. Also skip pr-context --pack when no context packer
(repomix/files-to-prompt/code2prompt) is on PATH, matching the guard
test-ai-context.sh already uses for the same reason.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two more real ubuntu CI-only failures, found by pushing the earlier ai-git
fix and letting scripts/check.sh run further than before (it stops at the
first failing test file, so these were always there, just never reached):

- libexec/ai-rollback's cmd_show_manifest used bare `label` as an object-
  construction shorthand key. jq < 1.7 (jq 1.6, Ubuntu 22.04's apt package)
  treats "label" as a reserved grammar keyword and rejects it there with a
  compile error -- the same bug class as the prior $label fix, just a
  second occurrence in a file that fix didn't touch. Confirmed against the
  real jq 1.6 binary before and after: `{label}` fails to parse, `{label:
  .label}` doesn't. The three test-ai-rollback.sh failures this caused
  (cmd_show_manifest / cmd_show / resolve_snapshot) already assert on the
  exact affected output, so no new test was needed -- they just needed the
  bug fixed to pass under real jq 1.6.

- test-ai-test.sh's path_without helper hid a binary by stripping every
  PATH *directory* that resolved it. That's unsafe on a real Linux host:
  system binaries share directories (php and mktemp both live in /usr/bin
  on stock Ubuntu), so removing "the directory with php" silently deletes
  unrelated tools a test still needs afterward, in a way that looks nothing
  like a PATH problem. Only invisible in the Nix dev sandbox, where every
  tool lives in its own isolated store path. Rewritten to symlink every
  executable except the named one into a flat directory (the same pattern
  test-ai-rollback.sh's build_path_without already uses), which stays
  correct regardless of directory layout. Verified against a reproduction
  of the exact single-directory collision (php + mktemp + grep + friends
  all in one dir): the old implementation broke mktemp resolution after
  filtering; the new one doesn't.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The fourth real CI-only failure this branch's push-and-watch cycle has
surfaced: the "die: missing generated index/plan/manifest/bundles" and
"bundle_count == 0" tests, plus the gitleaks-gated secrets-scan test, all
invoke a real (or copied) libexec/internal/run-repomix-context, whose very
first line hard-requires `scc` and `repomix` via require_bins before it ever
reaches the artifact checks or secrets-scan section these tests target.
CI's "Install validation tools" step only installs shellcheck/jq/ripgrep, so
require_bins dies first with its own "required tools not found" message,
not the message each test expects -- identically on both matrix legs, since
neither installs scc/repomix. Only invisible locally because this dev
sandbox happens to have both installed already.

Gate all six on `command -v scc && command -v repomix` (plus gitleaks for
the secrets-scan one), matching the skip_test convention the three sibling
tests just above them already use for the same reason. Verified both ways:
normal run (scc/repomix/gitleaks present) still exercises all six for real;
a PATH stripped of scc/repomix (reproducing CI's toolset) now skips all six
cleanly instead of failing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Investigated why "Run lint and tests" took 8+ minutes in CI by timing every
test/*.sh file individually (per-file start/end timestamps from the CI log)
and, for the worst offenders, instrumenting run_test() to time every
individual test. Found two unrelated, fully explained bottlenecks:

1. build_path_without / path_without / _ai_verify_path_without (13 call
   sites across test-common.sh, test-ai-rollback.sh,
   test-run-repomix-context.sh, test-ai-edit.sh, test-ai-test.sh, and
   test-ai-verify.sh) hide one binary by symlinking every OTHER executable
   on $PATH into a fresh dir. Each file in each PATH directory was resolved
   via `base="$(basename "$f")"` -- a forked process per file. On a PATH
   with hundreds of entries (routine on both this Nix sandbox and a real CI
   runner's toolcache-laden PATH) that's hundreds of forks, ~6-7s per call
   site measured directly (benchmarked old vs new: 6.489s vs 0.065s, with
   byte-identical output). Fixed by swapping to `${f##*/}` (bash builtin,
   no fork) and batching the `ln` calls (multiple sources, one destination
   dir -- portable POSIX form, not GNU-only `-t`) instead of one `ln` per
   file. ~13 call sites x ~6-7s each is the single largest chunk of the
   whole suite's wall time.

2. test-ai-verify.sh's test_branch_scope_recognized ran the real ai-verify
   against this repo's own tree with a 20s external timeout and reliably
   hit the full 20s every run, even though the thing it actually asserts
   (no "unknown AI_VERIFY_SCOPE" error) resolves in well under a second --
   confirmed empirically that a 3s cap still proves the same assertion.
   run_with_fake_lychee (backing 3 more tests) had the same shape of waste
   from a different cause: its fixture repo has no commits, so the default
   scope treats every file as "changed" and pulls in the
   trivy/semgrep/osv-scanner security-scan block (rule-DB loads, network
   probes) for ~6.5s, none of which the test cares about (it only checks
   whether lychee got invoked). Scoping both to AI_VERIFY_SCOPE=branch --
   confirmed empirically this skips that block entirely -- cut all four
   tests to a fraction of a second.

Also widened AI_GUARD_CPU_SAMPLE (the CPU-percent sampler's own internal
sleep, default 1s) to 0.2s in the two run_guarded tests that sample CPU at
all: a sleep(30) child reads the same 0% CPU over either window, so
detection accuracy is unchanged, and shortened the busy-survives test's
hardcoded busy-loop from 5s to 3s (still safely > AI_GUARD_IDLE_SECS=2).

Every change was verified against its actual failure/timing condition, not
assumed: build_path_without's old-vs-new was A/B benchmarked with identical
output; the scope-related fixes were reproduced standalone (manually
invoking the real script with the old vs. new env) before touching the
test files.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Profiled the remaining gap between the CI step's total time and the sum of
individual test/*.sh files (~114-160s unaccounted for) and found it: a
single `shellcheck` invocation over all 148 shell files takes ~93s locally,
by far the single largest chunk of the whole suite's wall time -- bigger
than any individual test file.

Sharding the file list across parallel shellcheck processes seemed like an
obvious win, but the naive version (one process per file, no other change)
silently introduced false positives: shellcheck refuses by default to
follow a `source` statement to a path outside the files given to that
specific invocation, so any lib/*.sh file whose variable is only consumed
by a sourced sibling -- $REPO_ROOT in lib/ai-edit/main.sh, $failures
threaded through lib/ai-verify/*.sh, both deliberate cross-module
conventions here, not bugs -- reports as "appears unused" the moment that
sibling lands in a different shard. Confirmed exactly which shellcheck flag
fixes this instead of guessing: -x/--external-sources lets each shard
follow `source` anywhere on disk regardless of what else is in its own
argument list. With -x, checking any of the 5 previously-flagged files in
total isolation comes back clean, matching the single-invocation baseline's
0 warnings exactly (verified before touching the codebase, so no actual
lint fixes were needed -- they were never real findings).

Also benchmarked shard size: batches of ~20+ files started blowing up
super-linearly (a 37-file shard alone exceeded 3 minutes), so this uses
small shards (5 files) across all available cores rather than one large
shard per core. Verified failure propagation survives the pipe + xargs +
-x rewrite (a deliberately-broken fixture file still fails the whole
check.sh run, exit 123 as expected under set -euo pipefail).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds docs/CI_PERFORMANCE.md: a short log of what was actually slow (per
root cause, with the fix and commit) from the investigation across
72aa5f2/31cdcab, plus the profiling method used to find them (per-file
timing from workflow logs, per-test timing via an instrumented run_test()
copy, and timing non-test phases like shellcheck separately -- it was the
single largest cost and would never show up in a per-test-file breakdown).

Points AGENTS.md's Validation section at it, so future slow-CI
investigations profile immediately instead of guessing a cause first.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… uninstall

Manually verified the full install/uninstall lifecycle in tmp directories per
request: global install (custom prefix/bindir, reinstall/upgrade, foreign-
wrapper refusal, unmarked-path refusal, real uninstall) all worked as
documented. --project mode also worked end to end (layout, wrapper actually
runs, custom AGENTKIT_DIR_NAME, AGENTKIT_PROJECT_DIR env form, explicit
--prefix overriding --project) -- but uninstalling a project-local install
left an empty .agent-kit/ (and .agent-kit/bin/) behind, breaking the
self-contained/cleanly-removable promise install.sh --project's own output
message makes. install.sh --project nests prefix and bindir as siblings
under one dedicated folder; uninstall.sh only ever removed the prefix dir
and the wrapper file, with no knowledge of that shared parent.

Fixed by having uninstall.sh rmdir bindir if it's now empty, and additionally
rmdir the shared parent directory only when prefix and bindir are siblings
(dirname(prefix) == dirname(bindir) -- true for a project-local install,
false for a global install where bindir is an unrelated shared location like
~/.local/bin that must never be touched). rmdir only succeeds on a truly
empty directory, so this is a safe no-op whenever there's anything else
there. Verified: a shared global bindir with an unrelated file survives
uninstall untouched; a project-local install leaves zero trace.

test/test-install.sh had no coverage of --project mode at all before this;
added 7 tests covering layout, the wrapper actually running, git status
staying untracked, full uninstall cleanup (the regression test for this bug,
confirmed to fail without the fix and pass with it), AGENTKIT_DIR_NAME,
AGENTKIT_PROJECT_DIR, and explicit --prefix overriding --project.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@UtmostCreator
UtmostCreator deleted the feat/project-local-install branch July 15, 2026 22:02
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant