From 42afdbfe44622024b38c909d4b951622241b9816 Mon Sep 17 00:00:00 2001 From: Nivesh353 Date: Wed, 9 Sep 2026 13:03:16 +0530 Subject: [PATCH 1/2] feat(version): tag, diff and roll back an agent's config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An agent is already a git repo, so a known-good configuration is just a tag — but restoring one meant hand-running git plumbing and knowing which paths are safe to touch. Adds a CLI over those primitives; no new storage. - gitagent version save/list/show/diff/rollback, dispatched like `plugin` before parseArgs. Versions are annotated tags under refs/tags/agentcfg/, namespaced so they never collide with a repo's release tags. - Scoped to config: agent.yaml, SOUL.md, RULES.md, DUTIES.md, AGENTS.md, config/, tools/, hooks/, knowledge/, examples/, compliance/, agents/, workflows/, schedules/, plugins/. memory/ and skills/ are deliberately excluded — memory commits on every save (src/tools/memory.ts) and skill_learner rewrites skills/ at runtime, so those commits interleave with config commits and restoring them would destroy what the agent learned after the tag was cut. - Rollback is forward-only: it writes a new commit rather than rewriting history, so the rollback is itself visible in git log and revertible. No reset --hard anywhere. - Restore is planned from `git diff --name-status --no-renames -z HEAD`, then remove-before-restore. A plain `git checkout -- tools/` leaves files added after the tag in place, which yields a merge of old and new config rather than a rollback; the ordering also handles file<->directory flips. test/version.test.ts covers both. - Adds src/git.ts: every call is execFileSync with an argv array, so quoting and injection bugs are structurally impossible. Also handles --no-pager, a 64MiB buffer, closed stdin, and per-invocation identity/no-gpgsign so commits work in a repo with no user.email without touching git config. Only isGitRepo is migrated here; porting the other 13 execSync sites is a separate change. - Establishes the temp-git-repo test fixture the suite lacked (hermetic via GIT_CONFIG_GLOBAL=/dev/null). 29 new cases, full suite 94 passing. --- Documentation.md | 77 ++++++++ README.md | 53 ++++- src/git.ts | 137 +++++++++++++ src/index.ts | 53 +++-- src/version-cli.ts | 270 +++++++++++++++++++++++++ src/version.ts | 461 +++++++++++++++++++++++++++++++++++++++++++ test/git.test.ts | 151 ++++++++++++++ test/version.test.ts | 363 ++++++++++++++++++++++++++++++++++ 8 files changed, 1543 insertions(+), 22 deletions(-) create mode 100644 src/git.ts create mode 100644 src/version-cli.ts create mode 100644 src/version.ts create mode 100644 test/git.test.ts create mode 100644 test/version.test.ts diff --git a/Documentation.md b/Documentation.md index c795966..6907e01 100644 --- a/Documentation.md +++ b/Documentation.md @@ -19,6 +19,7 @@ - [Workflows & SkillFlows](#workflows--skillflows) - [Hooks](#hooks) - [Plugins](#plugins) +- [Versioning & Rollback](#version-cli) - [Memory System](#memory-system) - [Schedules & Cron](#schedules--cron) - [Integrations](#integrations) @@ -121,6 +122,10 @@ gitagent --model anthropic:claude-opus-4-6 --voice --dir ~/assistant | `--repo` | `-r` | Clone and work on remote repository | — | | `--pat` | — | GitHub/GitLab personal access token | `GITHUB_TOKEN` env | | `--session` | — | Git branch name for session isolation | auto-generated | +| `--version` | — | Print the gitagent version (`-v` is taken by `--voice`) | — | + +> `plugin` and `version` are subcommands, so a prompt starting with either word must be +> quoted or passed via `-p`: `gitagent -p "version this repo"`. ### REPL Commands @@ -146,6 +151,78 @@ gitagent plugin remove my-plugin --dir ~/assistant gitagent plugin init my-plugin --dir ~/assistant ``` +### Version CLI + +Snapshot, inspect and restore an agent's configuration. Versions are annotated git tags under +`refs/tags/agentcfg/`, so the storage is the agent's own repo — nothing extra on disk. + +```bash +gitagent version save v1.0 -m "baseline" # tag current config +gitagent version save # name defaults to v +gitagent version save v1.1 --commit # commit dirty config first, then tag +gitagent version list --json +gitagent version show v1.0 --files +gitagent version diff v1.0 # against the working tree +gitagent version diff v1.0 v1.1 --stat +gitagent version rollback v1.0 --dry-run +gitagent version rollback v1.0 --yes +``` + +| Subcommand | Description | +|------------|-------------| +| `save []` | Tag the current config. Aliases: `tag` | +| `list` | List saved versions, newest first. Aliases: `ls` | +| `show ` | Version metadata, file inventory, drift since | +| `diff []` | Config diff between versions or against the working tree | +| `rollback ` | Restore config from a version as a new commit. Aliases: `restore` | + +| Flag | Applies to | Description | +|------|-----------|-------------| +| `-m`, `--message` | `save`, `rollback` | Tag or commit message | +| `--commit` | `save` | Commit uncommitted config before tagging | +| `--force` | `save` | Move an existing tag | +| `--force` | `rollback` | Discard local config changes; allow detached HEAD | +| `--dry-run` | `rollback` | Print the plan, change nothing | +| `--yes` | `rollback` | Skip confirmation (required when stdin is not a TTY) | +| `--json` | `list` | Machine-readable output | +| `--files` | `show` | List every file rather than the first 20 | +| `--stat`, `--name-only` | `diff` | Passed through to `git diff` | +| `--dir`, `-d` | all | Agent directory (default: cwd) | + +**What is versioned.** Only configuration: + +``` +agent.yaml SOUL.md RULES.md DUTIES.md AGENTS.md +config/ tools/ hooks/ knowledge/ examples/ +compliance/ agents/ workflows/ schedules/ plugins/ +``` + +**What is never touched.** `memory/` and `skills/`. The agent commits to `memory/` on every save and +`skill_learner` rewrites `skills/` at runtime, so those commits interleave with config commits +throughout history. Restoring them would silently destroy knowledge the agent gained after the tag +was cut. A rollback therefore reverts *how the agent is configured*, never *what it has learned*. + +**How rollback works.** It diffs the tag against `HEAD` scoped to the config paths, deletes files +added since, restores files that changed or were removed, and records the result as a **new commit**. +History is never rewritten — no `reset --hard`, no branch switching — so a rollback is itself visible +in `git log` and can be reverted like any other commit. + +**Edge-case behavior.** + +| Situation | Behavior | +|-----------|----------| +| Uncommitted config changes | `save` refuses (offers `--commit`); `rollback` refuses unless `--force`. Dirty `memory/` never blocks either | +| Staged changes in the index | Refused, so unrelated staged files can't ride along in the commit | +| Detached HEAD | `rollback` refuses unless `--force` | +| No git identity configured | An identity is injected per-invocation; your git config is never modified | +| Rollback with nothing to change | Reports "already at" and creates no empty commit | +| Agent in a repo subdirectory | Supported; only the agent's own paths are versioned | +| Shallow clone (`--repo` mode) | Warns that tags may be missing — `git fetch --unshallow --tags` | + +**Known limitations.** Tags are repo-global, so two agents sharing one repo share the `agentcfg/` +namespace. Gitignored config files are not captured. Pushing tags is manual — +`git push origin refs/tags/agentcfg/v1.0`. + --- ## Agent Configuration diff --git a/README.md b/README.md index 7d0257f..35f67dd 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,8 @@ Tools • Hooks • Skills • - Plugins + Plugins • + Versioning