fix(sync): dry-run no-write contract, unborn-HEAD recovery, no-gpg-sign (#2964)

Codex review on the initial self-heal patch (b1671ee) found 3 real gaps:

- P1: the self-heal ran even under --dry-run, mutating the filesystem
  during what's documented as a preview-only command. Gated the whole
  self-heal (both discoverGitRoot and the headCommit read) on
  !opts.dryRun, same as the existing !opts.sourceId ownership check.
- P2: if `git init` succeeded but the process died before the baseline
  commit landed, the next run's discoverGitRoot would succeed (`.git`
  exists) and skip recovery entirely, permanently wedging on "No
  commits in repo" forever. Added the same self-heal at the
  `git rev-parse HEAD` catch site, sharing a new createSyncBaselineCommit
  helper with the discoverGitRoot catch.
- P2: the baseline commit inherited the operator's global
  commit.gpgSign, which can block headless cron/launchd runs on an
  unavailable signing agent/pinentry. Added --no-gpg-sign.

Two new tests cover dry-run no-mutation and unborn-HEAD recovery.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NCVEvUVm15bFuKqskWgfNS
This commit is contained in:
masashiono0611
2026-07-19 01:58:43 +09:00
co-authored by Claude Sonnet 5
parent b1671ee718
commit a10eeab10c
2 changed files with 81 additions and 23 deletions
+36
View File
@@ -109,4 +109,40 @@ describe('#2964: sync auto-inits a never-git-initialized default brain dir', ()
).rejects.toThrow(/git repository/i);
expect(existsSync(join(dir, '.git'))).toBe(false);
});
test('--dry-run on a non-git dir throws without writing anything to disk', async () => {
const { performSync } = await import('../src/commands/sync.ts');
await expect(
performSync(engine, {
repoPath: dir,
dryRun: true,
noPull: true,
noEmbed: true,
full: true,
}),
).rejects.toThrow(/git repository/i);
// The whole point of --dry-run is "preview only" — it must never git-init
// or commit on our behalf, even though we could self-heal.
expect(existsSync(join(dir, '.git'))).toBe(false);
});
test('unborn-HEAD recovery: a bare `git init` with zero commits (interrupted prior self-heal) still completes', async () => {
const { performSync } = await import('../src/commands/sync.ts');
const { execSync } = await import('child_process');
// Simulate a self-heal that ran `git init` but died before the baseline
// commit landed (process killed, disk full, etc.) — `.git` exists so
// discoverGitRoot succeeds, but `git rev-parse HEAD` still fails.
execSync('git init -q', { cwd: dir });
const result = await performSync(engine, {
repoPath: dir,
noPull: true,
noEmbed: true,
full: true,
});
expect(result.status).toBe('first_sync');
expect(result.added).toBe(2);
expect(execSync('git rev-parse HEAD', { cwd: dir }).toString().trim()).not.toBe('');
});
});