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 { 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 { join } from 'node:path';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
@@ -116,6 +116,34 @@ describe('writeInputFiles', () => {
|
|||||||
writeInputFiles({ agentWorkdir: testDir });
|
writeInputFiles({ agentWorkdir: testDir });
|
||||||
expect(existsSync(join(testDir, '.cw', 'input'))).toBe(true);
|
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', () => {
|
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
|
// Write manifest listing exactly which files were created
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
join(inputDir, 'manifest.json'),
|
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
|
- \`pages/\` — one per page; frontmatter: title, parentPageId, sortOrder; body: markdown
|
||||||
|
|
||||||
**Context Files** (read-only, read on-demand)
|
**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/phases/\` — frontmatter: id, name, status, dependsOn; body: description
|
||||||
- \`context/tasks/\` — frontmatter: id, name, phaseId, parentTaskId, category, type, priority, status, summary; 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.
|
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.
|
Context files provide awareness of the broader initiative. There may be dozens — do NOT bulk-read them all.
|
||||||
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.
|
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>`;
|
</input_files>`;
|
||||||
|
|
||||||
export const ID_GENERATION = `
|
export const ID_GENERATION = `
|
||||||
|
|||||||
Reference in New Issue
Block a user