mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 22:15:33 +00:00
* feat(sync): sort files newest-first for faster salience on recent content
Problem: sync processes files in git-diff order (alphabetical), so
meetings/2020-* embeds before meetings/2026-*. After a burst of writes,
new pages can be invisible to search for hours while older pages process first.
Fix: sort addsAndMods descending in both incremental sync and full import.
Brain paths are date-prefixed by convention, so lexicographic descending
naturally prioritizes recent content.
This ensures the most relevant pages become searchable first.
* feat(import): path-based checkpoint resume + sort-newest-first helper
Replace gbrain import's positional `processedIndex` checkpoint with a
path-set checkpoint via `src/core/import-checkpoint.ts`. A file is only
"done" when its processFile returns success — failed files never enter
the set, parallel workers can't lose slow files, and sort-order changes
don't drop the newest N files on resume.
Three bug classes fixed:
- Parallel import + slow worker = silent file drop on crash-resume
- Failed file = checkpoint advanced past it, never retried until manual clear
- Sort-order flip (v0.33.x) = cross-version resume drops newest N files
Old positional checkpoints are detected on first resume and discarded
with a stderr log line. Re-walking is cheap because content_hash
short-circuits unchanged files.
Also extracts the descending-lex sort into src/core/sort-newest-first.ts
so import.ts and sync.ts share a single source of truth.
Tests:
- test/sort-newest-first.test.ts (5 hermetic cases)
- test/import-checkpoint.test.ts (18 unit cases over the helpers)
- test/import-resume.test.ts (refactored — GBRAIN_HOME isolation,
drives runImport against PGLite, 5 integration cases including
SLUG_MISMATCH retry regression)
Includes the original sort-newest-first contribution from
@garrytan-agents's PR #964 (commit 8dbcf6a5).
* chore: bump version and changelog (v0.34.2.0)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs: update project documentation for v0.34.2.0
Add CLAUDE.md Key Files entries for the path-based import checkpoint
work: new entries for src/core/import-checkpoint.ts and
src/core/sort-newest-first.ts, plus a dedicated src/commands/import.ts
entry covering the v0.34.2.0 refactor. Update src/commands/sync.ts
entry to reference sortNewestFirst. Regenerate llms-full.txt.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(tests): swap banned /data/brain placeholder for /tmp/example-brain
scripts/check-privacy.sh banlist includes /data/brain/ (legacy private
OpenClaw fork layout). New test files must not use it — CI privacy
guard caught this on PR #988's first push.
No behavior change. test/import-checkpoint.test.ts is unit-level with
no fs access; the dir string is just an identity marker for the
loadCheckpoint dir-mismatch guard.
---------
Co-authored-by: garrytan-agents <garrytan-agents@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
49 lines
1.5 KiB
TypeScript
49 lines
1.5 KiB
TypeScript
import { describe, test, expect } from 'bun:test';
|
|
import { sortNewestFirst } from '../src/core/sort-newest-first.ts';
|
|
|
|
describe('sortNewestFirst', () => {
|
|
test('date-prefixed paths get sorted newest-first', () => {
|
|
const input = [
|
|
'meetings/2020-01-01-old.md',
|
|
'meetings/2026-05-13-recent.md',
|
|
'meetings/2024-03-15-middle.md',
|
|
];
|
|
const out = sortNewestFirst([...input]);
|
|
expect(out[0]).toBe('meetings/2026-05-13-recent.md');
|
|
expect(out[1]).toBe('meetings/2024-03-15-middle.md');
|
|
expect(out[2]).toBe('meetings/2020-01-01-old.md');
|
|
});
|
|
|
|
test('mixed prefixes produce deterministic descending order', () => {
|
|
const input = [
|
|
'concepts/a.md',
|
|
'meetings/2026-05-13.md',
|
|
'people/zoe.md',
|
|
'daily/2026-05-12.md',
|
|
];
|
|
const out = sortNewestFirst([...input]);
|
|
// Pure lex descending — pin the exact order so the contract is locked.
|
|
expect(out).toEqual([
|
|
'people/zoe.md',
|
|
'meetings/2026-05-13.md',
|
|
'daily/2026-05-12.md',
|
|
'concepts/a.md',
|
|
]);
|
|
});
|
|
|
|
test('empty input returns empty', () => {
|
|
expect(sortNewestFirst([])).toEqual([]);
|
|
});
|
|
|
|
test('single element returns single element', () => {
|
|
expect(sortNewestFirst(['only.md'])).toEqual(['only.md']);
|
|
});
|
|
|
|
test('mutates in place AND returns the same reference', () => {
|
|
const arr = ['a.md', 'c.md', 'b.md'];
|
|
const ret = sortNewestFirst(arr);
|
|
expect(ret).toBe(arr); // same reference
|
|
expect(arr).toEqual(['c.md', 'b.md', 'a.md']); // caller's view reflects the sort
|
|
});
|
|
});
|