mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-08 22:22:52 +00:00
fix(cli): do not call zero-symbol detect-changes a clean tree (#3138)
* fix(cli): do not call zero-symbol detect-changes a clean tree Print backend summary.message and distinguish a parsed diff with no overlapping indexed symbols from an empty git diff. Pin --color=never so color.ui=always cannot hide +++ b/ headers. * fix(cli): localize clean-tree detect-changes and skip no-overlap when partial Production empty diffs carry English summary.message; route that through t() so zh-CN fires. Do not claim no indexed-symbol overlap on queryDegraded partial results. Pin formatter tests to en and cover the production payload shapes. * fix(cli): prettier detect-changes-format and degraded eval assertion --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
This commit is contained in:
parent
dea396a13c
commit
5e6b79deba
7 changed files with 127 additions and 2 deletions
|
|
@ -6,6 +6,7 @@ type DetectChangesSummary = {
|
|||
changed_count?: number;
|
||||
affected_count?: number;
|
||||
risk_level?: string;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
type ChangedSymbol = {
|
||||
|
|
@ -55,6 +56,29 @@ export function formatDetectChangesResult(result: unknown): string {
|
|||
);
|
||||
|
||||
if ((summary.changed_count ?? 0) === 0) {
|
||||
// Parse-fail payloads set `partial` and an honest `message` (#2915/#3131).
|
||||
// Production *clean* trees also set English `message: 'No changes detected.'`
|
||||
// — that must go through `t('tool.detectChanges.noChanges')` or zh-CN never
|
||||
// fires. Only pass the backend string through on a degraded/parse-fail run.
|
||||
if (
|
||||
payload.partial &&
|
||||
typeof summary.message === 'string' &&
|
||||
summary.message.trim().length > 0
|
||||
) {
|
||||
return [...notes, summary.message.trim()].join('\n');
|
||||
}
|
||||
// Confirmed no-overlap: files parsed, mapping succeeded, zero symbols.
|
||||
// `queryDegraded` is `partial: true` with the same counts and no message —
|
||||
// do not call that a confirmed mapping (#3131 honesty).
|
||||
if (!payload.partial && (summary.changed_files ?? 0) > 0) {
|
||||
return [
|
||||
...notes,
|
||||
t('tool.detectChanges.noOverlappingSymbols', { files: summary.changed_files }),
|
||||
].join('\n');
|
||||
}
|
||||
if (payload.partial) {
|
||||
return notes.join('\n');
|
||||
}
|
||||
return [...notes, t('tool.detectChanges.noChanges')].join('\n');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,8 @@ export const en = {
|
|||
'tool.warn.unknownKind':
|
||||
"--kind '{{kind}}' is not a known symbol kind (e.g. Function, Class, Method); it will not narrow the result.",
|
||||
'tool.detectChanges.noChanges': 'No changes detected.',
|
||||
'tool.detectChanges.noOverlappingSymbols':
|
||||
'Diff touched {{files}} file(s) but no indexed symbols overlap those hunks — not a clean tree.',
|
||||
'tool.detectChanges.partial':
|
||||
'PARTIAL RESULT: a graph query failed, so changed symbols may be missing. Do not read this as a clean pre-commit check.',
|
||||
'tool.detectChanges.truncated':
|
||||
|
|
|
|||
|
|
@ -78,6 +78,8 @@ export const zhCN = {
|
|||
'tool.warn.unknownKind':
|
||||
"--kind '{{kind}}' 不是已知的符号类型(如 Function、Class、Method),不会用于缩小结果范围。",
|
||||
'tool.detectChanges.noChanges': '未检测到变更。',
|
||||
'tool.detectChanges.noOverlappingSymbols':
|
||||
'diff 触及 {{files}} 个文件,但没有索引符号与这些 hunk 重叠 — 并非干净工作区。',
|
||||
'tool.detectChanges.partial':
|
||||
'结果不完整:图查询失败,可能遗漏已变更符号。请勿将其视为通过的提交前检查。',
|
||||
'tool.detectChanges.truncated':
|
||||
|
|
|
|||
|
|
@ -988,6 +988,9 @@ export function buildDetectChangesDiffArgs(scope: string, baseRef?: string): str
|
|||
'diff',
|
||||
'--ignore-cr-at-eol',
|
||||
'--no-ext-diff',
|
||||
// color.ui=always prefixes `+++ b/` with ANSI, so parseDiffHunks sees zero
|
||||
// files and the CLI used to print a clean "No changes detected." (#3131).
|
||||
'--color=never',
|
||||
'--src-prefix=a/',
|
||||
'--dst-prefix=b/',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@ import { parseDiffHunks } from '../../src/storage/git.js';
|
|||
import { diffArgsFor } from '../helpers/detect-changes-diff-args.js';
|
||||
import { commitAll, initGitRepo } from '../helpers/temp-git-repo.js';
|
||||
|
||||
/** The five flags every scope carries, ahead of its own ref/staging arguments. */
|
||||
/** The six flags every scope carries, ahead of its own ref/staging arguments. */
|
||||
const GUARD_FLAGS = [
|
||||
'diff',
|
||||
'--ignore-cr-at-eol',
|
||||
'--no-ext-diff',
|
||||
'--color=never',
|
||||
'--src-prefix=a/',
|
||||
'--dst-prefix=b/',
|
||||
];
|
||||
|
|
|
|||
92
gitnexus/test/unit/detect-changes-format.test.ts
Normal file
92
gitnexus/test/unit/detect-changes-format.test.ts
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { formatDetectChangesResult } from '../../src/cli/detect-changes-format.js';
|
||||
import { setCliLanguage } from '../../src/cli/i18n/index.js';
|
||||
|
||||
describe('formatDetectChangesResult — zero-symbol honesty (#3131)', () => {
|
||||
beforeEach(() => {
|
||||
setCliLanguage('en');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setCliLanguage(null);
|
||||
});
|
||||
|
||||
it('prints backend parse-fail message instead of a generic all-clear', () => {
|
||||
const text = formatDetectChangesResult({
|
||||
partial: true,
|
||||
summary: {
|
||||
changed_count: 0,
|
||||
affected_count: 0,
|
||||
risk_level: 'unknown',
|
||||
message: 'Could not parse the git diff output — no file headers recognised.',
|
||||
},
|
||||
});
|
||||
expect(text).toContain('PARTIAL RESULT');
|
||||
expect(text).toContain('Could not parse the git diff output');
|
||||
expect(text).not.toContain('No changes detected.');
|
||||
});
|
||||
|
||||
it('does not call a parsed diff with no symbol overlap a clean tree', () => {
|
||||
const text = formatDetectChangesResult({
|
||||
summary: {
|
||||
changed_count: 0,
|
||||
affected_count: 0,
|
||||
changed_files: 1,
|
||||
risk_level: 'low',
|
||||
},
|
||||
});
|
||||
expect(text).toMatch(/Diff touched 1 file/);
|
||||
expect(text).not.toContain('No changes detected.');
|
||||
expect(text).not.toContain('PARTIAL RESULT');
|
||||
});
|
||||
|
||||
it('does not claim no-overlap when a degraded query left changed_count at zero', () => {
|
||||
const text = formatDetectChangesResult({
|
||||
partial: true,
|
||||
summary: {
|
||||
changed_count: 0,
|
||||
affected_count: 0,
|
||||
changed_files: 1,
|
||||
risk_level: 'unknown',
|
||||
},
|
||||
});
|
||||
expect(text).toContain('PARTIAL RESULT');
|
||||
expect(text).not.toMatch(/no indexed symbols overlap/i);
|
||||
expect(text).not.toContain('No changes detected.');
|
||||
});
|
||||
|
||||
it('keeps the clean-tree sentence only when git produced no files', () => {
|
||||
const text = formatDetectChangesResult({
|
||||
summary: { changed_count: 0, affected_count: 0, changed_files: 0, risk_level: 'none' },
|
||||
});
|
||||
expect(text).toBe('No changes detected.');
|
||||
});
|
||||
|
||||
it('localizes the production clean-tree payload that carries English summary.message', () => {
|
||||
setCliLanguage('zh-CN');
|
||||
const text = formatDetectChangesResult({
|
||||
summary: {
|
||||
changed_count: 0,
|
||||
affected_count: 0,
|
||||
risk_level: 'none',
|
||||
message: 'No changes detected.',
|
||||
},
|
||||
});
|
||||
expect(text).toBe('未检测到变更。');
|
||||
});
|
||||
|
||||
it('localizes confirmed no-overlap under GITNEXUS_LANG=zh-CN', () => {
|
||||
setCliLanguage('zh-CN');
|
||||
const text = formatDetectChangesResult({
|
||||
summary: {
|
||||
changed_count: 0,
|
||||
affected_count: 0,
|
||||
changed_files: 1,
|
||||
risk_level: 'low',
|
||||
},
|
||||
});
|
||||
expect(text).toContain('diff 触及 1 个文件');
|
||||
expect(text).not.toContain('未检测到变更。');
|
||||
expect(text).not.toContain('No changes detected.');
|
||||
});
|
||||
});
|
||||
|
|
@ -604,7 +604,8 @@ describe('formatDetectChangesResult', () => {
|
|||
// counts at zero. Without the note the pre-commit gate reads as "clean".
|
||||
const result = formatDetectChangesResult({ partial: true, summary: { changed_count: 0 } });
|
||||
expect(result).toContain('PARTIAL RESULT');
|
||||
expect(result).toContain('No changes detected.');
|
||||
expect(result).toContain('a graph query failed');
|
||||
expect(result).not.toContain('No changes detected.');
|
||||
});
|
||||
|
||||
it('flags a degraded run that still found symbols', () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue