mirror of
https://github.com/garrytan/gbrain.git
synced 2026-08-16 09:52:22 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8cba9a76b |
@@ -1025,10 +1025,6 @@ async function extractForSlugs(
|
||||
let linksCreated = 0;
|
||||
let timelineCreated = 0;
|
||||
let pagesProcessed = 0;
|
||||
// #2636: successfully processed pages get their extraction watermark
|
||||
// stamped after the final flush (mode 'all' only — a partial-mode run
|
||||
// hasn't done the full extraction the watermark asserts).
|
||||
const processedRefs: Array<{ slug: string; source_id: string }> = [];
|
||||
|
||||
// Issue #972: read the basename flag once per extract run.
|
||||
const globalBasename = await isGlobalBasenameEnabled(engine);
|
||||
@@ -1117,7 +1113,6 @@ async function extractForSlugs(
|
||||
}
|
||||
|
||||
pagesProcessed++;
|
||||
if (!dryRun) processedRefs.push({ slug, source_id: sourceId ?? 'default' });
|
||||
} catch { /* skip unreadable */ }
|
||||
progress.tick(1);
|
||||
},
|
||||
@@ -1125,13 +1120,6 @@ async function extractForSlugs(
|
||||
|
||||
await flushLinks();
|
||||
await flushTimeline();
|
||||
// #2636: the Dream cycle disables sync's inline extraction and routes
|
||||
// changed slugs through this incremental path — without a stamp here,
|
||||
// those pages never get links_extracted_at and stay permanently visible
|
||||
// to `extract --stale` / doctor. Stamp only after BOTH batches flushed.
|
||||
if (!dryRun && mode === 'all') {
|
||||
await stampExtracted(engine, processedRefs);
|
||||
}
|
||||
progress.finish();
|
||||
|
||||
if (!jsonMode) {
|
||||
|
||||
@@ -453,7 +453,14 @@ function resolveActivity(
|
||||
* every `assemble()` call. 1 MB is generous for a human-edited task list. */
|
||||
const MAX_TASKS_MD_BYTES = 1_000_000;
|
||||
|
||||
/** Extract open tasks from ops/tasks.md "## Today" section. */
|
||||
/** Extract open tasks from ops/tasks.md Today section.
|
||||
*
|
||||
* The daily-task-manager skill's documented Output Format uses priority
|
||||
* headings (`## P1 — Today`) with plain `- [ ] task` lines; older fixtures
|
||||
* used a bare `## Today` heading with bold task names. Accept both so the
|
||||
* live-context reader matches the documented writer contract instead of
|
||||
* silently surfacing no tasks (#2186).
|
||||
*/
|
||||
function resolveTodayTasks(workspaceDir: string): string[] {
|
||||
try {
|
||||
const path = join(workspaceDir, 'ops', 'tasks.md');
|
||||
@@ -461,14 +468,18 @@ function resolveTodayTasks(workspaceDir: string): string[] {
|
||||
// statSync throws if the file doesn't exist; that lands in the outer catch.
|
||||
if (statSync(path).size > MAX_TASKS_MD_BYTES) return [];
|
||||
const raw = readFileSync(path, 'utf8');
|
||||
const todayMatch = raw.match(/## Today[\s\S]*?(?=\n## |$)/);
|
||||
const todayMatch = raw.match(/^##\s+(?:P\d\s*[—–-]\s*)?Today\b[\s\S]*?(?=\n##\s|$(?![\s\S]))/m);
|
||||
if (!todayMatch) return [];
|
||||
|
||||
const lines = todayMatch[0].split('\n');
|
||||
const open: string[] = [];
|
||||
for (const line of lines) {
|
||||
// Match unchecked task lines: - [ ] **task name** ...
|
||||
const m = line.match(/^\s*-\s*\[ \]\s*\*\*(.+?)\*\*/);
|
||||
// Match unchecked task lines. Legacy bold form first (extracts just
|
||||
// the task name, dropping trailing metadata), then the documented
|
||||
// plain form (whole line body is the task).
|
||||
const m =
|
||||
line.match(/^\s*-\s*\[ \]\s*\*\*(.+?)\*\*/) ??
|
||||
line.match(/^\s*-\s*\[ \]\s*(.+?)\s*$/);
|
||||
if (m) open.push(sanitizeForPrompt(m[1].trim()));
|
||||
}
|
||||
return open.slice(0, 5); // cap at 5 to keep prompt lean
|
||||
|
||||
@@ -322,6 +322,28 @@ describe('gbrain-context engine', () => {
|
||||
expect(result.systemPromptAddition).not.toContain('Something later');
|
||||
});
|
||||
|
||||
it('injects documented "## P1 — Today" plain tasks from ops/tasks.md (#2186)', async () => {
|
||||
tmpDir = makeWorkspace({
|
||||
heartbeat: { garryAwake: true },
|
||||
tasks: `# Tasks\n\n## P0 — Urgent\n- [ ] **Escalate outage**\n\n## P1 — Today\n- [ ] Call Alice about launch plan\n- [ ] **Review Bob contract** — due Friday\n- [x] Completed item\n\n## P2 — This Week\n- [ ] Should not surface`,
|
||||
});
|
||||
const engine = createGBrainContextEngine({ workspaceDir: tmpDir });
|
||||
|
||||
const result = await engine.assemble({
|
||||
sessionId: 'test-session',
|
||||
messages: [],
|
||||
});
|
||||
|
||||
expect(result.systemPromptAddition).toContain('Open tasks');
|
||||
expect(result.systemPromptAddition).toContain('Call Alice about launch plan');
|
||||
// Bold form still extracts just the task name, not trailing metadata.
|
||||
expect(result.systemPromptAddition).toContain('Review Bob contract');
|
||||
expect(result.systemPromptAddition).not.toContain('due Friday');
|
||||
expect(result.systemPromptAddition).not.toContain('Escalate outage');
|
||||
expect(result.systemPromptAddition).not.toContain('Completed item');
|
||||
expect(result.systemPromptAddition).not.toContain('Should not surface');
|
||||
});
|
||||
|
||||
it('no activity section when calendar is empty and no tasks', async () => {
|
||||
tmpDir = makeWorkspace({
|
||||
heartbeat: { garryAwake: true },
|
||||
|
||||
@@ -60,51 +60,6 @@ async function seedPage(slug: string, body: string): Promise<void> {
|
||||
}
|
||||
|
||||
describe('runExtractCore — incremental cycle path (#417)', () => {
|
||||
test('Dream incremental all-mode stamps the source-scoped extraction watermark (#2636)', async () => {
|
||||
await engine.executeRaw(
|
||||
`INSERT INTO sources (id, name, local_path) VALUES ($1, $2, $3)`,
|
||||
['repo-a', 'repo-a', tempDir],
|
||||
);
|
||||
await engine.putPage('people/alice-example', {
|
||||
type: 'person',
|
||||
title: 'alice-example',
|
||||
compiled_truth: '# alice',
|
||||
timeline: '',
|
||||
frontmatter: {},
|
||||
content_hash: 'h',
|
||||
}, { sourceId: 'repo-a' });
|
||||
writeFileSync(join(tempDir, 'people/alice-example.md'), '# alice');
|
||||
|
||||
await runExtractCore(engine as unknown as BrainEngine, {
|
||||
mode: 'all',
|
||||
dir: tempDir,
|
||||
slugs: ['people/alice-example'],
|
||||
sourceId: 'repo-a',
|
||||
});
|
||||
|
||||
const rows = await engine.executeRaw<{ links_extracted_at: string | null }>(
|
||||
`SELECT links_extracted_at FROM pages WHERE slug = $1 AND source_id = $2`,
|
||||
['people/alice-example', 'repo-a'],
|
||||
);
|
||||
expect(rows[0]?.links_extracted_at).not.toBeNull();
|
||||
expect(await engine.countStalePagesForExtraction({ sourceId: 'repo-a' })).toBe(0);
|
||||
});
|
||||
|
||||
test('Dream incremental dry-run does NOT stamp the watermark', async () => {
|
||||
await seedPage('people/alice-example', '# alice');
|
||||
await runExtractCore(engine as unknown as BrainEngine, {
|
||||
mode: 'all',
|
||||
dir: tempDir,
|
||||
slugs: ['people/alice-example'],
|
||||
dryRun: true,
|
||||
});
|
||||
const rows = await engine.executeRaw<{ links_extracted_at: string | null }>(
|
||||
`SELECT links_extracted_at FROM pages WHERE slug = $1`,
|
||||
['people/alice-example'],
|
||||
);
|
||||
expect(rows[0]?.links_extracted_at ?? null).toBeNull();
|
||||
});
|
||||
|
||||
test('1. slugs: [] returns immediately with zero counts (early-return path)', async () => {
|
||||
await seedPage('people/alice-example', '# alice');
|
||||
const result = await runExtractCore(engine as unknown as BrainEngine, {
|
||||
|
||||
Reference in New Issue
Block a user