← Files Repo ScoutARCHIVED FILE
CHANGELOG.md
11.8 KB · Oct 2, 2026 · 00:33 UTC
# Changelog All notable changes to Repo Scout are recorded here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow SemVer. ## [Unreleased] ### Planned for v0.2 - Behavioral evals with a no-skill control group on seeded-defect fixtures; evidence bound to content hashes, not just commit + dirty flag. - `install.py uninstall` stays deferred: "marker present, so delete the directory" could still remove files a user added or edited after installing. ## [0.1.2] — 2026-09-09 Installer, inventory and release hardening from the second external review (`docs/reviews/`, RS-REV-01/02/04/05). No change to the skill workflow itself. ### Added - `install.py`: no-replace publish. The payload is staged next to the destination, hashed, given a `.repo-scout-install.json` marker (schema 1: version, install id, RFC 3339 UTC time, per-file SHA-256) and then moved into place with `renameat2(RENAME_NOREPLACE)` on Linux, `renamex_np(RENAME_EXCL)` on macOS or `os.rename` on Windows. If the primitive is missing or the filesystem rejects it the installer fails; it never falls back to a replacing rename or a copy, and it never removes a published copy afterwards. - `install.py doctor`: static inventory of every `repo-scout` copy under the documented host roots (`--home`, repeatable `--project`): state, `managed` / `unmanaged` / `marker_invalid`, `version_observed` (the copy's `VERSION` file) and `version_recorded` (the marker) with a `version_mismatch` flag (`version` prefers the observed value), SKILL.md and whole-payload SHA-256, integrity against the marker (`intact`, `modified` with missing / modified / extra / irregular entries, or `incomplete` when part of the copy could not be read, in which case `payload_sha256` and `matches_bundled_source` are `null`), `enumeration_errors`, whether the copy matches the bundled source, and leftover staging directories. Output states `discovery_scope: known_roots_only` and `active_copy: unknown`; it writes nothing. - `skills/repo-scout/VERSION` travels with every copy; `tests/test_package.py` keeps it equal to the manifests, the marketplaces, `inventory.py`'s `TOOL_VERSION` and this changelog. - `inventory.py --include PATH` / `--exclude PATH` (repeatable, repo-relative, `/`-separated, component-wise matching, no absolute paths or `..`). Precedence: root boundary, symlinks, non-regular entries and hard sensitive names > `--exclude` > `--include` > convenience exclusions. `--include` is additive and passes through only the necessary ancestors of a convenience-excluded directory; an include under an exclude, or into a sensitive directory or file, is rejected up front. Output gains `scope` (normalized lists, `include_mode: additive`, `include_unmatched`, `exclude_unmatched`, the two exclusion lists), `gitignore_respected: false` and `skipped_path_samples_truncated`. - `scripts/release_acceptance.py`: takes (`--dist`) or builds the exact release asset set (source archive with embedded `MANIFEST.sha256` and external checksum, plugin bundle, skills bundle, `SHA256SUMS`; nothing else may be present), verifies every checksum and the manifest, requires the source archive to hold exactly the files of the released commit with their hashes (read from the git object store, so nothing can be added beside `install.py`), requires every file of the plugin bundle (installer, manifests, docs, assets, skill) and the bare bundle's `LICENSE` to be in that archive with the same hash and the skill in all three to be byte-identical, accepts only archives shaped as the builders emit them (regular-file members with canonical paths, listed once, no directory entries, no extra fields in the central directory or the local headers, local headers naming the same member, no bytecode), checks manifests and SKILL.md references, installs from the unpacked plugin root into a fresh home, runs `doctor`, and runs the inventory helper from both the installed copy and the bare bundle. Runs in CI on every OS and in the release workflow before assets are attached; `tests/test_release_acceptance.py` proves it rejects deliberately damaged asset sets (member removed, member edited with a refreshed manifest, installer replaced or a module added only in the source archive, archive of another commit, injected bytecode, altered bare `LICENSE`, stale checksums, missing or extra asset). Scope: the script verifies the assets the release job has just built against the tagged commit and rejects the re-packaging cases above; it is not a general ZIP sanitizer. It trusts the central directory the way `zipfile` does and compares only the name and the extra-field length of each local header, so an archive crafted with inconsistent local records (method, CRC or size mismatch, orphan or concatenated records, mode bits other than the file type) can be extracted differently, or as garbage, by other extractors. For downloaded assets the integrity binding is the `.zip.sha256` / `SHA256SUMS` published from the same job, not this script. - `scripts/build_source_archive.py` builds the published source archive (`repo-scout-vX.Y.Z.zip` with `MANIFEST.sha256`, plus `.zip.sha256`) from the tree of the released commit (`git ls-tree` + `git cat-file`, never the working tree), deterministically and without `zip`/`sha256sum`; the release workflow passes `--commit "$GITHUB_SHA"` and the acceptance script shares it. - Release workflow waits for a green CI run on the tagged commit before publishing; CI itself now also runs on `v*` tag pushes so that run exists for every release. Publication is bound to that commit: right before `gh release create --verify-tag` the remote tag is re-resolved (annotated tags peeled) and must still point at the commit whose CI passed. The repository's tag ruleset (`refs/tags/v*`: no update, delete or non-fast-forward) closes the remaining window. - `install.py doctor` treats the full marker schema as the contract: any missing, extra or malformed field (tool, version, install id, timestamp in exactly `YYYY-MM-DDTHH:MM:SSZ`, `payload_complete`, paths without NUL, absolute or drive/anchor syntax, digests) is reported as `marker_invalid`. The marker and `VERSION` are read only when they are regular files of at most 1 MiB, checked with no-follow metadata before and on the open descriptor (type, identity, size): a FIFO, device, directory, symlink or junction in their place, or a file swapped in between the two checks, is `marker_invalid`, never read or waited on. The installer applies the same 1 MiB bound when it writes the marker and refuses, before publishing anything, a payload whose marker its own reader would not accept. A marker whose JSON nesting exhausts the parser is `marker_invalid` too; the other copies are still diagnosed. - Windows junctions (and every other name-surrogate reparse point) are classified exactly like symlinks everywhere, with `lstat` / `stat(follow_symlinks=False)` metadata rather than `is_symlink()`: refused as a `.claude`, `.agents`, `.grok`, `skills` or `repo-scout` destination component, reported as `symlink-not-followed` by `doctor`, listed as irregular entries inside a payload (which the installer refuses and `doctor` never calls `intact`), and omitted as `symlink-not-followed` by the inventory helper. CI creates real junctions with `mklink /J` on Windows to prove the outside directory is neither read nor modified. ### Changed - `.gitattributes` (`* -text`): every tracked file is checked out with its committed bytes on every platform, whatever `core.autocrlf` says, so the plugin and skills bundles built from a working tree can equal the source archive built from git blobs; a stock Git for Windows clone therefore runs the release acceptance tests instead of failing them. - `inventory.py`: hard sensitive directory names (`.aws`, `.gnupg`, `.secrets`, `.ssh`) are split from convenience exclusions and reported as `sensitive-directory-not-inspected`; output `schema_version` is `1.1`. - `inventory.py` scope paths are literal: a component with leading or trailing whitespace is rejected instead of being trimmed, so `--exclude " private "` can never silently exclude `private` (a trailing `/` is still accepted). - `install.py` whole-payload digest (`payload_sha256`) is the SHA-256 of canonical JSON of the sorted `[path, sha256]` pairs; the previous `"<path> <sha256>"` line encoding let two different payloads share one digest. Markers are unaffected (they store per-file digests). - The installer refuses a source or staging tree it cannot enumerate completely (permission errors and the like) instead of publishing whatever it managed to read. - The abrupt-termination tests (killed during copy, before and after publish) now run on Windows too; only the signal assertion stays POSIX-specific. - Plugin bundle now includes `install.py` so the unpacked plugin root is installable as-is. - Codex manifest: `shortDescription` fits the directory's 30-character limit, the first default prompt no longer repeats `$repo-scout`, and `supportURL` is set. ### Documentation - README: `doctor`, scope flags, and the manual upgrade path (back up the old copy outside every skill discovery directory, install, verify in a new session, then handle the backup by hand). ## [0.1.1] — 2026-09-09 ### Added (packaging and distribution, no skill changes) - Directory listings: published in the OpenAI Plugins Directory (0.1.0); submitted to the Claude Code community marketplace; xai-org/plugin-marketplace PR #622 opened. - `.codex-plugin/plugin.json`, `.grok-plugin/plugin.json` and repo-local marketplaces for Codex (`.agents/plugins/marketplace.json`) and Grok Build (`.grok-plugin/marketplace.json`). - `assets/` logo and composer icon; `site/` website deployed to GitHub Pages with privacy, terms and support pages; `submission/` listing drafts, reviewer test cases and fixtures. - `scripts/build_plugin_zip.py` producing deterministic plugin and skills bundles, attached by the release workflow. - `tool_version` in the inventory output now reports the package version. ### Changed - License: relicensed from MIT to GPL-3.0-or-later (SPDX `GPL-3.0-or-later`) on 2026-09-09 at the repository owner's decision. Applies to this and every later commit. The v0.1.0 tag, its release assets and the 0.1.0 bundle in the OpenAI directory were released under MIT and remain available under it. `LICENSE`, manifest `license` fields, README badges, the website and per-file SPDX headers updated; `submission/fixtures/` left untouched. ### Fixed - Docs: the Codex examples single-quote the trigger (`codex exec '$repo-scout …'`). The double quotes in the v0.1.0 README let the shell expand `$repo-scout` to an empty string. ## [0.1.0] — 2026-09-09 ### Added - Shared `SKILL.md` workflow (inventory → lanes → challenge → verify → dedup → report). - Eight domain lanes: architecture, web, backend/data, mobile, systems, security/privacy, delivery/quality, AI agents. - Evidence contract (E0–E3), finding template, coverage matrix and verification ledger. - Read-only, filename-only `scripts/inventory.py` helper with traversal limits and partial reporting. - `install.py` for Claude Code (`~/.claude/skills`), Codex (`~/.agents/skills`) and Grok Build (`~/.grok/skills`), dry-run by default, never overwrites. - Claude Code plugin manifest and marketplace so the skill can be installed with `/plugin`. - 33 unit tests covering inventory limits, exclusions, symlink handling, sensitive filenames and installer safety. ### Verified - Skill loading and relative-path resolution on Claude Code 2.1.263, Codex CLI 0.153.0 and Grok Build 1.0.13 (macOS). Grok reads the Claude Code copy directly; do not install a second copy under `~/.grok/skills`. ### Not yet verified - Behavioral accuracy (precision/recall on seeded defects) and Grok Bot private-skill persistence.
SHA-256: 96d781b075a48839325ea8216888e45d77c792c1979f7ef822119ee841e35207