* feat(analyze): add opt-in --self-commit flag for AGENTS.md/CLAUDE.md churn
Adds a new `--self-commit` flag to `gitnexus analyze`. When passed, any
AGENTS.md/CLAUDE.md changes the run makes (including first-time creation)
are auto-committed, scoped to only those two files (never `git add -A`).
No-ops silently if neither exists, neither changed, or the repo has no
git identity configured — never fails the surrounding analyze run.
Complements #1478 (--no-stats): that flag removes the volatile counts
entirely, this one keeps them but eliminates the dangling working-tree
diff they otherwise leave behind on every run.
Closes#2639.
* fix(analyze): log a warning when --self-commit fails to commit
Addresses review feedback on #2640: the commit step's catch block was
silently swallowing failures (e.g. missing git identity) with no signal
to the user. Logs via the existing pino logger (matching the rest of
the codebase's convention) with the error and the file list, while
still never throwing — analyze must not fail over this.
New test forces a real commit failure (missing identity, with
useConfigOnly + isolated HOME/XDG_CONFIG_HOME/GIT_CONFIG_NOSYSTEM so no
ambient global git config on the CI runner can mask it) and asserts the
warning is captured via logger's _captureLogger test hook.
* fix(analyze): refuse to sweep pre-existing edits into --self-commit
Addresses both state-safety blockers from review round 2 on #2640:
1. selfCommitContextFiles could not distinguish a pre-existing unstaged
user edit in AGENTS.md/CLAUDE.md from this run's generated stats
refresh — both just showed up as "the file is dirty" — so a user
edit sitting in either file got silently swept into the generated
commit. Fixed by snapshotting each candidate's cleanliness via the
new snapshotSelfCommitSafety() BEFORE analyze writes to it; only
files confirmed safe (nonexistent pre-run, i.e. first-time creation,
or clean pre-run) are ever added/committed. A file already dirty
pre-run is skipped and logged, never touched.
2. On a failed `git commit` (e.g. missing identity), the preceding
`git add` had already staged the safe files, and analyze reported
nothing happened while silently leaving them staged. Fixed with a
`git reset -- <safe files>` in the commit-failure catch, restoring
the index to its pre-add state for exactly the files this helper
staged.
Wired analyze.ts to call snapshotSelfCommitSafety() once before
runFullAnalysis (which is where the actual AGENTS.md/CLAUDE.md write
happens, on both the fast path and the primary run), threading the
result through both existing selfCommitContextFiles() call sites.
New tests: a pre-dirty AGENTS.md is skipped while a clean CLAUDE.md
still commits normally, and a post-add commit failure leaves nothing
staged. Updated all existing selfCommitContextFiles() call sites for
the new required safety-map parameter.
* i18n(cli): add zh-CN translation for --self-commit help text
Addresses magyargergo's follow-up on #2640: --self-commit was missing
from the analyze command's OPTION_DESCRIPTION_KEYS map, so its help
text never went through localizeCliHelp and always rendered in English
regardless of locale. Adds the help.option.analyze.selfCommit key to
both en.ts and zh-CN.ts and wires it into help-i18n.ts, matching the
existing --no-stats/--skills entries.
---------
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Address review findings on PR #2488:
- skill-gen.ts: wrap mirror-root mkdir and per-skill mirror writes in
try/catch + warn, so a mirror failure (e.g. .agents/skills is a file)
no longer aborts canonical community-skill generation or destroys prior
output. Mirroring is now a weak side-flow, matching ai-context.ts.
- git.ts: exclude .agents/ + .agents/** from isWorkingTreeDirty so a
tracked .agents/ dir doesn't permanently defeat the up-to-date fast path.
- README + --skip-skills help (en/zh): note skills also mirror to
.agents/skills/ when .agents/ exists, and --skip-skills skips both.
Tests: +18 covering mirror failure paths (root-is-file, per-skill fail,
delete-then-rewrite ordering, namespace-scoped cleanup), dirty-check
excludes (real-edit regression, prefix collision, subdir .agents/,
non-git/git-missing conservative fallback), gate on file-not-dir, and
idempotency.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(git): suppress stderr leak in getCurrentCommit and getGitRoot (#1172)
Node's execSync forwards the child's stderr to the parent process when
the stdio option is not explicitly set. getCurrentCommit and getGitRoot
both caught the resulting error but did not suppress the stderr output,
causing "fatal: not a git repository" messages to leak to the terminal
whenever they were called on a path outside a git worktree.
Add stdio: ['ignore', 'pipe', 'ignore'] to both functions, matching the
pattern already used by getRemoteUrl, getRemoteOriginUrl, and
getCanonicalRepoRoot in the same file.
* address review: add getGitRoot stderr test, normalize em dashes to ASCII
- Add matching process.stderr.write spy test for getGitRoot (#1172)
- Replace U+2014 em dashes with ASCII -- in new comments
Previously gitnexus analyze exited with an error on any directory that
lacked a .git entry, making it impossible to index generated code,
vendored libraries, or monorepo sub-trees that are not git roots.
Changes:
storage/git.ts
- Add hasGitDir(dirPath): boolean — a lightweight synchronous check for
the presence of a .git file or directory. Works for git worktrees
(.git file pointing at the real repo) as well as standard repos.
cli/analyze.ts
- Add noGit?: boolean to AnalyzeOptions.
- When the explicit inputPath resolves to a non-git folder (or the cwd
is not inside any git repo), respect --no-git instead of hard-failing.
- Print an actionable tip pointing at --no-git when git is absent and the
flag was not supplied.
- currentCommit defaults to an empty string for non-git folders so the
up-to-date check still functions (empty string never matches a real
commit hash, so the index is always rebuilt).
- Skip addToGitignore() when no .git is present — there is nothing to
update and the function would create a stale .gitignore at the root.
Git-dependent features that remain disabled for non-git folders:
- Incremental update (always rebuilds from scratch)
- Commit tracking in metadata
- .gitignore update
Closes#384