GitNexus/gitnexus-claude-plugin
Ankit Verma 8bd71c8335
feat(staleness): report diverged and unknown index state instead of fresh (#3257)
* feat(staleness): report diverged and unknown index state instead of fresh

A staleness check collapsed every git failure into { isStale: false,
commitsBehind: 0 }. On a branch-pinned serve clone, a failed re-index
leaves the recorded commit orphaned by the --depth 1 fetch; once the
reflog expires and gc prunes it, rev-list fails and the index silently
reads as fresh while still behind.

checkStaleness / checkStalenessAsync now return an additive status:
current, behind, diverged (rev-list failed but HEAD resolved and differs
from lastCommit) or unknown (HEAD unresolvable, no lastCommit, timeout).
isStale and commitsBehind keep their values in every case.

One payload builder (core/staleness-status.ts) feeds MCP list_repos, the
hot read tools and /api/repos + /api/repo. diverged carries a hint and no
invented count; unknown appears on listings only. The helpers live in a
pure module so existing vi.mock stubs of git-staleness stay valid.

gitnexus group status no longer prints "-1 commits behind".

Fixes #3256

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Address PR review feedback (#3257)

- Document the `current` arm `fromHead` can return after a failed `rev-list`.
  The `staleness-status.ts` status list, the `fromHead` docstring and the
  `stalenessForTool` comment each named only `diverged`/`unknown`, so all three
  described a contract the helpers do not have.
- Document both `unknown` rows on the group status type: no recorded commit
  (`indexStale: true`, `commitsBehind: -1`) and a git probe that could not
  answer (`indexStale: false`, `commitsBehind: 0`), which do not agree on
  either field.
- Update both shipped `gitnexus-guide` copies to the new wire shape
  (`{ status, commitsBehind?, hint? }`), add a `diverged` example carrying no
  count, and state `unknown` is reported only by the `list_repos` listing.
  Presence now means "not `current`", not "behind N". Pinned in
  shipped-skills-sync.
- Lock the timed-out `rev-list` short-circuit with staleness-timeout.test.ts:
  it asserts `unknown` from exactly one git spawn, so removing the `killed`
  guard (which would probe HEAD again and double the #3232 bound) now fails.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Claim only the uncountable gap in the diverged hint (#3257)

`fromHead` reaches the `diverged` branch on any non-timeout `rev-list` failure
where `rev-parse HEAD` resolves to a different SHA. It compares the two SHAs and
runs no reachability check, so a transient object-read failure lands there while
`lastCommit` is still an ancestor of HEAD — and the hint told the operator the
commit was gone from history. `staleness-status.ts` already documents `diverged`
as only "provably not at HEAD; the count is unknown", so the string was also the
one place contradicting its own contract.

The hint now states what the check established and hedges the usual cause:
"Index is not at HEAD and the commit gap could not be counted — the recorded
commit may no longer be in this clone's history."

Updated with it: the `staleness.test.ts` diverged assertion, and the `diverged`
example plus its lead-in sentence in both shipped `gitnexus-guide` copies (still
byte-identical). `mcp/resources.ts` needs no change — it interpolates
`staleness.hint`, holding no copy of the text.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(staleness): cover the remaining #3256 review test gaps

The tri-review's lower-priority gaps, each now locked:

- staleness-fallback.test.ts: after a failed (not timed-out) rev-list,
  both helpers report `current` when HEAD alone still resolves to the
  indexed commit, `diverged` when it resolves elsewhere, and `unknown`
  when it cannot be read, each from exactly rev-list + rev-parse. This
  complements staleness-timeout.test.ts: a timeout spawns once, any other
  failure probes HEAD.
- list_repos carries the listing-only `unknown` and a count-free
  `diverged`; `current` carries nothing.
- group status: a resolvable repo with no recorded commit composes to
  indexStale: true, commitsBehind: -1, status: unknown and renders as
  "STALE (? commits behind)", through the real groupStatus path rather
  than a hand-built row.

Each test was checked against a mutation of the code it guards (the
fromHead current arm, the group no-commit literal, list_repos
includeUnknown); every mutation fails its test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: tech-admin3 <tech-admin@kodnest.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
2026-09-11 08:07:37 +01:00
..
.claude-plugin chore: release v1.6.11 (#3177) 2026-09-04 20:02:37 +01:00
.codex-plugin chore: release v1.6.11 (#3177) 2026-09-04 20:02:37 +01:00
hooks fix(cli): stop churning the committed agent guides, and nudge --index-only (#2907) (#2927) 2026-08-11 13:30:14 +01:00
skills feat(staleness): report diverged and unknown index state instead of fresh (#3257) 2026-09-11 08:07:37 +01:00
.mcp.json feat(plugin): transform Claude Code plugin into self-contained installable package 2026-02-25 11:47:16 +01:00