mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-01 02:01:24 +00:00
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
* feat(analyze): expose process-detection budget overrides (#3313) Operators can raise or lower process count, branching, trace depth, and the entry-point candidate pool via CLI, .gitnexusrc, or GITNEXUS_* without changing shipped defaults. A budget-only change re-detects flows on the next analyze without --force. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(review): say invalid budget flags still honor env A rejected --max-processes value was described as falling back to the built-in default even when GITNEXUS_MAX_* still won the next precedence tier. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor(analyze): share process-detection defaults and skip unused walks Keep DEFAULT_CONFIG aligned with the budget resolver and count symbols only when maxProcesses is still dynamic. Co-authored-by: Cursor <cursoragent@cursor.com> * style(analyze): wrap process-detection budget files for prettier Co-authored-by: Cursor <cursoragent@cursor.com> * docs(analyze): name the real process-detection default formula Co-authored-by: Cursor <cursoragent@cursor.com> * docs(analyze): stop calling maxProcesses*2 a hard trace quota Co-authored-by: Cursor <cursoragent@cursor.com> * fix(analyze): say invalid env budget tokens fall back to defaults Co-authored-by: Cursor <cursoragent@cursor.com> * fix(analyze): recertify process-detection after in-place FTS abort (#3324) Persist processDetection.uncertified on the in-place FTS dirty stamp when the budget mismatched so a flagless retry cannot keep rewritten flows. Qualify .gitnexusrc fail-fast copy and tighten related tests. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(analyze): skip live dirty stamp on atomic incremental (#3324) POSIX atomic incremental mutates a staging copy, so stamping live incrementalInProgress before swap made a crash force-rebuild a healthy index. Align analyze --help with CLI > .gitnexusrc > env > default. Co-authored-by: Cursor <cursoragent@cursor.com> * docs(changelog): drop the atomic-incremental dirty-stamp note The code fix stays; Unreleased no longer lists that recovery change. Co-authored-by: Cursor <cursoragent@cursor.com> * test(cli): survive FTS SIGSEGV in --limit e2e CREATE_FTS_INDEX can kill the setup analyze on some WSL hosts (status null). Rebuild with --skip-fts and skip BM25-only query --limit cases unless GITNEXUS_REQUIRE_FTS=1. Refs #3324 Co-authored-by: Cursor <cursoragent@cursor.com> * test(cli): mark update-check child at import Writing refresh-started from fetch() raced a 30s poll against cold tsx boot on a loaded default-project worker. Refs #3324 Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3324) Isolate default-budget FTS crash-marker tests from GITNEXUS_MAX_* env, assert uncertify-before-FTS order and deferred flow detection on park recovery, drop the dangling "then" from entry-point help, and correct stale streamGraphEmit docs without skipping the process-detection stamp. Co-authored-by: Cursor <cursoragent@cursor.com> * docs(changelog): drop Unreleased process-detection notes Keep the #3313 / #3322 code; Unreleased changelog matches main until release. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
133 lines
4.4 KiB
TypeScript
133 lines
4.4 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const runFullAnalysisMock = vi.fn();
|
|
|
|
vi.mock('../../src/core/run-analyze.js', () => ({
|
|
runFullAnalysis: runFullAnalysisMock,
|
|
}));
|
|
|
|
vi.mock('../../src/core/lbug/lbug-adapter.js', () => ({
|
|
closeLbug: vi.fn(async () => undefined),
|
|
closeLbugBeforeExit: vi.fn(async () => undefined),
|
|
isLbugReady: vi.fn(() => false),
|
|
}));
|
|
|
|
vi.mock('../../src/storage/repo-manager.js', () => ({
|
|
getStoragePaths: vi.fn(() => ({ storagePath: '.gitnexus', lbugPath: '.gitnexus/lbug' })),
|
|
getGlobalRegistryPath: vi.fn(() => 'registry.json'),
|
|
RegistryNameCollisionError: class RegistryNameCollisionError extends Error {},
|
|
AnalysisNotFinalizedError: class AnalysisNotFinalizedError extends Error {},
|
|
assertAnalysisFinalized: vi.fn(async () => undefined),
|
|
}));
|
|
|
|
vi.mock('../../src/storage/git.js', () => ({
|
|
getGitRoot: vi.fn(() => '/repo'),
|
|
hasGitDir: vi.fn(() => true),
|
|
}));
|
|
|
|
vi.mock('../../src/core/ingestion/utils/max-file-size.js', () => ({
|
|
getMaxFileSizeBannerMessage: vi.fn(() => null),
|
|
}));
|
|
|
|
describe('analyzeCommand process-detection budget (#3313)', () => {
|
|
const ORIGINAL_NODE_OPTIONS = process.env.NODE_OPTIONS;
|
|
|
|
beforeEach(() => {
|
|
vi.resetModules();
|
|
runFullAnalysisMock.mockReset();
|
|
process.exitCode = undefined;
|
|
process.env.NODE_OPTIONS = `${process.env.NODE_OPTIONS ?? ''} --max-old-space-size=8192`.trim();
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (ORIGINAL_NODE_OPTIONS === undefined) {
|
|
delete process.env.NODE_OPTIONS;
|
|
} else {
|
|
process.env.NODE_OPTIONS = ORIGINAL_NODE_OPTIONS;
|
|
}
|
|
vi.unstubAllEnvs();
|
|
});
|
|
|
|
const upToDate = {
|
|
repoName: 'repo',
|
|
repoPath: '/repo',
|
|
stats: {},
|
|
alreadyUpToDate: true,
|
|
};
|
|
|
|
it('threads the four CLI flags through runFullAnalysis without env mutation', async () => {
|
|
const { analyzeCommand } = await import('../../src/cli/analyze.js');
|
|
runFullAnalysisMock.mockResolvedValue(upToDate);
|
|
const before = {
|
|
processes: process.env.GITNEXUS_MAX_PROCESSES,
|
|
branching: process.env.GITNEXUS_MAX_PROCESS_BRANCHING,
|
|
depth: process.env.GITNEXUS_MAX_PROCESS_TRACE_DEPTH,
|
|
entries: process.env.GITNEXUS_MAX_ENTRY_POINT_CANDIDATES,
|
|
};
|
|
|
|
await analyzeCommand(undefined, {
|
|
maxProcesses: '25',
|
|
maxProcessBranching: '2',
|
|
maxProcessTraceDepth: '8',
|
|
maxEntryPointCandidates: '400',
|
|
});
|
|
|
|
expect(runFullAnalysisMock).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
expect.objectContaining({
|
|
maxProcesses: 25,
|
|
maxProcessBranching: 2,
|
|
maxProcessTraceDepth: 8,
|
|
maxEntryPointCandidates: 400,
|
|
}),
|
|
expect.any(Object),
|
|
);
|
|
expect(process.env.GITNEXUS_MAX_PROCESSES).toBe(before.processes);
|
|
expect(process.env.GITNEXUS_MAX_PROCESS_BRANCHING).toBe(before.branching);
|
|
expect(process.env.GITNEXUS_MAX_PROCESS_TRACE_DEPTH).toBe(before.depth);
|
|
expect(process.env.GITNEXUS_MAX_ENTRY_POINT_CANDIDATES).toBe(before.entries);
|
|
});
|
|
|
|
it.each(['0', 'abc', '-4'])(
|
|
'warns and continues when --max-processes is %s (AE5)',
|
|
async (value) => {
|
|
const { _captureLogger } = await import('../../src/core/logger.js');
|
|
const cap = _captureLogger();
|
|
try {
|
|
const { analyzeCommand } = await import('../../src/cli/analyze.js');
|
|
runFullAnalysisMock.mockResolvedValue(upToDate);
|
|
|
|
await analyzeCommand(undefined, { maxProcesses: value });
|
|
|
|
expect(process.exitCode).toBeUndefined();
|
|
expect(runFullAnalysisMock).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
expect.not.objectContaining({ maxProcesses: expect.any(Number) }),
|
|
expect.any(Object),
|
|
);
|
|
expect(
|
|
cap.records().some((r) => {
|
|
const msg = String(r.msg ?? '');
|
|
return (
|
|
msg.includes('--max-processes must be a positive integer') &&
|
|
msg.includes('next source (env, then the built-in default)')
|
|
);
|
|
}),
|
|
).toBe(true);
|
|
} finally {
|
|
cap.restore();
|
|
}
|
|
},
|
|
);
|
|
|
|
it('leaves option fields unset so runFullAnalysis can honor env-only overrides', async () => {
|
|
const { analyzeCommand } = await import('../../src/cli/analyze.js');
|
|
runFullAnalysisMock.mockResolvedValue(upToDate);
|
|
vi.stubEnv('GITNEXUS_MAX_PROCESSES', '80');
|
|
|
|
await analyzeCommand(undefined, {});
|
|
|
|
const opts = runFullAnalysisMock.mock.calls[0][1] as { maxProcesses?: number };
|
|
expect(opts.maxProcesses).toBeUndefined();
|
|
});
|
|
});
|