fix: Write context/index.json so agents can look up tasks by phase
Agents were bulk-reading all context task files (39 files) because filenames are opaque IDs and there was no way to find phase-relevant tasks without reading every file. Now writeInputFiles generates a context/index.json with tasksByPhase mapping phaseId to task metadata (file, id, name, status). Prompt updated to direct agents to read the index first.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { mkdirSync, writeFileSync, rmSync, existsSync } from 'node:fs';
|
||||
import { mkdirSync, writeFileSync, readFileSync, rmSync, existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { randomUUID } from 'crypto';
|
||||
@@ -116,6 +116,34 @@ describe('writeInputFiles', () => {
|
||||
writeInputFiles({ agentWorkdir: testDir });
|
||||
expect(existsSync(join(testDir, '.cw', 'input'))).toBe(true);
|
||||
});
|
||||
|
||||
it('writes context/index.json grouping tasks by phaseId', () => {
|
||||
writeInputFiles({
|
||||
agentWorkdir: testDir,
|
||||
tasks: [
|
||||
{ id: 't1', name: 'Task A', phaseId: 'ph1', status: 'pending', category: 'execute', type: 'auto', priority: 'medium' } as Task,
|
||||
{ id: 't2', name: 'Task B', phaseId: 'ph1', status: 'completed', category: 'execute', type: 'auto', priority: 'medium', summary: 'Done' } as Task,
|
||||
{ id: 't3', name: 'Task C', phaseId: 'ph2', status: 'pending', category: 'research', type: 'auto', priority: 'high' } as Task,
|
||||
],
|
||||
});
|
||||
|
||||
const indexPath = join(testDir, '.cw', 'input', 'context', 'index.json');
|
||||
expect(existsSync(indexPath)).toBe(true);
|
||||
const index = JSON.parse(readFileSync(indexPath, 'utf-8'));
|
||||
expect(index.tasksByPhase.ph1).toHaveLength(2);
|
||||
expect(index.tasksByPhase.ph2).toHaveLength(1);
|
||||
expect(index.tasksByPhase.ph1[0]).toEqual({
|
||||
file: 'context/tasks/t1.md',
|
||||
id: 't1',
|
||||
name: 'Task A',
|
||||
status: 'pending',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not write context/index.json when no tasks', () => {
|
||||
writeInputFiles({ agentWorkdir: testDir });
|
||||
expect(existsSync(join(testDir, '.cw', 'input', 'context', 'index.json'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('readSummary', () => {
|
||||
|
||||
@@ -262,6 +262,29 @@ export function writeInputFiles(options: WriteInputFilesOptions): void {
|
||||
}
|
||||
}
|
||||
|
||||
// Write context index — groups tasks by phaseId so agents can look up relevant files
|
||||
// without bulk-reading every context file
|
||||
if (options.tasks && options.tasks.length > 0) {
|
||||
const tasksByPhase: Record<string, Array<{ file: string; id: string; name: string; status: string }>> = {};
|
||||
for (const t of options.tasks) {
|
||||
const phaseId = t.phaseId ?? '_unassigned';
|
||||
if (!tasksByPhase[phaseId]) tasksByPhase[phaseId] = [];
|
||||
tasksByPhase[phaseId].push({
|
||||
file: `context/tasks/${t.id}.md`,
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
status: t.status,
|
||||
});
|
||||
}
|
||||
const contextDir = join(inputDir, 'context');
|
||||
mkdirSync(contextDir, { recursive: true });
|
||||
writeFileSync(
|
||||
join(contextDir, 'index.json'),
|
||||
JSON.stringify({ tasksByPhase }, null, 2) + '\n',
|
||||
'utf-8',
|
||||
);
|
||||
}
|
||||
|
||||
// Write manifest listing exactly which files were created
|
||||
writeFileSync(
|
||||
join(inputDir, 'manifest.json'),
|
||||
|
||||
@@ -25,12 +25,14 @@ Read \`.cw/input/manifest.json\` first. It contains two arrays:
|
||||
- \`pages/\` — one per page; frontmatter: title, parentPageId, sortOrder; body: markdown
|
||||
|
||||
**Context Files** (read-only, read on-demand)
|
||||
- \`context/index.json\` — **read this first** when you need context. Contains \`tasksByPhase\`: a map of phaseId → array of \`{ file, id, name, status }\`. Use it to find relevant task files without bulk-reading.
|
||||
- \`context/phases/\` — frontmatter: id, name, status, dependsOn; body: description
|
||||
- \`context/tasks/\` — frontmatter: id, name, phaseId, parentTaskId, category, type, priority, status, summary; body: description
|
||||
Completed tasks include a \`summary\` field with what the previous agent accomplished.
|
||||
|
||||
Context files provide awareness of the broader initiative. There may be dozens — do NOT bulk-read them.
|
||||
When you need to check a specific phase or task, read that one file. Do not duplicate or contradict context file content in your output.
|
||||
Context files provide awareness of the broader initiative. There may be dozens — do NOT bulk-read them all.
|
||||
Use \`context/index.json\` to find which task files belong to a specific phase, then read only those.
|
||||
Do not duplicate or contradict context file content in your output.
|
||||
</input_files>`;
|
||||
|
||||
export const ID_GENERATION = `
|
||||
|
||||
Reference in New Issue
Block a user